#include <sendmail.h>
SM_RCSID("@(#)$Id: usersmtp.c,v 8.473 2009/06/17 17:26:51 ca Exp $")
#include <sysexits.h>
static void esmtp_check __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
static void helo_options __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
static int smtprcptstat __P((ADDRESS *, MAILER *, MCI *, ENVELOPE *));
#if SASL
extern void *sm_sasl_malloc __P((unsigned long));
extern void sm_sasl_free __P((void *));
#endif
#define REPLYCLASS(r) (((r) / 10) % 10)
#define SMTPCLOSING 421
#define ENHSCN(e, d) ((e) == NULL ? (d) : (e))
#define ENHSCN_RPOOL(e, d, rpool) \
((e) == NULL ? (d) : sm_rpool_strdup_x(rpool, e))
static char SmtpMsgBuffer[MAXLINE];
static char SmtpReplyBuffer[MAXLINE];
static bool SmtpNeedIntro;
void
smtpinit(m, mci, e, onlyhelo)
MAILER *m;
register MCI *mci;
ENVELOPE *e;
bool onlyhelo;
{
register int r;
int state;
register char *p;
register char *hn;
char *enhsc;
enhsc = NULL;
if (tTd(18, 1))
{
sm_dprintf("smtpinit ");
mci_dump(sm_debug_file(), mci, false);
}
SmtpError[0] = '\0';
SmtpMsgBuffer[0] = '\0';
CurHostName = mci->mci_host;
if (CurHostName == NULL)
CurHostName = MyHostName;
SmtpNeedIntro = true;
state = mci->mci_state;
switch (state)
{
case MCIS_MAIL:
case MCIS_RCPT:
case MCIS_DATA:
smtprset(m, mci, e);
case MCIS_OPEN:
if (!onlyhelo)
return;
break;
case MCIS_ERROR:
case MCIS_QUITING:
case MCIS_SSD:
smtpquit(m, mci, e);
case MCIS_CLOSED:
syserr("451 4.4.0 smtpinit: state CLOSED (was %d)", state);
return;
case MCIS_OPENING:
break;
}
if (onlyhelo)
goto helo;
mci->mci_state = MCIS_OPENING;
clrsessenvelope(e);
SmtpPhase = mci->mci_phase = "client greeting";
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,
XS_DEFAULT);
if (r < 0)
goto tempfail1;
if (REPLYTYPE(r) == 4)
goto tempfail2;
if (REPLYTYPE(r) != 2)
goto unavailable;
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 _FFR_IGNORE_EXT_ON_HELO
mci->mci_flags &= ~MCIF_HELO;
#endif
if (bitnset(M_LMTP, m->m_flags))
{
smtpmessage("LHLO %s", m, mci, hn);
SmtpPhase = mci->mci_phase = "client LHLO";
}
else if (bitset(MCIF_ESMTP, mci->mci_flags) &&
!bitnset(M_FSMTP, m->m_flags))
{
smtpmessage("EHLO %s", m, mci, hn);
SmtpPhase = mci->mci_phase = "client EHLO";
}
else
{
smtpmessage("HELO %s", m, mci, hn);
SmtpPhase = mci->mci_phase = "client HELO";
#if _FFR_IGNORE_EXT_ON_HELO
mci->mci_flags |= MCIF_HELO;
#endif
}
sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
CurHostName, mci->mci_phase);
r = reply(m, mci, e,
bitnset(M_LMTP, m->m_flags) ? TimeOuts.to_lhlo
: TimeOuts.to_helo,
helo_options, NULL, XS_DEFAULT);
if (r < 0)
goto tempfail1;
else if (REPLYTYPE(r) == 5)
{
if (bitset(MCIF_ESMTP, mci->mci_flags) &&
!bitnset(M_LMTP, m->m_flags))
{
mci->mci_flags &= ~MCIF_ESMTP;
goto tryhelo;
}
goto unavailable;
}
else if (REPLYTYPE(r) != 2)
goto tempfail2;
p = strchr(&SmtpReplyBuffer[4], ' ');
if (p != NULL)
*p = '\0';
if (!bitnset(M_NOLOOPCHECK, m->m_flags) &&
!bitnset(M_LMTP, m->m_flags) &&
sm_strcasecmp(&SmtpReplyBuffer[4], MyHostName) == 0)
{
syserr("553 5.3.5 %s config error: mail loops back to me (MX problem?)",
CurHostName);
mci_setstat(mci, EX_CONFIG, "5.3.5",
"553 5.3.5 system config error");
mci->mci_errno = 0;
smtpquit(m, mci, e);
return;
}
if ((UseMSP && Verbose && bitset(MCIF_VERB, mci->mci_flags))
# if !_FFR_DEPRECATE_MAILER_FLAG_I
|| bitnset(M_INTERNAL, m->m_flags)
# endif
)
{
smtpmessage("VERB", m, mci);
r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, &enhsc,
XS_DEFAULT);
if (r < 0)
goto tempfail1;
}
if (mci->mci_state != MCIS_CLOSED)
{
mci->mci_state = MCIS_OPEN;
return;
}
tempfail1:
mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.4.2"), NULL);
if (mci->mci_state != MCIS_CLOSED)
smtpquit(m, mci, e);
return;
tempfail2:
mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"),
SmtpReplyBuffer);
if (mci->mci_state != MCIS_CLOSED)
smtpquit(m, mci, e);
return;
unavailable:
mci_setstat(mci, EX_UNAVAILABLE, "5.5.0", SmtpReplyBuffer);
smtpquit(m, mci, e);
return;
}
static void
esmtp_check(line, firstline, m, mci, e)
char *line;
bool firstline;
MAILER *m;
register MCI *mci;
ENVELOPE *e;
{
if (strstr(line, "ESMTP") != NULL)
mci->mci_flags |= MCIF_ESMTP;
if (strstr(line, "8BIT-OK") != NULL)
mci->mci_flags |= MCIF_8BITOK;
}
#if SASL
static char *str_union __P((char *, char *, SM_RPOOL_T *));
static char *
str_union(s1, s2, rpool)
char *s1, *s2;
SM_RPOOL_T *rpool;
{
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 *) sm_rpool_malloc(rpool, rl + 2);
if (res == NULL)
{
if (l1 > l2)
return s1;
return s2;
}
(void) sm_strlcpy(res, s1, rl);
hr = res + l1;
h1 = s2;
h = s2;
while (h != NULL && *h1 != '\0')
{
if ((h = strchr(h1, ' ')) != NULL)
*h = '\0';
l1 = strlen(h1);
if (iteminlist(h1, s1, " ") == NULL)
{
*hr++ = ' ';
memcpy(hr, h1, l1);
hr += l1;
*hr = '\0';
}
if (h != NULL)
{
*h = ' ';
h1 = h + 1;
}
}
return res;
}
#endif
static void
helo_options(line, firstline, m, mci, e)
char *line;
bool firstline;
MAILER *m;
register MCI *mci;
ENVELOPE *e;
{
register char *p;
#if _FFR_IGNORE_EXT_ON_HELO
static bool logged = false;
#endif
if (firstline)
{
#if SASL
mci->mci_saslcap = NULL;
#endif
#if _FFR_IGNORE_EXT_ON_HELO
logged = false;
#endif
return;
}
#if _FFR_IGNORE_EXT_ON_HELO
else if (bitset(MCIF_HELO, mci->mci_flags))
{
if (LogLevel > 8 && !logged)
{
sm_syslog(LOG_WARNING, NOQID,
"server=%s [%s] returned extensions despite HELO command",
macvalue(macid("{server_name}"), e),
macvalue(macid("{server_addr}"), e));
logged = true;
}
return;
}
#endif
if (strlen(line) < 5)
return;
line += 4;
p = strpbrk(line, " =");
if (p != NULL)
*p++ = '\0';
if (sm_strcasecmp(line, "size") == 0)
{
mci->mci_flags |= MCIF_SIZE;
if (p != NULL)
mci->mci_maxsize = atol(p);
}
else if (sm_strcasecmp(line, "8bitmime") == 0)
{
mci->mci_flags |= MCIF_8BITMIME;
mci->mci_flags &= ~MCIF_7BIT;
}
else if (sm_strcasecmp(line, "expn") == 0)
mci->mci_flags |= MCIF_EXPN;
else if (sm_strcasecmp(line, "dsn") == 0)
mci->mci_flags |= MCIF_DSN;
else if (sm_strcasecmp(line, "enhancedstatuscodes") == 0)
mci->mci_flags |= MCIF_ENHSTAT;
else if (sm_strcasecmp(line, "pipelining") == 0)
mci->mci_flags |= MCIF_PIPELINED;
else if (sm_strcasecmp(line, "verb") == 0)
mci->mci_flags |= MCIF_VERB;
#if STARTTLS
else if (sm_strcasecmp(line, "starttls") == 0)
mci->mci_flags |= MCIF_TLS;
#endif
else if (sm_strcasecmp(line, "deliverby") == 0)
{
mci->mci_flags |= MCIF_DLVR_BY;
if (p != NULL)
mci->mci_min_by = atol(p);
}
#if SASL
else if (sm_strcasecmp(line, "auth") == 0)
{
if (p != NULL && *p != '\0')
{
if (mci->mci_saslcap != NULL)
{
mci->mci_saslcap = str_union(mci->mci_saslcap,
p, mci->mci_rpool);
mci->mci_flags |= MCIF_AUTH;
}
else
{
int l;
l = strlen(p) + 1;
mci->mci_saslcap = (char *)
sm_rpool_malloc(mci->mci_rpool, l);
if (mci->mci_saslcap != NULL)
{
(void) sm_strlcpy(mci->mci_saslcap, p,
l);
mci->mci_flags |= MCIF_AUTH;
}
}
}
}
#endif
}
#if SASL
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 int readauth __P((char *, bool, SASL_AI_T *m, SM_RPOOL_T *));
static int getauth __P((MCI *, ENVELOPE *, SASL_AI_T *));
static char *removemech __P((char *, char *, SM_RPOOL_T *));
static int attemptauth __P((MAILER *, MCI *, ENVELOPE *, SASL_AI_T *));
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 },
#define CB_SAFESASL_IDX 4
{ SASL_CB_LIST_END, NULL, NULL }
};
static bool sasl_clt_init = false;
static int
init_sasl_client()
{
int result;
if (sasl_clt_init)
return SASL_OK;
result = sasl_client_init(callbacks);
if (result == SASL_OK)
sasl_clt_init = true;
return result;
}
void
stop_sasl_client()
{
if (!sasl_clt_init)
return;
sasl_clt_init = false;
sasl_done();
}
static void getsasldata __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
static void
getsasldata(line, firstline, m, mci, e)
char *line;
bool firstline;
MAILER *m;
register MCI *mci;
ENVELOPE *e;
{
int len;
int result;
# if SASL < 20000
char *out;
# endif
len = strlen(line);
if ((len <= 4) ||
(line[0] != '3') ||
!isascii(line[1]) || !isdigit(line[1]) ||
!isascii(line[2]) || !isdigit(line[2]))
{
SM_FREE_CLR(mci->mci_sasl_string);
return;
}
line += 4;
len -= 4;
# if SASL >= 20000
if (mci->mci_sasl_string != NULL)
{
if (mci->mci_sasl_string_len <= len)
{
sm_free(mci->mci_sasl_string);
mci->mci_sasl_string = xalloc(len + 1);
}
}
else
mci->mci_sasl_string = xalloc(len + 1);
result = sasl_decode64(line, len, mci->mci_sasl_string, len + 1,
(unsigned int *) &mci->mci_sasl_string_len);
if (result != SASL_OK)
{
mci->mci_sasl_string_len = 0;
*mci->mci_sasl_string = '\0';
}
# else
out = (char *) sm_rpool_malloc_x(mci->mci_rpool, len + 1);
result = sasl_decode64(line, len, out, (unsigned int *) &len);
if (result != SASL_OK)
{
len = 0;
*out = '\0';
}
if (mci->mci_sasl_string != NULL)
{
if (mci->mci_sasl_string_len <= len)
{
sm_free(mci->mci_sasl_string);
mci->mci_sasl_string = xalloc(len + 1);
}
}
else
mci->mci_sasl_string = xalloc(len + 1);
memcpy(mci->mci_sasl_string, out, len);
mci->mci_sasl_string[len] = '\0';
mci->mci_sasl_string_len = len;
# endif
return;
}
static char *sasl_info_name[] =
{
"user id",
"authentication id",
"password",
"realm",
"mechlist"
};
static int
readauth(filename, safe, sai, rpool)
char *filename;
bool safe;
SASL_AI_T *sai;
SM_RPOOL_T *rpool;
{
SM_FILE_T *f;
long sff;
pid_t pid;
int lc;
char *s;
char buf[MAXLINE];
if (filename == NULL || filename[0] == '\0')
return EX_UNAVAILABLE;
#if !_FFR_ALLOW_SASLINFO
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 = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
(void *) &fd, SM_IO_RDONLY, NULL);
}
else
#endif
{
pid = -1;
sff = SFF_REGONLY|SFF_SAFEDIRPATH|SFF_NOWLINK
|SFF_NOGWFILES|SFF_NOWWFILES|SFF_NOWRFILES;
# if _FFR_GROUPREADABLEAUTHINFOFILE
if (!bitnset(DBS_GROUPREADABLEAUTHINFOFILE, DontBlameSendmail))
# endif
sff |= SFF_NOGRFILES;
if (DontLockReadFiles)
sff |= SFF_NOLOCK;
#if _FFR_ALLOW_SASLINFO
sff |= SFF_MUSTOWN;
#else
if (safe)
sff |= SFF_OPENASROOT;
#endif
f = safefopen(filename, O_RDONLY, 0, sff);
}
if (f == NULL)
{
if (LogLevel > 5)
sm_syslog(LOG_ERR, NOQID,
"AUTH=client, error: can't open %s: %s",
filename, sm_errstring(errno));
return EX_TEMPFAIL;
}
lc = 0;
while (lc <= SASL_MECHLIST &&
sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
{
if (buf[0] != '#')
{
(*sai)[lc] = sm_rpool_strdup_x(rpool, buf);
if ((s = strchr((*sai)[lc], '\n')) != NULL)
*s = '\0';
lc++;
}
}
(void) sm_io_close(f, SM_TIME_DEFAULT);
if (pid > 0)
(void) waitfor(pid);
if (lc < SASL_PASSWORD)
{
if (LogLevel > 8)
sm_syslog(LOG_ERR, NOQID,
"AUTH=client, error: can't read %s from %s",
sasl_info_name[lc + 1], filename);
return EX_TEMPFAIL;
}
return EX_OK;
}
static int
getauth(mci, e, sai)
MCI *mci;
ENVELOPE *e;
SASL_AI_T *sai;
{
int i, r, l, got, ret;
char **pvp;
char pvpbuf[PSBUFSIZE];
r = rscap("authinfo", macvalue(macid("{server_name}"), e),
macvalue(macid("{server_addr}"), e), e,
&pvp, pvpbuf, sizeof(pvpbuf));
if (r != EX_OK)
return EX_UNAVAILABLE;
if (pvp == NULL || pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
return EX_OK;
if (pvp[1] != NULL && sm_strncasecmp(pvp[1], "temp", 4) == 0)
return EX_TEMPFAIL;
ret = EX_OK;
i = 0;
got = 0;
while (i < SASL_ENTRIES)
{
if (pvp[i + 1] == NULL)
break;
if (pvp[i + 1][0] != '"')
break;
switch (pvp[i + 1][1])
{
case 'U':
case 'u':
r = SASL_USER;
break;
case 'I':
case 'i':
r = SASL_AUTHID;
break;
case 'P':
case 'p':
r = SASL_PASSWORD;
break;
case 'R':
case 'r':
r = SASL_DEFREALM;
break;
case 'M':
case 'm':
r = SASL_MECHLIST;
break;
default:
goto fail;
}
l = strlen(pvp[i + 1]);
if (l <= 3 || pvp[i + 1][l - 1] != '"')
goto fail;
pvp[i + 1][l - 1] = '\0';
l -= 4;
(*sai)[r] = (char *) sm_rpool_malloc(mci->mci_rpool, l + 1);
if ((*sai)[r] == NULL)
goto tempfail;
if (pvp[i + 1][2] == ':')
{
(void) sm_strlcpy((*sai)[r], pvp[i + 1] + 3, l + 1);
got |= 1 << r;
}
else if (pvp[i + 1][2] == '=')
{
unsigned int len;
# if SASL >= 20000
ret = sasl_decode64(pvp[i + 1] + 3,
(unsigned int) l, (*sai)[r],
(unsigned int) l + 1, &len);
# else
ret = sasl_decode64(pvp[i + 1] + 3,
(unsigned int) l, (*sai)[r], &len);
# endif
if (ret != SASL_OK)
goto fail;
got |= 1 << r;
}
else
goto fail;
if (tTd(95, 5))
sm_syslog(LOG_DEBUG, NOQID, "getauth %s=%s",
sasl_info_name[r], (*sai)[r]);
++i;
}
if (!(bitset(SASL_USER_BIT|SASL_AUTHID_BIT, got) &&
bitset(SASL_PASSWORD_BIT, got)))
goto fail;
if (!bitset(SASL_AUTHID_BIT, got))
{
l = strlen((*sai)[SASL_USER]) + 1;
(*sai)[SASL_AUTHID] = (char *) sm_rpool_malloc(mci->mci_rpool,
l + 1);
if ((*sai)[SASL_AUTHID] == NULL)
goto tempfail;
(void) sm_strlcpy((*sai)[SASL_AUTHID], (*sai)[SASL_USER], l);
}
if (!bitset(SASL_USER_BIT, got))
{
l = strlen((*sai)[SASL_AUTHID]) + 1;
(*sai)[SASL_USER] = (char *) sm_rpool_malloc(mci->mci_rpool,
l + 1);
if ((*sai)[SASL_USER] == NULL)
goto tempfail;
(void) sm_strlcpy((*sai)[SASL_USER], (*sai)[SASL_AUTHID], l);
}
return EX_OK;
tempfail:
ret = EX_TEMPFAIL;
fail:
if (LogLevel > 8)
sm_syslog(LOG_WARNING, NOQID,
"AUTH=client, relay=%.64s [%.16s], authinfo %sfailed",
macvalue(macid("{server_name}"), e),
macvalue(macid("{server_addr}"), e),
ret == EX_TEMPFAIL ? "temp" : "");
for (i = 0; i <= SASL_MECHLIST; i++)
(*sai)[i] = NULL;
return ret;
}
# if SASL >= 20000
static int
getsimple(context, id, result, len)
void *context;
int id;
const char **result;
unsigned *len;
{
SASL_AI_T *sai;
if (result == NULL || context == NULL)
return SASL_BADPARAM;
sai = (SASL_AI_T *) context;
switch (id)
{
case SASL_CB_USER:
*result = (*sai)[SASL_USER];
if (tTd(95, 5))
sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
*result);
if (len != NULL)
*len = *result != NULL ? strlen(*result) : 0;
break;
case SASL_CB_AUTHNAME:
*result = (*sai)[SASL_AUTHID];
if (tTd(95, 5))
sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
*result);
if (len != NULL)
*len = *result != NULL ? strlen(*result) : 0;
break;
case SASL_CB_LANGUAGE:
*result = NULL;
if (len != NULL)
*len = 0;
break;
default:
return SASL_BADPARAM;
}
return SASL_OK;
}
static int
getsecret(conn, context, id, psecret)
sasl_conn_t *conn;
SM_UNUSED(void *context);
int id;
sasl_secret_t **psecret;
{
int len;
char *authpass;
MCI *mci;
if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
return SASL_BADPARAM;
mci = (MCI *) context;
authpass = mci->mci_sai[SASL_PASSWORD];
len = strlen(authpass);
*psecret = (sasl_secret_t *) sm_rpool_malloc(mci->mci_rpool,
sizeof(sasl_secret_t) +
len + 1);
if (*psecret == NULL)
return SASL_FAIL;
(void) sm_strlcpy((char *) (*psecret)->data, authpass, len + 1);
(*psecret)->len = (unsigned long) len;
return SASL_OK;
}
# else
static int
getsimple(context, id, result, len)
void *context;
int id;
const char **result;
unsigned *len;
{
char *h, *s;
# if SASL > 10509
bool addrealm;
# endif
size_t l;
SASL_AI_T *sai;
char *authid = NULL;
if (result == NULL || context == NULL)
return SASL_BADPARAM;
sai = (SASL_AI_T *) context;
switch (id)
{
case SASL_CB_USER:
l = strlen((*sai)[SASL_USER]) + 1;
s = sm_sasl_malloc(l);
if (s == NULL)
{
if (len != NULL)
*len = 0;
*result = NULL;
return SASL_NOMEM;
}
(void) sm_strlcpy(s, (*sai)[SASL_USER], l);
*result = s;
if (tTd(95, 5))
sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
*result);
if (len != NULL)
*len = *result != NULL ? strlen(*result) : 0;
break;
case SASL_CB_AUTHNAME:
h = (*sai)[SASL_AUTHID];
# if SASL > 10509
addrealm = (*sai)[SASL_MECH] != NULL &&
sm_strcasecmp((*sai)[SASL_MECH], "CRAM-MD5") == 0;
if (addrealm && h != NULL && strchr(h, '@') == NULL)
{
if ((*sai)[SASL_ID_REALM] == NULL)
{
char *realm;
realm = (*sai)[SASL_DEFREALM];
if (*realm == '\0')
{
authid = h;
(*sai)[SASL_ID_REALM] = NULL;
}
else
{
l = strlen(h) + strlen(realm) + 2;
authid = sm_sasl_malloc(l);
if (authid != NULL)
{
(void) sm_snprintf(authid, l,
"%s@%s",
h, realm);
(*sai)[SASL_ID_REALM] = authid;
}
else
{
authid = h;
(*sai)[SASL_ID_REALM] = NULL;
}
}
}
else
authid = (*sai)[SASL_ID_REALM];
}
else
# endif
authid = h;
l = strlen(authid) + 1;
s = sm_sasl_malloc(l);
if (s == NULL)
{
if (len != NULL)
*len = 0;
*result = NULL;
return SASL_NOMEM;
}
(void) sm_strlcpy(s, authid, l);
*result = s;
if (tTd(95, 5))
sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
*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;
}
static int
getsecret(conn, context, id, psecret)
sasl_conn_t *conn;
SM_UNUSED(void *context);
int id;
sasl_secret_t **psecret;
{
int len;
char *authpass;
SASL_AI_T *sai;
if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
return SASL_BADPARAM;
sai = (SASL_AI_T *) context;
authpass = (*sai)[SASL_PASSWORD];
len = strlen(authpass);
*psecret = (sasl_secret_t *) sm_sasl_malloc(sizeof(sasl_secret_t) +
len + 1);
if (*psecret == NULL)
return SASL_FAIL;
(void) sm_strlcpy((*psecret)->data, authpass, len + 1);
(*psecret)->len = (unsigned long) len;
return SASL_OK;
}
# endif
int
#if SASL > 10515
safesaslfile(context, file, type)
#else
safesaslfile(context, file)
#endif
void *context;
# if SASL >= 20000
const char *file;
# else
char *file;
# endif
#if SASL > 10515
# if SASL >= 20000
sasl_verify_type_t type;
# else
int type;
# endif
#endif
{
long sff;
int r;
#if SASL <= 10515
size_t len;
#endif
char *p;
if (file == NULL || *file == '\0')
return SASL_OK;
sff = SFF_SAFEDIRPATH|SFF_NOWLINK|SFF_NOWWFILES|SFF_ROOTOK;
#if SASL <= 10515
if ((p = strrchr(file, '/')) == NULL)
p = file;
else
++p;
len = strlen(p);
if ((len <= 3 || strncmp(p, "lib", 3) != 0) &&
(len <= 5 || strncmp(p + len - 5, ".conf", 5) != 0))
{
if (!bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
sff |= SFF_NORFILES;
if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
sff |= SFF_NOGWFILES;
}
#else
if (type == SASL_VRFY_PASSWD)
{
if (bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
sff |= SFF_NOWRFILES;
else
sff |= SFF_NORFILES;
if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
sff |= SFF_NOGWFILES;
}
#endif
p = (char *) file;
if ((r = safefile(p, RunAsUid, RunAsGid, RunAsUserName, sff,
S_IRUSR, NULL)) == 0)
return SASL_OK;
if (LogLevel > (r != ENOENT ? 8 : 10))
sm_syslog(LOG_WARNING, NOQID, "error: safesasl(%s) failed: %s",
p, sm_errstring(r));
return SASL_CONTINUE;
}
static int
saslgetrealm(context, id, availrealms, result)
void *context;
int id;
const char **availrealms;
const char **result;
{
char *r;
SASL_AI_T *sai;
sai = (SASL_AI_T *) context;
if (sai == NULL)
return SASL_FAIL;
r = (*sai)[SASL_DEFREALM];
if (LogLevel > 12)
sm_syslog(LOG_INFO, NOQID,
"AUTH=client, realm=%s, available realms=%s",
r == NULL ? "<No Realm>" : r,
(availrealms == NULL || *availrealms == NULL)
? "<No Realms>" : *availrealms);
if (availrealms != NULL && *availrealms != NULL)
{
if (iteminlist(context, (char *)(*availrealms + 1), " ,}") ==
NULL)
{
if (LogLevel > 8)
sm_syslog(LOG_ERR, NOQID,
"AUTH=client, realm=%s not in list=%s",
r, *availrealms);
return SASL_FAIL;
}
}
*result = r;
return SASL_OK;
}
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 (sm_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;
}
static char *
removemech(rem, list, rpool)
char *rem;
char *list;
SM_RPOOL_T *rpool;
{
char *ret;
char *needle;
int len;
if (list == NULL)
return NULL;
if (rem == NULL || *rem == '\0')
{
return NULL;
}
if ((needle = iteminlist(rem, list, " ")) == NULL)
{
return list;
}
len = strlen(list) - strlen(rem);
if (len <= 0)
{
ret = (char *) sm_rpool_malloc_x(rpool, 1);
*ret = '\0';
return ret;
}
ret = (char *) sm_rpool_malloc_x(rpool, len);
memset(ret, '\0', len);
memcpy(ret, list, needle - list);
len = strlen(needle) - strlen(rem) - 1;
if (len > 0)
{
memcpy(ret + (needle - list),
list + (needle - list) + strlen(rem) + 1,
len);
}
else
ret[(needle - list) - 1] = '\0';
return ret;
}
static int
attemptauth(m, mci, e, sai)
MAILER *m;
MCI *mci;
ENVELOPE *e;
SASL_AI_T *sai;
{
int saslresult, smtpresult;
# if SASL >= 20000
sasl_ssf_t ssf;
const char *auth_id;
const char *out;
# else
sasl_external_properties_t ssf;
char *out;
# endif
unsigned int outlen;
sasl_interact_t *client_interact = NULL;
char *mechusing;
sasl_security_properties_t ssp;
char in64[MAXOUTLEN + 1];
#if NETINET || (NETINET6 && SASL >= 20000)
extern SOCKADDR CurHostAddr;
#endif
(*sai)[SASL_MECH] = NULL;
if (mci->mci_conn != NULL)
sasl_dispose(&(mci->mci_conn));
# if SASL >= 20000
saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
: "smtp",
CurHostName, NULL, NULL, callbacks, 0,
&mci->mci_conn);
# else
saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
: "smtp",
CurHostName, NULL, 0, &mci->mci_conn);
# endif
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
(void) memset(&ssp, '\0', sizeof(ssp));
{
ssp.max_ssf = MaxSLBits;
ssp.maxbufsize = MAXOUTLEN;
# if 0
ssp.security_flags = SASL_SEC_NOPLAINTEXT;
# endif
}
saslresult = sasl_setprop(mci->mci_conn, SASL_SEC_PROPS, &ssp);
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
# if SASL >= 20000
ssf = 0;
auth_id = NULL;
# if STARTTLS
out = macvalue(macid("{cert_subject}"), e);
if (out != NULL && *out != '\0')
auth_id = out;
out = macvalue(macid("{cipher_bits}"), e);
if (out != NULL && *out != '\0')
ssf = atoi(out);
# endif
saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
saslresult = sasl_setprop(mci->mci_conn, SASL_AUTH_EXTERNAL, auth_id);
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
# if NETINET || NETINET6
if (mci->mci_out != NULL && (
# if NETINET6
CurHostAddr.sa.sa_family == AF_INET6 ||
# endif
CurHostAddr.sa.sa_family == AF_INET))
{
SOCKADDR_LEN_T addrsize;
SOCKADDR saddr_l;
char localip[60], remoteip[60];
switch (CurHostAddr.sa.sa_family)
{
case AF_INET:
addrsize = sizeof(struct sockaddr_in);
break;
# if NETINET6
case AF_INET6:
addrsize = sizeof(struct sockaddr_in6);
break;
# endif
default:
break;
}
if (iptostring(&CurHostAddr, addrsize,
remoteip, sizeof(remoteip)))
{
if (sasl_setprop(mci->mci_conn, SASL_IPREMOTEPORT,
remoteip) != SASL_OK)
return EX_TEMPFAIL;
}
addrsize = sizeof(saddr_l);
if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
NULL),
(struct sockaddr *) &saddr_l, &addrsize) == 0)
{
if (iptostring(&saddr_l, addrsize,
localip, sizeof(localip)))
{
if (sasl_setprop(mci->mci_conn,
SASL_IPLOCALPORT,
localip) != SASL_OK)
return EX_TEMPFAIL;
}
}
}
# endif
saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
&client_interact,
&out, &outlen,
(const char **) &mechusing);
# else
ssf.ssf = 0;
ssf.auth_id = NULL;
# if STARTTLS
out = macvalue(macid("{cert_subject}"), e);
if (out != NULL && *out != '\0')
ssf.auth_id = out;
out = macvalue(macid("{cipher_bits}"), e);
if (out != NULL && *out != '\0')
ssf.ssf = atoi(out);
# endif
saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
# if NETINET
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(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
NULL),
(struct sockaddr *) &saddr_l, &addrsize) == 0)
{
if (sasl_setprop(mci->mci_conn, SASL_IP_LOCAL,
&saddr_l) != SASL_OK)
return EX_TEMPFAIL;
}
}
# endif
saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
NULL, &client_interact,
&out, &outlen,
(const char **) &mechusing);
# endif
if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
{
if (saslresult == SASL_NOMECH && LogLevel > 8)
{
sm_syslog(LOG_NOTICE, e->e_id,
"AUTH=client, available mechanisms do not fulfill requirements");
}
return EX_TEMPFAIL;
}
(*sai)[SASL_MECH] = mechusing;
if (out == NULL
|| (outlen == 0 &&
(sm_strcasecmp(mechusing, "LOGIN") == 0 ||
sm_strcasecmp(mechusing, "DIGEST-MD5") == 0))
)
{
smtpmessage("AUTH %s", m, mci, mechusing);
}
else if (outlen == 0)
{
smtpmessage("AUTH %s =", m, mci, mechusing);
}
else
{
saslresult = sasl_encode64(out, outlen, in64, sizeof(in64),
NULL);
if (saslresult != SASL_OK)
{
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);
}
# if SASL < 20000
sm_sasl_free(out);
# endif
smtpresult = reply(m, mci, e, TimeOuts.to_auth, getsasldata, NULL,
XS_AUTH);
for (;;)
{
if (smtpresult == 235)
{
macdefine(&mci->mci_macro, A_TEMP, macid("{auth_type}"),
mechusing);
return EX_OK;
}
if (smtpresult == -1)
return EX_IOERR;
if (REPLYTYPE(smtpresult) == 5)
return EX_NOPERM;
if (REPLYTYPE(smtpresult) != 3)
{
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))
sm_dprintf("AUTH FAIL=%s (%d)\n",
sasl_errstring(saslresult, NULL, NULL),
saslresult);
smtpmessage("*", m, mci);
smtpresult = reply(m, mci, e, TimeOuts.to_auth,
getsasldata, NULL, XS_AUTH);
return EX_NOPERM;
}
if (outlen > 0)
{
saslresult = sasl_encode64(out, outlen, in64,
sizeof(in64), NULL);
if (saslresult != SASL_OK)
{
smtpmessage("*", m, mci);
return EX_TEMPFAIL;
}
}
else
in64[0] = '\0';
# if SASL < 20000
sm_sasl_free(out);
# endif
smtpmessage("%s", m, mci, in64);
smtpresult = reply(m, mci, e, TimeOuts.to_auth,
getsasldata, NULL, XS_AUTH);
}
}
int
smtpauth(m, mci, e)
MAILER *m;
MCI *mci;
ENVELOPE *e;
{
int result;
int i;
bool usedgetauth;
mci->mci_sasl_auth = false;
for (i = 0; i < SASL_MECH ; i++)
mci->mci_sai[i] = NULL;
result = getauth(mci, e, &(mci->mci_sai));
if (result == EX_TEMPFAIL)
return result;
usedgetauth = true;
if (result == EX_OK && mci->mci_sai[SASL_AUTHID] == NULL)
return result;
if (result != EX_OK)
{
if (SASLInfo == NULL)
return EX_UNAVAILABLE;
result = readauth(SASLInfo, true, &(mci->mci_sai),
mci->mci_rpool);
if (result != EX_OK)
return result;
usedgetauth = false;
}
if (mci->mci_sai[SASL_PASSWORD] == NULL ||
*(mci->mci_sai)[SASL_PASSWORD] == '\0')
return EX_UNAVAILABLE;
if ((mci->mci_sai[SASL_AUTHID] == NULL ||
*(mci->mci_sai)[SASL_AUTHID] == '\0') &&
(mci->mci_sai[SASL_USER] == NULL ||
*(mci->mci_sai)[SASL_USER] == '\0'))
return EX_UNAVAILABLE;
# if SASL >= 20000
callbacks[CB_PASS_IDX].context = (void *) mci;
# else
callbacks[CB_PASS_IDX].context = (void *) &mci->mci_sai;
# endif
callbacks[CB_USER_IDX].context = (void *) &mci->mci_sai;
callbacks[CB_AUTHNAME_IDX].context = (void *) &mci->mci_sai;
callbacks[CB_GETREALM_IDX].context = (void *) &mci->mci_sai;
#if 0
callbacks[CB_SAFESASL_IDX].context = (void *) &mci->mci_sai;
#endif
if ((mci->mci_sai)[SASL_DEFREALM] == NULL)
(mci->mci_sai)[SASL_DEFREALM] = sm_rpool_strdup_x(e->e_rpool,
macvalue('j', CurEnv));
if ((mci->mci_sai)[SASL_MECHLIST] == NULL ||
*(mci->mci_sai)[SASL_MECHLIST] == '\0')
(mci->mci_sai)[SASL_MECHLIST] = AuthMechanisms;
mci->mci_saslcap = intersect((mci->mci_sai)[SASL_MECHLIST],
mci->mci_saslcap, mci->mci_rpool);
result = init_sasl_client();
if (result != SASL_OK)
return usedgetauth ? EX_TEMPFAIL : EX_UNAVAILABLE;
do
{
result = attemptauth(m, mci, e, &(mci->mci_sai));
if (result == EX_OK)
mci->mci_sasl_auth = true;
else if (result == EX_TEMPFAIL || result == EX_NOPERM)
{
mci->mci_saslcap = removemech((mci->mci_sai)[SASL_MECH],
mci->mci_saslcap,
mci->mci_rpool);
if (mci->mci_saslcap == NULL ||
*(mci->mci_saslcap) == '\0')
return usedgetauth ? result
: EX_UNAVAILABLE;
}
else
return result;
} while (result != EX_OK);
return result;
}
#endif
int
smtpmailfrom(m, mci, e)
MAILER *m;
MCI *mci;
ENVELOPE *e;
{
int r;
char *bufp;
char *bodytype;
char *enhsc;
char buf[MAXNAME + 1];
char optbuf[MAXLINE];
if (tTd(18, 2))
sm_dprintf("smtpmailfrom: CurHost=%s\n", CurHostName);
enhsc = NULL;
if (mci->mci_state == MCIS_CLOSED)
{
errno = mci->mci_errno;
return EX_TEMPFAIL;
}
if (bitset(MCIF_SIZE, mci->mci_flags) && e->e_msgsize > 0)
{
(void) sm_snprintf(optbuf, sizeof(optbuf), " SIZE=%ld",
e->e_msgsize);
bufp = &optbuf[strlen(optbuf)];
}
else
{
optbuf[0] = '\0';
bufp = optbuf;
}
bodytype = e->e_bodytype;
if (bitset(MCIF_8BITMIME, mci->mci_flags))
{
if (bodytype == NULL &&
bitset(MM_MIME8BIT, MimeMode) &&
bitset(EF_HAS8BIT, e->e_flags) &&
!bitset(EF_DONT_MIME, e->e_flags) &&
!bitnset(M_8BITS, m->m_flags))
bodytype = "8BITMIME";
if (bodytype != NULL &&
SPACELEFT(optbuf, bufp) > strlen(bodytype) + 7)
{
(void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
" BODY=%s", bodytype);
bufp += strlen(bufp);
}
}
else if (bitnset(M_8BITS, m->m_flags) ||
!bitset(EF_HAS8BIT, e->e_flags) ||
bitset(MCIF_8BITOK, mci->mci_flags))
{
}
#if MIME8TO7
else if (bitset(MM_CVTMIME, MimeMode) &&
!bitset(EF_DONT_MIME, e->e_flags) &&
(!bitset(MM_PASS8BIT, MimeMode) ||
bitset(EF_IS_MIME, e->e_flags)))
{
mci->mci_flags |= MCIF_CVT8TO7;
}
#endif
else if (!bitset(MM_PASS8BIT, MimeMode))
{
extern char MsgBuf[];
usrerrenh("5.6.3", "%s does not support 8BITMIME", CurHostName);
mci_setstat(mci, EX_NOTSTICKY, "5.6.3", MsgBuf);
return EX_DATAERR;
}
if (bitset(MCIF_DSN, mci->mci_flags))
{
if (e->e_envid != NULL &&
SPACELEFT(optbuf, bufp) > strlen(e->e_envid) + 7)
{
(void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
" ENVID=%s", e->e_envid);
bufp += strlen(bufp);
}
if (bitset(EF_RET_PARAM, e->e_flags) &&
SPACELEFT(optbuf, bufp) > 9)
{
(void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
" RET=%s",
bitset(EF_NO_BODY_RETN, e->e_flags) ?
"HDRS" : "FULL");
bufp += strlen(bufp);
}
}
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
)
{
(void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
" AUTH=%s", e->e_auth_param);
bufp += strlen(bufp);
}
if (bitset(MCIF_DLVR_BY, mci->mci_flags) &&
IS_DLVR_BY(e) && SPACELEFT(optbuf, bufp) > 17)
{
long dby;
dby = e->e_deliver_by - (curtime() - e->e_ctime);
if (dby <= 0 && IS_DLVR_RETURN(e))
dby = mci->mci_min_by <= 0 ? 1 : mci->mci_min_by;
(void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
" BY=%ld;%c%s",
dby,
IS_DLVR_RETURN(e) ? 'R' : 'N',
IS_DLVR_TRACE(e) ? "T" : "");
bufp += strlen(bufp);
}
mci->mci_state = MCIS_MAIL;
if (bitset(EF_RESPONSE, e->e_flags) &&
!bitnset(M_NO_NULL_FROM, m->m_flags))
buf[0] = '\0';
else
expand("\201g", buf, sizeof(buf), e);
if (buf[0] == '<')
{
bufp = &buf[strlen(buf) - 1];
if (*bufp == '>')
*bufp = '\0';
bufp = &buf[1];
}
else
bufp = buf;
if (bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) ||
!bitnset(M_FROMPATH, m->m_flags))
{
smtpmessage("MAIL From:<%s>%s", m, mci, bufp, optbuf);
}
else
{
smtpmessage("MAIL From:<@%s%c%s>%s", m, mci, MyHostName,
*bufp == '@' ? ',' : ':', bufp, optbuf);
}
SmtpPhase = mci->mci_phase = "client MAIL";
sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
CurHostName, mci->mci_phase);
r = reply(m, mci, e, TimeOuts.to_mail, NULL, &enhsc, XS_DEFAULT);
if (r < 0)
{
mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
return EX_TEMPFAIL;
}
else if (r == SMTPCLOSING)
{
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 4)
{
mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, smtptodsn(r)),
SmtpReplyBuffer);
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 2)
{
return EX_OK;
}
else if (r == 501)
{
mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.5.2"),
SmtpReplyBuffer);
return EX_DATAERR;
}
else if (r == 553)
{
mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.1.3"),
SmtpReplyBuffer);
return EX_DATAERR;
}
else if (r == 552)
{
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;
}
else if (REPLYTYPE(r) == 5)
{
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));
}
mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
SmtpReplyBuffer);
smtpquit(m, mci, e);
return EX_PROTOCOL;
}
int
smtprcpt(to, m, mci, e, ctladdr, xstart)
ADDRESS *to;
register MAILER *m;
MCI *mci;
ENVELOPE *e;
ADDRESS *ctladdr;
time_t xstart;
{
char *bufp;
char optbuf[MAXLINE];
#if PIPELINING
while (mci->mci_nextaddr != NULL &&
sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0)
{
int r;
r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
if (r != EX_OK)
{
markfailure(e, mci->mci_nextaddr, mci, r, false);
giveresponse(r, mci->mci_nextaddr->q_status, m, mci,
ctladdr, xstart, e, to);
}
mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
}
#endif
if (mci->mci_state == MCIS_CLOSED)
{
errno = mci->mci_errno;
return EX_TEMPFAIL;
}
optbuf[0] = '\0';
bufp = optbuf;
if (bitset(MCIF_DSN, mci->mci_flags))
{
if (IS_DLVR_NOTIFY(e) &&
!bitset(MCIF_DLVR_BY, mci->mci_flags))
{
if (!bitset(QHASNOTIFY, to->q_flags))
to->q_flags |= QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY;
else if (bitset(QPINGONSUCCESS, to->q_flags) ||
bitset(QPINGONFAILURE, to->q_flags) ||
bitset(QPINGONDELAY, to->q_flags))
to->q_flags |= QPINGONDELAY;
}
if (bitset(QHASNOTIFY, to->q_flags) &&
bitset(QPRIMARY, to->q_flags) &&
!bitnset(M_LOCALMAILER, m->m_flags))
{
bool firstone = true;
(void) sm_strlcat(bufp, " NOTIFY=", sizeof(optbuf));
if (bitset(QPINGONSUCCESS, to->q_flags))
{
(void) sm_strlcat(bufp, "SUCCESS", sizeof(optbuf));
firstone = false;
}
if (bitset(QPINGONFAILURE, to->q_flags))
{
if (!firstone)
(void) sm_strlcat(bufp, ",",
sizeof(optbuf));
(void) sm_strlcat(bufp, "FAILURE", sizeof(optbuf));
firstone = false;
}
if (bitset(QPINGONDELAY, to->q_flags))
{
if (!firstone)
(void) sm_strlcat(bufp, ",",
sizeof(optbuf));
(void) sm_strlcat(bufp, "DELAY", sizeof(optbuf));
firstone = false;
}
if (firstone)
(void) sm_strlcat(bufp, "NEVER", sizeof(optbuf));
bufp += strlen(bufp);
}
if (to->q_orcpt != NULL &&
SPACELEFT(optbuf, bufp) > strlen(to->q_orcpt) + 7)
{
(void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
" ORCPT=%s", to->q_orcpt);
bufp += strlen(bufp);
}
}
smtpmessage("RCPT To:<%s>%s", m, mci, to->q_user, optbuf);
mci->mci_state = MCIS_RCPT;
SmtpPhase = mci->mci_phase = "client RCPT";
sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
CurHostName, mci->mci_phase);
#if PIPELINING
if (bitset(MCIF_PIPELINED, mci->mci_flags))
return EX_OK;
#endif
return smtprcptstat(to, m, mci, e);
}
static int
smtprcptstat(to, m, mci, e)
ADDRESS *to;
MAILER *m;
register MCI *mci;
register ENVELOPE *e;
{
int r;
int save_errno;
char *enhsc;
if (mci->mci_state == MCIS_CLOSED)
{
errno = mci->mci_errno;
return EX_TEMPFAIL;
}
enhsc = NULL;
r = reply(m, mci, e, TimeOuts.to_rcpt, NULL, &enhsc, XS_DEFAULT);
save_errno = errno;
to->q_rstatus = sm_rpool_strdup_x(e->e_rpool, SmtpReplyBuffer);
to->q_status = ENHSCN_RPOOL(enhsc, smtptodsn(r), e->e_rpool);
if (!bitnset(M_LMTP, m->m_flags))
to->q_statmta = mci->mci_host;
if (r < 0 || REPLYTYPE(r) == 4)
{
mci->mci_retryrcpt = true;
errno = save_errno;
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 2)
{
char *t;
if ((t = mci->mci_tolist) != NULL)
{
char *p;
*t++ = ',';
for (p = to->q_paddr; *p != '\0'; *t++ = *p++)
continue;
*t = '\0';
mci->mci_tolist = t;
}
#if PIPELINING
mci->mci_okrcpts++;
#endif
return EX_OK;
}
else if (r == 550)
{
to->q_status = ENHSCN_RPOOL(enhsc, "5.1.1", e->e_rpool);
return EX_NOUSER;
}
else if (r == 551)
{
to->q_status = ENHSCN_RPOOL(enhsc, "5.1.6", e->e_rpool);
return EX_NOUSER;
}
else if (r == 553)
{
to->q_status = ENHSCN_RPOOL(enhsc, "5.1.3", e->e_rpool);
return EX_NOUSER;
}
else if (REPLYTYPE(r) == 5)
{
return EX_UNAVAILABLE;
}
if (LogLevel > 1)
{
sm_syslog(LOG_CRIT, e->e_id,
"%.100s: SMTP RCPT protocol error: %s",
CurHostName,
shortenstring(SmtpReplyBuffer, 403));
}
mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
SmtpReplyBuffer);
return EX_PROTOCOL;
}
int
smtpdata(m, mci, e, ctladdr, xstart)
MAILER *m;
register MCI *mci;
register ENVELOPE *e;
ADDRESS *ctladdr;
time_t xstart;
{
register int r;
int rstat;
int xstat;
int timeout;
char *enhsc;
if (mci->mci_state == MCIS_CLOSED)
{
errno = mci->mci_errno;
return EX_TEMPFAIL;
}
enhsc = NULL;
smtpmessage("DATA", m, mci);
#if PIPELINING
if (mci->mci_nextaddr != NULL)
{
char *oldto = e->e_to;
while (mci->mci_nextaddr != NULL)
{
int r;
e->e_to = mci->mci_nextaddr->q_paddr;
r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
if (r != EX_OK)
{
markfailure(e, mci->mci_nextaddr, mci, r,
false);
giveresponse(r, mci->mci_nextaddr->q_status, m,
mci, ctladdr, xstart, e,
mci->mci_nextaddr);
if (r == EX_TEMPFAIL)
mci->mci_nextaddr->q_state = QS_RETRY;
}
mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
}
e->e_to = oldto;
if (mci->mci_state == MCIS_CLOSED)
{
errno = mci->mci_errno;
return EX_TEMPFAIL;
}
}
#endif
SmtpPhase = mci->mci_phase = "client DATA 354";
mci->mci_state = MCIS_DATA;
sm_setproctitle(true, e, "%s %s: %s",
qid_printname(e), CurHostName, mci->mci_phase);
r = reply(m, mci, e, TimeOuts.to_datainit, NULL, &enhsc, XS_DEFAULT);
if (r < 0 || REPLYTYPE(r) == 4)
{
if (r >= 0)
smtpquit(m, mci, e);
errno = mci->mci_errno;
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 5)
{
smtprset(m, mci, e);
#if PIPELINING
if (mci->mci_okrcpts <= 0)
return mci->mci_retryrcpt ? EX_TEMPFAIL
: EX_UNAVAILABLE;
#endif
return EX_UNAVAILABLE;
}
else if (REPLYTYPE(r) != 3)
{
if (LogLevel > 1)
{
sm_syslog(LOG_CRIT, e->e_id,
"%.100s: SMTP DATA-1 protocol error: %s",
CurHostName,
shortenstring(SmtpReplyBuffer, 403));
}
smtprset(m, mci, e);
mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
SmtpReplyBuffer);
#if PIPELINING
if (mci->mci_okrcpts <= 0)
return mci->mci_retryrcpt ? EX_TEMPFAIL
: EX_PROTOCOL;
#endif
return EX_PROTOCOL;
}
#if PIPELINING
if (mci->mci_okrcpts > 0)
{
#endif
if (tTd(18, 101))
{
timeout = 10;
}
else
timeout = DATA_PROGRESS_TIMEOUT * 1000;
sm_io_setinfo(mci->mci_out, SM_IO_WHAT_TIMEOUT, &timeout);
if (!(*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER))
goto writeerr;
if (tTd(18, 101))
{
(void) sleep(2);
}
if (!(*e->e_putbody)(mci, e, NULL))
goto writeerr;
#if PIPELINING
}
#endif
#if _FFR_CATCH_BROKEN_MTAS
if (sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0)
{
(void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, ".%s",
m->m_eol);
if (TrafficLogFile != NULL)
(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
"%05d >>> .\n", (int) CurrentPid);
if (Verbose)
nmessage(">>> .");
sm_syslog(LOG_CRIT, e->e_id,
"%.100s: SMTP DATA-1 protocol error: remote server returned response before final dot",
CurHostName);
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
if (sm_io_error(mci->mci_out))
{
mci->mci_errno = EIO;
mci->mci_state = MCIS_ERROR;
mci_setstat(mci, EX_IOERR, "4.4.2", NULL);
smtpquit(m, mci, e);
return EX_IOERR;
}
if (sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, "%s.%s",
bitset(MCIF_INLONGLINE, mci->mci_flags) ? m->m_eol : "",
m->m_eol) == SM_IO_EOF)
goto writeerr;
if (TrafficLogFile != NULL)
(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
"%05d >>> .\n", (int) CurrentPid);
if (Verbose)
nmessage(">>> .");
SmtpPhase = mci->mci_phase = "client DATA status";
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, &enhsc, XS_DEFAULT);
if (r < 0)
return EX_TEMPFAIL;
if (mci->mci_state == MCIS_DATA)
mci->mci_state = MCIS_OPEN;
xstat = EX_NOTSTICKY;
if (r == 452)
rstat = EX_TEMPFAIL;
else if (REPLYTYPE(r) == 4)
rstat = xstat = EX_TEMPFAIL;
else if (REPLYTYPE(r) == 2)
rstat = xstat = EX_OK;
else if (REPLYCLASS(r) != 5)
rstat = xstat = EX_PROTOCOL;
else if (REPLYTYPE(r) == 5)
rstat = EX_UNAVAILABLE;
else
rstat = EX_PROTOCOL;
mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)),
SmtpReplyBuffer);
if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
(r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
r += 5;
else
r = 4;
e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[r]);
SmtpPhase = mci->mci_phase = "idle";
sm_setproctitle(true, e, "%s: %s", CurHostName, mci->mci_phase);
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));
}
return rstat;
writeerr:
mci->mci_errno = errno;
mci->mci_state = MCIS_ERROR;
mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
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;
}
int
smtpgetstat(m, mci, e)
MAILER *m;
MCI *mci;
ENVELOPE *e;
{
int r;
int off;
int status, xstat;
char *enhsc;
enhsc = NULL;
r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc, XS_DEFAULT);
if (r < 0)
return EX_TEMPFAIL;
xstat = EX_NOTSTICKY;
if (REPLYTYPE(r) == 4)
status = EX_TEMPFAIL;
else if (REPLYTYPE(r) == 2)
status = xstat = EX_OK;
else if (REPLYCLASS(r) != 5)
status = xstat = EX_PROTOCOL;
else if (REPLYTYPE(r) == 5)
status = EX_UNAVAILABLE;
else
status = EX_PROTOCOL;
if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
(off = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
off += 5;
else
off = 4;
e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[off]);
mci_setstat(mci, xstat, 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));
}
return status;
}
void
smtpquit(m, mci, e)
register MAILER *m;
register MCI *mci;
ENVELOPE *e;
{
bool oldSuprErrs = SuprErrs;
int rcode;
char *oldcurhost;
if (mci->mci_state == MCIS_CLOSED)
{
mci_close(mci, "smtpquit:1");
return;
}
oldcurhost = CurHostName;
CurHostName = mci->mci_host;
if (CurHostName == NULL)
CurHostName = MyHostName;
#if PIPELINING
mci->mci_okrcpts = 0;
#endif
SuprErrs = true;
if (mci->mci_state != MCIS_ERROR &&
mci->mci_state != MCIS_QUITING)
{
SmtpPhase = "client QUIT";
mci->mci_state = MCIS_QUITING;
smtpmessage("QUIT", m, mci);
(void) reply(m, mci, e, TimeOuts.to_quit, NULL, NULL,
XS_DEFAULT);
SuprErrs = oldSuprErrs;
if (mci->mci_state == MCIS_CLOSED)
goto end;
}
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;
sm_syslog(LOG_ERR, e->e_id,
"smtpquit: mailer%s%s exited with exit value %d",
mailer == NULL ? "" : " ",
mailer == NULL ? "" : mailer,
rcode);
}
SuprErrs = oldSuprErrs;
end:
CurHostName = oldcurhost;
return;
}
void
smtprset(m, mci, e)
register MAILER *m;
register MCI *mci;
ENVELOPE *e;
{
int r;
CurHostName = mci->mci_host;
if (CurHostName == NULL)
CurHostName = MyHostName;
#if PIPELINING
mci->mci_okrcpts = 0;
#endif
if (mci->mci_state == MCIS_CLOSED)
{
errno = mci->mci_errno;
return;
}
SmtpPhase = "client RSET";
smtpmessage("RSET", m, mci);
r = reply(m, mci, e, TimeOuts.to_rset, NULL, NULL, XS_DEFAULT);
if (r < 0)
return;
if (mci->mci_state != MCIS_SSD && mci->mci_state != MCIS_CLOSED)
mci->mci_state = MCIS_OPEN;
else if (mci->mci_exitstat == EX_OK)
mci_setstat(mci, EX_TEMPFAIL, "4.5.0", NULL);
}
int
smtpprobe(mci)
register MCI *mci;
{
int r;
MAILER *m = mci->mci_mailer;
ENVELOPE *e;
extern ENVELOPE BlankEnvelope;
CurHostName = mci->mci_host;
if (CurHostName == NULL)
CurHostName = MyHostName;
e = &BlankEnvelope;
SmtpPhase = "client probe";
smtpmessage("RSET", m, mci);
r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, NULL, XS_DEFAULT);
if (REPLYTYPE(r) != 2)
smtpquit(m, mci, e);
return r;
}
int
reply(m, mci, e, timeout, pfunc, enhstat, rtype)
MAILER *m;
MCI *mci;
ENVELOPE *e;
time_t timeout;
void (*pfunc) __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
char **enhstat;
int rtype;
{
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) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
if (tTd(18, 1))
sm_dprintf("reply\n");
bufp = SmtpReplyBuffer;
set_tls_rd_tmo(timeout);
for (;;)
{
register char *p;
if (e->e_xfp != NULL)
(void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
if (mci->mci_state == MCIS_CLOSED)
return SMTPCLOSING;
if (mci->mci_in == NULL)
{
if (mci->mci_errno == 0)
mci->mci_errno = EBADF;
if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
{
errno = mci->mci_errno;
mci_close(mci, "reply:1");
return -1;
}
mci->mci_state = MCIS_ERROR;
smtpquit(m, mci, e);
errno = mci->mci_errno;
return -1;
}
if (mci->mci_out != NULL)
(void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
p = sfgets(bufp, MAXLINE, mci->mci_in, timeout, SmtpPhase);
save_errno = errno;
mci->mci_lastuse = curtime();
if (p == NULL)
{
bool oldholderrs;
extern char MsgBuf[];
if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
{
mci_close(mci, "reply:2");
return -1;
}
errno = save_errno;
if (errno == 0)
{
(void) sm_snprintf(SmtpReplyBuffer,
sizeof(SmtpReplyBuffer),
"421 4.4.1 Connection reset by %s",
CURHOSTNAME);
#ifdef ECONNRESET
errno = ECONNRESET;
#else
errno = EPIPE;
#endif
}
mci->mci_errno = errno;
oldholderrs = HoldErrs;
HoldErrs = true;
usrerr("451 4.4.1 reply: read error from %s",
CURHOSTNAME);
mci_setstat(mci, EX_TEMPFAIL, "4.4.2", MsgBuf);
if (tTd(18, 100))
(void) pause();
mci->mci_state = MCIS_ERROR;
smtpquit(m, mci, e);
#if XDEBUG
{
char wbuf[MAXLINE];
p = wbuf;
if (e->e_to != NULL)
{
(void) sm_snprintf(p,
SPACELEFT(wbuf, p),
"%s... ",
shortenstring(e->e_to, MAXSHORTSTR));
p += strlen(p);
}
(void) sm_snprintf(p, SPACELEFT(wbuf, p),
"reply(%.100s) during %s",
CURHOSTNAME, SmtpPhase);
checkfd012(wbuf);
}
#endif
HoldErrs = oldholderrs;
errno = save_errno;
return -1;
}
fixcrlf(bufp, true);
if (e->e_xfp != NULL && (bufp[0] == '4' ||
(bufp[0] == '5' && strncmp(SmtpMsgBuffer, "EHLO", 4) != 0)))
{
if (SmtpNeedIntro)
{
(void) sm_io_fprintf(CurEnv->e_xfp,
SM_TIME_DEFAULT,
"... while talking to %s:\n",
CURHOSTNAME);
SmtpNeedIntro = false;
}
if (SmtpMsgBuffer[0] != '\0')
{
(void) sm_io_fprintf(e->e_xfp,
SM_TIME_DEFAULT,
">>> %s\n",
(rtype == XS_STARTTLS)
? "STARTTLS dialogue"
: ((rtype == XS_AUTH)
? "AUTH dialogue"
: SmtpMsgBuffer));
SmtpMsgBuffer[0] = '\0';
}
(void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
"<<< %s\n", bufp);
}
if (Verbose)
nmessage("050 %s", bufp);
if (!ISSMTPREPLY(bufp))
continue;
if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
enhstat != NULL &&
extenhsc(bufp + 4, ' ', enhstatcode) > 0)
*enhstat = enhstatcode;
if (pfunc != NULL)
(*pfunc)(bufp, firstline, m, mci, e);
firstline = false;
r = atoi(bufp);
if (r < 100)
continue;
if (bufp[3] != '-')
break;
bufp = junkbuf;
}
if (SmtpReplyBuffer[0] == '4')
(void) sm_strlcpy(SmtpError, SmtpReplyBuffer, sizeof(SmtpError));
if (r == SMTPCLOSING && mci->mci_state != MCIS_SSD &&
mci->mci_state != MCIS_QUITING)
{
mci->mci_state = MCIS_SSD;
smtpquit(m, mci, e);
}
return r;
}
void
#ifdef __STDC__
smtpmessage(char *f, MAILER *m, MCI *mci, ...)
#else
smtpmessage(f, m, mci, va_alist)
char *f;
MAILER *m;
MCI *mci;
va_dcl
#endif
{
SM_VA_LOCAL_DECL
SM_VA_START(ap, mci);
(void) sm_vsnprintf(SmtpMsgBuffer, sizeof(SmtpMsgBuffer), f, ap);
SM_VA_END(ap);
if (tTd(18, 1) || Verbose)
nmessage(">>> %s", SmtpMsgBuffer);
if (TrafficLogFile != NULL)
(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
"%05d >>> %s\n", (int) CurrentPid,
SmtpMsgBuffer);
if (mci->mci_out != NULL)
{
(void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, "%s%s",
SmtpMsgBuffer, m == NULL ? "\r\n"
: m->m_eol);
}
else if (tTd(18, 1))
{
sm_dprintf("smtpmessage: NULL mci_out\n");
}
}