X-Git-Url: http://git.marmaro.de/?p=mmh;a=blobdiff_plain;f=mts%2Fsmtp%2Fsmtp.c;h=470d72de9153289ab2f9da775d55e74880c91f86;hp=1ab027f31f731da2d0178ea9fcc574786103c186;hb=5dd6771b28c257af405d7248639ed0e3bcdce38b;hpb=13f84dd50ca5754391dbd3296a5c7425f9363600 diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index 1ab027f..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. @@ -13,9 +11,6 @@ #include #include #include -#ifdef MPOP -#include -#endif #ifdef CYRUS_SASL #include @@ -27,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 @@ -82,11 +82,6 @@ static int sm_verbose = 0; static FILE *sm_rfp = NULL; static FILE *sm_wfp = NULL; -#ifdef MPOP -static int sm_ispool = 0; -static char sm_tmpfil[BUFSIZ]; -#endif /* MPOP */ - #ifdef CYRUS_SASL /* * Some globals needed by SASL @@ -97,6 +92,8 @@ 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 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 int sm_get_user(void *, int, const char **, unsigned *); static int sm_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **); @@ -105,10 +102,34 @@ static sasl_callback_t callbacks[] = { #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_LIST_END, NULL, NULL }, }; + +#else /* CYRUS_SASL */ +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 = "; "; @@ -122,11 +143,12 @@ char *EHLOkeys[MAXEHLO + 1]; /* * static prototypes */ -static int smtp_init (char *, char *, int, int, int, int, int, int, - char *, char *); -static int sendmail_init (char *, char *, int, int, int, int, int); +static int smtp_init (char *, char *, char *, int, 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 *, char *); +static int rclient (char *, char *); static int sm_ierror (char *fmt, ...); static int smtalk (int time, char *fmt, ...); static int sm_wrecord (char *, int); @@ -134,18 +156,14 @@ static int sm_wstream (char *, int); static int sm_werror (void); static int smhear (void); static int sm_rrecord (char *, int *); -static int sm_rerror (void); +static int sm_rerror (int); static RETSIGTYPE alrmser (int); static char *EHLOset (char *); - -#ifdef MPOP -/* - * smtp.c's own static copy of several nmh library subroutines - */ -static char **smail_brkstring (char *, char *, char *); -static int smail_brkany (char, char *); -char **smail_copyip (char **, char **, int); -#endif +static int sm_fwrite(char *, int); +static int sm_fputs(char *); +static int sm_fputc(int); +static void sm_fflush(void); +static int sm_fgets(char *, int, FILE *); #ifdef CYRUS_SASL /* @@ -155,26 +173,23 @@ char **smail_copyip (char **, char **, int); static int sm_auth_sasl(char *, char *, char *); #endif /* CYRUS_SASL */ -/* from mts/generic/client.c */ -int client (char *, char *, char *, int, char *, int); - int -sm_init (char *client, char *server, int watch, int verbose, +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, watch, verbose, - debug, onex, queued, sasl, saslmech, user); + return smtp_init (client, server, port, watch, verbose, + 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, int watch, int verbose, - int debug, int onex, int queued, int sasl, char *saslmech, - char *user) +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 tls) { #ifdef CYRUS_SASL char *server_mechs; @@ -187,11 +202,6 @@ smtp_init (char *client, char *server, int watch, int verbose, sm_verbose = verbose; sm_debug = debug; -#ifdef MPOP - if (sm_ispool) - goto all_done; -#endif - if (sm_rfp != NULL && sm_wfp != NULL) goto send_options; @@ -203,32 +213,22 @@ smtp_init (char *client, char *server, 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 - if ((sd1 = rclient (server, "tcp", "smtp")) == NOTOK) - return RP_BHST; +#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 || TLS_SUPPORT */ -#ifdef MPOP - if (sm_ispool) { - if (sm_rfp) { - alarm (SM_CLOS); - fclose (sm_rfp); - alarm (0); - sm_rfp = NULL; - } - if ((sm_wfp = fdopen (sd1, "w")) == NULL) { - unlink (sm_tmpfil); - close (sd1); - return sm_ierror ("unable to fdopen"); - } -all_done: ; - sm_reply.text[sm_reply.length = 0] = NULL; - return (sm_reply.code = RP_OK); - } -#endif /* MPOP */ + if ((sd1 = rclient (server, port)) == NOTOK) + return RP_BHST; if ((sd2 = dup (sd1)) == NOTOK) { close (sd1); @@ -246,6 +246,8 @@ all_done: ; return sm_ierror ("unable to fdopen"); } + tls_active = 0; + sm_alarmed = 0; alarm (SM_OPEN); result = smhear (); @@ -263,19 +265,107 @@ all_done: ; /* * 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 /* @@ -319,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]; @@ -340,10 +434,19 @@ 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); + if (!sasl_inbuffer) + return sm_ierror("Unable to allocate %d bytes for read buffer", + SASL_MAXRECVBUF); +#endif /* CYRUS_SASL */ if (pipe (pdi) == NOTOK) return sm_ierror ("no pipes"); @@ -377,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"; @@ -385,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 ()); @@ -421,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"); @@ -450,54 +576,16 @@ sendmail_init (char *client, char *server, int watch, int verbose, } } -#ifdef MPOP -# define MAXARGS 1000 -#endif /* MPOP */ - static int -rclient (char *server, char *protocol, char *service) +rclient (char *server, char *service) { int sd; char response[BUFSIZ]; -#ifdef MPOP - char *cp; -#endif /* MPOP */ - if ((sd = client (server, protocol, service, FALSE, response, sizeof(response))) != NOTOK) + if ((sd = client (server, service, response, sizeof(response), + sm_debug)) != NOTOK) return sd; -#ifdef MPOP - if (!server && servers && (cp = strchr(servers, '/'))) { - char **ap; - char *arguments[MAXARGS]; - - smail_copyip (smail_brkstring (cp = getcpy (servers), " ", "\n"), arguments, MAXARGS); - - for (ap = arguments; *ap; ap++) - if (**ap == '/') { - char *dp; - - if ((dp = strrchr(*ap, '/')) && *++dp == NULL) - *--dp = NULL; - snprintf (sm_tmpfil, sizeof(sm_tmpfil), "%s/smtpXXXXXX", *ap); -#ifdef HAVE_MKSTEMP - sd = mkstemp (sm_tmpfil); -#else - mktemp (sm_tmpfil); - - if ((sd = creat (sm_tmpfil, 0600)) != NOTOK) { - sm_ispool = 1; - break; - } -#endif - } - - free (cp); - if (sd != NOTOK) - return sd; - } -#endif /* MPOP */ - sm_ierror ("%s", response); return NOTOK; } @@ -505,15 +593,7 @@ rclient (char *server, char *protocol, char *service) int sm_winit (int mode, char *from) { - char *smtpcom; - -#ifdef MPOP - if (sm_ispool && !sm_wfp) { - strlen (strcpy (sm_reply.text, "unable to create new spool file")); - sm_reply.code = NOTOK; - return RP_BHST; - } -#endif /* MPOP */ + char *smtpcom = NULL; switch (mode) { case S_MAIL: @@ -531,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)) { @@ -681,7 +765,8 @@ sm_end (int type) case NOTOK: sm_note.code = sm_reply.code; - strncpy (sm_note.text, sm_reply.text, sm_note.length = sm_reply.length);/* fall */ + sm_note.length = sm_reply.length; + memcpy (sm_note.text, sm_reply.text, sm_reply.length + 1);/* fall */ case DONE: if (smtalk (SM_RSET, "RSET") == 250 && type == DONE) return RP_OK; @@ -694,22 +779,18 @@ sm_end (int type) } if (type == NOTOK) { sm_reply.code = sm_note.code; - strncpy (sm_reply.text, sm_note.text, sm_reply.length = sm_note.length); + sm_reply.length = sm_note.length; + memcpy (sm_reply.text, sm_note.text, sm_note.length + 1); } break; } -#ifdef MPOP - if (sm_ispool) { - sm_ispool = 0; - - if (sm_wfp) { - unlink (sm_tmpfil); - fclose (sm_wfp); - sm_wfp = NULL; - } +#ifdef TLS_SUPPORT + if (tls_active) { + SSL_shutdown(ssl); + SSL_free(ssl); } -#endif /* MPOP */ +#endif /* TLS_SUPPORT */ if (sm_rfp != NULL) { alarm (SM_CLOS); @@ -725,8 +806,14 @@ sm_end (int type) if (sm_mts == MTS_SMTP) { status = 0; #ifdef CYRUS_SASL - if (conn) + if (conn) { sasl_dispose(&conn); + if (sasl_outbuffer) { + free(sasl_outbuffer); + } + } + if (sasl_inbuffer) + free(sasl_inbuffer); #endif /* CYRUS_SASL */ } else { status = pidwait (sm_child, OK); @@ -737,372 +824,18 @@ sm_end (int type) return (status ? RP_BHST : RP_OK); } - -#ifdef MPOP - -int -sm_bulk (char *file) -{ - int cc, i, j, k, result; - long pos; - char *dp, *bp, *cp, s; - char buffer[BUFSIZ], sender[BUFSIZ]; - FILE *fp, *gp; - - gp = NULL; - k = strlen (file) - sizeof(".bulk"); - if ((fp = fopen (file, "r")) == NULL) { - int len; - - snprintf (sm_reply.text, sizeof(sm_reply.text), - "unable to read %s: ", file); - bp = sm_reply.text; - len = strlen (bp); - bp += len; - if ((s = strerror (errno))) - strncpy (bp, s, sizeof(sm_reply.text) - len); - else - snprintf (bp, sizeof(sm_reply.text) - len, "Error %d", errno); - sm_reply.length = strlen (sm_reply.text); - sm_reply.code = NOTOK; - return RP_BHST; - } - if (sm_debug) { - printf ("reading file %s\n", file); - fflush (stdout); - } - - i = j = 0; - while (fgets (buffer, sizeof(buffer), fp)) { - if (j++ == 0) - strncpy (sender, buffer + sizeof("MAIL FROM:") - 1, sizeof(sender)); - if (strcmp (buffer, "DATA\r\n") == 0) { - i = 1; - break; - } - } - if (i == 0) { - if (sm_debug) { - printf ("no DATA...\n"); - fflush (stdout); - } -losing0: - snprintf (buffer, sizeof(buffer), "%s.bad", file); - rename (file, buffer); - if (gp) { - snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); - unlink (buffer); - fclose (gp); - } - fclose (fp); - return RP_OK; - } - if (j < 3) { - if (sm_debug) { - printf ("no %srecipients...\n", j < 1 ? "sender or " : ""); - fflush (stdout); - } - goto losing0; - } - - if ((cp = malloc ((size_t) (cc = (pos = ftell (fp)) + 1))) == NULL) { - sm_reply.length = strlen (strcpy (sm_reply.text, "out of memory")); -losing1: ; - sm_reply.code = NOTOK; - fclose (fp); - return RP_BHST; - } - fseek (fp, 0L, SEEK_SET); - for (dp = cp, i = 0; i++ < j; dp += strlen (dp)) - if (fgets (dp, cc - (dp - cp), fp) == NULL) { - sm_reply.length = strlen (strcpy (sm_reply.text, "premature eof")); -losing2: - free (cp); - goto losing1; - } - *dp = NULL; - - for (dp = cp, i = cc - 1; i > 0; dp += cc, i -= cc) - if ((cc = write (fileno (sm_wfp), dp, i)) == NOTOK) { - int len; -losing3: - strcpy (sm_reply.text, "error writing to server: ", - sizeof(sm_reply.text)); - bp = sm_reply.text; - len = strlen (bp); - bp += len; - if ((s = strerror (errno))) - strncpy (bp, s, sizeof(sm_reply.text) - len); - else - snprintf (bp, sizeof(sm_reply.text) - len, - "unknown error %d", errno); - sm_reply.length = strlen (sm_reply.text); - goto losing2; - } - else - if (sm_debug) { - printf ("wrote %d octets to server\n", cc); - fflush (stdout); - } - - for (dp = cp, i = 0; i++ < j; dp = strchr(dp, '\n'), dp++) { - if (sm_debug) { - if (bp = strchr(dp, '\r')) - *bp = NULL; - printf ("=> %s\n", dp); - fflush (stdout); - if (bp) - *bp = '\r'; - } - - switch (smhear () + (i == 1 ? 1000 : i != j ? 2000 : 3000)) { - case 1000 + 250: - sm_addrs = 0; - result = RP_OK; - break; - - case 1000 + 500: - case 1000 + 501: - case 1000 + 552: - case 2000 + 500: - case 2000 + 501: - result = RP_PARM; - smtalk (SM_RSET, "RSET"); - free (cp); - goto losing0; - - case 2000 + 250: - case 2000 + 251: - sm_addrs++; - result = RP_OK; - break; - - case 2000 + 451: -#ifdef SENDMAILBUG - sm_addrs++; - result = RP_OK; - break; -#endif - case 2000 + 421: - case 2000 + 450: - case 2000 + 452: - result = RP_NO; - goto bad_addr; - - case 2000 + 550: - case 2000 + 551: - case 2000 + 552: - case 2000 + 553: - result = RP_USER; -bad_addr: - if (k <= 0 || strcmp (sender, "<>\r\n") == 0) - break; - if (gp == NULL) { - int l; - snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); - if ((gp = fopen (buffer, "w+")) == NULL) - goto bad_data; - fprintf (gp, "MAIL FROM:<>\r\nRCPT TO:%sDATA\r\n", sender); - l = strlen (sender); - fprintf (gp, - "To: %*.*s\r\nSubject: Invalid addresses (%s)\r\n", - l - 4, l - 4, sender + 1, file); - fprintf (gp, "Date: %s\r\nFrom: Postmaster@%s\r\n\r\n", - dtimenow (0), LocalName ()); - } - if (bp = strchr(dp, '\r')) - *bp = NULL; - fprintf (gp, "=> %s\r\n", dp); - if (bp) - *bp = '\r'; - fprintf (gp, "<= %s\r\n", rp_string (result)); - fflush (gp); - break; - - case 3000 + 354: -#ifdef SENDMAILBUG -ok_data: -#endif - result = RP_OK; - break; - - case 3000 + 451: -#ifdef SENDMAILBUG - goto ok_data; -#endif - case 3000 + 421: - result = RP_NO; -bad_data: - smtalk (SM_RSET, "RSET"); - free (cp); - if (gp) { - snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); - unlink (buffer); - fclose (gp); - } - fclose (fp); - return result; - - case 3000 + 500: - case 3000 + 501: - case 3000 + 503: - case 3000 + 554: - smtalk (SM_RSET, "RSET"); - free (cp); - goto no_dice; - - default: - result = RP_RPLY; - goto bad_data; - } - } - free (cp); - - { -#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE - struct stat st; - - if (fstat (fileno (sm_wfp), &st) == NOTOK || (cc = st.st_blksize) < BUFSIZ) - cc = BUFSIZ; -#else - cc = BUFSIZ; -#endif - if ((cp = malloc ((size_t) cc)) == NULL) { - smtalk (SM_RSET, "RSET"); - sm_reply.length = strlen (strcpy (sm_reply.text, "out of memory")); - goto losing1; - } - } - - fseek (fp, pos, SEEK_SET); - for (;;) { - int eof = 0; - - for (dp = cp, i = cc; i > 0; dp += j, i -= j) - if ((j = fread (cp, sizeof(*cp), i, fp)) == OK) { - if (ferror (fp)) { - int len; - - snprintf (sm_reply.text, sizeof(sm_reply.text), - "error reading %s: ", file); - bp = sm_reply.text; - len = strlen (bp); - bp += len; - if ((s = strerror (errno))) - strncpy (bp, s, sizeof(sm_reply.text) - len); - else - snprintf (bp, sizeof(sm_reply.text) - len, - "unknown error %d", errno); - sm_reply.length = strlen (sm_reply.text); - goto losing2; - } - cc = dp - cp; - eof = 1; - break; - } - - for (dp = cp, i = cc; i > 0; dp += j, i -= j) - if ((j = write (fileno (sm_wfp), dp, i)) == NOTOK) - goto losing3; - else - if (sm_debug) { - printf ("wrote %d octets to server\n", j); - fflush (stdout); - } - - if (eof) - break; - } - free (cp); - - switch (smhear ()) { - case 250: - case 251: -#ifdef SENDMAILBUG -ok_dot: -#endif - result = RP_OK; - unlink (file); - break; - - case 451: -#ifdef SENDMAILBUG - goto ok_dot; -#endif - case 452: - default: - result = RP_NO; - if (gp) { - snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); - unlink (buffer); - fclose (gp); - gp = NULL; - } - break; - - case 552: - case 554: -no_dice: - result = RP_NDEL; - if (k <= 0 || strcmp (sender, "<>\r\n") == 0) { - unlink (file); - break; - } - if (gp) { - fflush (gp); - ftruncate (fileno (gp), 0L); - fseek (gp, 0L, SEEK_SET); - } - else { - snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); - if ((gp = fopen (buffer, "w")) == NULL) - break; - } - fprintf (gp, "MAIL FROM:<>\r\nRCPT TO:%sDATA\r\n", sender); - i = strlen (sender); - fprintf (gp, "To: %*.*s\r\nSubject: Failed mail (%s)\r\n", - i - 4, i - 4, sender + 1, file); - fprintf (gp, "Date: %s\r\nFrom: Postmaster@%s\r\n\r\n", - dtimenow (0), LocalName ()); - break; - } - - if (gp) { - fputs ("\r\n------- Begin Returned message\r\n\r\n", gp); - fseek (fp, pos, SEEK_SET); - while (fgets (buffer, sizeof(buffer), fp)) { - if (buffer[0] == '-') - fputs ("- ", gp); - if (strcmp (buffer, ".\r\n")) - fputs (buffer, gp); - } - fputs ("\r\n------- End Returned Message\r\n\r\n.\r\n", gp); - fflush (gp); - if (!ferror (gp)) - unlink (file); - fclose (gp); - } - fclose (fp); - - return result; -} -#endif /* MPOP */ - - #ifdef CYRUS_SASL /* * This function implements SASL authentication for SMTP. If this function * completes successfully, then authentication is successful and we've * (optionally) negotiated a security layer. - * - * Right now we don't support session encryption. */ static int -sm_auth_sasl(char *user, char *mechlist, char *host) +sm_auth_sasl(char *user, char *mechlist, char *inhost) { int result, status; unsigned int buflen, outlen; - char *buf, outbuf[BUFSIZ]; + char *buf, outbuf[BUFSIZ], host[NI_MAXHOST]; const char *chosen_mech; sasl_security_properties_t secprops; sasl_ssf_t *ssf; @@ -1116,6 +849,7 @@ sm_auth_sasl(char *user, char *mechlist, char *host) 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 @@ -1123,10 +857,12 @@ sm_auth_sasl(char *user, char *mechlist, char *host) * reverse-address lookup on the IP address to get the name. */ - if (!host) { - struct sockaddr_in sin; - int len = sizeof(sin); - struct hostent *hp; + memset(host, 0, sizeof(host)); + + if (!inhost) { + struct sockaddr_storage sin; + socklen_t len = sizeof(sin); + int result; if (getpeername(fileno(sm_wfp), (struct sockaddr *) &sin, &len) < 0) { sm_ierror("getpeername on SMTP socket failed: %s", @@ -1134,14 +870,15 @@ sm_auth_sasl(char *user, char *mechlist, char *host) return NOTOK; } - if ((hp = gethostbyaddr((void *) &sin.sin_addr, sizeof(sin.sin_addr), - sin.sin_family)) == NULL) { - sm_ierror("DNS lookup on IP address %s failed", - inet_ntoa(sin.sin_addr)); + result = getnameinfo((struct sockaddr *) &sin, len, host, sizeof(host), + NULL, 0, NI_NAMEREQD); + if (result != 0) { + sm_ierror("Unable to look up name of connected host: %s", + gai_strerror(result)); return NOTOK; } - - host = strdup(hp->h_name); + } else { + strncpy(host, inhost, sizeof(host) - 1); } sasl_pw_context[0] = host; @@ -1166,12 +903,13 @@ sm_auth_sasl(char *user, char *mechlist, char *host) } /* - * 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 = BUFSIZ; - secprops.max_ssf = 0; /* XXX change this when we do encryption */ + secprops.maxbufsize = SASL_MAXRECVBUF; + secprops.max_ssf = tls_active ? 0 : UINT_MAX; result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); @@ -1190,8 +928,7 @@ sm_auth_sasl(char *user, char *mechlist, char *host) &buflen, (const char **) &chosen_mech); if (result != SASL_OK && result != SASL_CONTINUE) { - sm_ierror("SASL client start failed: %s", - sasl_errstring(result, NULL, NULL)); + sm_ierror("SASL client start failed: %s", sasl_errdetail(conn)); return NOTOK; } @@ -1308,8 +1045,22 @@ sm_auth_sasl(char *user, char *mechlist, char *host) return NOTOK; } - if (maxoutbuf == 0 || maxoutbuf > BUFSIZ) - maxoutbuf = BUFSIZ; + if (sasl_ssf > 0) { + sasl_outbuffer = malloc(maxoutbuf); + + if (sasl_outbuffer == NULL) { + sm_ierror("Unable to allocate %d bytes for SASL output " + "buffer", maxoutbuf); + return NOTOK; + } + sasl_outbuflen = 0; + sasl_inbuflen = 0; + sasl_inptr = sasl_inbuffer; + } else { + sasl_outbuffer = NULL; + /* Don't NULL out sasl_inbuffer because it could be used in + sm_fgetc (). */ + } sasl_complete = 1; @@ -1325,7 +1076,7 @@ sm_get_user(void *context, int id, const char **result, unsigned *len) { char *user = (char *) context; - if (! result || id != SASL_CB_USER) + if (! result || ((id != SASL_CB_USER) && (id != SASL_CB_AUTHNAME))) return SASL_BADPARAM; *result = user; @@ -1380,7 +1131,6 @@ sm_ierror (char *fmt, ...) return RP_BHST; } - static int smtalk (int time, char *fmt, ...) { @@ -1393,84 +1143,14 @@ smtalk (int time, char *fmt, ...) va_end(ap); if (sm_debug) { + if (sasl_ssf) + printf("(sasl-encrypted) "); + if (tls_active) + printf("(tls-encrypted) "); printf ("=> %s\n", buffer); fflush (stdout); } -#ifdef MPOP - if (sm_ispool) { - char file[BUFSIZ]; - - if (strcmp (buffer, ".") == 0) - time = SM_DOT; - fprintf (sm_wfp, "%s\r\n", buffer); - switch (time) { - case SM_DOT: - fflush (sm_wfp); - if (ferror (sm_wfp)) - return sm_werror (); - snprintf (file, sizeof(file), "%s%c.bulk", sm_tmpfil, - (char) (sm_ispool + 'a' - 1)); - if (rename (sm_tmpfil, file) == NOTOK) { - int len; - char *bp; - - snprintf (sm_reply.text, sizeof(sm_reply.text), - "error renaming %s to %s: ", sm_tmpfil, file); - bp = sm_reply.text; - len = strlen (bp); - bp += len; - if ((s = strerror (errno))) - strncpy (bp, s, sizeof(sm_reply.text) - len); - else - snprintf (bp, sizeof(sm_reply.text) - len, - "unknown error %d", errno); - sm_reply.length = strlen (sm_reply.text); - sm_reply.code = NOTOK; - return RP_BHST; - } - fclose (sm_wfp); - if (sm_wfp = fopen (sm_tmpfil, "w")) - chmod (sm_tmpfil, 0600); - sm_ispool++; - /* and fall... */ - - case SM_MAIL: - case SM_RCPT: - result = 250; - break; - - case SM_RSET: - fflush (sm_wfp); - ftruncate (fileno (sm_wfp), 0L); - fseek (sm_wfp, 0L, SEEK_SET); - result = 250; - break; - - case SM_DATA: - result = 354; - break; - - case SM_QUIT: - unlink (sm_tmpfil); - sm_ispool = 0; - result = 221; - break; - - default: - result = 500; - break; - } - if (sm_debug) { - printf ("<= %d\n", result); - fflush (stdout); - } - - sm_reply.text[sm_reply.length = 0] = NULL; - return (sm_reply.code = result); - } -#endif /* MPOP */ - sm_alarmed = 0; alarm ((unsigned) time); if ((result = sm_wrecord (buffer, strlen (buffer))) != NOTOK) @@ -1491,9 +1171,9 @@ sm_wrecord (char *buffer, int len) if (sm_wfp == NULL) return sm_werror (); - fwrite (buffer, sizeof(*buffer), len, sm_wfp); - fputs ("\r\n", sm_wfp); - fflush (sm_wfp); + sm_fwrite (buffer, len); + sm_fputs ("\r\n"); + sm_fflush (); return (ferror (sm_wfp) ? sm_werror () : OK); } @@ -1510,7 +1190,7 @@ sm_wstream (char *buffer, int len) if (buffer == NULL && len == 0) { if (lc != '\n') - fputs ("\r\n", sm_wfp); + sm_fputs ("\r\n"); lc = '\0'; return (ferror (sm_wfp) ? sm_werror () : OK); } @@ -1519,16 +1199,16 @@ sm_wstream (char *buffer, int len) switch (*bp) { case '\n': sm_nl = TRUE; - fputc ('\r', sm_wfp); + sm_fputc ('\r'); break; case '.': if (sm_nl) - fputc ('.', sm_wfp);/* FALL THROUGH */ + sm_fputc ('.');/* FALL THROUGH */ default: sm_nl = FALSE; } - fputc (*bp, sm_wfp); + sm_fputc (*bp); if (ferror (sm_wfp)) return sm_werror (); } @@ -1538,18 +1218,106 @@ sm_wstream (char *buffer, int len) return (ferror (sm_wfp) ? sm_werror () : OK); } +/* + * Write out to the network, but do buffering for SASL (if enabled) + */ + +static int +sm_fwrite(char *buffer, int len) +{ +#ifdef CYRUS_SASL + const char *output; + unsigned int outputlen; + + 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 { + while (len >= maxoutbuf - sasl_outbuflen) { + memcpy(sasl_outbuffer + sasl_outbuflen, buffer, + maxoutbuf - sasl_outbuflen); + len -= maxoutbuf - sasl_outbuflen; + sasl_outbuflen = 0; + + if (sasl_encode(conn, sasl_outbuffer, maxoutbuf, + &output, &outputlen) != SASL_OK) { + sm_ierror("Unable to SASL encode connection data: %s", + sasl_errdetail(conn)); + return NOTOK; + } + + fwrite(output, sizeof(*output), outputlen, sm_wfp); + } + + if (len > 0) { + memcpy(sasl_outbuffer + sasl_outbuflen, buffer, len); + sasl_outbuflen += len; + } + } +#endif /* CYRUS_SASL */ + return ferror(sm_wfp) ? NOTOK : RP_OK; +} /* - * On some systems, strlen and strcpy are defined as preprocessor macros. This - * causes compile problems with the #ifdef MPOP in the middle. Should the - * #ifdef MPOP be removed, remove these #undefs. + * Convenience functions to replace occurences of fputs() and fputc() */ -#ifdef strlen -# undef strlen -#endif -#ifdef strcpy -# undef strcpy -#endif + +static int +sm_fputs(char *buffer) +{ + return sm_fwrite(buffer, strlen(buffer)); +} + +static int +sm_fputc(int c) +{ + char h = c; + + return sm_fwrite(&h, 1); +} + +/* + * Flush out any pending data on the connection + */ + +static void +sm_fflush(void) +{ +#ifdef CYRUS_SASL + const char *output; + unsigned int outputlen; + int result; + + if (sasl_complete == 1 && sasl_ssf > 0 && sasl_outbuflen > 0) { + result = sasl_encode(conn, sasl_outbuffer, sasl_outbuflen, + &output, &outputlen); + if (result != SASL_OK) { + sm_ierror("Unable to SASL encode connection data: %s", + sasl_errdetail(conn)); + return; + } + + fwrite(output, sizeof(*output), outputlen, sm_wfp); + sasl_outbuflen = 0; + } +#endif /* CYRUS_SASL */ + + fflush(sm_wfp); +} static int sm_werror (void) @@ -1557,9 +1325,6 @@ sm_werror (void) sm_reply.length = strlen (strcpy (sm_reply.text, sm_wfp == NULL ? "no socket opened" : sm_alarmed ? "write to socket timed out" -#ifdef MPOP - : sm_ispool ? "error writing to spool file" -#endif : "error writing to socket")); return (sm_reply.code = NOTOK); @@ -1569,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; @@ -1599,8 +1364,13 @@ again: ; rp = sm_reply.text; rc = sizeof(sm_reply.text) - 1; - for (more = FALSE; sm_rrecord (bp = buffer, &bc) != NOTOK;) { + for (more = FALSE; sm_rrecord ((char *) (bp = (unsigned char *) buffer), + &bc) != NOTOK ; ) { if (sm_debug) { + if (sasl_ssf > 0) + printf("(sasl-decrypted) "); + if (tls_active) + printf("(tls-decrypted) "); printf ("<= %s\n", buffer); fflush (stdout); } @@ -1627,7 +1397,7 @@ again: ; continue; cont = FALSE; - code = atoi (bp); + code = atoi ((char *) bp); bp += 3, bc -= 3; for (; bc > 0 && isspace (*bp); bp++, bc--) continue; @@ -1646,19 +1416,22 @@ again: ; sm_reply.code = code; more = cont; if (bc <= 0) { + /* can never fail to 0-terminate because of size of buffer vs fixed string */ strncpy (buffer, sm_noreply, sizeof(buffer)); - bp = buffer; + bp = (unsigned char *) buffer; bc = strlen (sm_noreply); } } if ((i = min (bc, rc)) > 0) { - strncpy (rp, bp, i); + memcpy (rp, bp, i); rp += i; rc -= i; - if (more && rc > strlen (sm_moreply) + 1) { - strncpy (sm_reply.text + rc, sm_moreply, sizeof(sm_reply.text) - rc); - rc += strlen (sm_moreply); + i = strlen(sm_moreply); + if (more && rc > i + 1) { + memcpy (rp, sm_moreply, i); /* safe because of check in if() */ + rp += i; + rc -= i; } } if (more) @@ -1672,6 +1445,7 @@ again: ; } sm_reply.length = rp - sm_reply.text; + sm_reply.text[sm_reply.length] = 0; return sm_reply.code; } return NOTOK; @@ -1681,41 +1455,166 @@ again: ; static int sm_rrecord (char *buffer, int *len) { + int retval; + if (sm_rfp == NULL) - return sm_rerror (); + return sm_rerror(0); buffer[*len = 0] = 0; - fgets (buffer, BUFSIZ, sm_rfp); + if ((retval = sm_fgets (buffer, BUFSIZ, sm_rfp)) != RP_OK) + return retval; *len = strlen (buffer); - if (ferror (sm_rfp) || feof (sm_rfp)) - return sm_rerror (); + /* *len should be >0 except on EOF, but check for safety's sake */ + if (*len == 0) + return sm_rerror (RP_EOF); if (buffer[*len - 1] != '\n') - while (getc (sm_rfp) != '\n' && !ferror (sm_rfp) && !feof (sm_rfp)) + while ((retval = sm_fgetc (sm_rfp)) != '\n' && retval != EOF && + retval != -2) continue; else - if (buffer[*len - 2] == '\r') + if ((*len > 1) && (buffer[*len - 2] == '\r')) *len -= 1; - buffer[*len - 1] = 0; + *len -= 1; + buffer[*len] = 0; return OK; } +/* + * Our version of fgets, which calls our private fgetc function + */ static int -sm_rerror (void) +sm_fgets(char *buffer, int size, FILE *f) +{ + int c; + + do { + c = sm_fgetc(f); + + if (c == EOF) + return RP_EOF; + + if (c == -2) + return NOTOK; + + *buffer++ = c; + } while (size > 1 && c != '\n'); + + *buffer = '\0'; + + return RP_OK; +} + + +#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) +/* + * Read from the network, but do SASL or TLS encryption + */ + +static int +sm_fgetc(FILE *f) +{ + char tmpbuf[BUFSIZ], *retbuf; + unsigned int retbufsize = 0; + int cc, result; + + /* + * If we have leftover data, return it + */ + + if (sasl_inbuflen) { + sasl_inbuflen--; + return (int) *sasl_inptr++; + } + + /* + * If not, read from the network until we have some data to return + */ + + 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) + return EOF; + + if (cc < 0) { + sm_ierror("Unable to read from network: %s", strerror(errno)); + return -2; + } + + /* + * Don't call sasl_decode unless sasl is complete and we have + * encryption working + */ + + if (sasl_complete == 0 || sasl_ssf == 0) { + retbuf = tmpbuf; + retbufsize = cc; + } else { + result = sasl_decode(conn, tmpbuf, cc, (const char **) &retbuf, + &retbufsize); + + if (result != SASL_OK) { + sm_ierror("Unable to decode SASL network data: %s", + sasl_errdetail(conn)); + return -2; + } + } + } + + if (retbufsize > SASL_MAXRECVBUF) { + sm_ierror("Received data (%d bytes) is larger than the buffer " + "size (%d bytes)", retbufsize, SASL_MAXRECVBUF); + return -2; + } + + memcpy(sasl_inbuffer, retbuf, retbufsize); + sasl_inptr = sasl_inbuffer + 1; + sasl_inbuflen = retbufsize - 1; + + return (int) sasl_inbuffer[0]; +} +#endif /* CYRUS_SASL || TLS_SUPPORT */ + +static int +sm_rerror (int rc) { if (sm_mts == MTS_SMTP) sm_reply.length = strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no socket opened" : sm_alarmed ? "read from socket timed out" - : feof (sm_rfp) ? "premature end-of-file on socket" + : rc == RP_EOF ? "premature end-of-file on socket" : "error reading from socket")); else sm_reply.length = strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no pipe opened" : sm_alarmed ? "read from pipe timed out" - : feof (sm_rfp) ? "premature end-of-file on pipe" + : rc == RP_EOF ? "premature end-of-file on pipe" : "error reading from pipe")); return (sm_reply.code = NOTOK); @@ -1788,70 +1687,6 @@ rp_string (int code) return buffer; } - -#ifdef MPOP - -static char *broken[MAXARGS + 1]; - -static char ** -smail_brkstring (char *strg, char *brksep, char *brkterm) -{ - int bi; - char c, *sp; - - sp = strg; - - for (bi = 0; bi < MAXARGS; bi++) { - while (smail_brkany (c = *sp, brksep)) - *sp++ = 0; - if (!c || smail_brkany (c, brkterm)) { - *sp = 0; - broken[bi] = 0; - return broken; - } - - broken[bi] = sp; - while ((c = *++sp) && !smail_brkany (c, brksep) && !smail_brkany (c, brkterm)) - continue; - } - broken[MAXARGS] = 0; - - return broken; -} - - -/* - * returns 1 if chr in strg, 0 otherwise - */ -static int -smail_brkany (char chr, char *strg) -{ - char *sp; - - if (strg) - for (sp = strg; *sp; sp++) - if (chr == *sp) - return 1; - return 0; -} - -/* - * copy a string array and return pointer to end - */ -char ** -smail_copyip (char **p, char **q, int len_q) -{ - while (*p && --len_q > 0) - *q++ = *p++; - - *q = NULL; - - return q; -} - -#endif /* MPOP */ - - static char * EHLOset (char *s) {