/*
 * Copyright (c) 1983 Eric P. Allman
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
# define IN_SCCS_ID
# define _DEFINE
char copyright[] =
"@(#) Copyright (c) 1988 Regents of the University of California.\n\
 All rights reserved.\n";
static char sccsid[] = "@(#)main.c	5.32 (Berkeley) 3/2/91";
static char  rcsid[] = "@(#)$Id: main.c,v 5.32.0.15 1993/11/29 21:34:41 avatar Exp $";
# ifdef	__GNUC__
static	char	compiled[] = "@(#)compiled by gcc version "__VERSION__;
# endif	/* __GNUC__ */
#endif /* not lint */

#ifdef AIX
# include <sys/utsname.h>
# undef newstr	/* a structure name in sys/utsname.h under RT/AIX */
#endif /* AIX */
#include "sendmail.h"
#if defined(HAVE_UNAME) && !defined(AIX)
# include <sys/utsname.h>
#endif /* HAVE_UNAME && !AIX */
#ifndef linux
#include <sgtty.h>
#endif /*linux*/
#ifdef NAMED_BIND
# include <arpa/nameser.h>
# include <resolv.h>
#endif /* NAMED_BIND */
#ifdef VMUNIX
# include <netdb.h>
#endif /* VMUNIX */
#ifndef MAXHOSTNAMELEN
# define MAXHOSTNAMELEN	64
#endif /* !MAXHOSTNAMELEN */

#ifdef lint
int	edata, end;
#endif /* lint */

#if defined(ultrix)
static char BsmtpCharBuf;	/* one char buf for BSMTP */
#endif /* ultrix */

static SIG_TYPE intsig __P(());
static void initmacros __P(());
static void freeze __P((char *));
static thaw __P((char *));

/*
**  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
**	delivering the mail.
**
**	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
**	server mechanism).
**
**	Usage:
**		/usr/lib/sendmail [flags] addr ...
**
**		See the associated documentation for details.
**
**	Author:
**		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 */
char		*MatchRecipient;/* for selective queue runs on Recipients */
char		*MatchSender;	/* for selective queue runs on Senders */
char		*QueueID;	/* run a certain message in the queue */
ENVELOPE	BlankEnvelope;	/* a "blank" envelope */
ENVELOPE	MainEnvelope;	/* the envelope around the basic letter */
ADDRESS		NullAddress =	/* a null address */
		{ "", "", NULL, "" };
ENVIRONMENT	SavedEnv[] =	/* Values to save before environment reset */
	      { "NAME",	NULL,
		"HOME",	NULL,
		"TZ",	NULL,
		"HZ",	NULL,
		"LANG",	NULL,
		"LOGNAME", NULL,
		"SYSTYPE", NULL,
		"PERROR_FMT", NULL,
		"UU_MACHINE", NULL,		/* from HDB UUCP */
		"UU_USER", NULL,		/* from HDB UUCP */
		"PATH",	"/bin:/usr/bin",	/* preset */
		"SHELL","/bin/sh",		/* preset */
#ifdef apollo
		"NODEID", NULL,
		"NODETYPE", NULL,
		"APOLLO_STATUS", NULL,
		"ISP",	NULL,
#endif /* apollo */
		NULL,	NULL };
extern char	Version[];

/*
**  Pointers for setproctitle.
**	This allows "ps" listings to give more useful information.
**	These must be kept out of BSS for frozen configuration files
**		to work.
*/

#if defined(SETPROCTITLE) && !defined(SYSV)
char		**Argv = NULL;		/* pointer to argument vector */
char		*LastArgv = NULL;	/* end of argv */
#endif /* SETPROCTITLE && !SYSV */

#ifdef DAEMON
# ifndef SMTP
ERROR %%%%   Cannot have daemon mode without SMTP   %%%% ERROR
# endif /* SMTP */
#endif /* DAEMON */

