-
/*
* smtp.c -- nmh SMTP interface
*
#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.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
#define TRUE 1
#define FALSE 0
+#define NBITS ((sizeof (int)) * 8)
+
/*
* these codes must all be different!
*/
#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;
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;
/*
* 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, ...);
char **smail_copyip (char **, char **, int);
#endif
+#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)
if (sm_rfp != NULL && sm_wfp != NULL)
goto send_options;
- if (client == NULL || *client == '\0')
- if (clientname)
+ if (client == NULL || *client == '\0') {
+ if (clientname) {
client = clientname;
- else
+ } else {
client = LocalName(); /* no clientname -> LocalName */
+ }
+ }
#ifdef ZMAILER
if (client == NULL || *client == '\0')
}
}
+#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");
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
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)
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;
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);
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);
}
#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, ...)
{
#ifdef MPOP
if (sm_ispool) {
- char file[BUFSIZ];
+ char file[BUFSIZ];
if (strcmp (buffer, ".") == 0)
time = SM_DOT;
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;
}
-#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)
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);
}
}
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);
}