f9752a292d1667e23ea6fd39c58247bf6b16c503
* Copyright (c) 1983 Eric P. Allman
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)deliver.c 8.98 (Berkeley) %G%";
** SENDALL -- actually send all the messages.
** e -- the envelope to send.
** mode -- the delivery mode to use. If SM_DEFAULT, use
** the current e->e_sendmode.
** 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
** 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
if (bitset(EF_FATALERRS
, e
->e_flags
) &&
(OpMode
== MD_SMTP
|| OpMode
== MD_DAEMON
))
e
->e_flags
|= EF_CLRQUEUE
;
/* determine actual delivery mode */
shouldqueue(e
->e_msgpriority
, e
->e_ctime
))
announcequeueup
= mode
== SM_QUEUE
;
printf("\n===== SENDALL: mode %c, id %s, e_from ",
printaddr(&e
->e_from
, FALSE
);
printaddr(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.
if (e
->e_hopcount
> MaxHopCount
)
e
->e_flags
|= EF_FATALERRS
|EF_PM_NOTIFY
|EF_CLRQUEUE
;
syserr("554 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
,
e
->e_sendqueue
->q_paddr
);
** If the sender has the QQUEUEUP flag set, 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
) &&
!bitset(QQUEUEUP
, e
->e_from
.q_flags
))
printf("sendall: QDONTSEND ");
printaddr(&e
->e_from
, FALSE
);
e
->e_from
.q_flags
|= QDONTSEND
;
(void) recipient(&e
->e_from
, &e
->e_sendqueue
, e
);
if ((mode
== SM_QUEUE
|| mode
== SM_FORK
||
(mode
!= SM_VERIFY
&& SuperSafe
)) &&
!bitset(EF_INQUEUE
, e
->e_flags
))
/* be sure everything is instantiated in the queue */
queueup(e
, TRUE
, mode
== SM_QUEUE
);
e
->e_flags
|= EF_INQUEUE
|EF_KEEPQUEUE
;
** Since lockf has the interesting semantic that the
** lock is lost when we fork, we have to risk losing
** the lock here by closing before the fork, and then
** trying to get it back in the child.
(void) xfclose(e
->e_lockfp
, "sendenvelope", "lockfp");
/* be sure we leave the temp files to our child */
e
->e_id
= e
->e_df
= NULL
;
(void) xfclose(e
->e_lockfp
, "sendenvelope", "lockfp");
/* close any random open files in the envelope */
(void) xfclose(e
->e_dfp
, "sendenvelope", "dfp");
(void) xfclose(e
->e_xfp
, "sendenvelope", "xfp");
/* double fork to avoid zombies */
/* be sure we are immune from the terminal */
** Now try to get our lock back.
lfd
.l_whence
= lfd
.l_start
= lfd
.l_len
= 0;
e
->e_lockfp
= fopen(queuename(e
, 'q'), "r+");
if (e
->e_lockfp
== NULL
||
fcntl(fileno(e
->e_lockfp
), F_SETLK
, &lfd
) < 0)
printf("sendenvelope: %s lost lock: lockfp=%x, %s\n",
e
->e_id
, e
->e_lockfp
, errstring(errno
));
syslog(LOG_NOTICE
, "%s: lost lock: %m",
** 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
** If we haven't fully expanded aliases, do it now
if (bitset(EF_VRFYONLY
, e
->e_flags
))
e
->e_flags
&= ~EF_VRFYONLY
;
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
extern ADDRESS
*recipient();
if (bitset(QVERIFIED
, q
->q_flags
))
recipient(q
, &e
->e_sendqueue
, e
);
** 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
)
if (q
->q_owner
!= NULL
&&
!bitset(QDONTSEND
, q
->q_flags
) &&
strcmp(q
->q_owner
, e
->e_from
.q_paddr
) == 0)
while (owner
!= NULL
&& otherowners
> 0)
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
if (bitset(QDONTSEND
, q
->q_flags
))
else if (owner
!= q
->q_owner
)
if (strcmp(owner
, q
->q_owner
) == 0)
/* make future comparisons cheap */
if (owner
!= NULL
&& otherowners
> 0)
extern HDR
*copyheader();
extern ADDRESS
*copyqueue();
** Split this envelope into two.
ee
= (ENVELOPE
*) xalloc(sizeof(ENVELOPE
));
(void) queuename(ee
, '\0');
printf("sendall: split %s into %s\n",
ee
->e_header
= copyheader(e
->e_header
);
ee
->e_sendqueue
= copyqueue(e
->e_sendqueue
);
ee
->e_errorqueue
= copyqueue(e
->e_errorqueue
);
ee
->e_flags
= e
->e_flags
& ~(EF_INQUEUE
|EF_CLRQUEUE
|EF_FATALERRS
|EF_SENDRECEIPT
);
ee
->e_flags
|= EF_NORECEIPT
;
setsender(owner
, ee
, NULL
, TRUE
);
printf("sendall(split): QDONTSEND ");
printaddr(&ee
->e_from
, FALSE
);
ee
->e_from
.q_flags
|= QDONTSEND
;
ee
->e_errormode
= EM_MAIL
;
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
for (q
= ee
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
if (e
->e_df
!= NULL
&& mode
!= SM_VERIFY
)
ee
->e_df
= queuename(ee
, 'd');
ee
->e_df
= newstr(ee
->e_df
);
if (link(e
->e_df
, ee
->e_df
) < 0)
syserr("sendall: link(%s, %s)",
syslog(LOG_INFO
, "%s: clone %s, owner=%s",
ee
->e_id
, e
->e_id
, owner
);
sendenvelope(ee
, mode
, announcequeueup
);
** Split off envelopes have been sent -- now send original
setsender(owner
, e
, NULL
, TRUE
);
printf("sendall(owner): QDONTSEND ");
printaddr(&e
->e_from
, FALSE
);
e
->e_from
.q_flags
|= QDONTSEND
;
e
->e_errormode
= EM_MAIL
;
e
->e_flags
|= EF_NORECEIPT
;
sendenvelope(e
, mode
, announcequeueup
);
sendenvelope(e
, mode
, announcequeueup
)
bool oldverbose
= Verbose
;
** Run through the list and send everything.
** Set EF_GLOBALERRS so that error messages during delivery
** result in returned mail.
e
->e_flags
|= EF_GLOBALERRS
;
/* now run through the queue */
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
(void) sprintf(wbuf
, "sendall(%s)", q
->q_paddr
);
if (!bitset(QDONTSEND
|QBADADDR
, q
->q_flags
))
if (q
->q_host
!= NULL
&& q
->q_host
[0] != '\0')
message("deliverable: mailer %s, host %s, user %s",
message("deliverable: mailer %s, user %s",
else if (!bitset(QDONTSEND
|QBADADDR
, q
->q_flags
))
** Checkpoint the send list every few addresses
if (e
->e_nsent
>= CheckpointInterval
)
checkfd012("end of sendenvelope");
** 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!!!
** From a macro??? You've got to be kidding!
** Modifies the ==> LOCAL <== variable 'pid', leaving:
** pid of child in parent, zero in child.
** -1 on unrecoverable error.
** I'm awfully sorry this looks so awful. That's
# define DOFORK(fORKfN) \
for (i = NFORKTRIES; --i >= 0; )\
sleep((unsigned) NFORKTRIES - i);\
** DOFORK -- simple fork interface to DOFORK.
** pid of child in parent.
** returns twice, once in parent and once in child.
** 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
** e -- the envelope to deliver.
** firstto -- head of the address list to deliver to.
** zero -- successfully delivered.
** else -- some failure, see ExitStat for more info.
** The standard input is passed off to someone.
char *host
; /* host being sent to */
char *user
; /* user being sent to */
register MAILER
*m
; /* mailer for this recipient */
register ADDRESS
*to
= firstto
;
bool clever
= FALSE
; /* running user smtp to this mailer */
ADDRESS
*tochain
= NULL
; /* chain of users in this mailer call */
int rcode
; /* response code */
char *firstsig
; /* signature of firstto */
char tobuf
[TOBUFSIZE
]; /* text line of to people */
char rpathbuf
[MAXNAME
]; /* translated return path */
extern int checkcompat();
if (!ForceMail
&& bitset(QDONTSEND
|QPSEUDO
, to
->q_flags
))
/* unless interactive, try twice, over a minute */
if (OpMode
== MD_DAEMON
|| OpMode
== MD_SMTP
)
CurEnv
= e
; /* just in case */
printf("\n--deliver, id=%s, mailer=%s, host=`%s', first user=`%s'\n",
e
->e_id
, m
->m_name
, host
, to
->q_user
);
** 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
** This should be on a per-mailer basis.
if (NoConnect
&& bitnset(M_EXPENSIVE
, m
->m_flags
) && !Verbose
)
for (; to
!= NULL
; to
= to
->q_next
)
if (bitset(QDONTSEND
|QBADADDR
|QQUEUEUP
, to
->q_flags
) ||
logdelivery(m
, NULL
, "queued", NULL
, e
);
** 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 */
(void) strcpy(rpathbuf
, remotename(e
->e_from
.q_paddr
, m
,
RF_SENDERADDR
|RF_CANONICAL
,
define('g', rpathbuf
, e
); /* translated return path */
define('h', host
, e
); /* to host */
/* 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
++ = newstr(rpathbuf
);
** 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
for (mvp
= m
->m_argv
; (p
= *++mvp
) != NULL
; )
/* can't use strchr here because of sign extension problems */
if ((*p
++ & 0377) == MACROEXPAND
)
/* this entry is safe -- go ahead and process it */
expand(*mvp
, buf
, &buf
[sizeof buf
- 1], e
);
if (pvp
>= &pv
[MAXPV
- 3])
syserr("554 Too many parameters to %s before $u", pv
[0]);
** If we have no substitution for the user name in the argument
** list, we know that we must supply the names otherwise -- and
/* oops! we don't implement SMTP */
syserr("554 SMTP style mailer not implemented");
** 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.
firstsig
= hostsignature(firstto
->q_mailer
, firstto
->q_host
, e
);
for (; to
!= NULL
; to
= to
->q_next
)
/* avoid sending multiple recipients to dumb mailers */
if (tobuf
[0] != '\0' && !bitnset(M_MUSER
, m
->m_flags
))
/* if already sent or not for this host, don't send */
if (bitset(QDONTSEND
|QBADADDR
|QQUEUEUP
, to
->q_flags
) ||
to
->q_mailer
!= firstto
->q_mailer
||
strcmp(hostsignature(to
->q_mailer
, to
->q_host
, e
), firstsig
) != 0)
/* avoid overflowing tobuf */
if (sizeof tobuf
< (strlen(to
->q_paddr
) + strlen(tobuf
) + 2))
/* compute effective uid/gid when sending */
if (bitnset(M_RUNASRCPT
, to
->q_mailer
->m_flags
))
ctladdr
= getctladdr(to
);
printf("deliver: QDONTSEND ");
to
->q_flags
|= QDONTSEND
;
** Check to see that these people are allowed to
if (m
->m_maxsize
!= 0 && e
->e_msgsize
> m
->m_maxsize
)
e
->e_flags
|= EF_NORETURN
;
usrerr("552 Message is too large; %ld bytes max", m
->m_maxsize
);
giveresponse(EX_UNAVAILABLE
, m
, NULL
, ctladdr
, e
);
rcode
= checkcompat(to
, e
);
markfailure(e
, to
, rcode
);
giveresponse(rcode
, m
, NULL
, ctladdr
, e
);
** Strip quote bits from names if the mailer is dumb
if (bitnset(M_STRIPQ
, m
->m_flags
))
/* hack attack -- delivermail compatibility */
if (m
== ProgMailer
&& *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 (bitset(QBADADDR
|QQUEUEUP
, to
->q_flags
))
/* save statistics.... */
** 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.
rcode
= mailfile(user
, ctladdr
, e
);
giveresponse(rcode
, m
, NULL
, ctladdr
, e
);
** Address is verified -- add this user to mailer
** argv, and add it to the print list of recipients.
/* link together the chain of recipients */
/* create list of users for error messages */
(void) strcat(tobuf
, ",");
(void) strcat(tobuf
, to
->q_paddr
);
define('u', user
, e
); /* to user */
if (p
== NULL
&& ctladdr
!= NULL
)
define('z', p
, e
); /* user's home */
** Expand out this user into argument list.
expand(*mvp
, buf
, &buf
[sizeof buf
- 1], e
);
if (pvp
>= &pv
[MAXPV
- 2])
/* allow some space for trailing parms */
/* see if any addresses still exist */
define('g', (char *) NULL
, e
);
/* print out messages as full list */
** Fill out any parameters after the $u parameter.
while (!clever
&& *++mvp
!= NULL
)
expand(*mvp
, buf
, &buf
[sizeof buf
- 1], e
);
syserr("554 deliver: pv overflow after $u for %s", pv
[0]);
** The argument vector gets built, pipes
** are created as necessary, and we fork & exec as
** If we are running SMTP, we just need to clean up.
/*XXX this seems a bit wierd */
if (ctladdr
== NULL
&& m
!= ProgMailer
&&
bitset(QGOODUID
, e
->e_from
.q_flags
))
_res
.options
&= ~(RES_DEFNAMES
| RES_DNSRCH
); /* XXX */
CurHostName
= m
->m_mailer
;
** Deal with the special case of mail handled through an IPC
** 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.
/* make absolutely certain 0, 1, and 2 are in use */
sprintf(wbuf
, "%s... openmailer(%s)", e
->e_to
, m
->m_name
);
/* check for 8-bit available */
if (bitset(EF_HAS8BIT
, e
->e_flags
) &&
bitnset(M_7BITS
, m
->m_flags
) &&
!bitset(MM_MIME8BIT
, MimeMode
))
usrerr("554 Cannot send 8-bit data to 7-bit destination");
/* check for Local Person Communication -- not for mortals!!! */
if (strcmp(m
->m_mailer
, "[LPC]") == 0)
mci
= (MCI
*) xalloc(sizeof *mci
);
bzero((char *) mci
, sizeof *mci
);
mci
->mci_state
= clever
? MCIS_OPENING
: MCIS_OPEN
;
else if (strcmp(m
->m_mailer
, "[IPC]") == 0 ||
strcmp(m
->m_mailer
, "[TCP]") == 0)
if (pv
[0] == NULL
|| pv
[1] == NULL
|| pv
[1][0] == '\0')
syserr("null host name for %s mailer", m
->m_mailer
);
curhost
= hostsignature(m
, pv
[1], e
);
if (curhost
== NULL
|| curhost
[0] == '\0')
syserr("null host signature for %s", pv
[1]);
syserr("554 non-clever IPC");
static char hostbuf
[MAXNAME
];
/* pull the next host from the signature */
p
= strchr(curhost
, ':');
p
= &curhost
[strlen(curhost
)];
syserr("deliver: null host name in signature");
strncpy(hostbuf
, curhost
, p
- curhost
);
hostbuf
[p
- curhost
] = '\0';
/* see if we already know that this host is fried */
mci
= mci_get(hostbuf
, m
);
if (mci
->mci_state
!= MCIS_CLOSED
)
CurHostName
= mci
->mci_host
;
if (mci
->mci_exitstat
!= EX_OK
)
setproctitle("%s %s: %s", e
->e_id
, hostbuf
, "user open");
message("Connecting to %s (%s)...",
i
= makeconnection(hostbuf
, port
, mci
,
bitnset(M_SECURE_PORT
, m
->m_flags
));
mci
->mci_herrno
= h_errno
;
mci
->mci_state
= MCIS_OPENING
;
if (TrafficLogFile
!= NULL
)
fprintf(TrafficLogFile
, "%05d == CONNECT %s\n",
printf("openmailer: makeconnection => stat=%d, errno=%d\n",
/* enter status of this host */
/* should print some message here for -v mode */
syserr("deliver: no host name");
syserr("554 openmailer: no IPC");
printf("openmailer: NULL\n");
if (TrafficLogFile
!= NULL
)
fprintf(TrafficLogFile
, "%05d === EXEC", getpid());
for (av
= pv
; *av
!= NULL
; av
++)
fprintf(TrafficLogFile
, " %s", *av
);
fprintf(TrafficLogFile
, "\n");
/* create a pipe to shove the mail through */
syserr("%s... openmailer(%s): pipe (to mailer)",
printf("openmailer: NULL\n");
/* if this mailer speaks smtp, create a return pipe */
if (clever
&& pipe(rpvect
) < 0)
syserr("%s... openmailer(%s): pipe (from mailer)",
printf("openmailer: NULL\n");
** Actually fork the mailer process.
** DOFORK is clever about retrying.
** Dispose of SIGCHLD signal catchers that may be laying
** around so that endmail will get it.
(void) fflush(e
->e_xfp
); /* for debugging */
(void) setsignal(SIGCHLD
, SIG_DFL
);
/* pid is set by DOFORK */
syserr("%s... openmailer(%s): cannot fork",
printf("openmailer: NULL\n");
char *env
[MAXUSERENVIRON
];
/* child -- set up input & exec mailer */
(void) setsignal(SIGINT
, SIG_IGN
);
(void) setsignal(SIGHUP
, SIG_IGN
);
(void) setsignal(SIGTERM
, SIG_DFL
);
/* reset user and group */
if (bitnset(M_SPECIFIC_UID
, m
->m_flags
))
else if (ctladdr
!= NULL
&& ctladdr
->q_uid
!= 0)
(void) initgroups(ctladdr
->q_ruser
?
ctladdr
->q_ruser
: ctladdr
->q_user
,
(void) setgid(ctladdr
->q_gid
);
(void) setuid(ctladdr
->q_uid
);
(void) initgroups(DefUser
, DefGid
);
printf("openmailer: running as r/euid=%d/%d\n",
/* move into some "safe" directory */
if (m
->m_execdir
!= NULL
)
for (p
= m
->m_execdir
; p
!= NULL
; p
= q
)
expand(p
, buf
, &buf
[sizeof buf
] - 1, e
);
printf("openmailer: trydir %s\n",
if (buf
[0] != '\0' && chdir(buf
) >= 0)
/* arrange to filter std & diag output of command */
if (dup2(rpvect
[1], STDOUT_FILENO
) < 0)
syserr("%s... openmailer(%s): cannot dup pipe %d for stdout",
e
->e_to
, m
->m_name
, rpvect
[1]);
else if (OpMode
== MD_SMTP
|| OpMode
== MD_DAEMON
||
HoldErrs
|| DisConnected
)
/* put mailer output in transcript */
if (dup2(fileno(e
->e_xfp
), STDOUT_FILENO
) < 0)
syserr("%s... openmailer(%s): cannot dup xscript %d for stdout",
if (dup2(STDOUT_FILENO
, STDERR_FILENO
) < 0)
syserr("%s... openmailer(%s): cannot dup stdout for stderr",
/* arrange to get standard input */
if (dup2(mpvect
[0], STDIN_FILENO
) < 0)
syserr("%s... openmailer(%s): cannot dup pipe %d for stdin",
e
->e_to
, m
->m_name
, mpvect
[0]);
/* arrange for all the files to be closed */
for (i
= 3; i
< DtableSize
; i
++)
if ((j
= fcntl(i
, F_GETFD
, 0)) != -1)
(void) fcntl(i
, F_SETFD
, j
| 1);
** Set up the mailer environment
** _FORCE_MAIL_LOCAL_ is DG-UX equiv of -d flag.
** TZ is timezone information.
** SYSTYPE is Apollo software sys type (required).
** ISP is Apollo hardware system type (required).
env
[i
++] = "AGENT=sendmail";
env
[i
++] = "_FORCE_MAIL_LOCAL_=yes";
for (ep
= environ
; *ep
!= NULL
; ep
++)
if (strncmp(*ep
, "TZ=", 3) == 0 ||
strncmp(*ep
, "ISP=", 4) == 0 ||
strncmp(*ep
, "SYSTYPE=", 8) == 0)
/* run disconnected from terminal */
/* try to execute the mailer */
execve(m
->m_mailer
, pv
, env
);
syserr("Cannot exec %s", m
->m_mailer
);
if (bitnset(M_LOCALMAILER
, m
->m_flags
) ||
transienterror(saveerrno
))
mci
= (MCI
*) xalloc(sizeof *mci
);
bzero((char *) mci
, sizeof *mci
);
mci
->mci_state
= clever
? MCIS_OPENING
: MCIS_OPEN
;
mci
->mci_out
= fdopen(mpvect
[1], "w");
if (mci
->mci_out
== NULL
)
syserr("deliver: cannot create mailer output channel, fd=%d",
mci
->mci_in
= fdopen(rpvect
[0], "r");
syserr("deliver: cannot create mailer input channel, fd=%d",
mci
->mci_flags
|= MCIF_TEMP
;
if (bitset(EF_HAS8BIT
, e
->e_flags
) && bitnset(M_7BITS
, m
->m_flags
))
mci
->mci_flags
|= MCIF_CVT8TO7
;
** If we are in SMTP opening state, send initial protocol.
if (clever
&& mci
->mci_state
!= MCIS_CLOSED
)
if (mci
->mci_state
!= MCIS_OPEN
)
/* couldn't open the mailer */
rcode
= mci
->mci_exitstat
;
h_errno
= mci
->mci_herrno
;
syserr("554 deliver: rcode=%d, mci_state=%d, sig=%s",
rcode
, mci
->mci_state
, firstsig
);
else if (rcode
== EX_TEMPFAIL
&& curhost
!= NULL
&& *curhost
!= '\0')
** Format and send message.
(*e
->e_puthdr
)(mci
, e
->e_header
, e
);
(*e
->e_putbody
)(mci
, e
, NULL
);
/* get the exit status */
rcode
= endmailer(mci
, e
, pv
);
** Send the MAIL FROM: protocol
rcode
= smtpmailfrom(m
, mci
, e
);
register char *t
= tobuf
;
/* send the recipient list */
for (to
= tochain
; to
!= NULL
; to
= to
->q_tchain
)
if ((i
= smtprcpt(to
, m
, mci
, e
)) != EX_OK
)
giveresponse(i
, m
, mci
, ctladdr
, e
);
for (p
= to
->q_paddr
; *p
; *t
++ = *p
++)
if (bitset(MCIF_CACHED
, mci
->mci_flags
))
rcode
= smtpdata(m
, mci
, e
);
/* now close the connection */
if (!bitset(MCIF_CACHED
, mci
->mci_flags
))
if (rcode
!= EX_OK
&& curhost
!= NULL
&& *curhost
!= '\0')
syserr("554 deliver: need SMTP compiled to use clever mailer");
_res
.options
|= RES_DEFNAMES
| RES_DNSRCH
; /* XXX */
/* arrange a return receipt if requested */
if (rcode
== EX_OK
&& e
->e_receiptto
!= NULL
&&
bitnset(M_LOCALMAILER
, m
->m_flags
))
e
->e_flags
|= EF_SENDRECEIPT
;
/* do we want to send back more info? */
** Do final status disposal.
** We check for something in tobuf for the SMTP case.
** If we got a temporary failure, arrange to queue the
giveresponse(rcode
, m
, mci
, ctladdr
, e
);
for (to
= tochain
; to
!= NULL
; to
= to
->q_tchain
)
markfailure(e
, to
, rcode
);
if (e
->e_receiptto
!= NULL
&&
bitnset(M_LOCALMAILER
, m
->m_flags
))
fprintf(e
->e_xfp
, "%s... Successfully delivered\n",
** Restore state and return.
/* make absolutely certain 0, 1, and 2 are in use */
sprintf(wbuf
, "%s... end of deliver(%s)",
e
->e_to
== NULL
? "NO-TO-LIST" : e
->e_to
,
define('g', (char *) NULL
, e
);
** MARKFAILURE -- mark a failure on a specific address.
** e -- the envelope we are sending.
** q -- the address to mark.
** rcode -- the code signifying the particular failure.
** 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.
** 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.
** e -- the current envelope.
** pv -- the parameter vector that invoked the mailer
/* close any connections */
(void) xfclose(mci
->mci_in
, mci
->mci_mailer
->m_name
, "mci_in");
if (mci
->mci_out
!= NULL
)
(void) xfclose(mci
->mci_out
, mci
->mci_mailer
->m_name
, "mci_out");
mci
->mci_in
= mci
->mci_out
= NULL
;
mci
->mci_state
= MCIS_CLOSED
;
/* in the IPC case there is nothing to wait for */
/* wait for the mailer process to die and collect status */
st
= waitfor(mci
->mci_pid
);
syserr("endmailer %s: wait", pv
[0]);
/* normal death -- return status */
return (WEXITSTATUS(st
));
/* it died a horrid death */
syserr("451 mailer %s died with signal %o",
mci
->mci_mailer
->m_name
, st
);
if (pv
!= NULL
&& e
->e_xfp
!= NULL
)
fprintf(e
->e_xfp
, "Arguments:");
for (av
= pv
; *av
!= NULL
; av
++)
fprintf(e
->e_xfp
, " %s", *av
);
** GIVERESPONSE -- Interpret an error response from a mailer
** stat -- the status code from the mailer (high byte
** only; core dumps must have been taken care of
** 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
** e -- the current envelope.
** Errors may be incremented.
giveresponse(stat
, m
, mci
, ctladdr
, e
)
register const char *statmsg
;
** Compute status message from code.
if (e
->e_statmsg
!= NULL
)
(void) sprintf(buf
, "%s (%s)", statmsg
, e
->e_statmsg
);
else if (i
< 0 || i
> N_SysEx
)
(void) sprintf(buf
, "554 unknown mailer error %d", stat
);
else if (stat
== EX_TEMPFAIL
)
(void) strcpy(buf
, SysExMsg
[i
] + 1);
if (h_errno
== TRY_AGAIN
)
statmsg
= errstring(h_errno
+E_DNSBASE
);
statmsg
= errstring(errno
);
if (statmsg
!= NULL
&& statmsg
[0] != '\0')
(void) strcat(buf
, ": ");
(void) strcat(buf
, statmsg
);
else if (stat
== EX_NOHOST
&& h_errno
!= 0)
statmsg
= errstring(h_errno
+ E_DNSBASE
);
(void) sprintf(buf
, "%s (%s)", SysExMsg
[i
] + 1, statmsg
);
(void) sprintf(buf
, "%s: %s", statmsg
, errstring(errno
));
** Print the message as appropriate
if (stat
== EX_OK
|| stat
== EX_TEMPFAIL
)
message("%s", &statmsg
[4]);
if (stat
== EX_TEMPFAIL
&& e
->e_xfp
!= NULL
)
fprintf(e
->e_xfp
, "%s\n", &MsgBuf
[4]);
sprintf(mbuf
, "%.3s %%s", statmsg
);
usrerr(mbuf
, &statmsg
[4]);
** Log a record of the transaction. Compute the new
** ExitStat -- if we already had an error, stick with
if (LogLevel
> ((stat
== EX_TEMPFAIL
) ? 8 : (stat
== EX_OK
) ? 7 : 6))
logdelivery(m
, mci
, &statmsg
[4], ctladdr
, e
);
printf("giveresponse: stat=%d, e->e_message=%s\n",
if (stat
!= EX_OK
&& (stat
!= EX_TEMPFAIL
|| e
->e_message
== NULL
))
if (e
->e_message
!= NULL
)
e
->e_message
= newstr(&statmsg
[4]);
** 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.
** m -- the mailer info. Can be NULL for initial queue.
** mci -- the mailer connection info -- can be NULL if the
** log is occuring when no connection is active.
** stat -- the message to print for the status.
** ctladdr -- the controlling address for the to list.
** e -- the current envelope.
logdelivery(m
, mci
, stat
, ctladdr
, e
)
# if (SYSLOG_BUFSIZE) >= 256
strcpy(bp
, ", ctladdr=");
strcat(bp
, shortenstring(ctladdr
->q_paddr
, 83));
if (bitset(QGOODUID
, ctladdr
->q_flags
))
(void) sprintf(bp
, " (%d/%d)",
ctladdr
->q_uid
, ctladdr
->q_gid
);
(void) sprintf(bp
, ", delay=%s", pintvl(curtime() - e
->e_ctime
, TRUE
));
(void) strcpy(bp
, ", mailer=");
(void) strcat(bp
, m
->m_name
);
if (mci
!= NULL
&& mci
->mci_host
!= NULL
)
extern SOCKADDR CurHostAddr
;
(void) strcpy(bp
, ", relay=");
(void) strcat(bp
, mci
->mci_host
);
(void) strcat(bp
, anynet_ntoa(&CurHostAddr
));
else if (strcmp(stat
, "queued") != 0)
char *p
= macvalue('h', e
);
if (p
!= NULL
&& p
[0] != '\0')
(void) strcpy(bp
, ", relay=");
#define STATLEN (((SYSLOG_BUFSIZE) - 100) / 4)
if ((bp
- buf
) > (sizeof buf
- ((STATLEN
) + 20)))
/* desperation move -- truncate data */
bp
= buf
+ sizeof buf
- ((STATLEN
) + 17);
(void) strcpy(bp
, ", stat=");
(void) strcpy(bp
, shortenstring(stat
, (STATLEN
)));
l
= SYSLOG_BUFSIZE
- 100 - strlen(buf
);
register char *q
= strchr(p
+ l
, ',');
syslog(LOG_INFO
, "%s: to=%.*s [more]%s",
e
->e_id
, ++q
- p
, p
, buf
);
syslog(LOG_INFO
, "%s: to=%s%s", e
->e_id
, p
, buf
);
# else /* we have a very short log buffer size */
register char *q
= strchr(p
+ l
, ',');
syslog(LOG_INFO
, "%s: to=%.*s [more]",
syslog(LOG_INFO
, "%s: to=%s", e
->e_id
, p
);
strcpy(bp
, shortenstring(ctladdr
->q_paddr
, 83));
if (bitset(QGOODUID
, ctladdr
->q_flags
))
(void) sprintf(bp
, " (%d/%d)",
ctladdr
->q_uid
, ctladdr
->q_gid
);
syslog(LOG_INFO
, "%s: %s", e
->e_id
, buf
);
sprintf(bp
, "delay=%s", pintvl(curtime() - e
->e_ctime
, TRUE
));
sprintf(bp
, ", mailer=%s", m
->m_name
);
syslog(LOG_INFO
, "%s: %s", e
->e_id
, buf
);
if (mci
!= NULL
&& mci
->mci_host
!= NULL
)
extern SOCKADDR CurHostAddr
;
sprintf(buf
, "relay=%s", mci
->mci_host
);
(void) strcat(buf
, " [");
(void) strcat(buf
, anynet_ntoa(&CurHostAddr
));
else if (strcmp(stat
, "queued") != 0)
char *p
= macvalue('h', e
);
if (p
!= NULL
&& p
[0] != '\0')
sprintf(buf
, "relay=%s", p
);
syslog(LOG_INFO
, "%s: %s", e
->e_id
, buf
);
syslog(LOG_INFO
, "%s: stat=%s", e
->e_id
, shortenstring(stat
, 63));
# endif /* short log buffer */
** PUTFROMLINE -- output a UNIX-style from line (or whatever)
** then passes the rest of the message through. If we have
** managed to extract a date already, use that; otherwise,
** use the current date/time.
** 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????
** mci -- the connection information.
** outputs some text to fp.
char *template = "\201l\n";
if (bitnset(M_NHDR
, mci
->mci_mailer
->m_flags
))
if (bitnset(M_UGLYUUCP
, mci
->mci_mailer
->m_flags
))
expand("\201g", buf
, &buf
[sizeof buf
- 1], e
);
syserr("554 No ! in UUCP From address! (%s given)", buf
);
(void) sprintf(xbuf
, "From %s \201d remote from %s\n", bang
, buf
);
expand(template, buf
, &buf
[sizeof buf
- 1], e
);
** PUTBODY -- put the body of a message.
** 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.
** The message is written onto fp.
/* values for output state variable */
#define OS_HEAD 0 /* at beginning of line */
#define OS_CR 1 /* read a carriage return */
#define OS_INLINE 2 /* putting rest of line */
putbody(mci
, e
, separator
)
** Output the body of the message
e
->e_dfp
= fopen(e
->e_df
, "r");
syserr("putbody: Cannot open %s for %s from %s",
e
->e_df
, e
->e_to
, e
->e_from
.q_paddr
);
if (bitset(MCIF_INHEADER
, mci
->mci_flags
))
mci
->mci_flags
&= ~MCIF_INHEADER
;
putline("<<< No Message Collected >>>", mci
);
if (e
->e_dfp
!= NULL
&& e
->e_dfino
== (ino_t
) 0)
if (fstat(fileno(e
->e_dfp
), &stbuf
) < 0)
e
->e_dfdev
= stbuf
.st_dev
;
e
->e_dfino
= stbuf
.st_ino
;
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
);
/* as recommended by RFC 1428 section 3... */
if (hvalue("Content-Type", e
->e_header
) == NULL
)
putline("Content-Type: text/plain; charset=unknown-8bit", mci
);
/* now do the hard work */
mime8to7(mci
, e
->e_header
, e
, NULL
);
/* we can pass it through unmodified */
if (bitset(MCIF_INHEADER
, mci
->mci_flags
))
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 */
while (!ferror(mci
->mci_out
))
else if ((c
= fgetc(e
->e_dfp
)) == EOF
)
if (bitset(MCIF_7BIT
, mci
->mci_flags
))
if (c
!= '\r' && c
!= '\n' && bp
< buflim
)
/* check beginning of line for special cases */
bitnset(M_ESCFROM
, mci
->mci_mailer
->m_flags
) &&
strncmp(buf
, "From ", 5) == 0)
if (buf
[0] == '-' && buf
[1] == '-' &&
int sl
= strlen(separator
);
if (strncmp(&buf
[2], separator
, sl
) == 0)
bitnset(M_XDOT
, mci
->mci_mailer
->m_flags
))
/* now copy out saved line */
if (TrafficLogFile
!= NULL
)
fprintf(TrafficLogFile
, "%05d >>> ", getpid());
fputc(padc
, TrafficLogFile
);
for (xp
= buf
; xp
< bp
; xp
++)
fputc(*xp
, TrafficLogFile
);
fputs(mci
->mci_mailer
->m_eol
,
fputc(padc
, mci
->mci_out
);
for (xp
= buf
; xp
< bp
; xp
++)
fputc(*xp
, mci
->mci_out
);
fputs(mci
->mci_mailer
->m_eol
,
/* determine next state */
fputs(mci
->mci_mailer
->m_eol
, mci
->mci_out
);
if (TrafficLogFile
!= NULL
)
fputs(mci
->mci_mailer
->m_eol
,
/* had a naked carriage return */
if (mci
->mci_mailer
->m_linelimit
> 0 &&
pos
> mci
->mci_mailer
->m_linelimit
&&
fputs(mci
->mci_mailer
->m_eol
, mci
->mci_out
);
if (TrafficLogFile
!= NULL
)
fprintf(TrafficLogFile
, "!%s",
if (TrafficLogFile
!= NULL
)
fputc(c
, TrafficLogFile
);
ostate
= c
== '\n' ? OS_HEAD
: OS_INLINE
;
syserr("putbody: %s: read error", e
->e_df
);
/* some mailers want extra blank line at end of message */
if (bitnset(M_BLANKEND
, mci
->mci_mailer
->m_flags
) &&
buf
[0] != '\0' && buf
[0] != '\n')
(void) fflush(mci
->mci_out
);
if (ferror(mci
->mci_out
) && errno
!= EPIPE
)
syserr("putbody: write error");
** MAILFILE -- Send a message to a file.
** If the file has the setuid/setgid 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.
** filename -- the name of the file to send to.
** ctladdr -- the controlling address header -- includes
** the userid/groupid to be when sending.
** The exit code associated with the operation.
mailfile(filename
, ctladdr
, e
)
printf("mailfile %s\n ctladdr=", filename
);
printaddr(ctladdr
, FALSE
);
** Fork so we can change permissions here.
** Note that we MUST use fork, not vfork, because of
** the complications of calling subroutines, etc.
/* child -- actually write to file */
(void) setsignal(SIGINT
, SIG_DFL
);
(void) setsignal(SIGHUP
, SIG_DFL
);
(void) setsignal(SIGTERM
, SIG_DFL
);
if (stat(filename
, &stb
) < 0)
/* limit the errors to those actually caused in the child */
if (bitset(0111, stb
.st_mode
))
/* ignore setuid and setgid bits */
mode
&= ~(S_ISGID
|S_ISUID
);
/* we have to open the dfile BEFORE setuid */
if (e
->e_dfp
== NULL
&& e
->e_df
!= NULL
)
e
->e_dfp
= fopen(e
->e_df
, "r");
syserr("mailfile: Cannot open %s for %s from %s",
e
->e_df
, e
->e_to
, e
->e_from
.q_paddr
);
if (!bitset(S_ISGID
, mode
) || setgid(stb
.st_gid
) < 0)
if (ctladdr
== NULL
|| ctladdr
->q_uid
== 0)
(void) initgroups(DefUser
, DefGid
);
(void) initgroups(ctladdr
->q_ruser
?
ctladdr
->q_ruser
: ctladdr
->q_user
,
if (!bitset(S_ISUID
, mode
) || setuid(stb
.st_uid
) < 0)
if (ctladdr
== NULL
|| ctladdr
->q_uid
== 0)
(void) setuid(ctladdr
->q_uid
);
f
= dfopen(filename
, O_WRONLY
|O_CREAT
|O_APPEND
, FileMode
);
message("554 cannot open: %s", errstring(errno
));
bzero(&mcibuf
, sizeof mcibuf
);
mcibuf
.mci_mailer
= FileMailer
;
if (bitnset(M_7BITS
, FileMailer
->m_flags
))
mcibuf
.mci_flags
|= MCIF_7BIT
;
(*e
->e_puthdr
)(&mcibuf
, e
->e_header
, e
);
(*e
->e_putbody
)(&mcibuf
, e
, NULL
);
message("451 I/O error: %s", errstring(errno
));
(void) xfclose(f
, "mailfile", filename
);
/* reset ISUID & ISGID bits for paranoid systems */
(void) chmod(filename
, (int) stb
.st_mode
);
/* parent -- wait for exit status */
return (WEXITSTATUS(st
));
syserr("child died on signal %d", st
);
** 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.
** m -- the mailer describing this host.
** host -- the host name.
** e -- the current envelope.
** The signature for this host.
** Can tweak the symbol table.
hostsignature(m
, host
, e
)
char *mxhosts
[MAXMXHOSTS
+ 1];
** Check to see if this uses IPC -- if not, it can't have MX records.
if (strcmp(p
, "[IPC]") != 0 && strcmp(p
, "[TCP]") != 0)
/* just an ordinary mailer */
** Look it up in the symbol table.
s
= stab(host
, ST_HOSTSIG
, ST_ENTER
);
if (s
->s_hostsig
!= NULL
)
** Not already there -- create a signature.
oldoptions
= _res
.options
;
_res
.options
&= ~(RES_DEFNAMES
| RES_DNSRCH
); /* XXX */
for (hp
= host
; hp
!= NULL
; hp
= endp
)
nmx
= getmxrr(hp
, mxhosts
, TRUE
, &rcode
);
/* update the connection info for this host */
mci
->mci_exitstat
= rcode
;
mci
->mci_herrno
= h_errno
;
/* and return the original host name as the signature */
for (i
= 0; i
< nmx
; i
++)
len
+= strlen(mxhosts
[i
]) + 1;
if (s
->s_hostsig
!= NULL
)
len
+= strlen(s
->s_hostsig
) + 1;
if (s
->s_hostsig
!= NULL
)
(void) strcpy(p
, s
->s_hostsig
);
for (i
= 0; i
< nmx
; i
++)
_res
.options
= oldoptions
;
/* not using BIND -- the signature is just the host name */
printf("hostsignature(%s) = %s\n", host
, s
->s_hostsig
);