void
main(argc, argv, envp)
	int argc;
	char **argv;
	char **envp;
{
	register char *p;
	char **av;
	char *from;
	typedef int (*fnptr)();
	STAB *st;
	register int i;
	bool readconfig = TRUE;
	bool queuemode = FALSE;		/* process queue requests */
	bool NoName = FALSE;
#ifdef _PATH_SENDMAILFC
	bool nothaw = FALSE;
#endif /* _PATH_SENDMAILFC */
	static char sobuf[BUFSIZ];	/* stdout buffering, avoids malloc() */
	static bool reenter = FALSE;
	char jbuf[MAXHOSTNAMELEN+1];	/* holds MyHostName */
	extern char **environ;

	/*
	**  Check to see if we reentered.
	**	This would normally happen if e_putheader or e_putbody
	**	were NULL when invoked.
	*/

	if (reenter)
	{
		syserr("main: reentered!");
		abort();
	}
	reenter = TRUE;

	/*
	**  Be sure we have enough file descriptors.
	**	But also be sure that 0, 1, & 2 are open.
	*/

	i = open("/dev/null", O_RDWR, 0);
	while (i >= 0 && i < 2)
		i = dup(i);
#if defined(XPG3)
	for (i = (int) sysconf (_SC_OPEN_MAX); i > 2; --i)
#else /* !XPG3 */
	for (i = getdtablesize(); i > 2; --i)
#endif /* XPG3 */
		(void) close(i);
	errno = 0;

	/*
	 * How nice. Ultrix (v4.2) defines one syslog for kernel use
	 * and another for user processes.  Grrrrr.
	 */
#ifdef LOG
# if defined(LOG_MAIL) && !defined(ultrix)
	openlog("sendmail", LOG_PID, LOG_MAIL);
# else /* !LOG_MAIL || ultrix */
	openlog("sendmail", LOG_PID);
# endif /* LOG_MAIL && !ultrix */
#endif /* LOG */

	/*
	**  Set default values for variables.
	**	These cannot be in initialized data space.
	*/

	setdefaults();

	/* set up the blank envelope */
	BlankEnvelope.e_puthdr = putheader;
	BlankEnvelope.e_putbody = putbody;
	BlankEnvelope.e_xfp = NULL;
	STRUCTCOPY(NullAddress, BlankEnvelope.e_from);
	CurEnv = &BlankEnvelope;
	STRUCTCOPY(NullAddress, MainEnvelope.e_from);

	argv[argc] = NULL;
	av = argv;

	/*
	**  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 = &p[2];
			if (ConfFile[0] == '\0')
				ConfFile = "sendmail.cf";
			(void) setgid(getrgid());
			(void) setuid(getruid());
#ifdef _PATH_SENDMAILFC
			nothaw = TRUE;
#endif /* _PATH_SENDMAILFC */
		}
#ifdef _PATH_SENDMAILFC
		else if (strncmp(p, "-bz", 3) == 0)
			nothaw = TRUE;
#endif /* _PATH_SENDMAILFC */
		else if (strncmp(p, "-bd", 3) == 0 || strncmp(p, "-q", 2) == 0)
		{
			NoName = TRUE;
			OpMode = MD_DAEMON;	/* needed in thaw() */
		}
		else if (strncmp(p, "-Z", 2) == 0)
		{
#ifdef _PATH_SENDMAILFC
			FreezeFile = &p[2];
			if (FreezeFile[0] == '\0')
				FreezeFile = "sendmail.fc";
			(void) setgid(getrgid());
			(void) setuid(getruid());
#else /* !_PATH_SENDMAILFC */
			/* Use printf since OutChannel isn't assigned yet */
			printf("Frozen configuration files not available\n");
#endif /* _PATH_SENDMAILFC */
		}
		else if (strncmp(p, "-d", 2) == 0)
		{
			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
			tTflag(&p[2]);
#if defined(XPG3) || defined(SYSV)
			(void) setvbuf(stdout, sobuf, _IOLBF, sizeof sobuf);
#else /* !XPG3 && !SYSV */
			setlinebuf(stdout);
#endif /* XPG3 || SYSV */
			printf("Version %s\n", Version);
		}
	}

	InChannel = stdin;
	OutChannel = stdout;

#ifdef _PATH_SENDMAILFC
	if (!nothaw)
		readconfig = !thaw(FreezeFile);
#endif /* _PATH_SENDMAILFC */
#if defined(SETPROCTITLE) && !defined(SYSV)

	/*
	**  Save start and extent of argv for setproctitle.
	*/
	for (i = 0; envp[i] != NULL; i++)
		;
	Argv = argv;
	if (i > 0)
		LastArgv = envp[i - 1] + strlen(envp[i - 1]);
	else
		LastArgv = argv[argc - 1] + strlen(argv[argc - 1]);
#endif /* SETPROCTITLE && !SYSV */

	/*
	** Reset environment to safeguard PATH, etc.
	*/
	{
		register char	**P;
		ENVIRONMENT	*ep;

		/* Save needed values from environment before clobbering it. */
		for (ep = SavedEnv; ep->key; ep++)
			if (ep->value == NULL && (p = getenv(ep->key)) != NULL)
				ep->value = newstr(p);

		/* Reset it to a known state. */
		environ = (char **) xalloc((sizeof(SavedEnv) / sizeof(ENVIRONMENT)) * sizeof (char *));
		for (ep = SavedEnv, P = environ; ep->key; ep++)
		{
			if (ep->value == NULL)
				continue;
			*P = xalloc((int)(strlen(ep->key) + strlen(ep->value) + 2));
			(void) sprintf(*P, "%s=%s", ep->key, ep->value);
			P++;
		}
		*P = NULL;
	}
#ifdef NAMED_BIND

	/*
	** Make sure the resolver library is initialized and that enough time
	** is allowed for non-local servers.
	*/
	res_init();
	_res.retrans = 30;
#endif /* NAMED_BIND */
#ifdef HESIOD

	/* Ditto for Hesiod. */
	if (UseHesiod)
	{
		int hes_result;

		if ((hes_result = hes_init()) != 0)
		{
# ifdef LOG
			syslog(LOG_WARNING, "hes_init() returned %d, Hesiod use disabled", hes_result);
# endif /* LOG */
			UseHesiod = 0;
		}
	}
