summaryrefslogtreecommitdiffstats
path: root/contrib/sendmail/src/deliver.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/sendmail/src/deliver.c')
-rw-r--r--contrib/sendmail/src/deliver.c6271
1 files changed, 6271 insertions, 0 deletions
diff --git a/contrib/sendmail/src/deliver.c b/contrib/sendmail/src/deliver.c
new file mode 100644
index 0000000..acd977f
--- /dev/null
+++ b/contrib/sendmail/src/deliver.c
@@ -0,0 +1,6271 @@
+/*
+ * Copyright (c) 1998-2010 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>
+#include <sm/time.h>
+
+SM_RCSID("@(#)$Id: deliver.c,v 8.1024 2011/01/12 23:52:59 ca Exp $")
+
+#if HASSETUSERCONTEXT
+# include <login_cap.h>
+#endif /* HASSETUSERCONTEXT */
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+#if STARTTLS || SASL
+# include "sfsasl.h"
+#endif /* STARTTLS || SASL */
+
+static int deliver __P((ENVELOPE *, ADDRESS *));
+static void dup_queue_file __P((ENVELOPE *, ENVELOPE *, int));
+static void mailfiletimeout __P((int));
+static void endwaittimeout __P((int));
+static int parse_hostsignature __P((char *, char **, MAILER *));
+static void sendenvelope __P((ENVELOPE *, int));
+static int coloncmp __P((const char *, const char *));
+
+#if STARTTLS
+static int starttls __P((MAILER *, MCI *, ENVELOPE *));
+static int endtlsclt __P((MCI *));
+#endif /* STARTTLS */
+# if STARTTLS || SASL
+static bool iscltflgset __P((ENVELOPE *, int));
+# endif /* STARTTLS || SASL */
+
+/*
+** SENDALL -- actually send all the messages.
+**
+** Parameters:
+** e -- the envelope to send.
+** mode -- the delivery mode to use. If SM_DEFAULT, use
+** the current e->e_sendmode.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Scans the send lists and sends everything it finds.
+** Delivers any appropriate error messages.
+** If we are running in a non-interactive mode, takes the
+** appropriate action.
+*/
+
+void
+sendall(e, mode)
+ ENVELOPE *e;
+ int mode;
+{
+ register ADDRESS *q;
+ char *owner;
+ int otherowners;
+ int save_errno;
+ register ENVELOPE *ee;
+ ENVELOPE *splitenv = NULL;
+ int oldverbose = Verbose;
+ bool somedeliveries = false, expensive = false;
+ pid_t pid;
+
+ /*
+ ** If this message is to be discarded, don't bother sending
+ ** the message at all.
+ */
+
+ if (bitset(EF_DISCARD, e->e_flags))
+ {
+ if (tTd(13, 1))
+ sm_dprintf("sendall: discarding id %s\n", e->e_id);
+ e->e_flags |= EF_CLRQUEUE;
+ if (LogLevel > 9)
+ logundelrcpts(e, "discarded", 9, true);
+ else if (LogLevel > 4)
+ sm_syslog(LOG_INFO, e->e_id, "discarded");
+ markstats(e, NULL, STATS_REJECT);
+ return;
+ }
+
+ /*
+ ** If we have had global, fatal errors, don't bother sending
+ ** the message at all if we are in SMTP mode. Local errors
+ ** (e.g., a single address failing) will still cause the other
+ ** addresses to be sent.
+ */
+
+ if (bitset(EF_FATALERRS, e->e_flags) &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ e->e_flags |= EF_CLRQUEUE;
+ return;
+ }
+
+ /* determine actual delivery mode */
+ if (mode == SM_DEFAULT)
+ {
+ mode = e->e_sendmode;
+ if (mode != SM_VERIFY && mode != SM_DEFER &&
+ shouldqueue(e->e_msgpriority, e->e_ctime))
+ mode = SM_QUEUE;
+ }
+
+ if (tTd(13, 1))
+ {
+ sm_dprintf("\n===== SENDALL: mode %c, id %s, e_from ",
+ mode, e->e_id);
+ printaddr(sm_debug_file(), &e->e_from, false);
+ sm_dprintf("\te_flags = ");
+ printenvflags(e);
+ sm_dprintf("sendqueue:\n");
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ }
+
+ /*
+ ** Do any preprocessing necessary for the mode we are running.
+ ** Check to make sure the hop count is reasonable.
+ ** Delete sends to the sender in mailing lists.
+ */
+
+ CurEnv = e;
+ if (tTd(62, 1))
+ checkfds(NULL);
+
+ if (e->e_hopcount > MaxHopCount)
+ {
+ char *recip;
+
+ if (e->e_sendqueue != NULL &&
+ e->e_sendqueue->q_paddr != NULL)
+ recip = e->e_sendqueue->q_paddr;
+ else
+ recip = "(nobody)";
+
+ errno = 0;
+ queueup(e, WILL_BE_QUEUED(mode), false);
+ e->e_flags |= EF_FATALERRS|EF_PM_NOTIFY|EF_CLRQUEUE;
+ ExitStat = EX_UNAVAILABLE;
+ syserr("554 5.4.6 Too many hops %d (%d max): from %s via %s, to %s",
+ e->e_hopcount, MaxHopCount, e->e_from.q_paddr,
+ RealHostName == NULL ? "localhost" : RealHostName,
+ recip);
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ q->q_state = QS_BADADDR;
+ q->q_status = "5.4.6";
+ q->q_rstatus = "554 5.4.6 Too many hops";
+ }
+ return;
+ }
+
+ /*
+ ** Do sender deletion.
+ **
+ ** If the sender should be queued up, skip this.
+ ** This can happen if the name server is hosed when you
+ ** are trying to send mail. The result is that the sender
+ ** is instantiated in the queue as a recipient.
+ */
+
+ if (!bitset(EF_METOO, e->e_flags) &&
+ !QS_IS_QUEUEUP(e->e_from.q_state))
+ {
+ if (tTd(13, 5))
+ {
+ sm_dprintf("sendall: QS_SENDER ");
+ printaddr(sm_debug_file(), &e->e_from, false);
+ }
+ e->e_from.q_state = QS_SENDER;
+ (void) recipient(&e->e_from, &e->e_sendqueue, 0, e);
+ }
+
+ /*
+ ** Handle alias owners.
+ **
+ ** We scan up the q_alias chain looking for owners.
+ ** We discard owners that are the same as the return path.
+ */
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ register struct address *a;
+
+ for (a = q; a != NULL && a->q_owner == NULL; a = a->q_alias)
+ continue;
+ if (a != NULL)
+ q->q_owner = a->q_owner;
+
+ if (q->q_owner != NULL &&
+ !QS_IS_DEAD(q->q_state) &&
+ strcmp(q->q_owner, e->e_from.q_paddr) == 0)
+ q->q_owner = NULL;
+ }
+
+ if (tTd(13, 25))
+ {
+ sm_dprintf("\nAfter first owner pass, sendq =\n");
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ }
+
+ owner = "";
+ otherowners = 1;
+ while (owner != NULL && otherowners > 0)
+ {
+ if (tTd(13, 28))
+ sm_dprintf("owner = \"%s\", otherowners = %d\n",
+ owner, otherowners);
+ owner = NULL;
+ otherowners = bitset(EF_SENDRECEIPT, e->e_flags) ? 1 : 0;
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (tTd(13, 30))
+ {
+ sm_dprintf("Checking ");
+ printaddr(sm_debug_file(), q, false);
+ }
+ if (QS_IS_DEAD(q->q_state))
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... QS_IS_DEAD\n");
+ continue;
+ }
+ if (tTd(13, 29) && !tTd(13, 30))
+ {
+ sm_dprintf("Checking ");
+ printaddr(sm_debug_file(), q, false);
+ }
+
+ if (q->q_owner != NULL)
+ {
+ if (owner == NULL)
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... First owner = \"%s\"\n",
+ q->q_owner);
+ owner = q->q_owner;
+ }
+ else if (owner != q->q_owner)
+ {
+ if (strcmp(owner, q->q_owner) == 0)
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... Same owner = \"%s\"\n",
+ owner);
+
+ /* make future comparisons cheap */
+ q->q_owner = owner;
+ }
+ else
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... Another owner \"%s\"\n",
+ q->q_owner);
+ otherowners++;
+ }
+ owner = q->q_owner;
+ }
+ else if (tTd(13, 40))
+ sm_dprintf(" ... Same owner = \"%s\"\n",
+ owner);
+ }
+ else
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... Null owner\n");
+ otherowners++;
+ }
+
+ if (QS_IS_BADADDR(q->q_state))
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... QS_IS_BADADDR\n");
+ continue;
+ }
+
+ if (QS_IS_QUEUEUP(q->q_state))
+ {
+ MAILER *m = q->q_mailer;
+
+ /*
+ ** If we have temporary address failures
+ ** (e.g., dns failure) and a fallback MX is
+ ** set, send directly to the fallback MX host.
+ */
+
+ if (FallbackMX != NULL &&
+ !wordinclass(FallbackMX, 'w') &&
+ mode != SM_VERIFY &&
+ !bitnset(M_NOMX, m->m_flags) &&
+ strcmp(m->m_mailer, "[IPC]") == 0 &&
+ m->m_argv[0] != NULL &&
+ strcmp(m->m_argv[0], "TCP") == 0)
+ {
+ int len;
+ char *p;
+
+ if (tTd(13, 30))
+ sm_dprintf(" ... FallbackMX\n");
+
+ len = strlen(FallbackMX) + 1;
+ p = sm_rpool_malloc_x(e->e_rpool, len);
+ (void) sm_strlcpy(p, FallbackMX, len);
+ q->q_state = QS_OK;
+ q->q_host = p;
+ }
+ else
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... QS_IS_QUEUEUP\n");
+ continue;
+ }
+ }
+
+ /*
+ ** If this mailer is expensive, and if we don't
+ ** want to make connections now, just mark these
+ ** addresses and return. This is useful if we
+ ** want to batch connections to reduce load. This
+ ** will cause the messages to be queued up, and a
+ ** daemon will come along to send the messages later.
+ */
+
+ if (NoConnect && !Verbose &&
+ bitnset(M_EXPENSIVE, q->q_mailer->m_flags))
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... expensive\n");
+ q->q_state = QS_QUEUEUP;
+ expensive = true;
+ }
+ else if (bitnset(M_HOLD, q->q_mailer->m_flags) &&
+ QueueLimitId == NULL &&
+ QueueLimitSender == NULL &&
+ QueueLimitRecipient == NULL)
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... hold\n");
+ q->q_state = QS_QUEUEUP;
+ expensive = true;
+ }
+ else if (QueueMode != QM_QUARANTINE &&
+ e->e_quarmsg != NULL)
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... quarantine: %s\n",
+ e->e_quarmsg);
+ q->q_state = QS_QUEUEUP;
+ expensive = true;
+ }
+ else
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... deliverable\n");
+ somedeliveries = true;
+ }
+ }
+
+ if (owner != NULL && otherowners > 0)
+ {
+ /*
+ ** Split this envelope into two.
+ */
+
+ ee = (ENVELOPE *) sm_rpool_malloc_x(e->e_rpool,
+ sizeof(*ee));
+ STRUCTCOPY(*e, *ee);
+ ee->e_message = NULL;
+ ee->e_id = NULL;
+ assign_queueid(ee);
+
+ if (tTd(13, 1))
+ sm_dprintf("sendall: split %s into %s, owner = \"%s\", otherowners = %d\n",
+ e->e_id, ee->e_id, owner,
+ otherowners);
+
+ ee->e_header = copyheader(e->e_header, ee->e_rpool);
+ ee->e_sendqueue = copyqueue(e->e_sendqueue,
+ ee->e_rpool);
+ ee->e_errorqueue = copyqueue(e->e_errorqueue,
+ ee->e_rpool);
+ ee->e_flags = e->e_flags & ~(EF_INQUEUE|EF_CLRQUEUE|EF_FATALERRS|EF_SENDRECEIPT|EF_RET_PARAM);
+ ee->e_flags |= EF_NORECEIPT;
+ setsender(owner, ee, NULL, '\0', true);
+ if (tTd(13, 5))
+ {
+ sm_dprintf("sendall(split): QS_SENDER ");
+ printaddr(sm_debug_file(), &ee->e_from, false);
+ }
+ ee->e_from.q_state = QS_SENDER;
+ ee->e_dfp = NULL;
+ ee->e_lockfp = NULL;
+ ee->e_xfp = NULL;
+ ee->e_qgrp = e->e_qgrp;
+ ee->e_qdir = e->e_qdir;
+ ee->e_errormode = EM_MAIL;
+ ee->e_sibling = splitenv;
+ ee->e_statmsg = NULL;
+ if (e->e_quarmsg != NULL)
+ ee->e_quarmsg = sm_rpool_strdup_x(ee->e_rpool,
+ e->e_quarmsg);
+ splitenv = ee;
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (q->q_owner == owner)
+ {
+ q->q_state = QS_CLONED;
+ if (tTd(13, 6))
+ sm_dprintf("\t... stripping %s from original envelope\n",
+ q->q_paddr);
+ }
+ }
+ for (q = ee->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (q->q_owner != owner)
+ {
+ q->q_state = QS_CLONED;
+ if (tTd(13, 6))
+ sm_dprintf("\t... dropping %s from cloned envelope\n",
+ q->q_paddr);
+ }
+ else
+ {
+ /* clear DSN parameters */
+ q->q_flags &= ~(QHASNOTIFY|Q_PINGFLAGS);
+ q->q_flags |= DefaultNotify & ~QPINGONSUCCESS;
+ if (tTd(13, 6))
+ sm_dprintf("\t... moving %s to cloned envelope\n",
+ q->q_paddr);
+ }
+ }
+
+ if (mode != SM_VERIFY && bitset(EF_HAS_DF, e->e_flags))
+ dup_queue_file(e, ee, DATAFL_LETTER);
+
+ /*
+ ** Give the split envelope access to the parent
+ ** transcript file for errors obtained while
+ ** processing the recipients (done before the
+ ** envelope splitting).
+ */
+
+ if (e->e_xfp != NULL)
+ ee->e_xfp = sm_io_dup(e->e_xfp);
+
+ /* failed to dup e->e_xfp, start a new transcript */
+ if (ee->e_xfp == NULL)
+ openxscript(ee);
+
+ if (mode != SM_VERIFY && LogLevel > 4)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: clone: owner=%s",
+ ee->e_id, owner);
+ }
+ }
+
+ if (owner != NULL)
+ {
+ setsender(owner, e, NULL, '\0', true);
+ if (tTd(13, 5))
+ {
+ sm_dprintf("sendall(owner): QS_SENDER ");
+ printaddr(sm_debug_file(), &e->e_from, false);
+ }
+ e->e_from.q_state = QS_SENDER;
+ e->e_errormode = EM_MAIL;
+ e->e_flags |= EF_NORECEIPT;
+ e->e_flags &= ~EF_FATALERRS;
+ }
+
+ /* if nothing to be delivered, just queue up everything */
+ if (!somedeliveries && !WILL_BE_QUEUED(mode) &&
+ mode != SM_VERIFY)
+ {
+ time_t now;
+
+ if (tTd(13, 29))
+ sm_dprintf("No deliveries: auto-queueing\n");
+ mode = SM_QUEUE;
+ now = curtime();
+
+ /* treat this as a delivery in terms of counting tries */
+ e->e_dtime = now;
+ if (!expensive)
+ e->e_ntries++;
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ ee->e_dtime = now;
+ if (!expensive)
+ ee->e_ntries++;
+ }
+ }
+
+ if ((WILL_BE_QUEUED(mode) || mode == SM_FORK ||
+ (mode != SM_VERIFY &&
+ (SuperSafe == SAFE_REALLY ||
+ SuperSafe == SAFE_REALLY_POSTMILTER))) &&
+ (!bitset(EF_INQUEUE, e->e_flags) || splitenv != NULL))
+ {
+ bool msync;
+
+ /*
+ ** Be sure everything is instantiated in the queue.
+ ** Split envelopes first in case the machine crashes.
+ ** If the original were done first, we may lose
+ ** recipients.
+ */
+
+#if !HASFLOCK
+ msync = false;
+#else /* !HASFLOCK */
+ msync = mode == SM_FORK;
+#endif /* !HASFLOCK */
+
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ queueup(ee, WILL_BE_QUEUED(mode), msync);
+ queueup(e, WILL_BE_QUEUED(mode), msync);
+ }
+
+ if (tTd(62, 10))
+ checkfds("after envelope splitting");
+
+ /*
+ ** If we belong in background, fork now.
+ */
+
+ if (tTd(13, 20))
+ {
+ sm_dprintf("sendall: final mode = %c\n", mode);
+ if (tTd(13, 21))
+ {
+ sm_dprintf("\n================ Final Send Queue(s) =====================\n");
+ sm_dprintf("\n *** Envelope %s, e_from=%s ***\n",
+ e->e_id, e->e_from.q_paddr);
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ sm_dprintf("\n *** Envelope %s, e_from=%s ***\n",
+ ee->e_id, ee->e_from.q_paddr);
+ printaddr(sm_debug_file(), ee->e_sendqueue, true);
+ }
+ sm_dprintf("==========================================================\n\n");
+ }
+ }
+ switch (mode)
+ {
+ case SM_VERIFY:
+ Verbose = 2;
+ break;
+
+ case SM_QUEUE:
+ case SM_DEFER:
+#if HASFLOCK
+ queueonly:
+#endif /* HASFLOCK */
+ if (e->e_nrcpts > 0)
+ e->e_flags |= EF_INQUEUE;
+ (void) dropenvelope(e, splitenv != NULL, true);
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ if (ee->e_nrcpts > 0)
+ ee->e_flags |= EF_INQUEUE;
+ (void) dropenvelope(ee, false, true);
+ }
+ return;
+
+ case SM_FORK:
+ if (e->e_xfp != NULL)
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+
+#if !HASFLOCK
+ /*
+ ** Since fcntl locking has the interesting semantic that
+ ** the lock is owned by a process, not by an open file
+ ** descriptor, we have to flush this to the queue, and
+ ** then restart from scratch in the child.
+ */
+
+ {
+ /* save id for future use */
+ char *qid = e->e_id;
+
+ /* now drop the envelope in the parent */
+ e->e_flags |= EF_INQUEUE;
+ (void) dropenvelope(e, splitenv != NULL, false);
+
+ /* arrange to reacquire lock after fork */
+ e->e_id = qid;
+ }
+
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ /* save id for future use */
+ char *qid = ee->e_id;
+
+ /* drop envelope in parent */
+ ee->e_flags |= EF_INQUEUE;
+ (void) dropenvelope(ee, false, false);
+
+ /* and save qid for reacquisition */
+ ee->e_id = qid;
+ }
+
+#endif /* !HASFLOCK */
+
+ /*
+ ** Since the delivery may happen in a child and the parent
+ ** does not wait, the parent may close the maps thereby
+ ** removing any shared memory used by the map. Therefore,
+ ** close the maps now so the child will dynamically open
+ ** them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("deliver: fork 1");
+#if HASFLOCK
+ goto queueonly;
+#else /* HASFLOCK */
+ e->e_id = NULL;
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ ee->e_id = NULL;
+ return;
+#endif /* HASFLOCK */
+ }
+ else if (pid > 0)
+ {
+#if HASFLOCK
+ /* be sure we leave the temp files to our child */
+ /* close any random open files in the envelope */
+ closexscript(e);
+ if (e->e_dfp != NULL)
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ e->e_flags &= ~EF_HAS_DF;
+
+ /* can't call unlockqueue to avoid unlink of xfp */
+ if (e->e_lockfp != NULL)
+ (void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
+ else
+ syserr("%s: sendall: null lockfp", e->e_id);
+ e->e_lockfp = NULL;
+#endif /* HASFLOCK */
+
+ /* make sure the parent doesn't own the envelope */
+ e->e_id = NULL;
+
+#if USE_DOUBLE_FORK
+ /* catch intermediate zombie */
+ (void) waitfor(pid);
+#endif /* USE_DOUBLE_FORK */
+ return;
+ }
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** Since we have accepted responsbility for the message,
+ ** change the SIGTERM handler. intsig() (the old handler)
+ ** would remove the envelope if this was a command line
+ ** message submission.
+ */
+
+ (void) sm_signal(SIGTERM, SIG_DFL);
+
+#if USE_DOUBLE_FORK
+ /* double fork to avoid zombies */
+ pid = fork();
+ if (pid > 0)
+ exit(EX_OK);
+ save_errno = errno;
+#endif /* USE_DOUBLE_FORK */
+
+ CurrentPid = getpid();
+
+ /* be sure we are immune from the terminal */
+ disconnect(2, e);
+ clearstats();
+
+ /* prevent parent from waiting if there was an error */
+ if (pid < 0)
+ {
+ errno = save_errno;
+ syserr("deliver: fork 2");
+#if HASFLOCK
+ e->e_flags |= EF_INQUEUE;
+#else /* HASFLOCK */
+ e->e_id = NULL;
+#endif /* HASFLOCK */
+ finis(true, true, ExitStat);
+ }
+
+ /* be sure to give error messages in child */
+ QuickAbort = false;
+
+ /*
+ ** Close any cached connections.
+ **
+ ** We don't send the QUIT protocol because the parent
+ ** still knows about the connection.
+ **
+ ** This should only happen when delivering an error
+ ** message.
+ */
+
+ mci_flush(false, NULL);
+
+#if HASFLOCK
+ break;
+#else /* HASFLOCK */
+
+ /*
+ ** Now reacquire and run the various queue files.
+ */
+
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ ENVELOPE *sibling = ee->e_sibling;
+
+ (void) dowork(ee->e_qgrp, ee->e_qdir, ee->e_id,
+ false, false, ee);
+ ee->e_sibling = sibling;
+ }
+ (void) dowork(e->e_qgrp, e->e_qdir, e->e_id,
+ false, false, e);
+ finis(true, true, ExitStat);
+#endif /* HASFLOCK */
+ }
+
+ sendenvelope(e, mode);
+ (void) dropenvelope(e, true, true);
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ CurEnv = ee;
+ if (mode != SM_VERIFY)
+ openxscript(ee);
+ sendenvelope(ee, mode);
+ (void) dropenvelope(ee, true, true);
+ }
+ CurEnv = e;
+
+ Verbose = oldverbose;
+ if (mode == SM_FORK)
+ finis(true, true, ExitStat);
+}
+
+static void
+sendenvelope(e, mode)
+ register ENVELOPE *e;
+ int mode;
+{
+ register ADDRESS *q;
+ bool didany;
+
+ if (tTd(13, 10))
+ sm_dprintf("sendenvelope(%s) e_flags=0x%lx\n",
+ e->e_id == NULL ? "[NOQUEUE]" : e->e_id,
+ e->e_flags);
+ if (LogLevel > 80)
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "sendenvelope, flags=0x%lx",
+ e->e_flags);
+
+ /*
+ ** If we have had global, fatal errors, don't bother sending
+ ** the message at all if we are in SMTP mode. Local errors
+ ** (e.g., a single address failing) will still cause the other
+ ** addresses to be sent.
+ */
+
+ if (bitset(EF_FATALERRS, e->e_flags) &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ e->e_flags |= EF_CLRQUEUE;
+ return;
+ }
+
+ /*
+ ** Don't attempt deliveries if we want to bounce now
+ ** or if deliver-by time is exceeded.
+ */
+
+ if (!bitset(EF_RESPONSE, e->e_flags) &&
+ (TimeOuts.to_q_return[e->e_timeoutclass] == NOW ||
+ (IS_DLVR_RETURN(e) && e->e_deliver_by > 0 &&
+ curtime() > e->e_ctime + e->e_deliver_by)))
+ return;
+
+ /*
+ ** Run through the list and send everything.
+ **
+ ** Set EF_GLOBALERRS so that error messages during delivery
+ ** result in returned mail.
+ */
+
+ e->e_nsent = 0;
+ e->e_flags |= EF_GLOBALERRS;
+
+ macdefine(&e->e_macro, A_PERM, macid("{envid}"), e->e_envid);
+ macdefine(&e->e_macro, A_PERM, macid("{bodytype}"), e->e_bodytype);
+ didany = false;
+
+ if (!bitset(EF_SPLIT, e->e_flags))
+ {
+ ENVELOPE *oldsib;
+ ENVELOPE *ee;
+
+ /*
+ ** Save old sibling and set it to NULL to avoid
+ ** queueing up the same envelopes again.
+ ** This requires that envelopes in that list have
+ ** been take care of before (or at some other place).
+ */
+
+ oldsib = e->e_sibling;
+ e->e_sibling = NULL;
+ if (!split_by_recipient(e) &&
+ bitset(EF_FATALERRS, e->e_flags))
+ {
+ if (OpMode == MD_SMTP || OpMode == MD_DAEMON)
+ e->e_flags |= EF_CLRQUEUE;
+ return;
+ }
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ queueup(ee, false, true);
+
+ /* clean up */
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ {
+ /* now unlock the job */
+ closexscript(ee);
+ unlockqueue(ee);
+
+ /* this envelope is marked unused */
+ if (ee->e_dfp != NULL)
+ {
+ (void) sm_io_close(ee->e_dfp, SM_TIME_DEFAULT);
+ ee->e_dfp = NULL;
+ }
+ ee->e_id = NULL;
+ ee->e_flags &= ~EF_HAS_DF;
+ }
+ e->e_sibling = oldsib;
+ }
+
+ /* now run through the queue */
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+#if XDEBUG
+ char wbuf[MAXNAME + 20];
+
+ (void) sm_snprintf(wbuf, sizeof(wbuf), "sendall(%.*s)",
+ MAXNAME, q->q_paddr);
+ checkfd012(wbuf);
+#endif /* XDEBUG */
+ if (mode == SM_VERIFY)
+ {
+ e->e_to = q->q_paddr;
+ if (QS_IS_SENDABLE(q->q_state))
+ {
+ if (q->q_host != NULL && q->q_host[0] != '\0')
+ message("deliverable: mailer %s, host %s, user %s",
+ q->q_mailer->m_name,
+ q->q_host,
+ q->q_user);
+ else
+ message("deliverable: mailer %s, user %s",
+ q->q_mailer->m_name,
+ q->q_user);
+ }
+ }
+ else if (QS_IS_OK(q->q_state))
+ {
+ /*
+ ** Checkpoint the send list every few addresses
+ */
+
+ if (CheckpointInterval > 0 &&
+ e->e_nsent >= CheckpointInterval)
+ {
+ queueup(e, false, false);
+ e->e_nsent = 0;
+ }
+ (void) deliver(e, q);
+ didany = true;
+ }
+ }
+ if (didany)
+ {
+ e->e_dtime = curtime();
+ e->e_ntries++;
+ }
+
+#if XDEBUG
+ checkfd012("end of sendenvelope");
+#endif /* XDEBUG */
+}
+
+#if REQUIRES_DIR_FSYNC
+/*
+** SYNC_DIR -- fsync a directory based on a filename
+**
+** Parameters:
+** filename -- path of file
+** panic -- panic?
+**
+** Returns:
+** none
+*/
+
+void
+sync_dir(filename, panic)
+ char *filename;
+ bool panic;
+{
+ int dirfd;
+ char *dirp;
+ char dir[MAXPATHLEN];
+
+ if (!RequiresDirfsync)
+ return;
+
+ /* filesystems which require the directory be synced */
+ dirp = strrchr(filename, '/');
+ if (dirp != NULL)
+ {
+ if (sm_strlcpy(dir, filename, sizeof(dir)) >= sizeof(dir))
+ return;
+ dir[dirp - filename] = '\0';
+ dirp = dir;
+ }
+ else
+ dirp = ".";
+ dirfd = open(dirp, O_RDONLY, 0700);
+ if (tTd(40,32))
+ sm_syslog(LOG_INFO, NOQID, "sync_dir: %s: fsync(%d)",
+ dirp, dirfd);
+ if (dirfd >= 0)
+ {
+ if (fsync(dirfd) < 0)
+ {
+ if (panic)
+ syserr("!sync_dir: cannot fsync directory %s",
+ dirp);
+ else if (LogLevel > 1)
+ sm_syslog(LOG_ERR, NOQID,
+ "sync_dir: cannot fsync directory %s: %s",
+ dirp, sm_errstring(errno));
+ }
+ (void) close(dirfd);
+ }
+}
+#endif /* REQUIRES_DIR_FSYNC */
+/*
+** DUP_QUEUE_FILE -- duplicate a queue file into a split queue
+**
+** Parameters:
+** e -- the existing envelope
+** ee -- the new envelope
+** type -- the queue file type (e.g., DATAFL_LETTER)
+**
+** Returns:
+** none
+*/
+
+static void
+dup_queue_file(e, ee, type)
+ ENVELOPE *e, *ee;
+ int type;
+{
+ char f1buf[MAXPATHLEN], f2buf[MAXPATHLEN];
+
+ ee->e_dfp = NULL;
+ ee->e_xfp = NULL;
+
+ /*
+ ** Make sure both are in the same directory.
+ */
+
+ (void) sm_strlcpy(f1buf, queuename(e, type), sizeof(f1buf));
+ (void) sm_strlcpy(f2buf, queuename(ee, type), sizeof(f2buf));
+
+ /* Force the df to disk if it's not there yet */
+ if (type == DATAFL_LETTER && e->e_dfp != NULL &&
+ sm_io_setinfo(e->e_dfp, SM_BF_COMMIT, NULL) < 0 &&
+ errno != EINVAL)
+ {
+ syserr("!dup_queue_file: can't commit %s", f1buf);
+ /* NOTREACHED */
+ }
+
+ if (link(f1buf, f2buf) < 0)
+ {
+ int save_errno = errno;
+
+ syserr("sendall: link(%s, %s)", f1buf, f2buf);
+ if (save_errno == EEXIST)
+ {
+ if (unlink(f2buf) < 0)
+ {
+ syserr("!sendall: unlink(%s): permanent",
+ f2buf);
+ /* NOTREACHED */
+ }
+ if (link(f1buf, f2buf) < 0)
+ {
+ syserr("!sendall: link(%s, %s): permanent",
+ f1buf, f2buf);
+ /* NOTREACHED */
+ }
+ }
+ }
+ SYNC_DIR(f2buf, true);
+}
+/*
+** DOFORK -- do a fork, retrying a couple of times on failure.
+**
+** This MUST be a macro, since after a vfork we are running
+** two processes on the same stack!!!
+**
+** Parameters:
+** none.
+**
+** Returns:
+** From a macro??? You've got to be kidding!
+**
+** Side Effects:
+** Modifies the ==> LOCAL <== variable 'pid', leaving:
+** pid of child in parent, zero in child.
+** -1 on unrecoverable error.
+**
+** Notes:
+** I'm awfully sorry this looks so awful. That's
+** vfork for you.....
+*/
+
+#define NFORKTRIES 5
+
+#ifndef FORK
+# define FORK fork
+#endif /* ! FORK */
+
+#define DOFORK(fORKfN) \
+{\
+ register int i;\
+\
+ for (i = NFORKTRIES; --i >= 0; )\
+ {\
+ pid = fORKfN();\
+ if (pid >= 0)\
+ break;\
+ if (i > 0)\
+ (void) sleep((unsigned) NFORKTRIES - i);\
+ }\
+}
+/*
+** DOFORK -- simple fork interface to DOFORK.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** pid of child in parent.
+** zero in child.
+** -1 on error.
+**
+** Side Effects:
+** returns twice, once in parent and once in child.
+*/
+
+pid_t
+dofork()
+{
+ register pid_t pid = -1;
+
+ DOFORK(fork);
+ return pid;
+}
+
+/*
+** COLONCMP -- compare host-signatures up to first ':' or EOS
+**
+** This takes two strings which happen to be host-signatures and
+** compares them. If the lowest preference portions of the MX-RR's
+** match (up to ':' or EOS, whichever is first), then we have
+** match. This is used for coattail-piggybacking messages during
+** message delivery.
+** If the signatures are the same up to the first ':' the remainder of
+** the signatures are then compared with a normal strcmp(). This saves
+** re-examining the first part of the signatures.
+**
+** Parameters:
+** a - first host-signature
+** b - second host-signature
+**
+** Returns:
+** HS_MATCH_NO -- no "match".
+** HS_MATCH_FIRST -- "match" for the first MX preference
+** (up to the first colon (':')).
+** HS_MATCH_FULL -- match for the entire MX record.
+**
+** Side Effects:
+** none.
+*/
+
+#define HS_MATCH_NO 0
+#define HS_MATCH_FIRST 1
+#define HS_MATCH_FULL 2
+
+static int
+coloncmp(a, b)
+ register const char *a;
+ register const char *b;
+{
+ int ret = HS_MATCH_NO;
+ int braclev = 0;
+
+ while (*a == *b++)
+ {
+ /* Need to account for IPv6 bracketed addresses */
+ if (*a == '[')
+ braclev++;
+ else if (*a == ']' && braclev > 0)
+ braclev--;
+ else if (*a == ':' && braclev <= 0)
+ {
+ ret = HS_MATCH_FIRST;
+ a++;
+ break;
+ }
+ else if (*a == '\0')
+ return HS_MATCH_FULL; /* a full match */
+ a++;
+ }
+ if (ret == HS_MATCH_NO &&
+ braclev <= 0 &&
+ ((*a == '\0' && *(b - 1) == ':') ||
+ (*a == ':' && *(b - 1) == '\0')))
+ return HS_MATCH_FIRST;
+ if (ret == HS_MATCH_FIRST && strcmp(a, b) == 0)
+ return HS_MATCH_FULL;
+
+ return ret;
+}
+
+/*
+** SHOULD_TRY_FBSH -- Should try FallbackSmartHost?
+**
+** Parameters:
+** e -- envelope
+** tried_fallbacksmarthost -- has been tried already? (in/out)
+** hostbuf -- buffer for hostname (expand FallbackSmartHost) (out)
+** hbsz -- size of hostbuf
+** status -- current delivery status
+**
+** Returns:
+** true iff FallbackSmartHost should be tried.
+*/
+
+static bool should_try_fbsh __P((ENVELOPE *, bool *, char *, size_t, int));
+
+static bool
+should_try_fbsh(e, tried_fallbacksmarthost, hostbuf, hbsz, status)
+ ENVELOPE *e;
+ bool *tried_fallbacksmarthost;
+ char *hostbuf;
+ size_t hbsz;
+ int status;
+{
+ /*
+ ** If the host was not found or a temporary failure occurred
+ ** and a FallbackSmartHost is defined (and we have not yet
+ ** tried it), then make one last try with it as the host.
+ */
+
+ if ((status == EX_NOHOST || status == EX_TEMPFAIL) &&
+ FallbackSmartHost != NULL && !*tried_fallbacksmarthost)
+ {
+ *tried_fallbacksmarthost = true;
+ expand(FallbackSmartHost, hostbuf, hbsz, e);
+ if (!wordinclass(hostbuf, 'w'))
+ {
+ if (tTd(11, 1))
+ sm_dprintf("one last try with FallbackSmartHost %s\n",
+ hostbuf);
+ return true;
+ }
+ }
+ return false;
+}
+/*
+** DELIVER -- Deliver a message to a list of addresses.
+**
+** This routine delivers to everyone on the same host as the
+** user on the head of the list. It is clever about mailers
+** that don't handle multiple users. It is NOT guaranteed
+** that it will deliver to all these addresses however -- so
+** deliver should be called once for each address on the
+** list.
+** Deliver tries to be as opportunistic as possible about piggybacking
+** messages. Some definitions to make understanding easier follow below.
+** Piggybacking occurs when an existing connection to a mail host can
+** be used to send the same message to more than one recipient at the
+** same time. So "no piggybacking" means one message for one recipient
+** per connection. "Intentional piggybacking" happens when the
+** recipients' host address (not the mail host address) is used to
+** attempt piggybacking. Recipients with the same host address
+** have the same mail host. "Coincidental piggybacking" relies on
+** piggybacking based on all the mail host addresses in the MX-RR. This
+** is "coincidental" in the fact it could not be predicted until the
+** MX Resource Records for the hosts were obtained and examined. For
+** example (preference order and equivalence is important, not values):
+** domain1 IN MX 10 mxhost-A
+** IN MX 20 mxhost-B
+** domain2 IN MX 4 mxhost-A
+** IN MX 8 mxhost-B
+** Domain1 and domain2 can piggyback the same message to mxhost-A or
+** mxhost-B (if mxhost-A cannot be reached).
+** "Coattail piggybacking" relaxes the strictness of "coincidental
+** piggybacking" in the hope that most significant (lowest value)
+** MX preference host(s) can create more piggybacking. For example
+** (again, preference order and equivalence is important, not values):
+** domain3 IN MX 100 mxhost-C
+** IN MX 100 mxhost-D
+** IN MX 200 mxhost-E
+** domain4 IN MX 50 mxhost-C
+** IN MX 50 mxhost-D
+** IN MX 80 mxhost-F
+** A message for domain3 and domain4 can piggyback to mxhost-C if mxhost-C
+** is available. Same with mxhost-D because in both RR's the preference
+** value is the same as mxhost-C, respectively.
+** So deliver attempts coattail piggybacking when possible. If the
+** first MX preference level hosts cannot be used then the piggybacking
+** reverts to coincidental piggybacking. Using the above example you
+** cannot deliver to mxhost-F for domain3 regardless of preference value.
+** ("Coattail" from "riding on the coattails of your predecessor" meaning
+** gaining benefit from a predecessor effort with no or little addition
+** effort. The predecessor here being the preceding MX RR).
+**
+** Parameters:
+** e -- the envelope to deliver.
+** firstto -- head of the address list to deliver to.
+**
+** Returns:
+** zero -- successfully delivered.
+** else -- some failure, see ExitStat for more info.
+**
+** Side Effects:
+** The standard input is passed off to someone.
+*/
+
+static int
+deliver(e, firstto)
+ register ENVELOPE *e;
+ ADDRESS *firstto;
+{
+ char *host; /* host being sent to */
+ char *user; /* user being sent to */
+ char **pvp;
+ register char **mvp;
+ register char *p;
+ register MAILER *m; /* mailer for this recipient */
+ ADDRESS *volatile ctladdr;
+#if HASSETUSERCONTEXT
+ ADDRESS *volatile contextaddr = NULL;
+#endif /* HASSETUSERCONTEXT */
+ register MCI *volatile mci;
+ register ADDRESS *SM_NONVOLATILE to = firstto;
+ volatile bool clever = false; /* running user smtp to this mailer */
+ ADDRESS *volatile tochain = NULL; /* users chain in this mailer call */
+ int rcode; /* response code */
+ SM_NONVOLATILE int lmtp_rcode = EX_OK;
+ SM_NONVOLATILE int nummxhosts = 0; /* number of MX hosts available */
+ SM_NONVOLATILE int hostnum = 0; /* current MX host index */
+ char *firstsig; /* signature of firstto */
+ volatile pid_t pid = -1;
+ char *volatile curhost;
+ SM_NONVOLATILE unsigned short port = 0;
+ SM_NONVOLATILE time_t enough = 0;
+#if NETUNIX
+ char *SM_NONVOLATILE mux_path = NULL; /* path to UNIX domain socket */
+#endif /* NETUNIX */
+ time_t xstart;
+ bool suidwarn;
+ bool anyok; /* at least one address was OK */
+ SM_NONVOLATILE bool goodmxfound = false; /* at least one MX was OK */
+ bool ovr;
+ bool quarantine;
+ int strsize;
+ int rcptcount;
+ int ret;
+ static int tobufsize = 0;
+ static char *tobuf = NULL;
+ char *rpath; /* translated return path */
+ int mpvect[2];
+ int rpvect[2];
+ char *mxhosts[MAXMXHOSTS + 1];
+ char *pv[MAXPV + 1];
+ char buf[MAXNAME + 1];
+ char cbuf[MAXPATHLEN];
+
+ errno = 0;
+ SM_REQUIRE(firstto != NULL); /* same as to */
+ if (!QS_IS_OK(to->q_state))
+ return 0;
+
+ suidwarn = geteuid() == 0;
+
+ SM_REQUIRE(e != NULL);
+ m = to->q_mailer;
+ host = to->q_host;
+ CurEnv = e; /* just in case */
+ e->e_statmsg = NULL;
+ SmtpError[0] = '\0';
+ xstart = curtime();
+
+ if (tTd(10, 1))
+ sm_dprintf("\n--deliver, id=%s, mailer=%s, host=`%s', first user=`%s'\n",
+ e->e_id, m->m_name, host, to->q_user);
+ if (tTd(10, 100))
+ printopenfds(false);
+
+ /*
+ ** Clear {client_*} macros if this is a bounce message to
+ ** prevent rejection by check_compat ruleset.
+ */
+
+ if (bitset(EF_RESPONSE, e->e_flags))
+ {
+ macdefine(&e->e_macro, A_PERM, macid("{client_name}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_ptr}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_addr}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_port}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_resolve}"), "");
+ }
+
+ SM_TRY
+ {
+ ADDRESS *skip_back = NULL;
+
+ /*
+ ** Do initial argv setup.
+ ** Insert the mailer name. Notice that $x expansion is
+ ** NOT done on the mailer name. Then, if the mailer has
+ ** a picky -f flag, we insert it as appropriate. This
+ ** code does not check for 'pv' overflow; this places a
+ ** manifest lower limit of 4 for MAXPV.
+ ** The from address rewrite is expected to make
+ ** the address relative to the other end.
+ */
+
+ /* rewrite from address, using rewriting rules */
+ rcode = EX_OK;
+ SM_ASSERT(e->e_from.q_mailer != NULL);
+ if (bitnset(M_UDBENVELOPE, e->e_from.q_mailer->m_flags))
+ p = e->e_sender;
+ else
+ p = e->e_from.q_paddr;
+ rpath = remotename(p, m, RF_SENDERADDR|RF_CANONICAL, &rcode, e);
+ if (strlen(rpath) > MAXNAME)
+ {
+ rpath = shortenstring(rpath, MAXSHORTSTR);
+
+ /* avoid bogus errno */
+ errno = 0;
+ syserr("remotename: huge return path %s", rpath);
+ }
+ rpath = sm_rpool_strdup_x(e->e_rpool, rpath);
+ macdefine(&e->e_macro, A_PERM, 'g', rpath);
+ macdefine(&e->e_macro, A_PERM, 'h', host);
+ Errors = 0;
+ pvp = pv;
+ *pvp++ = m->m_argv[0];
+
+ /* ignore long term host status information if mailer flag W is set */
+ if (bitnset(M_NOHOSTSTAT, m->m_flags))
+ IgnoreHostStatus = true;
+
+ /* insert -f or -r flag as appropriate */
+ if (FromFlag &&
+ (bitnset(M_FOPT, m->m_flags) ||
+ bitnset(M_ROPT, m->m_flags)))
+ {
+ if (bitnset(M_FOPT, m->m_flags))
+ *pvp++ = "-f";
+ else
+ *pvp++ = "-r";
+ *pvp++ = rpath;
+ }
+
+ /*
+ ** Append the other fixed parts of the argv. These run
+ ** up to the first entry containing "$u". There can only
+ ** be one of these, and there are only a few more slots
+ ** in the pv after it.
+ */
+
+ for (mvp = m->m_argv; (p = *++mvp) != NULL; )
+ {
+ /* can't use strchr here because of sign extension problems */
+ while (*p != '\0')
+ {
+ if ((*p++ & 0377) == MACROEXPAND)
+ {
+ if (*p == 'u')
+ break;
+ }
+ }
+
+ if (*p != '\0')
+ break;
+
+ /* this entry is safe -- go ahead and process it */
+ expand(*mvp, buf, sizeof(buf), e);
+ *pvp++ = sm_rpool_strdup_x(e->e_rpool, buf);
+ if (pvp >= &pv[MAXPV - 3])
+ {
+ syserr("554 5.3.5 Too many parameters to %s before $u",
+ pv[0]);
+ rcode = -1;
+ goto cleanup;
+ }
+ }
+
+ /*
+ ** If we have no substitution for the user name in the argument
+ ** list, we know that we must supply the names otherwise -- and
+ ** SMTP is the answer!!
+ */
+
+ if (*mvp == NULL)
+ {
+ /* running LMTP or SMTP */
+ clever = true;
+ *pvp = NULL;
+ }
+ else if (bitnset(M_LMTP, m->m_flags))
+ {
+ /* not running LMTP */
+ sm_syslog(LOG_ERR, NULL,
+ "Warning: mailer %s: LMTP flag (F=z) turned off",
+ m->m_name);
+ clrbitn(M_LMTP, m->m_flags);
+ }
+
+ /*
+ ** At this point *mvp points to the argument with $u. We
+ ** run through our address list and append all the addresses
+ ** we can. If we run out of space, do not fret! We can
+ ** always send another copy later.
+ */
+
+ e->e_to = NULL;
+ strsize = 2;
+ rcptcount = 0;
+ ctladdr = NULL;
+ if (firstto->q_signature == NULL)
+ firstto->q_signature = hostsignature(firstto->q_mailer,
+ firstto->q_host);
+ firstsig = firstto->q_signature;
+
+ for (; to != NULL; to = to->q_next)
+ {
+ /* avoid sending multiple recipients to dumb mailers */
+ if (tochain != NULL && !bitnset(M_MUSER, m->m_flags))
+ break;
+
+ /* if already sent or not for this host, don't send */
+ if (!QS_IS_OK(to->q_state)) /* already sent; look at next */
+ continue;
+
+ /*
+ ** Must be same mailer to keep grouping rcpts.
+ ** If mailers don't match: continue; sendqueue is not
+ ** sorted by mailers, so don't break;
+ */
+
+ if (to->q_mailer != firstto->q_mailer)
+ continue;
+
+ if (to->q_signature == NULL) /* for safety */
+ to->q_signature = hostsignature(to->q_mailer,
+ to->q_host);
+
+ /*
+ ** This is for coincidental and tailcoat piggybacking messages
+ ** to the same mail host. While the signatures are identical
+ ** (that's the MX-RR's are identical) we can do coincidental
+ ** piggybacking. We try hard for coattail piggybacking
+ ** with the same mail host when the next recipient has the
+ ** same host at lowest preference. It may be that this
+ ** won't work out, so 'skip_back' is maintained if a backup
+ ** to coincidental piggybacking or full signature must happen.
+ */
+
+ ret = firstto == to ? HS_MATCH_FULL :
+ coloncmp(to->q_signature, firstsig);
+ if (ret == HS_MATCH_FULL)
+ skip_back = to;
+ else if (ret == HS_MATCH_NO)
+ break;
+
+ if (!clever)
+ {
+ /* avoid overflowing tobuf */
+ strsize += strlen(to->q_paddr) + 1;
+ if (strsize > TOBUFSIZE)
+ break;
+ }
+
+ if (++rcptcount > to->q_mailer->m_maxrcpt)
+ break;
+
+ if (tTd(10, 1))
+ {
+ sm_dprintf("\nsend to ");
+ printaddr(sm_debug_file(), to, false);
+ }
+
+ /* compute effective uid/gid when sending */
+ if (bitnset(M_RUNASRCPT, to->q_mailer->m_flags))
+# if HASSETUSERCONTEXT
+ contextaddr = ctladdr = getctladdr(to);
+# else /* HASSETUSERCONTEXT */
+ ctladdr = getctladdr(to);
+# endif /* HASSETUSERCONTEXT */
+
+ if (tTd(10, 2))
+ {
+ sm_dprintf("ctladdr=");
+ printaddr(sm_debug_file(), ctladdr, false);
+ }
+
+ user = to->q_user;
+ e->e_to = to->q_paddr;
+
+ /*
+ ** Check to see that these people are allowed to
+ ** talk to each other.
+ ** Check also for overflow of e_msgsize.
+ */
+
+ if (m->m_maxsize != 0 &&
+ (e->e_msgsize > m->m_maxsize || e->e_msgsize < 0))
+ {
+ e->e_flags |= EF_NO_BODY_RETN;
+ if (bitnset(M_LOCALMAILER, to->q_mailer->m_flags))
+ to->q_status = "5.2.3";
+ else
+ to->q_status = "5.3.4";
+
+ /* set to->q_rstatus = NULL; or to the following? */
+ usrerrenh(to->q_status,
+ "552 Message is too large; %ld bytes max",
+ m->m_maxsize);
+ markfailure(e, to, NULL, EX_UNAVAILABLE, false);
+ giveresponse(EX_UNAVAILABLE, to->q_status, m,
+ NULL, ctladdr, xstart, e, to);
+ continue;
+ }
+ SM_SET_H_ERRNO(0);
+ ovr = true;
+
+ /* do config file checking of compatibility */
+ quarantine = (e->e_quarmsg != NULL);
+ rcode = rscheck("check_compat", e->e_from.q_paddr, to->q_paddr,
+ e, RSF_RMCOMM|RSF_COUNT, 3, NULL,
+ e->e_id, NULL);
+ if (rcode == EX_OK)
+ {
+ /* do in-code checking if not discarding */
+ if (!bitset(EF_DISCARD, e->e_flags))
+ {
+ rcode = checkcompat(to, e);
+ ovr = false;
+ }
+ }
+ if (rcode != EX_OK)
+ {
+ markfailure(e, to, NULL, rcode, ovr);
+ giveresponse(rcode, to->q_status, m,
+ NULL, ctladdr, xstart, e, to);
+ continue;
+ }
+ if (!quarantine && e->e_quarmsg != NULL)
+ {
+ /*
+ ** check_compat or checkcompat() has tried
+ ** to quarantine but that isn't supported.
+ ** Revert the attempt.
+ */
+
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), "");
+ }
+ if (bitset(EF_DISCARD, e->e_flags))
+ {
+ if (tTd(10, 5))
+ {
+ sm_dprintf("deliver: discarding recipient ");
+ printaddr(sm_debug_file(), to, false);
+ }
+
+ /* pretend the message was sent */
+ /* XXX should we log something here? */
+ to->q_state = QS_DISCARDED;
+
+ /*
+ ** Remove discard bit to prevent discard of
+ ** future recipients. This is safe because the
+ ** true "global discard" has been handled before
+ ** we get here.
+ */
+
+ e->e_flags &= ~EF_DISCARD;
+ continue;
+ }
+
+ /*
+ ** Strip quote bits from names if the mailer is dumb
+ ** about them.
+ */
+
+ if (bitnset(M_STRIPQ, m->m_flags))
+ {
+ stripquotes(user);
+ stripquotes(host);
+ }
+
+ /*
+ ** Strip all leading backslashes if requested and the
+ ** next character is alphanumerical (the latter can
+ ** probably relaxed a bit, see RFC2821).
+ */
+
+ if (bitnset(M_STRIPBACKSL, m->m_flags) && user[0] == '\\')
+ stripbackslash(user);
+
+ /* hack attack -- delivermail compatibility */
+ if (m == ProgMailer && *user == '|')
+ user++;
+
+ /*
+ ** If an error message has already been given, don't
+ ** bother to send to this address.
+ **
+ ** >>>>>>>>>> This clause assumes that the local mailer
+ ** >> NOTE >> cannot do any further aliasing; that
+ ** >>>>>>>>>> function is subsumed by sendmail.
+ */
+
+ if (!QS_IS_OK(to->q_state))
+ continue;
+
+ /*
+ ** See if this user name is "special".
+ ** If the user name has a slash in it, assume that this
+ ** is a file -- send it off without further ado. Note
+ ** that this type of addresses is not processed along
+ ** with the others, so we fudge on the To person.
+ */
+
+ if (strcmp(m->m_mailer, "[FILE]") == 0)
+ {
+ macdefine(&e->e_macro, A_PERM, 'u', user);
+ p = to->q_home;
+ if (p == NULL && ctladdr != NULL)
+ p = ctladdr->q_home;
+ macdefine(&e->e_macro, A_PERM, 'z', p);
+ expand(m->m_argv[1], buf, sizeof(buf), e);
+ if (strlen(buf) > 0)
+ rcode = mailfile(buf, m, ctladdr, SFF_CREAT, e);
+ else
+ {
+ syserr("empty filename specification for mailer %s",
+ m->m_name);
+ rcode = EX_CONFIG;
+ }
+ giveresponse(rcode, to->q_status, m, NULL,
+ ctladdr, xstart, e, to);
+ markfailure(e, to, NULL, rcode, true);
+ e->e_nsent++;
+ if (rcode == EX_OK)
+ {
+ to->q_state = QS_SENT;
+ if (bitnset(M_LOCALMAILER, m->m_flags) &&
+ bitset(QPINGONSUCCESS, to->q_flags))
+ {
+ to->q_flags |= QDELIVERED;
+ to->q_status = "2.1.5";
+ (void) sm_io_fprintf(e->e_xfp,
+ SM_TIME_DEFAULT,
+ "%s... Successfully delivered\n",
+ to->q_paddr);
+ }
+ }
+ to->q_statdate = curtime();
+ markstats(e, to, STATS_NORMAL);
+ continue;
+ }
+
+ /*
+ ** Address is verified -- add this user to mailer
+ ** argv, and add it to the print list of recipients.
+ */
+
+ /* link together the chain of recipients */
+ to->q_tchain = tochain;
+ tochain = to;
+ e->e_to = "[CHAIN]";
+
+ macdefine(&e->e_macro, A_PERM, 'u', user); /* to user */
+ p = to->q_home;
+ if (p == NULL && ctladdr != NULL)
+ p = ctladdr->q_home;
+ macdefine(&e->e_macro, A_PERM, 'z', p); /* user's home */
+
+ /* set the ${dsn_notify} macro if applicable */
+ if (bitset(QHASNOTIFY, to->q_flags))
+ {
+ char notify[MAXLINE];
+
+ notify[0] = '\0';
+ if (bitset(QPINGONSUCCESS, to->q_flags))
+ (void) sm_strlcat(notify, "SUCCESS,",
+ sizeof(notify));
+ if (bitset(QPINGONFAILURE, to->q_flags))
+ (void) sm_strlcat(notify, "FAILURE,",
+ sizeof(notify));
+ if (bitset(QPINGONDELAY, to->q_flags))
+ (void) sm_strlcat(notify, "DELAY,",
+ sizeof(notify));
+
+ /* Set to NEVER or drop trailing comma */
+ if (notify[0] == '\0')
+ (void) sm_strlcat(notify, "NEVER",
+ sizeof(notify));
+ else
+ notify[strlen(notify) - 1] = '\0';
+
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{dsn_notify}"), notify);
+ }
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_notify}"), NULL);
+
+ /*
+ ** Expand out this user into argument list.
+ */
+
+ if (!clever)
+ {
+ expand(*mvp, buf, sizeof(buf), e);
+ *pvp++ = sm_rpool_strdup_x(e->e_rpool, buf);
+ if (pvp >= &pv[MAXPV - 2])
+ {
+ /* allow some space for trailing parms */
+ break;
+ }
+ }
+ }
+
+ /* see if any addresses still exist */
+ if (tochain == NULL)
+ {
+ rcode = 0;
+ goto cleanup;
+ }
+
+ /* print out messages as full list */
+ strsize = 1;
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ strsize += strlen(to->q_paddr) + 1;
+ if (strsize < TOBUFSIZE)
+ strsize = TOBUFSIZE;
+ if (strsize > tobufsize)
+ {
+ SM_FREE_CLR(tobuf);
+ tobuf = sm_pmalloc_x(strsize);
+ tobufsize = strsize;
+ }
+ p = tobuf;
+ *p = '\0';
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ {
+ (void) sm_strlcpyn(p, tobufsize - (p - tobuf), 2,
+ ",", to->q_paddr);
+ p += strlen(p);
+ }
+ e->e_to = tobuf + 1;
+
+ /*
+ ** Fill out any parameters after the $u parameter.
+ */
+
+ if (!clever)
+ {
+ while (*++mvp != NULL)
+ {
+ expand(*mvp, buf, sizeof(buf), e);
+ *pvp++ = sm_rpool_strdup_x(e->e_rpool, buf);
+ if (pvp >= &pv[MAXPV])
+ syserr("554 5.3.0 deliver: pv overflow after $u for %s",
+ pv[0]);
+ }
+ }
+ *pvp++ = NULL;
+
+ /*
+ ** Call the mailer.
+ ** The argument vector gets built, pipes
+ ** are created as necessary, and we fork & exec as
+ ** appropriate.
+ ** If we are running SMTP, we just need to clean up.
+ */
+
+ /* XXX this seems a bit weird */
+ if (ctladdr == NULL && m != ProgMailer && m != FileMailer &&
+ bitset(QGOODUID, e->e_from.q_flags))
+ ctladdr = &e->e_from;
+
+#if NAMED_BIND
+ if (ConfigLevel < 2)
+ _res.options &= ~(RES_DEFNAMES | RES_DNSRCH); /* XXX */
+#endif /* NAMED_BIND */
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("openmailer:");
+ printav(sm_debug_file(), pv);
+ }
+ errno = 0;
+ SM_SET_H_ERRNO(0);
+ CurHostName = NULL;
+
+ /*
+ ** Deal with the special case of mail handled through an IPC
+ ** connection.
+ ** In this case we don't actually fork. We must be
+ ** running SMTP for this to work. We will return a
+ ** zero pid to indicate that we are running IPC.
+ ** We also handle a debug version that just talks to stdin/out.
+ */
+
+ curhost = NULL;
+ SmtpPhase = NULL;
+ mci = NULL;
+
+#if XDEBUG
+ {
+ char wbuf[MAXLINE];
+
+ /* make absolutely certain 0, 1, and 2 are in use */
+ (void) sm_snprintf(wbuf, sizeof(wbuf), "%s... openmailer(%s)",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name);
+ checkfd012(wbuf);
+ }
+#endif /* XDEBUG */
+
+ /* check for 8-bit available */
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ bitnset(M_7BITS, m->m_flags) &&
+ (bitset(EF_DONT_MIME, e->e_flags) ||
+ !(bitset(MM_MIME8BIT, MimeMode) ||
+ (bitset(EF_IS_MIME, e->e_flags) &&
+ bitset(MM_CVTMIME, MimeMode)))))
+ {
+ e->e_status = "5.6.3";
+ usrerrenh(e->e_status,
+ "554 Cannot send 8-bit data to 7-bit destination");
+ rcode = EX_DATAERR;
+ goto give_up;
+ }
+
+ if (tTd(62, 8))
+ checkfds("before delivery");
+
+ /* check for Local Person Communication -- not for mortals!!! */
+ if (strcmp(m->m_mailer, "[LPC]") == 0)
+ {
+ if (clever)
+ {
+ /* flush any expired connections */
+ (void) mci_scan(NULL);
+
+ /* try to get a cached connection or just a slot */
+ mci = mci_get(m->m_name, m);
+ if (mci->mci_host == NULL)
+ mci->mci_host = m->m_name;
+ CurHostName = mci->mci_host;
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ message("Using cached SMTP/LPC connection for %s...",
+ m->m_name);
+ mci->mci_deliveries++;
+ goto do_transfer;
+ }
+ }
+ else
+ {
+ mci = mci_new(e->e_rpool);
+ }
+ mci->mci_in = smioin;
+ mci->mci_out = smioout;
+ mci->mci_mailer = m;
+ mci->mci_host = m->m_name;
+ if (clever)
+ {
+ mci->mci_state = MCIS_OPENING;
+ mci_cache(mci);
+ }
+ else
+ mci->mci_state = MCIS_OPEN;
+ }
+ else if (strcmp(m->m_mailer, "[IPC]") == 0)
+ {
+ register int i;
+
+ if (pv[0] == NULL || pv[1] == NULL || pv[1][0] == '\0')
+ {
+ syserr("null destination for %s mailer", m->m_mailer);
+ rcode = EX_CONFIG;
+ goto give_up;
+ }
+
+# if NETUNIX
+ if (strcmp(pv[0], "FILE") == 0)
+ {
+ curhost = CurHostName = "localhost";
+ mux_path = pv[1];
+ }
+ else
+# endif /* NETUNIX */
+ {
+ CurHostName = pv[1];
+ curhost = hostsignature(m, pv[1]);
+ }
+
+ if (curhost == NULL || curhost[0] == '\0')
+ {
+ syserr("null host signature for %s", pv[1]);
+ rcode = EX_CONFIG;
+ goto give_up;
+ }
+
+ if (!clever)
+ {
+ syserr("554 5.3.5 non-clever IPC");
+ rcode = EX_CONFIG;
+ goto give_up;
+ }
+ if (pv[2] != NULL
+# if NETUNIX
+ && mux_path == NULL
+# endif /* NETUNIX */
+ )
+ {
+ port = htons((unsigned short) atoi(pv[2]));
+ if (port == 0)
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("Invalid port number: %s", pv[2]);
+# else /* NO_GETSERVBYNAME */
+ struct servent *sp = getservbyname(pv[2], "tcp");
+
+ if (sp == NULL)
+ syserr("Service %s unknown", pv[2]);
+ else
+ port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ }
+
+ nummxhosts = parse_hostsignature(curhost, mxhosts, m);
+ if (TimeOuts.to_aconnect > 0)
+ enough = curtime() + TimeOuts.to_aconnect;
+tryhost:
+ while (hostnum < nummxhosts)
+ {
+ char sep = ':';
+ char *endp;
+ static char hostbuf[MAXNAME + 1];
+ bool tried_fallbacksmarthost = false;
+
+# if NETINET6
+ if (*mxhosts[hostnum] == '[')
+ {
+ endp = strchr(mxhosts[hostnum] + 1, ']');
+ if (endp != NULL)
+ endp = strpbrk(endp + 1, ":,");
+ }
+ else
+ endp = strpbrk(mxhosts[hostnum], ":,");
+# else /* NETINET6 */
+ endp = strpbrk(mxhosts[hostnum], ":,");
+# endif /* NETINET6 */
+ if (endp != NULL)
+ {
+ sep = *endp;
+ *endp = '\0';
+ }
+
+ if (hostnum == 1 && skip_back != NULL)
+ {
+ /*
+ ** Coattail piggybacking is no longer an
+ ** option with the mail host next to be tried
+ ** no longer the lowest MX preference
+ ** (hostnum == 1 meaning we're on the second
+ ** preference). We do not try to coattail
+ ** piggyback more than the first MX preference.
+ ** Revert 'tochain' to last location for
+ ** coincidental piggybacking. This works this
+ ** easily because the q_tchain kept getting
+ ** added to the top of the linked list.
+ */
+
+ tochain = skip_back;
+ }
+
+ if (*mxhosts[hostnum] == '\0')
+ {
+ syserr("deliver: null host name in signature");
+ hostnum++;
+ if (endp != NULL)
+ *endp = sep;
+ continue;
+ }
+ (void) sm_strlcpy(hostbuf, mxhosts[hostnum],
+ sizeof(hostbuf));
+ hostnum++;
+ if (endp != NULL)
+ *endp = sep;
+
+ one_last_try:
+ /* see if we already know that this host is fried */
+ CurHostName = hostbuf;
+ mci = mci_get(hostbuf, m);
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ char *type;
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("openmailer: ");
+ mci_dump(sm_debug_file(), mci, false);
+ }
+ CurHostName = mci->mci_host;
+ if (bitnset(M_LMTP, m->m_flags))
+ type = "L";
+ else if (bitset(MCIF_ESMTP, mci->mci_flags))
+ type = "ES";
+ else
+ type = "S";
+ message("Using cached %sMTP connection to %s via %s...",
+ type, hostbuf, m->m_name);
+ mci->mci_deliveries++;
+ break;
+ }
+ mci->mci_mailer = m;
+ if (mci->mci_exitstat != EX_OK)
+ {
+ if (mci->mci_exitstat == EX_TEMPFAIL)
+ goodmxfound = true;
+
+ /* Try FallbackSmartHost? */
+ if (should_try_fbsh(e, &tried_fallbacksmarthost,
+ hostbuf, sizeof(hostbuf),
+ mci->mci_exitstat))
+ goto one_last_try;
+
+ continue;
+ }
+
+ if (mci_lock_host(mci) != EX_OK)
+ {
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+ goodmxfound = true;
+ continue;
+ }
+
+ /* try the connection */
+ sm_setproctitle(true, e, "%s %s: %s",
+ qid_printname(e),
+ hostbuf, "user open");
+# if NETUNIX
+ if (mux_path != NULL)
+ {
+ message("Connecting to %s via %s...",
+ mux_path, m->m_name);
+ i = makeconnection_ds((char *) mux_path, mci);
+ }
+ else
+# endif /* NETUNIX */
+ {
+ if (port == 0)
+ message("Connecting to %s via %s...",
+ hostbuf, m->m_name);
+ else
+ message("Connecting to %s port %d via %s...",
+ hostbuf, ntohs(port),
+ m->m_name);
+ i = makeconnection(hostbuf, port, mci, e,
+ enough);
+ }
+ mci->mci_errno = errno;
+ mci->mci_lastuse = curtime();
+ mci->mci_deliveries = 0;
+ mci->mci_exitstat = i;
+ mci_clr_extensions(mci);
+# if NAMED_BIND
+ mci->mci_herrno = h_errno;
+# endif /* NAMED_BIND */
+
+ /*
+ ** Have we tried long enough to get a connection?
+ ** If yes, skip to the fallback MX hosts
+ ** (if existent).
+ */
+
+ if (enough > 0 && mci->mci_lastuse >= enough)
+ {
+ int h;
+# if NAMED_BIND
+ extern int NumFallbackMXHosts;
+# else /* NAMED_BIND */
+ const int NumFallbackMXHosts = 0;
+# endif /* NAMED_BIND */
+
+ if (hostnum < nummxhosts && LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Timeout.to_aconnect occurred before exhausting all addresses");
+
+ /* turn off timeout if fallback available */
+ if (NumFallbackMXHosts > 0)
+ enough = 0;
+
+ /* skip to a fallback MX host */
+ h = nummxhosts - NumFallbackMXHosts;
+ if (hostnum < h)
+ hostnum = h;
+ }
+ if (i == EX_OK)
+ {
+ goodmxfound = true;
+ markstats(e, firstto, STATS_CONNECT);
+ mci->mci_state = MCIS_OPENING;
+ mci_cache(mci);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d === CONNECT %s\n",
+ (int) CurrentPid,
+ hostbuf);
+ break;
+ }
+ else
+ {
+ /* Try FallbackSmartHost? */
+ if (should_try_fbsh(e, &tried_fallbacksmarthost,
+ hostbuf, sizeof(hostbuf), i))
+ goto one_last_try;
+
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: makeconnection => stat=%d, errno=%d\n",
+ i, errno);
+ if (i == EX_TEMPFAIL)
+ goodmxfound = true;
+ mci_unlock_host(mci);
+ }
+
+ /* enter status of this host */
+ setstat(i);
+
+ /* should print some message here for -v mode */
+ }
+ if (mci == NULL)
+ {
+ syserr("deliver: no host name");
+ rcode = EX_SOFTWARE;
+ goto give_up;
+ }
+ mci->mci_pid = 0;
+ }
+ else
+ {
+ /* flush any expired connections */
+ (void) mci_scan(NULL);
+ mci = NULL;
+
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ /* try to get a cached connection */
+ mci = mci_get(m->m_name, m);
+ if (mci->mci_host == NULL)
+ mci->mci_host = m->m_name;
+ CurHostName = mci->mci_host;
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ message("Using cached LMTP connection for %s...",
+ m->m_name);
+ mci->mci_deliveries++;
+ goto do_transfer;
+ }
+ }
+
+ /* announce the connection to verbose listeners */
+ if (host == NULL || host[0] == '\0')
+ message("Connecting to %s...", m->m_name);
+ else
+ message("Connecting to %s via %s...", host, m->m_name);
+ if (TrafficLogFile != NULL)
+ {
+ char **av;
+
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d === EXEC", (int) CurrentPid);
+ for (av = pv; *av != NULL; av++)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT, " %s",
+ *av);
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "\n");
+ }
+
+#if XDEBUG
+ checkfd012("before creating mail pipe");
+#endif /* XDEBUG */
+
+ /* create a pipe to shove the mail through */
+ if (pipe(mpvect) < 0)
+ {
+ syserr("%s... openmailer(%s): pipe (to mailer)",
+ shortenstring(e->e_to, MAXSHORTSTR), m->m_name);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+
+#if XDEBUG
+ /* make sure we didn't get one of the standard I/O files */
+ if (mpvect[0] < 3 || mpvect[1] < 3)
+ {
+ syserr("%s... openmailer(%s): bogus mpvect %d %d",
+ shortenstring(e->e_to, MAXSHORTSTR), m->m_name,
+ mpvect[0], mpvect[1]);
+ printopenfds(true);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+
+ /* make sure system call isn't dead meat */
+ checkfdopen(mpvect[0], "mpvect[0]");
+ checkfdopen(mpvect[1], "mpvect[1]");
+ if (mpvect[0] == mpvect[1] ||
+ (e->e_lockfp != NULL &&
+ (mpvect[0] == sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD,
+ NULL) ||
+ mpvect[1] == sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD,
+ NULL))))
+ {
+ if (e->e_lockfp == NULL)
+ syserr("%s... openmailer(%s): overlapping mpvect %d %d",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, mpvect[0], mpvect[1]);
+ else
+ syserr("%s... openmailer(%s): overlapping mpvect %d %d, lockfp = %d",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, mpvect[0], mpvect[1],
+ sm_io_getinfo(e->e_lockfp,
+ SM_IO_WHAT_FD, NULL));
+ }
+#endif /* XDEBUG */
+
+ /* create a return pipe */
+ if (pipe(rpvect) < 0)
+ {
+ syserr("%s... openmailer(%s): pipe (from mailer)",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name);
+ (void) close(mpvect[0]);
+ (void) close(mpvect[1]);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+#if XDEBUG
+ checkfdopen(rpvect[0], "rpvect[0]");
+ checkfdopen(rpvect[1], "rpvect[1]");
+#endif /* XDEBUG */
+
+ /*
+ ** Actually fork the mailer process.
+ ** DOFORK is clever about retrying.
+ **
+ ** Dispose of SIGCHLD signal catchers that may be laying
+ ** around so that endmailer will get it.
+ */
+
+ if (e->e_xfp != NULL) /* for debugging */
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+
+
+ DOFORK(FORK);
+ /* pid is set by DOFORK */
+
+ if (pid < 0)
+ {
+ /* failure */
+ syserr("%s... openmailer(%s): cannot fork",
+ shortenstring(e->e_to, MAXSHORTSTR), m->m_name);
+ (void) close(mpvect[0]);
+ (void) close(mpvect[1]);
+ (void) close(rpvect[0]);
+ (void) close(rpvect[1]);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+ else if (pid == 0)
+ {
+ int save_errno;
+ int sff;
+ int new_euid = NO_UID;
+ int new_ruid = NO_UID;
+ int new_gid = NO_GID;
+ char *user = NULL;
+ struct stat stb;
+ extern int DtableSize;
+
+ CurrentPid = getpid();
+
+ /* clear the events to turn off SIGALRMs */
+ sm_clear_events();
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ if (e->e_lockfp != NULL)
+ (void) close(sm_io_getinfo(e->e_lockfp,
+ SM_IO_WHAT_FD,
+ NULL));
+
+ /* child -- set up input & exec mailer */
+ (void) sm_signal(SIGALRM, sm_signal_noop);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_IGN);
+ (void) sm_signal(SIGINT, SIG_IGN);
+ (void) sm_signal(SIGTERM, SIG_DFL);
+# ifdef SIGUSR1
+ (void) sm_signal(SIGUSR1, sm_signal_noop);
+# endif /* SIGUSR1 */
+
+ if (m != FileMailer || stat(tochain->q_user, &stb) < 0)
+ stb.st_mode = 0;
+
+# if HASSETUSERCONTEXT
+ /*
+ ** Set user resources.
+ */
+
+ if (contextaddr != NULL)
+ {
+ int sucflags;
+ struct passwd *pwd;
+
+ if (contextaddr->q_ruser != NULL)
+ pwd = sm_getpwnam(contextaddr->q_ruser);
+ else
+ pwd = sm_getpwnam(contextaddr->q_user);
+ sucflags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
+#ifdef LOGIN_SETMAC
+ sucflags |= LOGIN_SETMAC;
+#endif /* LOGIN_SETMAC */
+ if (pwd != NULL &&
+ setusercontext(NULL, pwd, pwd->pw_uid,
+ sucflags) == -1 &&
+ suidwarn)
+ {
+ syserr("openmailer: setusercontext() failed");
+ exit(EX_TEMPFAIL);
+ }
+ }
+# endif /* HASSETUSERCONTEXT */
+
+#if HASNICE
+ /* tweak niceness */
+ if (m->m_nice != 0)
+ (void) nice(m->m_nice);
+#endif /* HASNICE */
+
+ /* reset group id */
+ if (bitnset(M_SPECIFIC_UID, m->m_flags))
+ {
+ if (m->m_gid == NO_GID)
+ new_gid = RunAsGid;
+ else
+ new_gid = m->m_gid;
+ }
+ else if (bitset(S_ISGID, stb.st_mode))
+ new_gid = stb.st_gid;
+ else if (ctladdr != NULL && ctladdr->q_gid != 0)
+ {
+ if (!DontInitGroups)
+ {
+ user = ctladdr->q_ruser;
+ if (user == NULL)
+ user = ctladdr->q_user;
+
+ if (initgroups(user,
+ ctladdr->q_gid) == -1
+ && suidwarn)
+ {
+ syserr("openmailer: initgroups(%s, %d) failed",
+ user, ctladdr->q_gid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = ctladdr->q_gid;
+ if (setgroups(1, gidset) == -1
+ && suidwarn)
+ {
+ syserr("openmailer: setgroups() failed");
+ exit(EX_TEMPFAIL);
+ }
+ }
+ new_gid = ctladdr->q_gid;
+ }
+ else
+ {
+ if (!DontInitGroups)
+ {
+ user = DefUser;
+ if (initgroups(DefUser, DefGid) == -1 &&
+ suidwarn)
+ {
+ syserr("openmailer: initgroups(%s, %d) failed",
+ DefUser, DefGid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = DefGid;
+ if (setgroups(1, gidset) == -1
+ && suidwarn)
+ {
+ syserr("openmailer: setgroups() failed");
+ exit(EX_TEMPFAIL);
+ }
+ }
+ if (m->m_gid == NO_GID)
+ new_gid = DefGid;
+ else
+ new_gid = m->m_gid;
+ }
+ if (new_gid != NO_GID)
+ {
+ if (RunAsUid != 0 &&
+ bitnset(M_SPECIFIC_UID, m->m_flags) &&
+ new_gid != getgid() &&
+ new_gid != getegid())
+ {
+ /* Only root can change the gid */
+ syserr("openmailer: insufficient privileges to change gid, RunAsUid=%d, new_gid=%d, gid=%d, egid=%d",
+ (int) RunAsUid, (int) new_gid,
+ (int) getgid(), (int) getegid());
+ exit(EX_TEMPFAIL);
+ }
+
+ if (setgid(new_gid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setgid(%ld) failed",
+ (long) new_gid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ /* change root to some "safe" directory */
+ if (m->m_rootdir != NULL)
+ {
+ expand(m->m_rootdir, cbuf, sizeof(cbuf), e);
+ if (tTd(11, 20))
+ sm_dprintf("openmailer: chroot %s\n",
+ cbuf);
+ if (chroot(cbuf) < 0)
+ {
+ syserr("openmailer: Cannot chroot(%s)",
+ cbuf);
+ exit(EX_TEMPFAIL);
+ }
+ if (chdir("/") < 0)
+ {
+ syserr("openmailer: cannot chdir(/)");
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ /* reset user id */
+ endpwent();
+ sm_mbdb_terminate();
+ if (bitnset(M_SPECIFIC_UID, m->m_flags))
+ {
+ if (m->m_uid == NO_UID)
+ new_euid = RunAsUid;
+ else
+ new_euid = m->m_uid;
+
+ /*
+ ** Undo the effects of the uid change in main
+ ** for signal handling. The real uid may
+ ** be used by mailer in adding a "From "
+ ** line.
+ */
+
+ if (RealUid != 0 && RealUid != getuid())
+ {
+# if MAILER_SETUID_METHOD == USE_SETEUID
+# if HASSETREUID
+ if (setreuid(RealUid, geteuid()) < 0)
+ {
+ syserr("openmailer: setreuid(%d, %d) failed",
+ (int) RealUid, (int) geteuid());
+ exit(EX_OSERR);
+ }
+# endif /* HASSETREUID */
+# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
+# if MAILER_SETUID_METHOD == USE_SETREUID
+ new_ruid = RealUid;
+# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
+ }
+ }
+ else if (bitset(S_ISUID, stb.st_mode))
+ new_ruid = stb.st_uid;
+ else if (ctladdr != NULL && ctladdr->q_uid != 0)
+ new_ruid = ctladdr->q_uid;
+ else if (m->m_uid != NO_UID)
+ new_ruid = m->m_uid;
+ else
+ new_ruid = DefUid;
+
+# if _FFR_USE_SETLOGIN
+ /* run disconnected from terminal and set login name */
+ if (setsid() >= 0 &&
+ ctladdr != NULL && ctladdr->q_uid != 0 &&
+ new_euid == ctladdr->q_uid)
+ {
+ struct passwd *pwd;
+
+ pwd = sm_getpwuid(ctladdr->q_uid);
+ if (pwd != NULL && suidwarn)
+ (void) setlogin(pwd->pw_name);
+ endpwent();
+ }
+# endif /* _FFR_USE_SETLOGIN */
+
+ if (new_euid != NO_UID)
+ {
+ if (RunAsUid != 0 && new_euid != RunAsUid)
+ {
+ /* Only root can change the uid */
+ syserr("openmailer: insufficient privileges to change uid, new_euid=%d, RunAsUid=%d",
+ (int) new_euid, (int) RunAsUid);
+ exit(EX_TEMPFAIL);
+ }
+
+ vendor_set_uid(new_euid);
+# if MAILER_SETUID_METHOD == USE_SETEUID
+ if (seteuid(new_euid) < 0 && suidwarn)
+ {
+ syserr("openmailer: seteuid(%ld) failed",
+ (long) new_euid);
+ exit(EX_TEMPFAIL);
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
+# if MAILER_SETUID_METHOD == USE_SETREUID
+ if (setreuid(new_ruid, new_euid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setreuid(%ld, %ld) failed",
+ (long) new_ruid, (long) new_euid);
+ exit(EX_TEMPFAIL);
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
+# if MAILER_SETUID_METHOD == USE_SETUID
+ if (new_euid != geteuid() && setuid(new_euid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setuid(%ld) failed",
+ (long) new_euid);
+ exit(EX_TEMPFAIL);
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETUID */
+ }
+ else if (new_ruid != NO_UID)
+ {
+ vendor_set_uid(new_ruid);
+ if (setuid(new_ruid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setuid(%ld) failed",
+ (long) new_ruid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ if (tTd(11, 2))
+ sm_dprintf("openmailer: running as r/euid=%d/%d, r/egid=%d/%d\n",
+ (int) getuid(), (int) geteuid(),
+ (int) getgid(), (int) getegid());
+
+ /* move into some "safe" directory */
+ if (m->m_execdir != NULL)
+ {
+ char *q;
+
+ for (p = m->m_execdir; p != NULL; p = q)
+ {
+ q = strchr(p, ':');
+ if (q != NULL)
+ *q = '\0';
+ expand(p, cbuf, sizeof(cbuf), e);
+ if (q != NULL)
+ *q++ = ':';
+ if (tTd(11, 20))
+ sm_dprintf("openmailer: trydir %s\n",
+ cbuf);
+ if (cbuf[0] != '\0' &&
+ chdir(cbuf) >= 0)
+ break;
+ }
+ }
+
+ /* Check safety of program to be run */
+ sff = SFF_ROOTOK|SFF_EXECOK;
+ if (!bitnset(DBS_RUNWRITABLEPROGRAM,
+ DontBlameSendmail))
+ sff |= SFF_NOGWFILES|SFF_NOWWFILES;
+ if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH,
+ DontBlameSendmail))
+ sff |= SFF_NOPATHCHECK;
+ else
+ sff |= SFF_SAFEDIRPATH;
+ ret = safefile(m->m_mailer, getuid(), getgid(),
+ user, sff, 0, NULL);
+ if (ret != 0)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Warning: program %s unsafe: %s",
+ m->m_mailer, sm_errstring(ret));
+
+ /* arrange to filter std & diag output of command */
+ (void) close(rpvect[0]);
+ if (dup2(rpvect[1], STDOUT_FILENO) < 0)
+ {
+ syserr("%s... openmailer(%s): cannot dup pipe %d for stdout",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, rpvect[1]);
+ _exit(EX_OSERR);
+ }
+ (void) close(rpvect[1]);
+
+ if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
+ {
+ syserr("%s... openmailer(%s): cannot dup stdout for stderr",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name);
+ _exit(EX_OSERR);
+ }
+
+ /* arrange to get standard input */
+ (void) close(mpvect[1]);
+ if (dup2(mpvect[0], STDIN_FILENO) < 0)
+ {
+ syserr("%s... openmailer(%s): cannot dup pipe %d for stdin",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, mpvect[0]);
+ _exit(EX_OSERR);
+ }
+ (void) close(mpvect[0]);
+
+ /* arrange for all the files to be closed */
+ sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
+
+# if !_FFR_USE_SETLOGIN
+ /* run disconnected from terminal */
+ (void) setsid();
+# endif /* !_FFR_USE_SETLOGIN */
+
+ /* try to execute the mailer */
+ (void) execve(m->m_mailer, (ARGV_T) pv,
+ (ARGV_T) UserEnviron);
+ save_errno = errno;
+ syserr("Cannot exec %s", m->m_mailer);
+ if (bitnset(M_LOCALMAILER, m->m_flags) ||
+ transienterror(save_errno))
+ _exit(EX_OSERR);
+ _exit(EX_UNAVAILABLE);
+ }
+
+ /*
+ ** Set up return value.
+ */
+
+ if (mci == NULL)
+ {
+ if (clever)
+ {
+ /*
+ ** Allocate from general heap, not
+ ** envelope rpool, because this mci
+ ** is going to be cached.
+ */
+
+ mci = mci_new(NULL);
+ }
+ else
+ {
+ /*
+ ** Prevent a storage leak by allocating
+ ** this from the envelope rpool.
+ */
+
+ mci = mci_new(e->e_rpool);
+ }
+ }
+ mci->mci_mailer = m;
+ if (clever)
+ {
+ mci->mci_state = MCIS_OPENING;
+ mci_cache(mci);
+ }
+ else
+ {
+ mci->mci_state = MCIS_OPEN;
+ }
+ mci->mci_pid = pid;
+ (void) close(mpvect[0]);
+ mci->mci_out = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &(mpvect[1]), SM_IO_WRONLY_B,
+ NULL);
+ if (mci->mci_out == NULL)
+ {
+ syserr("deliver: cannot create mailer output channel, fd=%d",
+ mpvect[1]);
+ (void) close(mpvect[1]);
+ (void) close(rpvect[0]);
+ (void) close(rpvect[1]);
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+
+ (void) close(rpvect[1]);
+ mci->mci_in = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &(rpvect[0]), SM_IO_RDONLY_B,
+ NULL);
+ if (mci->mci_in == NULL)
+ {
+ syserr("deliver: cannot create mailer input channel, fd=%d",
+ mpvect[1]);
+ (void) close(rpvect[0]);
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ mci->mci_out = NULL;
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+ }
+
+ /*
+ ** If we are in SMTP opening state, send initial protocol.
+ */
+
+ if (bitnset(M_7BITS, m->m_flags) &&
+ (!clever || mci->mci_state == MCIS_OPENING))
+ mci->mci_flags |= MCIF_7BIT;
+ if (clever && mci->mci_state != MCIS_CLOSED)
+ {
+# if STARTTLS || SASL
+ int dotpos;
+ char *srvname;
+ extern SOCKADDR CurHostAddr;
+# endif /* STARTTLS || SASL */
+
+# if SASL
+# define DONE_AUTH(f) bitset(MCIF_AUTHACT, f)
+# endif /* SASL */
+# if STARTTLS
+# define DONE_STARTTLS(f) bitset(MCIF_TLSACT, f)
+# endif /* STARTTLS */
+# define ONLY_HELO(f) bitset(MCIF_ONLY_EHLO, f)
+# define SET_HELO(f) f |= MCIF_ONLY_EHLO
+# define CLR_HELO(f) f &= ~MCIF_ONLY_EHLO
+
+# if STARTTLS || SASL
+ /* don't use CurHostName, it is changed in many places */
+ if (mci->mci_host != NULL)
+ {
+ srvname = mci->mci_host;
+ dotpos = strlen(srvname) - 1;
+ if (dotpos >= 0)
+ {
+ if (srvname[dotpos] == '.')
+ srvname[dotpos] = '\0';
+ else
+ dotpos = -1;
+ }
+ }
+ else if (mci->mci_mailer != NULL)
+ {
+ srvname = mci->mci_mailer->m_name;
+ dotpos = -1;
+ }
+ else
+ {
+ srvname = "local";
+ dotpos = -1;
+ }
+
+ /* don't set {server_name} to NULL or "": see getauth() */
+ macdefine(&mci->mci_macro, A_TEMP, macid("{server_name}"),
+ srvname);
+
+ /* CurHostAddr is set by makeconnection() and mci_get() */
+ if (CurHostAddr.sa.sa_family != 0)
+ {
+ macdefine(&mci->mci_macro, A_TEMP,
+ macid("{server_addr}"),
+ anynet_ntoa(&CurHostAddr));
+ }
+ else if (mci->mci_mailer != NULL)
+ {
+ /* mailer name is unique, use it as address */
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{server_addr}"),
+ mci->mci_mailer->m_name);
+ }
+ else
+ {
+ /* don't set it to NULL or "": see getauth() */
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{server_addr}"), "0");
+ }
+
+ /* undo change of srvname (mci->mci_host) */
+ if (dotpos >= 0)
+ srvname[dotpos] = '.';
+
+reconnect: /* after switching to an encrypted connection */
+# endif /* STARTTLS || SASL */
+
+ /* set the current connection information */
+ e->e_mci = mci;
+# if SASL
+ mci->mci_saslcap = NULL;
+# endif /* SASL */
+ smtpinit(m, mci, e, ONLY_HELO(mci->mci_flags));
+ CLR_HELO(mci->mci_flags);
+
+ if (IS_DLVR_RETURN(e))
+ {
+ /*
+ ** Check whether other side can deliver e-mail
+ ** fast enough
+ */
+
+ if (!bitset(MCIF_DLVR_BY, mci->mci_flags))
+ {
+ e->e_status = "5.4.7";
+ usrerrenh(e->e_status,
+ "554 Server does not support Deliver By");
+ rcode = EX_UNAVAILABLE;
+ goto give_up;
+ }
+ if (e->e_deliver_by > 0 &&
+ e->e_deliver_by - (curtime() - e->e_ctime) <
+ mci->mci_min_by)
+ {
+ e->e_status = "5.4.7";
+ usrerrenh(e->e_status,
+ "554 Message can't be delivered in time; %ld < %ld",
+ e->e_deliver_by - (curtime() - e->e_ctime),
+ mci->mci_min_by);
+ rcode = EX_UNAVAILABLE;
+ goto give_up;
+ }
+ }
+
+# if STARTTLS
+ /* first TLS then AUTH to provide a security layer */
+ if (mci->mci_state != MCIS_CLOSED &&
+ !DONE_STARTTLS(mci->mci_flags))
+ {
+ int olderrors;
+ bool usetls;
+ bool saveQuickAbort = QuickAbort;
+ bool saveSuprErrs = SuprErrs;
+ char *host = NULL;
+
+ rcode = EX_OK;
+ usetls = bitset(MCIF_TLS, mci->mci_flags);
+ if (usetls)
+ usetls = !iscltflgset(e, D_NOTLS);
+
+ host = macvalue(macid("{server_name}"), e);
+ if (usetls)
+ {
+ olderrors = Errors;
+ QuickAbort = false;
+ SuprErrs = true;
+ if (rscheck("try_tls", host, NULL, e,
+ RSF_RMCOMM, 7, host, NOQID, NULL)
+ != EX_OK
+ || Errors > olderrors)
+ {
+ usetls = false;
+ }
+ SuprErrs = saveSuprErrs;
+ QuickAbort = saveQuickAbort;
+ }
+
+ if (usetls)
+ {
+ if ((rcode = starttls(m, mci, e)) == EX_OK)
+ {
+ /* start again without STARTTLS */
+ mci->mci_flags |= MCIF_TLSACT;
+ }
+ else
+ {
+ char *s;
+
+ /*
+ ** TLS negotiation failed, what to do?
+ ** fall back to unencrypted connection
+ ** or abort? How to decide?
+ ** set a macro and call a ruleset.
+ */
+
+ mci->mci_flags &= ~MCIF_TLS;
+ switch (rcode)
+ {
+ case EX_TEMPFAIL:
+ s = "TEMP";
+ break;
+ case EX_USAGE:
+ s = "USAGE";
+ break;
+ case EX_PROTOCOL:
+ s = "PROTOCOL";
+ break;
+ case EX_SOFTWARE:
+ s = "SOFTWARE";
+ break;
+ case EX_UNAVAILABLE:
+ s = "NONE";
+ break;
+
+ /* everything else is a failure */
+ default:
+ s = "FAILURE";
+ rcode = EX_TEMPFAIL;
+ }
+ macdefine(&e->e_macro, A_PERM,
+ macid("{verify}"), s);
+ }
+ }
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{verify}"), "NONE");
+ olderrors = Errors;
+ QuickAbort = false;
+ SuprErrs = true;
+
+ /*
+ ** rcode == EX_SOFTWARE is special:
+ ** the TLS negotiation failed
+ ** we have to drop the connection no matter what
+ ** However, we call tls_server to give it the chance
+ ** to log the problem and return an appropriate
+ ** error code.
+ */
+
+ if (rscheck("tls_server",
+ macvalue(macid("{verify}"), e),
+ NULL, e, RSF_RMCOMM|RSF_COUNT, 5,
+ host, NOQID, NULL) != EX_OK ||
+ Errors > olderrors ||
+ rcode == EX_SOFTWARE)
+ {
+ char enhsc[ENHSCLEN];
+ extern char MsgBuf[];
+
+ if (ISSMTPCODE(MsgBuf) &&
+ extenhsc(MsgBuf + 4, ' ', enhsc) > 0)
+ {
+ p = sm_rpool_strdup_x(e->e_rpool,
+ MsgBuf);
+ }
+ else
+ {
+ p = "403 4.7.0 server not authenticated.";
+ (void) sm_strlcpy(enhsc, "4.7.0",
+ sizeof(enhsc));
+ }
+ SuprErrs = saveSuprErrs;
+ QuickAbort = saveQuickAbort;
+
+ if (rcode == EX_SOFTWARE)
+ {
+ /* drop the connection */
+ mci->mci_state = MCIS_QUITING;
+ if (mci->mci_in != NULL)
+ {
+ (void) sm_io_close(mci->mci_in,
+ SM_TIME_DEFAULT);
+ mci->mci_in = NULL;
+ }
+ mci->mci_flags &= ~MCIF_TLSACT;
+ (void) endmailer(mci, e, pv);
+ }
+ else
+ {
+ /* abort transfer */
+ smtpquit(m, mci, e);
+ }
+
+ /* avoid bogus error msg */
+ mci->mci_errno = 0;
+
+ /* temp or permanent failure? */
+ rcode = (*p == '4') ? EX_TEMPFAIL
+ : EX_UNAVAILABLE;
+ mci_setstat(mci, rcode, enhsc, p);
+
+ /*
+ ** hack to get the error message into
+ ** the envelope (done in giveresponse())
+ */
+
+ (void) sm_strlcpy(SmtpError, p,
+ sizeof(SmtpError));
+ }
+ else if (mci->mci_state == MCIS_CLOSED)
+ {
+ /* connection close caused by 421 */
+ mci->mci_errno = 0;
+ rcode = EX_TEMPFAIL;
+ mci_setstat(mci, rcode, NULL, "421");
+ }
+ else
+ rcode = 0;
+
+ QuickAbort = saveQuickAbort;
+ SuprErrs = saveSuprErrs;
+ if (DONE_STARTTLS(mci->mci_flags) &&
+ mci->mci_state != MCIS_CLOSED)
+ {
+ SET_HELO(mci->mci_flags);
+ mci_clr_extensions(mci);
+ goto reconnect;
+ }
+ }
+# endif /* STARTTLS */
+# if SASL
+ /* if other server supports authentication let's authenticate */
+ if (mci->mci_state != MCIS_CLOSED &&
+ mci->mci_saslcap != NULL &&
+ !DONE_AUTH(mci->mci_flags) && !iscltflgset(e, D_NOAUTH))
+ {
+ /* Should we require some minimum authentication? */
+ if ((ret = smtpauth(m, mci, e)) == EX_OK)
+ {
+ int result;
+ sasl_ssf_t *ssf = NULL;
+
+ /* Get security strength (features) */
+ result = sasl_getprop(mci->mci_conn, SASL_SSF,
+# if SASL >= 20000
+ (const void **) &ssf);
+# else /* SASL >= 20000 */
+ (void **) &ssf);
+# endif /* SASL >= 20000 */
+
+ /* XXX authid? */
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, NOQID,
+ "AUTH=client, relay=%.100s, mech=%.16s, bits=%d",
+ mci->mci_host,
+ macvalue(macid("{auth_type}"), e),
+ result == SASL_OK ? *ssf : 0);
+
+ /*
+ ** Only switch to encrypted connection
+ ** if a security layer has been negotiated
+ */
+
+ if (result == SASL_OK && *ssf > 0)
+ {
+ int tmo;
+
+ /*
+ ** Convert I/O layer to use SASL.
+ ** If the call fails, the connection
+ ** is aborted.
+ */
+
+ tmo = DATA_PROGRESS_TIMEOUT * 1000;
+ if (sfdcsasl(&mci->mci_in,
+ &mci->mci_out,
+ mci->mci_conn, tmo) == 0)
+ {
+ mci_clr_extensions(mci);
+ mci->mci_flags |= MCIF_AUTHACT|
+ MCIF_ONLY_EHLO;
+ goto reconnect;
+ }
+ syserr("AUTH TLS switch failed in client");
+ }
+ /* else? XXX */
+ mci->mci_flags |= MCIF_AUTHACT;
+
+ }
+ else if (ret == EX_TEMPFAIL)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "AUTH=client, relay=%.100s, temporary failure, connection abort",
+ mci->mci_host);
+ smtpquit(m, mci, e);
+
+ /* avoid bogus error msg */
+ mci->mci_errno = 0;
+ rcode = EX_TEMPFAIL;
+ mci_setstat(mci, rcode, "4.3.0", p);
+
+ /*
+ ** hack to get the error message into
+ ** the envelope (done in giveresponse())
+ */
+
+ (void) sm_strlcpy(SmtpError,
+ "Temporary AUTH failure",
+ sizeof(SmtpError));
+ }
+ }
+# endif /* SASL */
+ }
+
+
+do_transfer:
+ /* clear out per-message flags from connection structure */
+ mci->mci_flags &= ~(MCIF_CVT7TO8|MCIF_CVT8TO7);
+
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ bitnset(M_7BITS, m->m_flags))
+ mci->mci_flags |= MCIF_CVT8TO7;
+
+#if MIME7TO8
+ if (bitnset(M_MAKE8BIT, m->m_flags) &&
+ !bitset(MCIF_7BIT, mci->mci_flags) &&
+ (p = hvalue("Content-Transfer-Encoding", e->e_header)) != NULL &&
+ (sm_strcasecmp(p, "quoted-printable") == 0 ||
+ sm_strcasecmp(p, "base64") == 0) &&
+ (p = hvalue("Content-Type", e->e_header)) != NULL)
+ {
+ /* may want to convert 7 -> 8 */
+ /* XXX should really parse it here -- and use a class XXX */
+ if (sm_strncasecmp(p, "text/plain", 10) == 0 &&
+ (p[10] == '\0' || p[10] == ' ' || p[10] == ';'))
+ mci->mci_flags |= MCIF_CVT7TO8;
+ }
+#endif /* MIME7TO8 */
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("openmailer: ");
+ mci_dump(sm_debug_file(), mci, false);
+ }
+
+#if _FFR_CLIENT_SIZE
+ /*
+ ** See if we know the maximum size and
+ ** abort if the message is too big.
+ **
+ ** NOTE: _FFR_CLIENT_SIZE is untested.
+ */
+
+ if (bitset(MCIF_SIZE, mci->mci_flags) &&
+ mci->mci_maxsize > 0 &&
+ e->e_msgsize > mci->mci_maxsize)
+ {
+ e->e_flags |= EF_NO_BODY_RETN;
+ if (bitnset(M_LOCALMAILER, m->m_flags))
+ e->e_status = "5.2.3";
+ else
+ e->e_status = "5.3.4";
+
+ usrerrenh(e->e_status,
+ "552 Message is too large; %ld bytes max",
+ mci->mci_maxsize);
+ rcode = EX_DATAERR;
+
+ /* Need an e_message for error */
+ (void) sm_snprintf(SmtpError, sizeof(SmtpError),
+ "Message is too large; %ld bytes max",
+ mci->mci_maxsize);
+ goto give_up;
+ }
+#endif /* _FFR_CLIENT_SIZE */
+
+ if (mci->mci_state != MCIS_OPEN)
+ {
+ /* couldn't open the mailer */
+ rcode = mci->mci_exitstat;
+ errno = mci->mci_errno;
+ SM_SET_H_ERRNO(mci->mci_herrno);
+ if (rcode == EX_OK)
+ {
+ /* shouldn't happen */
+ syserr("554 5.3.5 deliver: mci=%lx rcode=%d errno=%d state=%d sig=%s",
+ (unsigned long) mci, rcode, errno,
+ mci->mci_state, firstsig);
+ mci_dump_all(smioout, true);
+ rcode = EX_SOFTWARE;
+ }
+ else if (nummxhosts > hostnum)
+ {
+ /* try next MX site */
+ goto tryhost;
+ }
+ }
+ else if (!clever)
+ {
+ bool ok;
+
+ /*
+ ** Format and send message.
+ */
+
+ rcode = EX_OK;
+ errno = 0;
+ ok = putfromline(mci, e);
+ if (ok)
+ ok = (*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER);
+ if (ok)
+ ok = (*e->e_putbody)(mci, e, NULL);
+ if (ok && bitset(MCIF_INLONGLINE, mci->mci_flags))
+ ok = putline("", mci);
+
+ /*
+ ** Ignore an I/O error that was caused by EPIPE.
+ ** Some broken mailers don't read the entire body
+ ** but just exit() thus causing an I/O error.
+ */
+
+ if (!ok && (sm_io_error(mci->mci_out) && errno == EPIPE))
+ ok = true;
+
+ /* (always) get the exit status */
+ rcode = endmailer(mci, e, pv);
+ if (!ok)
+ rcode = EX_TEMPFAIL;
+ if (rcode == EX_TEMPFAIL && SmtpError[0] == '\0')
+ {
+ /*
+ ** Need an e_message for mailq display.
+ ** We set SmtpError as
+ */
+
+ (void) sm_snprintf(SmtpError, sizeof(SmtpError),
+ "%s mailer (%s) exited with EX_TEMPFAIL",
+ m->m_name, m->m_mailer);
+ }
+ }
+ else
+ {
+ /*
+ ** Send the MAIL FROM: protocol
+ */
+
+ /* XXX this isn't pipelined... */
+ rcode = smtpmailfrom(m, mci, e);
+ if (rcode == EX_OK)
+ {
+ register int i;
+# if PIPELINING
+ ADDRESS *volatile pchain;
+# endif /* PIPELINING */
+
+ /* send the recipient list */
+ tobuf[0] = '\0';
+ mci->mci_retryrcpt = false;
+ mci->mci_tolist = tobuf;
+# if PIPELINING
+ pchain = NULL;
+ mci->mci_nextaddr = NULL;
+# endif /* PIPELINING */
+
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ {
+ if (!QS_IS_UNMARKED(to->q_state))
+ continue;
+
+ /* mark recipient state as "ok so far" */
+ to->q_state = QS_OK;
+ e->e_to = to->q_paddr;
+# if STARTTLS
+ i = rscheck("tls_rcpt", to->q_user, NULL, e,
+ RSF_RMCOMM|RSF_COUNT, 3,
+ mci->mci_host, e->e_id, NULL);
+ if (i != EX_OK)
+ {
+ markfailure(e, to, mci, i, false);
+ giveresponse(i, to->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ if (i == EX_TEMPFAIL)
+ {
+ mci->mci_retryrcpt = true;
+ to->q_state = QS_RETRY;
+ }
+ continue;
+ }
+# endif /* STARTTLS */
+
+ i = smtprcpt(to, m, mci, e, ctladdr, xstart);
+# if PIPELINING
+ if (i == EX_OK &&
+ bitset(MCIF_PIPELINED, mci->mci_flags))
+ {
+ /*
+ ** Add new element to list of
+ ** recipients for pipelining.
+ */
+
+ to->q_pchain = NULL;
+ if (mci->mci_nextaddr == NULL)
+ mci->mci_nextaddr = to;
+ if (pchain == NULL)
+ pchain = to;
+ else
+ {
+ pchain->q_pchain = to;
+ pchain = pchain->q_pchain;
+ }
+ }
+# endif /* PIPELINING */
+ if (i != EX_OK)
+ {
+ markfailure(e, to, mci, i, false);
+ giveresponse(i, to->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ if (i == EX_TEMPFAIL)
+ to->q_state = QS_RETRY;
+ }
+ }
+
+ /* No recipients in list and no missing responses? */
+ if (tobuf[0] == '\0'
+# if PIPELINING
+ && bitset(MCIF_PIPELINED, mci->mci_flags)
+ && mci->mci_nextaddr == NULL
+# endif /* PIPELINING */
+ )
+ {
+ rcode = EX_OK;
+ e->e_to = NULL;
+ if (bitset(MCIF_CACHED, mci->mci_flags))
+ smtprset(m, mci, e);
+ }
+ else
+ {
+ e->e_to = tobuf + 1;
+ rcode = smtpdata(m, mci, e, ctladdr, xstart);
+ }
+ }
+ if (rcode == EX_TEMPFAIL && nummxhosts > hostnum)
+ {
+ /* try next MX site */
+ goto tryhost;
+ }
+ }
+#if NAMED_BIND
+ if (ConfigLevel < 2)
+ _res.options |= RES_DEFNAMES | RES_DNSRCH; /* XXX */
+#endif /* NAMED_BIND */
+
+ if (tTd(62, 1))
+ checkfds("after delivery");
+
+ /*
+ ** Do final status disposal.
+ ** We check for something in tobuf for the SMTP case.
+ ** If we got a temporary failure, arrange to queue the
+ ** addressees.
+ */
+
+ give_up:
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ lmtp_rcode = rcode;
+ tobuf[0] = '\0';
+ anyok = false;
+ strsize = 0;
+ }
+ else
+ anyok = rcode == EX_OK;
+
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ {
+ /* see if address already marked */
+ if (!QS_IS_OK(to->q_state))
+ continue;
+
+ /* if running LMTP, get the status for each address */
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ if (lmtp_rcode == EX_OK)
+ rcode = smtpgetstat(m, mci, e);
+ if (rcode == EX_OK)
+ {
+ strsize += sm_strlcat2(tobuf + strsize, ",",
+ to->q_paddr,
+ tobufsize - strsize);
+ SM_ASSERT(strsize < tobufsize);
+ anyok = true;
+ }
+ else
+ {
+ e->e_to = to->q_paddr;
+ markfailure(e, to, mci, rcode, true);
+ giveresponse(rcode, to->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ e->e_to = tobuf + 1;
+ continue;
+ }
+ }
+ else
+ {
+ /* mark bad addresses */
+ if (rcode != EX_OK)
+ {
+ if (goodmxfound && rcode == EX_NOHOST)
+ rcode = EX_TEMPFAIL;
+ markfailure(e, to, mci, rcode, true);
+ continue;
+ }
+ }
+
+ /* successful delivery */
+ to->q_state = QS_SENT;
+ to->q_statdate = curtime();
+ e->e_nsent++;
+
+ /*
+ ** Checkpoint the send list every few addresses
+ */
+
+ if (CheckpointInterval > 0 && e->e_nsent >= CheckpointInterval)
+ {
+ queueup(e, false, false);
+ e->e_nsent = 0;
+ }
+
+ if (bitnset(M_LOCALMAILER, m->m_flags) &&
+ bitset(QPINGONSUCCESS, to->q_flags))
+ {
+ to->q_flags |= QDELIVERED;
+ to->q_status = "2.1.5";
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... Successfully delivered\n",
+ to->q_paddr);
+ }
+ else if (bitset(QPINGONSUCCESS, to->q_flags) &&
+ bitset(QPRIMARY, to->q_flags) &&
+ !bitset(MCIF_DSN, mci->mci_flags))
+ {
+ to->q_flags |= QRELAYED;
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... relayed; expect no further notifications\n",
+ to->q_paddr);
+ }
+ else if (IS_DLVR_NOTIFY(e) &&
+ !bitset(MCIF_DLVR_BY, mci->mci_flags) &&
+ bitset(QPRIMARY, to->q_flags) &&
+ (!bitset(QHASNOTIFY, to->q_flags) ||
+ bitset(QPINGONSUCCESS, to->q_flags) ||
+ bitset(QPINGONFAILURE, to->q_flags) ||
+ bitset(QPINGONDELAY, to->q_flags)))
+ {
+ /* RFC 2852, 4.1.4.2: no NOTIFY, or not NEVER */
+ to->q_flags |= QBYNRELAY;
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... Deliver-by notify: relayed\n",
+ to->q_paddr);
+ }
+ else if (IS_DLVR_TRACE(e) &&
+ (!bitset(QHASNOTIFY, to->q_flags) ||
+ bitset(QPINGONSUCCESS, to->q_flags) ||
+ bitset(QPINGONFAILURE, to->q_flags) ||
+ bitset(QPINGONDELAY, to->q_flags)) &&
+ bitset(QPRIMARY, to->q_flags))
+ {
+ /* RFC 2852, 4.1.4: no NOTIFY, or not NEVER */
+ to->q_flags |= QBYTRACE;
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... Deliver-By trace: relayed\n",
+ to->q_paddr);
+ }
+ }
+
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ /*
+ ** Global information applies to the last recipient only;
+ ** clear it out to avoid bogus errors.
+ */
+
+ rcode = EX_OK;
+ e->e_statmsg = NULL;
+
+ /* reset the mci state for the next transaction */
+ if (mci != NULL &&
+ (mci->mci_state == MCIS_MAIL ||
+ mci->mci_state == MCIS_RCPT ||
+ mci->mci_state == MCIS_DATA))
+ {
+ mci->mci_state = MCIS_OPEN;
+ SmtpPhase = mci->mci_phase = "idle";
+ sm_setproctitle(true, e, "%s: %s", CurHostName,
+ mci->mci_phase);
+ }
+ }
+
+ if (tobuf[0] != '\0')
+ {
+ giveresponse(rcode, NULL, m, mci, ctladdr, xstart, e, tochain);
+#if 0
+ /*
+ ** This code is disabled for now because I am not
+ ** sure that copying status from the first recipient
+ ** to all non-status'ed recipients is a good idea.
+ */
+
+ if (tochain->q_message != NULL &&
+ !bitnset(M_LMTP, m->m_flags) && rcode != EX_OK)
+ {
+ for (to = tochain->q_tchain; to != NULL;
+ to = to->q_tchain)
+ {
+ /* see if address already marked */
+ if (QS_IS_QUEUEUP(to->q_state) &&
+ to->q_message == NULL)
+ to->q_message = sm_rpool_strdup_x(e->e_rpool,
+ tochain->q_message);
+ }
+ }
+#endif /* 0 */
+ }
+ if (anyok)
+ markstats(e, tochain, STATS_NORMAL);
+ mci_store_persistent(mci);
+
+ /* Some recipients were tempfailed, try them on the next host */
+ if (mci != NULL && mci->mci_retryrcpt && nummxhosts > hostnum)
+ {
+ /* try next MX site */
+ goto tryhost;
+ }
+
+ /* now close the connection */
+ if (clever && mci != NULL && mci->mci_state != MCIS_CLOSED &&
+ !bitset(MCIF_CACHED, mci->mci_flags))
+ smtpquit(m, mci, e);
+
+cleanup: ;
+ }
+ SM_FINALLY
+ {
+ /*
+ ** Restore state and return.
+ */
+#if XDEBUG
+ char wbuf[MAXLINE];
+
+ /* make absolutely certain 0, 1, and 2 are in use */
+ (void) sm_snprintf(wbuf, sizeof(wbuf),
+ "%s... end of deliver(%s)",
+ e->e_to == NULL ? "NO-TO-LIST"
+ : shortenstring(e->e_to,
+ MAXSHORTSTR),
+ m->m_name);
+ checkfd012(wbuf);
+#endif /* XDEBUG */
+
+ errno = 0;
+
+ /*
+ ** It was originally necessary to set macro 'g' to NULL
+ ** because it previously pointed to an auto buffer.
+ ** We don't do this any more, so this may be unnecessary.
+ */
+
+ macdefine(&e->e_macro, A_PERM, 'g', (char *) NULL);
+ e->e_to = NULL;
+ }
+ SM_END_TRY
+ return rcode;
+}
+
+/*
+** MARKFAILURE -- mark a failure on a specific address.
+**
+** Parameters:
+** e -- the envelope we are sending.
+** q -- the address to mark.
+** mci -- mailer connection information.
+** rcode -- the code signifying the particular failure.
+** ovr -- override an existing code?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** marks the address (and possibly the envelope) with the
+** failure so that an error will be returned or
+** the message will be queued, as appropriate.
+*/
+
+void
+markfailure(e, q, mci, rcode, ovr)
+ register ENVELOPE *e;
+ register ADDRESS *q;
+ register MCI *mci;
+ int rcode;
+ bool ovr;
+{
+ int save_errno = errno;
+ char *status = NULL;
+ char *rstatus = NULL;
+
+ switch (rcode)
+ {
+ case EX_OK:
+ break;
+
+ case EX_TEMPFAIL:
+ case EX_IOERR:
+ case EX_OSERR:
+ q->q_state = QS_QUEUEUP;
+ break;
+
+ default:
+ q->q_state = QS_BADADDR;
+ break;
+ }
+
+ /* find most specific error code possible */
+ if (mci != NULL && mci->mci_status != NULL)
+ {
+ status = sm_rpool_strdup_x(e->e_rpool, mci->mci_status);
+ if (mci->mci_rstatus != NULL)
+ rstatus = sm_rpool_strdup_x(e->e_rpool,
+ mci->mci_rstatus);
+ else
+ rstatus = NULL;
+ }
+ else if (e->e_status != NULL)
+ {
+ status = e->e_status;
+ rstatus = NULL;
+ }
+ else
+ {
+ switch (rcode)
+ {
+ case EX_USAGE:
+ status = "5.5.4";
+ break;
+
+ case EX_DATAERR:
+ status = "5.5.2";
+ break;
+
+ case EX_NOUSER:
+ status = "5.1.1";
+ break;
+
+ case EX_NOHOST:
+ status = "5.1.2";
+ break;
+
+ case EX_NOINPUT:
+ case EX_CANTCREAT:
+ case EX_NOPERM:
+ status = "5.3.0";
+ break;
+
+ case EX_UNAVAILABLE:
+ case EX_SOFTWARE:
+ case EX_OSFILE:
+ case EX_PROTOCOL:
+ case EX_CONFIG:
+ status = "5.5.0";
+ break;
+
+ case EX_OSERR:
+ case EX_IOERR:
+ status = "4.5.0";
+ break;
+
+ case EX_TEMPFAIL:
+ status = "4.2.0";
+ break;
+ }
+ }
+
+ /* new status? */
+ if (status != NULL && *status != '\0' && (ovr || q->q_status == NULL ||
+ *q->q_status == '\0' || *q->q_status < *status))
+ {
+ q->q_status = status;
+ q->q_rstatus = rstatus;
+ }
+ if (rcode != EX_OK && q->q_rstatus == NULL &&
+ q->q_mailer != NULL && q->q_mailer->m_diagtype != NULL &&
+ sm_strcasecmp(q->q_mailer->m_diagtype, "X-UNIX") == 0)
+ {
+ char buf[16];
+
+ (void) sm_snprintf(buf, sizeof(buf), "%d", rcode);
+ q->q_rstatus = sm_rpool_strdup_x(e->e_rpool, buf);
+ }
+
+ q->q_statdate = curtime();
+ if (CurHostName != NULL && CurHostName[0] != '\0' &&
+ mci != NULL && !bitset(M_LOCALMAILER, mci->mci_flags))
+ q->q_statmta = sm_rpool_strdup_x(e->e_rpool, CurHostName);
+
+ /* restore errno */
+ errno = save_errno;
+}
+/*
+** ENDMAILER -- Wait for mailer to terminate.
+**
+** We should never get fatal errors (e.g., segmentation
+** violation), so we report those specially. For other
+** errors, we choose a status message (into statmsg),
+** and if it represents an error, we print it.
+**
+** Parameters:
+** mci -- the mailer connection info.
+** e -- the current envelope.
+** pv -- the parameter vector that invoked the mailer
+** (for error messages).
+**
+** Returns:
+** exit code of mailer.
+**
+** Side Effects:
+** none.
+*/
+
+static jmp_buf EndWaitTimeout;
+
+static void
+endwaittimeout(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(EndWaitTimeout, 1);
+}
+
+int
+endmailer(mci, e, pv)
+ register MCI *mci;
+ register ENVELOPE *e;
+ char **pv;
+{
+ int st;
+ int save_errno = errno;
+ char buf[MAXLINE];
+ SM_EVENT *ev = NULL;
+
+
+ mci_unlock_host(mci);
+
+ /* close output to mailer */
+ if (mci->mci_out != NULL)
+ {
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ mci->mci_out = NULL;
+ }
+
+ /* copy any remaining input to transcript */
+ if (mci->mci_in != NULL && mci->mci_state != MCIS_ERROR &&
+ e->e_xfp != NULL)
+ {
+ while (sfgets(buf, sizeof(buf), mci->mci_in,
+ TimeOuts.to_quit, "Draining Input") != NULL)
+ (void) sm_io_fputs(e->e_xfp, SM_TIME_DEFAULT, buf);
+ }
+
+#if SASL
+ /* close SASL connection */
+ if (bitset(MCIF_AUTHACT, mci->mci_flags))
+ {
+ sasl_dispose(&mci->mci_conn);
+ mci->mci_flags &= ~MCIF_AUTHACT;
+ }
+#endif /* SASL */
+
+#if STARTTLS
+ /* shutdown TLS */
+ (void) endtlsclt(mci);
+#endif /* STARTTLS */
+
+ /* now close the input */
+ if (mci->mci_in != NULL)
+ {
+ (void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
+ mci->mci_in = NULL;
+ }
+ mci->mci_state = MCIS_CLOSED;
+
+ errno = save_errno;
+
+ /* in the IPC case there is nothing to wait for */
+ if (mci->mci_pid == 0)
+ return EX_OK;
+
+ /* put a timeout around the wait */
+ if (mci->mci_mailer->m_wait > 0)
+ {
+ if (setjmp(EndWaitTimeout) == 0)
+ ev = sm_setevent(mci->mci_mailer->m_wait,
+ endwaittimeout, 0);
+ else
+ {
+ syserr("endmailer %s: wait timeout (%ld)",
+ mci->mci_mailer->m_name,
+ (long) mci->mci_mailer->m_wait);
+ return EX_TEMPFAIL;
+ }
+ }
+
+ /* wait for the mailer process, collect status */
+ st = waitfor(mci->mci_pid);
+ save_errno = errno;
+ if (ev != NULL)
+ sm_clrevent(ev);
+ errno = save_errno;
+
+ if (st == -1)
+ {
+ syserr("endmailer %s: wait", mci->mci_mailer->m_name);
+ return EX_SOFTWARE;
+ }
+
+ if (WIFEXITED(st))
+ {
+ /* normal death -- return status */
+ return (WEXITSTATUS(st));
+ }
+
+ /* it died a horrid death */
+ syserr("451 4.3.0 mailer %s died with signal %d%s",
+ mci->mci_mailer->m_name, WTERMSIG(st),
+ WCOREDUMP(st) ? " (core dumped)" :
+ (WIFSTOPPED(st) ? " (stopped)" : ""));
+
+ /* log the arguments */
+ if (pv != NULL && e->e_xfp != NULL)
+ {
+ register char **av;
+
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, "Arguments:");
+ for (av = pv; *av != NULL; av++)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, " %s",
+ *av);
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, "\n");
+ }
+
+ ExitStat = EX_TEMPFAIL;
+ return EX_TEMPFAIL;
+}
+/*
+** GIVERESPONSE -- Interpret an error response from a mailer
+**
+** Parameters:
+** status -- the status code from the mailer (high byte
+** only; core dumps must have been taken care of
+** already).
+** dsn -- the DSN associated with the address, if any.
+** m -- the mailer info for this mailer.
+** mci -- the mailer connection info -- can be NULL if the
+** response is given before the connection is made.
+** ctladdr -- the controlling address for the recipient
+** address(es).
+** xstart -- the transaction start time, for computing
+** transaction delays.
+** e -- the current envelope.
+** to -- the current recipient (NULL if none).
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Errors may be incremented.
+** ExitStat may be set.
+*/
+
+void
+giveresponse(status, dsn, m, mci, ctladdr, xstart, e, to)
+ int status;
+ char *dsn;
+ register MAILER *m;
+ register MCI *mci;
+ ADDRESS *ctladdr;
+ time_t xstart;
+ ENVELOPE *e;
+ ADDRESS *to;
+{
+ register const char *statmsg;
+ int errnum = errno;
+ int off = 4;
+ bool usestat = false;
+ char dsnbuf[ENHSCLEN];
+ char buf[MAXLINE];
+ char *exmsg;
+
+ if (e == NULL)
+ {
+ syserr("giveresponse: null envelope");
+ /* NOTREACHED */
+ SM_ASSERT(0);
+ }
+
+ /*
+ ** Compute status message from code.
+ */
+
+ exmsg = sm_sysexmsg(status);
+ if (status == 0)
+ {
+ statmsg = "250 2.0.0 Sent";
+ if (e->e_statmsg != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof(buf), "%s (%s)",
+ statmsg,
+ shortenstring(e->e_statmsg, 403));
+ statmsg = buf;
+ }
+ }
+ else if (exmsg == NULL)
+ {
+ (void) sm_snprintf(buf, sizeof(buf),
+ "554 5.3.0 unknown mailer error %d",
+ status);
+ status = EX_UNAVAILABLE;
+ statmsg = buf;
+ usestat = true;
+ }
+ else if (status == EX_TEMPFAIL)
+ {
+ char *bp = buf;
+
+ (void) sm_strlcpy(bp, exmsg + 1, SPACELEFT(buf, bp));
+ bp += strlen(bp);
+#if NAMED_BIND
+ if (h_errno == TRY_AGAIN)
+ statmsg = sm_errstring(h_errno + E_DNSBASE);
+ else
+#endif /* NAMED_BIND */
+ {
+ if (errnum != 0)
+ statmsg = sm_errstring(errnum);
+ else
+ statmsg = SmtpError;
+ }
+ if (statmsg != NULL && statmsg[0] != '\0')
+ {
+ switch (errnum)
+ {
+#ifdef ENETDOWN
+ case ENETDOWN: /* Network is down */
+#endif /* ENETDOWN */
+#ifdef ENETUNREACH
+ case ENETUNREACH: /* Network is unreachable */
+#endif /* ENETUNREACH */
+#ifdef ENETRESET
+ case ENETRESET: /* Network dropped connection on reset */
+#endif /* ENETRESET */
+#ifdef ECONNABORTED
+ case ECONNABORTED: /* Software caused connection abort */
+#endif /* ECONNABORTED */
+#ifdef EHOSTDOWN
+ case EHOSTDOWN: /* Host is down */
+#endif /* EHOSTDOWN */
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH: /* No route to host */
+#endif /* EHOSTUNREACH */
+ if (mci != NULL && mci->mci_host != NULL)
+ {
+ (void) sm_strlcpyn(bp,
+ SPACELEFT(buf, bp),
+ 2, ": ",
+ mci->mci_host);
+ bp += strlen(bp);
+ }
+ break;
+ }
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ": ",
+ statmsg);
+ usestat = true;
+ }
+ statmsg = buf;
+ }
+#if NAMED_BIND
+ else if (status == EX_NOHOST && h_errno != 0)
+ {
+ statmsg = sm_errstring(h_errno + E_DNSBASE);
+ (void) sm_snprintf(buf, sizeof(buf), "%s (%s)", exmsg + 1,
+ statmsg);
+ statmsg = buf;
+ usestat = true;
+ }
+#endif /* NAMED_BIND */
+ else
+ {
+ statmsg = exmsg;
+ if (*statmsg++ == ':' && errnum != 0)
+ {
+ (void) sm_snprintf(buf, sizeof(buf), "%s: %s", statmsg,
+ sm_errstring(errnum));
+ statmsg = buf;
+ usestat = true;
+ }
+ else if (bitnset(M_LMTP, m->m_flags) && e->e_statmsg != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof(buf), "%s (%s)", statmsg,
+ shortenstring(e->e_statmsg, 403));
+ statmsg = buf;
+ usestat = true;
+ }
+ }
+
+ /*
+ ** Print the message as appropriate
+ */
+
+ if (status == EX_OK || status == EX_TEMPFAIL)
+ {
+ extern char MsgBuf[];
+
+ if ((off = isenhsc(statmsg + 4, ' ')) > 0)
+ {
+ if (dsn == NULL)
+ {
+ (void) sm_snprintf(dsnbuf, sizeof(dsnbuf),
+ "%.*s", off, statmsg + 4);
+ dsn = dsnbuf;
+ }
+ off += 5;
+ }
+ else
+ {
+ off = 4;
+ }
+ message("%s", statmsg + off);
+ if (status == EX_TEMPFAIL && e->e_xfp != NULL)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, "%s\n",
+ &MsgBuf[4]);
+ }
+ else
+ {
+ char mbuf[ENHSCLEN + 4];
+
+ Errors++;
+ if ((off = isenhsc(statmsg + 4, ' ')) > 0 &&
+ off < sizeof(mbuf) - 4)
+ {
+ if (dsn == NULL)
+ {
+ (void) sm_snprintf(dsnbuf, sizeof(dsnbuf),
+ "%.*s", off, statmsg + 4);
+ dsn = dsnbuf;
+ }
+ off += 5;
+
+ /* copy only part of statmsg to mbuf */
+ (void) sm_strlcpy(mbuf, statmsg, off);
+ (void) sm_strlcat(mbuf, " %s", sizeof(mbuf));
+ }
+ else
+ {
+ dsnbuf[0] = '\0';
+ (void) sm_snprintf(mbuf, sizeof(mbuf), "%.3s %%s",
+ statmsg);
+ off = 4;
+ }
+ usrerr(mbuf, &statmsg[off]);
+ }
+
+ /*
+ ** Final cleanup.
+ ** Log a record of the transaction. Compute the new
+ ** ExitStat -- if we already had an error, stick with
+ ** that.
+ */
+
+ if (OpMode != MD_VERIFY && !bitset(EF_VRFYONLY, e->e_flags) &&
+ LogLevel > ((status == EX_TEMPFAIL) ? 8 : (status == EX_OK) ? 7 : 6))
+ logdelivery(m, mci, dsn, statmsg + off, ctladdr, xstart, e);
+
+ if (tTd(11, 2))
+ sm_dprintf("giveresponse: status=%d, dsn=%s, e->e_message=%s, errnum=%d\n",
+ status,
+ dsn == NULL ? "<NULL>" : dsn,
+ e->e_message == NULL ? "<NULL>" : e->e_message,
+ errnum);
+
+ if (status != EX_TEMPFAIL)
+ setstat(status);
+ if (status != EX_OK && (status != EX_TEMPFAIL || e->e_message == NULL))
+ e->e_message = sm_rpool_strdup_x(e->e_rpool, statmsg + off);
+ if (status != EX_OK && to != NULL && to->q_message == NULL)
+ {
+ if (!usestat && e->e_message != NULL)
+ to->q_message = sm_rpool_strdup_x(e->e_rpool,
+ e->e_message);
+ else
+ to->q_message = sm_rpool_strdup_x(e->e_rpool,
+ statmsg + off);
+ }
+ errno = 0;
+ SM_SET_H_ERRNO(0);
+}
+/*
+** LOGDELIVERY -- log the delivery in the system log
+**
+** Care is taken to avoid logging lines that are too long, because
+** some versions of syslog have an unfortunate proclivity for core
+** dumping. This is a hack, to be sure, that is at best empirical.
+**
+** Parameters:
+** m -- the mailer info. Can be NULL for initial queue.
+** mci -- the mailer connection info -- can be NULL if the
+** log is occurring when no connection is active.
+** dsn -- the DSN attached to the status.
+** status -- the message to print for the status.
+** ctladdr -- the controlling address for the to list.
+** xstart -- the transaction start time, used for
+** computing transaction delay.
+** e -- the current envelope.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** none
+*/
+
+void
+logdelivery(m, mci, dsn, status, ctladdr, xstart, e)
+ MAILER *m;
+ register MCI *mci;
+ char *dsn;
+ const char *status;
+ ADDRESS *ctladdr;
+ time_t xstart;
+ register ENVELOPE *e;
+{
+ register char *bp;
+ register char *p;
+ int l;
+ time_t now = curtime();
+ char buf[1024];
+
+#if (SYSLOG_BUFSIZE) >= 256
+ /* ctladdr: max 106 bytes */
+ bp = buf;
+ if (ctladdr != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", ctladdr=",
+ shortenstring(ctladdr->q_paddr, 83));
+ bp += strlen(bp);
+ if (bitset(QGOODUID, ctladdr->q_flags))
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), " (%d/%d)",
+ (int) ctladdr->q_uid,
+ (int) ctladdr->q_gid);
+ bp += strlen(bp);
+ }
+ }
+
+ /* delay & xdelay: max 41 bytes */
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", delay=",
+ pintvl(now - e->e_ctime, true));
+ bp += strlen(bp);
+
+ if (xstart != (time_t) 0)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", xdelay=",
+ pintvl(now - xstart, true));
+ bp += strlen(bp);
+ }
+
+ /* mailer: assume about 19 bytes (max 10 byte mailer name) */
+ if (m != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", mailer=",
+ m->m_name);
+ bp += strlen(bp);
+ }
+
+ /* pri: changes with each delivery attempt */
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), ", pri=%ld",
+ e->e_msgpriority);
+ bp += strlen(bp);
+
+ /* relay: max 66 bytes for IPv4 addresses */
+ if (mci != NULL && mci->mci_host != NULL)
+ {
+ extern SOCKADDR CurHostAddr;
+
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", relay=",
+ shortenstring(mci->mci_host, 40));
+ bp += strlen(bp);
+
+ if (CurHostAddr.sa.sa_family != 0)
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), " [%s]",
+ anynet_ntoa(&CurHostAddr));
+ }
+ }
+ else if (strcmp(status, "quarantined") == 0)
+ {
+ if (e->e_quarmsg != NULL)
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", quarantine=%s",
+ shortenstring(e->e_quarmsg, 40));
+ }
+ else if (strcmp(status, "queued") != 0)
+ {
+ p = macvalue('h', e);
+ if (p != NULL && p[0] != '\0')
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", relay=%s", shortenstring(p, 40));
+ }
+ }
+ bp += strlen(bp);
+
+ /* dsn */
+ if (dsn != NULL && *dsn != '\0')
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", dsn=",
+ shortenstring(dsn, ENHSCLEN));
+ bp += strlen(bp);
+ }
+
+#if _FFR_LOG_NTRIES
+ /* ntries */
+ if (e->e_ntries >= 0)
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", ntries=%d", e->e_ntries + 1);
+ bp += strlen(bp);
+ }
+#endif /* _FFR_LOG_NTRIES */
+
+# define STATLEN (((SYSLOG_BUFSIZE) - 100) / 4)
+# if (STATLEN) < 63
+# undef STATLEN
+# define STATLEN 63
+# endif /* (STATLEN) < 63 */
+# if (STATLEN) > 203
+# undef STATLEN
+# define STATLEN 203
+# endif /* (STATLEN) > 203 */
+
+ /* stat: max 210 bytes */
+ if ((bp - buf) > (sizeof(buf) - ((STATLEN) + 20)))
+ {
+ /* desperation move -- truncate data */
+ bp = buf + sizeof(buf) - ((STATLEN) + 17);
+ (void) sm_strlcpy(bp, "...", SPACELEFT(buf, bp));
+ bp += 3;
+ }
+
+ (void) sm_strlcpy(bp, ", stat=", SPACELEFT(buf, bp));
+ bp += strlen(bp);
+
+ (void) sm_strlcpy(bp, shortenstring(status, STATLEN),
+ SPACELEFT(buf, bp));
+
+ /* id, to: max 13 + TOBUFSIZE bytes */
+ l = SYSLOG_BUFSIZE - 100 - strlen(buf);
+ if (l < 0)
+ l = 0;
+ p = e->e_to == NULL ? "NO-TO-LIST" : e->e_to;
+ while (strlen(p) >= l)
+ {
+ register char *q;
+
+ for (q = p + l; q > p; q--)
+ {
+ if (*q == ',')
+ break;
+ }
+ if (p == q)
+ break;
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s [more]%s",
+ (int) (++q - p), p, buf);
+ p = q;
+ }
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s%s", l, p, buf);
+
+#else /* (SYSLOG_BUFSIZE) >= 256 */
+
+ l = SYSLOG_BUFSIZE - 85;
+ if (l < 0)
+ l = 0;
+ p = e->e_to == NULL ? "NO-TO-LIST" : e->e_to;
+ while (strlen(p) >= l)
+ {
+ register char *q;
+
+ for (q = p + l; q > p; q--)
+ {
+ if (*q == ',')
+ break;
+ }
+ if (p == q)
+ break;
+
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s [more]",
+ (int) (++q - p), p);
+ p = q;
+ }
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s", l, p);
+
+ if (ctladdr != NULL)
+ {
+ bp = buf;
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, "ctladdr=",
+ shortenstring(ctladdr->q_paddr, 83));
+ bp += strlen(bp);
+ if (bitset(QGOODUID, ctladdr->q_flags))
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), " (%d/%d)",
+ ctladdr->q_uid, ctladdr->q_gid);
+ bp += strlen(bp);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "%s", buf);
+ }
+ bp = buf;
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, "delay=",
+ pintvl(now - e->e_ctime, true));
+ bp += strlen(bp);
+ if (xstart != (time_t) 0)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", xdelay=",
+ pintvl(now - xstart, true));
+ bp += strlen(bp);
+ }
+
+ if (m != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", mailer=",
+ m->m_name);
+ bp += strlen(bp);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "%.1000s", buf);
+
+ buf[0] = '\0';
+ bp = buf;
+ if (mci != NULL && mci->mci_host != NULL)
+ {
+ extern SOCKADDR CurHostAddr;
+
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), "relay=%.100s",
+ mci->mci_host);
+ bp += strlen(bp);
+
+ if (CurHostAddr.sa.sa_family != 0)
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ " [%.100s]",
+ anynet_ntoa(&CurHostAddr));
+ }
+ else if (strcmp(status, "quarantined") == 0)
+ {
+ if (e->e_quarmsg != NULL)
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", quarantine=%.100s",
+ e->e_quarmsg);
+ }
+ else if (strcmp(status, "queued") != 0)
+ {
+ p = macvalue('h', e);
+ if (p != NULL && p[0] != '\0')
+ (void) sm_snprintf(buf, sizeof(buf), "relay=%.100s", p);
+ }
+ if (buf[0] != '\0')
+ sm_syslog(LOG_INFO, e->e_id, "%.1000s", buf);
+
+ sm_syslog(LOG_INFO, e->e_id, "stat=%s", shortenstring(status, 63));
+#endif /* (SYSLOG_BUFSIZE) >= 256 */
+}
+/*
+** PUTFROMLINE -- output a UNIX-style from line (or whatever)
+**
+** This can be made an arbitrary message separator by changing $l
+**
+** One of the ugliest hacks seen by human eyes is contained herein:
+** UUCP wants those stupid "remote from <host>" lines. Why oh why
+** does a well-meaning programmer such as myself have to deal with
+** this kind of antique garbage????
+**
+** Parameters:
+** mci -- the connection information.
+** e -- the envelope.
+**
+** Returns:
+** true iff line was written successfully
+**
+** Side Effects:
+** outputs some text to fp.
+*/
+
+bool
+putfromline(mci, e)
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ char *template = UnixFromLine;
+ char buf[MAXLINE];
+ char xbuf[MAXLINE];
+
+ if (bitnset(M_NHDR, mci->mci_mailer->m_flags))
+ return true;
+
+ mci->mci_flags |= MCIF_INHEADER;
+
+ if (bitnset(M_UGLYUUCP, mci->mci_mailer->m_flags))
+ {
+ char *bang;
+
+ expand("\201g", buf, sizeof(buf), e);
+ bang = strchr(buf, '!');
+ if (bang == NULL)
+ {
+ char *at;
+ char hname[MAXNAME];
+
+ /*
+ ** If we can construct a UUCP path, do so
+ */
+
+ at = strrchr(buf, '@');
+ if (at == NULL)
+ {
+ expand("\201k", hname, sizeof(hname), e);
+ at = hname;
+ }
+ else
+ *at++ = '\0';
+ (void) sm_snprintf(xbuf, sizeof(xbuf),
+ "From %.800s \201d remote from %.100s\n",
+ buf, at);
+ }
+ else
+ {
+ *bang++ = '\0';
+ (void) sm_snprintf(xbuf, sizeof(xbuf),
+ "From %.800s \201d remote from %.100s\n",
+ bang, buf);
+ template = xbuf;
+ }
+ }
+ expand(template, buf, sizeof(buf), e);
+ return putxline(buf, strlen(buf), mci, PXLF_HEADER);
+}
+
+/*
+** PUTBODY -- put the body of a message.
+**
+** Parameters:
+** mci -- the connection information.
+** e -- the envelope to put out.
+** separator -- if non-NULL, a message separator that must
+** not be permitted in the resulting message.
+**
+** Returns:
+** true iff message was written successfully
+**
+** Side Effects:
+** The message is written onto fp.
+*/
+
+/* values for output state variable */
+#define OSTATE_HEAD 0 /* at beginning of line */
+#define OSTATE_CR 1 /* read a carriage return */
+#define OSTATE_INLINE 2 /* putting rest of line */
+
+bool
+putbody(mci, e, separator)
+ register MCI *mci;
+ register ENVELOPE *e;
+ char *separator;
+{
+ bool dead = false;
+ bool ioerr = false;
+ int save_errno;
+ char buf[MAXLINE];
+#if MIME8TO7
+ char *boundaries[MAXMIMENESTING + 1];
+#endif /* MIME8TO7 */
+
+ /*
+ ** Output the body of the message
+ */
+
+ if (e->e_dfp == NULL && bitset(EF_HAS_DF, e->e_flags))
+ {
+ char *df = queuename(e, DATAFL_LETTER);
+
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, df,
+ SM_IO_RDONLY_B, NULL);
+ if (e->e_dfp == NULL)
+ {
+ char *msg = "!putbody: Cannot open %s for %s from %s";
+
+ if (errno == ENOENT)
+ msg++;
+ syserr(msg, df, e->e_to, e->e_from.q_paddr);
+ }
+
+ }
+ if (e->e_dfp == NULL)
+ {
+ if (bitset(MCIF_INHEADER, mci->mci_flags))
+ {
+ if (!putline("", mci))
+ goto writeerr;
+ mci->mci_flags &= ~MCIF_INHEADER;
+ }
+ if (!putline("<<< No Message Collected >>>", mci))
+ goto writeerr;
+ goto endofmessage;
+ }
+
+ if (e->e_dfino == (ino_t) 0)
+ {
+ struct stat stbuf;
+
+ if (fstat(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD, NULL), &stbuf)
+ < 0)
+ e->e_dfino = -1;
+ else
+ {
+ e->e_dfdev = stbuf.st_dev;
+ e->e_dfino = stbuf.st_ino;
+ }
+ }
+
+ /* paranoia: the data file should always be in a rewound state */
+ (void) bfrewind(e->e_dfp);
+
+ /* simulate an I/O timeout when used as source */
+ if (tTd(84, 101))
+ sleep(319);
+
+#if MIME8TO7
+ if (bitset(MCIF_CVT8TO7, mci->mci_flags))
+ {
+ /*
+ ** Do 8 to 7 bit MIME conversion.
+ */
+
+ /* make sure it looks like a MIME message */
+ if (hvalue("MIME-Version", e->e_header) == NULL &&
+ !putline("MIME-Version: 1.0", mci))
+ goto writeerr;
+
+ if (hvalue("Content-Type", e->e_header) == NULL)
+ {
+ (void) sm_snprintf(buf, sizeof(buf),
+ "Content-Type: text/plain; charset=%s",
+ defcharset(e));
+ if (!putline(buf, mci))
+ goto writeerr;
+ }
+
+ /* now do the hard work */
+ boundaries[0] = NULL;
+ mci->mci_flags |= MCIF_INHEADER;
+ if (mime8to7(mci, e->e_header, e, boundaries, M87F_OUTER, 0) ==
+ SM_IO_EOF)
+ goto writeerr;
+ }
+# if MIME7TO8
+ else if (bitset(MCIF_CVT7TO8, mci->mci_flags))
+ {
+ if (!mime7to8(mci, e->e_header, e))
+ goto writeerr;
+ }
+# endif /* MIME7TO8 */
+ else if (MaxMimeHeaderLength > 0 || MaxMimeFieldLength > 0)
+ {
+ bool oldsuprerrs = SuprErrs;
+
+ /* Use mime8to7 to check multipart for MIME header overflows */
+ boundaries[0] = NULL;
+ mci->mci_flags |= MCIF_INHEADER;
+
+ /*
+ ** If EF_DONT_MIME is set, we have a broken MIME message
+ ** and don't want to generate a new bounce message whose
+ ** body propagates the broken MIME. We can't just not call
+ ** mime8to7() as is done above since we need the security
+ ** checks. The best we can do is suppress the errors.
+ */
+
+ if (bitset(EF_DONT_MIME, e->e_flags))
+ SuprErrs = true;
+
+ if (mime8to7(mci, e->e_header, e, boundaries,
+ M87F_OUTER|M87F_NO8TO7, 0) == SM_IO_EOF)
+ goto writeerr;
+
+ /* restore SuprErrs */
+ SuprErrs = oldsuprerrs;
+ }
+ else
+#endif /* MIME8TO7 */
+ {
+ int ostate;
+ register char *bp;
+ register char *pbp;
+ register int c;
+ register char *xp;
+ int padc;
+ char *buflim;
+ int pos = 0;
+ char peekbuf[12];
+
+ if (bitset(MCIF_INHEADER, mci->mci_flags))
+ {
+ if (!putline("", mci))
+ goto writeerr;
+ mci->mci_flags &= ~MCIF_INHEADER;
+ }
+
+ /* determine end of buffer; allow for short mailer lines */
+ buflim = &buf[sizeof(buf) - 1];
+ if (mci->mci_mailer->m_linelimit > 0 &&
+ mci->mci_mailer->m_linelimit < sizeof(buf) - 1)
+ buflim = &buf[mci->mci_mailer->m_linelimit - 1];
+
+ /* copy temp file to output with mapping */
+ ostate = OSTATE_HEAD;
+ bp = buf;
+ pbp = peekbuf;
+ while (!sm_io_error(mci->mci_out) && !dead)
+ {
+ if (pbp > peekbuf)
+ c = *--pbp;
+ else if ((c = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT))
+ == SM_IO_EOF)
+ break;
+ if (bitset(MCIF_7BIT, mci->mci_flags))
+ c &= 0x7f;
+ switch (ostate)
+ {
+ case OSTATE_HEAD:
+ if (c == '\0' &&
+ bitnset(M_NONULLS,
+ mci->mci_mailer->m_flags))
+ break;
+ if (c != '\r' && c != '\n' && bp < buflim)
+ {
+ *bp++ = c;
+ break;
+ }
+
+ /* check beginning of line for special cases */
+ *bp = '\0';
+ pos = 0;
+ padc = SM_IO_EOF;
+ if (buf[0] == 'F' &&
+ bitnset(M_ESCFROM, mci->mci_mailer->m_flags)
+ && strncmp(buf, "From ", 5) == 0)
+ {
+ padc = '>';
+ }
+ if (buf[0] == '-' && buf[1] == '-' &&
+ separator != NULL)
+ {
+ /* possible separator */
+ int sl = strlen(separator);
+
+ if (strncmp(&buf[2], separator, sl)
+ == 0)
+ padc = ' ';
+ }
+ if (buf[0] == '.' &&
+ bitnset(M_XDOT, mci->mci_mailer->m_flags))
+ {
+ padc = '.';
+ }
+
+ /* now copy out saved line */
+ if (TrafficLogFile != NULL)
+ {
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d >>> ",
+ (int) CurrentPid);
+ if (padc != SM_IO_EOF)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ padc);
+ for (xp = buf; xp < bp; xp++)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) *xp);
+ if (c == '\n')
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ }
+ if (padc != SM_IO_EOF)
+ {
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT, padc)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ pos++;
+ }
+ for (xp = buf; xp < bp; xp++)
+ {
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT,
+ (unsigned char) *xp)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ }
+ if (dead)
+ continue;
+ if (c == '\n')
+ {
+ if (sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ break;
+ pos = 0;
+ }
+ else
+ {
+ pos += bp - buf;
+ if (c != '\r')
+ {
+ SM_ASSERT(pbp < peekbuf +
+ sizeof(peekbuf));
+ *pbp++ = c;
+ }
+ }
+
+ bp = buf;
+
+ /* determine next state */
+ if (c == '\n')
+ ostate = OSTATE_HEAD;
+ else if (c == '\r')
+ ostate = OSTATE_CR;
+ else
+ ostate = OSTATE_INLINE;
+ continue;
+
+ case OSTATE_CR:
+ if (c == '\n')
+ {
+ /* got CRLF */
+ if (sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ continue;
+
+ if (TrafficLogFile != NULL)
+ {
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ }
+ pos = 0;
+ ostate = OSTATE_HEAD;
+ continue;
+ }
+
+ /* had a naked carriage return */
+ SM_ASSERT(pbp < peekbuf + sizeof(peekbuf));
+ *pbp++ = c;
+ c = '\r';
+ ostate = OSTATE_INLINE;
+ goto putch;
+
+ case OSTATE_INLINE:
+ if (c == '\r')
+ {
+ ostate = OSTATE_CR;
+ continue;
+ }
+ if (c == '\0' &&
+ bitnset(M_NONULLS,
+ mci->mci_mailer->m_flags))
+ break;
+putch:
+ if (mci->mci_mailer->m_linelimit > 0 &&
+ pos >= mci->mci_mailer->m_linelimit - 1 &&
+ c != '\n')
+ {
+ int d;
+
+ /* check next character for EOL */
+ if (pbp > peekbuf)
+ d = *(pbp - 1);
+ else if ((d = sm_io_getc(e->e_dfp,
+ SM_TIME_DEFAULT))
+ != SM_IO_EOF)
+ {
+ SM_ASSERT(pbp < peekbuf +
+ sizeof(peekbuf));
+ *pbp++ = d;
+ }
+
+ if (d == '\n' || d == SM_IO_EOF)
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) c);
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT,
+ (unsigned char) c)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ pos++;
+ continue;
+ }
+
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT, '!')
+ == SM_IO_EOF ||
+ sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+
+ if (TrafficLogFile != NULL)
+ {
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "!%s",
+ mci->mci_mailer->m_eol);
+ }
+ ostate = OSTATE_HEAD;
+ SM_ASSERT(pbp < peekbuf +
+ sizeof(peekbuf));
+ *pbp++ = c;
+ continue;
+ }
+ if (c == '\n')
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ if (sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ continue;
+ pos = 0;
+ ostate = OSTATE_HEAD;
+ }
+ else
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) c);
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT,
+ (unsigned char) c)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ pos++;
+ ostate = OSTATE_INLINE;
+ }
+ break;
+ }
+ }
+
+ /* make sure we are at the beginning of a line */
+ if (bp > buf)
+ {
+ if (TrafficLogFile != NULL)
+ {
+ for (xp = buf; xp < bp; xp++)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) *xp);
+ }
+ for (xp = buf; xp < bp; xp++)
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ (unsigned char) *xp)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ }
+ pos += bp - buf;
+ }
+ if (!dead && pos > 0)
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ if (sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol) == SM_IO_EOF)
+ goto writeerr;
+ }
+ }
+
+ if (sm_io_error(e->e_dfp))
+ {
+ syserr("putbody: %s/%cf%s: read error",
+ qid_printqueue(e->e_dfqgrp, e->e_dfqdir),
+ DATAFL_LETTER, e->e_id);
+ ExitStat = EX_IOERR;
+ ioerr = true;
+ }
+
+endofmessage:
+ /*
+ ** Since mailfile() uses e_dfp in a child process,
+ ** the file offset in the stdio library for the
+ ** parent process will not agree with the in-kernel
+ ** file offset since the file descriptor is shared
+ ** between the processes. Therefore, it is vital
+ ** that the file always be rewound. This forces the
+ ** kernel offset (lseek) and stdio library (ftell)
+ ** offset to match.
+ */
+
+ save_errno = errno;
+ if (e->e_dfp != NULL)
+ (void) bfrewind(e->e_dfp);
+
+ /* some mailers want extra blank line at end of message */
+ if (!dead && bitnset(M_BLANKEND, mci->mci_mailer->m_flags) &&
+ buf[0] != '\0' && buf[0] != '\n')
+ {
+ if (!putline("", mci))
+ goto writeerr;
+ }
+
+ if (!dead &&
+ (sm_io_flush(mci->mci_out, SM_TIME_DEFAULT) == SM_IO_EOF ||
+ (sm_io_error(mci->mci_out) && errno != EPIPE)))
+ {
+ save_errno = errno;
+ syserr("putbody: write error");
+ ExitStat = EX_IOERR;
+ ioerr = true;
+ }
+
+ errno = save_errno;
+ return !dead && !ioerr;
+
+ writeerr:
+ return false;
+}
+
+/*
+** MAILFILE -- Send a message to a file.
+**
+** If the file has the set-user-ID/set-group-ID bits set, but NO
+** execute bits, sendmail will try to become the owner of that file
+** rather than the real user. Obviously, this only works if
+** sendmail runs as root.
+**
+** This could be done as a subordinate mailer, except that it
+** is used implicitly to save messages in ~/dead.letter. We
+** view this as being sufficiently important as to include it
+** here. For example, if the system is dying, we shouldn't have
+** to create another process plus some pipes to save the message.
+**
+** Parameters:
+** filename -- the name of the file to send to.
+** mailer -- mailer definition for recipient -- if NULL,
+** use FileMailer.
+** ctladdr -- the controlling address header -- includes
+** the userid/groupid to be when sending.
+** sfflags -- flags for opening.
+** e -- the current envelope.
+**
+** Returns:
+** The exit code associated with the operation.
+**
+** Side Effects:
+** none.
+*/
+
+# define RETURN(st) exit(st);
+
+static jmp_buf CtxMailfileTimeout;
+
+int
+mailfile(filename, mailer, ctladdr, sfflags, e)
+ char *volatile filename;
+ MAILER *volatile mailer;
+ ADDRESS *ctladdr;
+ volatile long sfflags;
+ register ENVELOPE *e;
+{
+ register SM_FILE_T *f;
+ register pid_t pid = -1;
+ volatile int mode;
+ int len;
+ off_t curoff;
+ bool suidwarn = geteuid() == 0;
+ char *p;
+ char *volatile realfile;
+ SM_EVENT *ev;
+ char buf[MAXPATHLEN];
+ char targetfile[MAXPATHLEN];
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("mailfile %s\n ctladdr=", filename);
+ printaddr(sm_debug_file(), ctladdr, false);
+ }
+
+ if (mailer == NULL)
+ mailer = FileMailer;
+
+ if (e->e_xfp != NULL)
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+
+ /*
+ ** Special case /dev/null. This allows us to restrict file
+ ** delivery to regular files only.
+ */
+
+ if (sm_path_isdevnull(filename))
+ return EX_OK;
+
+ /* check for 8-bit available */
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ bitnset(M_7BITS, mailer->m_flags) &&
+ (bitset(EF_DONT_MIME, e->e_flags) ||
+ !(bitset(MM_MIME8BIT, MimeMode) ||
+ (bitset(EF_IS_MIME, e->e_flags) &&
+ bitset(MM_CVTMIME, MimeMode)))))
+ {
+ e->e_status = "5.6.3";
+ usrerrenh(e->e_status,
+ "554 Cannot send 8-bit data to 7-bit destination");
+ errno = 0;
+ return EX_DATAERR;
+ }
+
+ /* Find the actual file */
+ if (SafeFileEnv != NULL && SafeFileEnv[0] != '\0')
+ {
+ len = strlen(SafeFileEnv);
+
+ if (strncmp(SafeFileEnv, filename, len) == 0)
+ filename += len;
+
+ if (len + strlen(filename) + 1 >= sizeof(targetfile))
+ {
+ syserr("mailfile: filename too long (%s/%s)",
+ SafeFileEnv, filename);
+ return EX_CANTCREAT;
+ }
+ (void) sm_strlcpy(targetfile, SafeFileEnv, sizeof(targetfile));
+ realfile = targetfile + len;
+ if (*filename == '/')
+ filename++;
+ if (*filename != '\0')
+ {
+ /* paranoia: trailing / should be removed in readcf */
+ if (targetfile[len - 1] != '/')
+ (void) sm_strlcat(targetfile,
+ "/", sizeof(targetfile));
+ (void) sm_strlcat(targetfile, filename,
+ sizeof(targetfile));
+ }
+ }
+ else if (mailer->m_rootdir != NULL)
+ {
+ expand(mailer->m_rootdir, targetfile, sizeof(targetfile), e);
+ len = strlen(targetfile);
+
+ if (strncmp(targetfile, filename, len) == 0)
+ filename += len;
+
+ if (len + strlen(filename) + 1 >= sizeof(targetfile))
+ {
+ syserr("mailfile: filename too long (%s/%s)",
+ targetfile, filename);
+ return EX_CANTCREAT;
+ }
+ realfile = targetfile + len;
+ if (targetfile[len - 1] != '/')
+ (void) sm_strlcat(targetfile, "/", sizeof(targetfile));
+ if (*filename == '/')
+ (void) sm_strlcat(targetfile, filename + 1,
+ sizeof(targetfile));
+ else
+ (void) sm_strlcat(targetfile, filename,
+ sizeof(targetfile));
+ }
+ else
+ {
+ if (sm_strlcpy(targetfile, filename, sizeof(targetfile)) >=
+ sizeof(targetfile))
+ {
+ syserr("mailfile: filename too long (%s)", filename);
+ return EX_CANTCREAT;
+ }
+ realfile = targetfile;
+ }
+
+ /*
+ ** Fork so we can change permissions here.
+ ** Note that we MUST use fork, not vfork, because of
+ ** the complications of calling subroutines, etc.
+ */
+
+
+ /*
+ ** Dispose of SIGCHLD signal catchers that may be laying
+ ** around so that the waitfor() below will get it.
+ */
+
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+
+ DOFORK(fork);
+
+ if (pid < 0)
+ return EX_OSERR;
+ else if (pid == 0)
+ {
+ /* child -- actually write to file */
+ struct stat stb;
+ MCI mcibuf;
+ int err;
+ volatile int oflags = O_WRONLY|O_APPEND;
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ if (e->e_lockfp != NULL)
+ {
+ int fd;
+
+ fd = sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL);
+ /* SM_ASSERT(fd >= 0); */
+ if (fd >= 0)
+ (void) close(fd);
+ }
+
+ (void) sm_signal(SIGINT, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_DFL);
+ (void) sm_signal(SIGTERM, SIG_DFL);
+ (void) umask(OldUmask);
+ e->e_to = filename;
+ ExitStat = EX_OK;
+
+ if (setjmp(CtxMailfileTimeout) != 0)
+ {
+ RETURN(EX_TEMPFAIL);
+ }
+
+ if (TimeOuts.to_fileopen > 0)
+ ev = sm_setevent(TimeOuts.to_fileopen, mailfiletimeout,
+ 0);
+ else
+ ev = NULL;
+
+ /* check file mode to see if set-user-ID */
+ if (stat(targetfile, &stb) < 0)
+ mode = FileMode;
+ else
+ mode = stb.st_mode;
+
+ /* limit the errors to those actually caused in the child */
+ errno = 0;
+ ExitStat = EX_OK;
+
+ /* Allow alias expansions to use the S_IS{U,G}ID bits */
+ if ((ctladdr != NULL && !bitset(QALIAS, ctladdr->q_flags)) ||
+ bitset(SFF_RUNASREALUID, sfflags))
+ {
+ /* ignore set-user-ID and set-group-ID bits */
+ mode &= ~(S_ISGID|S_ISUID);
+ if (tTd(11, 20))
+ sm_dprintf("mailfile: ignoring set-user-ID/set-group-ID bits\n");
+ }
+
+ /* we have to open the data file BEFORE setuid() */
+ if (e->e_dfp == NULL && bitset(EF_HAS_DF, e->e_flags))
+ {
+ char *df = queuename(e, DATAFL_LETTER);
+
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, df,
+ SM_IO_RDONLY_B, NULL);
+ if (e->e_dfp == NULL)
+ {
+ syserr("mailfile: Cannot open %s for %s from %s",
+ df, e->e_to, e->e_from.q_paddr);
+ }
+ }
+
+ /* select a new user to run as */
+ if (!bitset(SFF_RUNASREALUID, sfflags))
+ {
+ if (bitnset(M_SPECIFIC_UID, mailer->m_flags))
+ {
+ RealUserName = NULL;
+ if (mailer->m_uid == NO_UID)
+ RealUid = RunAsUid;
+ else
+ RealUid = mailer->m_uid;
+ if (RunAsUid != 0 && RealUid != RunAsUid)
+ {
+ /* Only root can change the uid */
+ syserr("mailfile: insufficient privileges to change uid, RunAsUid=%d, RealUid=%d",
+ (int) RunAsUid, (int) RealUid);
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+ else if (bitset(S_ISUID, mode))
+ {
+ RealUserName = NULL;
+ RealUid = stb.st_uid;
+ }
+ else if (ctladdr != NULL && ctladdr->q_uid != 0)
+ {
+ if (ctladdr->q_ruser != NULL)
+ RealUserName = ctladdr->q_ruser;
+ else
+ RealUserName = ctladdr->q_user;
+ RealUid = ctladdr->q_uid;
+ }
+ else if (mailer != NULL && mailer->m_uid != NO_UID)
+ {
+ RealUserName = DefUser;
+ RealUid = mailer->m_uid;
+ }
+ else
+ {
+ RealUserName = DefUser;
+ RealUid = DefUid;
+ }
+
+ /* select a new group to run as */
+ if (bitnset(M_SPECIFIC_UID, mailer->m_flags))
+ {
+ if (mailer->m_gid == NO_GID)
+ RealGid = RunAsGid;
+ else
+ RealGid = mailer->m_gid;
+ if (RunAsUid != 0 &&
+ (RealGid != getgid() ||
+ RealGid != getegid()))
+ {
+ /* Only root can change the gid */
+ syserr("mailfile: insufficient privileges to change gid, RealGid=%d, RunAsUid=%d, gid=%d, egid=%d",
+ (int) RealGid, (int) RunAsUid,
+ (int) getgid(), (int) getegid());
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+ else if (bitset(S_ISGID, mode))
+ RealGid = stb.st_gid;
+ else if (ctladdr != NULL &&
+ ctladdr->q_uid == DefUid &&
+ ctladdr->q_gid == 0)
+ {
+ /*
+ ** Special case: This means it is an
+ ** alias and we should act as DefaultUser.
+ ** See alias()'s comments.
+ */
+
+ RealGid = DefGid;
+ RealUserName = DefUser;
+ }
+ else if (ctladdr != NULL && ctladdr->q_uid != 0)
+ RealGid = ctladdr->q_gid;
+ else if (mailer != NULL && mailer->m_gid != NO_GID)
+ RealGid = mailer->m_gid;
+ else
+ RealGid = DefGid;
+ }
+
+ /* last ditch */
+ if (!bitset(SFF_ROOTOK, sfflags))
+ {
+ if (RealUid == 0)
+ RealUid = DefUid;
+ if (RealGid == 0)
+ RealGid = DefGid;
+ }
+
+ /* set group id list (needs /etc/group access) */
+ if (RealUserName != NULL && !DontInitGroups)
+ {
+ if (initgroups(RealUserName, RealGid) == -1 && suidwarn)
+ {
+ syserr("mailfile: initgroups(%s, %d) failed",
+ RealUserName, RealGid);
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = RealGid;
+ if (setgroups(1, gidset) == -1 && suidwarn)
+ {
+ syserr("mailfile: setgroups() failed");
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+
+ /*
+ ** If you have a safe environment, go into it.
+ */
+
+ if (realfile != targetfile)
+ {
+ char save;
+
+ save = *realfile;
+ *realfile = '\0';
+ if (tTd(11, 20))
+ sm_dprintf("mailfile: chroot %s\n", targetfile);
+ if (chroot(targetfile) < 0)
+ {
+ syserr("mailfile: Cannot chroot(%s)",
+ targetfile);
+ RETURN(EX_CANTCREAT);
+ }
+ *realfile = save;
+ }
+
+ if (tTd(11, 40))
+ sm_dprintf("mailfile: deliver to %s\n", realfile);
+
+ if (chdir("/") < 0)
+ {
+ syserr("mailfile: cannot chdir(/)");
+ RETURN(EX_CANTCREAT);
+ }
+
+ /* now reset the group and user ids */
+ endpwent();
+ sm_mbdb_terminate();
+ if (setgid(RealGid) < 0 && suidwarn)
+ {
+ syserr("mailfile: setgid(%ld) failed", (long) RealGid);
+ RETURN(EX_TEMPFAIL);
+ }
+ vendor_set_uid(RealUid);
+ if (setuid(RealUid) < 0 && suidwarn)
+ {
+ syserr("mailfile: setuid(%ld) failed", (long) RealUid);
+ RETURN(EX_TEMPFAIL);
+ }
+
+ if (tTd(11, 2))
+ sm_dprintf("mailfile: running as r/euid=%d/%d, r/egid=%d/%d\n",
+ (int) getuid(), (int) geteuid(),
+ (int) getgid(), (int) getegid());
+
+
+ /* move into some "safe" directory */
+ if (mailer->m_execdir != NULL)
+ {
+ char *q;
+
+ for (p = mailer->m_execdir; p != NULL; p = q)
+ {
+ q = strchr(p, ':');
+ if (q != NULL)
+ *q = '\0';
+ expand(p, buf, sizeof(buf), e);
+ if (q != NULL)
+ *q++ = ':';
+ if (tTd(11, 20))
+ sm_dprintf("mailfile: trydir %s\n",
+ buf);
+ if (buf[0] != '\0' && chdir(buf) >= 0)
+ break;
+ }
+ }
+
+ /*
+ ** Recheck the file after we have assumed the ID of the
+ ** delivery user to make sure we can deliver to it as
+ ** that user. This is necessary if sendmail is running
+ ** as root and the file is on an NFS mount which treats
+ ** root as nobody.
+ */
+
+#if HASLSTAT
+ if (bitnset(DBS_FILEDELIVERYTOSYMLINK, DontBlameSendmail))
+ err = stat(realfile, &stb);
+ else
+ err = lstat(realfile, &stb);
+#else /* HASLSTAT */
+ err = stat(realfile, &stb);
+#endif /* HASLSTAT */
+
+ if (err < 0)
+ {
+ stb.st_mode = ST_MODE_NOFILE;
+ mode = FileMode;
+ oflags |= O_CREAT|O_EXCL;
+ }
+ else if (bitset(S_IXUSR|S_IXGRP|S_IXOTH, mode) ||
+ (!bitnset(DBS_FILEDELIVERYTOHARDLINK,
+ DontBlameSendmail) &&
+ stb.st_nlink != 1) ||
+ (realfile != targetfile && !S_ISREG(mode)))
+ exit(EX_CANTCREAT);
+ else
+ mode = stb.st_mode;
+
+ if (!bitnset(DBS_FILEDELIVERYTOSYMLINK, DontBlameSendmail))
+ sfflags |= SFF_NOSLINK;
+ if (!bitnset(DBS_FILEDELIVERYTOHARDLINK, DontBlameSendmail))
+ sfflags |= SFF_NOHLINK;
+ sfflags &= ~SFF_OPENASROOT;
+ f = safefopen(realfile, oflags, mode, sfflags);
+ if (f == NULL)
+ {
+ if (transienterror(errno))
+ {
+ usrerr("454 4.3.0 cannot open %s: %s",
+ shortenstring(realfile, MAXSHORTSTR),
+ sm_errstring(errno));
+ RETURN(EX_TEMPFAIL);
+ }
+ else
+ {
+ usrerr("554 5.3.0 cannot open %s: %s",
+ shortenstring(realfile, MAXSHORTSTR),
+ sm_errstring(errno));
+ RETURN(EX_CANTCREAT);
+ }
+ }
+ if (filechanged(realfile, sm_io_getinfo(f, SM_IO_WHAT_FD, NULL),
+ &stb))
+ {
+ syserr("554 5.3.0 file changed after open");
+ RETURN(EX_CANTCREAT);
+ }
+ if (fstat(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL), &stb) < 0)
+ {
+ syserr("554 5.3.0 cannot fstat %s",
+ sm_errstring(errno));
+ RETURN(EX_CANTCREAT);
+ }
+
+ curoff = stb.st_size;
+
+ if (ev != NULL)
+ sm_clrevent(ev);
+
+ memset(&mcibuf, '\0', sizeof(mcibuf));
+ mcibuf.mci_mailer = mailer;
+ mcibuf.mci_out = f;
+ if (bitnset(M_7BITS, mailer->m_flags))
+ mcibuf.mci_flags |= MCIF_7BIT;
+
+ /* clear out per-message flags from connection structure */
+ mcibuf.mci_flags &= ~(MCIF_CVT7TO8|MCIF_CVT8TO7);
+
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ bitnset(M_7BITS, mailer->m_flags))
+ mcibuf.mci_flags |= MCIF_CVT8TO7;
+
+#if MIME7TO8
+ if (bitnset(M_MAKE8BIT, mailer->m_flags) &&
+ !bitset(MCIF_7BIT, mcibuf.mci_flags) &&
+ (p = hvalue("Content-Transfer-Encoding", e->e_header)) != NULL &&
+ (sm_strcasecmp(p, "quoted-printable") == 0 ||
+ sm_strcasecmp(p, "base64") == 0) &&
+ (p = hvalue("Content-Type", e->e_header)) != NULL)
+ {
+ /* may want to convert 7 -> 8 */
+ /* XXX should really parse it here -- and use a class XXX */
+ if (sm_strncasecmp(p, "text/plain", 10) == 0 &&
+ (p[10] == '\0' || p[10] == ' ' || p[10] == ';'))
+ mcibuf.mci_flags |= MCIF_CVT7TO8;
+ }
+#endif /* MIME7TO8 */
+
+ if (!putfromline(&mcibuf, e) ||
+ !(*e->e_puthdr)(&mcibuf, e->e_header, e, M87F_OUTER) ||
+ !(*e->e_putbody)(&mcibuf, e, NULL) ||
+ !putline("\n", &mcibuf) ||
+ (sm_io_flush(f, SM_TIME_DEFAULT) != 0 ||
+ (SuperSafe != SAFE_NO &&
+ fsync(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL)) < 0) ||
+ sm_io_error(f)))
+ {
+ setstat(EX_IOERR);
+#if !NOFTRUNCATE
+ (void) ftruncate(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL),
+ curoff);
+#endif /* !NOFTRUNCATE */
+ }
+
+ /* reset ISUID & ISGID bits for paranoid systems */
+#if HASFCHMOD
+ (void) fchmod(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL),
+ (MODE_T) mode);
+#else /* HASFCHMOD */
+ (void) chmod(filename, (MODE_T) mode);
+#endif /* HASFCHMOD */
+ if (sm_io_close(f, SM_TIME_DEFAULT) < 0)
+ setstat(EX_IOERR);
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ (void) setuid(RealUid);
+ exit(ExitStat);
+ /* NOTREACHED */
+ }
+ else
+ {
+ /* parent -- wait for exit status */
+ int st;
+
+ st = waitfor(pid);
+ if (st == -1)
+ {
+ syserr("mailfile: %s: wait", mailer->m_name);
+ return EX_SOFTWARE;
+ }
+ if (WIFEXITED(st))
+ {
+ errno = 0;
+ return (WEXITSTATUS(st));
+ }
+ else
+ {
+ syserr("mailfile: %s: child died on signal %d",
+ mailer->m_name, st);
+ return EX_UNAVAILABLE;
+ }
+ /* NOTREACHED */
+ }
+ return EX_UNAVAILABLE; /* avoid compiler warning on IRIX */
+}
+
+static void
+mailfiletimeout(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(CtxMailfileTimeout, 1);
+}
+/*
+** HOSTSIGNATURE -- return the "signature" for a host.
+**
+** The signature describes how we are going to send this -- it
+** can be just the hostname (for non-Internet hosts) or can be
+** an ordered list of MX hosts.
+**
+** Parameters:
+** m -- the mailer describing this host.
+** host -- the host name.
+**
+** Returns:
+** The signature for this host.
+**
+** Side Effects:
+** Can tweak the symbol table.
+*/
+
+#define MAXHOSTSIGNATURE 8192 /* max len of hostsignature */
+
+char *
+hostsignature(m, host)
+ register MAILER *m;
+ char *host;
+{
+ register char *p;
+ register STAB *s;
+ time_t now;
+#if NAMED_BIND
+ char sep = ':';
+ char prevsep = ':';
+ int i;
+ int len;
+ int nmx;
+ int hl;
+ char *hp;
+ char *endp;
+ int oldoptions = _res.options;
+ char *mxhosts[MAXMXHOSTS + 1];
+ unsigned short mxprefs[MAXMXHOSTS + 1];
+#endif /* NAMED_BIND */
+
+ if (tTd(17, 3))
+ sm_dprintf("hostsignature(%s)\n", host);
+
+ /*
+ ** If local delivery (and not remote), just return a constant.
+ */
+
+ if (bitnset(M_LOCALMAILER, m->m_flags) &&
+ strcmp(m->m_mailer, "[IPC]") != 0 &&
+ !(m->m_argv[0] != NULL && strcmp(m->m_argv[0], "TCP") == 0))
+ return "localhost";
+
+ /* an empty host does not have MX records */
+ if (*host == '\0')
+ return "_empty_";
+
+ /*
+ ** Check to see if this uses IPC -- if not, it can't have MX records.
+ */
+
+ if (strcmp(m->m_mailer, "[IPC]") != 0 ||
+ CurEnv->e_sendmode == SM_DEFER)
+ {
+ /* just an ordinary mailer or deferred mode */
+ return host;
+ }
+#if NETUNIX
+ else if (m->m_argv[0] != NULL &&
+ strcmp(m->m_argv[0], "FILE") == 0)
+ {
+ /* rendezvous in the file system, no MX records */
+ return host;
+ }
+#endif /* NETUNIX */
+
+ /*
+ ** Look it up in the symbol table.
+ */
+
+ now = curtime();
+ s = stab(host, ST_HOSTSIG, ST_ENTER);
+ if (s->s_hostsig.hs_sig != NULL)
+ {
+ if (s->s_hostsig.hs_exp >= now)
+ {
+ if (tTd(17, 3))
+ sm_dprintf("hostsignature(): stab(%s) found %s\n", host,
+ s->s_hostsig.hs_sig);
+ return s->s_hostsig.hs_sig;
+ }
+
+ /* signature is expired: clear it */
+ sm_free(s->s_hostsig.hs_sig);
+ s->s_hostsig.hs_sig = NULL;
+ }
+
+ /* set default TTL */
+ s->s_hostsig.hs_exp = now + SM_DEFAULT_TTL;
+
+ /*
+ ** Not already there or expired -- create a signature.
+ */
+
+#if NAMED_BIND
+ if (ConfigLevel < 2)
+ _res.options &= ~(RES_DEFNAMES | RES_DNSRCH); /* XXX */
+
+ for (hp = host; hp != NULL; hp = endp)
+ {
+#if NETINET6
+ if (*hp == '[')
+ {
+ endp = strchr(hp + 1, ']');
+ if (endp != NULL)
+ endp = strpbrk(endp + 1, ":,");
+ }
+ else
+ endp = strpbrk(hp, ":,");
+#else /* NETINET6 */
+ endp = strpbrk(hp, ":,");
+#endif /* NETINET6 */
+ if (endp != NULL)
+ {
+ sep = *endp;
+ *endp = '\0';
+ }
+
+ if (bitnset(M_NOMX, m->m_flags))
+ {
+ /* skip MX lookups */
+ nmx = 1;
+ mxhosts[0] = hp;
+ }
+ else
+ {
+ auto int rcode;
+ int ttl;
+
+ nmx = getmxrr(hp, mxhosts, mxprefs, true, &rcode, true,
+ &ttl);
+ if (nmx <= 0)
+ {
+ int save_errno;
+ register MCI *mci;
+
+ /* update the connection info for this host */
+ save_errno = errno;
+ mci = mci_get(hp, m);
+ mci->mci_errno = save_errno;
+ mci->mci_herrno = h_errno;
+ mci->mci_lastuse = now;
+ if (rcode == EX_NOHOST)
+ mci_setstat(mci, rcode, "5.1.2",
+ "550 Host unknown");
+ else
+ mci_setstat(mci, rcode, NULL, NULL);
+
+ /* use the original host name as signature */
+ nmx = 1;
+ mxhosts[0] = hp;
+ }
+ if (tTd(17, 3))
+ sm_dprintf("hostsignature(): getmxrr() returned %d, mxhosts[0]=%s\n",
+ nmx, mxhosts[0]);
+
+ /*
+ ** Set new TTL: we use only one!
+ ** We could try to use the minimum instead.
+ */
+
+ s->s_hostsig.hs_exp = now + SM_MIN(ttl, SM_DEFAULT_TTL);
+ }
+
+ len = 0;
+ for (i = 0; i < nmx; i++)
+ len += strlen(mxhosts[i]) + 1;
+ if (s->s_hostsig.hs_sig != NULL)
+ len += strlen(s->s_hostsig.hs_sig) + 1;
+ if (len < 0 || len >= MAXHOSTSIGNATURE)
+ {
+ sm_syslog(LOG_WARNING, NOQID, "hostsignature for host '%s' exceeds maxlen (%d): %d",
+ host, MAXHOSTSIGNATURE, len);
+ len = MAXHOSTSIGNATURE;
+ }
+ p = sm_pmalloc_x(len);
+ if (s->s_hostsig.hs_sig != NULL)
+ {
+ (void) sm_strlcpy(p, s->s_hostsig.hs_sig, len);
+ sm_free(s->s_hostsig.hs_sig); /* XXX */
+ s->s_hostsig.hs_sig = p;
+ hl = strlen(p);
+ p += hl;
+ *p++ = prevsep;
+ len -= hl + 1;
+ }
+ else
+ s->s_hostsig.hs_sig = p;
+ for (i = 0; i < nmx; i++)
+ {
+ hl = strlen(mxhosts[i]);
+ if (len - 1 < hl || len <= 1)
+ {
+ /* force to drop out of outer loop */
+ len = -1;
+ break;
+ }
+ if (i != 0)
+ {
+ if (mxprefs[i] == mxprefs[i - 1])
+ *p++ = ',';
+ else
+ *p++ = ':';
+ len--;
+ }
+ (void) sm_strlcpy(p, mxhosts[i], len);
+ p += hl;
+ len -= hl;
+ }
+
+ /*
+ ** break out of loop if len exceeded MAXHOSTSIGNATURE
+ ** because we won't have more space for further hosts
+ ** anyway (separated by : in the .cf file).
+ */
+
+ if (len < 0)
+ break;
+ if (endp != NULL)
+ *endp++ = sep;
+ prevsep = sep;
+ }
+ makelower(s->s_hostsig.hs_sig);
+ if (ConfigLevel < 2)
+ _res.options = oldoptions;
+#else /* NAMED_BIND */
+ /* not using BIND -- the signature is just the host name */
+ /*
+ ** 'host' points to storage that will be freed after we are
+ ** done processing the current envelope, so we copy it.
+ */
+ s->s_hostsig.hs_sig = sm_pstrdup_x(host);
+#endif /* NAMED_BIND */
+ if (tTd(17, 1))
+ sm_dprintf("hostsignature(%s) = %s\n", host, s->s_hostsig.hs_sig);
+ return s->s_hostsig.hs_sig;
+}
+/*
+** PARSE_HOSTSIGNATURE -- parse the "signature" and return MX host array.
+**
+** The signature describes how we are going to send this -- it
+** can be just the hostname (for non-Internet hosts) or can be
+** an ordered list of MX hosts which must be randomized for equal
+** MX preference values.
+**
+** Parameters:
+** sig -- the host signature.
+** mxhosts -- array to populate.
+** mailer -- mailer.
+**
+** Returns:
+** The number of hosts inserted into mxhosts array.
+**
+** Side Effects:
+** Randomizes equal MX preference hosts in mxhosts.
+*/
+
+static int
+parse_hostsignature(sig, mxhosts, mailer)
+ char *sig;
+ char **mxhosts;
+ MAILER *mailer;
+{
+ unsigned short curpref = 0;
+ int nmx = 0, i, j; /* NOTE: i, j, and nmx must have same type */
+ char *hp, *endp;
+ unsigned short prefer[MAXMXHOSTS];
+ long rndm[MAXMXHOSTS];
+
+ for (hp = sig; hp != NULL; hp = endp)
+ {
+ char sep = ':';
+
+#if NETINET6
+ if (*hp == '[')
+ {
+ endp = strchr(hp + 1, ']');
+ if (endp != NULL)
+ endp = strpbrk(endp + 1, ":,");
+ }
+ else
+ endp = strpbrk(hp, ":,");
+#else /* NETINET6 */
+ endp = strpbrk(hp, ":,");
+#endif /* NETINET6 */
+ if (endp != NULL)
+ {
+ sep = *endp;
+ *endp = '\0';
+ }
+
+ mxhosts[nmx] = hp;
+ prefer[nmx] = curpref;
+ if (mci_match(hp, mailer))
+ rndm[nmx] = 0;
+ else
+ rndm[nmx] = get_random();
+
+ if (endp != NULL)
+ {
+ /*
+ ** Since we don't have the original MX prefs,
+ ** make our own. If the separator is a ':', that
+ ** means the preference for the next host will be
+ ** higher than this one, so simply increment curpref.
+ */
+
+ if (sep == ':')
+ curpref++;
+
+ *endp++ = sep;
+ }
+ if (++nmx >= MAXMXHOSTS)
+ break;
+ }
+
+ /* sort the records using the random factor for equal preferences */
+ for (i = 0; i < nmx; i++)
+ {
+ for (j = i + 1; j < nmx; j++)
+ {
+ /*
+ ** List is already sorted by MX preference, only
+ ** need to look for equal preference MX records
+ */
+
+ if (prefer[i] < prefer[j])
+ break;
+
+ if (prefer[i] > prefer[j] ||
+ (prefer[i] == prefer[j] && rndm[i] > rndm[j]))
+ {
+ register unsigned short tempp;
+ register long tempr;
+ register char *temp1;
+
+ tempp = prefer[i];
+ prefer[i] = prefer[j];
+ prefer[j] = tempp;
+ temp1 = mxhosts[i];
+ mxhosts[i] = mxhosts[j];
+ mxhosts[j] = temp1;
+ tempr = rndm[i];
+ rndm[i] = rndm[j];
+ rndm[j] = tempr;
+ }
+ }
+ }
+ return nmx;
+}
+
+# if STARTTLS
+static SSL_CTX *clt_ctx = NULL;
+static bool tls_ok_clt = true;
+
+/*
+** SETCLTTLS -- client side TLS: allow/disallow.
+**
+** Parameters:
+** tls_ok -- should tls be done?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets tls_ok_clt (static variable in this module)
+*/
+
+void
+setclttls(tls_ok)
+ bool tls_ok;
+{
+ tls_ok_clt = tls_ok;
+ return;
+}
+/*
+** INITCLTTLS -- initialize client side TLS
+**
+** Parameters:
+** tls_ok -- should tls initialization be done?
+**
+** Returns:
+** succeeded?
+**
+** Side Effects:
+** sets tls_ok_clt (static variable in this module)
+*/
+
+bool
+initclttls(tls_ok)
+ bool tls_ok;
+{
+ if (!tls_ok_clt)
+ return false;
+ tls_ok_clt = tls_ok;
+ if (!tls_ok_clt)
+ return false;
+ if (clt_ctx != NULL)
+ return true; /* already done */
+ tls_ok_clt = inittls(&clt_ctx, TLS_I_CLT, Clt_SSL_Options, false,
+ CltCertFile, CltKeyFile,
+ CACertPath, CACertFile, DHParams);
+ return tls_ok_clt;
+}
+
+/*
+** STARTTLS -- try to start secure connection (client side)
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+**
+** Returns:
+** success?
+** (maybe this should be some other code than EX_
+** that denotes which stage failed.)
+*/
+
+static int
+starttls(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int smtpresult;
+ int result = 0;
+ int rfd, wfd;
+ SSL *clt_ssl = NULL;
+ time_t tlsstart;
+
+ if (clt_ctx == NULL && !initclttls(true))
+ return EX_TEMPFAIL;
+
+# if USE_OPENSSL_ENGINE
+ if (!SSLEngineInitialized && !SSL_set_engine(NULL))
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=client, SSL_set_engine=failed");
+ return EX_TEMPFAIL;
+ }
+ SSLEngineInitialized = true;
+# endif /* USE_OPENSSL_ENGINE */
+
+ smtpmessage("STARTTLS", m, mci);
+
+ /* get the reply */
+ smtpresult = reply(m, mci, e, TimeOuts.to_starttls, NULL, NULL,
+ XS_STARTTLS);
+
+ /* check return code from server */
+ if (REPLYTYPE(smtpresult) == 4)
+ return EX_TEMPFAIL;
+ if (smtpresult == 501)
+ return EX_USAGE;
+ if (smtpresult == -1)
+ return smtpresult;
+
+ /* not an expected reply but we have to deal with it */
+ if (REPLYTYPE(smtpresult) == 5)
+ return EX_UNAVAILABLE;
+ if (smtpresult != 220)
+ return EX_PROTOCOL;
+
+ if (LogLevel > 13)
+ sm_syslog(LOG_INFO, NOQID, "STARTTLS=client, start=ok");
+
+ /* start connection */
+ if ((clt_ssl = SSL_new(clt_ctx)) == NULL)
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=client, error: SSL_new failed");
+ if (LogLevel > 9)
+ tlslogerr("client");
+ }
+ return EX_SOFTWARE;
+ }
+
+ rfd = sm_io_getinfo(mci->mci_in, SM_IO_WHAT_FD, NULL);
+ wfd = sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD, NULL);
+
+ /* SSL_clear(clt_ssl); ? */
+ if (rfd < 0 || wfd < 0 ||
+ (result = SSL_set_rfd(clt_ssl, rfd)) != 1 ||
+ (result = SSL_set_wfd(clt_ssl, wfd)) != 1)
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=client, error: SSL_set_xfd failed=%d",
+ result);
+ if (LogLevel > 9)
+ tlslogerr("client");
+ }
+ return EX_SOFTWARE;
+ }
+ SSL_set_connect_state(clt_ssl);
+ tlsstart = curtime();
+
+ssl_retry:
+ if ((result = SSL_connect(clt_ssl)) <= 0)
+ {
+ int i, ssl_err;
+
+ ssl_err = SSL_get_error(clt_ssl, result);
+ i = tls_retry(clt_ssl, rfd, wfd, tlsstart,
+ TimeOuts.to_starttls, ssl_err, "client");
+ if (i > 0)
+ goto ssl_retry;
+
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=client, error: connect failed=%d, SSL_error=%d, errno=%d, retry=%d",
+ result, ssl_err, errno, i);
+ if (LogLevel > 8)
+ tlslogerr("client");
+ }
+
+ SSL_free(clt_ssl);
+ clt_ssl = NULL;
+ return EX_SOFTWARE;
+ }
+ mci->mci_ssl = clt_ssl;
+ result = tls_get_info(mci->mci_ssl, false, mci->mci_host,
+ &mci->mci_macro, true);
+
+ /* switch to use TLS... */
+ if (sfdctls(&mci->mci_in, &mci->mci_out, mci->mci_ssl) == 0)
+ return EX_OK;
+
+ /* failure */
+ SSL_free(clt_ssl);
+ clt_ssl = NULL;
+ return EX_SOFTWARE;
+}
+/*
+** ENDTLSCLT -- shutdown secure connection (client side)
+**
+** Parameters:
+** mci -- the mailer connection info.
+**
+** Returns:
+** success?
+*/
+
+static int
+endtlsclt(mci)
+ MCI *mci;
+{
+ int r;
+
+ if (!bitset(MCIF_TLSACT, mci->mci_flags))
+ return EX_OK;
+ r = endtls(mci->mci_ssl, "client");
+ mci->mci_flags &= ~MCIF_TLSACT;
+ return r;
+}
+# endif /* STARTTLS */
+# if STARTTLS || SASL
+/*
+** ISCLTFLGSET -- check whether client flag is set.
+**
+** Parameters:
+** e -- envelope.
+** flag -- flag to check in {client_flags}
+**
+** Returns:
+** true iff flag is set.
+*/
+
+static bool
+iscltflgset(e, flag)
+ ENVELOPE *e;
+ int flag;
+{
+ char *p;
+
+ p = macvalue(macid("{client_flags}"), e);
+ if (p == NULL)
+ return false;
+ for (; *p != '\0'; p++)
+ {
+ /* look for just this one flag */
+ if (*p == (char) flag)
+ return true;
+ }
+ return false;
+}
+# endif /* STARTTLS || SASL */
OpenPOWER on IntegriCloud