Port fixes from trunk (SASL fixes and mishandling of reply string buffer)
[mmh] / mts / smtp / smtp.c
index ac6b32e..35eefb4 100644 (file)
@@ -2,17 +2,31 @@
  * 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.
  */
 
 #include <h/mh.h>
 #include "smtp.h"
-#include <zotnet/mts/mts.h>
+#include <h/mts.h>
 #include <signal.h>
 #include <h/signals.h>
 #ifdef MPOP
 #include <errno.h>
 #endif
 
+#ifdef CYRUS_SASL
+#include <sasl/sasl.h>
+#include <sasl/saslutil.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#endif /* CYRUS_SASL */
+
 /*
  * This module implements an interface to SendMail very similar
  * to the MMDF mm_(3) routines.  The sm_() routines herein talk
 /*
  * these codes must all be different!
  */
-#define        SM_OPEN  90      /* Changed from 30 in case of nameserver flakiness */
+#define        SM_OPEN  300      /* Changed to 5 minutes to comply with a SHOULD in RFC 1123 */
 #define        SM_HELO  20
 #define        SM_RSET  15
-#define        SM_MAIL  40
-#define        SM_RCPT 120
-#define        SM_DATA  20
-#define        SM_TEXT 150
-#define        SM_DOT  180
+#define        SM_MAIL  301      /* changed to 5 minutes and a second (for uniqueness), see above */
+#define        SM_RCPT  302      /* see above */
+#define        SM_DATA  120      /* see above */
+#define        SM_TEXT 180     /* see above */
+#define        SM_DOT  600     /* see above */
 #define        SM_QUIT  30
 #define        SM_CLOS  10
+#define        SM_AUTH  45
 
 static int sm_addrs = 0;
 static int sm_alarmed = 0;
@@ -72,6 +87,30 @@ static int sm_ispool = 0;
 static char sm_tmpfil[BUFSIZ];
 #endif /* MPOP */
 
+#ifdef CYRUS_SASL
+/*
+ * Some globals needed by SASL
+ */
+
+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 int maxoutbuf;                  /* Maximum crypto output buffer */
+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 },
+#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 },
+};
+#endif /* CYRUS_SASL */
+
 static char *sm_noreply = "No reply text given";
 static char *sm_moreply = "; ";
 
@@ -85,7 +124,8 @@ char *EHLOkeys[MAXEHLO + 1];
 /*
  * static prototypes
  */
-static int smtp_init (char *, char *, int, int, int, int, int);
+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 rclient (char *, char *, char *);
@@ -101,6 +141,7 @@ static RETSIGTYPE alrmser (int);
 static char *EHLOset (char *);
 
 #ifdef MPOP
+static int sm_perror (char *fmt, ...);
 /*
  * smtp.c's own static copy of several nmh library subroutines
  */
@@ -109,16 +150,25 @@ static int smail_brkany (char, char *);
 char **smail_copyip (char **, char **, int);
 #endif
 
-/* from zotnet/mts/client.c */
+#ifdef CYRUS_SASL
+/*
+ * Function prototypes needed for SASL
+ */
+
+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,
-         int debug, int onex, int queued)
+         int debug, int onex, int queued, int sasl, char *saslmech,
+         char *user)
 {
     if (sm_mts == MTS_SMTP)
        return smtp_init (client, server, watch, verbose,
-                         debug, onex, queued);
+                         debug, onex, queued, sasl, saslmech, user);
     else
        return sendmail_init (client, server, watch, verbose,
                              debug, onex, queued);
@@ -126,8 +176,12 @@ sm_init (char *client, char *server, int watch, int verbose,
 
 static int
 smtp_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 result, sd1, sd2;
 
     if (watch)
@@ -226,6 +280,35 @@ all_done: ;
        }
     }
 
+#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 */
+
 send_options: ;
     if (watch && EHLOset ("XVRB"))
        smtalk (SM_HELO, "VERB on");
@@ -422,15 +505,6 @@ rclient (char *server, char *protocol, char *service)
     return NOTOK;
 }
 
-#ifdef CYRUS_SASL
-#include <sasl.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-#include <errno.h>
-#endif /* CYRUS_SASL */
-
 int
 sm_winit (int mode, char *from)
 {
@@ -438,7 +512,7 @@ sm_winit (int mode, char *from)
 
 #ifdef MPOP
     if (sm_ispool && !sm_wfp) {
-       strlen (strcpy (sm_reply.text, "unable to create new spool file"));
+       sm_reply.length = strlen (strcpy (sm_reply.text, "unable to create new spool file"));
        sm_reply.code = NOTOK;
        return RP_BHST;
     }
@@ -610,7 +684,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;
@@ -623,7 +698,8 @@ 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;
     }