#endif /* HESIOD */

	if (signal(SIGINT, SIG_IGN) != (SIG_TYPE (*)()) SIG_IGN)
		(void) signal(SIGINT, intsig);
	if (signal(SIGHUP, SIG_IGN) != (SIG_TYPE (*)()) SIG_IGN)
		(void) signal(SIGHUP, intsig);
	(void) signal(SIGTERM, intsig);
	(void) signal(SIGPIPE, SIG_IGN);
	OldUmask = umask(0);
	OpMode = MD_DELIVER;
	MotherPid = getpid();
	srand(MotherPid);
	FullName = (NoName) ? NULL : getenv("NAME");

	errno = 0;
	from = NULL;

	if (readconfig)
	{
		/* initialize some macros, etc. */
		initmacros();

		/* hostname */
		av = myhostname(jbuf, sizeof jbuf);
		if (jbuf[0] != '\0')
		{
#ifdef HAVE_UNAME
			struct	utsname	utsname;
#endif /* HAVE_UNAME */
			if (tTd(0, 4))
				printf("canonical name: %s\n", jbuf);
			p = newstr(jbuf);
			define('w', p, CurEnv);
			setclass('w', p);
			if ((p = index(jbuf, '.')) != NULL)
				*p = '\0';
			makelower(jbuf);
#ifdef HAVE_UNAME
			if ((uname(&utsname) != -1) &&
			    strncmp(utsname.nodename, jbuf, 8))
			{
				define('k', newstr(utsname.nodename), CurEnv);
				if (tTd(0, 4))
					printf("UUCP nodename: %s\n",
					    utsname.nodename);
			}
			else
#endif /* HAVE_UNAME */
			define('k', newstr(jbuf), CurEnv);
		}
		while (av != NULL && *av != NULL)
		{
			if (tTd(0, 4))
				printf("\ta.k.a.: %s\n", *av);
			setclass('w', *av++);
		}

		/* version */
		define('v', Version, CurEnv);
	}

	/* current time */
	define('b', arpadate((char *) NULL), CurEnv);

	/*
	** Crack argv.
	*/

	av = argv;
	p = rindex(*av, '/');
	if (p++ == NULL)
		p = *av;
	if (strcmp(p, "newaliases") == 0)
		OpMode = MD_INITALIAS;
	else if (strcmp(p, "mailq") == 0 || strcmp(p, "-mailq") == 0)
		OpMode = MD_PRINT;
	else if (strcmp(p, "smtpd") == 0)
		OpMode = MD_DAEMON;
	else if (strcmp(p, "bsmtp") == 0)
		OpMode = MD_BSMTP;
	else if (strcmp(p, "runq") == 0)
		queuemode = TRUE;
	while ((p = *++av) != NULL && p[0] == '-')
	{
		switch (p[1])
		{
		  case 'b':	/* operations mode */
			switch (p[2])
			{
			  case MD_DAEMON:
#ifdef DAEMON
				if (getuid() != 0)
				{
					usrerr("Permission denied");
					exit (EX_USAGE);
				}
#else /* !DAEMON */
				usrerr("Daemon mode not implemented");
				ExitStat = EX_USAGE;
				break;
#endif /* DAEMON */
			  case MD_SMTP:
			  case MD_BSMTP:
#ifndef SMTP
				usrerr("I don't speak SMTP");
				ExitStat = EX_USAGE;
				break;
#endif /* SMTP */
			  case MD_ARPAFTP:
			  case MD_DELIVER:
			  case MD_VERIFY:
			  case MD_TEST:
			  case MD_INITALIAS:
			  case MD_PRINT:
			  case MD_FREEZE:
				OpMode = p[2];
				break;

			  default:
				usrerr("Invalid operation mode %c", p[2]);
				ExitStat = EX_USAGE;
				break;
			}
			break;

		  case 'C':	/* select configuration file (already done) */
			break;

		  case 'Z':	/* select frozen config file (already done) */
			break;

		  case 'd':	/* debugging -- redo in case frozen */
			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
			tTflag(&p[2]);
			tTflag(&p[2]);
#if defined(XPG3) || defined(SYSV)
			(void) setvbuf(stdout, sobuf, _IOLBF, sizeof sobuf);
#else /* !XPG3 && !SYSV */
			setlinebuf(stdout);
#endif /* XPG3 || SYSV */
#ifdef NAMED_BIND
			if (tTd(8, 8))
			_res.options |= RES_DEBUG;
#endif /* NAMED_BIND */
			break;

		  case 'f':	/* from address */
		  case 'r':	/* obsolete -f flag */
			p += 2;
			if (*p == '\0' && ((p = *++av) == NULL || *p == '-'))
			{
				p = *++av;
				if (p == NULL || *p == '-')
				{
					usrerr("No \"from\" person");
					ExitStat = EX_USAGE;
					av--;
					break;
				}
			}
			if (from != NULL)
			{
				usrerr("More than one \"from\" person");
				ExitStat = EX_USAGE;
				break;
			}
			from = newstr(p);
			break;

		  case 'F':	/* set full name */
			p += 2;
			if (*p == '\0' && ((p = *++av) == NULL || *p == '-'))
			{
				usrerr("Bad -F flag");
				ExitStat = EX_USAGE;
				av--;
				break;
			}
			FullName = newstr(p);
			break;

		  case 'h':	/* hop count */
			p += 2;
			if (*p == '\0' && ((p = *++av) == NULL || !isdigit(*p)))
			{
				usrerr("Bad hop count (%s)", p);
				ExitStat = EX_USAGE;
				av--;
				break;
			}
			CurEnv->e_hopcount = atoi(p);
			break;
		
		  case 'n':	/* don't alias */
			NoAlias = TRUE;
			break;

		  case 'o':	/* set option */
			setoption(p[2], &p[3], FALSE, TRUE);
			break;

		  /*
		  ** Run queue message with this queue ID or if
		  ** 'a' < p[2] <= 'z', set the macro.  This crock is
		  ** to handle what mail11dv3 hands us.
		  */
		  case 'M':
			if (p[2] != '\0' && islower(p[2]))
			{
				setoption(p[1], &p[2], FALSE, TRUE);
				break;
			}
		  case 'q':	/* run queue files at intervals */
		  case 'R':	/* run queued messages matching pattern */
		  case 'S':	/* run queued messages matching pattern */
#ifdef QUEUE
		  {
			char c = p[1];

			p += 2;
			if (getuid() != 0)
			{
				usrerr("Permission denied");
				exit (EX_USAGE);
			}
			if (queuemode)
			{
			        usrerr("Use only one of -q, -R, -S, and -M");
				exit (EX_USAGE);
			}
			if (c != 'q')
			{
				if (*p == '\0' && ((p = *++av) == NULL || 
				    *p == '-'))
				{
					usrerr("Bad -%c flag", c);
					ExitStat = EX_USAGE;
					av--;
					break;
				}
			}
			queuemode = TRUE;
			switch (c)
			{
			  case 'q':
				QueueIntvl = convtime(p);
				break;

			  case 'R':
				MatchRecipient = newstr(p);
				break;

			  case 'S':
				MatchSender = newstr(p);
				break;

			  case 'M':
				QueueID = newstr(p);
				break;
			}
		  }
#else /* !QUEUE */
			usrerr("I don't know about queues");
			ExitStat = EX_USAGE;
#endif /* QUEUE */
			break;

		  case 't':	/* read recipients from message */
			GrabTo = TRUE;
			break;

			/* 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);
			break;

		  case 's':	/* save From lines in headers */
			setoption('f', &p[2], FALSE, TRUE);
			break;

#ifdef DBM
		  case 'I':	/* initialize alias DBM file */
			OpMode = MD_INITALIAS;
			break;
#endif /* DBM */
		}
	}

	/*
	**  Do basic initialization.
	**	Read system control file.
	**	Extract special fields for local use.
	*/

