deliver.c revision e9af4bc0b1cc30cea75d6ad4aa2fde97d985e9be
/*
* Copyright (c) 1998-2008 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.1020 2009/12/18 17:08:01 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 wierd */
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;
# 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->mci_flags &= ~MCIF_EXTENS;
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->mci_flags &= ~MCIF_EXTENS;
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 (!SSL_set_engine(NULL))
{
sm_syslog(LOG_ERR, NOQID,
"STARTTLS=client, SSL_set_engine=failed");
return EX_TEMPFAIL;
}
# 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 */