int auth_spa_server(auth_instance *ablock, uschar *data) { auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block); uint8x lmRespData[24]; uint8x ntRespData[24]; SPAAuthRequest request; SPAAuthChallenge challenge; SPAAuthResponse response; SPAAuthResponse *responseptr = &response; uschar msgbuf[2048]; uschar *clearpass; /* send a 334, MS Exchange style, and grab the client's request, unless we already have it via an initial response. */ if ((*data == '\0') && (auth_get_no64_data(&data, US"NTLM supported") != OK)) { /* something borked */ return FAIL; } if (spa_base64_to_bits((char *)(&request), sizeof(request), (const char *)(data)) < 0) { DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in " "request: %s\n", data); return FAIL; } /* create a challenge and send it back */ spa_build_auth_challenge(&request,&challenge); spa_bits_to_base64 (msgbuf, (unsigned char*)&challenge, spa_request_length(&challenge)); if (auth_get_no64_data(&data, msgbuf) != OK) { /* something borked */ return FAIL; } /* dump client response */ if (spa_base64_to_bits((char *)(&response), sizeof(response), (const char *)(data)) < 0) { DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in " "response: %s\n", data); return FAIL; } /*************************************************************** PH 07-Aug-2003: The original code here was this: Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) + IVAL(&responseptr->uUser.offset,0), SVAL(&responseptr->uUser.len,0)/2) ); However, if the response data is too long, unicodeToString bombs out on an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good idea. It's too messy to try to rework that function to return an error because it is called from a number of other places in the auth-spa.c module. Instead, since it is a very small function, I reproduce its code here, with a size check that causes failure if the size of msgbuf is exceeded. ****/ { int i; char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0); int len = SVAL(&responseptr->uUser.len,0)/2; if (len + 1 >= sizeof(msgbuf)) return FAIL; for (i = 0; i < len; ++i) { msgbuf[i] = *p & 0x7f; p += 2; } msgbuf[i] = 0; } /***************************************************************/ /* Put the username in $auth1 and $1. The former is now the preferred variable; the latter is the original variable. These have to be out of stack memory, and need to be available once known even if not authenticated, for error messages (server_set_id, which only makes it to authenticated_id if we return OK) */ auth_vars[0] = expand_nstring[1] = string_copy(msgbuf); expand_nlength[1] = Ustrlen(msgbuf); expand_nmax = 1; debug_print_string(ablock->server_debug_string); /* customized debug */ /* look up password */ clearpass = expand_string(ob->spa_serverpassword); if (clearpass == NULL) { if (expand_string_forcedfail) { DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while " "expanding spa_serverpassword\n"); return FAIL; } else { DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding " "spa_serverpassword: %s\n", expand_string_message); return DEFER; } } /* create local hash copy */ spa_smb_encrypt (clearpass, challenge.challengeData, lmRespData); spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData); /* compare NT hash (LM may not be available) */ if (memcmp(ntRespData, ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0), 24) == 0) /* success. we have a winner. */ { return auth_check_serv_cond(ablock); } /* Expand server_condition as an authorization check (PH) */ return FAIL; }
int auth_dovecot_server(auth_instance * ablock, uschar * data) { auth_dovecot_options_block *ob = (auth_dovecot_options_block *) ablock->options_block; struct sockaddr_un sa; uschar buffer[DOVECOT_AUTH_MAXLINELEN]; uschar *args[DOVECOT_AUTH_MAXFIELDCOUNT]; uschar *auth_command; uschar *auth_extra_data = US""; uschar *p; int nargs, tmp; int crequid = 1, cont = 1, fd = -1, ret = DEFER; BOOL found = FALSE, have_mech_line = FALSE; HDEBUG(D_auth) debug_printf("dovecot authentication\n"); if (!data) { ret = FAIL; goto out; } memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; /* This was the original code here: it is nonsense because strncpy() does not return an integer. I have converted this to use the function that formats and checks length. PH */ /* if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) { } */ if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s", ob->server_socket)) { auth_defer_msg = US"authentication socket path too long"; return DEFER; } auth_defer_msg = US"authentication socket connection error"; if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) return DEFER; if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) goto out; auth_defer_msg = US"authentication socket protocol error"; socket_buffer_left = 0; /* Global, used to read more than a line but return by line */ while (cont) { if (dc_gets(buffer, sizeof(buffer), fd) == NULL) OUT("authentication socket read error or premature eof"); p = buffer + Ustrlen(buffer) - 1; if (*p != '\n') OUT("authentication socket protocol line too long"); *p = '\0'; HDEBUG(D_auth) debug_printf("received: %s\n", buffer); nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0])); /* HDEBUG(D_auth) debug_strcut(args, nargs, sizeof(args) / sizeof(args[0])); */ /* Code below rewritten by Kirill Miazine ([email protected]). Only check commands that Exim will need. Original code also failed if Dovecot server sent unknown command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */ /* pdp: note that CUID is a per-connection identifier sent by the server, which increments at server discretion. By contrast, the "id" field of the protocol is a connection-specific request identifier, which needs to be unique per request from the client and is not connected to the CUID value, so we ignore CUID from server. It's purely for diagnostics. */ if (Ustrcmp(args[0], US"VERSION") == 0) { CHECK_COMMAND("VERSION", 2, 2); if (Uatoi(args[1]) != VERSION_MAJOR) OUT("authentication socket protocol version mismatch"); } else if (Ustrcmp(args[0], US"MECH") == 0) { CHECK_COMMAND("MECH", 1, INT_MAX); have_mech_line = TRUE; if (strcmpic(US args[1], ablock->public_name) == 0) found = TRUE; } else if (Ustrcmp(args[0], US"SPID") == 0) { /* Unfortunately the auth protocol handshake wasn't designed well to differentiate between auth-client/userdb/master. auth-userdb and auth-master send VERSION + SPID lines only and nothing afterwards, while auth-client sends VERSION + MECH + SPID + CUID + more. The simplest way that we can determine if we've connected to the correct socket is to see if MECH line exists or not (alternatively we'd have to have a small timeout after SPID to see if CUID is sent or not). */ if (!have_mech_line) OUT("authentication socket type mismatch" " (connected to auth-master instead of auth-client)"); } else if (Ustrcmp(args[0], US"DONE") == 0) { CHECK_COMMAND("DONE", 0, 0); cont = 0; } } if (!found) { auth_defer_msg = string_sprintf( "Dovecot did not advertise mechanism \"%s\" to us", ablock->public_name); goto out; } /* Added by PH: data must not contain tab (as it is b64 it shouldn't, but check for safety). */ if (Ustrchr(data, '\t') != NULL) { ret = FAIL; goto out; } /* Added by PH: extra fields when TLS is in use or if the TCP/IP connection is local. */ if (tls_in.cipher != NULL) auth_extra_data = string_sprintf("secured\t%s%s", tls_in.certificate_verified? "valid-client-cert" : "", tls_in.certificate_verified? "\t" : ""); else if ( interface_address != NULL && Ustrcmp(sender_host_address, interface_address) == 0) auth_extra_data = US"secured\t"; /**************************************************************************** The code below was the original code here. It didn't work. A reading of the file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that this was not right. Maybe something changed. I changed it to move the service indication into the AUTH command, and it seems to be better. PH fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n" "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n", VERSION_MAJOR, VERSION_MINOR, getpid(), cuid, ablock->public_name, sender_host_address, interface_address, data ? CS data : ""); Subsequently, the command was modified to add "secured" and "valid-client- cert" when relevant. ****************************************************************************/ auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n" "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n", VERSION_MAJOR, VERSION_MINOR, getpid(), crequid, ablock->public_name, auth_extra_data, sender_host_address, interface_address, data); if (write(fd, auth_command, Ustrlen(auth_command)) < 0) HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n", strerror(errno)); HDEBUG(D_auth) debug_printf("sent: %s", auth_command); while (1) { uschar *temp; uschar *auth_id_pre = NULL; int i; if (dc_gets(buffer, sizeof(buffer), fd) == NULL) { auth_defer_msg = US"authentication socket read error or premature eof"; goto out; } buffer[Ustrlen(buffer) - 1] = 0; HDEBUG(D_auth) debug_printf("received: %s\n", buffer); nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0])); if (Uatoi(args[1]) != crequid) OUT("authentication socket connection id mismatch"); switch (toupper(*args[0])) { case 'C': CHECK_COMMAND("CONT", 1, 2); if ((tmp = auth_get_no64_data(&data, US args[2])) != OK) { ret = tmp; goto out; } /* Added by PH: data must not contain tab (as it is b64 it shouldn't, but check for safety). */ if (Ustrchr(data, '\t') != NULL) { ret = FAIL; goto out; } temp = string_sprintf("CONT\t%d\t%s\n", crequid, data); if (write(fd, temp, Ustrlen(temp)) < 0) OUT("authentication socket write error"); break; case 'F': CHECK_COMMAND("FAIL", 1, -1); for (i=2; (i<nargs) && (auth_id_pre == NULL); i++) { if ( Ustrncmp(args[i], US"user="******"OK", 2, -1); /* Search for the "user=$USER" string in the args array and return the proper value. */ for (i=2; (i<nargs) && (auth_id_pre == NULL); i++) { if ( Ustrncmp(args[i], US"user="******"authentication socket protocol error, username missing"); ret = OK; /* fallthrough */ default: goto out; } } out: /* close the socket used by dovecot */ if (fd >= 0) close(fd); /* Expand server_condition as an authorization check */ return ret == OK ? auth_check_serv_cond(ablock) : ret; }
int auth_dovecot_server(auth_instance *ablock, uschar *data) { auth_dovecot_options_block *ob = (auth_dovecot_options_block *)(ablock->options_block); struct sockaddr_un sa; uschar buffer[4096]; uschar *args[8]; uschar *auth_command; uschar *auth_extra_data = US""; int nargs, tmp; int cuid = 0, cont = 1, found = 0, fd, ret = DEFER; HDEBUG(D_auth) debug_printf("dovecot authentication\n"); memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; /* This was the original code here: it is nonsense because strncpy() does not return an integer. I have converted this to use the function that formats and checks length. PH */ /* if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) { */ if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s", ob->server_socket)) { auth_defer_msg = US"authentication socket path too long"; return DEFER; } auth_defer_msg = US"authentication socket connection error"; fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) return DEFER; if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) goto out; auth_defer_msg = US"authentication socket protocol error"; sbp = 0; /* Socket buffer pointer */ while (cont) { if (dc_gets(buffer, sizeof(buffer), fd) == NULL) OUT("authentication socket read error or premature eof"); buffer[Ustrlen(buffer) - 1] = 0; HDEBUG(D_auth) debug_printf("received: %s\n", buffer); nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0])); /* Code below rewritten by Kirill Miazine ([email protected]). Only check commands that Exim will need. Original code also failed if Dovecot server sent unknown command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */ if (Ustrcmp(args[0], US"CUID") == 0) { CHECK_COMMAND("CUID", 1, 1); cuid = Uatoi(args[1]); } else if (Ustrcmp(args[0], US"VERSION") == 0) { CHECK_COMMAND("VERSION", 2, 2); if (Uatoi(args[1]) != VERSION_MAJOR) OUT("authentication socket protocol version mismatch"); } else if (Ustrcmp(args[0], US"MECH") == 0) { CHECK_COMMAND("MECH", 1, INT_MAX); if (strcmpic(US args[1], ablock->public_name) == 0) found = 1; } else if (Ustrcmp(args[0], US"DONE") == 0) { CHECK_COMMAND("DONE", 0, 0); cont = 0; } } if (!found) goto out; /* Added by PH: data must not contain tab (as it is b64 it shouldn't, but check for safety). */ if (Ustrchr(data, '\t') != NULL) { ret = FAIL; goto out; } /* Added by PH: extra fields when TLS is in use or if the TCP/IP connection is local. */ if (tls_cipher != NULL) auth_extra_data = string_sprintf("secured\t%s%s", tls_certificate_verified? "valid-client-cert" : "", tls_certificate_verified? "\t" : ""); else if (interface_address != NULL && Ustrcmp(sender_host_address, interface_address) == 0) auth_extra_data = US"secured\t"; /**************************************************************************** The code below was the original code here. It didn't work. A reading of the file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that this was not right. Maybe something changed. I changed it to move the service indication into the AUTH command, and it seems to be better. PH fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n" "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n", VERSION_MAJOR, VERSION_MINOR, getpid(), cuid, ablock->public_name, sender_host_address, interface_address, data ? (char *) data : ""); Subsequently, the command was modified to add "secured" and "valid-client- cert" when relevant. The auth protocol is documented here: http://wiki.dovecot.org/Authentication_Protocol ****************************************************************************/ auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n" "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n", VERSION_MAJOR, VERSION_MINOR, getpid(), cuid, ablock->public_name, auth_extra_data, sender_host_address, interface_address, data ? (char *) data : ""); if (write(fd, auth_command, Ustrlen(auth_command)) < 0) HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n", strerror(errno)); HDEBUG(D_auth) debug_printf("sent: %s", auth_command); while (1) { uschar *temp; uschar *auth_id_pre = NULL; int i; if (dc_gets(buffer, sizeof(buffer), fd) == NULL) { auth_defer_msg = US"authentication socket read error or premature eof"; goto out; } buffer[Ustrlen(buffer) - 1] = 0; HDEBUG(D_auth) debug_printf("received: %s\n", buffer); nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0])); if (Uatoi(args[1]) != cuid) OUT("authentication socket connection id mismatch"); switch (toupper(*args[0])) { case 'C': CHECK_COMMAND("CONT", 1, 2); tmp = auth_get_no64_data(&data, US args[2]); if (tmp != OK) { ret = tmp; goto out; } /* Added by PH: data must not contain tab (as it is b64 it shouldn't, but check for safety). */ if (Ustrchr(data, '\t') != NULL) { ret = FAIL; goto out; } temp = string_sprintf("CONT\t%d\t%s\n", cuid, data); if (write(fd, temp, Ustrlen(temp)) < 0) OUT("authentication socket write error"); break; case 'F': CHECK_COMMAND("FAIL", 1, -1); for (i=2; (i<nargs) && (auth_id_pre == NULL); i++) { if ( Ustrncmp(args[i], US"user="******"OK", 2, -1); /* * Search for the "user=$USER" string in the args array * and return the proper value. */ for (i=2; (i<nargs) && (auth_id_pre == NULL); i++) { if ( Ustrncmp(args[i], US"user="******"authentication socket protocol error, username missing"); ret = OK; /* fallthrough */ default: goto out; } } out: /* close the socket used by dovecot */ if (fd >= 0) close(fd); /* Expand server_condition as an authorization check */ return (ret == OK)? auth_check_serv_cond(ablock) : ret; }