@@ -651,9 +727,13 @@ sm_end (int type)
        alarm (0);
     }
 
-    if (sm_mts == MTS_SMTP)
+    if (sm_mts == MTS_SMTP) {
        status = 0;
-    else {
+#ifdef CYRUS_SASL
+       if (conn)
+           sasl_dispose(&conn);
+#endif /* CYRUS_SASL */
+    } else {
        status = pidwait (sm_child, OK);
        sm_child = NOTOK;
     }
@@ -677,20 +757,7 @@ sm_bulk (char *file)
     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;
+       return sm_perror("unable to read %s: ", file);
     }
     if (sm_debug) {
        printf ("reading file %s\n", file);
@@ -751,17 +818,7 @@ losing2:
        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);
+           sm_perror("error writing to server: ");
            goto losing2;
        }
        else
@@ -884,7 +941,7 @@ bad_data:
     free (cp);
 
     {
-#ifdef HAVE_ST_BLKSIZE
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
        struct stat st;
 
        if (fstat (fileno (sm_wfp), &st) == NOTOK || (cc = st.st_blksize) < BUFSIZ)
@@ -906,19 +963,7 @@ bad_data:
        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);
+                   sm_perror("error reading %s: ", file);
                    goto losing2;
                }
                cc = dp - cp;
@@ -1025,12 +1070,11 @@ no_dice:
 static int
 sm_auth_sasl(char *user, char *mechlist, char *host)
 {
-    int result, status, outlen;
-    unsigned int buflen;
+    int result, status;
+    unsigned int buflen, outlen;
     char *buf, outbuf[BUFSIZ];
     const char *chosen_mech;
     sasl_security_properties_t secprops;
-    sasl_external_properties_t extprops;
     sasl_ssf_t *ssf;
     int *outbufmax;
 
@@ -1042,6 +1086,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
@@ -1083,7 +1128,7 @@ sm_auth_sasl(char *user, char *mechlist, char *host)
        return NOTOK;
     }
 
