X-Git-Url: http://git.marmaro.de/?p=mmh;a=blobdiff_plain;f=mts%2Fsmtp%2Fsmtp.c;h=ca56781a637e73ff82df35ee7aec64181deb8f42;hp=a0d84d5fbe8a8f2c69e5eb36442521c58c27d24b;hb=6c42153ad9362cc676ea66563bf400d7511b3b68;hpb=8563731b02ce9d750806f6b1769af8b399d964e8 diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index a0d84d5..ca56781 100644 --- a/mts/smtp/smtp.c +++ b/mts/smtp/smtp.c @@ -1,20 +1,30 @@ - /* * 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 #include "smtp.h" -#include +#include #include #include #ifdef MPOP #include #endif - +#ifdef CYRUS_SASL +#include +#include +#include +#include +#include +#include +#endif /* CYRUS_SASL */ /* * This module implements an interface to SendMail very similar @@ -44,6 +54,8 @@ #define TRUE 1 #define FALSE 0 +#define NBITS ((sizeof (int)) * 8) + /* * these codes must all be different! */ @@ -57,9 +69,11 @@ #define SM_DOT 180 #define SM_QUIT 30 #define SM_CLOS 10 +#define SM_AUTH 45 static int sm_addrs = 0; static int sm_alarmed = 0; +static int sm_child = NOTOK; static int sm_debug = 0; static int sm_nl = TRUE; static int sm_verbose = 0; @@ -72,12 +86,33 @@ 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_LIST_END, NULL, NULL }, +}; +#endif /* CYRUS_SASL */ + static char *sm_noreply = "No reply text given"; static char *sm_moreply = "; "; struct smtp sm_reply; /* global... */ - #define MAXEHLO 20 static int doingEHLO; @@ -86,6 +121,10 @@ 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 rclient (char *, char *, char *); static int sm_ierror (char *fmt, ...); static int smtalk (int time, char *fmt, ...); @@ -107,13 +146,38 @@ 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, sasl, saslmech, user); + else + return sendmail_init (client, server, watch, verbose, + debug, onex, queued); +} + +static int +smtp_init (char *client, char *server, int watch, int verbose, + 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) @@ -212,6 +276,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"); @@ -223,6 +316,138 @@ send_options: ; return RP_OK; } +int +sendmail_init (char *client, char *server, int watch, int verbose, + int debug, int onex, int queued) +{ + int i, result, vecp; + int pdi[2], pdo[2]; + char *vec[15]; + + if (watch) + verbose = TRUE; + + sm_verbose = verbose; + sm_debug = debug; + if (sm_rfp != NULL && sm_wfp != NULL) + return RP_OK; + + if (client == NULL || *client == '\0') { + if (clientname) + client = clientname; + else + client = LocalName(); /* no clientname -> LocalName */ + } + +#ifdef ZMAILER + if (client == NULL || *client == '\0') + client = "localhost"; +#endif + + if (pipe (pdi) == NOTOK) + return sm_ierror ("no pipes"); + if (pipe (pdo) == NOTOK) { + close (pdi[0]); + close (pdi[1]); + return sm_ierror ("no pipes"); + } + + for (i = 0; (sm_child = fork ()) == NOTOK && i < 5; i++) + sleep (5); + + switch (sm_child) { + case NOTOK: + close (pdo[0]); + close (pdo[1]); + close (pdi[0]); + close (pdi[1]); + return sm_ierror ("unable to fork"); + + case OK: + if (pdo[0] != fileno (stdin)) + dup2 (pdo[0], fileno (stdin)); + if (pdi[1] != fileno (stdout)) + dup2 (pdi[1], fileno (stdout)); + if (pdi[1] != fileno (stderr)) + dup2 (pdi[1], fileno (stderr)); + for (i = fileno (stderr) + 1; i < NBITS; i++) + close (i); + + 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 ()); + setuid (geteuid ()); + execvp (sendmail, vec); + fprintf (stderr, "unable to exec "); + perror (sendmail); + _exit (-1); /* NOTREACHED */ + + default: + SIGNAL (SIGALRM, alrmser); + SIGNAL (SIGPIPE, SIG_IGN); + + close (pdi[1]); + close (pdo[0]); + if ((sm_rfp = fdopen (pdi[0], "r")) == NULL + || (sm_wfp = fdopen (pdo[1], "w")) == NULL) { + close (pdi[0]); + close (pdo[1]); + sm_rfp = sm_wfp = NULL; + return sm_ierror ("unable to fdopen"); + } + sm_alarmed = 0; + alarm (SM_OPEN); + result = smhear (); + alarm (0); + switch (result) { + case 220: + break; + + default: + sm_end (NOTOK); + return RP_RPLY; + } + + if (client && *client) { + doingEHLO = 1; + result = smtalk (SM_HELO, "EHLO %s", client); + doingEHLO = 0; + + if (500 <= result && result <= 599) + result = smtalk (SM_HELO, "HELO %s", client); + + switch (result) { + case 250: + break; + + default: + sm_end (NOTOK); + return RP_RPLY; + } + } + +#ifndef ZMAILER + if (onex) + smtalk (SM_HELO, "ONEX"); +#endif + if (watch) + smtalk (SM_HELO, "VERB on"); + + return RP_OK; + } +} #ifdef MPOP # define MAXARGS 1000 @@ -276,6 +501,14 @@ rclient (char *server, char *protocol, char *service) return NOTOK; } +#ifdef CYRUS_SASL +#include +#include +#include +#include +#include +#include +#endif /* CYRUS_SASL */ int sm_winit (int mode, char *from) @@ -435,6 +668,17 @@ sm_end (int type) int status; struct smtp sm_note; + if (sm_mts == MTS_SENDMAIL) { + switch (sm_child) { + case NOTOK: + case OK: + return RP_OK; + + default: + break; + } + } + if (sm_rfp == NULL && sm_wfp == NULL) return RP_OK; @@ -449,7 +693,13 @@ sm_end (int type) case DONE: if (smtalk (SM_RSET, "RSET") == 250 && type == DONE) return RP_OK; - smtalk (SM_QUIT, "QUIT"); + if (sm_mts == MTS_SMTP) + smtalk (SM_QUIT, "QUIT"); + else { + kill (sm_child, SIGKILL); + discard (sm_rfp); + discard (sm_wfp); + } if (type == NOTOK) { sm_reply.code = sm_note.code; strncpy (sm_reply.text, sm_note.text, sm_reply.length = sm_note.length); @@ -480,7 +730,17 @@ sm_end (int type) alarm (0); } - status = 0; + if (sm_mts == MTS_SMTP) { + status = 0; +#ifdef CYRUS_SASL + if (conn) + sasl_dispose(&conn); +#endif /* CYRUS_SASL */ + } else { + status = pidwait (sm_child, OK); + sm_child = NOTOK; + } + sm_rfp = sm_wfp = NULL; return (status ? RP_BHST : RP_OK); } @@ -837,6 +1097,306 @@ no_dice: #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) +{ + int result, status, outlen; + unsigned int buflen; + char *buf, outbuf[BUFSIZ]; + const char *chosen_mech; + sasl_security_properties_t secprops; + sasl_external_properties_t extprops; + sasl_ssf_t *ssf; + int *outbufmax; + + /* + * Initialize the callback contexts + */ + + if (user == NULL) + user = getusername(); + + callbacks[SM_SASL_N_CB_USER].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 + * reverse-address lookup on the IP address to get the name. + */ + + if (!host) { + struct sockaddr_in sin; + int len = sizeof(sin); + struct hostent *hp; + + if (getpeername(fileno(sm_wfp), (struct sockaddr *) &sin, &len) < 0) { + sm_ierror("getpeername on SMTP socket failed: %s", + strerror(errno)); + 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)); + return NOTOK; + } + + host = strdup(hp->h_name); + } + + sasl_pw_context[0] = host; + sasl_pw_context[1] = user; + + callbacks[SM_SASL_N_CB_PASS].context = sasl_pw_context; + + result = sasl_client_init(callbacks); + + if (result != SASL_OK) { + sm_ierror("SASL library initialization failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + result = sasl_client_new("smtp", host, NULL, SASL_SECURITY_LAYER, &conn); + + if (result != SASL_OK) { + sm_ierror("SASL client initialization failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + /* + * Initialize the security properties + */ + + 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); + + if (result != SASL_OK) { + sm_ierror("SASL security property initialization failed: %s", + sasl_errstring(result, NULL, NULL)); + 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); + + if (result != SASL_OK && result != SASL_CONTINUE) { + sm_ierror("SASL client start failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + /* + * If we got an initial challenge, send it as part of the AUTH + * command; otherwise, just send a plain AUTH command. + */ + + 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)); + return NOTOK; + } + + status = smtalk(SM_AUTH, "AUTH %s %s", chosen_mech, outbuf); + } else + status = smtalk(SM_AUTH, "AUTH %s", chosen_mech); + + /* + * Now we loop until we either fail, get a SASL_OK, or a 235 + * response code. Receive the challenges and process them until + * we're all done. + */ + + while (result == SASL_CONTINUE) { + + /* + * If we get a 235 response, that means authentication has + * succeeded and we need to break out of the loop (yes, even if + * we still get SASL_CONTINUE from sasl_client_step()). + * + * Otherwise, if we get a message that doesn't seem to be a + * valid response, then abort + */ + + if (status == 235) + break; + else if (status < 300 || status > 399) + return RP_BHST; + + /* + * Special case; a zero-length response from the SMTP server + * is returned as a single =. If we get that, then set buflen + * to be zero. Otherwise, just decode the response. + */ + + if (strcmp("=", sm_reply.text) == 0) { + outlen = 0; + } else { + result = sasl_decode64(sm_reply.text, sm_reply.length, + outbuf, &outlen); + + if (result != SASL_OK) { + smtalk(SM_AUTH, "*"); + sm_ierror("SASL base64 decode failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + } + + result = sasl_client_step(conn, outbuf, outlen, NULL, &buf, &buflen); + + if (result != SASL_OK && result != SASL_CONTINUE) { + smtalk(SM_AUTH, "*"); + sm_ierror("SASL client negotiation failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL); + free(buf); + + if (status != SASL_OK) { + smtalk(SM_AUTH, "*"); + sm_ierror("SASL base64 encode failed: %s", + sasl_errstring(status, NULL, NULL)); + return NOTOK; + } + + status = smtalk(SM_AUTH, outbuf); + } + + /* + * Make sure that we got the correct response + */ + + if (status < 200 || status > 299) + 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); + + if (result != SASL_OK) { + sm_ierror("Cannot retrieve SASL negotiated output buffer size: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + maxoutbuf = *outbufmax; + + result = sasl_getprop(conn, SASL_SSF, (void **) &ssf); + + sasl_ssf = *ssf; + + if (result != SASL_OK) { + sm_ierror("Cannot retrieve SASL negotiated security strength " + "factor: %s", sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + if (maxoutbuf == 0 || maxoutbuf > BUFSIZ) + maxoutbuf = BUFSIZ; + + sasl_complete = 1; + + return RP_OK; +} + +/* + * Our callback functions to feed data to the SASL library + */ + +static int +sm_get_user(void *context, int id, const char **result, unsigned *len) +{ + char *user = (char *) context; + + if (! result || id != SASL_CB_USER) + return SASL_BADPARAM; + + *result = user; + if (len) + *len = strlen(user); + + return SASL_OK; +} + +static int +sm_get_pass(sasl_conn_t *conn, void *context, int id, + sasl_secret_t **psecret) +{ + char **pw_context = (char **) context; + char *pass = NULL; + int len; + + if (! psecret || id != SASL_CB_PASS) + return SASL_BADPARAM; + + ruserpass(pw_context[0], &(pw_context[1]), &pass); + + len = strlen(pass); + + *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len); + + if (! *psecret) { + free(pass); + return SASL_NOMEM; + } + + (*psecret)->len = len; + strcpy((*psecret)->data, pass); +/* free(pass); */ + + return SASL_OK; +} +#endif /* CYRUS_SASL */ + static int sm_ierror (char *fmt, ...) { @@ -871,7 +1431,7 @@ smtalk (int time, char *fmt, ...) #ifdef MPOP if (sm_ispool) { - char file[BUFSIZ]; + char file[BUFSIZ]; if (strcmp (buffer, ".") == 0) time = SM_DOT; @@ -888,7 +1448,7 @@ smtalk (int time, char *fmt, ...) char *bp; snprintf (sm_reply.text, sizeof(sm_reply.text), - "error renaming %s to %s: ", sm_tmpfil, file); + "error renaming %s to %s: ", sm_tmpfil, file); bp = sm_reply.text; len = strlen (bp); bp += len; @@ -1011,16 +1571,17 @@ sm_wstream (char *buffer, int len) } -#ifdef _AIX /* - * AIX by default will inline the strlen and strcpy commands by redefining - * them as __strlen and __strcpy respectively. This causes compile problems - * with the #ifdef MPOP in the middle. Should the #ifdef MPOP be removed, - * remove these #undefs. + * 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. */ +#ifdef strlen # undef strlen +#endif +#ifdef strcpy # undef strcpy -#endif /* _AIX */ +#endif static int sm_werror (void) @@ -1124,9 +1685,10 @@ again: ; if ((i = min (bc, rc)) > 0) { strncpy (rp, bp, i); - rp += i, rc -= i; + rp += i; + rc -= i; if (more && rc > strlen (sm_moreply) + 1) { - strcpy (sm_reply.text + rc, sm_moreply); + strncpy (sm_reply.text + rc, sm_moreply, sizeof(sm_reply.text) - rc); rc += strlen (sm_moreply); } } @@ -1174,11 +1736,18 @@ sm_rrecord (char *buffer, int *len) static int sm_rerror (void) { - 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" - : "error reading from socket")); + 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" + : "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" + : "error reading from pipe")); return (sm_reply.code = NOTOK); }