#ifdef BIT8
	{
		char	*abuf = newstr("ASCII");
		ascii = getchset(abuf, 29);
		free(abuf);
	}
#endif /* BIT8 */

	if (OpMode == MD_FREEZE || readconfig)
		readcf(ConfFile);

	switch (OpMode)
	{
	  case MD_FREEZE:
#ifdef _PATH_SENDMAILFC
		/* this is critical to avoid forgeries of the frozen config */
		(void) setgid(getgid());
		(void) setuid(getuid());

		/* freeze the configuration */
		freeze(FreezeFile);
		exit(EX_OK);
#else /* !_PATH_SENDMAILFC */
		usrerr("Frozen configuration files not available");
		exit(EX_USAGE);
#endif /* _PATH_SENDMAILFC */

	  case MD_INITALIAS:
		Verbose = TRUE;
		break;
	}

#ifdef notdef
	/*
	** Print the argument list when debugging.  This has to come after the
	** freeze() statement as the first syslog() call sets a state variable
	** that won't get saved or restored.
	*/
	{
		char	*cmdline = xalloc(MAXLINE);

		for (*cmdline = '\0', av = argv; *av; av++)
		{
			(void) strcat(cmdline, *av);
			(void) strcat(cmdline, " ");
		}
		syslog(LOG_DEBUG, "Invoked as: %s", cmdline);
		free(cmdline);
	}
