]> diplodocus.org Git - nmh/blobdiff - mts/smtp/smtp.c
Null-terminate buffer in mbx_copy() in dropsbr.c. Noticed by
[nmh] / mts / smtp / smtp.c
index 5e4b7bdfcb2a5d5ead6dc253b3d3da82f6314a8f..7177e5d59ea330f086a2323f4576dcbb0042980f 100644 (file)
@@ -9,17 +9,22 @@
 #include <h/mh.h>
 #include "smtp.h"
 #include <h/mts.h>
-#include <signal.h>
 #include <h/signals.h>
 
 #ifdef CYRUS_SASL
 #include <sasl/sasl.h>
 #include <sasl/saslutil.h>
+# 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 <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netdb.h>
-#include <errno.h>
 #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,24 +185,25 @@ 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 */
@@ -204,7 +212,6 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose,
     NMH_UNUSED (saslmech);
     NMH_UNUSED (user);
 #endif /* CYRUS_SASL */
-    int result, sd1, sd2;
 
     if (watch)
        verbose = TRUE;
@@ -258,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 ();
@@ -295,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");
@@ -315,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));
-       }
+       result = tls_negotiate();
 
-       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) {
-           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);
@@ -441,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");
 
@@ -451,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 */
@@ -463,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;
@@ -531,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 ());
@@ -615,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");
 
@@ -768,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: 
@@ -797,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;
@@ -839,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;
@@ -864,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
@@ -905,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;
 
@@ -1007,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",
@@ -1115,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 (pw_context[2]);
 
-    len = strlen(pass);
-
-    *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len);
-
-    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;
 }
@@ -1299,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()
  */