X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/3bede3fae77775088b8b66e7a26a5e2ee1f61fff..28610ff9a604a75ae0c383be03aa19415ddb1965:/mts/smtp/smtp.c diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index 620dabc5..7177e5d5 100644 --- a/mts/smtp/smtp.c +++ b/mts/smtp/smtp.c @@ -9,17 +9,22 @@ #include #include "smtp.h" #include -#include #include #ifdef CYRUS_SASL #include #include +# if SASL_VERSION_FULL < 0x020125 + /* Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype, + which has an explicit void parameter list, according to best + practice. So we need to cast to avoid compile warnings. + Provide this prototype for earlier versions. */ + typedef int (*sasl_callback_ft)(); +# endif /* SASL_VERSION_FULL < 0x020125 */ #include #include #include #include -#include #endif /* CYRUS_SASL */ #ifdef TLS_SUPPORT @@ -92,7 +97,7 @@ static FILE *sm_wfp = NULL; static sasl_conn_t *conn = NULL; /* SASL connection state */ static int sasl_complete = 0; /* Has authentication succeded? */ static sasl_ssf_t sasl_ssf; /* Our security strength factor */ -static char *sasl_pw_context[2]; /* Context to pass into sm_get_pass */ +static char *sasl_pw_context[3]; /* Context to pass into sm_get_pass */ static int maxoutbuf; /* Maximum crypto output buffer */ static char *sasl_outbuffer; /* SASL output buffer for encryption */ static int sasl_outbuflen; /* Current length of data in outbuf */ @@ -100,12 +105,12 @@ static int sm_get_user(void *, int, const char **, unsigned *); static int sm_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **); static sasl_callback_t callbacks[] = { - { SASL_CB_USER, sm_get_user, NULL }, + { SASL_CB_USER, (sasl_callback_ft) sm_get_user, NULL }, #define SM_SASL_N_CB_USER 0 - { SASL_CB_PASS, sm_get_pass, NULL }, -#define SM_SASL_N_CB_PASS 1 - { SASL_CB_AUTHNAME, sm_get_user, NULL }, -#define SM_SASL_N_CB_AUTHNAME 2 + { SASL_CB_AUTHNAME, (sasl_callback_ft) sm_get_user, NULL }, +#define SM_SASL_N_CB_AUTHNAME 1 + { SASL_CB_PASS, (sasl_callback_ft) sm_get_pass, NULL }, +#define SM_SASL_N_CB_PASS 2 { SASL_CB_LIST_END, NULL, NULL }, }; @@ -119,6 +124,8 @@ static SSL *ssl = NULL; static BIO *sbior = NULL; static BIO *sbiow = NULL; static BIO *io = NULL; + +static int tls_negotiate(void); #endif /* TLS_SUPPORT */ #if defined(CYRUS_SASL) || defined(TLS_SUPPORT) @@ -147,9 +154,9 @@ char *EHLOkeys[MAXEHLO + 1]; * static prototypes */ static int smtp_init (char *, char *, char *, int, int, int, int, int, int, - int, char *, char *, int); + char *, char *, int); static int sendmail_init (char *, char *, int, int, int, int, int, int, - int, char *, char *); + char *, char *); static int rclient (char *, char *); static int sm_ierror (char *fmt, ...); @@ -178,32 +185,33 @@ static int sm_auth_sasl(char *, int, char *, char *); int sm_init (char *client, char *server, char *port, int watch, int verbose, - int debug, int onex, int queued, int sasl, int saslssf, + int debug, int queued, int sasl, int saslssf, char *saslmech, char *user, int tls) { if (sm_mts == MTS_SMTP) return smtp_init (client, server, port, watch, verbose, - debug, onex, queued, sasl, saslssf, saslmech, + debug, queued, sasl, saslssf, saslmech, user, tls); else return sendmail_init (client, server, watch, verbose, - debug, onex, queued, sasl, saslssf, saslmech, + debug, queued, sasl, saslssf, saslmech, user); } static int smtp_init (char *client, char *server, char *port, int watch, int verbose, - int debug, int onex, int queued, + int debug, int queued, int sasl, int saslssf, char *saslmech, char *user, int tls) { + int result, sd1, sd2; #ifdef CYRUS_SASL char *server_mechs; #else /* CYRUS_SASL */ NMH_UNUSED (sasl); + NMH_UNUSED (saslssf); NMH_UNUSED (saslmech); NMH_UNUSED (user); #endif /* CYRUS_SASL */ - int result, sd1, sd2; if (watch) verbose = TRUE; @@ -257,6 +265,25 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, tls_active = 0; +#ifdef TLS_SUPPORT + /* + * If tls == 2, that means that the user requested "initial" TLS, + * which happens right after the connection has opened. Do that + * negotiation now + */ + + if (tls == 2) { + result = tls_negotiate(); + + /* + * Note: if tls_negotiate() fails it will call sm_end() for us, + * which closes the connection. + */ + if (result != RP_OK) + return result; + } +#endif /* TLS_SUPPORT */ + sm_alarmed = 0; alarm (SM_OPEN); result = smhear (); @@ -294,9 +321,7 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, * restart the EHLO dialog after TLS negotiation is complete. */ - if (tls) { - BIO *ssl_bio; - + if (tls == 1) { if (! EHLOset("STARTTLS")) { sm_end(NOTOK); return sm_ierror("SMTP server does not support TLS"); @@ -314,86 +339,10 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, * negotiation. Oblige them. */ - if (! sslctx) { - SSL_METHOD *method; - - SSL_library_init(); - SSL_load_error_strings(); - - method = TLSv1_client_method(); /* Not sure about this */ - - sslctx = SSL_CTX_new(method); - - if (! sslctx) { - sm_end(NOTOK); - return sm_ierror("Unable to initialize OpenSSL context: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - } - - ssl = SSL_new(sslctx); - - if (! ssl) { - sm_end(NOTOK); - return sm_ierror("Unable to create SSL connection: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - sbior = BIO_new_socket(fileno(sm_rfp), BIO_NOCLOSE); - sbiow = BIO_new_socket(fileno(sm_wfp), BIO_NOCLOSE); - - if (sbior == NULL || sbiow == NULL) { - sm_end(NOTOK); - return sm_ierror("Unable to create BIO endpoints: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - SSL_set_bio(ssl, sbior, sbiow); - SSL_set_connect_state(ssl); - - /* - * Set up a BIO to handle buffering for us - */ - - io = BIO_new(BIO_f_buffer()); - - if (! io) { - sm_end(NOTOK); - return sm_ierror("Unable to create a buffer BIO: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - ssl_bio = BIO_new(BIO_f_ssl()); - - if (! ssl_bio) { - sm_end(NOTOK); - return sm_ierror("Unable to create a SSL BIO: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE); - BIO_push(io, ssl_bio); - - /* - * Try doing the handshake now - */ - - if (BIO_do_handshake(io) < 1) { - sm_end(NOTOK); - return sm_ierror("Unable to negotiate SSL connection: %s", - ERR_error_string(ERR_get_error(), NULL)); - } + result = tls_negotiate(); - if (sm_debug) { - SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); - printf("SSL negotiation successful: %s(%d) %s\n", - SSL_CIPHER_get_name(cipher), - SSL_CIPHER_get_bits(cipher, NULL), - SSL_CIPHER_get_version(cipher)); - - } - - tls_active = 1; + if (result != RP_OK) + return result; doingEHLO = 1; result = smtalk (SM_HELO, "EHLO %s", client); @@ -440,8 +389,6 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, send_options: ; if (watch && EHLOset ("XVRB")) smtalk (SM_HELO, "VERB on"); - if (onex && EHLOset ("XONE")) - smtalk (SM_HELO, "ONEX"); if (queued && EHLOset ("XQUE")) smtalk (SM_HELO, "QUED"); @@ -450,9 +397,12 @@ send_options: ; int sendmail_init (char *client, char *server, int watch, int verbose, - int debug, int onex, int queued, + int debug, int queued, int sasl, int saslssf, char *saslmech, char *user) { + unsigned int i, result, vecp; + int pdi[2], pdo[2]; + char *vec[15]; #ifdef CYRUS_SASL char *server_mechs; #else /* CYRUS_SASL */ @@ -462,9 +412,6 @@ sendmail_init (char *client, char *server, int watch, int verbose, NMH_UNUSED (saslmech); NMH_UNUSED (user); #endif /* CYRUS_SASL */ - unsigned int i, result, vecp; - int pdi[2], pdo[2]; - char *vec[15]; if (watch) verbose = TRUE; @@ -530,10 +477,8 @@ sendmail_init (char *client, char *server, int watch, int verbose, vec[vecp++] = watch ? "-odi" : queued ? "-odq" : "-odb"; vec[vecp++] = "-oem"; vec[vecp++] = "-om"; -# ifndef RAND if (verbose) vec[vecp++] = "-ov"; -# endif /* not RAND */ vec[vecp++] = NULL; setgid (getegid ()); @@ -614,8 +559,6 @@ sendmail_init (char *client, char *server, int watch, int verbose, } #endif /* CYRUS_SASL */ - if (onex) - smtalk (SM_HELO, "ONEX"); if (watch) smtalk (SM_HELO, "VERB on"); @@ -638,33 +581,9 @@ rclient (char *server, char *service) } int -sm_winit (int mode, char *from) +sm_winit (char *from) { - char *smtpcom = NULL; - - switch (mode) { - case S_MAIL: - smtpcom = "MAIL"; - break; - - case S_SEND: - smtpcom = "SEND"; - break; - - case S_SOML: - smtpcom = "SOML"; - break; - - case S_SAML: - smtpcom = "SAML"; - break; - - default: - /* Hopefully, we do not get here. */ - break; - } - - switch (smtalk (SM_MAIL, "%s FROM:<%s>", smtpcom, from)) { + switch (smtalk (SM_MAIL, "MAIL FROM:<%s>", from)) { case 250: sm_addrs = 0; return RP_OK; @@ -791,7 +710,7 @@ sm_end (int type) int status; struct smtp sm_note; - if (sm_mts == MTS_SENDMAIL) { + if (sm_mts == MTS_SENDMAIL_SMTP) { switch (sm_child) { case NOTOK: case OK: @@ -820,9 +739,19 @@ sm_end (int type) if (sm_mts == MTS_SMTP) smtalk (SM_QUIT, "QUIT"); else { + /* The SIGPIPE block replaces old calls to discard (). + We're not sure what the discard () calls were for, + maybe to prevent deadlock on old systems. In any + case, blocking SIGPIPE should be harmless. + Because the file handles are closed below, leave it + blocked. */ + sigset_t set, oset; + sigemptyset (&set); + sigaddset (&set, SIGPIPE); + sigprocmask (SIG_BLOCK, &set, &oset); + kill (sm_child, SIGKILL); - discard (sm_rfp); - discard (sm_wfp); + sm_child = NOTOK; } if (type == NOTOK) { sm_reply.code = sm_note.code; @@ -862,9 +791,11 @@ sm_end (int type) if (sasl_inbuffer) free(sasl_inbuffer); #endif /* CYRUS_SASL */ - } else { + } else if (sm_child != NOTOK) { status = pidwait (sm_child, OK); sm_child = NOTOK; + } else { + status = OK; } sm_rfp = sm_wfp = NULL; @@ -887,17 +818,12 @@ sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) sasl_security_properties_t secprops; sasl_ssf_t *ssf; int *outbufmax; + struct nmh_creds creds = { 0, 0, 0 }; /* * Initialize the callback contexts */ - if (user == NULL) - user = getusername(); - - callbacks[SM_SASL_N_CB_USER].context = user; - callbacks[SM_SASL_N_CB_AUTHNAME].context = user; - /* * This is a _bit_ of a hack ... but if the hostname wasn't supplied * to us on the command line, then call getpeername and do a @@ -928,8 +854,15 @@ sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) strncpy(host, inhost, sizeof(host) - 1); } + nmh_get_credentials (host, user, 0, &creds); + + /* It's OK to copy the creds pointers here. The callbacks that + use them will only be called before this function returns. */ + callbacks[SM_SASL_N_CB_USER].context = creds.user; + callbacks[SM_SASL_N_CB_AUTHNAME].context = creds.user; sasl_pw_context[0] = host; - sasl_pw_context[1] = user; + sasl_pw_context[1] = creds.user; + sasl_pw_context[2] = creds.password; callbacks[SM_SASL_N_CB_PASS].context = sasl_pw_context; @@ -956,7 +889,8 @@ sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) memset(&secprops, 0, sizeof(secprops)); secprops.maxbufsize = SASL_MAXRECVBUF; - secprops.max_ssf = tls_active ? 0 : (saslssf != -1 ? saslssf : UINT_MAX); + secprops.max_ssf = + tls_active ? 0 : (saslssf != -1 ? (unsigned int) saslssf : UINT_MAX); result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); @@ -1029,7 +963,6 @@ sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) } else { result = sasl_decode64(sm_reply.text, sm_reply.length, outbuf, sizeof(outbuf), &outlen); - if (result != SASL_OK) { smtalk(SM_AUTH, "*"); sm_ierror("SASL base64 decode failed: %s", @@ -1137,29 +1070,22 @@ static int sm_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret) { - NMH_UNUSED (conn); - char **pw_context = (char **) context; - char *pass = NULL; int len; + NMH_UNUSED (conn); + if (! psecret || id != SASL_CB_PASS) return SASL_BADPARAM; - ruserpass(pw_context[0], &(pw_context[1]), &pass); - - len = strlen(pass); - - *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len); + len = strlen (pw_context[2]); - if (! *psecret) { - free(pass); + if (! (*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len))) { return SASL_NOMEM; } (*psecret)->len = len; - strcpy((char *) (*psecret)->data, pass); -/* free(pass); */ + strcpy((char *) (*psecret)->data, pw_context[2]); return SASL_OK; } @@ -1321,6 +1247,102 @@ sm_fwrite(char *buffer, int len) return ferror(sm_wfp) ? NOTOK : RP_OK; } +#ifdef TLS_SUPPORT +/* + * Negotiate Transport Layer Security + */ + +static int +tls_negotiate(void) +{ + BIO *ssl_bio; + + if (! sslctx) { + const SSL_METHOD *method; + + SSL_library_init(); + SSL_load_error_strings(); + + method = TLSv1_client_method(); /* Not sure about this */ + + /* Older ssl takes a non-const arg. */ + sslctx = SSL_CTX_new((SSL_METHOD *) method); + + if (! sslctx) { + sm_end(NOTOK); + return sm_ierror("Unable to initialize OpenSSL context: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + } + + ssl = SSL_new(sslctx); + + if (! ssl) { + sm_end(NOTOK); + return sm_ierror("Unable to create SSL connection: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + sbior = BIO_new_socket(fileno(sm_rfp), BIO_NOCLOSE); + sbiow = BIO_new_socket(fileno(sm_wfp), BIO_NOCLOSE); + + if (sbior == NULL || sbiow == NULL) { + sm_end(NOTOK); + return sm_ierror("Unable to create BIO endpoints: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + SSL_set_bio(ssl, sbior, sbiow); + SSL_set_connect_state(ssl); + + /* + * Set up a BIO to handle buffering for us + */ + + io = BIO_new(BIO_f_buffer()); + + if (! io) { + sm_end(NOTOK); + return sm_ierror("Unable to create a buffer BIO: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + ssl_bio = BIO_new(BIO_f_ssl()); + + if (! ssl_bio) { + sm_end(NOTOK); + return sm_ierror("Unable to create a SSL BIO: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE); + BIO_push(io, ssl_bio); + + /* + * Try doing the handshake now + */ + + if (BIO_do_handshake(io) < 1) { + sm_end(NOTOK); + return sm_ierror("Unable to negotiate SSL connection: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + if (sm_debug) { + const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); + printf("SSL negotiation successful: %s(%d) %s\n", + SSL_CIPHER_get_name(cipher), + SSL_CIPHER_get_bits(cipher, NULL), + SSL_CIPHER_get_version(cipher)); + + } + + tls_active = 1; + + return RP_OK; +} +#endif /* TLS_SUPPORT */ + /* * Convenience functions to replace occurences of fputs() and fputc() */ @@ -1518,7 +1540,7 @@ sm_rrecord (char *buffer, int *len) buffer[*len = 0] = 0; if ((retval = sm_fgets (buffer, BUFSIZ, sm_rfp)) != RP_OK) - return retval; + return sm_rerror (retval); *len = strlen (buffer); /* *len should be >0 except on EOF, but check for safety's sake */ if (*len == 0)