#endif /* notdef */

	/*
	**	The call to setdefuser() is moved from readcf.c to here.
	**	The result is only used in deliver.c, so delaying it until
	**	now should be safe.
	**
	**	The function setdefuser() looks up the passwd database.  In
	**	some systems, notably Suns, this may involve a YP lookup.
	**	Unfortunately YP lookups leave behind some state information
	**	which seems to be incompatible with the freeze/thaw operations.
	**
	**	Placing the call here delays it till after the freeze/thaw
	**	is complete.
	*/
	setdefuser();

	/* do heuristic mode adjustment */
	if (Verbose)
	{
		/* 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);
	MyHostName = jbuf;

	/* the indices of local and program mailers */
	st = stab("local", ST_MAILER, ST_FIND);
	if (st == NULL)
		syserr("No local mailer defined");
	else
		LocalMailer = st->s_mailer;
	st = stab("prog", ST_MAILER, ST_FIND);
	if (st == NULL)
		syserr("No prog mailer defined");
	else
		ProgMailer = st->s_mailer;

	/* operate in queue directory */
	if (chdir(QueueDir) < 0)
	{
		syserr("cannot chdir(%s)", QueueDir);
		exit(EX_SOFTWARE);
	}

	/*
	**  Do operation-mode-dependent initialization.
	*/

	switch (OpMode)
	{
	  case MD_PRINT:
		/* print the queue */
#ifdef QUEUE
		dropenvelope(CurEnv);
		printqueue();
		exit(EX_OK);
#else /* !QUEUE */
		usrerr("No queue to print");
		finis();
#endif /* QUEUE */

	  case MD_INITALIAS:
		/* initialize alias database */
		initaliases(TRUE);
		exit(EX_OK);

	  case MD_DAEMON:
		/* don't open alias database -- done in srvrsmtp */
		break;

	  default:
#if !defined(DBM_AUTOBUILD) || ( !defined(NDBM) && !defined(OTHERDBM) )
		/* open the alias database */
		initaliases(FALSE);
#endif /* !DBM_AUTOBUILD || (!NDBM && !OTHERDBM) */
		break;
	}

	if (tTd(0, 15))
	{
		/* print configuration table (or at least part of it) */
		printrules();
		for (i = 0; i < MAXMAILERS; i++)
		{
			register struct mailer *m = Mailer[i];
			int j;

			if (m == NULL)
				continue;
			printf("mailer %d (%s): P=%s S=%d/%d R=%d/%d M=%ld F=",
				i, m->m_name, m->m_mailer,
				m->m_se_rwset, m->m_sh_rwset,
				m->m_re_rwset, m->m_rh_rwset, m->m_maxsize);
			for (j = '\0'; j <= '\177'; j++)
				if (bitnset(j, m->m_mflags))
					(void) putchar(j);
			printf(" E=");
			xputs(m->m_eol);
			printf("\n");
		}
	}

	/*
	**  Switch to the main envelope.
	*/

	CurEnv = newenvelope(&MainEnvelope);
	MainEnvelope.e_flags = BlankEnvelope.e_flags;

	if (tTd(3, 1))
		(void) getla();	/* prints load average in getla() */

	/*
	**  If test mode, read addresses from stdin and process.
	*/

	if (OpMode == MD_TEST)
	{
		char buf[MAXLINE];

		printf("ADDRESS TEST MODE\nEnter <ruleset> <address>\n");
		printf("[Note: No initial ruleset 3 call]\n");
		for (;;)
		{
			register char **pvp;
			char *q;
			extern char *DelimChar;

			printf("> ");
			(void) fflush(stdout);
			if (fgets(buf, sizeof buf, stdin) == NULL)
				finis();
			for (p = buf; isspace(*p); p++)
				continue;
			q = p;
			while (*p != '\0' && !isspace(*p))
				p++;
			if (*p == '\0')
				continue;
			*p = '\0';
			if (invalidaddr(p+1))
				continue;
			do
			{
				char pvpbuf[PSBUFSIZE];

				pvp = prescan(++p, '\0', pvpbuf);
				if (pvp == NULL)
					continue;
				/* rewrite(pvp, 3); */
				p = q;
				while (*p != '\0')
				{
					rewrite(pvp, atoi(p));
					while (*p != '\0' && *p++ != ',')
						continue;
				}
			} while (*(p = DelimChar) != '\0');
		}
	}

#ifdef QUEUE
	/*
	**  If collecting stuff from the queue, go start doing that.
	*/

	if (queuemode && OpMode != MD_DAEMON && QueueIntvl == 0)
	{
		runqueue(FALSE);
		finis();
	}
#endif /* QUEUE */

	/*
	**  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
	**		during startup.
	*/

	if (OpMode == MD_DAEMON || QueueIntvl != 0)
	{
		if (!tTd(0, 1))
		{
			/* put us in background */
			if ((i = fork()) < 0)
				syserr("daemon: cannot fork");
			if (i != 0)
				exit(0);

			/* get our pid right */
			MotherPid = getpid();

			/* disconnect from our controlling tty */
			disconnect(TRUE);
		}

#ifdef QUEUE
		if (queuemode)
		{
			runqueue(TRUE);
			if (OpMode != MD_DAEMON)
				for (;;)
					pause();
		}
#endif /* QUEUE */
		dropenvelope(CurEnv);

#ifdef DAEMON
		getrequests();

		/* at this point we are in a child: reset state */
		OpMode = MD_SMTP;
		(void) newenvelope(CurEnv);
		openxscript(CurEnv);
#endif /* DAEMON */
	}
	
#ifdef SMTP
	/*
	**  If running SMTP protocol, start collecting and executing
	**  commands.  This will never return.
	*/

	if (OpMode == MD_SMTP || OpMode == MD_BSMTP)
	{
		auto int lotherend = sizeof RealHostAddr;
		bool batched = (OpMode == MD_BSMTP);
		OpMode = MD_SMTP;

		/* have to run unbuffered or else will lose synchronization */
		if (batched)
# if defined(ultrix)
			setvbuf(InChannel, (char *) &BsmtpCharBuf,
				_IOFBF, sizeof(BsmtpCharBuf));
# else /* !ultrix */
			setbuf(InChannel, (char *) NULL);
# endif /* ultrix */


# ifdef VMUNIX
		/*
		** If invoked from inetd instead of as a daemon,
		** collect the real identity of the caller.
		*/
		if (getpeername(fileno(InChannel),
		    (struct sockaddr *)&RealHostAddr, &lotherend) == 0)
		{
			register struct hostent *hp;
			char buf[MAXNAME];

			/* determine host name */
			hp = gethostbyaddr((char *) &RealHostAddr.sin_addr, sizeof RealHostAddr.sin_addr, AF_INET);
			if (hp != NULL)
				(void) strcpy(buf, hp->h_name);
			else
			{
				/* produce a dotted quad */
				(void) sprintf(buf, "[%s]",
					inet_ntoa(RealHostAddr.sin_addr));
			}
			RealHostName = newstr(buf);
		}
# endif /* VMUNIX */
		smtp(batched);
	}
#endif /* SMTP */

	/*
	**  Do basic system initialization and set the sender
	*/

	initsys();
	setsender(from);

	if (OpMode != MD_ARPAFTP && *av == NULL && !GrabTo)
	{
		usrerr("Recipient names must be specified");

		/* collect body for UUCP return */
		if (OpMode != MD_VERIFY)
			collect(FALSE);
		finis();
	}
	if (OpMode == MD_VERIFY)
		SendMode = SM_VERIFY;

	/*
	**  Scan argv and deliver the message to everyone.
	*/

	sendtoargv(av);

	/* if we have had errors sofar, arrange a meaningful exit stat */
	if (Errors > 0 && ExitStat == EX_OK)
		ExitStat = EX_USAGE;

	/*
	**  Read the input mail.
	*/

	CurEnv->e_to = NULL;
	if (OpMode != MD_VERIFY || GrabTo)
		collect(FALSE);
	errno = 0;

	if (tTd(1, 1))
		printf("From person = \"%s\"\n", CurEnv->e_from.q_paddr);

	/*
	**  Actually send everything.
	**	If verifying, just ack.
	*/

	CurEnv->e_from.q_flags |= QDONTSEND;
	CurEnv->e_to = NULL;
	sendall(CurEnv, SM_DEFAULT);

	/* collect statistics (done after sendall, since sendall may fork) */
	if (OpMode != MD_VERIFY)
		markstats(CurEnv, (ADDRESS *) NULL);

	/*
	** All done.
	*/

	finis();
}
/*
**  FINIS -- Clean up and exit.
**
**	Parameters:
**		none
**
**	Returns:
**		never
**
**	Side Effects:
**		exits sendmail
*/

