summaryrefslogtreecommitdiffstats
path: root/contrib/sendmail/src/usersmtp.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/sendmail/src/usersmtp.c')
-rw-r--r--contrib/sendmail/src/usersmtp.c1553
1 files changed, 1401 insertions, 152 deletions
diff --git a/contrib/sendmail/src/usersmtp.c b/contrib/sendmail/src/usersmtp.c
index c82942b..4254e58 100644
--- a/contrib/sendmail/src/usersmtp.c
+++ b/contrib/sendmail/src/usersmtp.c
@@ -1,5 +1,6 @@
/*
- * Copyright (c) 1998 Sendmail, Inc. All rights reserved.
+ * Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
* Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
@@ -10,20 +11,24 @@
*
*/
-# include "sendmail.h"
+#include <sendmail.h>
#ifndef lint
+# if SMTP
+static char id[] = "@(#)$Id: usersmtp.c,v 8.245.4.12 2000/07/17 19:55:08 ca Exp $ (with SMTP)";
+# else /* SMTP */
+static char id[] = "@(#)$Id: usersmtp.c,v 8.245.4.12 2000/07/17 19:55:08 ca Exp $ (without SMTP)";
+# endif /* SMTP */
+#endif /* ! lint */
+
+#include <sysexits.h>
+
#if SMTP
-static char sccsid[] = "@(#)usersmtp.c 8.111 (Berkeley) 2/3/1999 (with SMTP)";
-#else
-static char sccsid[] = "@(#)usersmtp.c 8.111 (Berkeley) 2/3/1999 (without SMTP)";
-#endif
-#endif /* not lint */
-# include <sysexits.h>
-# include <errno.h>
-# if SMTP
+static void datatimeout __P((void));
+static void esmtp_check __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
+static void helo_options __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
/*
** USERSMTP -- run SMTP protocol from the user end.
@@ -31,17 +36,15 @@ static char sccsid[] = "@(#)usersmtp.c 8.111 (Berkeley) 2/3/1999 (without SMTP)"
** This protocol is described in RFC821.
*/
-#define REPLYTYPE(r) ((r) / 100) /* first digit of reply code */
-#define REPLYCLASS(r) (((r) / 10) % 10) /* second digit of reply code */
-#define SMTPCLOSING 421 /* "Service Shutting Down" */
+# define REPLYTYPE(r) ((r) / 100) /* first digit of reply code */
+# define REPLYCLASS(r) (((r) / 10) % 10) /* second digit of reply code */
+# define SMTPCLOSING 421 /* "Service Shutting Down" */
-char SmtpMsgBuffer[MAXLINE]; /* buffer for commands */
-char SmtpReplyBuffer[MAXLINE]; /* buffer for replies */
-char SmtpError[MAXLINE] = ""; /* save failure error messages */
-bool SmtpNeedIntro; /* need "while talking" in transcript */
+#define ENHSCN(e, d) (e) == NULL ? (d) : newstr(e)
-extern void smtpmessage __P((char *f, MAILER *m, MCI *mci, ...));
-extern int reply __P((MAILER *, MCI *, ENVELOPE *, time_t, void (*)()));
+static char SmtpMsgBuffer[MAXLINE]; /* buffer for commands */
+static char SmtpReplyBuffer[MAXLINE]; /* buffer for replies */
+static bool SmtpNeedIntro; /* need "while talking" in transcript */
/*
** SMTPINIT -- initialize SMTP.
**
@@ -49,8 +52,9 @@ extern int reply __P((MAILER *, MCI *, ENVELOPE *, time_t, void (*)()));
**
** Parameters:
** m -- mailer to create connection to.
-** pvp -- pointer to parameter vector to pass to
-** the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+** onlyhelo -- send only helo command?
**
** Returns:
** none.
@@ -60,19 +64,21 @@ extern int reply __P((MAILER *, MCI *, ENVELOPE *, time_t, void (*)()));
*/
void
-smtpinit(m, mci, e)
+smtpinit(m, mci, e, onlyhelo)
MAILER *m;
register MCI *mci;
ENVELOPE *e;
+ bool onlyhelo;
{
register int r;
register char *p;
- extern void esmtp_check __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
- extern void helo_options __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
+ register char *hn;
+ char *enhsc;
+ enhsc = NULL;
if (tTd(18, 1))
{
- printf("smtpinit ");
+ dprintf("smtpinit ");
mci_dump(mci, FALSE);
}
@@ -90,24 +96,29 @@ smtpinit(m, mci, e)
case MCIS_ACTIVE:
/* need to clear old information */
smtprset(m, mci, e);
- /* fall through */
+ /* FALLTHROUGH */
case MCIS_OPEN:
- return;
+ if (!onlyhelo)
+ return;
+ break;
case MCIS_ERROR:
+ case MCIS_QUITING:
case MCIS_SSD:
/* shouldn't happen */
smtpquit(m, mci, e);
- /* fall through */
+ /* FALLTHROUGH */
case MCIS_CLOSED:
- syserr("451 smtpinit: state CLOSED");
+ syserr("451 4.4.0 smtpinit: state CLOSED");
return;
case MCIS_OPENING:
break;
}
+ if (onlyhelo)
+ goto helo;
mci->mci_state = MCIS_OPENING;
@@ -118,8 +129,9 @@ smtpinit(m, mci, e)
*/
SmtpPhase = mci->mci_phase = "client greeting";
- sm_setproctitle(TRUE, "%s %s: %s", e->e_id, CurHostName, mci->mci_phase);
- r = reply(m, mci, e, TimeOuts.to_initial, esmtp_check);
+ sm_setproctitle(TRUE, e, "%s %s: %s",
+ qid_printname(e), CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_initial, esmtp_check, NULL);
if (r < 0)
goto tempfail1;
if (REPLYTYPE(r) == 4)
@@ -132,27 +144,30 @@ smtpinit(m, mci, e)
** My mother taught me to always introduce myself.
*/
+helo:
if (bitnset(M_ESMTP, m->m_flags) || bitnset(M_LMTP, m->m_flags))
mci->mci_flags |= MCIF_ESMTP;
+ hn = mci->mci_heloname ? mci->mci_heloname : MyHostName;
tryhelo:
if (bitnset(M_LMTP, m->m_flags))
{
- smtpmessage("LHLO %s", m, mci, MyHostName);
+ smtpmessage("LHLO %s", m, mci, hn);
SmtpPhase = mci->mci_phase = "client LHLO";
}
else if (bitset(MCIF_ESMTP, mci->mci_flags))
{
- smtpmessage("EHLO %s", m, mci, MyHostName);
+ smtpmessage("EHLO %s", m, mci, hn);
SmtpPhase = mci->mci_phase = "client EHLO";
}
else
{
- smtpmessage("HELO %s", m, mci, MyHostName);
+ smtpmessage("HELO %s", m, mci, hn);
SmtpPhase = mci->mci_phase = "client HELO";
}
- sm_setproctitle(TRUE, "%s %s: %s", e->e_id, CurHostName, mci->mci_phase);
- r = reply(m, mci, e, TimeOuts.to_helo, helo_options);
+ sm_setproctitle(TRUE, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_helo, helo_options, NULL);
if (r < 0)
goto tempfail1;
else if (REPLYTYPE(r) == 5)
@@ -182,9 +197,9 @@ tryhelo:
!bitnset(M_LMTP, m->m_flags) &&
strcasecmp(&SmtpReplyBuffer[4], MyHostName) == 0)
{
- syserr("553 %s config error: mail loops back to me (MX problem?)",
+ syserr("553 5.3.5 %s config error: mail loops back to me (MX problem?)",
CurHostName);
- mci_setstat(mci, EX_CONFIG, NULL, NULL);
+ mci_setstat(mci, EX_CONFIG, "5.3.5", "system config error");
mci->mci_errno = 0;
smtpquit(m, mci, e);
return;
@@ -199,7 +214,7 @@ tryhelo:
{
/* tell it to be verbose */
smtpmessage("VERB", m, mci);
- r = reply(m, mci, e, TimeOuts.to_miscshort, NULL);
+ r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, &enhsc);
if (r < 0)
goto tempfail1;
}
@@ -215,7 +230,7 @@ tryhelo:
tempfail1:
if (mci->mci_errno == 0)
mci->mci_errno = errno;
- mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
+ mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.4.2"), NULL);
if (mci->mci_state != MCIS_CLOSED)
smtpquit(m, mci, e);
return;
@@ -224,7 +239,8 @@ tryhelo:
if (mci->mci_errno == 0)
mci->mci_errno = errno;
/* XXX should use code from other end iff ENHANCEDSTATUSCODES */
- mci_setstat(mci, EX_TEMPFAIL, "4.5.0", SmtpReplyBuffer);
+ mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"),
+ SmtpReplyBuffer);
if (mci->mci_state != MCIS_CLOSED)
smtpquit(m, mci, e);
return;
@@ -249,7 +265,7 @@ tryhelo:
** none.
*/
-void
+static void
esmtp_check(line, firstline, m, mci, e)
char *line;
bool firstline;
@@ -262,6 +278,74 @@ esmtp_check(line, firstline, m, mci, e)
if (strstr(line, "8BIT-OK") != NULL)
mci->mci_flags |= MCIF_8BITOK;
}
+# if SASL
+ /*
+** STR_UNION -- create the union of two lists
+**
+** Parameters:
+** s1, s2 -- lists of items (separated by single blanks).
+**
+** Returns:
+** the union of both lists.
+*/
+
+static char *
+str_union(s1, s2)
+ char *s1, *s2;
+{
+ char *hr, *h1, *h, *res;
+ int l1, l2, rl;
+
+ if (s1 == NULL || *s1 == '\0')
+ return s2;
+ if (s2 == NULL || *s2 == '\0')
+ return s1;
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+ rl = l1 + l2;
+ res = (char *)malloc(rl + 2);
+ if (res == NULL)
+ {
+ if (l1 > l2)
+ return s1;
+ return s2;
+ }
+ (void) strlcpy(res, s1, rl);
+ hr = res;
+ h1 = s2;
+ h = s2;
+
+ /* walk through s2 */
+ while (h != NULL && *h1 != '\0')
+ {
+ /* is there something after the current word? */
+ if ((h = strchr(h1, ' ')) != NULL)
+ *h = '\0';
+ l1 = strlen(h1);
+
+ /* does the current word appear in s1 ? */
+ if (iteminlist(h1, s1, " ") == NULL)
+ {
+ /* add space as delimiter */
+ *hr++ = ' ';
+
+ /* copy the item */
+ memcpy(hr, h1, l1);
+
+ /* advance pointer in result list */
+ hr += l1;
+ *hr = '\0';
+ }
+ if (h != NULL)
+ {
+ /* there are more items */
+ *h = ' ';
+ h1 = h + 1;
+ }
+ }
+ return res;
+}
+# endif /* SASL */
/*
** HELO_OPTIONS -- process the options on a HELO line.
**
@@ -276,7 +360,7 @@ esmtp_check(line, firstline, m, mci, e)
** none.
*/
-void
+static void
helo_options(line, firstline, m, mci, e)
char *line;
bool firstline;
@@ -287,12 +371,19 @@ helo_options(line, firstline, m, mci, e)
register char *p;
if (firstline)
+ {
+# if SASL
+ if (mci->mci_saslcap != NULL)
+ free(mci->mci_saslcap);
+ mci->mci_saslcap = NULL;
+# endif /* SASL */
return;
+ }
if (strlen(line) < (SIZE_T) 5)
return;
line += 4;
- p = strchr(line, ' ');
+ p = strpbrk(line, " =");
if (p != NULL)
*p++ = '\0';
if (strcasecmp(line, "size") == 0)
@@ -310,7 +401,987 @@ helo_options(line, firstline, m, mci, e)
mci->mci_flags |= MCIF_EXPN;
else if (strcasecmp(line, "dsn") == 0)
mci->mci_flags |= MCIF_DSN;
+ else if (strcasecmp(line, "enhancedstatuscodes") == 0)
+ mci->mci_flags |= MCIF_ENHSTAT;
+# if STARTTLS
+ else if (strcasecmp(line, "starttls") == 0)
+ mci->mci_flags |= MCIF_TLS;
+# endif /* STARTTLS */
+# if SASL
+ else if (strcasecmp(line, "auth") == 0)
+ {
+ if (p != NULL && *p != '\0')
+ {
+ if (mci->mci_saslcap != NULL)
+ {
+ char *h;
+
+ /*
+ ** create the union with previous auth
+ ** offerings because we recognize "auth "
+ ** and "auth=" (old format).
+ */
+ h = mci->mci_saslcap;
+ mci->mci_saslcap = str_union(h, p);
+ if (h != mci->mci_saslcap)
+ free(h);
+ mci->mci_flags |= MCIF_AUTH;
+ }
+ else
+ {
+ int l;
+
+ l = strlen(p) + 1;
+ mci->mci_saslcap = (char *)malloc(l);
+
+ /* XXX this may be leaked */
+ if (mci->mci_saslcap != NULL)
+ {
+ (void) strlcpy(mci->mci_saslcap, p, l);
+ mci->mci_flags |= MCIF_AUTH;
+ }
+ }
+ }
+ }
+# endif /* SASL */
+}
+# if SASL
+
+ /*
+** GETSASLDATA -- process the challenges from the SASL protocol
+**
+** This gets the relevant sasl response data out of the reply
+** from the server
+**
+** Parameters:
+** line -- the response line.
+** firstline -- set if this is the first line of the reply.
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+void
+getsasldata(line, firstline, m, mci, e)
+ char *line;
+ bool firstline;
+ MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ int len;
+ char *out;
+ int result;
+
+ /* if not a continue we don't care about it */
+ if ((strlen(line) <= 4) ||
+ (line[0] != '3') ||
+ (line[1] != '3') ||
+ (line[2] != '4'))
+ {
+ mci->mci_sasl_string = NULL;
+ return;
+ }
+
+ /* forget about "334 " */
+ line += 4;
+ len = strlen(line);
+
+ out = xalloc(len + 1);
+ result = sasl_decode64(line, len, out, (u_int *)&len);
+ if (result != SASL_OK)
+ {
+ len = 0;
+ *out = '\0';
+ }
+ if (mci->mci_sasl_string != NULL)
+ {
+ if (mci->mci_sasl_string_len <= len)
+ {
+ free(mci->mci_sasl_string);
+ mci->mci_sasl_string = xalloc(len + 1);
+ }
+ }
+ else
+ mci->mci_sasl_string = xalloc(len + 1);
+ /* XXX this is probably leaked */
+ memcpy(mci->mci_sasl_string, out, len);
+ mci->mci_sasl_string[len] = '\0';
+ mci->mci_sasl_string_len = len;
+ free(out);
+ return;
}
+
+ /*
+** READAUTH -- read auth value from a file
+**
+** Parameters:
+** l -- line to define.
+** filename -- name of file to read.
+** safe -- if set, this is a safe read.
+**
+** Returns:
+** line from file
+**
+** Side Effects:
+** overwrites local static buffer. The caller should copy
+** the result.
+**
+*/
+
+/* lines in authinfo file */
+# define SASL_USER 1
+# define SASL_AUTHID 2
+# define SASL_PASSWORD 3
+# define SASL_DEFREALM 4
+# define SASL_MECH 5
+
+static char *sasl_info_name[] =
+{
+ "",
+ "user id",
+ "authorization id",
+ "password",
+ "realm",
+ "mechanism"
+};
+
+static char *
+readauth(l, filename, safe)
+ int l;
+ char *filename;
+ bool safe;
+{
+ FILE *f;
+ long sff;
+ pid_t pid;
+ int lc;
+ static char buf[MAXLINE];
+
+ if (filename == NULL || filename[0] == '\0')
+ return "";
+#if !_FFR_ALLOW_SASLINFO
+ /*
+ ** make sure we don't use a program that is not
+ ** accesible to the user who specified a different authinfo file.
+ ** However, currently we don't pass this info (authinfo file
+ ** specified by user) around, so we just turn off program access.
+ */
+ if (filename[0] == '|')
+ {
+ auto int fd;
+ int i;
+ char *p;
+ char *argv[MAXPV + 1];
+
+ i = 0;
+ for (p = strtok(&filename[1], " \t"); p != NULL;
+ p = strtok(NULL, " \t"))
+ {
+ if (i >= MAXPV)
+ break;
+ argv[i++] = p;
+ }
+ argv[i] = NULL;
+ pid = prog_open(argv, &fd, CurEnv);
+ if (pid < 0)
+ f = NULL;
+ else
+ f = fdopen(fd, "r");
+ }
+ else
+#endif /* !_FFR_ALLOW_SASLINFO */
+ {
+ pid = -1;
+ sff = SFF_REGONLY | SFF_SAFEDIRPATH | SFF_NOWLINK
+ | SFF_NOGWFILES | SFF_NOWWFILES | SFF_NORFILES;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+#if _FFR_ALLOW_SASLINFO
+ /*
+ ** XXX: make sure we don't read or open files that are not
+ ** accesible to the user who specified a different authinfo
+ ** file.
+ */
+ sff |= SFF_MUSTOWN;
+#else /* _FFR_ALLOW_SASLINFO */
+ if (safe)
+ sff |= SFF_OPENASROOT;
+#endif /* _FFR_ALLOW_SASLINFO */
+
+ f = safefopen(filename, O_RDONLY, 0, sff);
+ }
+ if (f == NULL)
+ {
+ syserr("readauth: cannot open %s", filename);
+ return "";
+ }
+
+ lc = 0;
+ while (lc < l && fgets(buf, sizeof buf, f) != NULL)
+ {
+ if (buf[0] != '#')
+ lc++;
+ }
+
+ (void) fclose(f);
+ if (pid > 0)
+ (void) waitfor(pid);
+ if (lc < l)
+ {
+ if (LogLevel >= 9)
+ sm_syslog(LOG_WARNING, NOQID, "SASL: error: can't read %s from %s",
+ sasl_info_name[l], filename);
+ return "";
+ }
+ lc = strlen(buf) - 1;
+ if (lc >= 0)
+ buf[lc] = '\0';
+ if (tTd(95, 6))
+ dprintf("readauth(%s, %d) = '%s'\n", filename, l, buf);
+ return buf;
+}
+
+# ifndef __attribute__
+# define __attribute__(x)
+# endif /* ! __attribute__ */
+
+static int getsimple __P((void *, int, const char **, unsigned *));
+static int getsecret __P((sasl_conn_t *, void *, int, sasl_secret_t **));
+static int saslgetrealm __P((void *, int, const char **, const char **));
+
+static sasl_callback_t callbacks[] =
+{
+ { SASL_CB_GETREALM, &saslgetrealm, NULL },
+# define CB_GETREALM_IDX 0
+ { SASL_CB_PASS, &getsecret, NULL },
+# define CB_PASS_IDX 1
+ { SASL_CB_USER, &getsimple, NULL },
+# define CB_USER_IDX 2
+ { SASL_CB_AUTHNAME, &getsimple, NULL },
+# define CB_AUTHNAME_IDX 3
+ { SASL_CB_VERIFYFILE, &safesaslfile, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+
+ /*
+** GETSIMPLE -- callback to get userid or authid
+**
+** Parameters:
+** context -- unused
+** id -- what to do
+** result -- (pointer to) result
+** len -- (pointer to) length of result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsimple(context, id, result, len)
+ void *context __attribute__((unused));
+ int id;
+ const char **result;
+ unsigned *len;
+{
+ char *h;
+# if SASL > 10509
+ int addrealm;
+ static int addedrealm = FALSE;
+# endif /* SASL > 10509 */
+ static char *user = NULL;
+ static char *authid = NULL;
+
+ if (result == NULL)
+ return SASL_BADPARAM;
+
+ switch (id)
+ {
+ case SASL_CB_USER:
+ if (user == NULL)
+ {
+ h = readauth(SASL_USER, SASLInfo, TRUE);
+ user = newstr(h);
+ }
+ *result = user;
+ if (tTd(95, 5))
+ dprintf("AUTH username '%s'\n", *result);
+ if (len != NULL)
+ *len = user ? strlen(user) : 0;
+ break;
+
+ case SASL_CB_AUTHNAME:
+# if SASL > 10509
+ /* XXX maybe other mechanisms too?! */
+ addrealm = context != NULL &&
+ strcasecmp(context, "CRAM-MD5") == 0;
+ if (addedrealm != addrealm && authid != NULL)
+ {
+# if SASL > 10522
+ /*
+ ** digest-md5 prior to 1.5.23 doesn't copy the
+ ** value it gets from the callback, but free()s
+ ** it later on
+ ** workaround: don't free() it here
+ ** this can cause a memory leak!
+ */
+ free(authid);
+# endif /* SASL > 10522 */
+ authid = NULL;
+ addedrealm = addrealm;
+ }
+# endif /* SASL > 10509 */
+ if (authid == NULL)
+ {
+ h = readauth(SASL_AUTHID, SASLInfo, TRUE);
+# if SASL > 10509
+ if (addrealm && strchr(h, '@') == NULL)
+ {
+ size_t l;
+ char *realm;
+
+ realm = callbacks[CB_GETREALM_IDX].context;
+ l = strlen(h) + strlen(realm) + 2;
+ authid = xalloc(l);
+ snprintf(authid, l, "%s@%s", h, realm);
+ }
+ else
+# endif /* SASL > 10509 */
+ authid = newstr(h);
+ }
+ *result = authid;
+ if (tTd(95, 5))
+ dprintf("AUTH authid '%s'\n", *result);
+ if (len != NULL)
+ *len = authid ? strlen(authid) : 0;
+ break;
+
+ case SASL_CB_LANGUAGE:
+ *result = NULL;
+ if (len != NULL)
+ *len = 0;
+ break;
+
+ default:
+ return SASL_BADPARAM;
+ }
+ return SASL_OK;
+}
+
+ /*
+** GETSECRET -- callback to get password
+**
+** Parameters:
+** conn -- connection information
+** context -- unused
+** id -- what to do
+** psecret -- (pointer to) result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsecret(conn, context, id, psecret)
+ sasl_conn_t *conn;
+ void *context __attribute__((unused));
+ int id;
+ sasl_secret_t **psecret;
+{
+ char *h;
+ int len;
+ static char *authpass = NULL;
+
+ if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ if (authpass == NULL)
+ {
+ h = readauth(SASL_PASSWORD, SASLInfo, TRUE);
+ authpass = newstr(h);
+ }
+ len = strlen(authpass);
+ *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len + 1);
+ if (*psecret == NULL)
+ return SASL_FAIL;
+ (void) strlcpy((*psecret)->data, authpass, len + 1);
+ (*psecret)->len = len;
+ return SASL_OK;
+}
+
+ /*
+** SAFESASLFILE -- callback for sasl: is file safe?
+**
+** Parameters:
+** context -- pointer to context between invocations (unused)
+** file -- name of file to check
+** type -- type of file to check
+**
+** Returns:
+** SASL_OK: file can be used
+** SASL_CONTINUE: don't use file
+** SASL_FAIL: failure (not used here)
+**
+*/
+int
+# if SASL > 10515
+safesaslfile(context, file, type)
+# else /* SASL > 10515 */
+safesaslfile(context, file)
+# endif /* SASL > 10515 */
+ void *context;
+ char *file;
+# if SASL > 10515
+ int type;
+# endif /* SASL > 10515 */
+{
+ long sff;
+ int r;
+ char *p;
+
+ if (file == NULL || *file == '\0')
+ return SASL_OK;
+ sff = SFF_SAFEDIRPATH|SFF_NOWLINK|SFF_NOGWFILES|SFF_NOWWFILES|SFF_ROOTOK;
+ if ((p = strrchr(file, '/')) == NULL)
+ p = file;
+ else
+ ++p;
+
+# if SASL <= 10515
+ /* everything beside libs and .conf files must not be readable */
+ r = strlen(p);
+ if ((r <= 3 || strncmp(p, "lib", 3) != 0) &&
+ (r <= 5 || strncmp(p + r - 5, ".conf", 5) != 0)
+# if _FFR_UNSAFE_SASL
+ && !bitnset(DBS_GROUPREADABLESASLFILE, DontBlameSendmail)
+# endif /* _FFR_UNSAFE_SASL */
+ )
+ sff |= SFF_NORFILES;
+# else /* SASL > 10515 */
+ /* files containing passwords should be not readable */
+ if (type == SASL_VRFY_PASSWD)
+ {
+# if _FFR_UNSAFE_SASL
+ if (bitnset(DBS_GROUPREADABLESASLFILE, DontBlameSendmail))
+ sff |= SFF_NOWRFILES;
+ else
+# endif /* _FFR_UNSAFE_SASL */
+ sff |= SFF_NORFILES;
+ }
+# endif /* SASL <= 10515 */
+
+ if ((r = safefile(file, RunAsUid, RunAsGid, RunAsUserName, sff,
+ S_IRUSR, NULL)) == 0)
+ return SASL_OK;
+ if (LogLevel >= 11 || (r != ENOENT && LogLevel >= 9))
+ sm_syslog(LOG_WARNING, NOQID, "error: safesasl(%s) failed: %s",
+ file, errstring(r));
+ return SASL_CONTINUE;
+}
+
+/*
+** SASLGETREALM -- return the realm for SASL
+**
+** return the realm for the client
+**
+** Parameters:
+** context -- context shared between invocations
+** here: realm to return
+** availrealms -- list of available realms
+** {realm, realm, ...}
+** result -- pointer to result
+**
+** Returns:
+** failure/success
+*/
+static int
+saslgetrealm(context, id, availrealms, result)
+ void *context;
+ int id;
+ const char **availrealms;
+ const char **result;
+{
+ if (LogLevel > 12)
+ sm_syslog(LOG_INFO, NOQID, "saslgetrealm: realm %s available realms %s",
+ context,
+ availrealms == NULL ? "<No Realms>" : *availrealms);
+ if (context == NULL)
+ return SASL_FAIL;
+
+ /* check whether context is in list? */
+ if (availrealms != NULL)
+ {
+ if (iteminlist(context, (char *)(*availrealms + 1), " ,}") ==
+ NULL)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "saslgetrealm: realm %s not in list %s",
+ context, *availrealms);
+ return SASL_FAIL;
+ }
+ }
+ *result = (char *)context;
+ return SASL_OK;
+}
+ /*
+** ITEMINLIST -- does item appear in list?
+**
+** Check whether item appears in list (which must be separated by a
+** character in delim) as a "word", i.e. it must appear at the begin
+** of the list or after a space, and it must end with a space or the
+** end of the list.
+**
+** Parameters:
+** item -- item to search.
+** list -- list of items.
+** delim -- list of delimiters.
+**
+** Returns:
+** pointer to occurrence (NULL if not found).
+*/
+
+char *
+iteminlist(item, list, delim)
+ char *item;
+ char *list;
+ char *delim;
+{
+ char *s;
+ int len;
+
+ if (list == NULL || *list == '\0')
+ return NULL;
+ if (item == NULL || *item == '\0')
+ return NULL;
+ s = list;
+ len = strlen(item);
+ while (s != NULL && *s != '\0')
+ {
+ if (strncasecmp(s, item, len) == 0 &&
+ (s[len] == '\0' || strchr(delim, s[len]) != NULL))
+ return s;
+ s = strpbrk(s, delim);
+ if (s != NULL)
+ while (*++s == ' ')
+ continue;
+ }
+ return NULL;
+}
+ /*
+** REMOVEMECH -- remove item [rem] from list [list]
+**
+** Parameters:
+** rem -- item to remove
+** list -- list of items
+**
+** Returns:
+** pointer to new list (NULL in case of error).
+*/
+
+char *
+removemech(rem, list)
+ char *rem;
+ char *list;
+{
+ char *ret;
+ char *needle;
+ int len;
+
+ if (list == NULL)
+ return NULL;
+ if (rem == NULL || *rem == '\0')
+ {
+ /* take out what? */
+ return NULL;
+ }
+
+ /* find the item in the list */
+ if ((needle = iteminlist(rem, list, " ")) == NULL)
+ {
+ /* not in there: return original */
+ return list;
+ }
+
+ /* length of string without rem */
+ len = strlen(list) - strlen(rem);
+ if (len == 0)
+ {
+ ret = xalloc(1); /* XXX leaked */
+ *ret = '\0';
+ return ret;
+ }
+ ret = xalloc(len); /* XXX leaked */
+ memset(ret, '\0', len);
+
+ /* copy from start to removed item */
+ memcpy(ret, list, needle - list);
+
+ /* length of rest of string past removed item */
+ len = strlen(needle) - strlen(rem) - 1;
+ if (len > 0)
+ {
+ /* not last item -- copy into string */
+ memcpy(ret + (needle - list),
+ list + (needle - list) + strlen(rem) + 1,
+ len);
+ }
+ else
+ ret[(needle - list) - 1] = '\0';
+ return ret;
+}
+ /*
+** INTERSECT -- create the intersection between two lists
+**
+** Parameters:
+** s1, s2 -- lists of items (separated by single blanks).
+**
+** Returns:
+** the intersection of both lists.
+*/
+
+char *
+intersect(s1, s2)
+ char *s1, *s2;
+{
+ char *hr, *h1, *h, *res;
+ int l1, l2, rl;
+
+ if (s1 == NULL || s2 == NULL) /* NULL string(s) -> NULL result */
+ return NULL;
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+ rl = min(l1, l2);
+ res = (char *)malloc(rl + 1);
+ if (res == NULL)
+ return NULL;
+ *res = '\0';
+ if (rl == 0) /* at least one string empty? */
+ return res;
+ hr = res;
+ h1 = s1;
+ h = s1;
+
+ /* walk through s1 */
+ while (h != NULL && *h1 != '\0')
+ {
+ /* is there something after the current word? */
+ if ((h = strchr(h1, ' ')) != NULL)
+ *h = '\0';
+ l1 = strlen(h1);
+
+ /* does the current word appear in s2 ? */
+ if (iteminlist(h1, s2, " ") != NULL)
+ {
+ /* add a blank if not first item */
+ if (hr != res)
+ *hr++ = ' ';
+
+ /* copy the item */
+ memcpy(hr, h1, l1);
+
+ /* advance pointer in result list */
+ hr += l1;
+ *hr = '\0';
+ }
+ if (h != NULL)
+ {
+ /* there are more items */
+ *h = ' ';
+ h1 = h + 1;
+ }
+ }
+ return res;
+}
+ /*
+** ATTEMPTAUTH -- try to AUTHenticate using one mechanism
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection structure.
+** e -- the envelope (including the sender to specify).
+** mechused - filled in with mechanism used
+**
+** Returns:
+** EX_OK/EX_TEMPFAIL
+*/
+
+int
+attemptauth(m, mci, e, mechused)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+ char **mechused;
+{
+ int saslresult, smtpresult;
+ sasl_external_properties_t ssf;
+ sasl_interact_t *client_interact = NULL;
+ char *out;
+ unsigned int outlen;
+ static char *mechusing;
+ sasl_security_properties_t ssp;
+ char in64[MAXOUTLEN];
+# if NETINET
+ extern SOCKADDR CurHostAddr;
+# endif /* NETINET */
+
+ *mechused = NULL;
+ if (mci->mci_conn != NULL)
+ {
+ sasl_dispose(&(mci->mci_conn));
+
+ /* just in case, sasl_dispose() should take care of it */
+ mci->mci_conn = NULL;
+ }
+
+ /* make a new client sasl connection */
+ saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
+ : "smtp",
+ CurHostName, NULL, 0, &mci->mci_conn);
+
+ /* set properties */
+ (void) memset(&ssp, '\0', sizeof ssp);
+# if SFIO
+ /* XXX should these be options settable via .cf ? */
+ /* ssp.min_ssf = 0; is default due to memset() */
+ {
+ ssp.max_ssf = INT_MAX;
+ ssp.maxbufsize = MAXOUTLEN;
+# if 0
+ ssp.security_flags = SASL_SEC_NOPLAINTEXT;
+# endif /* 0 */
+ }
+# endif /* SFIO */
+ saslresult = sasl_setprop(mci->mci_conn, SASL_SEC_PROPS, &ssp);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+ /* external security strength factor, authentication id */
+ ssf.ssf = 0;
+ ssf.auth_id = NULL;
+# if _FFR_EXT_MECH
+ out = macvalue(macid("{cert_subject}", NULL), e);
+ if (out != NULL && *out != '\0')
+ ssf.auth_id = out;
+ out = macvalue(macid("{cipher_bits}", NULL), e);
+ if (out != NULL && *out != '\0')
+ ssf.ssf = atoi(out);
+# endif /* _FFR_EXT_MECH */
+ saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+# if NETINET
+ /* set local/remote ipv4 addresses */
+ if (mci->mci_out != NULL && CurHostAddr.sa.sa_family == AF_INET)
+ {
+ SOCKADDR_LEN_T addrsize;
+ struct sockaddr_in saddr_l;
+
+ if (sasl_setprop(mci->mci_conn, SASL_IP_REMOTE,
+ (struct sockaddr_in *) &CurHostAddr)
+ != SASL_OK)
+ return EX_TEMPFAIL;
+ addrsize = sizeof(struct sockaddr_in);
+ if (getsockname(fileno(mci->mci_out),
+ (struct sockaddr *) &saddr_l, &addrsize) != 0)
+ {
+ if (sasl_setprop(mci->mci_conn, SASL_IP_LOCAL,
+ &saddr_l) != SASL_OK)
+ return EX_TEMPFAIL;
+ }
+ }
+# endif /* NETINET */
+
+ /* start client side of sasl */
+ saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
+ NULL, &client_interact,
+ &out, &outlen,
+ (const char **)&mechusing);
+ callbacks[CB_AUTHNAME_IDX].context = mechusing;
+
+ if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
+ {
+# if SFIO
+ if (saslresult == SASL_NOMECH && LogLevel > 8)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "available AUTH mechanisms do not fulfill requirements");
+ }
+# endif /* SFIO */
+ return EX_TEMPFAIL;
+ }
+
+ *mechused = mechusing;
+
+ /* send the info across the wire */
+ if (outlen > 0)
+ {
+ saslresult = sasl_encode64(out, outlen, in64, MAXOUTLEN, NULL);
+ if (saslresult != SASL_OK) /* internal error */
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, e->e_id,
+ "encode64 for AUTH failed");
+ return EX_TEMPFAIL;
+ }
+ smtpmessage("AUTH %s %s", m, mci, mechusing, in64);
+ }
+ else
+ {
+ smtpmessage("AUTH %s", m, mci, mechusing);
+ }
+
+ /* get the reply */
+ smtpresult = reply(m, mci, e, TimeOuts.to_datafinal, getsasldata, NULL);
+ /* which timeout? XXX */
+
+ for (;;)
+ {
+ /* check return code from server */
+ if (smtpresult == 235)
+ {
+ define(macid("{auth_type}", NULL),
+ newstr(mechusing), e);
+# if !SFIO
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, NOQID,
+ "SASL: outgoing connection to %.64s: mech=%.16s",
+ mci->mci_host, mechusing);
+# endif /* !SFIO */
+ return EX_OK;
+ }
+ if (smtpresult == -1)
+ return EX_IOERR;
+ if (smtpresult != 334)
+ return EX_TEMPFAIL;
+
+ saslresult = sasl_client_step(mci->mci_conn,
+ mci->mci_sasl_string,
+ mci->mci_sasl_string_len,
+ &client_interact,
+ &out, &outlen);
+
+ if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
+ {
+ if (tTd(95, 5))
+ dprintf("AUTH FAIL: %s (%d)\n",
+ sasl_errstring(saslresult, NULL, NULL),
+ saslresult);
+
+ /* fail deliberately, see RFC 2254 4. */
+ smtpmessage("*", m, mci);
+
+ /*
+ ** but we should only fail for this authentication
+ ** mechanism; how to do that?
+ */
+
+ smtpresult = reply(m, mci, e, TimeOuts.to_datafinal,
+ getsasldata, NULL);
+ return EX_TEMPFAIL;
+ }
+
+ if (outlen > 0)
+ {
+ saslresult = sasl_encode64(out, outlen, in64,
+ MAXOUTLEN, NULL);
+ if (saslresult != SASL_OK)
+ {
+ /* give an error reply to the other side! */
+ smtpmessage("*", m, mci);
+ return EX_TEMPFAIL;
+ }
+ }
+ else
+ in64[0] = '\0';
+ smtpmessage(in64, m, mci);
+ smtpresult = reply(m, mci, e, TimeOuts.to_datafinal,
+ getsasldata, NULL);
+ /* which timeout? XXX */
+ }
+ /* NOTREACHED */
+}
+
+ /*
+** SMTPAUTH -- try to AUTHenticate
+**
+** This will try mechanisms in the order the sasl library decided until:
+** - there are no more mechanisms
+** - a mechanism succeeds
+** - the sasl library fails initializing
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+**
+** Returns:
+** EX_OK/EX_TEMPFAIL
+*/
+
+int
+smtpauth(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int result;
+ char *mechused;
+ char *h;
+ static char *defrealm = NULL;
+ static char *mechs = NULL;
+
+ mci->mci_sasl_auth = FALSE;
+ if (defrealm == NULL)
+ {
+ h = readauth(SASL_DEFREALM, SASLInfo, TRUE);
+ if (h != NULL && *h != '\0')
+ defrealm = newstr(h);
+ }
+ if (defrealm == NULL || *defrealm == '\0')
+ defrealm = newstr(macvalue('j', CurEnv));
+ callbacks[CB_GETREALM_IDX].context = defrealm;
+
+# if _FFR_DEFAUTHINFO_MECHS
+ if (mechs == NULL)
+ {
+ h = readauth(SASL_MECH, SASLInfo, TRUE);
+ if (h != NULL && *h != '\0')
+ mechs = newstr(h);
+ }
+# endif /* _FFR_DEFAUTHINFO_MECHS */
+ if (mechs == NULL || *mechs == '\0')
+ mechs = AuthMechanisms;
+ mci->mci_saslcap = intersect(mechs, mci->mci_saslcap);
+
+ /* initialize sasl client library */
+ result = sasl_client_init(callbacks);
+ if (result != SASL_OK)
+ return EX_TEMPFAIL;
+ do
+ {
+ result = attemptauth(m, mci, e, &mechused);
+ if (result == EX_OK)
+ mci->mci_sasl_auth = TRUE;
+ else if (result == EX_TEMPFAIL)
+ {
+ mci->mci_saslcap = removemech(mechused,
+ mci->mci_saslcap);
+ if (mci->mci_saslcap == NULL ||
+ *(mci->mci_saslcap) == '\0')
+ return EX_TEMPFAIL;
+ }
+ else /* all others for now */
+ return EX_TEMPFAIL;
+ } while (result != EX_OK);
+ return result;
+}
+# endif /* SASL */
+
/*
** SMTPMAILFROM -- send MAIL command
**
@@ -331,17 +1402,23 @@ smtpmailfrom(m, mci, e)
char *bodytype;
char buf[MAXNAME + 1];
char optbuf[MAXLINE];
+ char *enhsc;
if (tTd(18, 2))
- printf("smtpmailfrom: CurHost=%s\n", CurHostName);
+ dprintf("smtpmailfrom: CurHost=%s\n", CurHostName);
+ enhsc = NULL;
/* set up appropriate options to include */
- bufp = optbuf;
if (bitset(MCIF_SIZE, mci->mci_flags) && e->e_msgsize > 0)
+ {
snprintf(optbuf, sizeof optbuf, " SIZE=%ld", e->e_msgsize);
+ bufp = &optbuf[strlen(optbuf)];
+ }
else
- strcpy(optbuf, "");
- bufp = &optbuf[strlen(optbuf)];
+ {
+ optbuf[0] = '\0';
+ bufp = optbuf;
+ }
bodytype = e->e_bodytype;
if (bitset(MCIF_8BITMIME, mci->mci_flags))
@@ -364,9 +1441,10 @@ smtpmailfrom(m, mci, e)
!bitset(EF_HAS8BIT, e->e_flags) ||
bitset(MCIF_8BITOK, mci->mci_flags))
{
+ /* EMPTY */
/* just pass it through */
}
-#if MIME8TO7
+# if MIME8TO7
else if (bitset(MM_CVTMIME, MimeMode) &&
!bitset(EF_DONT_MIME, e->e_flags) &&
(!bitset(MM_PASS8BIT, MimeMode) ||
@@ -375,13 +1453,13 @@ smtpmailfrom(m, mci, e)
/* must convert from 8bit MIME format to 7bit encoded */
mci->mci_flags |= MCIF_CVT8TO7;
}
-#endif
+# endif /* MIME8TO7 */
else if (!bitset(MM_PASS8BIT, MimeMode))
{
/* cannot just send a 8-bit version */
extern char MsgBuf[];
- usrerr("%s does not support 8BITMIME", CurHostName);
+ usrerrenh("5.6.3", "%s does not support 8BITMIME", CurHostName);
mci_setstat(mci, EX_NOTSTICKY, "5.6.3", MsgBuf);
return EX_DATAERR;
}
@@ -408,6 +1486,18 @@ smtpmailfrom(m, mci, e)
}
}
+ if (bitset(MCIF_AUTH, mci->mci_flags) && e->e_auth_param != NULL &&
+ SPACELEFT(optbuf, bufp) > strlen(e->e_auth_param) + 7
+# if SASL
+ && (!bitset(SASL_AUTH_AUTH, SASLOpts) || mci->mci_sasl_auth)
+# endif /* SASL */
+ )
+ {
+ snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " AUTH=%s", e->e_auth_param);
+ bufp += strlen(bufp);
+ }
+
/*
** Send the MAIL command.
** Designates the sender.
@@ -417,7 +1507,7 @@ smtpmailfrom(m, mci, e)
if (bitset(EF_RESPONSE, e->e_flags) &&
!bitnset(M_NO_NULL_FROM, m->m_flags))
- (void) strcpy(buf, "");
+ buf[0] = '\0';
else
expand("\201g", buf, sizeof buf, e);
if (buf[0] == '<')
@@ -441,8 +1531,9 @@ smtpmailfrom(m, mci, e)
*bufp == '@' ? ',' : ':', bufp, optbuf);
}
SmtpPhase = mci->mci_phase = "client MAIL";
- sm_setproctitle(TRUE, "%s %s: %s", e->e_id, CurHostName, mci->mci_phase);
- r = reply(m, mci, e, TimeOuts.to_mail, NULL);
+ sm_setproctitle(TRUE, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_mail, NULL, &enhsc);
if (r < 0)
{
/* communications failure */
@@ -454,13 +1545,15 @@ smtpmailfrom(m, mci, e)
else if (r == 421)
{
/* service shutting down */
- mci_setstat(mci, EX_TEMPFAIL, "4.5.0", SmtpReplyBuffer);
+ mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"),
+ SmtpReplyBuffer);
smtpquit(m, mci, e);
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 4)
{
- mci_setstat(mci, EX_NOTSTICKY, smtptodsn(r), SmtpReplyBuffer);
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, smtptodsn(r)),
+ SmtpReplyBuffer);
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 2)
@@ -470,19 +1563,22 @@ smtpmailfrom(m, mci, e)
else if (r == 501)
{
/* syntax error in arguments */
- mci_setstat(mci, EX_NOTSTICKY, "5.5.2", SmtpReplyBuffer);
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.5.2"),
+ SmtpReplyBuffer);
return EX_DATAERR;
}
else if (r == 553)
{
/* mailbox name not allowed */
- mci_setstat(mci, EX_NOTSTICKY, "5.1.3", SmtpReplyBuffer);
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.1.3"),
+ SmtpReplyBuffer);
return EX_DATAERR;
}
else if (r == 552)
{
/* exceeded storage allocation */
- mci_setstat(mci, EX_NOTSTICKY, "5.3.4", SmtpReplyBuffer);
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.3.4"),
+ SmtpReplyBuffer);
if (bitset(MCIF_SIZE, mci->mci_flags))
e->e_flags |= EF_NO_BODY_RETN;
return EX_UNAVAILABLE;
@@ -490,20 +1586,22 @@ smtpmailfrom(m, mci, e)
else if (REPLYTYPE(r) == 5)
{
/* unknown error */
- mci_setstat(mci, EX_NOTSTICKY, "5.0.0", SmtpReplyBuffer);
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.0.0"),
+ SmtpReplyBuffer);
return EX_UNAVAILABLE;
}
if (LogLevel > 1)
{
sm_syslog(LOG_CRIT, e->e_id,
- "%.100s: SMTP MAIL protocol error: %s",
- CurHostName,
- shortenstring(SmtpReplyBuffer, 403));
+ "%.100s: SMTP MAIL protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
}
/* protocol error -- close up */
- mci_setstat(mci, EX_PROTOCOL, "5.5.1", SmtpReplyBuffer);
+ mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
+ SmtpReplyBuffer);
smtpquit(m, mci, e);
return EX_PROTOCOL;
}
@@ -533,9 +1631,16 @@ smtprcpt(to, m, mci, e)
register int r;
char *bufp;
char optbuf[MAXLINE];
+ char *enhsc;
+
+ enhsc = NULL;
+ optbuf[0] = '\0';
+ bufp = optbuf;
- strcpy(optbuf, "");
- bufp = &optbuf[strlen(optbuf)];
+ /*
+ ** warning: in the following it is assumed that the free space
+ ** in bufp is sizeof optbuf
+ */
if (bitset(MCIF_DSN, mci->mci_flags))
{
/* NOTIFY= parameter */
@@ -545,28 +1650,30 @@ smtprcpt(to, m, mci, e)
{
bool firstone = TRUE;
- strcat(bufp, " NOTIFY=");
+ (void) strlcat(bufp, " NOTIFY=", sizeof optbuf);
if (bitset(QPINGONSUCCESS, to->q_flags))
{
- strcat(bufp, "SUCCESS");
+ (void) strlcat(bufp, "SUCCESS", sizeof optbuf);
firstone = FALSE;
}
if (bitset(QPINGONFAILURE, to->q_flags))
{
if (!firstone)
- strcat(bufp, ",");
- strcat(bufp, "FAILURE");
+ (void) strlcat(bufp, ",",
+ sizeof optbuf);
+ (void) strlcat(bufp, "FAILURE", sizeof optbuf);
firstone = FALSE;
}
if (bitset(QPINGONDELAY, to->q_flags))
{
if (!firstone)
- strcat(bufp, ",");
- strcat(bufp, "DELAY");
+ (void) strlcat(bufp, ",",
+ sizeof optbuf);
+ (void) strlcat(bufp, "DELAY", sizeof optbuf);
firstone = FALSE;
}
if (firstone)
- strcat(bufp, "NEVER");
+ (void) strlcat(bufp, "NEVER", sizeof optbuf);
bufp += strlen(bufp);
}
@@ -583,28 +1690,30 @@ smtprcpt(to, m, mci, e)
smtpmessage("RCPT To:<%s>%s", m, mci, to->q_user, optbuf);
SmtpPhase = mci->mci_phase = "client RCPT";
- sm_setproctitle(TRUE, "%s %s: %s", e->e_id, CurHostName, mci->mci_phase);
- r = reply(m, mci, e, TimeOuts.to_rcpt, NULL);
+ sm_setproctitle(TRUE, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_rcpt, NULL, &enhsc);
to->q_rstatus = newstr(SmtpReplyBuffer);
- to->q_status = smtptodsn(r);
- to->q_statmta = mci->mci_host;
+ to->q_status = ENHSCN(enhsc, smtptodsn(r));
+ if (!bitnset(M_LMTP, m->m_flags))
+ to->q_statmta = mci->mci_host;
if (r < 0 || REPLYTYPE(r) == 4)
return EX_TEMPFAIL;
else if (REPLYTYPE(r) == 2)
return EX_OK;
else if (r == 550)
{
- to->q_status = "5.1.1";
+ to->q_status = ENHSCN(enhsc, "5.1.1");
return EX_NOUSER;
}
else if (r == 551)
{
- to->q_status = "5.1.6";
+ to->q_status = ENHSCN(enhsc, "5.1.6");
return EX_NOUSER;
}
else if (r == 553)
{
- to->q_status = "5.1.3";
+ to->q_status = ENHSCN(enhsc, "5.1.3");
return EX_NOUSER;
}
else if (REPLYTYPE(r) == 5)
@@ -615,12 +1724,13 @@ smtprcpt(to, m, mci, e)
if (LogLevel > 1)
{
sm_syslog(LOG_CRIT, e->e_id,
- "%.100s: SMTP RCPT protocol error: %s",
- CurHostName,
- shortenstring(SmtpReplyBuffer, 403));
+ "%.100s: SMTP RCPT protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
}
- mci_setstat(mci, EX_PROTOCOL, "5.5.1", SmtpReplyBuffer);
+ mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
+ SmtpReplyBuffer);
return EX_PROTOCOL;
}
/*
@@ -639,7 +1749,6 @@ smtprcpt(to, m, mci, e)
*/
static jmp_buf CtxDataTimeout;
-static void datatimeout __P((void));
int
smtpdata(m, mci, e)
@@ -652,7 +1761,9 @@ smtpdata(m, mci, e)
int rstat;
int xstat;
time_t timeout;
+ char *enhsc;
+ enhsc = NULL;
/*
** Send the data.
** First send the command and check that it is ok.
@@ -664,8 +1775,9 @@ smtpdata(m, mci, e)
/* send the command and check ok to proceed */
smtpmessage("DATA", m, mci);
SmtpPhase = mci->mci_phase = "client DATA 354";
- sm_setproctitle(TRUE, "%s %s: %s", e->e_id, CurHostName, mci->mci_phase);
- r = reply(m, mci, e, TimeOuts.to_datainit, NULL);
+ sm_setproctitle(TRUE, e, "%s %s: %s",
+ qid_printname(e), CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_datainit, NULL, &enhsc);
if (r < 0 || REPLYTYPE(r) == 4)
{
smtpquit(m, mci, e);
@@ -681,13 +1793,14 @@ smtpdata(m, mci, e)
if (LogLevel > 1)
{
sm_syslog(LOG_CRIT, e->e_id,
- "%.100s: SMTP DATA-1 protocol error: %s",
- CurHostName,
- shortenstring(SmtpReplyBuffer, 403));
+ "%.100s: SMTP DATA-1 protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
}
smtprset(m, mci, e);
- mci_setstat(mci, EX_PROTOCOL, "5.5.1", SmtpReplyBuffer);
- return (EX_PROTOCOL);
+ mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
+ SmtpReplyBuffer);
+ return EX_PROTOCOL;
}
/*
@@ -701,17 +1814,39 @@ smtpdata(m, mci, e)
mci->mci_errno = errno;
mci->mci_state = MCIS_ERROR;
mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
- syserr("451 timeout writing message to %s", CurHostName);
+
+ /*
+ ** If putbody() couldn't finish due to a timeout,
+ ** rewind it here in the timeout handler. See
+ ** comments at the end of putbody() for reasoning.
+ */
+
+ if (e->e_dfp != NULL)
+ (void) bfrewind(e->e_dfp);
+
+ errno = mci->mci_errno;
+ syserr("451 4.4.1 timeout writing message to %s", CurHostName);
smtpquit(m, mci, e);
return EX_TEMPFAIL;
}
- timeout = e->e_msgsize / 16;
- if (timeout < (time_t) 600)
- timeout = (time_t) 600;
- timeout += e->e_nrcpts * 300;
+ if (tTd(18, 101))
+ {
+ /* simulate a DATA timeout */
+ timeout = 1;
+ }
+ else
+ timeout = DATA_PROGRESS_TIMEOUT;
+
ev = setevent(timeout, datatimeout, 0);
+
+ if (tTd(18, 101))
+ {
+ /* simulate a DATA timeout */
+ (void) sleep(1);
+ }
+
/*
** Output the actual message.
*/
@@ -725,6 +1860,36 @@ smtpdata(m, mci, e)
clrevent(ev);
+# if _FFR_CATCH_BROKEN_MTAS
+ {
+ fd_set readfds;
+ struct timeval timeout;
+
+ FD_ZERO(&readfds);
+ FD_SET(fileno(mci->mci_in), &readfds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ if (select(fileno(mci->mci_in) + 1, FDSET_CAST &readfds,
+ NULL, NULL, &timeout) > 0 &&
+ FD_ISSET(fileno(mci->mci_in), &readfds))
+ {
+ /* terminate the message */
+ fprintf(mci->mci_out, ".%s", m->m_eol);
+ if (TrafficLogFile != NULL)
+ fprintf(TrafficLogFile, "%05d >>> .\n",
+ (int) getpid());
+ if (Verbose)
+ nmessage(">>> .");
+
+ mci->mci_errno = EIO;
+ mci->mci_state = MCIS_ERROR;
+ mci_setstat(mci, EX_PROTOCOL, "5.5.0", NULL);
+ smtpquit(m, mci, e);
+ return EX_PROTOCOL;
+ }
+ }
+# endif /* _FFR_CATCH_BROKEN_MTAS */
+
if (ferror(mci->mci_out))
{
/* error during processing -- don't send the dot */
@@ -744,10 +1909,11 @@ smtpdata(m, mci, e)
/* check for the results of the transaction */
SmtpPhase = mci->mci_phase = "client DATA status";
- sm_setproctitle(TRUE, "%s %s: %s", e->e_id, CurHostName, mci->mci_phase);
+ sm_setproctitle(TRUE, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
if (bitnset(M_LMTP, m->m_flags))
return EX_OK;
- r = reply(m, mci, e, TimeOuts.to_datafinal, NULL);
+ r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc);
if (r < 0)
{
smtpquit(m, mci, e);
@@ -767,18 +1933,24 @@ smtpdata(m, mci, e)
rstat = EX_UNAVAILABLE;
else
rstat = EX_PROTOCOL;
- mci_setstat(mci, xstat, smtptodsn(r), SmtpReplyBuffer);
+ mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)),
+ SmtpReplyBuffer);
if (e->e_statmsg != NULL)
free(e->e_statmsg);
- e->e_statmsg = newstr(&SmtpReplyBuffer[4]);
+ if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
+ (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
+ r += 5;
+ else
+ r = 4;
+ e->e_statmsg = newstr(&SmtpReplyBuffer[r]);
if (rstat != EX_PROTOCOL)
return rstat;
if (LogLevel > 1)
{
sm_syslog(LOG_CRIT, e->e_id,
- "%.100s: SMTP DATA-2 protocol error: %s",
- CurHostName,
- shortenstring(SmtpReplyBuffer, 403));
+ "%.100s: SMTP DATA-2 protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
}
return rstat;
}
@@ -787,7 +1959,28 @@ smtpdata(m, mci, e)
static void
datatimeout()
{
- longjmp(CtxDataTimeout, 1);
+ if (DataProgress)
+ {
+ time_t timeout;
+ register EVENT *ev;
+
+ /* check back again later */
+ if (tTd(18, 101))
+ {
+ /* simulate a DATA timeout */
+ timeout = 1;
+ }
+ else
+ timeout = DATA_PROGRESS_TIMEOUT;
+
+ DataProgress = FALSE;
+ ev = setevent(timeout, datatimeout, 0);
+ }
+ else
+ {
+ /* no progress, give up */
+ longjmp(CtxDataTimeout, 1);
+ }
}
/*
** SMTPGETSTAT -- get status code from DATA in LMTP
@@ -808,37 +2001,45 @@ smtpgetstat(m, mci, e)
ENVELOPE *e;
{
int r;
- int stat;
+ int status;
+ char *enhsc;
+ enhsc = NULL;
/* check for the results of the transaction */
- r = reply(m, mci, e, TimeOuts.to_datafinal, NULL);
+ r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc);
if (r < 0)
{
smtpquit(m, mci, e);
return EX_TEMPFAIL;
}
- if (e->e_statmsg != NULL)
- free(e->e_statmsg);
- e->e_statmsg = newstr(&SmtpReplyBuffer[4]);
if (REPLYTYPE(r) == 4)
- stat = EX_TEMPFAIL;
+ status = EX_TEMPFAIL;
else if (REPLYCLASS(r) != 5)
- stat = EX_PROTOCOL;
+ status = EX_PROTOCOL;
else if (REPLYTYPE(r) == 2)
- stat = EX_OK;
+ status = EX_OK;
else if (REPLYTYPE(r) == 5)
- stat = EX_UNAVAILABLE;
+ status = EX_UNAVAILABLE;
+ else
+ status = EX_PROTOCOL;
+ if (e->e_statmsg != NULL)
+ free(e->e_statmsg);
+ if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
+ (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
+ r += 5;
else
- stat = EX_PROTOCOL;
- mci_setstat(mci, stat, smtptodsn(r), SmtpReplyBuffer);
- if (LogLevel > 1 && stat == EX_PROTOCOL)
+ r = 4;
+ e->e_statmsg = newstr(&SmtpReplyBuffer[r]);
+ mci_setstat(mci, status, ENHSCN(enhsc, smtptodsn(r)),
+ SmtpReplyBuffer);
+ if (LogLevel > 1 && status == EX_PROTOCOL)
{
sm_syslog(LOG_CRIT, e->e_id,
- "%.100s: SMTP DATA-3 protocol error: %s",
- CurHostName,
- shortenstring(SmtpReplyBuffer, 403));
+ "%.100s: SMTP DATA-3 protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
}
- return stat;
+ return status;
}
/*
** SMTPQUIT -- close the SMTP connection.
@@ -862,10 +2063,15 @@ smtpquit(m, mci, e)
ENVELOPE *e;
{
bool oldSuprErrs = SuprErrs;
+ int rcode;
+
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
/*
** Suppress errors here -- we may be processing a different
- ** job when we do the quit connection, and we don't want the
+ ** job when we do the quit connection, and we don't want the
** new job to be penalized for something that isn't it's
** problem.
*/
@@ -873,18 +2079,38 @@ smtpquit(m, mci, e)
SuprErrs = TRUE;
/* send the quit message if we haven't gotten I/O error */
- if (mci->mci_state != MCIS_ERROR)
+ if (mci->mci_state != MCIS_ERROR &&
+ mci->mci_state != MCIS_QUITING)
{
+ int origstate = mci->mci_state;
+
SmtpPhase = "client QUIT";
+ mci->mci_state = MCIS_QUITING;
smtpmessage("QUIT", m, mci);
- (void) reply(m, mci, e, TimeOuts.to_quit, NULL);
+ (void) reply(m, mci, e, TimeOuts.to_quit, NULL, NULL);
SuprErrs = oldSuprErrs;
- if (mci->mci_state == MCIS_CLOSED)
+ if (mci->mci_state == MCIS_CLOSED ||
+ origstate == MCIS_CLOSED)
return;
}
/* now actually close the connection and pick up the zombie */
- (void) endmailer(mci, e, NULL);
+ rcode = endmailer(mci, e, NULL);
+ if (rcode != EX_OK)
+ {
+ char *mailer = NULL;
+
+ if (mci->mci_mailer != NULL &&
+ mci->mci_mailer->m_name != NULL)
+ mailer = mci->mci_mailer->m_name;
+
+ /* look for naughty mailers */
+ sm_syslog(LOG_ERR, e->e_id,
+ "smtpquit: mailer%s%s exited with exit value %d\n",
+ mailer == NULL ? "" : " ",
+ mailer == NULL ? "" : mailer,
+ rcode);
+ }
SuprErrs = oldSuprErrs;
}
@@ -900,13 +2126,23 @@ smtprset(m, mci, e)
{
int r;
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+
SmtpPhase = "client RSET";
smtpmessage("RSET", m, mci);
- r = reply(m, mci, e, TimeOuts.to_rset, NULL);
+ r = reply(m, mci, e, TimeOuts.to_rset, NULL, NULL);
if (r < 0)
mci->mci_state = MCIS_ERROR;
- else if (REPLYTYPE(r) == 2)
+ else
{
+ /*
+ ** Any response is deemed to be acceptable.
+ ** The standard does not state the proper action
+ ** to take when a value other than 250 is received.
+ */
+
mci->mci_state = MCIS_OPEN;
return;
}
@@ -922,12 +2158,17 @@ smtpprobe(mci)
{
int r;
MAILER *m = mci->mci_mailer;
+ ENVELOPE *e;
extern ENVELOPE BlankEnvelope;
- ENVELOPE *e = &BlankEnvelope;
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+
+ e = &BlankEnvelope;
SmtpPhase = "client probe";
smtpmessage("RSET", m, mci);
- r = reply(m, mci, e, TimeOuts.to_miscshort, NULL);
+ r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, NULL);
if (r < 0 || REPLYTYPE(r) != 2)
smtpquit(m, mci, e);
return r;
@@ -951,23 +2192,26 @@ smtpprobe(mci)
*/
int
-reply(m, mci, e, timeout, pfunc)
+reply(m, mci, e, timeout, pfunc, enhstat)
MAILER *m;
MCI *mci;
ENVELOPE *e;
time_t timeout;
void (*pfunc)();
+ char **enhstat;
{
register char *bufp;
register int r;
bool firstline = TRUE;
char junkbuf[MAXLINE];
+ static char enhstatcode[ENHSCLEN];
+ int save_errno;
if (mci->mci_out != NULL)
(void) fflush(mci->mci_out);
if (tTd(18, 1))
- printf("reply\n");
+ dprintf("reply\n");
/*
** Read the input line, being careful not to hang.
@@ -984,10 +2228,10 @@ reply(m, mci, e, timeout, pfunc)
/* if we are in the process of closing just give the code */
if (mci->mci_state == MCIS_CLOSED)
- return (SMTPCLOSING);
+ return SMTPCLOSING;
if (mci->mci_out != NULL)
- fflush(mci->mci_out);
+ (void) fflush(mci->mci_out);
/* get the line from the other side */
p = sfgets(bufp, MAXLINE, mci->mci_in, timeout, SmtpPhase);
@@ -1009,7 +2253,8 @@ reply(m, mci, e, timeout, pfunc)
mci->mci_errno = errno;
oldholderrs = HoldErrs;
HoldErrs = TRUE;
- usrerr("451 reply: read error from %s", CurHostName);
+ usrerr("451 4.4.1 reply: read error from %s",
+ CurHostName == NULL ? "NO_HOST" : CurHostName);
/* errors on QUIT should not be persistent */
if (strncmp(SmtpMsgBuffer, "QUIT", 4) != 0)
@@ -1017,15 +2262,16 @@ reply(m, mci, e, timeout, pfunc)
/* if debugging, pause so we can see state */
if (tTd(18, 100))
- pause();
+ (void) pause();
mci->mci_state = MCIS_ERROR;
+ save_errno = errno;
smtpquit(m, mci, e);
-#if XDEBUG
+# if XDEBUG
{
char wbuf[MAXLINE];
- char *p = wbuf;
int wbufleft = sizeof wbuf;
+ p = wbuf;
if (e->e_to != NULL)
{
int plen;
@@ -1041,9 +2287,10 @@ reply(m, mci, e, timeout, pfunc)
SmtpPhase);
checkfd012(wbuf);
}
-#endif
+# endif /* XDEBUG */
+ errno = save_errno;
HoldErrs = oldholderrs;
- return (-1);
+ return -1;
}
fixcrlf(bufp, TRUE);
@@ -1057,7 +2304,7 @@ reply(m, mci, e, timeout, pfunc)
/* inform user who we are chatting with */
fprintf(CurEnv->e_xfp,
"... while talking to %s:\n",
- CurHostName);
+ CurHostName == NULL ? "NO_HOST" : CurHostName);
SmtpNeedIntro = FALSE;
}
if (SmtpMsgBuffer[0] != '\0')
@@ -1072,13 +2319,15 @@ reply(m, mci, e, timeout, pfunc)
if (Verbose)
nmessage("050 %s", bufp);
- /* ignore improperly formated input */
- if (!(isascii(bufp[0]) && isdigit(bufp[0])) ||
- !(isascii(bufp[1]) && isdigit(bufp[1])) ||
- !(isascii(bufp[2]) && isdigit(bufp[2])) ||
- !(bufp[3] == ' ' || bufp[3] == '-' || bufp[3] == '\0'))
+ /* ignore improperly formatted input */
+ if (!ISSMTPREPLY(bufp))
continue;
+ if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
+ enhstat != NULL &&
+ extenhsc(bufp + 4, ' ', enhstatcode) > 0)
+ *enhstat = enhstatcode;
+
/* process the line */
if (pfunc != NULL)
(*pfunc)(bufp, firstline, m, mci, e);
@@ -1118,7 +2367,7 @@ reply(m, mci, e, timeout, pfunc)
smtpquit(m, mci, e);
}
- return (r);
+ return r;
}
/*
** SMTPMESSAGE -- send message to server
@@ -1137,15 +2386,15 @@ reply(m, mci, e, timeout, pfunc)
/*VARARGS1*/
void
-#ifdef __STDC__
+# ifdef __STDC__
smtpmessage(char *f, MAILER *m, MCI *mci, ...)
-#else
+# else /* __STDC__ */
smtpmessage(f, m, mci, va_alist)
char *f;
MAILER *m;
MCI *mci;
va_dcl
-#endif
+# endif /* __STDC__ */
{
VA_LOCAL_DECL
@@ -1165,8 +2414,8 @@ smtpmessage(f, m, mci, va_alist)
}
else if (tTd(18, 1))
{
- printf("smtpmessage: NULL mci_out\n");
+ dprintf("smtpmessage: NULL mci_out\n");
}
}
-# endif /* SMTP */
+#endif /* SMTP */
OpenPOWER on IntegriCloud