X-Git-Url: http://git.marmaro.de/?p=mmh;a=blobdiff_plain;f=mts%2Fsmtp%2Fsmtp.c;h=470d72de9153289ab2f9da775d55e74880c91f86;hp=7af8d30980de2e60066ebc4604e1b5d1cc2214d8;hb=5dd6771b28c257af405d7248639ed0e3bcdce38b;hpb=86048c14a070e3382577dd22519319bce77c70b9 diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index 7af8d30..470d72d 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 @@ -91,12 +94,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 +107,29 @@ 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; +#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 +144,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, ...); @@ -144,7 +162,6 @@ 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 *); @@ -159,20 +176,20 @@ static int sm_auth_sasl(char *, char *, char *); 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) + char *user, int tls) { if (sm_mts == MTS_SMTP) return smtp_init (client, server, port, watch, verbose, - debug, onex, queued, sasl, saslmech, user); + debug, onex, queued, sasl, saslmech, user, tls); else return sendmail_init (client, server, watch, verbose, - debug, onex, queued); + debug, onex, queued, sasl, 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 onex, int queued, + int sasl, char *saslmech, char *user, int tls) { #ifdef CYRUS_SASL char *server_mechs; @@ -196,17 +213,19 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, } } -#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 +246,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 +265,107 @@ 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) { + 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) { + 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); + + if (SSL_connect(ssl) < 1) { + sm_end(NOTOK); + return sm_ierror("Unable to negotiate SSL connection: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + 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; + 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; } } +#endif /* TLS_SUPPORT */ #ifdef CYRUS_SASL /* @@ -300,8 +409,12 @@ send_options: ; int sendmail_init (char *client, char *server, int watch, int verbose, - int debug, int onex, int queued) + int debug, int onex, int queued, + int sasl, char *saslmech, char *user) { +#ifdef CYRUS_SASL + char *server_mechs; +#endif /* CYRUS_SASL */ int i, result, vecp; int pdi[2], pdo[2]; char *vec[15]; @@ -321,10 +434,12 @@ sendmail_init (char *client, char *server, int watch, int verbose, client = LocalName(); /* 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 sasl_inbuffer = malloc(SASL_MAXRECVBUF); @@ -365,7 +480,6 @@ 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"; @@ -373,7 +487,6 @@ sendmail_init (char *client, char *server, int watch, int verbose, if (verbose) vec[vecp++] = "-ov"; # endif /* not RAND */ -#endif /* not ZMAILER */ vec[vecp++] = NULL; setgid (getegid ()); @@ -409,28 +522,53 @@ 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 +#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, saslmech ? saslmech : server_mechs, + server) != RP_OK) { + sm_end(NOTOK); + return NOTOK; + } + } +#endif /* CYRUS_SASL */ + if (onex) smtalk (SM_HELO, "ONEX"); -#endif if (watch) smtalk (SM_HELO, "VERB on"); @@ -455,7 +593,7 @@ rclient (char *server, char *service) int sm_winit (int mode, char *from) { - char *smtpcom; + char *smtpcom = NULL; switch (mode) { case S_MAIL: @@ -473,6 +611,10 @@ sm_winit (int mode, char *from) case S_SAML: smtpcom = "SAML"; break; + + default: + /* Hopefully, we do not get here. */ + break; } switch (smtalk (SM_MAIL, "%s FROM:<%s>", smtpcom, from)) { @@ -643,6 +785,13 @@ sm_end (int type) break; } +#ifdef TLS_SUPPORT + if (tls_active) { + SSL_shutdown(ssl); + SSL_free(ssl); + } +#endif /* TLS_SUPPORT */ + if (sm_rfp != NULL) { alarm (SM_CLOS); fclose (sm_rfp); @@ -754,12 +903,13 @@ 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 : UINT_MAX; result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); @@ -904,20 +1054,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; @@ -1001,10 +1143,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 +1229,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 = SSL_write(ssl, buffer, len); + + if (SSL_get_error(ssl, ret) != SSL_ERROR_NONE) { + 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); @@ -1179,10 +1334,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 +1367,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); } @@ -1353,9 +1508,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 +1535,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) @@ -1422,7 +1599,7 @@ sm_fgetc(FILE *f) return (int) sasl_inbuffer[0]; } -#endif /* CYRUS_SASL */ +#endif /* CYRUS_SASL || TLS_SUPPORT */ static int sm_rerror (int rc)