void
finis()
{
	if (tTd(2, 1))
		printf("\n====finis: stat %d e_flags %o\n", ExitStat, CurEnv->e_flags);

	/* clean up temp files */
	CurEnv->e_to = NULL;
	dropenvelope(CurEnv);

	/* post statistics */
	poststats(StatFile);

	/* and exit */
#ifdef LOG
	if (LogLevel > 11)
		syslog(LOG_DEBUG, "finis, pid=%d", getpid());
#endif /* LOG */
	if (ExitStat == EX_TEMPFAIL)
		ExitStat = EX_OK;
	exit(ExitStat);
}
/*
**  INTSIG -- clean up on interrupt
**
**	This just arranges to exit.  It pessimises in that it
**	may resend a message.
**
**	Parameters:
**		none.
**
**	Returns:
**		none.
**
**	Side Effects:
**		Unlocks the current job.
*/

static SIG_TYPE
intsig()
{
	FileName = NULL;
	unlockqueue(CurEnv);
	exit(EX_OK);
}
/*
**  INITMACROS -- initialize the macro system
**
**	This just involves defining some macros that are actually
**	used internally as metasymbols to be themselves.
**
**	Parameters:
**		none.
**
**	Returns:
**		none.
**
**	Side Effects:
**		initializes several macros to be themselves.
*/

struct metamac	MetaMacros[] =
{
	/* LHS pattern matching characters */
	'*', MATCHZANY,	'+', MATCHANY,	'-', MATCHONE,	'=', MATCHCLASS,
	'~', MATCHNCLASS,		'%', MATCHMAP,	'^', MATCHNMAP,		

	/* these are RHS metasymbols */
	'#', CANONNET,	'@', CANONHOST,	':', CANONUSER,	'>', CALLSUBR,

	/* the conditional operations */
	'?', CONDIF,	'|', CONDELSE,	'.', CONDFI,

