SCCSID(@
(#)main.c 3.125 %G%);
** SENDMAIL -- Post mail to a set of destinations.
** This is the basic mail router. All user mail programs should
** call this routine to actually deliver mail. Sendmail in
** turn calls a bunch of mail servers that do the real work of
** Sendmail is driven by tables read in from /usr/lib/sendmail.cf
** (read by readcf.c). Some more static configuration info,
** including some code that you may want to tailor for your
** installation, is in conf.c. You may also want to touch
** daemon.c (if you have some other IPC mechanism), acct.c
** (to change your accounting), names.c (to adjust the name
** /etc/sendmail [flags] addr ...
** Positional Parameters:
** addr -- the address to deliver the mail to. There
** -f name The mail is from "name" -- used for
** the header in local mail, and to
** deliver reports of failures to.
** -r name Same as -f; however, this flag is
** reserved to indicate special processing
** for remote mail delivery as needed
** in the future. So, network servers
** -Ffullname Select what the full-name should be
** -a This mail should be in ARPANET std
** format (obsolete version).
** -n Don't do aliasing. This might be used
** when delivering responses, for
** -dN Run with debugging set to level N.
** -em Mail back a response if there was an
** error in processing. This should be
** used when the origin of this message
** -ew Write back a response if the user is
** still logged in, otherwise, act like
** -eq Don't print any error message (just
** -ep (default) Print error messages
** -ee Send BerkNet style errors. This
** is equivalent to MailBack except
** that it has gives zero return code
** (unless there were errors during
** returning). This used to be
** "EchoBack", but you know how the old
** -m In group expansion, send to the
** sender also (stands for the Mail metoo
** -i Do not terminate mail on a line
** -s Save UNIX-like "From" lines on the
** -v Give blow-by-blow description of
** everything that happens.
** -t Read "to" addresses from message.
** Looks at To:, Cc:, and Bcc: lines.
** -I Initialize the DBM alias files from
** the text format files.
** -Cfilename Use alternate configuration file.
** -Afilename Use alternate alias file.
** -DXvalue Define macro X to have value.
** -bv Verify addresses only.
** -bd Run as a daemon. Berkeley 4.2 only.
** -bf Fork after address verification.
** -bq Queue up for later delivery.
** -ba Process mail completely.
** As defined in <sysexits.h>.
** These codes are actually returned from the auxiliary
** mailers; it is their responsibility to make them
** LOG -- if set, everything is logged.
** Eric Allman, UCB/INGRES
int NextMailer
= 0; /* "free" index into Mailer struct */
static char *FullName
; /* sender's full name */
ENVELOPE MainEnvelope
; /* the envelope around the basic letter */
ERROR
%%%% Cannot have daemon mode without SMTP
%%%% ERROR
bool safecf
= TRUE
; /* this conf file is sys default */
char jbuf
[30]; /* holds HostName */
bool queuemode
= FALSE
; /* process queue requests */
extern time_t convtime();
extern putheader(), putbody();
static bool reenter
= FALSE
;
syserr("main: reentered!");
extern ADDRESS
*recipient();
if (signal(SIGINT
, SIG_IGN
) != SIG_IGN
)
(void) signal(SIGINT
, finis
);
if (signal(SIGHUP
, SIG_IGN
) != SIG_IGN
)
(void) signal(SIGHUP
, finis
);
(void) signal(SIGTERM
, finis
);
FullName
= getenv("NAME");
/* set up the main envelope */
MainEnvelope
.e_puthdr
= putheader
;
MainEnvelope
.e_putbody
= putbody
;
while (--argc
> 0 && (p
= *++argv
)[0] == '-')
case 'a': /* arpanet format */
syserr("I don't speak SMTP");
case 'C': /* select configuration file */
ConfFile
= "sendmail.cf";
tTsetup(tTdvect
, sizeof tTdvect
, "0-99.1");
setbuf(stdout
, (char *) NULL
);
printf("Version %s\n", Version
);
case 'f': /* from address */
case 'r': /* obsolete -f flag */
if (--argc
<= 0 || *p
== '-')
syserr("No \"from\" person");
syserr("More than one \"from\" person");
case 'F': /* set full name */
if (--argc
<= 0 || *p
== '-')
case 'h': /* hop count */
if (--argc
<= 0 || *p
< '0' || *p
> '9')
syserr("Bad hop count (%s)", p
);
case 'I': /* initialize alias DBM file */
case 'n': /* don't alias */
case 'o': /* set option */
setoption(p
[2], &p
[3], FALSE
, TRUE
);
case 'q': /* run queue files at intervals */
QueueIntvl
= convtime(&p
[2]);
syserr("I don't know about queues");
case 't': /* read recipients from message */
/* compatibility flags */
case 'b': /* operations mode */
case 'c': /* connect to non-local mailers */
case 'e': /* error message disposition */
case 'i': /* don't let dot stop me */
case 'm': /* send to me too */
case 'T': /* set timeout interval */
case 'v': /* give blow-by-blow description */
setoption(p
[1], &p
[2], FALSE
, TRUE
);
case 's': /* save From lines in headers */
setoption('f', &p
[2], FALSE
, TRUE
);
** Do basic initialization.
** Read system control file.
** Extract special fields for local use.
syslog(LOG_DEBUG
, "entered, uid=%d, pid=%d", getuid(), getpid());
readcf(ConfFile
, safecf
);
/* do heuristic mode adjustment */
setoption('b', "a", TRUE
, FALSE
);
/* our name for SMTP codes */
expand("$j", jbuf
, &jbuf
[sizeof jbuf
- 1], CurEnv
);
/* the indices of local and program mailers */
st
= stab("local", ST_MAILER
, ST_FIND
);
syserr("No local mailer defined");
LocalMailer
= st
->s_mailer
;
st
= stab("prog", ST_MAILER
, ST_FIND
);
syserr("No prog mailer defined");
ProgMailer
= st
->s_mailer
;
/* operate in queue directory */
syserr("cannot chdir(%s)", QueueDir
);
initaliases(AliasFile
, aliasinit
);
/* print configuration table (or at least part of it) */
for (i
= 0; i
< MAXMAILERS
; i
++)
register struct mailer
*m
= Mailer
[i
];
printf("mailer %d: %s %s %lo %d %d\n", i
, m
->m_name
,
m
->m_mailer
, m
->m_flags
, m
->m_s_rwset
, m
->m_r_rwset
);
** If test mode, read addresses from stdin and process.
printf("ADDRESS TEST MODE\nEnter <ruleset> <address>\n");
if (fgets(buf
, sizeof buf
, stdin
) == NULL
)
for (p
= buf
; isspace(*p
); *p
++)
while (*p
!= '\0' && !isspace(*p
))
while (*p
!= '\0' && *p
++ != ',')
} while (*(p
= DelimChar
) != '\0');
** If a daemon, wait for a request.
** getrequests will always return in a child.
** If we should also be processing the queue, start
** doing it in background.
** We check for any errors that might have happened
if (Mode
== MD_DAEMON
|| QueueIntvl
!= 0)
/* put us in background */
syserr("daemon: cannot fork");
syslog(LOG_DEBUG
, "background daemon, pid=%d",
/* disconnect from our controlling tty */
(void) ioctl(i
, TIOCNOTTY
, 0);
/* at this point we are in a child: reset state */
CurEnv
->e_id
= CurEnv
->e_qf
= CurEnv
->e_df
= NULL
;
** If collecting stuff from the queue, go start doing that.
if (queuemode
&& Mode
!= MD_DAEMON
)
/* do basic system initialization */
** If running SMTP protocol, start collecting and executing
** commands. This will never return.
if (Mode
!= MD_DAEMON
&& argc
<= 0 && !GrabTo
)
usrerr("Usage: /etc/sendmail [flags] addr...");
** The Hop count tells us how many times this message has
** been processed by sendmail. If it exceeds some
** fairly large threshold, then we assume that we have
** an infinite forwarding loop and die.
syserr("Infinite forwarding loop (%s->%s)", CurEnv
->e_from
.q_paddr
, *argv
);
** Scan argv and deliver the message to everyone.
** Actually, suppress delivery if we are taking To:
** lines from the message.
/* if we have had errors sofar, arrange a meaningful exit stat */
if (Errors
> 0 && ExitStat
== EX_OK
)
if (Mode
!= MD_VERIFY
|| GrabTo
)
** If we don't want to wait around for actual delivery, this
** is a good time to fork off.
** We have examined what we can without doing actual
** delivery, so we will inform our caller of
** Since the parent process will go away immediately,
** the child will be caught by init.
** If the fork fails, we will just continue in the
** parent; this is perfectly safe, albeit
** slower than it must be.
for (q
= CurEnv
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
if (!bitset(QDONTSEND
, q
->q_flags
))
CurEnv
->e_to
= q
->q_paddr
;
message(Arpa_Info
, "queued");
if (Mode
== MD_QUEUE
|| Mode
== MD_FORK
||
(Mode
!= MD_VERIFY
&& SuperSafe
))
syslog(LOG_DEBUG
, "background delivery, pid=%d", getpid());
else if (Mode
== MD_QUEUE
)
CurEnv
->e_df
= CurEnv
->e_qf
= NULL
;
CurEnv
->e_dontqueue
= TRUE
;
Stat
.stat_nf
[CurEnv
->e_from
.q_mailer
->m_mno
]++;
Stat
.stat_bf
[CurEnv
->e_from
.q_mailer
->m_mno
] += kbytes(CurEnv
->e_msgsize
);
** Arrange that the person who is sending the mail
** will not be expanded (unless explicitly requested).
printf("From person = \"%s\"\n", CurEnv
->e_from
.q_paddr
);
CurEnv
->e_from
.q_flags
|= QDONTSEND
;
recipient(&CurEnv
->e_from
, &CurEnv
->e_sendqueue
);
** Actually send everything.
** If verifying, just ack.
sendall(CurEnv
, Mode
== MD_VERIFY
);
** SETFROM -- set the person who this message is from
** Under certain circumstances allow the user to say who
** s/he is (using -f or -r). These are:
** 1. The user's uid is zero (root).
** 2. The user's login name is "network" (mail from
** 3. The user's login name is "uucp" (mail from the
** 4. The address the user is trying to claim has a
** "!" character in it (since #3 doesn't do it for
** us if we are dialing out).
** A better check to replace #3 & #4 would be if the
** effective uid is "UUCP" -- this would require me
** to rewrite getpwent to "grab" uucp as it went by,
** make getname more nasty, do another passwd file
** scan, or compile the UID of "UUCP" into the code,
** all of which are reprehensible.
** Assuming all of these fail, we figure out something
** from -- the person it is from.
** realname -- the actual person executing sendmail.
** If NULL, then take whoever we previously
** thought was the from person.
** sets sendmail's notion of who the from person is.
realname
= CurEnv
->e_from
.q_paddr
;
printf("setfrom(%s, %s)\n", from
, realname
);
** Do validation to determine whether this user is allowed
** to change the sender name.
if (strcmp(realname
, "network") != 0 &&
strcmp(realname
, "uucp") != 0 &&
strcmp(realname
, "daemon") != 0 &&
(!tTd(1, 9) || getuid() != geteuid()) &&
index(from
, '!') == NULL
&& getuid() != 0)
/* network sends -r regardless (why why why?) */
/* syserr("%s, you cannot use the -f flag", realname); */
** Parse the sender name.
** Arrange to send return messages to the same person.
** Set up some environment info.
if (from
== NULL
|| parse(from
, &CurEnv
->e_from
, 1) == NULL
)
(void) parse(from
, &CurEnv
->e_from
, 1);
CurEnv
->e_returnto
= &CurEnv
->e_from
;
CurEnv
->e_from
.q_uid
= getuid();
CurEnv
->e_from
.q_gid
= getgid();
CurEnv
->e_from
.q_home
= getenv("HOME");
if (CurEnv
->e_from
.q_uid
!= 0)
DefUid
= CurEnv
->e_from
.q_uid
;
DefGid
= CurEnv
->e_from
.q_gid
;
** Rewrite the from person to dispose of possible implicit
pvp
= prescan(from
, '\0');
syserr("cannot prescan from (%s)", from
);
cataddr(pvp
, frombuf
, sizeof frombuf
);
define('f', newstr(frombuf
));
/* save the domain spec if this mailer wants it */
if (bitset(M_CANONICAL
, CurEnv
->e_from
.q_mailer
->m_flags
))
extern char **copyplist();
while (*pvp
!= NULL
&& strcmp(*pvp
, "@") != 0)
CurEnv
->e_fromdomain
= copyplist(pvp
, TRUE
);
** FINIS -- Clean up and exit.
printf("\n====finis: stat %d sendreceipt %d FatalErrors %d\n",
ExitStat
, CurEnv
->e_sendreceipt
, FatalErrors
);
** Send back return receipts as requested.
if (CurEnv
->e_receiptto
!= NULL
&&
(CurEnv
->e_sendreceipt
|| ExitStat
!= EX_OK
))
sendto(CurEnv
->e_receiptto
, (ADDRESS
*) NULL
, &rlist
);
(void) returntosender("Return receipt", rlist
, FALSE
);
** Arrange to return errors or queue up as appropriate.
** If we are running a queue file and exiting abnormally,
** be sure we save the queue file.
** This clause will arrange to return error messages.
** Now clean up temp files and exit.
syslog(LOG_DEBUG
, "finis, pid=%d", getpid());
** OPENXSCRPT -- Open transcript file
** Creates a transcript file for possible eventual mailing or
** Turns the standard output into a special file
syserr("Can't create %s", p
);
** SETSENDER -- set sendmail's idea of the sender.
** from -- the person we would like to believe this
** Sets the idea of the sender.
register struct passwd
*pw
;
** Figure out the real user executing us.
** Getlogin can return errno != 0 on non-errors.
nofullname
= (from
!= NULL
);
if (p
!= NULL
&& p
[0] != '\0')
extern struct passwd
*getpwnam();
syserr("Who are you? (name=%s)", p
);
if (p
== NULL
|| p
[0] == '\0')
extern struct passwd
*getpwuid();
syserr("Who are you? (uid=%d)", uid
);
if (p
== NULL
|| p
[0] == '\0' || pw
== NULL
)
** Process passwd file entry.
/* run user's .mailcf file */
expand("$z/.mailcf", cfbuf
, &cfbuf
[sizeof cfbuf
- 1], CurEnv
);
if (!nofullname
&& safefile(cfbuf
, getruid(), S_IREAD
))
/* if the user has given fullname already, don't redefine */
FullName
= macvalue('x', CurEnv
);
/* extract full name from passwd file */
if (!nofullname
&& (FullName
== NULL
|| FullName
[0] == '\0') &&
pw
!= NULL
&& pw
->pw_gecos
!= NULL
)
if (FullName
!= NULL
&& FullName
[0] != '\0')
** INITSYS -- initialize instantiation of system
** In Daemon mode, this is done in the child.
** Initializes the system macros, some global variables,
** etc. In particular, the current time in various
static char cbuf
[5]; /* holds hop count */
static char dbuf
[30]; /* holds ctime(tbuf) */
static char pbuf
[10]; /* holds pid */
static char tbuf
[20]; /* holds "current" time */
static char ybuf
[10]; /* holds tty id */
extern struct tm
*gmtime();
** Give this envelope a reality.
** I.e., an id and a creation time.
(void) queuename(CurEnv
, '\0');
CurEnv
->e_ctime
= curtime();
** Set OutChannel to something useful if stdout isn't it.
** This arranges that any extra stuff the mailer produces
** gets sent back to the user on error (because it is
** tucked away in the transcript).
if ((Mode
== MD_DAEMON
&& QueueRun
) || HoldErrs
)
** Set up some basic system macros.
(void) sprintf(pbuf
, "%d", getpid());
(void) sprintf(cbuf
, "%d", HopCount
);
/* time as integer, unix time, arpa time */
(void) sprintf(tbuf
, "%02d%02d%02d%02d%02d", tm
->tm_year
, tm
->tm_mon
,
tm
->tm_mday
, tm
->tm_hour
, tm
->tm_min
);
(void) strcpy(dbuf
, ctime(&now
));
*index(dbuf
, '\n') = '\0';
if (macvalue('d', CurEnv
) == NULL
)
p
= newstr(arpadate(dbuf
));
if (macvalue('a', CurEnv
) == NULL
)
if (macvalue('y', CurEnv
) == NULL
)
if (rindex(p
, '/') != NULL
)
** INITMACROS -- initialize the macro system
** This just involves defining some macros that are actually
** used internally as metasymbols to be themselves.
** initializes several macros to be themselves.
struct metamac MetaMacros
[] =
/* these are important on the LHS */
'*', MATCHZANY
, '+', MATCHANY
, '-', MATCHONE
, '=', MATCHCLASS
,
/* these are RHS metasymbols */
'#', CANONNET
, '@', CANONHOST
, ':', CANONUSER
, '>', CALLSUBR
,
/* and finally the conditional operations */
'?', CONDIF
, '|', CONDELSE
, '.', CONDFI
,
register struct metamac
*m
;
for (m
= MetaMacros
; m
->metaname
!= '\0'; m
++)
define(m
->metaname
, newstr(buf
));
for (c
= '0'; c
<= '9'; c
++)
** NEWENVELOPE -- allocate a new envelope
** e -- the new envelope to fill in.
clear((char *) e
, sizeof *e
);
bmove(&CurEnv
->e_from
, &e
->e_from
, sizeof e
->e_from
);
e
->e_puthdr
= CurEnv
->e_puthdr
;
e
->e_putbody
= CurEnv
->e_putbody
;
** DROPENVELOPE -- deallocate an envelope.
** e -- the envelope to deallocate.
** housekeeping necessary to dispose of an envelope.
xunlink(queuename(e
, 'l'));
** QUEUENAME -- build a file name in the queue directory for this envelope.
** Assigns an id code if one does not already exist.
** This code is very careful to avoid trashing existing files
** under any circumstances.
** We first create an xf file that is only used when
** assigning an id. This file is always empty, so that
** we can never accidently truncate an lf file.
** e -- envelope to build it in/from.
** type -- the file type, used as the first character
** a pointer to the new file name (in a static buffer).
** Will create the lf and qf files if no id code is
** already assigned. This will cause the envelope
static char buf
[MAXNAME
];
(void) sprintf(qf
, "qf_%05d", getpid());
qf
[2] = lf
[2] = xf
[2] = ++counter
;
printf("queuename: trying \"%s\"\n", xf
);
if (access(lf
, 0) >= 0 || access(qf
, 0) >= 0)
(void) unlink(xf
); /* kernel bug */
syserr("queuename: Cannot create \"%s\" in \"%s\"",
printf("queuename: assigned id %s, env=%x\n", e
->e_id
, e
);
(void) sprintf(buf
, "%cf%s", type
, e
->e_id
);
printf("queuename: %s\n", buf
);