-    result = sasl_client_new("smtp", host, NULL, SASL_SECURITY_LAYER, &conn);
+    result = sasl_client_new("smtp", host, NULL, NULL, NULL, 0, &conn);
 
     if (result != SASL_OK) {
        sm_ierror("SASL client initialization failed: %s",
@@ -1098,7 +1143,6 @@ sm_auth_sasl(char *user, char *mechlist, char *host)
     memset(&secprops, 0, sizeof(secprops));
     secprops.maxbufsize = BUFSIZ;
     secprops.max_ssf = 0;      /* XXX change this when we do encryption */
-    memset(&extprops, 0, sizeof(extprops));
 
     result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
 
@@ -1108,21 +1152,13 @@ sm_auth_sasl(char *user, char *mechlist, char *host)
        return NOTOK;
     }
 
-    result = sasl_setprop(conn, SASL_SSF_EXTERNAL, &extprops);
-
-    if (result != SASL_OK) {
-       sm_ierror("SASL external property initialization failed: %s",
-                 sasl_errstring(result, NULL, NULL));
-       return NOTOK;
-    }
-
     /*
      * Start the actual protocol.  Feed the mech list into the library
      * and get out a possible initial challenge
      */
 
-    result = sasl_client_start(conn, mechlist, NULL, NULL, &buf, &buflen,
-                              &chosen_mech);
+    result = sasl_client_start(conn, mechlist, NULL, (const char **) &buf,
+                              &buflen, (const char **) &chosen_mech);
 
     if (result != SASL_OK && result != SASL_CONTINUE) {
        sm_ierror("SASL client start failed: %s",
@@ -1137,7 +1173,6 @@ sm_auth_sasl(char *user, char *mechlist, char *host)
 
     if (buflen) {
        status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
-       free(buf);
        if (status != SASL_OK) {
            sm_ierror("SASL base64 encode failed: %s",
                      sasl_errstring(status, NULL, NULL));
@@ -1180,7 +1215,7 @@ sm_auth_sasl(char *user, char *mechlist, char *host)
            outlen = 0;
        } else {
            result = sasl_decode64(sm_reply.text, sm_reply.length,
-                                  outbuf, &outlen);
+                                  outbuf, sizeof(outbuf), &outlen);
        
            if (result != SASL_OK) {
                smtalk(SM_AUTH, "*");
@@ -1190,7 +1225,8 @@ sm_auth_sasl(char *user, char *mechlist, char *host)
            }
        }
 
-       result = sasl_client_step(conn, outbuf, outlen, NULL, &buf, &buflen);
+       result = sasl_client_step(conn, outbuf, outlen, NULL,
+                                 (const char **) &buf, &buflen);
 
        if (result != SASL_OK && result != SASL_CONTINUE) {
            smtalk(SM_AUTH, "*");
@@ -1200,7 +1236,6 @@ sm_auth_sasl(char *user, char *mechlist, char *host)
        }
 
        status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
-       free(buf);
 
        if (status != SASL_OK) {
            smtalk(SM_AUTH, "*");
@@ -1220,24 +1255,11 @@ sm_auth_sasl(char *user, char *mechlist, char *host)
        return RP_BHST;
 
     /*
-     * Depending on the mechanism, we need to do a FINAL call to
-     * sasl_client_step().  Do that now.
-     */
-
-    result = sasl_client_step(conn, NULL, 0, NULL, &buf, &buflen);
-
-    if (result != SASL_OK) {
-       sm_ierror("SASL final client negotiation failed: %s",
-                 sasl_errstring(result, NULL, NULL));
-       return NOTOK;
-    }
-
-    /*
      * We _should_ have completed the authentication successfully.
      * Get a few properties from the authentication exchange.
      */
 
-    result = sasl_getprop(conn, SASL_MAXOUTBUF, (void **) &outbufmax);
+    result = sasl_getprop(conn, SASL_MAXOUTBUF, (const void **) &outbufmax);
 
     if (result != SASL_OK) {
        sm_ierror("Cannot retrieve SASL negotiated output buffer size: %s",
@@ -1247,7 +1269,7 @@ sm_auth_sasl(char *user, char *mechlist, char *host)
 
     maxoutbuf = *outbufmax;
 
-    result = sasl_getprop(conn, SASL_SSF, (void **) &ssf);
+    result = sasl_getprop(conn, SASL_SSF, (const void **) &ssf);
 
     sasl_ssf = *ssf;
 
@@ -1274,7 +1296,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;
@@ -1307,7 +1329,7 @@ sm_get_pass(sasl_conn_t *conn, void *context, int id,
     }
 
     (*psecret)->len = len;
-    strcpy((*psecret)->data, pass);
+    strcpy((char *) (*psecret)->data, pass);
 /*    free(pass); */
 
     return SASL_OK;
@@ -1329,6 +1351,36 @@ sm_ierror (char *fmt, ...)
     return RP_BHST;
 }
 
+#ifdef MPOP
+static int
+sm_perror (char *fmt, ...)
+{
+    /* Fill in sm_reply with a suitable error string based on errno.
+     * This isn't particularly MPOP specific, it just happens that that's
+     * the only code that uses it currently.
+     */
+    char *bp, *s;
+    int len, eno = errno;
+
+    va_list ap;
+    va_start(ap,fmt);
+    vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap);
+    va_end(ap);
+
+    bp = sm_reply.text;
+    len = strlen(bp);
+    bp += len;
+    if ((s = strerror(eno)))
+       snprintf(bp, sizeof(sm_reply.text) - len, "%s", s);
+    else
+       snprintf(bp, sizeof(sm_reply.text) - len, "unknown error %d", eno);
+    
+    sm_reply.length = strlen (sm_reply.text);
+    sm_reply.code = NOTOK;
+
+    return RP_BHST;
+}
+#endif
 
 static int
 smtalk (int time, char *fmt, ...)
@@ -1361,22 +1413,7 @@ smtalk (int time, char *fmt, ...)
                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;
+                   return sm_perror("error renaming %s to %s: ", sm_tmpfil, file);
                }
                fclose (sm_wfp);
                if (sm_wfp = fopen (sm_tmpfil, "w"))
@@ -1519,7 +1556,8 @@ static int
 smhear (void)
 {
     int i, code, cont, bc, rc, more;
-    char *bp, *rp;
+    unsigned char *bp;
+    char *rp;
     char **ehlo, buffer[BUFSIZ];
 
     if (doingEHLO) {
@@ -1594,6 +1632,7 @@ 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;
                bc = strlen (sm_noreply);
@@ -1601,12 +1640,14 @@ again: ;
        }
 
        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)
@@ -1620,6 +1661,7 @@ again: ;
        }
 
        sm_reply.length = rp - sm_reply.text;
+       sm_reply.text[sm_reply.length] = 0;
        return sm_reply.code;
     }
     return NOTOK;
@@ -1636,15 +1678,17 @@ sm_rrecord (char *buffer, int *len)
 
     fgets (buffer, BUFSIZ, sm_rfp);
     *len = strlen (buffer);
-    if (ferror (sm_rfp) || feof (sm_rfp))
+    /* *len should be >0 except on EOF, but check for safety's sake */
+    if (ferror (sm_rfp) || feof (sm_rfp) || (*len == 0))
        return sm_rerror ();
     if (buffer[*len - 1] != '\n')
        while (getc (sm_rfp) != '\n' && !ferror (sm_rfp) && !feof (sm_rfp))
            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;
 }