	/* and finally the hostname and database lookup characters */
	'[', HOSTBEGIN,	']', HOSTEND,
	'(', KEYBEGIN,	')', KEYEND,

#ifdef MACVALUE
	/* run-time macro expansion, not at freeze time */
	'&', MACVALUE,
#endif /* MACVALUE */
#ifdef QUOTE822
	'!', QUOTE822,	/* quote next macro if RFC822 requires it */
#endif /* QUOTE822 */
	'\0'
};

static void
initmacros()
{
	register struct metamac *m;
	char buf[5];
	register int c;

	for (m = MetaMacros; m->metaname != '\0'; m++)
	{
		buf[0] = m->metaval;
		buf[1] = '\0';
		define(m->metaname, newstr(buf), CurEnv);
	}
	buf[0] = MATCHREPL;
	buf[2] = '\0';
	for (c = '1'; c <= '9'; c++)	/* $0 has no meaning */
	{
		buf[1] = c;
		define(c, newstr(buf), CurEnv);
	}
}

#ifdef _PATH_SENDMAILFC
/*
**  FREEZE -- freeze BSS & allocated memory
**
**	This will be used to efficiently load the configuration file.
**
**	Parameters:
**		freezefile -- the name of the file to freeze to.
**
**	Returns:
**		none.
**
**	Side Effects:
**		Writes BSS and malloc'ed memory to freezefile
*/

union frz
{
	char		frzpad[BUFSIZ];	/* insure we are on a BUFSIZ boundary */
	struct
	{
		TIME_TYPE 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 */
		char	frzdatecompiled[64];	/* sendmail compilation date */
	} frzinfo;
};

static void
freeze(freezefile)
	char *freezefile;
{
	int f;
	union frz fhdr;
	extern int edata, end;
# if defined(__hpux) || defined(SYSV)
	extern void *sbrk __P((int));
# else /* !__hpux */
	extern char *sbrk __P((int));
# endif /* __hpux */
	extern char datecompiled[];

	if (freezefile == NULL)
		return;
# ifdef notdef
	{
		/*
		** NIS (nee YP) state is saved in the freeze, the readback
		** of the freeze file destroys similar information.  This
		** causes strange results later due to the unexpected closing
		** of a file descriptor using a stale copy.
		*/

		char *domain;
		if (yp_get_default_domain(&domain) == 0)
			yp_unbind(domain);
	}
# endif /* YP */

	/* try to open the freeze file */
	if ((f = open(freezefile, O_WRONLY|O_CREAT|O_TRUNC, FileMode)) < 0)
	{
		syserr("Cannot freeze %s", freezefile);
		errno = 0;
		return;
	}

	/* build the freeze header */
	fhdr.frzinfo.frzstamp = curtime();
	fhdr.frzinfo.frzbrk = sbrk(0);
# ifdef ISC
	/*
	 * There is a giant hack here. Thanks to Michael Havemester,
	 * tik@abqhh.hanse.de. The "datecompiled" must be linked as the
	 * first module for this hack to work. The problem was that only
	 * part of .data and .bss was written into the frozen config file.
	 */
	fhdr.frzinfo.frzedata = datecompiled;
# else /* !ISC */
	fhdr.frzinfo.frzedata = (char *) &edata;
# endif /* ISC */
	fhdr.frzinfo.frzend = (char *) &end;
	(void) strcpy(fhdr.frzinfo.frzver, Version);
	(void) strcpy(fhdr.frzinfo.frzdatecompiled, datecompiled);

	/* write out the freeze header */
	if (write(f, (char *) &fhdr, sizeof fhdr) != sizeof fhdr ||
	    write(f, fhdr.frzinfo.frzedata,
		  (int) (fhdr.frzinfo.frzbrk - fhdr.frzinfo.frzedata)) !=
		  (int) (fhdr.frzinfo.frzbrk - fhdr.frzinfo.frzedata))
		syserr("Cannot freeze %s", freezefile);

	/* fine, clean up */
	(void) close(f);
}
/*
**  THAW -- read in the frozen configuration file.
**
**	Parameters:
**		freezefile -- the name of the file to thaw from.
**
**	Returns:
**		TRUE if it successfully read the freeze file.
**		FALSE otherwise.
**
**	Side Effects:
**		reads freezefile in to BSS area.
*/

