#include <sendmail.h>
SM_RCSID("@(#)$Id: recipient.c,v 8.349 2007/07/10 17:01:22 ca Exp $")
static void includetimeout __P((int));
static ADDRESS *self_reference __P((ADDRESS *));
static int sortexpensive __P((ADDRESS *, ADDRESS *));
static int sortbysignature __P((ADDRESS *, ADDRESS *));
static int sorthost __P((ADDRESS *, ADDRESS *));
typedef int sortfn_t __P((ADDRESS *, ADDRESS *));
static int
sorthost(xx, yy)
register ADDRESS *xx;
register ADDRESS *yy;
{
#if _FFR_HOST_SORT_REVERSE
return sm_strrevcasecmp(xx->q_host, yy->q_host);
#else
return sm_strcasecmp(xx->q_host, yy->q_host);
#endif
}
static int
sortexpensive(xx, yy)
ADDRESS *xx;
ADDRESS *yy;
{
if (!bitnset(M_EXPENSIVE, yy->q_mailer->m_flags))
return 1;
#if _FFR_HOST_SORT_REVERSE
return sm_strrevcasecmp(xx->q_host, yy->q_host);
#else
return sm_strcasecmp(xx->q_host, yy->q_host);
#endif
}
static int
sortbysignature(xx, yy)
ADDRESS *xx;
ADDRESS *yy;
{
register int ret;
if (xx->q_signature == NULL)
xx->q_signature = hostsignature(xx->q_mailer, xx->q_host);
if (yy->q_signature == NULL)
yy->q_signature = hostsignature(yy->q_mailer, yy->q_host);
ret = strcmp(xx->q_signature, yy->q_signature);
if (ret == 0)
return strcmp(yy->q_user, xx->q_user);
else
return ret;
}
#define QINHERITEDBITS (QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY)
int
sendtolist(list, ctladdr, sendq, aliaslevel, e)
char *list;
ADDRESS *ctladdr;
ADDRESS **sendq;
int aliaslevel;
register ENVELOPE *e;
{
register char *p;
register ADDRESS *SM_NONVOLATILE al;
SM_NONVOLATILE char delimiter;
SM_NONVOLATILE int naddrs;
SM_NONVOLATILE int i;
char *endp;
char *oldto = e->e_to;
char *SM_NONVOLATILE bufp;
char buf[MAXNAME + 1];
if (list == NULL)
{
syserr("sendtolist: null list");
return 0;
}
if (tTd(25, 1))
{
sm_dprintf("sendto: %s\n ctladdr=", list);
printaddr(sm_debug_file(), ctladdr, false);
}
if (ctladdr == NULL &&
(strchr(list, ',') != NULL || strchr(list, ';') != NULL ||
strchr(list, '<') != NULL || strchr(list, '(') != NULL))
e->e_flags &= ~EF_OLDSTYLE;
delimiter = ' ';
if (!bitset(EF_OLDSTYLE, e->e_flags) || ctladdr != NULL)
delimiter = ',';
al = NULL;
naddrs = 0;
i = strlen(list) + 1;
if (i <= sizeof(buf))
{
bufp = buf;
i = sizeof(buf);
}
else
bufp = sm_malloc_x(i);
endp = bufp + i;
SM_TRY
{
(void) sm_strlcpy(bufp, denlstring(list, false, true), i);
macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e r");
for (p = bufp; *p != '\0'; )
{
auto char *delimptr;
register ADDRESS *a;
SM_ASSERT(p < endp);
while ((isascii(*p) && isspace(*p)) || *p == ',')
p++;
SM_ASSERT(p < endp);
a = parseaddr(p, NULLADDR, RF_COPYALL, delimiter,
&delimptr, e, true);
p = delimptr;
SM_ASSERT(p < endp);
if (a == NULL)
continue;
a->q_next = al;
a->q_alias = ctladdr;
if (ctladdr != NULL)
{
ADDRESS *b;
if (sameaddr(ctladdr, a))
{
if (tTd(27, 5))
{
sm_dprintf("sendtolist: QSELFREF ");
printaddr(sm_debug_file(), ctladdr, false);
}
ctladdr->q_flags |= QSELFREF;
}
b = self_reference(a);
if (b != NULL)
{
b->q_flags |= QSELFREF;
if (tTd(27, 5))
{
sm_dprintf("sendtolist: QSELFREF ");
printaddr(sm_debug_file(), b, false);
}
if (a != b)
{
if (tTd(27, 5))
{
sm_dprintf("sendtolist: QS_DONTSEND ");
printaddr(sm_debug_file(), a, false);
}
a->q_state = QS_DONTSEND;
b->q_flags |= a->q_flags & QNOTREMOTE;
continue;
}
}
if (a->q_fullname == NULL)
a->q_fullname = ctladdr->q_fullname;
a->q_flags &= ~QINHERITEDBITS;
a->q_flags |= ctladdr->q_flags & QINHERITEDBITS;
a->q_finalrcpt = ctladdr->q_finalrcpt;
a->q_orcpt = ctladdr->q_orcpt;
}
al = a;
}
while (al != NULL)
{
register ADDRESS *a = al;
al = a->q_next;
a = recipient(a, sendq, aliaslevel, e);
naddrs++;
}
}
SM_FINALLY
{
e->e_to = oldto;
if (bufp != buf)
sm_free(bufp);
macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
}
SM_END_TRY
return naddrs;
}
#if MILTER
int
removefromlist(list, sendq, e)
char *list;
ADDRESS **sendq;
ENVELOPE *e;
{
SM_NONVOLATILE char delimiter;
SM_NONVOLATILE int naddrs;
SM_NONVOLATILE int i;
char *p;
char *oldto = e->e_to;
char *SM_NONVOLATILE bufp;
char buf[MAXNAME + 1];
if (list == NULL)
{
syserr("removefromlist: null list");
return 0;
}
if (tTd(25, 1))
sm_dprintf("removefromlist: %s\n", list);
if (strchr(list, ',') != NULL || strchr(list, ';') != NULL ||
strchr(list, '<') != NULL || strchr(list, '(') != NULL)
e->e_flags &= ~EF_OLDSTYLE;
delimiter = ' ';
if (!bitset(EF_OLDSTYLE, e->e_flags))
delimiter = ',';
naddrs = 0;
i = strlen(list) + 1;
if (i <= sizeof(buf))
{
bufp = buf;
i = sizeof(buf);
}
else
bufp = sm_malloc_x(i);
SM_TRY
{
(void) sm_strlcpy(bufp, denlstring(list, false, true), i);
#if _FFR_ADDR_TYPE_MODES
if (AddrTypeModes)
macdefine(&e->e_macro, A_PERM, macid("{addr_type}"),
"e r d");
else
#endif
macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e r");
for (p = bufp; *p != '\0'; )
{
ADDRESS a;
ADDRESS *q;
ADDRESS **pq;
char *delimptr;
while ((isascii(*p) && isspace(*p)) || *p == ',')
p++;
if (parseaddr(p, &a, RF_COPYALL|RF_RM_ADDR,
delimiter, &delimptr, e, true) == NULL)
{
p = delimptr;
continue;
}
p = delimptr;
for (pq = sendq; (q = *pq) != NULL; pq = &q->q_next)
{
if (!QS_IS_DEAD(q->q_state) &&
(sameaddr(q, &a) ||
strcmp(q->q_paddr, a.q_paddr) == 0))
{
if (tTd(25, 5))
{
sm_dprintf("removefromlist: QS_REMOVED ");
printaddr(sm_debug_file(), &a, false);
}
q->q_state = QS_REMOVED;
naddrs++;
break;
}
}
}
}
SM_FINALLY
{
e->e_to = oldto;
if (bufp != buf)
sm_free(bufp);
macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
}
SM_END_TRY
return naddrs;
}
#endif
ADDRESS *
recipient(new, sendq, aliaslevel, e)
register ADDRESS *new;
register ADDRESS **sendq;
int aliaslevel;
register ENVELOPE *e;
{
register ADDRESS *q;
ADDRESS **pq;
ADDRESS **prev;
register struct mailer *m;
register char *p;
int i, buflen;
bool quoted;
bool insert;
int findusercount;
bool initialdontsend;
char *buf;
char buf0[MAXNAME + 1];
sortfn_t *sortfn;
p = NULL;
quoted = false;
insert = false;
findusercount = 0;
initialdontsend = QS_IS_DEAD(new->q_state);
e->e_to = new->q_paddr;
m = new->q_mailer;
errno = 0;
if (aliaslevel == 0)
new->q_flags |= QPRIMARY;
if (tTd(26, 1))
{
sm_dprintf("\nrecipient (%d): ", aliaslevel);
printaddr(sm_debug_file(), new, false);
}
if (new->q_alias == NULL)
{
if (e->e_origrcpt == NULL)
e->e_origrcpt = new->q_paddr;
else if (e->e_origrcpt != new->q_paddr)
e->e_origrcpt = "";
}
for (q = new; q->q_alias != NULL; q = q->q_alias)
continue;
if (new->q_finalrcpt == NULL &&
e->e_from.q_mailer != NULL)
{
char frbuf[MAXLINE];
p = e->e_from.q_mailer->m_addrtype;
if (p == NULL)
p = "rfc822";
if (sm_strcasecmp(p, "rfc822") != 0)
{
(void) sm_snprintf(frbuf, sizeof(frbuf), "%s; %.800s",
q->q_mailer->m_addrtype,
q->q_user);
}
else if (strchr(q->q_user, '@') != NULL)
{
(void) sm_snprintf(frbuf, sizeof(frbuf), "%s; %.800s",
p, q->q_user);
}
else if (strchr(q->q_paddr, '@') != NULL)
{
char *qp;
bool b;
qp = q->q_paddr;
b = false;
if (*qp == '<')
{
b = qp[strlen(qp) - 1] == '>';
if (b)
qp[strlen(qp) - 1] = '\0';
qp++;
}
(void) sm_snprintf(frbuf, sizeof(frbuf), "%s; %.800s",
p, qp);
if (b)
qp[strlen(qp)] = '>';
}
else
{
(void) sm_snprintf(frbuf, sizeof(frbuf),
"%s; %.700s@%.100s",
p, q->q_user, MyHostName);
}
new->q_finalrcpt = sm_rpool_strdup_x(e->e_rpool, frbuf);
}
#if _FFR_GEN_ORCPT
if (new->q_orcpt == NULL)
{
if (q->q_orcpt != NULL)
new->q_orcpt = q->q_orcpt;
else
{
bool b = false;
char *qp;
char obuf[MAXLINE];
if (e->e_from.q_mailer != NULL)
p = e->e_from.q_mailer->m_addrtype;
if (p == NULL)
p = "rfc822";
(void) sm_strlcpyn(obuf, sizeof(obuf), 2, p, ";");
qp = q->q_paddr;
if (*qp == '<')
{
b = qp[strlen(qp) - 1] == '>';
if (b)
qp[strlen(qp) - 1] = '\0';
qp++;
}
p = xtextify(denlstring(qp, true, false), "=");
if (sm_strlcat(obuf, p, sizeof(obuf)) >= sizeof(obuf))
{
obuf[0] = '\0';
}
if (b)
qp[strlen(qp)] = '>';
if (obuf[0] != '\0')
new->q_orcpt =
sm_rpool_strdup_x(e->e_rpool, obuf);
}
}
#endif
if (aliaslevel > MaxAliasRecursion)
{
new->q_state = QS_BADADDR;
new->q_status = "5.4.6";
if (new->q_alias != NULL)
{
new->q_alias->q_state = QS_BADADDR;
new->q_alias->q_status = "5.4.6";
}
if ((SuprErrs || !LogUsrErrs) && LogLevel > 0)
{
sm_syslog(LOG_ERR, e->e_id,
"aliasing/forwarding loop broken: %s (%d aliases deep; %d max)",
FileName != NULL ? FileName : "", aliaslevel,
MaxAliasRecursion);
}
usrerrenh(new->q_status,
"554 aliasing/forwarding loop broken (%d aliases deep; %d max)",
aliaslevel, MaxAliasRecursion);
return new;
}
i = strlen(new->q_user);
if (i >= sizeof(buf0))
{
buflen = i + 1;
buf = xalloc(buflen);
}
else
{
buf = buf0;
buflen = sizeof(buf0);
}
(void) sm_strlcpy(buf, new->q_user, buflen);
for (p = buf; *p != '\0' && !quoted; p++)
{
if (*p == '\\')
quoted = true;
}
stripquotes(buf);
if (m == ProgMailer)
{
if (new->q_alias == NULL || UseMSP ||
bitset(EF_UNSAFE, e->e_flags))
{
new->q_state = QS_BADADDR;
new->q_status = "5.7.1";
usrerrenh(new->q_status,
"550 Cannot mail directly to programs");
}
else if (bitset(QBOGUSSHELL, new->q_alias->q_flags))
{
new->q_state = QS_BADADDR;
new->q_status = "5.7.1";
if (new->q_alias->q_ruser == NULL)
usrerrenh(new->q_status,
"550 UID %d is an unknown user: cannot mail to programs",
new->q_alias->q_uid);
else
usrerrenh(new->q_status,
"550 User %s@%s doesn't have a valid shell for mailing to programs",
new->q_alias->q_ruser, MyHostName);
}
else if (bitset(QUNSAFEADDR, new->q_alias->q_flags))
{
new->q_state = QS_BADADDR;
new->q_status = "5.7.1";
new->q_rstatus = "550 Unsafe for mailing to programs";
usrerrenh(new->q_status,
"550 Address %s is unsafe for mailing to programs",
new->q_alias->q_paddr);
}
}
prev = NULL;
if (UseMSP || WILL_BE_QUEUED(e->e_sendmode) ||
(!bitset(EF_SPLIT, e->e_flags) && e->e_ntries == 0 &&
FastSplit > 0))
sortfn = sorthost;
else if (NoConnect && bitnset(M_EXPENSIVE, new->q_mailer->m_flags))
sortfn = sortexpensive;
else
sortfn = sortbysignature;
for (pq = sendq; (q = *pq) != NULL; pq = &q->q_next)
{
i = (*sortfn)(new, q);
if (i == 0)
{
if (sameaddr(q, new) &&
(bitset(QRCPTOK, q->q_flags) ||
!bitset(QPRIMARY, q->q_flags)))
{
if (tTd(26, 1))
{
sm_dprintf("%s in sendq: ",
new->q_paddr);
printaddr(sm_debug_file(), q, false);
}
if (!bitset(QPRIMARY, q->q_flags))
{
if (!QS_IS_DEAD(new->q_state))
message("duplicate suppressed");
else
q->q_state = QS_DUPLICATE;
q->q_flags |= new->q_flags;
}
else if (bitset(QSELFREF, q->q_flags)
|| q->q_state == QS_REMOVED)
{
q->q_state = new->q_state;
q->q_flags |= new->q_flags;
}
new = q;
goto done;
}
}
else if (i < 0)
{
insert = true;
break;
}
prev = pq;
}
SM_ASSERT(pq != NULL);
if (insert)
{
new->q_next = *pq;
if (prev == NULL)
*sendq = new;
else
(*prev)->q_next = new;
}
else
{
new->q_next = NULL;
*pq = new;
}
e->e_flags &= ~EF_SPLIT;
trylocaluser:
if (tTd(29, 7))
{
sm_dprintf("at trylocaluser: ");
printaddr(sm_debug_file(), new, false);
}
if (!QS_IS_OK(new->q_state))
{
if (QS_IS_UNDELIVERED(new->q_state))
e->e_nrcpts++;
goto testselfdestruct;
}
if (m == InclMailer)
{
new->q_state = QS_INCLUDED;
if (new->q_alias == NULL || UseMSP ||
bitset(EF_UNSAFE, e->e_flags))
{
new->q_state = QS_BADADDR;
new->q_status = "5.7.1";
usrerrenh(new->q_status,
"550 Cannot mail directly to :include:s");
}
else
{
int ret;
message("including file %s", new->q_user);
ret = include(new->q_user, false, new,
sendq, aliaslevel, e);
if (transienterror(ret))
{
if (LogLevel > 2)
sm_syslog(LOG_ERR, e->e_id,
"include %s: transient error: %s",
shortenstring(new->q_user,
MAXSHORTSTR),
sm_errstring(ret));
new->q_state = QS_QUEUEUP;
usrerr("451 4.2.4 Cannot open %s: %s",
shortenstring(new->q_user,
MAXSHORTSTR),
sm_errstring(ret));
}
else if (ret != 0)
{
new->q_state = QS_BADADDR;
new->q_status = "5.2.4";
usrerrenh(new->q_status,
"550 Cannot open %s: %s",
shortenstring(new->q_user,
MAXSHORTSTR),
sm_errstring(ret));
}
}
}
else if (m == FileMailer)
{
if (new->q_alias == NULL || UseMSP ||
bitset(EF_UNSAFE, e->e_flags))
{
new->q_state = QS_BADADDR;
new->q_status = "5.7.1";
usrerrenh(new->q_status,
"550 Cannot mail directly to files");
}
else if (bitset(QBOGUSSHELL, new->q_alias->q_flags))
{
new->q_state = QS_BADADDR;
new->q_status = "5.7.1";
if (new->q_alias->q_ruser == NULL)
usrerrenh(new->q_status,
"550 UID %d is an unknown user: cannot mail to files",
new->q_alias->q_uid);
else
usrerrenh(new->q_status,
"550 User %s@%s doesn't have a valid shell for mailing to files",
new->q_alias->q_ruser, MyHostName);
}
else if (bitset(QUNSAFEADDR, new->q_alias->q_flags))
{
new->q_state = QS_BADADDR;
new->q_status = "5.7.1";
new->q_rstatus = "550 Unsafe for mailing to files";
usrerrenh(new->q_status,
"550 Address %s is unsafe for mailing to files",
new->q_alias->q_paddr);
}
}
if (!quoted && QS_IS_OK(new->q_state) &&
bitnset(M_ALIASABLE, m->m_flags))
alias(new, sendq, aliaslevel, e);
#if USERDB
if (!bitset(QNOTREMOTE, new->q_flags) &&
QS_IS_SENDABLE(new->q_state) &&
bitnset(M_CHECKUDB, m->m_flags))
{
if (udbexpand(new, sendq, aliaslevel, e) == EX_TEMPFAIL)
{
new->q_state = QS_QUEUEUP;
if (e->e_message == NULL)
e->e_message = sm_rpool_strdup_x(e->e_rpool,
"Deferred: user database error");
if (new->q_message == NULL)
new->q_message = "Deferred: user database error";
if (LogLevel > 8)
sm_syslog(LOG_INFO, e->e_id,
"deferred: udbexpand: %s",
sm_errstring(errno));
message("queued (user database error): %s",
sm_errstring(errno));
e->e_nrcpts++;
goto testselfdestruct;
}
}
#endif
if (tTd(29, 5))
{
sm_dprintf("recipient: testing local? cl=%d, rr5=%p\n\t",
ConfigLevel, RewriteRules[5]);
printaddr(sm_debug_file(), new, false);
}
if (ConfigLevel >= 2 && RewriteRules[5] != NULL &&
bitnset(M_TRYRULESET5, m->m_flags) &&
!bitset(QNOTREMOTE, new->q_flags) &&
QS_IS_OK(new->q_state))
{
maplocaluser(new, sendq, aliaslevel + 1, e);
}
if (QS_IS_OK(new->q_state) &&
bitnset(M_HASPWENT, m->m_flags))
{
auto bool fuzzy;
SM_MBDB_T user;
int status;
status = finduser(buf, &fuzzy, &user);
switch (status)
{
case EX_TEMPFAIL:
new->q_state = QS_QUEUEUP;
new->q_status = "4.5.2";
giveresponse(EX_TEMPFAIL, new->q_status, m, NULL,
new->q_alias, (time_t) 0, e, new);
break;
default:
new->q_state = QS_BADADDR;
new->q_status = "5.1.1";
new->q_rstatus = "550 5.1.1 User unknown";
giveresponse(EX_NOUSER, new->q_status, m, NULL,
new->q_alias, (time_t) 0, e, new);
break;
case EX_OK:
if (fuzzy)
{
new->q_user = sm_rpool_strdup_x(e->e_rpool,
user.mbdb_name);
if (findusercount++ > 3)
{
new->q_state = QS_BADADDR;
new->q_status = "5.4.6";
usrerrenh(new->q_status,
"554 aliasing/forwarding loop for %s broken",
user.mbdb_name);
goto done;
}
(void) sm_strlcpy(buf, user.mbdb_name, buflen);
goto trylocaluser;
}
if (*user.mbdb_homedir == '\0')
new->q_home = NULL;
else if (strcmp(user.mbdb_homedir, "/") == 0)
new->q_home = "";
else
new->q_home = sm_rpool_strdup_x(e->e_rpool,
user.mbdb_homedir);
if (user.mbdb_uid != SM_NO_UID)
{
new->q_uid = user.mbdb_uid;
new->q_gid = user.mbdb_gid;
new->q_flags |= QGOODUID;
}
new->q_ruser = sm_rpool_strdup_x(e->e_rpool,
user.mbdb_name);
if (user.mbdb_fullname[0] != '\0')
new->q_fullname = sm_rpool_strdup_x(e->e_rpool,
user.mbdb_fullname);
if (!usershellok(user.mbdb_name, user.mbdb_shell))
{
new->q_flags |= QBOGUSSHELL;
}
if (bitset(EF_VRFYONLY, e->e_flags))
{
new->q_state = QS_VERIFIED;
}
else if (!quoted)
forward(new, sendq, aliaslevel, e);
}
}
if (!QS_IS_DEAD(new->q_state))
e->e_nrcpts++;
testselfdestruct:
new->q_flags |= QTHISPASS;
if (tTd(26, 8))
{
sm_dprintf("testselfdestruct: ");
printaddr(sm_debug_file(), new, false);
if (tTd(26, 10))
{
sm_dprintf("SENDQ:\n");
printaddr(sm_debug_file(), *sendq, true);
sm_dprintf("----\n");
}
}
if (new->q_alias == NULL && new != &e->e_from &&
QS_IS_DEAD(new->q_state))
{
for (q = *sendq; q != NULL; q = q->q_next)
{
if (!QS_IS_DEAD(q->q_state))
break;
}
if (q == NULL)
{
new->q_state = QS_BADADDR;
new->q_status = "5.4.6";
usrerrenh(new->q_status,
"554 aliasing/forwarding loop broken");
}
}
done:
new->q_flags |= QTHISPASS;
if (buf != buf0)
sm_free(buf);
if (aliaslevel == 0)
{
int nrcpts = 0;
ADDRESS *only = NULL;
for (q = *sendq; q != NULL; q = q->q_next)
{
if (bitset(QTHISPASS, q->q_flags) &&
QS_IS_SENDABLE(q->q_state))
{
nrcpts++;
only = q;
}
q->q_flags &= ~QTHISPASS;
}
if (nrcpts == 1)
{
q = only;
while ((q = q->q_alias) != NULL)
{
if (q->q_owner != NULL)
break;
}
if (q == NULL)
only->q_flags |= QPRIMARY;
}
else if (!initialdontsend && nrcpts > 0)
{
e->e_flags |= EF_SENDRECEIPT;
new->q_flags |= QEXPANDED;
if (e->e_xfp != NULL &&
bitset(QPINGONSUCCESS, new->q_flags))
(void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
"%s... expanded to multiple addresses\n",
new->q_paddr);
}
}
new->q_flags |= QRCPTOK;
(void) sm_snprintf(buf0, sizeof(buf0), "%d", e->e_nrcpts);
macdefine(&e->e_macro, A_TEMP, macid("{nrcpts}"), buf0);
return new;
}
int
finduser(name, fuzzyp, user)
char *name;
bool *fuzzyp;
SM_MBDB_T *user;
{
#if MATCHGECOS
register struct passwd *pw;
#endif
register char *p;
bool tryagain;
int status;
if (tTd(29, 4))
sm_dprintf("finduser(%s): ", name);
*fuzzyp = false;
#if HESIOD
for (p = name; *p != '\0'; p++)
if (!isascii(*p) || !isdigit(*p))
break;
if (*p == '\0')
{
if (tTd(29, 4))
sm_dprintf("failed (numeric input)\n");
return EX_NOUSER;
}
#endif
status = sm_mbdb_lookup(name, user);
if (status != EX_NOUSER)
{
if (tTd(29, 4))
sm_dprintf("%s (non-fuzzy)\n", sm_strexit(status));
return status;
}
tryagain = false;
for (p = name; *p != '\0'; p++)
{
if (isascii(*p) && isupper(*p))
{
*p = tolower(*p);
tryagain = true;
}
}
if (tryagain && (status = sm_mbdb_lookup(name, user)) != EX_NOUSER)
{
if (tTd(29, 4))
sm_dprintf("%s (lower case)\n", sm_strexit(status));
*fuzzyp = true;
return status;
}
#if MATCHGECOS
if (!MatchGecos)
{
if (tTd(29, 4))
sm_dprintf("not found (fuzzy disabled)\n");
return EX_NOUSER;
}
for (p = name; *p != '\0'; p++)
{
if (*p == (SpaceSub & 0177) || *p == '_')
*p = ' ';
}
(void) setpwent();
while ((pw = getpwent()) != NULL)
{
char buf[MAXNAME + 1];
# if 0
if (sm_strcasecmp(pw->pw_name, name) == 0)
{
if (tTd(29, 4))
sm_dprintf("found (case wrapped)\n");
break;
}
# endif
sm_pwfullname(pw->pw_gecos, pw->pw_name, buf, sizeof(buf));
if (strchr(buf, ' ') != NULL && sm_strcasecmp(buf, name) == 0)
{
if (tTd(29, 4))
sm_dprintf("fuzzy matches %s\n", pw->pw_name);
message("sending to login name %s", pw->pw_name);
break;
}
}
if (pw != NULL)
*fuzzyp = true;
else if (tTd(29, 4))
sm_dprintf("no fuzzy match found\n");
# if DEC_OSF_BROKEN_GETPWENT
endpwent();
# endif
if (pw == NULL)
return EX_NOUSER;
sm_mbdb_frompw(user, pw);
return EX_OK;
#else
if (tTd(29, 4))
sm_dprintf("not found (fuzzy disabled)\n");
return EX_NOUSER;
#endif
}
bool
writable(filename, ctladdr, flags)
char *filename;
ADDRESS *ctladdr;
long flags;
{
uid_t euid = 0;
gid_t egid = 0;
char *user = NULL;
if (tTd(44, 5))
sm_dprintf("writable(%s, 0x%lx)\n", filename, flags);
if (geteuid() != 0)
{
euid = geteuid();
egid = getegid();
user = NULL;
}
else if (ctladdr != NULL)
{
euid = ctladdr->q_uid;
egid = ctladdr->q_gid;
user = ctladdr->q_user;
}
else if (bitset(SFF_RUNASREALUID, flags))
{
euid = RealUid;
egid = RealGid;
user = RealUserName;
}
else if (FileMailer != NULL && !bitset(SFF_ROOTOK, flags))
{
if (FileMailer->m_uid == NO_UID)
{
euid = DefUid;
user = DefUser;
}
else
{
euid = FileMailer->m_uid;
user = NULL;
}
if (FileMailer->m_gid == NO_GID)
egid = DefGid;
else
egid = FileMailer->m_gid;
}
else
{
euid = egid = 0;
user = NULL;
}
if (!bitset(SFF_ROOTOK, flags))
{
if (euid == 0)
{
euid = DefUid;
user = DefUser;
}
if (egid == 0)
egid = DefGid;
}
if (geteuid() == 0 &&
(ctladdr == NULL || !bitset(QGOODUID, ctladdr->q_flags)))
flags |= SFF_SETUIDOK;
if (!bitnset(DBS_FILEDELIVERYTOSYMLINK, DontBlameSendmail))
flags |= SFF_NOSLINK;
if (!bitnset(DBS_FILEDELIVERYTOHARDLINK, DontBlameSendmail))
flags |= SFF_NOHLINK;
errno = safefile(filename, euid, egid, user, flags, S_IWRITE, NULL);
return errno == 0;
}
static jmp_buf CtxIncludeTimeout;
int
include(fname, forwarding, ctladdr, sendq, aliaslevel, e)
char *fname;
bool forwarding;
ADDRESS *ctladdr;
ADDRESS **sendq;
int aliaslevel;
ENVELOPE *e;
{
SM_FILE_T *volatile fp = NULL;
char *oldto = e->e_to;
char *oldfilename = FileName;
int oldlinenumber = LineNumber;
register SM_EVENT *ev = NULL;
int nincludes;
int mode;
volatile bool maxreached = false;
register ADDRESS *ca;
volatile uid_t saveduid;
volatile gid_t savedgid;
volatile uid_t uid;
volatile gid_t gid;
char *volatile user;
int rval = 0;
volatile long sfflags = SFF_REGONLY;
register char *p;
bool safechown = false;
volatile bool safedir = false;
struct stat st;
char buf[MAXLINE];
if (tTd(27, 2))
sm_dprintf("include(%s)\n", fname);
if (tTd(27, 4))
sm_dprintf(" ruid=%d euid=%d\n",
(int) getuid(), (int) geteuid());
if (tTd(27, 14))
{
sm_dprintf("ctladdr ");
printaddr(sm_debug_file(), ctladdr, false);
}
if (tTd(27, 9))
sm_dprintf("include: old uid = %d/%d\n",
(int) getuid(), (int) geteuid());
if (forwarding)
{
sfflags |= SFF_MUSTOWN|SFF_ROOTOK;
if (!bitnset(DBS_GROUPWRITABLEFORWARDFILE, DontBlameSendmail))
sfflags |= SFF_NOGWFILES;
if (!bitnset(DBS_WORLDWRITABLEFORWARDFILE, DontBlameSendmail))
sfflags |= SFF_NOWWFILES;
}
else
{
if (!bitnset(DBS_GROUPWRITABLEINCLUDEFILE, DontBlameSendmail))
sfflags |= SFF_NOGWFILES;
if (!bitnset(DBS_WORLDWRITABLEINCLUDEFILE, DontBlameSendmail))
sfflags |= SFF_NOWWFILES;
}
if ((geteuid() != 0 || RunAsUid != 0) &&
!bitnset(DBS_NONROOTSAFEADDR, DontBlameSendmail))
{
if (tTd(27, 4))
sm_dprintf("include: not safe (euid=%d, RunAsUid=%d)\n",
(int) geteuid(), (int) RunAsUid);
ctladdr->q_flags |= QUNSAFEADDR;
}
ca = getctladdr(ctladdr);
if (ca == NULL ||
(ca->q_uid == DefUid && ca->q_gid == 0))
{
uid = DefUid;
gid = DefGid;
user = DefUser;
}
else
{
uid = ca->q_uid;
gid = ca->q_gid;
user = ca->q_user;
}
#if MAILER_SETUID_METHOD != USE_SETUID
saveduid = geteuid();
savedgid = getegid();
if (saveduid == 0)
{
if (!DontInitGroups)
{
if (initgroups(user, gid) == -1)
{
rval = EAGAIN;
syserr("include: initgroups(%s, %d) failed",
user, gid);
goto resetuid;
}
}
else
{
GIDSET_T gidset[1];
gidset[0] = gid;
if (setgroups(1, gidset) == -1)
{
rval = EAGAIN;
syserr("include: setgroups() failed");
goto resetuid;
}
}
if (gid != 0 && setgid(gid) < -1)
{
rval = EAGAIN;
syserr("setgid(%d) failure", gid);
goto resetuid;
}
if (uid != 0)
{
# if MAILER_SETUID_METHOD == USE_SETEUID
if (seteuid(uid) < 0)
{
rval = EAGAIN;
syserr("seteuid(%d) failure (real=%d, eff=%d)",
uid, (int) getuid(), (int) geteuid());
goto resetuid;
}
# endif
# if MAILER_SETUID_METHOD == USE_SETREUID
if (setreuid(0, uid) < 0)
{
rval = EAGAIN;
syserr("setreuid(0, %d) failure (real=%d, eff=%d)",
uid, (int) getuid(), (int) geteuid());
goto resetuid;
}
# endif
}
}
#endif
if (tTd(27, 9))
sm_dprintf("include: new uid = %d/%d\n",
(int) getuid(), (int) geteuid());
if (setjmp(CtxIncludeTimeout) != 0)
{
ctladdr->q_state = QS_QUEUEUP;
errno = 0;
rval = E_SM_OPENTIMEOUT;
goto resetuid;
}
if (TimeOuts.to_fileopen > 0)
ev = sm_setevent(TimeOuts.to_fileopen, includetimeout, 0);
else
ev = NULL;
p = strrchr(fname, '/');
if (p != NULL)
{
int ret;
*p = '\0';
ret = safedirpath(fname, uid, gid, user,
sfflags|SFF_SAFEDIRPATH, 0, 0);
if (ret == 0)
{
safedir = true;
sfflags |= SFF_NOPATHCHECK;
}
else
{
if (bitnset((forwarding ?
DBS_FORWARDFILEINUNSAFEDIRPATH :
DBS_INCLUDEFILEINUNSAFEDIRPATH),
DontBlameSendmail))
sfflags |= SFF_NOPATHCHECK;
else if (bitnset((forwarding ?
DBS_FORWARDFILEINGROUPWRITABLEDIRPATH :
DBS_INCLUDEFILEINGROUPWRITABLEDIRPATH),
DontBlameSendmail) &&
ret == E_SM_GWDIR)
{
setbitn(DBS_GROUPWRITABLEDIRPATHSAFE,
DontBlameSendmail);
ret = safedirpath(fname, uid, gid, user,
sfflags|SFF_SAFEDIRPATH,
0, 0);
clrbitn(DBS_GROUPWRITABLEDIRPATHSAFE,
DontBlameSendmail);
if (ret == 0)
sfflags |= SFF_NOPATHCHECK;
else
sfflags |= SFF_SAFEDIRPATH;
}
else
sfflags |= SFF_SAFEDIRPATH;
if (ret > E_PSEUDOBASE &&
!bitnset((forwarding ?
DBS_FORWARDFILEINUNSAFEDIRPATHSAFE :
DBS_INCLUDEFILEINUNSAFEDIRPATHSAFE),
DontBlameSendmail))
{
if (LogLevel > 11)
sm_syslog(LOG_INFO, e->e_id,
"%s: unsafe directory path, marked unsafe",
shortenstring(fname, MAXSHORTSTR));
ctladdr->q_flags |= QUNSAFEADDR;
}
}
*p = '/';
}
if (!safedir &&
!bitnset((forwarding ?
DBS_LINKEDFORWARDFILEINWRITABLEDIR :
DBS_LINKEDINCLUDEFILEINWRITABLEDIR),
DontBlameSendmail))
sfflags |= SFF_NOLINK;
rval = safefile(fname, uid, gid, user, sfflags, S_IREAD, &st);
if (rval != 0)
{
if (tTd(27, 4))
sm_dprintf("include: not safe (uid=%d): %s\n",
(int) uid, sm_errstring(rval));
}
else if ((fp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, fname,
SM_IO_RDONLY, NULL)) == NULL)
{
rval = errno;
if (tTd(27, 4))
sm_dprintf("include: open: %s\n", sm_errstring(rval));
}
else if (filechanged(fname, sm_io_getinfo(fp,SM_IO_WHAT_FD, NULL), &st))
{
rval = E_SM_FILECHANGE;
if (tTd(27, 4))
sm_dprintf("include: file changed after open\n");
}
if (ev != NULL)
sm_clrevent(ev);
resetuid:
#if HASSETREUID || USESETEUID
if (saveduid == 0)
{
if (uid != 0)
{
# if USESETEUID
if (seteuid(0) < 0)
syserr("!seteuid(0) failure (real=%d, eff=%d)",
(int) getuid(), (int) geteuid());
# else
if (setreuid(-1, 0) < 0)
syserr("!setreuid(-1, 0) failure (real=%d, eff=%d)",
(int) getuid(), (int) geteuid());
if (setreuid(RealUid, 0) < 0)
syserr("!setreuid(%d, 0) failure (real=%d, eff=%d)",
(int) RealUid, (int) getuid(),
(int) geteuid());
# endif
}
if (setgid(savedgid) < 0)
syserr("!setgid(%d) failure (real=%d eff=%d)",
(int) savedgid, (int) getgid(),
(int) getegid());
}
#endif
if (tTd(27, 9))
sm_dprintf("include: reset uid = %d/%d\n",
(int) getuid(), (int) geteuid());
if (rval == E_SM_OPENTIMEOUT)
usrerr("451 4.4.1 open timeout on %s", fname);
if (fp == NULL)
return rval;
if (fstat(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), &st) < 0)
{
rval = errno;
syserr("Cannot fstat %s!", fname);
(void) sm_io_close(fp, SM_TIME_DEFAULT);
return rval;
}
safechown = chownsafe(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), safedir);
if (tTd(27, 6))
sm_dprintf("include: parent of %s is %s, chown is %ssafe\n",
fname, safedir ? "safe" : "dangerous",
safechown ? "" : "un");
if (safechown &&
(ca == NULL ||
(ca->q_uid == DefUid && ca->q_gid == 0)))
{
ctladdr->q_uid = st.st_uid;
ctladdr->q_gid = st.st_gid;
ctladdr->q_flags |= QGOODUID;
}
if (ca != NULL && ca->q_uid == st.st_uid)
{
ctladdr->q_flags |= ca->q_flags & QBOGUSSHELL;
ctladdr->q_ruser = ca->q_ruser;
}
else if (!forwarding)
{
register struct passwd *pw;
pw = sm_getpwuid(st.st_uid);
if (pw == NULL)
{
ctladdr->q_uid = st.st_uid;
ctladdr->q_flags |= QBOGUSSHELL;
}
else
{
char *sh;
ctladdr->q_ruser = sm_rpool_strdup_x(e->e_rpool,
pw->pw_name);
if (safechown)
sh = pw->pw_shell;
else
sh = "/SENDMAIL/ANY/SHELL/";
if (!usershellok(pw->pw_name, sh))
{
if (LogLevel > 11)
sm_syslog(LOG_INFO, e->e_id,
"%s: user %s has bad shell %s, marked %s",
shortenstring(fname,
MAXSHORTSTR),
pw->pw_name, sh,
safechown ? "bogus" : "unsafe");
if (safechown)
ctladdr->q_flags |= QBOGUSSHELL;
else
ctladdr->q_flags |= QUNSAFEADDR;
}
}
}
if (bitset(EF_VRFYONLY, e->e_flags))
{
ctladdr->q_state = QS_VERIFIED;
e->e_nrcpts++;
(void) sm_io_close(fp, SM_TIME_DEFAULT);
return rval;
}
mode = S_IWOTH;
if (!bitnset((forwarding ?
DBS_GROUPWRITABLEFORWARDFILESAFE :
DBS_GROUPWRITABLEINCLUDEFILESAFE),
DontBlameSendmail))
mode |= S_IWGRP;
if (bitset(mode, st.st_mode))
{
if (tTd(27, 6))
sm_dprintf("include: %s is %s writable, marked unsafe\n",
shortenstring(fname, MAXSHORTSTR),
bitset(S_IWOTH, st.st_mode) ? "world"
: "group");
if (LogLevel > 11)
sm_syslog(LOG_INFO, e->e_id,
"%s: %s writable %s file, marked unsafe",
shortenstring(fname, MAXSHORTSTR),
bitset(S_IWOTH, st.st_mode) ? "world" : "group",
forwarding ? "forward" : ":include:");
ctladdr->q_flags |= QUNSAFEADDR;
}
FileName = fname;
LineNumber = 0;
ctladdr->q_flags &= ~QSELFREF;
nincludes = 0;
while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL &&
!maxreached)
{
fixcrlf(buf, true);
LineNumber++;
if (buf[0] == '#' || buf[0] == '\0')
continue;
for (p = buf; (p = strchr(++p, '#')) != NULL; )
{
if (p[1] == '@' && p[2] == '#' &&
isascii(p[-1]) && isspace(p[-1]) &&
(p[3] == '\0' || (isascii(p[3]) && isspace(p[3]))))
{
--p;
while (p > buf && isascii(p[-1]) &&
isspace(p[-1]))
--p;
p[0] = '\0';
break;
}
}
if (buf[0] == '\0')
continue;
e->e_to = NULL;
message("%s to %s",
forwarding ? "forwarding" : "sending", buf);
if (forwarding && LogLevel > 10)
sm_syslog(LOG_INFO, e->e_id,
"forward %.200s => %s",
oldto, shortenstring(buf, MAXSHORTSTR));
nincludes += sendtolist(buf, ctladdr, sendq, aliaslevel + 1, e);
if (forwarding &&
MaxForwardEntries > 0 &&
nincludes >= MaxForwardEntries)
{
#if 0
ctladdr->q_state = QS_DONTSEND;
#endif
syserr("Attempt to forward to more than %d addresses (in %s)!",
MaxForwardEntries, fname);
maxreached = true;
}
}
if (sm_io_error(fp) && tTd(27, 3))
sm_dprintf("include: read error: %s\n", sm_errstring(errno));
if (nincludes > 0 && !bitset(QSELFREF, ctladdr->q_flags))
{
if (aliaslevel <= MaxAliasRecursion ||
ctladdr->q_state != QS_BADADDR)
{
ctladdr->q_state = QS_DONTSEND;
if (tTd(27, 5))
{
sm_dprintf("include: QS_DONTSEND ");
printaddr(sm_debug_file(), ctladdr, false);
}
}
}
(void) sm_io_close(fp, SM_TIME_DEFAULT);
FileName = oldfilename;
LineNumber = oldlinenumber;
e->e_to = oldto;
return rval;
}
static void
includetimeout(ignore)
int ignore;
{
errno = ETIMEDOUT;
longjmp(CtxIncludeTimeout, 1);
}
void
sendtoargv(argv, e)
register char **argv;
register ENVELOPE *e;
{
register char *p;
while ((p = *argv++) != NULL)
(void) sendtolist(p, NULLADDR, &e->e_sendqueue, 0, e);
}
ADDRESS *
getctladdr(a)
register ADDRESS *a;
{
while (a != NULL && !bitset(QGOODUID, a->q_flags))
a = a->q_alias;
return a;
}
static ADDRESS *
self_reference(a)
ADDRESS *a;
{
ADDRESS *b;
ADDRESS *c;
if (tTd(27, 1))
sm_dprintf("self_reference(%s)\n", a->q_paddr);
for (b = a->q_alias; b != NULL; b = b->q_alias)
{
if (sameaddr(a, b))
break;
}
if (b == NULL)
{
if (tTd(27, 1))
sm_dprintf("\t... no self ref\n");
return NULL;
}
c = a;
while (c != NULL)
{
if (tTd(27, 10))
sm_dprintf(" %s", c->q_user);
if (bitnset(M_HASPWENT, c->q_mailer->m_flags))
{
SM_MBDB_T user;
if (tTd(27, 2))
sm_dprintf("\t... getpwnam(%s)... ", c->q_user);
if (sm_mbdb_lookup(c->q_user, &user) == EX_OK)
{
if (tTd(27, 2))
sm_dprintf("found\n");
if (sameaddr(b, c))
return b;
else
return c;
}
if (tTd(27, 2))
sm_dprintf("failed\n");
}
else
{
if (bitnset(M_LOCALMAILER, c->q_mailer->m_flags) &&
b->q_mailer == c->q_mailer)
{
if (tTd(27, 2))
sm_dprintf("\t... local match (%s)\n",
c->q_user);
if (sameaddr(b, c))
return b;
else
return c;
}
}
if (tTd(27, 10))
sm_dprintf("\n");
c = c->q_alias;
}
if (tTd(27, 1))
sm_dprintf("\t... cannot break loop for \"%s\"\n", a->q_paddr);
return NULL;
}