SCCSID(@
(#)deliver.c 3.118 %G%);
** 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
** 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 struct mailer
*m
; /* mailer for this recipient */
extern bool checkcompat();
char tobuf
[MAXLINE
-50]; /* text line of to people */
extern ADDRESS
*getctladdr();
char tfrombuf
[MAXNAME
]; /* translated from person */
register ADDRESS
*to
= firstto
;
bool clever
= FALSE
; /* running user smtp to this mailer */
ADDRESS
*tochain
= NULL
; /* chain of users in this mailer call */
bool notopen
= TRUE
; /* set if connection not quite open */
register int rcode
; /* response code */
if (!ForceMail
&& bitset(QDONTSEND
|QPSEUDO
, 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
&& bitset(M_EXPENSIVE
, m
->m_flags
))
for (; to
!= NULL
; to
= to
->q_next
)
if (!bitset(QDONTSEND
, to
->q_flags
) &&
to
->q_mailer
== firstto
->q_mailer
)
to
->q_flags
|= QQUEUEUP
|QDONTSEND
;
** 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], CurEnv
);
mvp
= prescan(buf
, '\0');
rewrite(mvp
, m
->m_s_rwset
);
cataddr(mvp
, tfrombuf
, sizeof tfrombuf
);
define('g', tfrombuf
); /* translated sender address */
define('h', host
); /* to host */
/* insert -f or -r flag as appropriate */
if (bitset(M_FOPT
|M_ROPT
, m
->m_flags
) && FromFlag
)
if (bitset(M_FOPT
, m
->m_flags
))
expand("$g", buf
, &buf
[sizeof buf
- 1], CurEnv
);
** 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], CurEnv
);
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' && !bitset(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
) + 1) < 0)
/* compute effective uid/gid when sending */
if (to
->q_mailer
== ProgMailer
)
ctladdr
= getctladdr(to
);
CurEnv
->e_to
= to
->q_paddr
;
to
->q_flags
|= QDONTSEND
;
** Check to see that these people are allowed to
giveresponse(EX_UNAVAILABLE
, TRUE
, m
);
** Strip quote bits from names if the mailer is dumb
if (bitset(M_STRIPQ
, m
->m_flags
))
stripquotes(user
, FALSE
);
stripquotes(host
, FALSE
);
** Do initial connection setup if needed.
message(Arpa_Info
, "Connecting to %s.%s...", host
, m
->m_name
);
/* send the initial SMTP protocol */
rcode
= smtpinit(m
, pv
, (ADDRESS
*) NULL
);
** Pass it to the other host if we are running SMTP.
if (rcode
== EX_TEMPFAIL
)
giveresponse(rcode
, TRUE
, m
);
syserr("trying to be clever");
** 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.... */
Stat
.stat_nt
[to
->q_mailer
->m_mno
]++;
Stat
.stat_bt
[to
->q_mailer
->m_mno
] += kbytes(CurEnv
->e_msgsize
);
** 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
, TRUE
, m
);
** 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
); /* to user */
define('z', to
->q_home
); /* user's home */
** Expand out this user into argument list.
expand(*mvp
, buf
, &buf
[sizeof buf
- 1], CurEnv
);
if (pvp
>= &pv
[MAXPV
- 2])
/* allow some space for trailing parms */
/* see if any addresses still exist */
define('g', (char *) NULL
);
/* 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], CurEnv
);
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.
ctladdr
= &CurEnv
->e_from
;
rcode
= smtpfinish(m
, CurEnv
);
giveresponse(rcode
, TRUE
, m
);
smtpquit(pv
[0], rcode
== EX_OK
);
rcode
= sendoff(m
, pv
, ctladdr
);
** If we got a temporary failure, arrange to queue the
if (rcode
== EX_TEMPFAIL
)
for (to
= tochain
; to
!= NULL
; to
= to
->q_tchain
)
define('g', (char *) NULL
);
** 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.
** 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.
** Create connection to mailer.
pid
= openmailer(m
, pvp
, ctladdr
, FALSE
, &mfile
, &rfile
);
** Format and send message.
(void) signal(SIGPIPE
, SIG_IGN
);
(*CurEnv
->e_puthdr
)(mfile
, m
, CurEnv
);
(*CurEnv
->e_putbody
)(mfile
, m
, FALSE
);
i
= endmailer(pid
, pvp
[0]);
giveresponse(i
, TRUE
, m
);
/* arrange a return receipt if requested */
if (CurEnv
->e_receiptto
!= NULL
&& bitset(M_LOCAL
, m
->m_flags
))
CurEnv
->e_sendreceipt
= TRUE
;
fprintf(Xscript
, "%s... successfully delivered\n",
/* 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 */
} while ((i
>= 0 && i
!= pid
) || errno
== EINTR
);
/* see if it died a horrid death */
syserr("%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.
if (strcmp(m
->m_mailer
, "[IPC]") == 0)
syserr("non-clever IPC");
i
= makeconnection(pvp
[1], port
, pmfile
, prfile
);
/* create a pipe to shove the mail through */
syserr("pipe (to mailer)");
/* if this mailer speaks smtp, create a return pipe */
if (clever
&& pipe(rpvect
) < 0)
syserr("pipe (from mailer)");
** Actually fork the mailer process.
** DOFORK is clever about retrying.
(void) fflush(Xscript
); /* for debugging */
/* pid is set by DOFORK */
/* 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 (OutChannel
!= stdout
)
(void) dup(fileno(OutChannel
));
/* arrange to get standard input */
syserr("Cannot dup to zero!");
if (!bitset(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
** force -- if set, force an error message output, even
** if the mailer seems to like to print its own
** m -- the mailer descriptor for this mailer.
** Errors may be incremented.
giveresponse(stat
, force
, m
)
register struct mailer
*m
;
** Compute status message from code.
if (i
< 0 || i
> N_SysEx
)
message(Arpa_Info
, &statmsg
[4]);
else if (stat
== EX_TEMPFAIL
)
message(Arpa_Info
, "deferred");
if (statmsg
== NULL
&& m
->m_badstat
!= 0)
if (i
< 0 || i
>= N_SysEx
)
syserr("Bad m_badstat %d", stat
);
usrerr("unknown mailer response %d", stat
);
else if (force
|| !bitset(M_QUIET
, m
->m_flags
) || Verbose
)
fprintf(Xscript
, "%s\n", &statmsg
[4]);
** Log a record of the transaction. Compute the new
** ExitStat -- if we already had an error, stick with
(void) sprintf(buf
, "554 error %d", stat
);
if (LogLevel
> ((stat
== 0 || stat
== EX_TEMPFAIL
) ? 3 : 2))
syslog(LOG_INFO
, "%s: to=%s, delay=%s, stat=%s", CurEnv
->e_id
,
CurEnv
->e_to
, pintvl(curtime() - CurEnv
->e_ctime
, TRUE
),
** 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????
** fp -- the file to output to.
** m -- the mailer describing this entry.
** outputs some text to fp.
if (bitset(M_NHDR
, m
->m_flags
))
if (bitset(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
);
putline(buf
, fp
, bitset(M_FULLSMTP
, m
->m_flags
));
** PUTHEADER -- put the header part of a message from the in-core copy
** fp -- file to put it on.
register struct mailer
*m
;
extern char *capitalize();
bool fullsmtp
= bitset(M_FULLSMTP
, m
->m_flags
);
for (h
= e
->e_header
; h
!= NULL
; h
= h
->h_link
)
if (bitset(H_CHECK
|H_ACHECK
, h
->h_flags
) && !bitset(h
->h_mflags
, m
->m_flags
))
if (bitset(H_DEFAULT
, h
->h_flags
))
/* macro expand value if generated internally */
expand(p
, buf
, &buf
[sizeof buf
], e
);
if (p
== NULL
|| *p
== '\0')
if (bitset(H_FROM
|H_RCPT
, h
->h_flags
))
bool oldstyle
= e
->e_oldstyle
;
if (bitset(H_FROM
, h
->h_flags
))
commaize(h
, p
, fp
, oldstyle
, m
);
/* vanilla header line */
(void) sprintf(obuf
, "%s: %s\n", capitalize(h
->h_field
), p
);
putline(obuf
, fp
, fullsmtp
);
** COMMAIZE -- output a header field, making a comma-translated list.
** h -- the header field to output.
** p -- the value to put in it.
** fp -- file to put it to.
** oldstyle -- TRUE if this is an old style header.
** m -- a pointer to the mailer descriptor. If NULL,
** don't transform the name at all.
** outputs "p" to file "fp".
commaize(h
, p
, fp
, oldstyle
, m
)
** Output the address list translated by the
** mailer and with commas.
printf("commaize(%s: %s)\n", h
->h_field
, p
);
if (m
!= NULL
&& bitset(M_FULLSMTP
, m
->m_flags
))
(void) sprintf(obp
, "%s: ", capitalize(h
->h_field
));
opos
= strlen(h
->h_field
) + 2;
** Run through the list of values.
extern char *remotename();
extern char *DelimChar
; /* defined in prescan */
** Find the end of the name. New style names
** end with a comma, old style names end with
** a space character. However, spaces do not
** necessarily delimit an old-style name -- at
** signs mean keep going.
while (isspace(*p
) || *p
== ',')
(void) prescan(p
, oldstyle
? ' ' : ',');
/* look to see if we have an at sign */
while (*p
!= '\0' && isspace(*p
))
if (*p
!= '@' && !isatword(p
))
while (*p
!= '\0' && isspace(*p
))
/* at the end of one complete name */
/* strip off trailing white space */
while (p
>= name
&& (isspace(*p
) || *p
== ',' || *p
== '\0'))
/* translate the name to be relative */
name
= remotename(name
, m
, bitset(H_FROM
, h
->h_flags
));
/* output the name with nice formatting */
if (opos
> 78 && !firstone
)
(void) sprintf(obp
, ",\n");
putline(obuf
, fp
, fullsmtp
);
(void) sprintf(obp
, " ");
(void) sprintf(obp
, ", ");
(void) sprintf(obp
, "%s", name
);
(void) strcpy(obp
, "\n");
putline(obuf
, fp
, fullsmtp
);
** PUTBODY -- put the body of a message.
** fp -- file to output onto.
** m -- a mailer descriptor.
** xdot -- if set, use SMTP hidden dot algorithm.
** The message is written onto fp.
bool fullsmtp
= bitset(M_FULLSMTP
, m
->m_flags
);
** Output the body of the message
/* m will be needed later for complete smtp emulation */
while (!ferror(fp
) && fgets(&buf
[1], sizeof buf
- 1, TempFile
) != NULL
)
putline((xdot
&& buf
[1] == '.') ? buf
: &buf
[1], fp
, fullsmtp
);
syserr("putbody: read error");
if (ferror(fp
) && errno
!= EPIPE
)
syserr("putbody: write error");
** ISATWORD -- tell if the word we are pointing to is "at".
** TRUE -- if p is the word at.
if (lower(p
[0]) == 'a' && lower(p
[1]) == 't' &&
p
[2] != '\0' && isspace(p
[2]))
** SAMEFROM -- tell if two text addresses represent the same from address.
** ifrom -- internally generated form of from address.
** efrom -- external form of from address.
** TRUE -- if they convey the same info.
** FALSE -- if any information has been lost.
printf("samefrom(%s,%s)-->", ifrom
, efrom
);
if (strcmp(ifrom
, efrom
) == 0)
(void) strcpy(buf
, ifrom
);
(void) strcat(buf
, " at ");
if (strcmp(buf
, efrom
) == 0)
** 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.
** 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
, Mailer
[1]);
(*CurEnv
->e_puthdr
)(f
, Mailer
[1], CurEnv
);
(*CurEnv
->e_putbody
)(f
, Mailer
[1], FALSE
);
/* reset ISUID & ISGID bits for paranoid systems */
(void) chmod(filename
, (int) stb
.st_mode
);
/* parent -- wait for exit status */
while ((i
= wait(&stat
)) != pid
)
stat
= EX_UNAVAILABLE
<< 8;
return ((stat
>> 8) & 0377);
** SENDALL -- actually send all the messages.
** e -- the envelope to send.
** verifyonly -- if set, only give verification messages.
** Scans the send lists and sends everything it finds.
** Delivers any appropriate error messages.
printf("\nSENDALL: verify %d, sendqueue:\n");
printaddr(e
->e_sendqueue
, TRUE
);
** Run through the list and send everything.
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
CurEnv
->e_to
= q
->q_paddr
;
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
)
if (bitset(QQUEUEUP
, 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 (!bitset(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
);
/* add in an errors-to field */
/* ugh... must happen before delivery.....
addheader("errors-to", newstr(obuf), e);
.... i guess this should go in sendto */
/* only send errors if the message failed */
if (!bitset(QBADADDR
, q
->q_flags
))
/* owner list exists -- add it to the error queue */
qq
->q_flags
&= ~QPRIMARY
;
sendto(obuf
, qq
, &e
->e_errorqueue
);
/* if we did not find an owner, send to the sender */
if (qq
== NULL
&& bitset(QBADADDR
, q
->q_flags
))
sendto(e
->e_from
.q_paddr
, qq
, &e
->e_errorqueue
);
** CHECKERRORS -- check a queue of addresses and process errors.
** e -- the envelope to check.
** Arranges to queue all tempfailed messages in q
** or deliver error responses.
printf("\ncheckerrors: FatalErrors %d, errorqueue:\n");
printaddr(e
->e_errorqueue
, TRUE
);
/* mail back the transcript on errors */
/* queue up anything laying around */
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
if (bitset(QQUEUEUP
, q
->q_flags
))
e
->e_df
= e
->e_qf
= NULL
;
syserr("checkerrors: trying to queue %s", e
->e_df
);