summaryrefslogtreecommitdiffstats
path: root/contrib/sendmail/src/recipient.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/sendmail/src/recipient.c')
-rw-r--r--contrib/sendmail/src/recipient.c2072
1 files changed, 2072 insertions, 0 deletions
diff --git a/contrib/sendmail/src/recipient.c b/contrib/sendmail/src/recipient.c
new file mode 100644
index 0000000..4064632
--- /dev/null
+++ b/contrib/sendmail/src/recipient.c
@@ -0,0 +1,2072 @@
+/*
+ * Copyright (c) 1998-2003, 2006 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.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#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 *));
+
+/*
+** SORTHOST -- strcmp()-like func for host portion of an ADDRESS
+**
+** Parameters:
+** xx -- first ADDRESS
+** yy -- second ADDRESS
+**
+** Returns:
+** <0 when xx->q_host is less than yy->q_host
+** >0 when xx->q_host is greater than yy->q_host
+** 0 when equal
+*/
+
+static int
+sorthost(xx, yy)
+ register ADDRESS *xx;
+ register ADDRESS *yy;
+{
+#if _FFR_HOST_SORT_REVERSE
+ /* XXX maybe compare hostnames from the end? */
+ return sm_strrevcasecmp(xx->q_host, yy->q_host);
+#else /* _FFR_HOST_SORT_REVERSE */
+ return sm_strcasecmp(xx->q_host, yy->q_host);
+#endif /* _FFR_HOST_SORT_REVERSE */
+}
+
+/*
+** SORTEXPENSIVE -- strcmp()-like func for expensive mailers
+**
+** The mailer has been noted already as "expensive" for 'xx'. This
+** will give a result relative to 'yy'. Expensive mailers get rated
+** "greater than" non-expensive mailers because during the delivery phase
+** it will get queued -- no use it getting in the way of less expensive
+** recipients. We avoid an MX RR lookup when both 'xx' and 'yy' are
+** expensive since an MX RR lookup happens when extracted from the queue
+** later.
+**
+** Parameters:
+** xx -- first ADDRESS
+** yy -- second ADDRESS
+**
+** Returns:
+** <0 when xx->q_host is less than yy->q_host and both are
+** expensive
+** >0 when xx->q_host is greater than yy->q_host, or when
+** 'yy' is non-expensive
+** 0 when equal (by expense and q_host)
+*/
+
+static int
+sortexpensive(xx, yy)
+ ADDRESS *xx;
+ ADDRESS *yy;
+{
+ if (!bitnset(M_EXPENSIVE, yy->q_mailer->m_flags))
+ return 1; /* xx should go later */
+#if _FFR_HOST_SORT_REVERSE
+ /* XXX maybe compare hostnames from the end? */
+ return sm_strrevcasecmp(xx->q_host, yy->q_host);
+#else /* _FFR_HOST_SORT_REVERSE */
+ return sm_strcasecmp(xx->q_host, yy->q_host);
+#endif /* _FFR_HOST_SORT_REVERSE */
+}
+
+/*
+** SORTBYSIGNATURE -- a strcmp()-like func for q_mailer and q_host in ADDRESS
+**
+** Parameters:
+** xx -- first ADDRESS
+** yy -- second ADDRESS
+**
+** Returns:
+** 0 when the "signature"'s are same
+** <0 when xx->q_signature is less than yy->q_signature
+** >0 when xx->q_signature is greater than yy->q_signature
+**
+** Side Effect:
+** May set ADDRESS pointer for q_signature if not already set.
+*/
+
+static int
+sortbysignature(xx, yy)
+ ADDRESS *xx;
+ ADDRESS *yy;
+{
+ register int ret;
+
+ /* Let's avoid redoing the signature over and over again */
+ 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 the two signatures are the same then we will return a sort
+ ** value based on 'q_user'. But note that we have reversed xx and yy
+ ** on purpose. This additional compare helps reduce the number of
+ ** sameaddr() calls and loops in recipient() for the case when
+ ** the rcpt list has been provided already in-order.
+ */
+
+ if (ret == 0)
+ return strcmp(yy->q_user, xx->q_user);
+ else
+ return ret;
+}
+
+/*
+** SENDTOLIST -- Designate a send list.
+**
+** The parameter is a comma-separated list of people to send to.
+** This routine arranges to send to all of them.
+**
+** Parameters:
+** list -- the send list.
+** ctladdr -- the address template for the person to
+** send to -- effective uid/gid are important.
+** This is typically the alias that caused this
+** expansion.
+** sendq -- a pointer to the head of a queue to put
+** these people into.
+** aliaslevel -- the current alias nesting depth -- to
+** diagnose loops.
+** e -- the envelope in which to add these recipients.
+**
+** Returns:
+** The number of addresses actually on the list.
+*/
+
+/* q_flags bits inherited from ctladdr */
+#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; /* list of addresses to send to */
+ SM_NONVOLATILE char delimiter; /* the address 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);
+ }
+
+ /* heuristic to determine old versus new style addresses */
+ 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;
+
+ /* make sure we have enough space to copy the string */
+ 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);
+
+ /* parse the address */
+ 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;
+
+ /* arrange to inherit attributes from parent */
+ if (ctladdr != NULL)
+ {
+ ADDRESS *b;
+
+ /* self reference test */
+ if (sameaddr(ctladdr, a))
+ {
+ if (tTd(27, 5))
+ {
+ sm_dprintf("sendtolist: QSELFREF ");
+ printaddr(sm_debug_file(), ctladdr, false);
+ }
+ ctladdr->q_flags |= QSELFREF;
+ }
+
+ /* check for address loops */
+ 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;
+ }
+ }
+
+ /* full name */
+ if (a->q_fullname == NULL)
+ a->q_fullname = ctladdr->q_fullname;
+
+ /* various flag bits */
+ a->q_flags &= ~QINHERITEDBITS;
+ a->q_flags |= ctladdr->q_flags & QINHERITEDBITS;
+
+ /* DSN recipient information */
+ a->q_finalrcpt = ctladdr->q_finalrcpt;
+ a->q_orcpt = ctladdr->q_orcpt;
+ }
+
+ al = a;
+ }
+
+ /* arrange to send to everyone on the local send list */
+ 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
+/*
+** REMOVEFROMLIST -- Remove addresses from a send list.
+**
+** The parameter is a comma-separated list of recipients to remove.
+** Note that it only deletes matching addresses. If those addresses
+** have been expanded already in the sendq, it won't mark the
+** expanded recipients as QS_REMOVED.
+**
+** Parameters:
+** list -- the list to remove.
+** sendq -- a pointer to the head of a queue to remove
+** these addresses from.
+** e -- the envelope in which to remove these recipients.
+**
+** Returns:
+** The number of addresses removed from the list.
+**
+*/
+
+int
+removefromlist(list, sendq, e)
+ char *list;
+ ADDRESS **sendq;
+ ENVELOPE *e;
+{
+ SM_NONVOLATILE char delimiter; /* the address 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);
+
+ /* heuristic to determine old versus new style addresses */
+ 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;
+
+ /* make sure we have enough space to copy the string */
+ 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 /* _FFR_ADDR_TYPE_MODES */
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e r");
+ for (p = bufp; *p != '\0'; )
+ {
+ ADDRESS a; /* parsed address to be removed */
+ ADDRESS *q;
+ ADDRESS **pq;
+ char *delimptr;
+
+ /* parse the address */
+ 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 /* MILTER */
+
+/*
+** RECIPIENT -- Designate a message recipient
+** Saves the named person for future mailing (after some checks).
+**
+** Parameters:
+** new -- the (preparsed) address header for the recipient.
+** sendq -- a pointer to the head of a queue to put the
+** recipient in. Duplicate suppression is done
+** in this queue.
+** aliaslevel -- the current alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** The actual address in the queue. This will be "a" if
+** the address is not a duplicate, else the original address.
+**
+*/
+
+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; /* set if the addr has a quote bit */
+ bool insert;
+ int findusercount;
+ bool initialdontsend;
+ char *buf;
+ char buf0[MAXNAME + 1]; /* unquoted image of the user name */
+ 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 this is primary, use it as original recipient */
+ 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 = "";
+ }
+
+ /* find parent recipient for finalrcpt and orcpt */
+ for (q = new; q->q_alias != NULL; q = q->q_alias)
+ continue;
+
+ /* find final recipient DSN address */
+ 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;
+
+ /* strip brackets from address */
+ 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);
+
+ /* undo damage */
+ 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
+ /* set ORCPT DSN arg if not already set */
+ if (new->q_orcpt == NULL)
+ {
+ /* check for an existing ORCPT */
+ if (q->q_orcpt != NULL)
+ new->q_orcpt = q->q_orcpt;
+ else
+ {
+ /* make our own */
+ 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;
+
+ /* FFR: Needs to strip comments from stdin addrs */
+
+ /* strip brackets from address */
+ 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))
+ {
+ /* if too big, don't use it */
+ obuf[0] = '\0';
+ }
+
+ /* undo damage */
+ if (b)
+ qp[strlen(qp)] = '>';
+
+ if (obuf[0] != '\0')
+ new->q_orcpt =
+ sm_rpool_strdup_x(e->e_rpool, obuf);
+ }
+ }
+#endif /* _FFR_GEN_ORCPT */
+
+ /* break aliasing loops */
+ 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;
+ }
+
+ /*
+ ** Finish setting up address structure.
+ */
+
+ /* get unquoted user for file, program or user.name check */
+ 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);
+
+ /* check for direct mailing to restricted mailers */
+ 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);
+ }
+ }
+
+ /*
+ ** Look up this person in the recipient list.
+ ** If they are there already, return, otherwise continue.
+ ** If the list is empty, just add it. Notice the cute
+ ** hack to make from addresses suppress things correctly:
+ ** the QS_DUPLICATE state will be set in the send list.
+ ** [Please note: the emphasis is on "hack."]
+ */
+
+ prev = NULL;
+
+ /*
+ ** If this message is going to the queue or FastSplit is set
+ ** and it is the first try and the envelope hasn't split, then we
+ ** avoid doing an MX RR lookup now because one will be done when the
+ ** message is extracted from the queue later. It can go to the queue
+ ** because all messages are going to the queue or this mailer of
+ ** the current recipient is marked expensive.
+ */
+
+ 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)
+ {
+ /*
+ ** If address is "less than" it should be inserted now.
+ ** If address is "greater than" current comparison it'll
+ ** insert later in the list; so loop again (if possible).
+ ** If address is "equal" (different equal than sameaddr()
+ ** call) then check if sameaddr() will be true.
+ ** Because this list is now sorted, it'll mean fewer
+ ** comparisons and fewer loops which is important for more
+ ** recipients.
+ */
+
+ i = (*sortfn)(new, q);
+ if (i == 0) /* equal */
+ {
+ /*
+ ** Sortbysignature() has said that the two have
+ ** equal MX RR's and the same user. Calling sameaddr()
+ ** now checks if the two hosts are as identical as the
+ ** MX RR's are (which might not be the case)
+ ** before saying these are the identical addresses.
+ */
+
+ 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)
+ {
+ /*
+ ** If an earlier milter removed the
+ ** address, a later one can still add
+ ** it back.
+ */
+
+ q->q_state = new->q_state;
+ q->q_flags |= new->q_flags;
+ }
+ new = q;
+ goto done;
+ }
+ }
+ else if (i < 0) /* less than */
+ {
+ insert = true;
+ break;
+ }
+ prev = pq;
+ }
+
+ /* pq should point to an address, never NULL */
+ SM_ASSERT(pq != NULL);
+
+ /* add address on list */
+ if (insert)
+ {
+ /*
+ ** insert before 'pq'. Only possible when at least 1
+ ** ADDRESS is in the list already.
+ */
+
+ new->q_next = *pq;
+ if (prev == NULL)
+ *sendq = new; /* To be the first ADDRESS */
+ else
+ (*prev)->q_next = new;
+ }
+ else
+ {
+ /*
+ ** Place in list at current 'pq' position. Possible
+ ** when there are 0 or more ADDRESS's in the list.
+ */
+
+ new->q_next = NULL;
+ *pq = new;
+ }
+
+ /* added a new address: clear split flag */
+ e->e_flags &= ~EF_SPLIT;
+
+ /*
+ ** Alias the name and handle special mailer types.
+ */
+
+ 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)
+ {
+ /* check if allowed */
+ 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);
+ }
+ }
+
+ /* try aliasing */
+ if (!quoted && QS_IS_OK(new->q_state) &&
+ bitnset(M_ALIASABLE, m->m_flags))
+ alias(new, sendq, aliaslevel, e);
+
+#if USERDB
+ /* if not aliased, look it up in the user database */
+ 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 /* USERDB */
+
+ /*
+ ** If we have a level two config file, then pass the name through
+ ** Ruleset 5 before sending it off. Ruleset 5 has the right
+ ** to rewrite it to another mailer. This gives us a hook
+ ** after local aliasing has been done.
+ */
+
+ 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 it didn't get rewritten to another mailer, go ahead
+ ** and deliver it.
+ */
+
+ if (QS_IS_OK(new->q_state) &&
+ bitnset(M_HASPWENT, m->m_flags))
+ {
+ auto bool fuzzy;
+ SM_MBDB_T user;
+ int status;
+
+ /* warning -- finduser may trash buf */
+ 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)
+ {
+ /* name was a fuzzy match */
+ 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;
+ }
+
+ /* see if it aliases */
+ (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))
+ {
+ /* don't do any more now */
+ 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); /* XXX leak if above code raises exception */
+
+ /*
+ ** If we are at the top level, check to see if this has
+ ** expanded to exactly one address. If so, it can inherit
+ ** the primaryness of the address.
+ **
+ ** While we're at it, clear the QTHISPASS bits.
+ */
+
+ 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)
+ {
+ /* check to see if this actually got a new owner */
+ 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)
+ {
+ /* arrange for return receipt */
+ 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;
+}
+
+/*
+** FINDUSER -- find the password entry for a user.
+**
+** This looks a lot like getpwnam, except that it may want to
+** do some fancier pattern matching in /etc/passwd.
+**
+** This routine contains most of the time of many sendmail runs.
+** It deserves to be optimized.
+**
+** Parameters:
+** name -- the name to match against.
+** fuzzyp -- an outarg that is set to true if this entry
+** was found using the fuzzy matching algorithm;
+** set to false otherwise.
+** user -- structure to fill in if user is found
+**
+** Returns:
+** On success, fill in *user, set *fuzzyp and return EX_OK.
+** If the user was not found, return EX_NOUSER.
+** On error, return EX_TEMPFAIL or EX_OSERR.
+**
+** Side Effects:
+** may modify name.
+*/
+
+int
+finduser(name, fuzzyp, user)
+ char *name;
+ bool *fuzzyp;
+ SM_MBDB_T *user;
+{
+#if MATCHGECOS
+ register struct passwd *pw;
+#endif /* MATCHGECOS */
+ register char *p;
+ bool tryagain;
+ int status;
+
+ if (tTd(29, 4))
+ sm_dprintf("finduser(%s): ", name);
+
+ *fuzzyp = false;
+
+#if HESIOD
+ /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
+ 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 /* HESIOD */
+
+ /* look up this login name using fast path */
+ 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;
+ }
+
+ /* try mapping it to lower case */
+ 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
+ /* see if fuzzy matching allowed */
+ if (!MatchGecos)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("not found (fuzzy disabled)\n");
+ return EX_NOUSER;
+ }
+
+ /* search for a matching full name instead */
+ 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 /* 0 */
+
+ 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 /* DEC OSF/1 3.2 or earlier */
+ endpwent();
+# endif /* DEC_OSF_BROKEN_GETPWENT */
+ if (pw == NULL)
+ return EX_NOUSER;
+ sm_mbdb_frompw(user, pw);
+ return EX_OK;
+#else /* MATCHGECOS */
+ if (tTd(29, 4))
+ sm_dprintf("not found (fuzzy disabled)\n");
+ return EX_NOUSER;
+#endif /* MATCHGECOS */
+}
+
+/*
+** WRITABLE -- predicate returning if the file is writable.
+**
+** This routine must duplicate the algorithm in sys/fio.c.
+** Unfortunately, we cannot use the access call since we
+** won't necessarily be the real uid when we try to
+** actually open the file.
+**
+** Notice that ANY file with ANY execute bit is automatically
+** not writable. This is also enforced by mailfile.
+**
+** Parameters:
+** filename -- the file name to check.
+** ctladdr -- the controlling address for this file.
+** flags -- SFF_* flags to control the function.
+**
+** Returns:
+** true -- if we will be able to write this file.
+** false -- if we cannot write this file.
+**
+** Side Effects:
+** none.
+*/
+
+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);
+
+ /*
+ ** File does exist -- check that it is writable.
+ */
+
+ 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;
+}
+
+/*
+** INCLUDE -- handle :include: specification.
+**
+** Parameters:
+** fname -- filename to include.
+** forwarding -- if true, we are reading a .forward file.
+** if false, it's a :include: file.
+** ctladdr -- address template to use to fill in these
+** addresses -- effective user/group id are
+** the important things.
+** sendq -- a pointer to the head of the send queue
+** to put these addresses in.
+** aliaslevel -- the alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** open error status
+**
+** Side Effects:
+** reads the :include: file and sends to everyone
+** listed in that file.
+**
+** Security Note:
+** If you have restricted chown (that is, you can't
+** give a file away), it is reasonable to allow programs
+** and files called from this :include: file to be to be
+** run as the owner of the :include: file. This is bogus
+** if there is any chance of someone giving away a file.
+** We assume that pre-POSIX systems can give away files.
+**
+** There is an additional restriction that if you
+** forward to a :include: file, it will not take on
+** the ownership of the :include: file. This may not
+** be necessary, but shouldn't hurt.
+*/
+
+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 RunAsUser set, won't be able to run programs as user
+ ** so mark them as unsafe unless the administrator knows better.
+ */
+
+ 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 /* MAILER_SETUID_METHOD == USE_SETEUID */
+# 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 /* MAILER_SETUID_METHOD == USE_SETREUID */
+ }
+ }
+#endif /* MAILER_SETUID_METHOD != USE_SETUID */
+
+ if (tTd(27, 9))
+ sm_dprintf("include: new uid = %d/%d\n",
+ (int) getuid(), (int) geteuid());
+
+ /*
+ ** If home directory is remote mounted but server is down,
+ ** this can hang or give errors; use a timeout to avoid this
+ */
+
+ if (setjmp(CtxIncludeTimeout) != 0)
+ {
+ ctladdr->q_state = QS_QUEUEUP;
+ errno = 0;
+
+ /* return pseudo-error code */
+ rval = E_SM_OPENTIMEOUT;
+ goto resetuid;
+ }
+ if (TimeOuts.to_fileopen > 0)
+ ev = sm_setevent(TimeOuts.to_fileopen, includetimeout, 0);
+ else
+ ev = NULL;
+
+
+ /* check for writable parent directory */
+ p = strrchr(fname, '/');
+ if (p != NULL)
+ {
+ int ret;
+
+ *p = '\0';
+ ret = safedirpath(fname, uid, gid, user,
+ sfflags|SFF_SAFEDIRPATH, 0, 0);
+ if (ret == 0)
+ {
+ /* in safe directory: relax chown & link rules */
+ 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 = '/';
+ }
+
+ /* allow links only in unwritable directories */
+ 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)
+ {
+ /* don't use this :include: file */
+ 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 /* USESETEUID */
+ 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 /* USESETEUID */
+ }
+ if (setgid(savedgid) < 0)
+ syserr("!setgid(%d) failure (real=%d eff=%d)",
+ (int) savedgid, (int) getgid(),
+ (int) getegid());
+ }
+#endif /* HASSETREUID || USESETEUID */
+
+ 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;
+ }
+
+ /* if path was writable, check to avoid file giveaway tricks */
+ 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 no controlling user or coming from an alias delivery */
+ 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)
+ {
+ /* optimization -- avoid getpwuid if we already have info */
+ 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))
+ {
+ /* don't do any more now */
+ ctladdr->q_state = QS_VERIFIED;
+ e->e_nrcpts++;
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ return rval;
+ }
+
+ /*
+ ** Check to see if some bad guy can write this file
+ **
+ ** Group write checking could be more clever, e.g.,
+ ** guessing as to which groups are actually safe ("sys"
+ ** may be; "user" probably is not).
+ */
+
+ 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;
+ }
+
+ /* read the file -- each line is a comma-separated list. */
+ 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;
+
+ /* <sp>#@# introduces a comment anywhere */
+ /* for Japanese character sets */
+ 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)
+ {
+ /* just stop reading and processing further entries */
+#if 0
+ /* additional: (?) */
+ ctladdr->q_state = QS_DONTSEND;
+#endif /* 0 */
+
+ 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;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxIncludeTimeout, 1);
+}
+
+/*
+** SENDTOARGV -- send to an argument vector.
+**
+** Parameters:
+** argv -- argument vector to send to.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** puts all addresses on the argument vector onto the
+** send queue.
+*/
+
+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);
+}
+
+/*
+** GETCTLADDR -- get controlling address from an address header.
+**
+** If none, get one corresponding to the effective userid.
+**
+** Parameters:
+** a -- the address to find the controller of.
+**
+** Returns:
+** the controlling address.
+*/
+
+ADDRESS *
+getctladdr(a)
+ register ADDRESS *a;
+{
+ while (a != NULL && !bitset(QGOODUID, a->q_flags))
+ a = a->q_alias;
+ return a;
+}
+
+/*
+** SELF_REFERENCE -- check to see if an address references itself
+**
+** The check is done through a chain of aliases. If it is part of
+** a loop, break the loop at the "best" address, that is, the one
+** that exists as a real user.
+**
+** This is to handle the case of:
+** awc: Andrew.Chang
+** Andrew.Chang: awc@mail.server
+** which is a problem only on mail.server.
+**
+** Parameters:
+** a -- the address to check.
+**
+** Returns:
+** The address that should be retained.
+*/
+
+static ADDRESS *
+self_reference(a)
+ ADDRESS *a;
+{
+ ADDRESS *b; /* top entry in self ref loop */
+ ADDRESS *c; /* entry that point to a real mail box */
+
+ 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;
+ }
+
+ /*
+ ** Pick the first address that resolved to a real mail box
+ ** i.e has a mbdb entry. The returned value will be marked
+ ** QSELFREF in recipient(), which in turn will disable alias()
+ ** from marking it as QS_IS_DEAD(), which mean it will be used
+ ** as a deliverable address.
+ **
+ ** The 2 key thing to note here are:
+ ** 1) we are in a recursive call sequence:
+ ** alias->sendtolist->recipient->alias
+ ** 2) normally, when we return back to alias(), the address
+ ** will be marked QS_EXPANDED, since alias() assumes the
+ ** expanded form will be used instead of the current address.
+ ** This behaviour is turned off if the address is marked
+ ** QSELFREF. We set QSELFREF when we return to recipient().
+ */
+
+ 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");
+
+ /* ought to cache results here */
+ if (sameaddr(b, c))
+ return b;
+ else
+ return c;
+ }
+ if (tTd(27, 2))
+ sm_dprintf("failed\n");
+ }
+ else
+ {
+ /* if local delivery, compare usernames */
+ 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;
+}
OpenPOWER on IntegriCloud