* Copyright (c) 1983 Eric P. Allman
* Copyright (c) 1988 Regents of the University of California.
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
"@(#) Copyright (c) 1988 Regents of the University of California.\n\
static char sccsid
[] = "@(#)main.c 5.22 (Berkeley) %G%";
#include <arpa/nameser.h>
** 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
** /usr/lib/sendmail [flags] addr ...
** See the associated documentation for details.
** Eric Allman, UCB/INGRES (until 10/81)
** Britton-Lee, Inc., purveyors of fine
** database computers (from 11/81)
** The support of the INGRES Project and Britton-Lee is
** gratefully acknowledged. Britton-Lee in
** particular had absolutely nothing to gain from
** my involvement in this project.
int NextMailer
; /* "free" index into Mailer struct */
char *FullName
; /* sender's full name */
ENVELOPE BlankEnvelope
; /* a "blank" envelope */
ENVELOPE MainEnvelope
; /* the envelope around the basic letter */
ADDRESS NullAddress
= /* a null address */
** Pointers for setproctitle.
** This allows "ps" listings to give more useful information.
** These must be kept out of BSS for frozen configuration files
char **Argv
= NULL
; /* pointer to argument vector */
char *LastArgv
= NULL
; /* end of argv */
ERROR
%%%% Cannot have daemon mode without SMTP
%%%% ERROR
bool queuemode
= FALSE
; /* process queue requests */
static bool reenter
= FALSE
;
char jbuf
[30]; /* holds MyHostName */
extern time_t convtime();
extern putheader(), putbody();
extern ENVELOPE
*newenvelope();
extern char **myhostname();
** Check to see if we reentered.
** This would normally happen if e_putheader or e_putbody
** were NULL when invoked.
syserr("main: reentered!");
extern ADDRESS
*recipient();
/* Enforce use of local time */
** Be sure we have enough file descriptors.
** But also be sure that 0, 1, & 2 are open.
i
= open("/dev/null", O_RDWR
);
for (i
= getdtablesize(); i
> 2; --i
)
** Set default values for variables.
** These cannot be in initialized data space.
/* set up the blank envelope */
BlankEnvelope
.e_puthdr
= putheader
;
BlankEnvelope
.e_putbody
= putbody
;
BlankEnvelope
.e_xfp
= NULL
;
STRUCTCOPY(NullAddress
, BlankEnvelope
.e_from
);
STRUCTCOPY(BlankEnvelope
, MainEnvelope
);
** Do a quick prescan of the argument list.
** We do this to find out if we can potentially thaw the
** configuration file. If not, we do the thaw now so that
** the argument processing applies to this run rather than
** to the run that froze the configuration.
while ((p
= *++av
) != NULL
)
if (strncmp(p
, "-C", 2) == 0)
ConfFile
= "sendmail.cf";
(void) setgid(getrgid());
(void) setuid(getruid());
else if (strncmp(p
, "-bz", 3) == 0)
else if (strncmp(p
, "-d", 2) == 0)
tTsetup(tTdvect
, sizeof tTdvect
, "0-99.1");
setbuf(stdout
, (char *) NULL
);
printf("Version %s\n", Version
);
readconfig
= !thaw(FreezeFile
);
/* reset the environment after the thaw */
for (i
= 0; i
< MAXUSERENVIRON
&& envp
[i
] != NULL
; i
++)
UserEnviron
[i
] = newstr(envp
[i
]);
** Save start and extent of argv for setproctitle.
LastArgv
= envp
[i
- 1] + strlen(envp
[i
- 1]);
LastArgv
= argv
[argc
- 1] + strlen(argv
[argc
- 1]);
** Now do basic initialization
if (signal(SIGINT
, SIG_IGN
) != SIG_IGN
)
(void) signal(SIGINT
, intsig
);
if (signal(SIGHUP
, SIG_IGN
) != SIG_IGN
)
(void) signal(SIGHUP
, intsig
);
(void) signal(SIGTERM
, intsig
);
(void) signal(SIGPIPE
, SIG_IGN
);
FullName
= getenv("NAME");
/* initialize some macros, etc. */
av
= myhostname(jbuf
, sizeof jbuf
);
printf("canonical name: %s\n", jbuf
);
while (av
!= NULL
&& *av
!= NULL
)
printf("\ta.k.a.: %s\n", *av
);
define('v', Version
, CurEnv
);
define('b', arpadate((char *) NULL
), CurEnv
);
if (strcmp(p
, "newaliases") == 0)
else if (strcmp(p
, "mailq") == 0)
else if (strcmp(p
, "smtpd") == 0)
while ((p
= *++av
) != NULL
&& p
[0] == '-')
case 'b': /* operations mode */
usrerr("Daemon mode not implemented");
usrerr("I don't speak SMTP");
usrerr("Invalid operation mode %c", p
[2]);
case 'C': /* select configuration file (already done) */
case 'd': /* debugging -- redo in case frozen */
tTsetup(tTdvect
, sizeof tTdvect
, "0-99.1");
setbuf(stdout
, (char *) NULL
);
_res
.options
|= RES_DEBUG
;
case 'f': /* from address */
case 'r': /* obsolete -f flag */
if (*p
== '\0' && ((p
= *++av
) == NULL
|| *p
== '-'))
if (p
== NULL
|| *p
== '-')
usrerr("No \"from\" person");
usrerr("More than one \"from\" person");
case 'F': /* set full name */
if (*p
== '\0' && ((p
= *++av
) == NULL
|| *p
== '-'))
case 'h': /* hop count */
if (*p
== '\0' && ((p
= *++av
) == NULL
|| !isdigit(*p
)))
usrerr("Bad hop count (%s)", p
);
CurEnv
->e_hopcount
= atoi(p
);
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]);
usrerr("I don't know about queues");
case 't': /* read recipients from message */
/* compatibility flags */
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
);
case 'I': /* initialize alias DBM file */
** Do basic initialization.
** Read system control file.
** Extract special fields for local use.
if (OpMode
== MD_FREEZE
|| readconfig
)
/* this is critical to avoid forgeries of the frozen config */
/* freeze the configuration */
/* do heuristic mode adjustment */
/* turn off noconnect option */
setoption('c', "F", TRUE
, FALSE
);
/* turn on interactive delivery */
setoption('d', "", TRUE
, FALSE
);
/* our name for SMTP codes */
expand("\001j", 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
);
** Do operation-mode-dependent initialization.
usrerr("No queue to print");
/* initialize alias database */
initaliases(AliasFile
, TRUE
);
/* don't open alias database -- done in srvrsmtp */
/* open the alias database */
initaliases(AliasFile
, FALSE
);
/* 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): P=%s S=%d R=%d M=%ld F=", i
, m
->m_name
,
m
->m_mailer
, m
->m_s_rwset
, m
->m_r_rwset
,
for (j
= '\0'; j
<= '\177'; j
++)
if (bitnset(j
, m
->m_flags
))
** Switch to the main envelope.
CurEnv
= newenvelope(&MainEnvelope
);
MainEnvelope
.e_flags
= BlankEnvelope
.e_flags
;
** 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
))
pvp
= prescan(++p
, ',', pvpbuf
);
while (*p
!= '\0' && *p
++ != ',')
} while (*(p
= DelimChar
) != '\0');
** If collecting stuff from the queue, go start doing that.
if (queuemode
&& OpMode
!= MD_DAEMON
&& QueueIntvl
== 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 (OpMode
== MD_DAEMON
|| QueueIntvl
!= 0)
/* put us in background */
syserr("daemon: cannot fork");
/* disconnect from our controlling tty */
/* at this point we are in a child: reset state */
(void) newenvelope(CurEnv
);
** If running SMTP protocol, start collecting and executing
** commands. This will never return.
** Do basic system initialization and set the sender
if (OpMode
!= MD_ARPAFTP
&& *av
== NULL
&& !GrabTo
)
usrerr("Recipient names must be specified");
/* collect body for UUCP return */
** Scan argv and deliver the message to everyone.
/* if we have had errors sofar, arrange a meaningful exit stat */
if (Errors
> 0 && ExitStat
== EX_OK
)
if (OpMode
!= MD_VERIFY
|| GrabTo
)
markstats(CurEnv
, (ADDRESS
*) NULL
);
printf("From person = \"%s\"\n", CurEnv
->e_from
.q_paddr
);
** Actually send everything.
** If verifying, just ack.
CurEnv
->e_from
.q_flags
|= QDONTSEND
;
sendall(CurEnv
, SM_DEFAULT
);
** FINIS -- Clean up and exit.
printf("\n====finis: stat %d e_flags %o\n", ExitStat
, CurEnv
->e_flags
);
/* clean up temp files */
syslog(LOG_DEBUG
, "finis, pid=%d", getpid());
if (ExitStat
== EX_TEMPFAIL
)
** INTSIG -- clean up on interrupt
** This just arranges to exit. It pessimises in that it
** Unlocks the current job.
** 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
[] =
/* LHS pattern matching characters */
'*', MATCHZANY
, '+', MATCHANY
, '-', MATCHONE
, '=', MATCHCLASS
,
/* these are RHS metasymbols */
'#', CANONNET
, '@', CANONHOST
, ':', CANONUSER
, '>', CALLSUBR
,
'{', MATCHLOOKUP
, '}', MATCHELOOKUP
,
/* the conditional operations */
'?', CONDIF
, '|', CONDELSE
, '.', CONDFI
,
/* and finally the hostname lookup characters */
'[', HOSTBEGIN
, ']', HOSTEND
,
register struct metamac
*m
;
for (m
= MetaMacros
; m
->metaname
!= '\0'; m
++)
define(m
->metaname
, newstr(buf
), CurEnv
);
for (c
= '0'; c
<= '9'; c
++)
define(c
, newstr(buf
), CurEnv
);
** FREEZE -- freeze BSS & allocated memory
** This will be used to efficiently load the configuration file.
** freezefile -- the name of the file to freeze to.
** Writes BSS and malloc'ed memory to freezefile
char frzpad
[BUFSIZ
]; /* insure we are on a BUFSIZ boundary */
time_t frzstamp
; /* timestamp on this freeze */
char *frzbrk
; /* the current break */
char *frzedata
; /* address of edata */
char *frzend
; /* address of end */
char frzver
[252]; /* sendmail version */
/* try to open the freeze file */
f
= creat(freezefile
, FileMode
);
syserr("Cannot freeze %s", freezefile
);
/* build the freeze header */
fhdr
.frzinfo
.frzstamp
= curtime();
fhdr
.frzinfo
.frzbrk
= sbrk(0);
fhdr
.frzinfo
.frzedata
= &edata
;
fhdr
.frzinfo
.frzend
= &end
;
(void) strcpy(fhdr
.frzinfo
.frzver
, Version
);
/* write out the freeze header */
if (write(f
, (char *) &fhdr
, sizeof fhdr
) != sizeof fhdr
||
write(f
, (char *) &edata
, (int) (fhdr
.frzinfo
.frzbrk
- &edata
)) !=
(int) (fhdr
.frzinfo
.frzbrk
- &edata
))
syserr("Cannot freeze %s", freezefile
);
** THAW -- read in the frozen configuration file.
** freezefile -- the name of the file to thaw from.
** TRUE if it successfully read the freeze file.
** reads freezefile in to BSS area.
/* open the freeze file */
if (read(f
, (char *) &fhdr
, sizeof fhdr
) < sizeof fhdr
||
fhdr
.frzinfo
.frzedata
!= &edata
||
fhdr
.frzinfo
.frzend
!= &end
||
strcmp(fhdr
.frzinfo
.frzver
, Version
) != 0)
/* arrange to have enough space */
if (brk(fhdr
.frzinfo
.frzbrk
) == (caddr_t
) -1)
syserr("Cannot break to %x", fhdr
.frzinfo
.frzbrk
);
/* now read in the freeze file */
if (read(f
, (char *) &edata
, (int) (fhdr
.frzinfo
.frzbrk
- &edata
)) !=
(int) (fhdr
.frzinfo
.frzbrk
- &edata
))
/* oops! we have trashed memory..... */
(void) write(2, "Cannot read freeze file\n", 24);
** DISCONNECT -- remove our connection with any foreground process
** fulldrop -- if set, we should also drop the controlling
** TTY if possible -- this should only be done when
** setting up the daemon since otherwise UUCP can
** leave us trying to open a dialin, and we will
** Trys to insure that we are immune to vagaries of
printf("disconnect: In %d Out %d\n", fileno(InChannel
),
/* be sure we don't get nasty signals */
(void) signal(SIGHUP
, SIG_IGN
);
(void) signal(SIGINT
, SIG_IGN
);
(void) signal(SIGQUIT
, SIG_IGN
);
/* we can't communicate with our caller, so.... */
/* all input from /dev/null */
(void) fclose(InChannel
);
(void) freopen("/dev/null", "r", stdin
);
/* output to the transcript */
if (OutChannel
!= stdout
)
(void) fclose(OutChannel
);
if (CurEnv
->e_xfp
== NULL
)
CurEnv
->e_xfp
= fopen("/dev/null", "w");
while ((fd
= dup(fileno(CurEnv
->e_xfp
))) < 2 && fd
> 0)
/* drop our controlling TTY completely if possible */
fd
= open("/dev/tty", 2);
(void) ioctl(fd
, (int) TIOCNOTTY
, (char *) 0);
syslog(LOG_DEBUG
, "in background, pid=%d", getpid());