X-Git-Url: http://git.marmaro.de/?a=blobdiff_plain;f=mts%2Fsmtp%2Fsmtp.c;h=404c463e82f059d418691ba0ac7d102358df5097;hb=572cac455e444c60c4f7803d3e4b69fff89b9af2;hp=7af8d30980de2e60066ebc4604e1b5d1cc2214d8;hpb=86048c14a070e3382577dd22519319bce77c70b9;p=mmh diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index 7af8d30..404c463 100644 --- a/mts/smtp/smtp.c +++ b/mts/smtp/smtp.c @@ -1,8 +1,6 @@ /* * smtp.c -- nmh SMTP interface * - * $Id$ - * * This code is Copyright (c) 2002, by the authors of nmh. See the * COPYRIGHT file in the root directory of the nmh distribution for * complete copyright information. @@ -24,6 +22,11 @@ #include #endif /* CYRUS_SASL */ +#ifdef TLS_SUPPORT +#include +#include +#endif /* TLS_SUPPORT */ + /* * This module implements an interface to SendMail very similar * to the MMDF mm_(3) routines. The sm_() routines herein talk @@ -67,7 +70,9 @@ #define SM_DOT 600 /* see above */ #define SM_QUIT 30 #define SM_CLOS 10 +#ifdef CYRUS_SASL #define SM_AUTH 45 +#endif /* CYRUS_SASL */ static int sm_addrs = 0; static int sm_alarmed = 0; @@ -91,12 +96,8 @@ static char *sasl_pw_context[2]; /* 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 */ -static char *sasl_inbuffer; /* SASL input buffer for encryption */ -static char *sasl_inptr; /* Pointer to current inbuf position */ -static int sasl_inbuflen; /* Current length of data in inbuf */ static int sm_get_user(void *, int, const char **, unsigned *); static int sm_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **); -static int sm_fgetc(FILE *); static sasl_callback_t callbacks[] = { { SASL_CB_USER, sm_get_user, NULL }, @@ -108,11 +109,30 @@ static sasl_callback_t callbacks[] = { { SASL_CB_LIST_END, NULL, NULL }, }; -#define SASL_MAXRECVBUF 65536 #else /* CYRUS_SASL */ -#define sm_fgetc fgetc +int sasl_ssf = 0; #endif /* CYRUS_SASL */ +#ifdef TLS_SUPPORT +static SSL_CTX *sslctx = NULL; +static SSL *ssl = NULL; +static BIO *sbior = NULL; +static BIO *sbiow = NULL; +static BIO *io = NULL; +#endif /* TLS_SUPPORT */ + +#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) +#define SASL_MAXRECVBUF 65536 +static int sm_fgetc(FILE *); +static char *sasl_inbuffer; /* SASL input buffer for encryption */ +static char *sasl_inptr; /* Pointer to current inbuf position */ +static int sasl_inbuflen; /* Current length of data in inbuf */ +#else +#define sm_fgetc fgetc +#endif + +static int tls_active = 0; + static char *sm_noreply = "No reply text given"; static char *sm_moreply = "; "; @@ -127,8 +147,9 @@ char *EHLOkeys[MAXEHLO + 1]; * static prototypes */ static int smtp_init (char *, char *, char *, int, int, int, int, int, int, - char *, char *); -static int sendmail_init (char *, char *, int, int, int, int, int); + char *, char *, int); +static int sendmail_init (char *, char *, int, int, int, int, int, int, + char *, char *); static int rclient (char *, char *); static int sm_ierror (char *fmt, ...); @@ -139,12 +160,11 @@ static int sm_werror (void); static int smhear (void); static int sm_rrecord (char *, int *); static int sm_rerror (int); -static RETSIGTYPE alrmser (int); +static void alrmser (int); static char *EHLOset (char *); static int sm_fwrite(char *, int); static int sm_fputs(char *); static int sm_fputc(int); -static int sm_fgetc(FILE *); static void sm_fflush(void); static int sm_fgets(char *, int, FILE *); @@ -153,31 +173,38 @@ static int sm_fgets(char *, int, FILE *); * Function prototypes needed for SASL */ -static int sm_auth_sasl(char *, char *, char *); +static int sm_auth_sasl(char *, int, char *, char *); #endif /* CYRUS_SASL */ int sm_init (char *client, char *server, char *port, int watch, int verbose, - int debug, int onex, int queued, int sasl, char *saslmech, - char *user) + 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, saslmech, user); + debug, queued, sasl, saslssf, saslmech, + user, tls); else return sendmail_init (client, server, watch, verbose, - debug, onex, queued); + 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 sasl, char *saslmech, - char *user) + 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; @@ -192,21 +219,23 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, if (clientname) { client = clientname; } else { - client = LocalName(); /* no clientname -> LocalName */ + client = LocalName(1); /* no clientname -> LocalName */ } } -#ifdef ZMAILER + /* + * Last-ditch check just in case client still isn't set to anything + */ + if (client == NULL || *client == '\0') client = "localhost"; -#endif -#ifdef CYRUS_SASL +#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) sasl_inbuffer = malloc(SASL_MAXRECVBUF); if (!sasl_inbuffer) return sm_ierror("Unable to allocate %d bytes for read buffer", SASL_MAXRECVBUF); -#endif /* CYRUS_SASL */ +#endif /* CYRUS_SASL || TLS_SUPPORT */ if ((sd1 = rclient (server, port)) == NOTOK) return RP_BHST; @@ -227,6 +256,8 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, return sm_ierror ("unable to fdopen"); } + tls_active = 0; + sm_alarmed = 0; alarm (SM_OPEN); result = smhear (); @@ -244,19 +275,139 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, /* * Give EHLO or HELO command */ - if (client && *client) { + + doingEHLO = 1; + result = smtalk (SM_HELO, "EHLO %s", client); + doingEHLO = 0; + + if (result >= 500 && result <= 599) + result = smtalk (SM_HELO, "HELO %s", client); + + if (result != 250) { + sm_end (NOTOK); + return RP_RPLY; + } + +#ifdef TLS_SUPPORT + /* + * If the user requested TLS support, then try to do the STARTTLS command + * as part of the initial dialog. Assuming this works, we then need to + * restart the EHLO dialog after TLS negotiation is complete. + */ + + if (tls) { + BIO *ssl_bio; + + if (! EHLOset("STARTTLS")) { + sm_end(NOTOK); + return sm_ierror("SMTP server does not support TLS"); + } + + result = smtalk(SM_HELO, "STARTTLS"); + + if (result != 220) { + sm_end(NOTOK); + return RP_RPLY; + } + + /* + * Okay, the other side should be waiting for us to start TLS + * negotiation. Oblige them. + */ + + if (! sslctx) { + const 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)); + } + + 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; + doingEHLO = 1; result = smtalk (SM_HELO, "EHLO %s", client); doingEHLO = 0; - if (result >= 500 && result <= 599) - result = smtalk (SM_HELO, "HELO %s", client); - if (result != 250) { sm_end (NOTOK); return RP_RPLY; } } +#else /* TLS_SUPPORT */ + NMH_UNUSED (tls); +#endif /* TLS_SUPPORT */ #ifdef CYRUS_SASL /* @@ -279,7 +430,7 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, saslmech, server_mechs); } - if (sm_auth_sasl(user, saslmech ? saslmech : server_mechs, + if (sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs, server) != RP_OK) { sm_end(NOTOK); return NOTOK; @@ -290,8 +441,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"); @@ -300,11 +449,21 @@ 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) { - int i, result, vecp; + unsigned int i, result, vecp; int pdi[2], pdo[2]; char *vec[15]; +#ifdef CYRUS_SASL + char *server_mechs; +#else /* CYRUS_SASL */ + NMH_UNUSED (server); + NMH_UNUSED (sasl); + NMH_UNUSED (saslssf); + NMH_UNUSED (saslmech); + NMH_UNUSED (user); +#endif /* CYRUS_SASL */ if (watch) verbose = TRUE; @@ -318,20 +477,22 @@ sendmail_init (char *client, char *server, int watch, int verbose, if (clientname) client = clientname; else - client = LocalName(); /* no clientname -> LocalName */ + client = LocalName(1); /* no clientname -> LocalName */ } -#ifdef ZMAILER + /* + * Last-ditch check just in case client still isn't set to anything + */ + if (client == NULL || *client == '\0') client = "localhost"; -#endif -#ifdef CYRUS_SASL +#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) sasl_inbuffer = malloc(SASL_MAXRECVBUF); if (!sasl_inbuffer) return sm_ierror("Unable to allocate %d bytes for read buffer", SASL_MAXRECVBUF); -#endif /* CYRUS_SASL */ +#endif /* CYRUS_SASL || TLS_SUPPORT */ if (pipe (pdi) == NOTOK) return sm_ierror ("no pipes"); @@ -365,15 +526,11 @@ sendmail_init (char *client, char *server, int watch, int verbose, vecp = 0; vec[vecp++] = r1bindex (sendmail, '/'); vec[vecp++] = "-bs"; -#ifndef ZMAILER vec[vecp++] = watch ? "-odi" : queued ? "-odq" : "-odb"; vec[vecp++] = "-oem"; vec[vecp++] = "-om"; -# ifndef RAND if (verbose) vec[vecp++] = "-ov"; -# endif /* not RAND */ -#endif /* not ZMAILER */ vec[vecp++] = NULL; setgid (getegid ()); @@ -409,28 +566,51 @@ sendmail_init (char *client, char *server, int watch, int verbose, return RP_RPLY; } - if (client && *client) { - doingEHLO = 1; - result = smtalk (SM_HELO, "EHLO %s", client); - doingEHLO = 0; + doingEHLO = 1; + result = smtalk (SM_HELO, "EHLO %s", client); + doingEHLO = 0; - if (500 <= result && result <= 599) - result = smtalk (SM_HELO, "HELO %s", client); + if (500 <= result && result <= 599) + result = smtalk (SM_HELO, "HELO %s", client); - switch (result) { - case 250: - break; + switch (result) { + case 250: + break; - default: - sm_end (NOTOK); - return RP_RPLY; - } + default: + sm_end (NOTOK); + return RP_RPLY; } -#ifndef ZMAILER - if (onex) - smtalk (SM_HELO, "ONEX"); -#endif +#ifdef CYRUS_SASL + /* + * If the user asked for SASL, then check to see if the SMTP server + * supports it. Otherwise, error out (because the SMTP server + * might have been spoofed; we don't want to just silently not + * do authentication + */ + + if (sasl) { + if (! (server_mechs = EHLOset("AUTH"))) { + sm_end(NOTOK); + return sm_ierror("SMTP server does not support SASL"); + } + + if (saslmech && stringdex(saslmech, server_mechs) == -1) { + sm_end(NOTOK); + return sm_ierror("Requested SASL mech \"%s\" is not in the " + "list of supported mechanisms:\n%s", + saslmech, server_mechs); + } + + if (sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs, + server) != RP_OK) { + sm_end(NOTOK); + return NOTOK; + } + } +#endif /* CYRUS_SASL */ + if (watch) smtalk (SM_HELO, "VERB on"); @@ -453,29 +633,9 @@ rclient (char *server, char *service) } int -sm_winit (int mode, char *from) +sm_winit (char *from) { - char *smtpcom; - - 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; - } - - 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; @@ -602,7 +762,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: @@ -643,6 +803,13 @@ sm_end (int type) break; } +#ifdef TLS_SUPPORT + if (tls_active) { + BIO_ssl_shutdown(io); + BIO_free_all(io); + } +#endif /* TLS_SUPPORT */ + if (sm_rfp != NULL) { alarm (SM_CLOS); fclose (sm_rfp); @@ -682,7 +849,7 @@ sm_end (int type) * (optionally) negotiated a security layer. */ static int -sm_auth_sasl(char *user, char *mechlist, char *inhost) +sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) { int result, status; unsigned int buflen, outlen; @@ -754,12 +921,14 @@ sm_auth_sasl(char *user, char *mechlist, char *inhost) } /* - * Initialize the security properties + * Initialize the security properties. But if TLS is active, then + * don't negotiate encryption here. */ memset(&secprops, 0, sizeof(secprops)); secprops.maxbufsize = SASL_MAXRECVBUF; - secprops.max_ssf = UINT_MAX; + secprops.max_ssf = + tls_active ? 0 : (saslssf != -1 ? (unsigned int) saslssf : UINT_MAX); result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); @@ -904,20 +1073,12 @@ sm_auth_sasl(char *user, char *mechlist, char *inhost) return NOTOK; } sasl_outbuflen = 0; - - sasl_inbuffer = malloc(SASL_MAXRECVBUF); - - if (sasl_inbuffer == NULL) { - sm_ierror("Unable to allocate %d bytes for SASL input " - "buffer", SASL_MAXRECVBUF); - free(sasl_outbuffer); - return NOTOK; - } sasl_inbuflen = 0; sasl_inptr = sasl_inbuffer; } else { sasl_outbuffer = NULL; - sasl_inbuffer = NULL; + /* Don't NULL out sasl_inbuffer because it could be used in + sm_fgetc (). */ } sasl_complete = 1; @@ -952,6 +1113,8 @@ sm_get_pass(sasl_conn_t *conn, void *context, int id, char *pass = NULL; int len; + NMH_UNUSED (conn); + if (! psecret || id != SASL_CB_PASS) return SASL_BADPARAM; @@ -1001,10 +1164,10 @@ smtalk (int time, char *fmt, ...) va_end(ap); if (sm_debug) { -#ifdef CYRUS_SASL if (sasl_ssf) - printf("(encrypted) "); -#endif /* CYRUS_SASL */ + printf("(sasl-encrypted) "); + if (tls_active) + printf("(tls-encrypted) "); printf ("=> %s\n", buffer); fflush (stdout); } @@ -1087,11 +1250,24 @@ sm_fwrite(char *buffer, int len) const char *output; unsigned int outputlen; - if (sasl_complete == 0 || sasl_ssf == 0) + if (sasl_complete == 0 || sasl_ssf == 0) { #endif /* CYRUS_SASL */ +#ifdef TLS_SUPPORT + if (tls_active) { + int ret; + + ret = BIO_write(io, buffer, len); + + if (ret <= 0) { + sm_ierror("TLS error during write: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NOTOK; + } + } else +#endif /* TLS_SUPPORT */ fwrite(buffer, sizeof(*buffer), len, sm_wfp); #ifdef CYRUS_SASL - else { + } else { while (len >= maxoutbuf - sasl_outbuflen) { memcpy(sasl_outbuffer + sasl_outbuflen, buffer, maxoutbuf - sasl_outbuflen); @@ -1161,6 +1337,12 @@ sm_fflush(void) } #endif /* CYRUS_SASL */ +#ifdef TLS_SUPPORT + if (tls_active) { + (void) BIO_flush(io); + } +#endif /* TLS_SUPPORT */ + fflush(sm_wfp); } @@ -1179,10 +1361,10 @@ sm_werror (void) static int smhear (void) { - int i, code, cont, bc, rc, more; + int i, code, cont, bc = 0, rc, more; unsigned char *bp; char *rp; - char **ehlo, buffer[BUFSIZ]; + char **ehlo = NULL, buffer[BUFSIZ]; if (doingEHLO) { static int at_least_once = 0; @@ -1212,10 +1394,10 @@ again: ; for (more = FALSE; sm_rrecord ((char *) (bp = (unsigned char *) buffer), &bc) != NOTOK ; ) { if (sm_debug) { -#ifdef CYRUS_SASL if (sasl_ssf > 0) - printf("(decrypted) "); -#endif /* CYRUS_SASL */ + printf("(sasl-decrypted) "); + if (tls_active) + printf("(tls-decrypted) "); printf ("<= %s\n", buffer); fflush (stdout); } @@ -1308,7 +1490,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) @@ -1353,9 +1535,9 @@ sm_fgets(char *buffer, int size, FILE *f) } -#ifdef CYRUS_SASL +#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) /* - * Read from the network, but do SASL encryption + * Read from the network, but do SASL or TLS encryption */ static int @@ -1380,6 +1562,28 @@ sm_fgetc(FILE *f) while (retbufsize == 0) { +#ifdef TLS_SUPPORT + if (tls_active) { + cc = SSL_read(ssl, tmpbuf, sizeof(tmpbuf)); + + if (cc == 0) { + result = SSL_get_error(ssl, cc); + + if (result != SSL_ERROR_ZERO_RETURN) { + sm_ierror("TLS peer aborted connection"); + } + + return EOF; + } + + if (cc < 0) { + sm_ierror("SSL_read failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + return -2; + } + } else +#endif /* TLS_SUPPORT */ + cc = read(fileno(f), tmpbuf, sizeof(tmpbuf)); if (cc == 0) @@ -1395,6 +1599,7 @@ sm_fgetc(FILE *f) * encryption working */ +#ifdef CYRUS_SASL if (sasl_complete == 0 || sasl_ssf == 0) { retbuf = tmpbuf; retbufsize = cc; @@ -1408,6 +1613,10 @@ sm_fgetc(FILE *f) return -2; } } +#else /* ! CYRUS_SASL */ + retbuf = tmpbuf; + retbufsize = cc; +#endif /* CYRUS_SASL */ } if (retbufsize > SASL_MAXRECVBUF) { @@ -1422,7 +1631,7 @@ sm_fgetc(FILE *f) return (int) sasl_inbuffer[0]; } -#endif /* CYRUS_SASL */ +#endif /* CYRUS_SASL || TLS_SUPPORT */ static int sm_rerror (int rc) @@ -1444,9 +1653,11 @@ sm_rerror (int rc) } -static RETSIGTYPE +static void alrmser (int i) { + NMH_UNUSED (i); + #ifndef RELIABLE_SIGNALS SIGNAL (SIGALRM, alrmser); #endif