static int
thaw(freezefile)
	char *freezefile;
{
	int f;
	union frz fhdr;
	extern int edata, end;
	extern char datecompiled[];
# if defined(__hpux) || defined(SYSV)
	extern int brk __P((void *));
# else /* !__hpux */
	extern char *brk __P((const char *));
# endif /* __hpux */

	if (freezefile == NULL)
		return (FALSE);

	/* open the freeze file */
	if ((f = open(freezefile, O_RDONLY, 0)) < 0)
	{
# ifdef LOG
		if (OpMode == MD_DAEMON)
			syslog(LOG_WARNING, "Cannot open frozen config file %s: %m", freezefile);
# endif /* LOG */
		errno = 0;
		return (FALSE);
	}
# ifdef notdef
	{
		/*
		** NIS (nee YP) state is saved in the freeze, the readback
		** of the freeze file destroys similar information.  This
		** causes strange results later due to the unexpected closing
		** of a file descriptor using a stale copy.
		*/

		char *domain;
		if (yp_get_default_domain(&domain) == 0)
			yp_unbind(domain);
	}
# endif /* YP */

	/* read in the header */
	if (read(f, (char *) &fhdr, sizeof fhdr) < sizeof fhdr)
	{
		syserr("Cannot read frozen config file");
		(void) close(f);
		return (FALSE);
	}
	{
		char why[40];

		*why = '\0';
#ifdef ISC
		/* Hack - see comment in freeze(). */
		if (fhdr.frzinfo.frzedata != datecompiled)
#else
		if (fhdr.frzinfo.frzedata != (char *) &edata)
#endif /* ISC */
			(void) strcat(why, "Edata");
		if (fhdr.frzinfo.frzend != (char *) &end)
			(void) strcat(why, "End");
		if (strcmp(fhdr.frzinfo.frzver, Version))
			(void) strcat(why, "Version");
		if (strcmp(fhdr.frzinfo.frzdatecompiled, datecompiled))
			(void) strcat(why, "Datecompiled");
		if (*why)
		{
			if (tTd(0, 1))
				printf("Wrong version of frozen config file (%s mismatch)\n", why);
# ifdef LOG
			syslog(LOG_WARNING, "Wrong version of frozen config file (%s mismatch)", why);
# endif /* LOG */
			(void) close(f);
			return (FALSE);
		}
	}

	/* arrange to have enough space */
	if ((int)brk(fhdr.frzinfo.frzbrk) == -1)
	{
		syserr("Cannot break to %x", fhdr.frzinfo.frzbrk);
		(void) close(f);
		return (FALSE);
	}

	/* now read in the freeze file */
	if (read(f, fhdr.frzinfo.frzedata,
		 (int) (fhdr.frzinfo.frzbrk - fhdr.frzinfo.frzedata)) !=
		 (int) (fhdr.frzinfo.frzbrk - fhdr.frzinfo.frzedata))
	{
		syserr("Cannot read frozen config file");
		/* oops!  we have trashed memory..... */
		(void) write(2, "Cannot read freeze file\n", 24);
		_exit(EX_SOFTWARE);
	}

	(void) close(f);
	return (TRUE);
}
#endif /* _PATH_SENDMAILFC */
/*
**  DISCONNECT -- remove our connection with any foreground process
**
**	Parameters:
**		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
**			wait for the carrier.
**
**	Returns:
**		none
**
**	Side Effects:
**		Trys to insure that we are immune to vagaries of
**		the controlling tty.
*/

void
disconnect(fulldrop)
	bool fulldrop;
{
	int fd;

	if (tTd(52, 1))
		printf("disconnect: In %d Out %d\n", fileno(InChannel),
						fileno(OutChannel));
	if (tTd(52, 5))
	{
		printf("don't\n");
		return;
	}

	/* 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.... */
	HoldErrs = TRUE;
	setoption('e', "m", TRUE, TRUE);
	Verbose = FALSE;

	/* all input from /dev/null */
	if (InChannel != stdin)
	{
		(void) fclose(InChannel);
		InChannel = stdin;
	}
	(void) freopen("/dev/null", "r", stdin);

	/* output to the transcript */
	if (OutChannel != stdout)
	{
		(void) fclose(OutChannel);
		OutChannel = stdout;
	}
	if (CurEnv->e_xfp == NULL)
		CurEnv->e_xfp = fopen("/dev/null", "w");
	(void) fflush(stdout);
	(void) close(1);
	(void) close(2);
	while ((fd = dup(fileno(CurEnv->e_xfp))) < 2 && fd > 0)
		continue;

	/* drop our controlling TTY completely if possible */
	if (fulldrop)
	{
#if defined(XPG3)
	/*
	** setsid will provide a new session w/o any tty associated at all.
	** STANDARDS CONFORMANCE
	**     setpgrp: SVID2, XPG2
	**     setsid: XPG3, POSIX.1, FIPS 151-1
	*/
	(void) setsid();
#else /* !XPG3 */
# if defined(BSD) && BSD > 43
		daemon(1, 1);
# else /* !BSD || BSD <= 43 */
#  ifdef TIOCNOTTY
		fd = open("/dev/tty", O_RDWR, 0);
		if (fd >= 0)
		{
			(void) ioctl(fd, (int) TIOCNOTTY, (char *) 0);
			(void) close(fd);
		}
#       ifdef linux
		setpgrp();
#       else
		(void) setpgrp(0, 0);
#       endif /*linux*/
#  endif /* TIOCNOTTY */
# endif /* BSD && BSD > 43 */
# if !defined(BSD) && !defined(TIOCNOTTY) && defined(SYSV)
		setpgrp();
		if (fork() != 0)
			exit(0);
# endif /* !BSD && !TIOCNOTTY && SYSV */
#endif /* XPG3 */
		errno = 0;
	}

#ifdef LOG
	if (LogLevel > 11)
		syslog(LOG_DEBUG, "in background, pid=%d", getpid());
#endif /* LOG */

	errno = 0;
}
