/*
* 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 <login_cap.h>
#endif /* HASSETUSERCONTEXT */
#endif /* NETINET || NETINET6 */
# include "sfsasl.h"
#endif /* STARTTLS || SASL */
static void mailfiletimeout __P((int));
static void endwaittimeout __P((int));
#if STARTTLS
#endif /* STARTTLS */
# 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
ENVELOPE *e;
int mode;
{
register ADDRESS *q;
char *owner;
int otherowners;
int save_errno;
/*
** If this message is to be discarded, don't bother sending
** the message at all.
*/
{
e->e_flags |= EF_CLRQUEUE;
if (LogLevel > 9)
else if (LogLevel > 4)
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.
*/
{
e->e_flags |= EF_CLRQUEUE;
return;
}
/* determine actual delivery mode */
if (mode == SM_DEFAULT)
{
mode = e->e_sendmode;
}
{
sm_dprintf("\n===== SENDALL: mode %c, id %s, e_from ",
sm_dprintf("\te_flags = ");
printenvflags(e);
sm_dprintf("sendqueue:\n");
}
/*
** 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 (e->e_hopcount > MaxHopCount)
{
char *recip;
if (e->e_sendqueue != NULL &&
else
recip = "(nobody)";
errno = 0;
syserr("554 5.4.6 Too many hops %d (%d max): from %s via %s, to %s",
recip);
{
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.
*/
{
{
sm_dprintf("sendall: QS_SENDER ");
}
}
/*
** Handle alias owners.
**
** We scan up the q_alias chain looking for owners.
** We discard owners that are the same as the return path.
*/
{
register struct address *a;
continue;
if (a != NULL)
!QS_IS_DEAD(q->q_state) &&
}
{
sm_dprintf("\nAfter first owner pass, sendq =\n");
}
owner = "";
otherowners = 1;
{
sm_dprintf("owner = \"%s\", otherowners = %d\n",
owner, otherowners);
{
{
sm_dprintf("Checking ");
printaddr(sm_debug_file(), q, false);
}
if (QS_IS_DEAD(q->q_state))
{
sm_dprintf(" ... QS_IS_DEAD\n");
continue;
}
{
sm_dprintf("Checking ");
printaddr(sm_debug_file(), q, false);
}
{
{
sm_dprintf(" ... First owner = \"%s\"\n",
q->q_owner);
}
{
{
sm_dprintf(" ... Same owner = \"%s\"\n",
owner);
/* make future comparisons cheap */
}
else
{
sm_dprintf(" ... Another owner \"%s\"\n",
q->q_owner);
otherowners++;
}
}
sm_dprintf(" ... Same owner = \"%s\"\n",
owner);
}
else
{
sm_dprintf(" ... Null owner\n");
otherowners++;
}
if (QS_IS_BADADDR(q->q_state))
{
sm_dprintf(" ... QS_IS_BADADDR\n");
continue;
}
if (QS_IS_QUEUEUP(q->q_state))
{
/*
** 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 &&
{
int len;
char *p;
sm_dprintf(" ... FallbackMX\n");
q->q_host = p;
}
else
{
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.
*/
{
sm_dprintf(" ... expensive\n");
q->q_state = QS_QUEUEUP;
expensive = true;
}
QueueLimitId == NULL &&
QueueLimitSender == NULL &&
{
sm_dprintf(" ... hold\n");
q->q_state = QS_QUEUEUP;
expensive = true;
}
else if (QueueMode != QM_QUARANTINE &&
{
sm_dprintf(" ... quarantine: %s\n",
e->e_quarmsg);
q->q_state = QS_QUEUEUP;
expensive = true;
}
else
{
sm_dprintf(" ... deliverable\n");
somedeliveries = true;
}
}
{
/*
** Split this envelope into two.
*/
sizeof(*ee));
STRUCTCOPY(*e, *ee);
sm_dprintf("sendall: split %s into %s, owner = \"%s\", otherowners = %d\n",
{
sm_dprintf("sendall(split): QS_SENDER ");
}
e->e_quarmsg);
{
{
sm_dprintf("\t... stripping %s from original envelope\n",
q->q_paddr);
}
}
{
{
sm_dprintf("\t... dropping %s from cloned envelope\n",
q->q_paddr);
}
else
{
/* clear DSN parameters */
sm_dprintf("\t... moving %s to cloned envelope\n",
q->q_paddr);
}
}
/*
** Give the split envelope access to the parent
** transcript file for errors obtained while
** processing the recipients (done before the
** envelope splitting).
*/
/* failed to dup e->e_xfp, start a new transcript */
"%s: clone: owner=%s",
}
}
{
{
sm_dprintf("sendall(owner): 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 */
{
sm_dprintf("No deliveries: auto-queueing\n");
/* treat this as a delivery in terms of counting tries */
if (!expensive)
e->e_ntries++;
{
if (!expensive)
}
}
(SuperSafe == SAFE_REALLY ||
SuperSafe == SAFE_REALLY_POSTMILTER))) &&
{
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 */
#endif /* !HASFLOCK */
}
checkfds("after envelope splitting");
/*
** If we belong in background, fork now.
*/
{
{
sm_dprintf("\n================ Final Send Queue(s) =====================\n");
sm_dprintf("\n *** Envelope %s, e_from=%s ***\n",
{
sm_dprintf("\n *** Envelope %s, e_from=%s ***\n",
}
sm_dprintf("==========================================================\n\n");
}
}
switch (mode)
{
case SM_VERIFY:
Verbose = 2;
break;
case SM_QUEUE:
case SM_DEFER:
#if HASFLOCK
#endif /* HASFLOCK */
if (e->e_nrcpts > 0)
e->e_flags |= EF_INQUEUE;
{
(void) dropenvelope(ee, false, true);
}
return;
case SM_FORK:
#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 */
/* now drop the envelope in the parent */
e->e_flags |= EF_INQUEUE;
/* arrange to reacquire lock after fork */
}
{
/* save id for future use */
/* drop envelope in parent */
(void) dropenvelope(ee, false, false);
/* and save qid for reacquisition */
}
#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);
if (pid < 0)
{
syserr("deliver: fork 1");
#if HASFLOCK
goto queueonly;
#else /* HASFLOCK */
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);
/* can't call unlockqueue to avoid unlink of xfp */
else
#endif /* HASFLOCK */
/* make sure the parent doesn't own the envelope */
#if USE_DOUBLE_FORK
/* catch intermediate zombie */
#endif /* USE_DOUBLE_FORK */
return;
}
/* Reset global flags */
RestartWorkGroup = false;
PendingSignal = 0;
/*
** Initialize exception stack and default exception
** handler for child process.
*/
/*
** 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.
*/
#if USE_DOUBLE_FORK
/* double fork to avoid zombies */
if (pid > 0)
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 */
#endif /* HASFLOCK */
}
/* 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.
*/
#if HASFLOCK
break;
#else /* HASFLOCK */
/*
** Now reacquire and run the various queue files.
*/
{
false, false, ee);
}
false, false, e);
#endif /* HASFLOCK */
}
sendenvelope(e, mode);
(void) dropenvelope(e, true, true);
{
(void) dropenvelope(ee, true, true);
}
CurEnv = e;
}
static void
register ENVELOPE *e;
int mode;
{
register ADDRESS *q;
bool didany;
sm_dprintf("sendenvelope(%s) e_flags=0x%lx\n",
e->e_flags);
if (LogLevel > 80)
"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.
*/
{
e->e_flags |= EF_CLRQUEUE;
return;
}
/*
** Don't attempt deliveries if we want to bounce now
** or if deliver-by time is exceeded.
*/
(IS_DLVR_RETURN(e) && e->e_deliver_by > 0 &&
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;
didany = false;
{
/*
** 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).
*/
if (!split_by_recipient(e) &&
{
e->e_flags |= EF_CLRQUEUE;
return;
}
/* clean up */
{
/* now unlock the job */
/* this envelope is marked unused */
{
}
}
}
/* now run through the queue */
{
#if XDEBUG
#endif /* XDEBUG */
{
if (QS_IS_SENDABLE(q->q_state))
{
message("deliverable: mailer %s, host %s, user %s",
q->q_host,
q->q_user);
else
message("deliverable: mailer %s, user %s",
q->q_user);
}
}
{
/*
** 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_ntries++;
}
#if XDEBUG
checkfd012("end of sendenvelope");
#endif /* XDEBUG */
}
/*
** SYNC_DIR -- fsync a directory based on a filename
**
** Parameters:
** filename -- path of file
** panic -- panic?
**
** Returns:
** none
*/
void
char *filename;
bool panic;
{
int dirfd;
char *dirp;
if (!RequiresDirfsync)
return;
/* filesystems which require the directory be synced */
{
return;
}
else
dirp = ".";
if (dirfd >= 0)
{
{
if (panic)
syserr("!sync_dir: cannot fsync directory %s",
dirp);
else if (LogLevel > 1)
"sync_dir: cannot fsync directory %s: %s",
}
}
}
#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
int type;
{
/*
** Make sure both are in the same directory.
*/
/* Force the df to disk if it's not there yet */
{
/* NOTREACHED */
}
{
if (save_errno == EEXIST)
{
{
syserr("!sendall: unlink(%s): permanent",
f2buf);
/* NOTREACHED */
}
{
syserr("!sendall: link(%s, %s): permanent",
/* NOTREACHED */
}
}
}
}
/*
** 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.....
*/
#ifndef FORK
#endif /* ! FORK */
{\
register int i;\
\
for (i = NFORKTRIES; --i >= 0; )\
{\
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.
*/
dofork()
{
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
static int
coloncmp(a, b)
register const char *a;
register const char *b;
{
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)
{
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;
return HS_MATCH_FULL;
return ret;
}
/*
** SHOULD_TRY_FBSH -- Should try FallbackSmartHost?
**
** Parameters:
** e -- envelope
** hostbuf -- buffer for hostname (expand FallbackSmartHost) (out)
** hbsz -- size of hostbuf
** status -- current delivery status
**
** Returns:
** true iff FallbackSmartHost should be tried.
*/
static bool
ENVELOPE *e;
bool *tried_fallbacksmarthost;
char *hostbuf;
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.
*/
{
*tried_fallbacksmarthost = true;
{
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
register ENVELOPE *e;
{
char **pvp;
register char **mvp;
register char *p;
register MAILER *m; /* mailer for this recipient */
#endif /* HASSETUSERCONTEXT */
char *volatile curhost;
#if NETUNIX
#endif /* NETUNIX */
bool suidwarn;
bool ovr;
bool quarantine;
int strsize;
int rcptcount;
int ret;
static int tobufsize = 0;
errno = 0;
return 0;
SM_REQUIRE(e != NULL);
CurEnv = e; /* just in case */
SmtpError[0] = '\0';
sm_dprintf("\n--deliver, id=%s, mailer=%s, host=`%s', first user=`%s'\n",
printopenfds(false);
/*
** Clear {client_*} macros if this is a bounce message to
** prevent rejection by check_compat ruleset.
*/
{
}
{
/*
** 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 */
p = e->e_sender;
else
{
/* avoid bogus errno */
errno = 0;
}
Errors = 0;
/* ignore long term host status information if mailer flag W is set */
IgnoreHostStatus = true;
/* insert -f or -r flag as appropriate */
if (FromFlag &&
{
*pvp++ = "-f";
else
*pvp++ = "-r";
}
/*
** 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.
*/
{
/* 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 */
{
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!!
*/
{
/* running LMTP or SMTP */
clever = true;
}
{
/* not running LMTP */
"Warning: mailer %s: LMTP flag (F=z) turned off",
m->m_name);
}
/*
** 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.
*/
strsize = 2;
rcptcount = 0;
{
/* avoid sending multiple recipients to dumb mailers */
break;
/* if already sent or not for this host, don't send */
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;
*/
continue;
/*
** 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.
*/
if (ret == HS_MATCH_FULL)
else if (ret == HS_MATCH_NO)
break;
if (!clever)
{
/* avoid overflowing tobuf */
break;
}
break;
{
sm_dprintf("\nsend to ");
}
# if HASSETUSERCONTEXT
# else /* HASSETUSERCONTEXT */
# endif /* HASSETUSERCONTEXT */
{
sm_dprintf("ctladdr=");
}
/*
** 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_flags |= EF_NO_BODY_RETN;
else
/* set to->q_rstatus = NULL; or to the following? */
"552 Message is too large; %ld bytes max",
m->m_maxsize);
continue;
}
SM_SET_H_ERRNO(0);
ovr = true;
/* do config file checking of compatibility */
{
/* do in-code checking if not discarding */
{
ovr = false;
}
}
{
continue;
}
{
/*
** check_compat or checkcompat() has tried
** to quarantine but that isn't supported.
** Revert the attempt.
*/
}
{
{
sm_dprintf("deliver: discarding recipient ");
}
/* pretend the message was sent */
/* XXX should we log something here? */
/*
** 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.
*/
{
}
/*
** Strip all leading backslashes if requested and the
** next character is alphanumerical (the latter can
** probably relaxed a bit, see RFC2821).
*/
/* hack attack -- delivermail compatibility */
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.
*/
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.
*/
{
else
{
syserr("empty filename specification for mailer %s",
m->m_name);
}
e->e_nsent++;
{
{
(void) sm_io_fprintf(e->e_xfp,
"%s... Successfully delivered\n",
}
}
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 */
e->e_to = "[CHAIN]";
/* set the ${dsn_notify} macro if applicable */
{
notify[0] = '\0';
sizeof(notify));
sizeof(notify));
sizeof(notify));
/* Set to NEVER or drop trailing comma */
if (notify[0] == '\0')
sizeof(notify));
else
}
else
/*
** Expand out this user into argument list.
*/
if (!clever)
{
{
/* allow some space for trailing parms */
break;
}
}
}
/* see if any addresses still exist */
{
rcode = 0;
goto cleanup;
}
/* print out messages as full list */
strsize = 1;
{
}
p = tobuf;
*p = '\0';
{
p += strlen(p);
}
/*
** Fill out any parameters after the $u parameter.
*/
if (!clever)
{
{
syserr("554 5.3.0 deliver: pv overflow after $u for %s",
pv[0]);
}
}
/*
** 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 NAMED_BIND
if (ConfigLevel < 2)
#endif /* NAMED_BIND */
{
sm_dprintf("openmailer:");
}
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.
*/
#if XDEBUG
{
/* make absolutely certain 0, 1, and 2 are in use */
m->m_name);
}
#endif /* XDEBUG */
/* check for 8-bit available */
{
e->e_status = "5.6.3";
"554 Cannot send 8-bit data to 7-bit destination");
rcode = EX_DATAERR;
goto give_up;
}
checkfds("before delivery");
/* check for Local Person Communication -- not for mortals!!! */
{
if (clever)
{
/* flush any expired connections */
/* try to get a cached connection or just a slot */
{
m->m_name);
mci->mci_deliveries++;
goto do_transfer;
}
}
else
{
}
mci->mci_mailer = m;
if (clever)
{
}
else
}
{
register int i;
{
goto give_up;
}
# if NETUNIX
{
}
else
# endif /* NETUNIX */
{
}
{
goto give_up;
}
if (!clever)
{
syserr("554 5.3.5 non-clever IPC");
goto give_up;
}
# if NETUNIX
# endif /* NETUNIX */
)
{
if (port == 0)
{
# ifdef NO_GETSERVBYNAME
# else /* NO_GETSERVBYNAME */
else
# endif /* NO_GETSERVBYNAME */
}
}
if (TimeOuts.to_aconnect > 0)
while (hostnum < nummxhosts)
{
char *endp;
bool tried_fallbacksmarthost = false;
# if NETINET6
{
}
else
# else /* NETINET6 */
# endif /* NETINET6 */
{
*endp = '\0';
}
{
/*
** 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.
*/
}
{
syserr("deliver: null host name in signature");
hostnum++;
continue;
}
sizeof(hostbuf));
hostnum++;
/* see if we already know that this host is fried */
{
char *type;
{
sm_dprintf("openmailer: ");
}
type = "L";
type = "ES";
else
type = "S";
message("Using cached %sMTP connection to %s via %s...",
mci->mci_deliveries++;
break;
}
mci->mci_mailer = m;
{
goodmxfound = true;
/* Try FallbackSmartHost? */
if (should_try_fbsh(e, &tried_fallbacksmarthost,
mci->mci_exitstat))
goto one_last_try;
continue;
}
{
goodmxfound = true;
continue;
}
/* try the connection */
sm_setproctitle(true, e, "%s %s: %s",
qid_printname(e),
hostbuf, "user open");
# if NETUNIX
{
message("Connecting to %s via %s...",
}
else
# endif /* NETUNIX */
{
if (port == 0)
message("Connecting to %s via %s...",
else
message("Connecting to %s port %d via %s...",
m->m_name);
enough);
}
mci->mci_deliveries = 0;
mci->mci_exitstat = i;
# if NAMED_BIND
# endif /* NAMED_BIND */
/*
** Have we tried long enough to get a connection?
** If yes, skip to the fallback MX hosts
** (if existent).
*/
{
int h;
# if NAMED_BIND
extern int NumFallbackMXHosts;
# else /* NAMED_BIND */
const int NumFallbackMXHosts = 0;
# endif /* NAMED_BIND */
"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;
if (TrafficLogFile != NULL)
(void) sm_io_fprintf(TrafficLogFile,
"%05d === CONNECT %s\n",
(int) CurrentPid,
hostbuf);
break;
}
else
{
/* Try FallbackSmartHost? */
if (should_try_fbsh(e, &tried_fallbacksmarthost,
goto one_last_try;
sm_dprintf("openmailer: makeconnection => stat=%d, errno=%d\n",
i, errno);
if (i == EX_TEMPFAIL)
goodmxfound = true;
}
/* enter status of this host */
setstat(i);
/* should print some message here for -v mode */
}
{
syserr("deliver: no host name");
rcode = EX_SOFTWARE;
goto give_up;
}
}
else
{
/* flush any expired connections */
{
/* try to get a cached connection */
{
message("Using cached LMTP connection for %s...",
m->m_name);
mci->mci_deliveries++;
goto do_transfer;
}
}
/* announce the connection to verbose listeners */
else
if (TrafficLogFile != NULL)
{
char **av;
"%05d === EXEC", (int) CurrentPid);
(void) sm_io_fprintf(TrafficLogFile,
SM_TIME_DEFAULT, " %s",
*av);
"\n");
}
#if XDEBUG
checkfd012("before creating mail pipe");
#endif /* XDEBUG */
/* create a pipe to shove the mail through */
{
syserr("%s... openmailer(%s): pipe (to mailer)",
sm_dprintf("openmailer: NULL\n");
goto give_up;
}
#if XDEBUG
/* make sure we didn't get one of the standard I/O files */
{
syserr("%s... openmailer(%s): bogus mpvect %d %d",
printopenfds(true);
sm_dprintf("openmailer: NULL\n");
goto give_up;
}
/* make sure system call isn't dead meat */
NULL) ||
NULL))))
{
syserr("%s... openmailer(%s): overlapping mpvect %d %d",
else
syserr("%s... openmailer(%s): overlapping mpvect %d %d, lockfp = %d",
SM_IO_WHAT_FD, NULL));
}
#endif /* XDEBUG */
/* create a return pipe */
{
syserr("%s... openmailer(%s): pipe (from mailer)",
m->m_name);
sm_dprintf("openmailer: NULL\n");
goto give_up;
}
#if XDEBUG
#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.
*/
/* pid is set by DOFORK */
if (pid < 0)
{
/* failure */
syserr("%s... openmailer(%s): cannot fork",
sm_dprintf("openmailer: NULL\n");
goto give_up;
}
else if (pid == 0)
{
int save_errno;
int sff;
extern int DtableSize;
CurrentPid = getpid();
/* clear the events to turn off SIGALRMs */
/* Reset global flags */
RestartWorkGroup = false;
PendingSignal = 0;
NULL));
/* child -- set up input & exec mailer */
# ifdef SIGUSR1
# endif /* SIGUSR1 */
# if HASSETUSERCONTEXT
/*
** Set user resources.
*/
if (contextaddr != NULL)
{
int sucflags;
else
#ifdef LOGIN_SETMAC
sucflags |= LOGIN_SETMAC;
#endif /* LOGIN_SETMAC */
sucflags) == -1 &&
{
syserr("openmailer: setusercontext() failed");
}
}
# endif /* HASSETUSERCONTEXT */
#if HASNICE
/* tweak niceness */
if (m->m_nice != 0)
#endif /* HASNICE */
/* reset group id */
{
else
}
{
if (!DontInitGroups)
{
if (initgroups(user,
&& suidwarn)
{
syserr("openmailer: initgroups(%s, %d) failed",
}
}
else
{
&& suidwarn)
{
syserr("openmailer: setgroups() failed");
}
}
}
else
{
if (!DontInitGroups)
{
{
syserr("openmailer: initgroups(%s, %d) failed",
}
}
else
{
&& suidwarn)
{
syserr("openmailer: setgroups() failed");
}
}
else
}
{
if (RunAsUid != 0 &&
{
/* Only root can change the gid */
syserr("openmailer: insufficient privileges to change gid, RunAsUid=%d, new_gid=%d, gid=%d, egid=%d",
}
{
syserr("openmailer: setgid(%ld) failed",
(long) new_gid);
}
}
/* change root to some "safe" directory */
{
sm_dprintf("openmailer: chroot %s\n",
cbuf);
{
syserr("openmailer: Cannot chroot(%s)",
cbuf);
}
if (chdir("/") < 0)
{
syserr("openmailer: cannot chdir(/)");
}
}
/* reset user id */
endpwent();
{
else
/*
** 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 MAILER_SETUID_METHOD == USE_SETEUID
# if HASSETREUID
{
syserr("openmailer: setreuid(%d, %d) failed",
}
# endif /* HASSETREUID */
# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
# if MAILER_SETUID_METHOD == USE_SETREUID
# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
}
}
else
# if _FFR_USE_SETLOGIN
/* run disconnected from terminal and set login name */
if (setsid() >= 0 &&
{
endpwent();
}
# endif /* _FFR_USE_SETLOGIN */
{
{
/* Only root can change the uid */
syserr("openmailer: insufficient privileges to change uid, new_euid=%d, RunAsUid=%d",
}
# if MAILER_SETUID_METHOD == USE_SETEUID
{
syserr("openmailer: seteuid(%ld) failed",
(long) new_euid);
}
# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
# if MAILER_SETUID_METHOD == USE_SETREUID
{
syserr("openmailer: setreuid(%ld, %ld) failed",
}
# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
# if MAILER_SETUID_METHOD == USE_SETUID
{
syserr("openmailer: setuid(%ld) failed",
(long) new_euid);
}
# endif /* MAILER_SETUID_METHOD == USE_SETUID */
}
{
{
syserr("openmailer: setuid(%ld) failed",
(long) new_ruid);
}
}
sm_dprintf("openmailer: running as r/euid=%d/%d, r/egid=%d/%d\n",
/* move into some "safe" directory */
{
char *q;
{
q = strchr(p, ':');
if (q != NULL)
*q = '\0';
if (q != NULL)
*q++ = ':';
sm_dprintf("openmailer: trydir %s\n",
cbuf);
if (cbuf[0] != '\0' &&
break;
}
}
/* Check safety of program to be run */
sff |= SFF_NOPATHCHECK;
else
sff |= SFF_SAFEDIRPATH;
if (ret != 0)
"Warning: program %s unsafe: %s",
/* arrange to filter std & diag output of command */
{
syserr("%s... openmailer(%s): cannot dup pipe %d for stdout",
}
{
syserr("%s... openmailer(%s): cannot dup stdout for stderr",
m->m_name);
}
/* arrange to get standard input */
{
syserr("%s... openmailer(%s): cannot dup pipe %d for stdin",
}
/* arrange for all the files to be closed */
# if !_FFR_USE_SETLOGIN
/* run disconnected from terminal */
(void) setsid();
# endif /* !_FFR_USE_SETLOGIN */
/* try to execute the mailer */
(ARGV_T) UserEnviron);
save_errno = errno;
}
/*
** Set up return value.
*/
{
if (clever)
{
/*
** Allocate from general heap, not
** envelope rpool, because this mci
** is going to be cached.
*/
}
else
{
/*
** Prevent a storage leak by allocating
** this from the envelope rpool.
*/
}
}
mci->mci_mailer = m;
if (clever)
{
}
else
{
}
NULL);
{
syserr("deliver: cannot create mailer output channel, fd=%d",
mpvect[1]);
goto give_up;
}
(void *) &(rpvect[0]), SM_IO_RDONLY_B,
NULL);
{
syserr("deliver: cannot create mailer input channel, fd=%d",
mpvect[1]);
goto give_up;
}
}
/*
** If we are in SMTP opening state, send initial protocol.
*/
{
int dotpos;
char *srvname;
extern SOCKADDR CurHostAddr;
# endif /* STARTTLS || SASL */
# if SASL
# endif /* SASL */
# if STARTTLS
# endif /* STARTTLS */
/* don't use CurHostName, it is changed in many places */
{
if (dotpos >= 0)
{
else
dotpos = -1;
}
}
{
dotpos = -1;
}
else
{
srvname = "local";
dotpos = -1;
}
/* don't set {server_name} to NULL or "": see getauth() */
srvname);
/* CurHostAddr is set by makeconnection() and mci_get() */
{
macid("{server_addr}"),
}
{
/* mailer name is unique, use it as address */
macid("{server_addr}"),
}
else
{
/* don't set it to NULL or "": see getauth() */
}
/* undo change of srvname (mci->mci_host) */
if (dotpos >= 0)
reconnect: /* after switching to an encrypted connection */
# endif /* STARTTLS || SASL */
/* set the current connection information */
# if SASL
# endif /* SASL */
if (IS_DLVR_RETURN(e))
{
/*
** Check whether other side can deliver e-mail
** fast enough
*/
{
e->e_status = "5.4.7";
"554 Server does not support Deliver By");
goto give_up;
}
if (e->e_deliver_by > 0 &&
{
e->e_status = "5.4.7";
"554 Message can't be delivered in time; %ld < %ld",
mci->mci_min_by);
goto give_up;
}
}
# if STARTTLS
/* first TLS then AUTH to provide a security layer */
{
int olderrors;
bool usetls;
if (usetls)
if (usetls)
{
QuickAbort = false;
SuprErrs = true;
!= EX_OK
{
usetls = false;
}
}
if (usetls)
{
{
/* start again without STARTTLS */
}
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.
*/
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;
}
macid("{verify}"), s);
}
}
else
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",
rcode == EX_SOFTWARE)
{
extern char MsgBuf[];
if (ISSMTPCODE(MsgBuf) &&
{
p = sm_rpool_strdup_x(e->e_rpool,
MsgBuf);
}
else
{
p = "403 4.7.0 server not authenticated.";
sizeof(enhsc));
}
if (rcode == EX_SOFTWARE)
{
/* drop the connection */
{
}
}
else
{
/* abort transfer */
}
/* avoid bogus error msg */
/* temp or permanent failure? */
/*
** hack to get the error message into
** the envelope (done in giveresponse())
*/
(void) sm_strlcpy(SmtpError, p,
sizeof(SmtpError));
}
{
/* connection close caused by 421 */
rcode = EX_TEMPFAIL;
}
else
rcode = 0;
{
goto reconnect;
}
}
# endif /* STARTTLS */
# if SASL
/* if other server supports authentication let's authenticate */
{
/* Should we require some minimum authentication? */
{
int result;
/* Get security strength (features) */
# if SASL >= 20000
(const void **) &ssf);
# else /* SASL >= 20000 */
(void **) &ssf);
# endif /* SASL >= 20000 */
/* XXX authid? */
if (LogLevel > 9)
"AUTH=client, relay=%.100s, mech=%.16s, bits=%d",
/*
** Only switch to encrypted connection
** if a security layer has been negotiated
*/
{
int tmo;
/*
** Convert I/O layer to use SASL.
** If the call fails, the connection
** is aborted.
*/
{
goto reconnect;
}
syserr("AUTH TLS switch failed in client");
}
/* else? XXX */
}
else if (ret == EX_TEMPFAIL)
{
if (LogLevel > 8)
"AUTH=client, relay=%.100s, temporary failure, connection abort",
/* avoid bogus error msg */
rcode = EX_TEMPFAIL;
/*
** hack to get the error message into
** the envelope (done in giveresponse())
*/
(void) sm_strlcpy(SmtpError,
"Temporary AUTH failure",
sizeof(SmtpError));
}
}
# endif /* SASL */
}
/* clear out per-message flags from connection structure */
#if MIME7TO8
(sm_strcasecmp(p, "quoted-printable") == 0 ||
sm_strcasecmp(p, "base64") == 0) &&
{
/* may want to convert 7 -> 8 */
/* XXX should really parse it here -- and use a class XXX */
(p[10] == '\0' || p[10] == ' ' || p[10] == ';'))
}
#endif /* MIME7TO8 */
{
sm_dprintf("openmailer: ");
}
#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.
*/
mci->mci_maxsize > 0 &&
{
e->e_flags |= EF_NO_BODY_RETN;
e->e_status = "5.2.3";
else
e->e_status = "5.3.4";
"552 Message is too large; %ld bytes max",
mci->mci_maxsize);
rcode = EX_DATAERR;
/* Need an e_message for error */
"Message is too large; %ld bytes max",
mci->mci_maxsize);
goto give_up;
}
#endif /* _FFR_CLIENT_SIZE */
{
/* couldn't open the mailer */
{
/* shouldn't happen */
syserr("554 5.3.5 deliver: mci=%lx rcode=%d errno=%d state=%d sig=%s",
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.
*/
errno = 0;
if (ok)
if (ok)
/*
** 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.
*/
ok = true;
/* (always) get the exit status */
if (!ok)
rcode = EX_TEMPFAIL;
{
/*
** Need an e_message for mailq display.
** We set SmtpError as
*/
"%s mailer (%s) exited with EX_TEMPFAIL",
}
}
else
{
/*
** Send the MAIL FROM: protocol
*/
/* XXX this isn't pipelined... */
{
register int i;
# if PIPELINING
# endif /* PIPELINING */
/* send the recipient list */
tobuf[0] = '\0';
mci->mci_retryrcpt = false;
# if PIPELINING
# endif /* PIPELINING */
{
continue;
/* mark recipient state as "ok so far" */
# if STARTTLS
if (i != EX_OK)
{
if (i == EX_TEMPFAIL)
{
mci->mci_retryrcpt = true;
}
continue;
}
# endif /* STARTTLS */
# if PIPELINING
if (i == EX_OK &&
{
/*
** Add new element to list of
** recipients for pipelining.
*/
else
{
}
}
# endif /* PIPELINING */
if (i != EX_OK)
{
if (i == EX_TEMPFAIL)
}
}
/* No recipients in list and no missing responses? */
if (tobuf[0] == '\0'
# if PIPELINING
# endif /* PIPELINING */
)
{
}
else
{
}
}
{
/* try next MX site */
goto tryhost;
}
}
#if NAMED_BIND
if (ConfigLevel < 2)
#endif /* NAMED_BIND */
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.
*/
{
lmtp_rcode = rcode;
tobuf[0] = '\0';
anyok = false;
strsize = 0;
}
else
{
/* see if address already marked */
continue;
/* if running LMTP, get the status for each address */
{
if (lmtp_rcode == EX_OK)
{
anyok = true;
}
else
{
continue;
}
}
else
{
/* mark bad addresses */
{
rcode = EX_TEMPFAIL;
continue;
}
}
/* successful delivery */
e->e_nsent++;
/*
** Checkpoint the send list every few addresses
*/
{
queueup(e, false, false);
e->e_nsent = 0;
}
{
"%s... Successfully delivered\n",
}
{
"%s... relayed; expect no further notifications\n",
}
else if (IS_DLVR_NOTIFY(e) &&
{
/* RFC 2852, 4.1.4.2: no NOTIFY, or not NEVER */
"%s... Deliver-by notify: relayed\n",
}
else if (IS_DLVR_TRACE(e) &&
{
/* RFC 2852, 4.1.4: no NOTIFY, or not NEVER */
"%s... Deliver-By trace: relayed\n",
}
}
{
/*
** Global information applies to the last recipient only;
** clear it out to avoid bogus errors.
*/
/* reset the mci state for the next transaction */
{
}
}
if (tobuf[0] != '\0')
{
#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.
*/
{
{
/* see if address already marked */
}
}
#endif /* 0 */
}
if (anyok)
/* Some recipients were tempfailed, try them on the next host */
{
/* try next MX site */
goto tryhost;
}
/* now close the connection */
cleanup: ;
}
{
/*
** Restore state and return.
*/
#if XDEBUG
/* make absolutely certain 0, 1, and 2 are in use */
"%s... end of deliver(%s)",
: shortenstring(e->e_to,
m->m_name);
#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.
*/
}
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
register ENVELOPE *e;
register ADDRESS *q;
int rcode;
bool ovr;
{
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 */
{
mci->mci_rstatus);
else
}
{
}
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? */
{
}
{
}
q->q_statdate = curtime();
/* 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 void
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.
*/
}
int
register ENVELOPE *e;
char **pv;
{
int st;
/* close output to mailer */
{
}
/* copy any remaining input to transcript */
{
}
#if SASL
/* close SASL connection */
{
}
#endif /* SASL */
#if STARTTLS
/* shutdown TLS */
#endif /* STARTTLS */
/* now close the input */
{
}
errno = save_errno;
/* in the IPC case there is nothing to wait for */
return EX_OK;
/* put a timeout around the wait */
{
if (setjmp(EndWaitTimeout) == 0)
endwaittimeout, 0);
else
{
syserr("endmailer %s: wait timeout (%ld)",
return EX_TEMPFAIL;
}
}
/* wait for the mailer process, collect status */
save_errno = errno;
errno = save_errno;
if (st == -1)
{
return EX_SOFTWARE;
}
{
/* normal death -- return status */
return (WEXITSTATUS(st));
}
/* it died a horrid death */
syserr("451 4.3.0 mailer %s died with signal %d%s",
/* log the arguments */
{
register char **av;
*av);
}
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
int status;
char *dsn;
register MAILER *m;
ENVELOPE *e;
{
register const char *statmsg;
bool usestat = false;
char *exmsg;
if (e == NULL)
{
syserr("giveresponse: null envelope");
/* NOTREACHED */
SM_ASSERT(0);
}
/*
** Compute status message from code.
*/
if (status == 0)
{
statmsg = "250 2.0.0 Sent";
{
}
}
{
"554 5.3.0 unknown mailer error %d",
status);
usestat = true;
}
else if (status == EX_TEMPFAIL)
{
#if NAMED_BIND
else
#endif /* NAMED_BIND */
{
if (errnum != 0)
else
}
{
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 */
{
(void) sm_strlcpyn(bp,
2, ": ",
}
break;
}
statmsg);
usestat = true;
}
}
#if NAMED_BIND
{
statmsg);
usestat = true;
}
#endif /* NAMED_BIND */
else
{
{
usestat = true;
}
{
usestat = true;
}
}
/*
** Print the message as appropriate
*/
{
extern char MsgBuf[];
{
{
}
off += 5;
}
else
{
off = 4;
}
&MsgBuf[4]);
}
else
{
Errors++;
{
{
}
off += 5;
/* copy only part of statmsg to mbuf */
}
else
{
dsnbuf[0] = '\0';
statmsg);
off = 4;
}
}
/*
** Final cleanup.
** Log a record of the transaction. Compute the new
** ExitStat -- if we already had an error, stick with
** that.
*/
sm_dprintf("giveresponse: status=%d, dsn=%s, e->e_message=%s, errnum=%d\n",
errnum);
if (status != EX_TEMPFAIL)
{
e->e_message);
else
}
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
MAILER *m;
char *dsn;
const char *status;
register ENVELOPE *e;
{
register char *bp;
register char *p;
int l;
#if (SYSLOG_BUFSIZE) >= 256
/* ctladdr: max 106 bytes */
{
{
}
}
/* delay & xdelay: max 41 bytes */
{
}
/* mailer: assume about 19 bytes (max 10 byte mailer name) */
if (m != NULL)
{
m->m_name);
}
/* pri: changes with each delivery attempt */
e->e_msgpriority);
/* relay: max 66 bytes for IPv4 addresses */
{
extern SOCKADDR CurHostAddr;
{
}
}
{
", quarantine=%s",
}
{
p = macvalue('h', e);
if (p != NULL && p[0] != '\0')
{
}
}
/* dsn */
{
}
#if _FFR_LOG_NTRIES
/* ntries */
if (e->e_ntries >= 0)
{
}
#endif /* _FFR_LOG_NTRIES */
# if (STATLEN) < 63
# endif /* (STATLEN) < 63 */
# if (STATLEN) > 203
# endif /* (STATLEN) > 203 */
/* stat: max 210 bytes */
{
/* desperation move -- truncate data */
bp += 3;
}
/* id, to: max 13 + TOBUFSIZE bytes */
if (l < 0)
l = 0;
while (strlen(p) >= l)
{
register char *q;
for (q = p + l; q > p; q--)
{
if (*q == ',')
break;
}
if (p == q)
break;
(int) (++q - p), p, buf);
p = q;
}
#else /* (SYSLOG_BUFSIZE) >= 256 */
l = SYSLOG_BUFSIZE - 85;
if (l < 0)
l = 0;
while (strlen(p) >= l)
{
register char *q;
for (q = p + l; q > p; q--)
{
if (*q == ',')
break;
}
if (p == q)
break;
(int) (++q - p), p);
p = q;
}
{
{
}
}
{
}
if (m != NULL)
{
m->m_name);
}
buf[0] = '\0';
{
extern SOCKADDR CurHostAddr;
" [%.100s]",
}
{
", quarantine=%.100s",
e->e_quarmsg);
}
{
p = macvalue('h', e);
if (p != NULL && p[0] != '\0')
}
if (buf[0] != '\0')
#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
ENVELOPE *e;
{
return true;
{
char *bang;
{
char *at;
/*
** If we can construct a UUCP path, do so
*/
{
}
else
*at++ = '\0';
"From %.800s \201d remote from %.100s\n",
}
else
{
*bang++ = '\0';
"From %.800s \201d remote from %.100s\n",
}
}
}
/*
** 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 */
bool
register ENVELOPE *e;
char *separator;
{
bool dead = false;
bool ioerr = false;
int save_errno;
#if MIME8TO7
#endif /* MIME8TO7 */
/*
** Output the body of the message
*/
{
{
msg++;
}
}
{
{
goto writeerr;
}
goto writeerr;
goto endofmessage;
}
{
< 0)
e->e_dfino = -1;
else
{
}
}
/* paranoia: the data file should always be in a rewound state */
/* simulate an I/O timeout when used as source */
sleep(319);
#if MIME8TO7
{
/*
** Do 8 to 7 bit MIME conversion.
*/
/* make sure it looks like a MIME message */
goto writeerr;
{
defcharset(e));
goto writeerr;
}
/* now do the hard work */
boundaries[0] = NULL;
goto writeerr;
}
# if MIME7TO8
{
goto writeerr;
}
# endif /* MIME7TO8 */
else if (MaxMimeHeaderLength > 0 || MaxMimeFieldLength > 0)
{
/* Use mime8to7 to check multipart for MIME header overflows */
boundaries[0] = NULL;
/*
** 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.
*/
SuprErrs = true;
goto writeerr;
/* restore SuprErrs */
}
else
#endif /* MIME8TO7 */
{
int ostate;
register char *bp;
register char *pbp;
register int c;
register char *xp;
int padc;
char *buflim;
int pos = 0;
{
goto writeerr;
}
/* determine end of buffer; allow for short mailer lines */
/* copy temp file to output with mapping */
{
c = *--pbp;
== SM_IO_EOF)
break;
c &= 0x7f;
switch (ostate)
{
case OSTATE_HEAD:
if (c == '\0' &&
break;
{
*bp++ = c;
break;
}
/* check beginning of line for special cases */
*bp = '\0';
pos = 0;
if (buf[0] == 'F' &&
{
padc = '>';
}
{
/* possible separator */
== 0)
padc = ' ';
}
if (buf[0] == '.' &&
{
padc = '.';
}
/* now copy out saved line */
if (TrafficLogFile != NULL)
{
(void) sm_io_fprintf(TrafficLogFile,
"%05d >>> ",
(int) CurrentPid);
(void) sm_io_putc(TrafficLogFile,
padc);
(void) sm_io_putc(TrafficLogFile,
(unsigned char) *xp);
if (c == '\n')
(void) sm_io_fputs(TrafficLogFile,
}
{
== SM_IO_EOF)
{
dead = true;
continue;
}
pos++;
}
{
(unsigned char) *xp)
== SM_IO_EOF)
{
dead = true;
break;
}
}
if (dead)
continue;
if (c == '\n')
{
== SM_IO_EOF)
break;
pos = 0;
}
else
{
if (c != '\r')
{
sizeof(peekbuf));
*pbp++ = c;
}
}
/* determine next state */
if (c == '\n')
else if (c == '\r')
else
continue;
case OSTATE_CR:
if (c == '\n')
{
/* got CRLF */
== SM_IO_EOF)
continue;
if (TrafficLogFile != NULL)
{
(void) sm_io_fputs(TrafficLogFile,
}
pos = 0;
continue;
}
/* had a naked carriage return */
*pbp++ = c;
c = '\r';
goto putch;
case OSTATE_INLINE:
if (c == '\r')
{
continue;
}
if (c == '\0' &&
break;
c != '\n')
{
int d;
/* check next character for EOL */
d = *(pbp - 1);
else if ((d = sm_io_getc(e->e_dfp,
!= SM_IO_EOF)
{
sizeof(peekbuf));
*pbp++ = d;
}
if (d == '\n' || d == SM_IO_EOF)
{
if (TrafficLogFile != NULL)
(void) sm_io_putc(TrafficLogFile,
(unsigned char) c);
(unsigned char) c)
== SM_IO_EOF)
{
dead = true;
continue;
}
pos++;
continue;
}
SM_TIME_DEFAULT, '!')
== SM_IO_EOF ||
== SM_IO_EOF)
{
dead = true;
continue;
}
if (TrafficLogFile != NULL)
{
(void) sm_io_fprintf(TrafficLogFile,
"!%s",
}
sizeof(peekbuf));
*pbp++ = c;
continue;
}
if (c == '\n')
{
if (TrafficLogFile != NULL)
(void) sm_io_fputs(TrafficLogFile,
== SM_IO_EOF)
continue;
pos = 0;
}
else
{
if (TrafficLogFile != NULL)
(void) sm_io_putc(TrafficLogFile,
(unsigned char) c);
(unsigned char) c)
== SM_IO_EOF)
{
dead = true;
continue;
}
pos++;
}
break;
}
}
/* make sure we are at the beginning of a line */
{
if (TrafficLogFile != NULL)
{
(void) sm_io_putc(TrafficLogFile,
(unsigned char) *xp);
}
{
(unsigned char) *xp)
== SM_IO_EOF)
{
dead = true;
break;
}
}
}
{
if (TrafficLogFile != NULL)
(void) sm_io_fputs(TrafficLogFile,
goto writeerr;
}
}
if (sm_io_error(e->e_dfp))
{
syserr("putbody: %s/%cf%s: read error",
DATAFL_LETTER, e->e_id);
ioerr = true;
}
/*
** 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;
/* some mailers want extra blank line at end of message */
{
goto writeerr;
}
if (!dead &&
{
save_errno = errno;
syserr("putbody: write error");
ioerr = true;
}
errno = save_errno;
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
** sfflags -- flags for opening.
** e -- the current envelope.
**
** Returns:
** The exit code associated with the operation.
**
** Side Effects:
** none.
*/
int
char *volatile filename;
volatile long sfflags;
register ENVELOPE *e;
{
register SM_FILE_T *f;
volatile int mode;
int len;
char *p;
char *volatile realfile;
{
}
mailer = FileMailer;
/*
** delivery to regular files only.
*/
if (sm_path_isdevnull(filename))
return EX_OK;
/* check for 8-bit available */
{
e->e_status = "5.6.3";
"554 Cannot send 8-bit data to 7-bit destination");
errno = 0;
return EX_DATAERR;
}
/* Find the actual file */
{
{
syserr("mailfile: filename too long (%s/%s)",
return EX_CANTCREAT;
}
if (*filename == '/')
filename++;
if (*filename != '\0')
{
/* paranoia: trailing / should be removed in readcf */
(void) sm_strlcat(targetfile,
"/", sizeof(targetfile));
sizeof(targetfile));
}
}
{
{
syserr("mailfile: filename too long (%s/%s)",
return EX_CANTCREAT;
}
if (*filename == '/')
sizeof(targetfile));
else
sizeof(targetfile));
}
else
{
sizeof(targetfile))
{
return EX_CANTCREAT;
}
}
/*
** 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.
*/
if (pid < 0)
return EX_OSERR;
else if (pid == 0)
{
/* child -- actually write to file */
int err;
/* Reset global flags */
RestartWorkGroup = false;
PendingSignal = 0;
CurrentPid = getpid();
{
int fd;
/* SM_ASSERT(fd >= 0); */
if (fd >= 0)
}
if (setjmp(CtxMailfileTimeout) != 0)
{
}
if (TimeOuts.to_fileopen > 0)
0);
else
/* check file mode to see if set-user-ID */
else
/* limit the errors to those actually caused in the child */
errno = 0;
/* Allow alias expansions to use the S_IS{U,G}ID bits */
{
/* ignore set-user-ID and set-group-ID bits */
sm_dprintf("mailfile: ignoring set-user-ID/set-group-ID bits\n");
}
/* we have to open the data file BEFORE setuid() */
{
{
syserr("mailfile: Cannot open %s for %s from %s",
}
}
/* select a new user to run as */
{
{
RealUserName = NULL;
else
{
/* Only root can change the uid */
syserr("mailfile: insufficient privileges to change uid, RunAsUid=%d, RealUid=%d",
}
}
{
RealUserName = NULL;
}
{
else
}
{
}
else
{
}
/* select a new group to run as */
{
else
if (RunAsUid != 0 &&
{
/* Only root can change the gid */
syserr("mailfile: insufficient privileges to change gid, RealGid=%d, RunAsUid=%d, gid=%d, egid=%d",
}
}
{
/*
** Special case: This means it is an
** alias and we should act as DefaultUser.
** See alias()'s comments.
*/
}
else
}
/* last ditch */
{
if (RealUid == 0)
if (RealGid == 0)
}
{
{
syserr("mailfile: initgroups(%s, %d) failed",
}
}
else
{
{
syserr("mailfile: setgroups() failed");
}
}
/*
** If you have a safe environment, go into it.
*/
if (realfile != targetfile)
{
char save;
*realfile = '\0';
if (chroot(targetfile) < 0)
{
syserr("mailfile: Cannot chroot(%s)",
}
}
if (chdir("/") < 0)
{
syserr("mailfile: cannot chdir(/)");
}
/* now reset the group and user ids */
endpwent();
{
}
{
}
sm_dprintf("mailfile: running as r/euid=%d/%d, r/egid=%d/%d\n",
/* move into some "safe" directory */
{
char *q;
{
q = strchr(p, ':');
if (q != NULL)
*q = '\0';
if (q != NULL)
*q++ = ':';
sm_dprintf("mailfile: trydir %s\n",
buf);
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
else
#else /* HASLSTAT */
#endif /* HASLSTAT */
if (err < 0)
{
}
else
sfflags |= SFF_NOSLINK;
sfflags |= SFF_NOHLINK;
sfflags &= ~SFF_OPENASROOT;
if (f == NULL)
{
if (transienterror(errno))
{
usrerr("454 4.3.0 cannot open %s: %s",
}
else
{
usrerr("554 5.3.0 cannot open %s: %s",
}
}
&stb))
{
syserr("554 5.3.0 file changed after open");
}
{
syserr("554 5.3.0 cannot fstat %s",
}
/* clear out per-message flags from connection structure */
#if MIME7TO8
(sm_strcasecmp(p, "quoted-printable") == 0 ||
sm_strcasecmp(p, "base64") == 0) &&
{
/* may want to convert 7 -> 8 */
/* XXX should really parse it here -- and use a class XXX */
(p[10] == '\0' || p[10] == ' ' || p[10] == ';'))
}
#endif /* MIME7TO8 */
if (!putfromline(&mcibuf, e) ||
(sm_io_flush(f, SM_TIME_DEFAULT) != 0 ||
sm_io_error(f)))
{
#if !NOFTRUNCATE
curoff);
#endif /* !NOFTRUNCATE */
}
/* reset ISUID & ISGID bits for paranoid systems */
#if HASFCHMOD
#else /* HASFCHMOD */
#endif /* HASFCHMOD */
if (sm_io_close(f, SM_TIME_DEFAULT) < 0)
/* NOTREACHED */
}
else
{
/* parent -- wait for exit status */
int st;
if (st == -1)
{
return EX_SOFTWARE;
}
{
errno = 0;
return (WEXITSTATUS(st));
}
else
{
syserr("mailfile: %s: child died on signal %d",
return EX_UNAVAILABLE;
}
/* NOTREACHED */
}
return EX_UNAVAILABLE; /* avoid compiler warning on IRIX */
}
static void
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.
*/
}
/*
** 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.
*/
char *
register MAILER *m;
char *host;
{
register char *p;
register STAB *s;
#if NAMED_BIND
int i;
int len;
int nmx;
int hl;
char *hp;
char *endp;
#endif /* NAMED_BIND */
/*
** If local delivery (and not remote), just return a constant.
*/
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.
*/
{
/* just an ordinary mailer or deferred mode */
return host;
}
#if NETUNIX
{
/* rendezvous in the file system, no MX records */
return host;
}
#endif /* NETUNIX */
/*
** Look it up in the symbol table.
*/
{
{
}
/* signature is expired: clear it */
}
/* set default TTL */
/*
** Not already there or expired -- create a signature.
*/
#if NAMED_BIND
if (ConfigLevel < 2)
{
#if NETINET6
if (*hp == '[')
{
}
else
#else /* NETINET6 */
#endif /* NETINET6 */
{
*endp = '\0';
}
{
/* skip MX lookups */
nmx = 1;
}
else
{
auto int rcode;
int ttl;
&ttl);
if (nmx <= 0)
{
int save_errno;
/* update the connection info for this host */
save_errno = errno;
"550 Host unknown");
else
/* use the original host name as signature */
nmx = 1;
}
sm_dprintf("hostsignature(): getmxrr() returned %d, mxhosts[0]=%s\n",
/*
** Set new TTL: we use only one!
** We could try to use the minimum instead.
*/
}
len = 0;
for (i = 0; i < nmx; i++)
{
}
p = sm_pmalloc_x(len);
{
p += hl;
*p++ = prevsep;
}
else
for (i = 0; i < nmx; i++)
{
{
/* force to drop out of outer loop */
len = -1;
break;
}
if (i != 0)
{
*p++ = ',';
else
*p++ = ':';
len--;
}
p += 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 (ConfigLevel < 2)
#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.
*/
#endif /* NAMED_BIND */
}
/*
** 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
char *sig;
char **mxhosts;
{
unsigned short curpref = 0;
{
#if NETINET6
if (*hp == '[')
{
}
else
#else /* NETINET6 */
#endif /* NETINET6 */
{
*endp = '\0';
}
else
{
/*
** 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++;
}
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
*/
break;
{
register unsigned short tempp;
register long tempr;
register char *temp1;
}
}
}
return nmx;
}
# if STARTTLS
static bool tls_ok_clt = true;
/*
**
** Parameters:
** tls_ok -- should tls be done?
**
** Returns:
** none.
**
** Side Effects:
** sets tls_ok_clt (static variable in this module)
*/
void
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
bool tls_ok;
{
if (!tls_ok_clt)
return false;
tls_ok_clt = tls_ok;
if (!tls_ok_clt)
return false;
return true; /* already done */
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
MAILER *m;
ENVELOPE *e;
{
int smtpresult;
int result = 0;
return EX_TEMPFAIL;
# if USE_OPENSSL_ENGINE
if (!SSL_set_engine(NULL))
{
"STARTTLS=client, SSL_set_engine=failed");
return EX_TEMPFAIL;
}
# endif /* USE_OPENSSL_ENGINE */
/* get the reply */
/* check return code from server */
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 */
return EX_UNAVAILABLE;
if (smtpresult != 220)
return EX_PROTOCOL;
if (LogLevel > 13)
/* start connection */
{
if (LogLevel > 5)
{
"STARTTLS=client, error: SSL_new failed");
if (LogLevel > 9)
tlslogerr("client");
}
return EX_SOFTWARE;
}
/* SSL_clear(clt_ssl); ? */
{
if (LogLevel > 5)
{
"STARTTLS=client, error: SSL_set_xfd failed=%d",
result);
if (LogLevel > 9)
tlslogerr("client");
}
return EX_SOFTWARE;
}
{
int i, ssl_err;
if (i > 0)
goto ssl_retry;
if (LogLevel > 5)
{
"STARTTLS=client, error: connect failed=%d, SSL_error=%d, errno=%d, retry=%d",
if (LogLevel > 8)
tlslogerr("client");
}
return EX_SOFTWARE;
}
/* switch to use TLS... */
return EX_OK;
/* failure */
return EX_SOFTWARE;
}
/*
** ENDTLSCLT -- shutdown secure connection (client side)
**
** Parameters:
** mci -- the mailer connection info.
**
** Returns:
** success?
*/
static int
{
int r;
return EX_OK;
return r;
}
# endif /* STARTTLS */
/*
** 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
ENVELOPE *e;
int flag;
{
char *p;
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 */