SCCSID(@
(#)deliver.c 3.149 2/20/83);
** 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 */
register int rcode
; /* response code */
char tobuf
[MAXLINE
-50]; /* text line of to people */
char tfrombuf
[MAXNAME
]; /* translated from person */
extern bool checkcompat();
extern ADDRESS
*getctladdr();
extern char *remotename();
if (bitset(QDONTSEND
, to
->q_flags
))
printf("\n--deliver, mailer=%d, host=`%s', first user=`%s'\n",
m
->m_mno
, 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
&& !QueueRun
&& bitnset(M_EXPENSIVE
, m
->m_flags
) &&
for (; to
!= NULL
; to
= to
->q_next
)
if (bitset(QDONTSEND
, to
->q_flags
) || to
->q_mailer
!= m
)
to
->q_flags
|= QQUEUEUP
|QDONTSEND
;
message(Arpa_Info
, "queued");
** 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 */
expand("$f", buf
, &buf
[sizeof buf
- 1], e
);
(void) strcpy(tfrombuf
, remotename(buf
, m
, TRUE
, TRUE
));
define('g', tfrombuf
, e
); /* translated sender address */
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
))
expand("$g", buf
, &buf
[sizeof buf
- 1], e
);
** 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
; )
while ((p
= index(p
, '$')) != NULL
)
/* this entry is safe -- go ahead and process it */
expand(*mvp
, buf
, &buf
[sizeof buf
- 1], e
);
if (pvp
>= &pv
[MAXPV
- 3])
syserr("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("SMTP style mailer");
** 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.
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
, to
->q_flags
) ||
strcmp(to
->q_host
, host
) != 0 ||
to
->q_mailer
!= firstto
->q_mailer
)
/* avoid overflowing tobuf */
if (sizeof tobuf
- (strlen(to
->q_paddr
) + strlen(tobuf
) + 2) < 0)
/* compute effective uid/gid when sending */
if (to
->q_mailer
== ProgMailer
)
ctladdr
= getctladdr(to
);
to
->q_flags
|= QDONTSEND
;
** Check to see that these people are allowed to
if (m
->m_maxsize
!= 0 && e
->e_msgsize
> m
->m_maxsize
)
usrerr("Message is too large; %ld bytes max", m
->m_maxsize
);
giveresponse(EX_UNAVAILABLE
, m
, e
);
giveresponse(EX_UNAVAILABLE
, m
, e
);
** Strip quote bits from names if the mailer is dumb
if (bitnset(M_STRIPQ
, m
->m_flags
))
stripquotes(user
, FALSE
);
stripquotes(host
, FALSE
);
/* 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
, getctladdr(to
));
giveresponse(rcode
, m
, 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 */
define('z', to
->q_home
, 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("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.
message(Arpa_Info
, "Connecting to %s.%s...", host
, m
->m_name
);
/* send the initial SMTP protocol */
/* send the recipient list */
for (to
= tochain
; to
!= NULL
; to
= to
->q_tchain
)
strcat(tobuf
, to
->q_paddr
);
/* now close the connection */
rcode
= sendoff(e
, m
, pv
, ctladdr
);
** 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
, e
);
for (to
= tochain
; to
!= NULL
; to
= to
->q_tchain
)
markfailure(e
, to
, rcode
);
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.
else if (rcode
!= EX_TEMPFAIL
)
else if (curtime() > e
->e_ctime
+ TimeOut
)
if (!bitset(EF_TIMEOUT
, e
->e_flags
))
(void) sprintf(buf
, "Cannot send message for %s",
if (e
->e_message
!= NULL
)
e
->e_message
= newstr(buf
);
e
->e_flags
|= EF_TIMEOUT
;
** 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; )\
** DOFORK -- simple fork interface to DOFORK.
** pid of child in parent.
** returns twice, once in parent and once in child.
** SENDOFF -- send off call to mailer & collect response.
** e -- the envelope to mail.
** m -- mailer descriptor.
** pvp -- parameter vector to send to it.
** ctladdr -- an address pointer controlling the
** user/groupid etc. of the mailer.
** exit status of mailer.
sendoff(e
, m
, pvp
, ctladdr
)
** Create connection to mailer.
pid
= openmailer(m
, pvp
, ctladdr
, FALSE
, &mfile
, &rfile
);
** Format and send message.
(*e
->e_puthdr
)(mfile
, m
, e
);
(*e
->e_putbody
)(mfile
, m
, e
);
i
= endmailer(pid
, pvp
[0]);
/* arrange a return receipt if requested */
if (e
->e_receiptto
!= NULL
&& bitnset(M_LOCAL
, m
->m_flags
))
e
->e_flags
|= EF_SENDRECEIPT
;
/* do we want to send back more info? */
** 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.
** name -- name of mailer (for error messages).
/* in the IPC case there is nothing to wait for */
/* wait for the mailer process to die and collect status */
syserr("endmailer %s: wait", name
);
/* see if it died a horrid death */
syserr("endmailer %s: stat %o", name
, st
);
ExitStat
= EX_UNAVAILABLE
;
/* normal death -- return status */
** OPENMAILER -- open connection to mailer.
** m -- mailer descriptor.
** pvp -- parameter vector to pass to mailer.
** ctladdr -- controlling address for user.
** clever -- create a full duplex connection.
** pmfile -- pointer to mfile (to mailer) connection.
** prfile -- pointer to rfile (from mailer) connection.
** pid of mailer ( > 0 ).
** zero on an IPC connection.
** creates a mailer in a subprocess.
openmailer(m
, pvp
, ctladdr
, clever
, pmfile
, prfile
)
** 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.
/* check for Local Person Communication -- not for mortals!!! */
if (strcmp(m
->m_mailer
, "[LPC]") == 0)
if (strcmp(m
->m_mailer
, "[IPC]") == 0)
syserr("non-clever IPC");
i
= makeconnection(pvp
[1], port
, pmfile
, prfile
);
syserr("openmailer: no IPC");
/* create a pipe to shove the mail through */
syserr("openmailer: pipe (to mailer)");
/* if this mailer speaks smtp, create a return pipe */
if (clever
&& pipe(rpvect
) < 0)
syserr("openmailer: pipe (from mailer)");
** Actually fork the mailer process.
** DOFORK is clever about retrying.
if (CurEnv
->e_xfp
!= NULL
)
(void) fflush(CurEnv
->e_xfp
); /* for debugging */
/* pid is set by DOFORK */
syserr("openmailer: cannot fork");
/* child -- set up input & exec mailer */
/* make diagnostic output be standard output */
(void) signal(SIGINT
, SIG_IGN
);
(void) signal(SIGHUP
, SIG_IGN
);
(void) signal(SIGTERM
, SIG_DFL
);
/* arrange to filter standard & diag output of command */
else if (OpMode
== MD_SMTP
|| HoldErrs
)
/* put mailer output in transcript */
(void) dup(fileno(CurEnv
->e_xfp
));
/* arrange to get standard input */
syserr("Cannot dup to zero!");
if (!bitnset(M_RESTR
, m
->m_flags
))
(void) setgid(ctladdr
->q_gid
);
(void) setuid(ctladdr
->q_uid
);
** We have to be careful with vfork - we can't mung up the
** memory but we don't want the mailer to inherit any extra
** open files. Chances are the mailer won't
** care about an extra file, but then again you never know.
** Actually, we would like to close(fileno(pwf)), but it's
** declared static so we can't. But if we fclose(pwf), which
** is what endpwent does, it closes it in the parent too and
** the next getpwnam will be slower. If you have a weird
** mailer that chokes on the extra file you should do the
** Similar comments apply to log. However, openlog is
** clever enough to set the FIOCLEX mode on the file,
** so it will be closed automatically on the exec.
/* try to execute the mailer */
/* syserr fails because log is closed */
/* syserr("Cannot exec %s", m->m_mailer); */
printf("Cannot exec '%s' errno=%d\n", m
->m_mailer
, errno
);
mfile
= fdopen(mpvect
[1], "w");
rfile
= fdopen(rpvect
[0], "r");
** 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 descriptor for this mailer.
** Errors may be incremented.
** Compute status message from code.
else if (i
< 0 || i
> N_SysEx
)
(void) sprintf(buf
, "554 unknown mailer error %d", stat
);
else if (stat
== EX_TEMPFAIL
)
extern char *sys_errlist
[];
(void) strcpy(buf
, SysExMsg
[i
]);
(void) strcat(buf
, ": ");
if (errno
> 0 && errno
< sys_nerr
)
(void) strcat(buf
, sys_errlist
[errno
]);
(void) sprintf(xbuf
, "Error %d", errno
);
(void) strcat(buf
, xbuf
);
** Print the message as appropriate
if (stat
== EX_OK
|| stat
== EX_TEMPFAIL
)
message(Arpa_Info
, &statmsg
[4]);
** Log a record of the transaction. Compute the new
** ExitStat -- if we already had an error, stick with
if (LogLevel
> ((stat
== 0 || stat
== EX_TEMPFAIL
) ? 3 : 2))
logdelivery(&statmsg
[4]);
if (e
->e_message
!= NULL
)
e
->e_message
= newstr(&statmsg
[4]);
** LOGDELIVERY -- log the delivery in the system log
** stat -- the message to print for the status
syslog(LOG_INFO
, "%s: to=%s, delay=%s, stat=%s", CurEnv
->e_id
,
CurEnv
->e_to
, pintvl(curtime() - CurEnv
->e_ctime
, TRUE
), stat
);
** 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
** "emote from <host>" lines. Why oh why does a
** well-meaning programmer such as myself have to
** deal with this kind of antique garbage????
** fp -- the file to output to.
** m -- the mailer describing this entry.
** outputs some text to fp.
if (bitnset(M_NHDR
, m
->m_flags
))
if (bitnset(M_UGLYUUCP
, m
->m_flags
))
char *sys
= macvalue('g', CurEnv
);
char *bang
= index(sys
, '!');
syserr("No ! in UUCP! (%s)", sys
);
expand("From $f $d remote from $g\n", buf
,
&buf
[sizeof buf
- 1], CurEnv
);
expand("$l\n", buf
, &buf
[sizeof buf
- 1], CurEnv
);
** PUTBODY -- put the body of a message.
** fp -- file to output onto.
** m -- a mailer descriptor to control output format.
** e -- the envelope to put out.
** The message is written onto fp.
** Output the body of the message
e
->e_dfp
= fopen(e
->e_df
, "r");
syserr("Cannot open %s", e
->e_df
);
putline("<<< No Message Collected >>>", fp
, m
);
while (!ferror(fp
) && fgets(buf
, sizeof buf
, e
->e_dfp
) != NULL
)
syserr("putbody: read error");
if (ferror(fp
) && 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
)
** 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) signal(SIGINT
, SIG_DFL
);
(void) signal(SIGHUP
, SIG_DFL
);
(void) signal(SIGTERM
, SIG_DFL
);
if (stat(filename
, &stb
) < 0)
if (bitset(0111, stb
.st_mode
))
ctladdr
= &CurEnv
->e_from
;
if (!bitset(S_ISGID
, stb
.st_mode
) || setgid(stb
.st_gid
) < 0)
(void) setgid(ctladdr
->q_gid
);
if (!bitset(S_ISUID
, stb
.st_mode
) || setuid(stb
.st_uid
) < 0)
(void) setuid(ctladdr
->q_uid
);
f
= dfopen(filename
, "a");
putfromline(f
, ProgMailer
);
(*CurEnv
->e_puthdr
)(f
, ProgMailer
, CurEnv
);
putline("\n", f
, ProgMailer
);
(*CurEnv
->e_putbody
)(f
, ProgMailer
, CurEnv
);
putline("\n", f
, ProgMailer
);
/* reset ISUID & ISGID bits for paranoid systems */
(void) chmod(filename
, (int) stb
.st_mode
);
/* parent -- wait for exit status */
return ((st
>> 8) & 0377);
** SENDALL -- actually send all the messages.
** e -- the envelope to send.
** mode -- the delivery mode to use.
** 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
printf("\nSENDALL: mode %c, sendqueue:\n", mode
);
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
> MAXHOP
)
syserr("sendall: too many hops (%d max)", MAXHOP
);
e
->e_from
.q_flags
|= QDONTSEND
;
recipient(&e
->e_from
, &e
->e_sendqueue
);
if ((mode
== SM_QUEUE
|| mode
== SM_FORK
||
(mode
!= SM_VERIFY
&& SuperSafe
)) &&
!bitset(EF_INQUEUE
, e
->e_flags
))
queueup(e
, TRUE
, mode
== SM_QUEUE
);
e
->e_flags
|= EF_INQUEUE
|EF_KEEPQUEUE
;
/* be sure we leave the temp files to our child */
e
->e_id
= e
->e_df
= NULL
;
/* double fork to avoid zombies */
/* be sure we are immune from the terminal */
** Run through the list and send everything.
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
if (!bitset(QDONTSEND
|QBADADDR
, q
->q_flags
))
message(Arpa_Info
, "deliverable");
** Now run through and check for errors.
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
/* only send errors if the message failed */
if (!bitset(QBADADDR
, q
->q_flags
))
/* we have an address that failed -- find the parent */
for (qq
= q
; qq
!= NULL
; qq
= qq
->q_alias
)
extern char *aliaslookup();
/* we can only have owners for local addresses */
if (!bitnset(M_LOCAL
, qq
->q_mailer
->m_flags
))
/* see if the owner list exists */
(void) strcpy(obuf
, "owner-");
if (strncmp(qq
->q_user
, "owner-", 6) == 0)
(void) strcat(obuf
, "owner");
(void) strcat(obuf
, qq
->q_user
);
if (aliaslookup(obuf
) == NULL
)
printf("Errors to %s\n", obuf
);
/* owner list exists -- add it to the error queue */
sendtolist(obuf
, (ADDRESS
*) NULL
, &e
->e_errorqueue
);
/* if we did not find an owner, send to the sender */
if (qq
== NULL
&& bitset(QBADADDR
, q
->q_flags
))
sendtolist(e
->e_from
.q_paddr
, qq
, &e
->e_errorqueue
);