peter a392fe0bdb Apply the sendmail 8.9.3 denial-of-service patch which prevents untrusted
users from running newaliases.  (This is to protect aliases.db against
truncation).

PR:		15088
1999-11-25 18:03:05 +00:00

2761 lines
60 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 1998 Sendmail, Inc. All rights reserved.
* Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*
*/
#ifndef lint
static char copyright[] =
"@(#) Copyright (c) 1998 Sendmail, Inc. All rights reserved.\n\
Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.\n\
Copyright (c) 1988, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
static char sccsid[] = "@(#)main.c 8.322 (Berkeley) 12/18/1998";
#endif /* not lint */
#define _DEFINE
#include "sendmail.h"
#include <arpa/inet.h>
#include <grp.h>
#if NAMED_BIND
#include <resolv.h>
#endif
/*
** 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 settings read in from /etc/sendmail.cf
** (read by readcf.c).
**
** 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 (11/81 - 10/88).
** International Computer Science Institute
** (11/88 - 9/89).
** UCB/Mammoth Project (10/89 - 7/95).
** InReference, Inc. (8/95 - 1/97).
** Sendmail, Inc. (1/98 - present).
** The support of the my employers is gratefully acknowledged.
** Few of them (Britton-Lee in particular) have had
** anything 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 */
{ "", "", NULL, "" };
char *CommandLineArgs; /* command line args for pid file */
bool Warn_Q_option = FALSE; /* warn about Q option use */
char **SaveArgv; /* argument vector for re-execing */
int MissingFds = 0; /* bit map of fds missing on startup */
#ifdef NGROUPS_MAX
GIDSET_T InitialGidSet[NGROUPS_MAX];
#endif
static void obsolete __P((char **));
extern void printmailer __P((MAILER *));
extern void tTflag __P((char *));
#if DAEMON && !SMTP
ERROR %%%% Cannot have DAEMON mode without SMTP %%%% ERROR
#endif /* DAEMON && !SMTP */
#if SMTP && !QUEUE
ERROR %%%% Cannot have SMTP mode without QUEUE %%%% ERROR
#endif /* DAEMON && !SMTP */
#define MAXCONFIGLEVEL 8 /* highest config version level known */
int
main(argc, argv, envp)
int argc;
char **argv;
char **envp;
{
register char *p;
char **av;
extern char Version[];
char *ep, *from;
STAB *st;
register int i;
int j;
bool queuemode = FALSE; /* process queue requests */
bool safecf = TRUE;
bool warn_C_flag = FALSE;
char warn_f_flag = '\0';
bool run_in_foreground = FALSE; /* -bD mode */
static bool reenter = FALSE;
struct passwd *pw;
struct hostent *hp;
char *nullserver = NULL;
bool forged;
char jbuf[MAXHOSTNAMELEN]; /* holds MyHostName */
static char rnamebuf[MAXNAME]; /* holds RealUserName */
char *emptyenviron[1];
QUEUE_CHAR *new;
extern int DtableSize;
extern int optind;
extern int opterr;
extern char *optarg;
extern char **environ;
extern time_t convtime __P((char *, char));
extern SIGFUNC_DECL intsig __P((int));
extern struct hostent *myhostname __P((char *, int));
extern char *getauthinfo __P((int, bool *));
extern char *getcfname __P((void));
extern SIGFUNC_DECL sigusr1 __P((int));
extern SIGFUNC_DECL sighup __P((int));
extern SIGFUNC_DECL quiesce __P((int));
extern void initmacros __P((ENVELOPE *));
extern void init_md __P((int, char **));
extern int getdtsize __P((void));
extern void tTsetup __P((u_char *, int, char *));
extern void setdefaults __P((ENVELOPE *));
extern void initsetproctitle __P((int, char **, char **));
extern void init_vendor_macros __P((ENVELOPE *));
extern void load_if_names __P((void));
extern void vendor_pre_defaults __P((ENVELOPE *));
extern void vendor_post_defaults __P((ENVELOPE *));
extern void readcf __P((char *, bool, ENVELOPE *));
extern void printqueue __P((void));
extern void sendtoargv __P((char **, ENVELOPE *));
extern void resetlimits __P((void));
#ifndef HASUNSETENV
extern void unsetenv __P((char *));
#endif
/*
** 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;
/* avoid null pointer dereferences */
TermEscape.te_rv_on = TermEscape.te_rv_off = "";
/* do machine-dependent initializations */
init_md(argc, argv);
/* in 4.4BSD, the table can be huge; impose a reasonable limit */
DtableSize = getdtsize();
if (DtableSize > 256)
DtableSize = 256;
/*
** Be sure we have enough file descriptors.
** But also be sure that 0, 1, & 2 are open.
*/
fill_fd(STDIN_FILENO, NULL);
fill_fd(STDOUT_FILENO, NULL);
fill_fd(STDERR_FILENO, NULL);
i = DtableSize;
while (--i > 0)
{
if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO)
(void) close(i);
}
errno = 0;
#if LOG
# ifdef LOG_MAIL
openlog("sendmail", LOG_PID, LOG_MAIL);
# else
openlog("sendmail", LOG_PID);
# endif
#endif
if (MissingFds != 0)
{
char mbuf[MAXLINE];
mbuf[0] = '\0';
if (bitset(1 << STDIN_FILENO, MissingFds))
strcat(mbuf, ", stdin");
if (bitset(1 << STDOUT_FILENO, MissingFds))
strcat(mbuf, ", stdout");
if (bitset(1 << STDERR_FILENO, MissingFds))
strcat(mbuf, ", stderr");
syserr("File descriptors missing on startup: %s", &mbuf[2]);
}
/* reset status from syserr() calls for missing file descriptors */
Errors = 0;
ExitStat = EX_OK;
#if XDEBUG
checkfd012("after openlog");
#endif
tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
#ifdef NGROUPS_MAX
/* save initial group set for future checks */
i = getgroups(NGROUPS_MAX, InitialGidSet);
if (i == 0)
InitialGidSet[0] = (GID_T) -1;
while (i < NGROUPS_MAX)
InitialGidSet[i++] = InitialGidSet[0];
#endif
/* drop group id privileges (RunAsUser not yet set) */
(void) drop_privileges(FALSE);
#ifdef SIGUSR1
/* arrange to dump state on user-1 signal */
setsignal(SIGUSR1, sigusr1);
#endif
/* initialize for setproctitle */
initsetproctitle(argc, argv, envp);
/* Handle any non-getoptable constructions. */
obsolete(argv);
/*
** Do a quick prescan of the argument list.
*/
#if defined(__osf__) || defined(_AIX3)
# define OPTIONS "B:b:C:cd:e:F:f:h:IiM:mN:nO:o:p:q:R:r:sTtUV:vX:x"
#endif
#if defined(sony_news)
# define OPTIONS "B:b:C:cd:E:e:F:f:h:IiJ:M:mN:nO:o:p:q:R:r:sTtUV:vX:"
#endif
#ifndef OPTIONS
# define OPTIONS "B:b:C:cd:e:F:f:h:IiM:mN:nO:o:p:q:R:r:sTtUV:vX:"
#endif
opterr = 0;
while ((j = getopt(argc, argv, OPTIONS)) != -1)
{
switch (j)
{
case 'd':
/* hack attack -- see if should use ANSI mode */
if (strcmp(optarg, "ANSI") == 0)
{
TermEscape.te_rv_on = "\033[7m";
TermEscape.te_rv_off = "\033[0m";
break;
}
tTflag(optarg);
setbuf(stdout, (char *) NULL);
break;
}
}
opterr = 1;
/* 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);
/*
** Set default values for variables.
** These cannot be in initialized data space.
*/
setdefaults(&BlankEnvelope);
RealUid = getuid();
RealGid = getgid();
pw = sm_getpwuid(RealUid);
if (pw != NULL)
(void) snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
else
(void) snprintf(rnamebuf, sizeof rnamebuf, "Unknown UID %d", RealUid);
RealUserName = rnamebuf;
if (tTd(0, 101))
{
printf("Version %s\n", Version);
finis(FALSE, EX_OK);
}
/*
** if running non-setuid binary as non-root, pretend
** we are the RunAsUid
*/
if (RealUid != 0 && geteuid() == RealUid)
{
if (tTd(47, 1))
printf("Non-setuid binary: RunAsUid = RealUid = %d\n",
(int)RealUid);
RunAsUid = RealUid;
}
else if (geteuid() != 0)
RunAsUid = geteuid();
if (RealUid != 0 && getegid() == RealGid)
RunAsGid = RealGid;
if (tTd(47, 5))
{
printf("main: e/ruid = %d/%d e/rgid = %d/%d\n",
(int)geteuid(), (int)getuid(), (int)getegid(), (int)getgid());
printf("main: RunAsUser = %d:%d\n", (int)RunAsUid, (int)RunAsGid);
}
/* save command line arguments */
i = 0;
for (av = argv; *av != NULL; )
i += strlen(*av++) + 1;
SaveArgv = (char **) xalloc(sizeof (char *) * (argc + 1));
CommandLineArgs = xalloc(i);
p = CommandLineArgs;
for (av = argv, i = 0; *av != NULL; )
{
SaveArgv[i++] = newstr(*av);
if (av != argv)
*p++ = ' ';
strcpy(p, *av++);
p += strlen(p);
}
SaveArgv[i] = NULL;
if (tTd(0, 1))
{
int ll;
extern char *CompileOptions[];
printf("Version %s\n Compiled with:", Version);
av = CompileOptions;
ll = 7;
while (*av != NULL)
{
if (ll + strlen(*av) > 63)
{
putchar('\n');
ll = 0;
}
if (ll == 0)
{
putchar('\t');
putchar('\t');
}
else
putchar(' ');
printf("%s", *av);
ll += strlen(*av++) + 1;
}
putchar('\n');
}
if (tTd(0, 10))
{
int ll;
extern char *OsCompileOptions[];
printf(" OS Defines:");
av = OsCompileOptions;
ll = 7;
while (*av != NULL)
{
if (ll + strlen(*av) > 63)
{
putchar('\n');
ll = 0;
}
if (ll == 0)
{
putchar('\t');
putchar('\t');
}
else
putchar(' ');
printf("%s", *av);
ll += strlen(*av++) + 1;
}
putchar('\n');
#ifdef _PATH_UNIX
printf("Kernel symbols:\t%s\n", _PATH_UNIX);
#endif
printf(" Def Conf file:\t%s\n", getcfname());
printf(" Pid file:\t%s\n", PidFile);
}
InChannel = stdin;
OutChannel = stdout;
/* clear sendmail's environment */
ExternalEnviron = environ;
emptyenviron[0] = NULL;
environ = emptyenviron;
/*
** restore any original TZ setting until TimeZoneSpec has been
** determined - or early log messages may get bogus time stamps
*/
if ((p = getextenv("TZ")) != NULL)
{
char *tz;
int tzlen;
tzlen = strlen(p) + 4;
tz = xalloc(tzlen);
snprintf(tz, tzlen, "TZ=%s", p);
putenv(tz);
}
/* prime the child environment */
setuserenv("AGENT", "sendmail");
if (setsignal(SIGINT, SIG_IGN) != SIG_IGN)
(void) setsignal(SIGINT, intsig);
(void) setsignal(SIGTERM, intsig);
(void) setsignal(SIGPIPE, SIG_IGN);
OldUmask = umask(022);
OpMode = MD_DELIVER;
FullName = getextenv("NAME");
/*
** Initialize name server if it is going to be used.
*/
#if NAMED_BIND
if (!bitset(RES_INIT, _res.options))
res_init();
if (tTd(8, 8))
_res.options |= RES_DEBUG;
else
_res.options &= ~RES_DEBUG;
# ifdef RES_NOALIASES
_res.options |= RES_NOALIASES;
# endif
#endif
errno = 0;
from = NULL;
/* initialize some macros, etc. */
initmacros(CurEnv);
init_vendor_macros(CurEnv);
/* version */
define('v', Version, CurEnv);
/* hostname */
hp = myhostname(jbuf, sizeof jbuf);
if (jbuf[0] != '\0')
{
struct utsname utsname;
if (tTd(0, 4))
printf("canonical name: %s\n", jbuf);
define('w', newstr(jbuf), CurEnv); /* must be new string */
define('j', newstr(jbuf), CurEnv);
setclass('w', jbuf);
p = strchr(jbuf, '.');
if (p != NULL)
{
if (p[1] != '\0')
{
define('m', newstr(&p[1]), CurEnv);
}
while (p != NULL && strchr(&p[1], '.') != NULL)
{
*p = '\0';
if (tTd(0, 4))
printf("\ta.k.a.: %s\n", jbuf);
setclass('w', jbuf);
*p++ = '.';
p = strchr(p, '.');
}
}
if (uname(&utsname) >= 0)
p = utsname.nodename;
else
{
if (tTd(0, 22))
printf("uname failed (%s)\n", errstring(errno));
makelower(jbuf);
p = jbuf;
}
if (tTd(0, 4))
printf(" UUCP nodename: %s\n", p);
p = newstr(p);
define('k', p, CurEnv);
setclass('k', p);
setclass('w', p);
}
if (hp != NULL)
{
for (av = hp->h_aliases; av != NULL && *av != NULL; av++)
{
if (tTd(0, 4))
printf("\ta.k.a.: %s\n", *av);
setclass('w', *av);
}
#if NETINET
if (hp->h_addrtype == AF_INET && hp->h_length == INADDRSZ)
{
for (i = 0; hp->h_addr_list[i] != NULL; i++)
{
char ipbuf[103];
snprintf(ipbuf, sizeof ipbuf, "[%.100s]",
inet_ntoa(*((struct in_addr *) hp->h_addr_list[i])));
if (tTd(0, 4))
printf("\ta.k.a.: %s\n", ipbuf);
setclass('w', ipbuf);
}
}
#endif
}
/* current time */
define('b', arpadate((char *) NULL), CurEnv);
QueueLimitRecipient = (QUEUE_CHAR *) NULL;
QueueLimitSender = (QUEUE_CHAR *) NULL;
QueueLimitId = (QUEUE_CHAR *) NULL;
/*
** Crack argv.
*/
av = argv;
p = strrchr(*av, '/');
if (p++ == NULL)
p = *av;
if (strcmp(p, "newaliases") == 0)
OpMode = MD_INITALIAS;
else if (strcmp(p, "mailq") == 0)
OpMode = MD_PRINT;
else if (strcmp(p, "smtpd") == 0)
OpMode = MD_DAEMON;
else if (strcmp(p, "hoststat") == 0)
OpMode = MD_HOSTSTAT;
else if (strcmp(p, "purgestat") == 0)
OpMode = MD_PURGESTAT;
optind = 1;
while ((j = getopt(argc, argv, OPTIONS)) != -1)
{
switch (j)
{
case 'b': /* operations mode */
switch (j = *optarg)
{
case MD_DAEMON:
case MD_FGDAEMON:
# if !DAEMON
usrerr("Daemon mode not implemented");
ExitStat = EX_USAGE;
break;
# endif /* DAEMON */
case MD_SMTP:
# if !SMTP
usrerr("I don't speak SMTP");
ExitStat = EX_USAGE;
break;
# endif /* SMTP */
case MD_INITALIAS:
case MD_DELIVER:
case MD_VERIFY:
case MD_TEST:
case MD_PRINT:
case MD_HOSTSTAT:
case MD_PURGESTAT:
case MD_ARPAFTP:
OpMode = j;
break;
case MD_FREEZE:
usrerr("Frozen configurations unsupported");
ExitStat = EX_USAGE;
break;
default:
usrerr("Invalid operation mode %c", j);
ExitStat = EX_USAGE;
break;
}
break;
case 'B': /* body type */
CurEnv->e_bodytype = optarg;
break;
case 'C': /* select configuration file (already done) */
if (RealUid != 0)
warn_C_flag = TRUE;
ConfFile = optarg;
(void) drop_privileges(TRUE);
safecf = FALSE;
break;
case 'd': /* debugging -- already done */
break;
case 'f': /* from address */
case 'r': /* obsolete -f flag */
if (from != NULL)
{
usrerr("More than one \"from\" person");
ExitStat = EX_USAGE;
break;
}
from = newstr(denlstring(optarg, TRUE, TRUE));
if (strcmp(RealUserName, from) != 0)
warn_f_flag = j;
break;
case 'F': /* set full name */
FullName = newstr(optarg);
break;
case 'h': /* hop count */
CurEnv->e_hopcount = strtol(optarg, &ep, 10);
if (*ep)
{
usrerr("Bad hop count (%s)", optarg);
ExitStat = EX_USAGE;
}
break;
case 'n': /* don't alias */
NoAlias = TRUE;
break;
case 'N': /* delivery status notifications */
DefaultNotify |= QHASNOTIFY;
if (strcasecmp(optarg, "never") == 0)
break;
for (p = optarg; p != NULL; optarg = p)
{
p = strchr(p, ',');
if (p != NULL)
*p++ = '\0';
if (strcasecmp(optarg, "success") == 0)
DefaultNotify |= QPINGONSUCCESS;
else if (strcasecmp(optarg, "failure") == 0)
DefaultNotify |= QPINGONFAILURE;
else if (strcasecmp(optarg, "delay") == 0)
DefaultNotify |= QPINGONDELAY;
else
{
usrerr("Invalid -N argument");
ExitStat = EX_USAGE;
}
}
break;
case 'o': /* set option */
setoption(*optarg, optarg + 1, FALSE, TRUE, CurEnv);
break;
case 'O': /* set option (long form) */
setoption(' ', optarg, FALSE, TRUE, CurEnv);
break;
case 'p': /* set protocol */
p = strchr(optarg, ':');
if (p != NULL)
{
*p++ = '\0';
if (*p != '\0')
{
ep = xalloc(strlen(p) + 1);
cleanstrcpy(ep, p, MAXNAME);
define('s', ep, CurEnv);
}
}
if (*optarg != '\0')
{
ep = xalloc(strlen(optarg) + 1);
cleanstrcpy(ep, optarg, MAXNAME);
define('r', ep, CurEnv);
}
break;
case 'q': /* run queue files at intervals */
# if QUEUE
FullName = NULL;
queuemode = TRUE;
switch (optarg[0])
{
case 'I':
if ((new = (QUEUE_CHAR *)malloc(sizeof(QUEUE_CHAR))) == NULL)
syserr("!Out of memory!!");
new->queue_match = newstr(&optarg[1]);
new->queue_next = QueueLimitId;
QueueLimitId = new;
break;
case 'R':
if ((new = (QUEUE_CHAR *)malloc(sizeof(QUEUE_CHAR))) == NULL)
syserr("!Out of memory!!");
new->queue_match = newstr(&optarg[1]);
new->queue_next = QueueLimitRecipient;
QueueLimitRecipient = new;
break;
case 'S':
if ((new = (QUEUE_CHAR *)malloc(sizeof(QUEUE_CHAR))) == NULL)
syserr("!Out of memory!!");
new->queue_match = newstr(&optarg[1]);
new->queue_next = QueueLimitSender;
QueueLimitSender = new;
break;
default:
QueueIntvl = convtime(optarg, 'm');
break;
}
# else /* QUEUE */
usrerr("I don't know about queues");
ExitStat = EX_USAGE;
# endif /* QUEUE */
break;
case 'R': /* DSN RET: what to return */
if (bitset(EF_RET_PARAM, CurEnv->e_flags))
{
usrerr("Duplicate -R flag");
ExitStat = EX_USAGE;
break;
}
CurEnv->e_flags |= EF_RET_PARAM;
if (strcasecmp(optarg, "hdrs") == 0)
CurEnv->e_flags |= EF_NO_BODY_RETN;
else if (strcasecmp(optarg, "full") != 0)
{
usrerr("Invalid -R value");
ExitStat = EX_USAGE;
}
break;
case 't': /* read recipients from message */
GrabTo = TRUE;
break;
case 'U': /* initial (user) submission */
UserSubmission = TRUE;
break;
case 'V': /* DSN ENVID: set "original" envelope id */
if (!xtextok(optarg))
{
usrerr("Invalid syntax in -V flag");
ExitStat = EX_USAGE;
}
else
CurEnv->e_envid = newstr(optarg);
break;
case 'X': /* traffic log file */
(void) drop_privileges(TRUE);
TrafficLogFile = fopen(optarg, "a");
if (TrafficLogFile == NULL)
{
syserr("cannot open %s", optarg);
ExitStat = EX_CANTCREAT;
break;
}
#ifdef HASSETVBUF
setvbuf(TrafficLogFile, NULL, _IOLBF, 0);
#else
setlinebuf(TrafficLogFile);
#endif
break;
/* compatibility flags */
case 'c': /* connect to non-local mailers */
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(j, "T", FALSE, TRUE, CurEnv);
break;
case 'e': /* error message disposition */
case 'M': /* define macro */
setoption(j, optarg, FALSE, TRUE, CurEnv);
break;
case 's': /* save From lines in headers */
setoption('f', "T", FALSE, TRUE, CurEnv);
break;
# ifdef DBM
case 'I': /* initialize alias DBM file */
OpMode = MD_INITALIAS;
break;
# endif /* DBM */
# if defined(__osf__) || defined(_AIX3)
case 'x': /* random flag that OSF/1 & AIX mailx passes */
break;
# endif
# if defined(sony_news)
case 'E':
case 'J': /* ignore flags for Japanese code conversion
impremented on Sony NEWS */
break;
# endif
default:
finis(TRUE, EX_USAGE);
break;
}
}
av += optind;
/*
** Do basic initialization.
** Read system control file.
** Extract special fields for local use.
*/
/* set up ${opMode} for use in config file */
{
char mbuf[2];
mbuf[0] = OpMode;
mbuf[1] = '\0';
define(MID_OPMODE, newstr(mbuf), CurEnv);
}
#if XDEBUG
checkfd012("before readcf");
#endif
vendor_pre_defaults(CurEnv);
readcf(getcfname(), safecf, CurEnv);
ConfigFileRead = TRUE;
vendor_post_defaults(CurEnv);
/* Enforce use of local time (null string overrides this) */
if (TimeZoneSpec == NULL)
unsetenv("TZ");
else if (TimeZoneSpec[0] != '\0')
setuserenv("TZ", TimeZoneSpec);
else
setuserenv("TZ", NULL);
tzset();
/* avoid denial-of-service attacks */
resetlimits();
if (OpMode != MD_DAEMON && OpMode != MD_FGDAEMON)
{
/* drop privileges -- daemon mode done after socket/bind */
(void) drop_privileges(FALSE);
}
/*
** Find our real host name for future logging.
*/
p = getauthinfo(STDIN_FILENO, &forged);
define('_', p, CurEnv);
/* suppress error printing if errors mailed back or whatever */
if (CurEnv->e_errormode != EM_PRINT)
HoldErrs = TRUE;
/* set up the $=m class now, after .cf has a chance to redefine $m */
expand("\201m", jbuf, sizeof jbuf, CurEnv);
setclass('m', jbuf);
/* probe interfaces and locate any additional names */
if (!DontProbeInterfaces)
load_if_names();
if (tTd(0, 1))
{
printf("\n============ SYSTEM IDENTITY (after readcf) ============");
printf("\n (short domain name) $w = ");
xputs(macvalue('w', CurEnv));
printf("\n (canonical domain name) $j = ");
xputs(macvalue('j', CurEnv));
printf("\n (subdomain name) $m = ");
xputs(macvalue('m', CurEnv));
printf("\n (node name) $k = ");
xputs(macvalue('k', CurEnv));
printf("\n========================================================\n\n");
}
/*
** Do more command line checking -- these are things that
** have to modify the results of reading the config file.
*/
/* process authorization warnings from command line */
if (warn_C_flag)
auth_warning(CurEnv, "Processed by %s with -C %s",
RealUserName, ConfFile);
if (Warn_Q_option)
auth_warning(CurEnv, "Processed from queue %s", QueueDir);
/* check body type for legality */
if (CurEnv->e_bodytype == NULL)
/* nothing */ ;
else if (strcasecmp(CurEnv->e_bodytype, "7BIT") == 0)
SevenBitInput = TRUE;
else if (strcasecmp(CurEnv->e_bodytype, "8BITMIME") == 0)
SevenBitInput = FALSE;
else
{
usrerr("Illegal body type %s", CurEnv->e_bodytype);
CurEnv->e_bodytype = NULL;
}
/* tweak default DSN notifications */
if (DefaultNotify == 0)
DefaultNotify = QPINGONFAILURE|QPINGONDELAY;
/* be sure we don't pick up bogus HOSTALIASES environment variable */
if (queuemode && RealUid != 0)
(void) unsetenv("HOSTALIASES");
/* check for sane configuration level */
if (ConfigLevel > MAXCONFIGLEVEL)
{
syserr("Warning: .cf version level (%d) exceeds sendmail version %s functionality (%d)",
ConfigLevel, Version, MAXCONFIGLEVEL);
}
/* need MCI cache to have persistence */
if (HostStatDir != NULL && MaxMciCache == 0)
{
HostStatDir = NULL;
printf("Warning: HostStatusDirectory disabled with ConnectionCacheSize = 0\n");
}
/* need HostStatusDir in order to have SingleThreadDelivery */
if (SingleThreadDelivery && HostStatDir == NULL)
{
SingleThreadDelivery = FALSE;
printf("Warning: HostStatusDirectory required for SingleThreadDelivery\n");
}
/* check for permissions */
if ((OpMode == MD_DAEMON ||
OpMode == MD_FGDAEMON ||
OpMode == MD_PURGESTAT) &&
RealUid != 0 &&
RealUid != TrustedUid)
{
if (LogLevel > 1)
sm_syslog(LOG_ALERT, NOQID,
"user %d attempted to %s",
RealUid,
OpMode != MD_PURGESTAT ? "run daemon"
: "purge host status");
usrerr("Permission denied");
finis(FALSE, EX_USAGE);
}
if (OpMode == MD_INITALIAS &&
RealUid != 0 &&
RealUid != TrustedUid &&
!wordinclass(RealUserName, 't'))
{
if (LogLevel > 1)
sm_syslog(LOG_ALERT, NOQID,
"user %d attempted to rebuild the alias map",
RealUid);
usrerr("Permission denied");
finis(FALSE, EX_USAGE);
}
if (MeToo)
BlankEnvelope.e_flags |= EF_METOO;
switch (OpMode)
{
case MD_TEST:
/* don't have persistent host status in test mode */
HostStatDir = NULL;
if (Verbose == 0)
Verbose = 2;
CurEnv->e_errormode = EM_PRINT;
HoldErrs = FALSE;
break;
case MD_VERIFY:
CurEnv->e_errormode = EM_PRINT;
HoldErrs = FALSE;
/* arrange to exit cleanly on hangup signal */
if (setsignal(SIGHUP, SIG_IGN) == (sigfunc_t) SIG_DFL)
setsignal(SIGHUP, intsig);
break;
case MD_FGDAEMON:
run_in_foreground = TRUE;
OpMode = MD_DAEMON;
/* fall through ... */
case MD_DAEMON:
vendor_daemon_setup(CurEnv);
/* remove things that don't make sense in daemon mode */
FullName = NULL;
GrabTo = FALSE;
/* arrange to restart on hangup signal */
if (SaveArgv[0] == NULL || SaveArgv[0][0] != '/')
sm_syslog(LOG_WARNING, NOQID,
"daemon invoked without full pathname; kill -1 won't work");
setsignal(SIGHUP, sighup);
/* workaround: can't seem to release the signal in the parent */
releasesignal(SIGHUP);
break;
case MD_INITALIAS:
Verbose = 2;
CurEnv->e_errormode = EM_PRINT;
HoldErrs = FALSE;
/* fall through... */
case MD_PRINT:
/* to handle sendmail -bp -qSfoobar properly */
queuemode = FALSE;
/* fall through... */
default:
/* arrange to exit cleanly on hangup signal */
if (setsignal(SIGHUP, SIG_IGN) == (sigfunc_t) SIG_DFL)
setsignal(SIGHUP, intsig);
break;
}
/* special considerations for FullName */
if (FullName != NULL)
{
char *full = NULL;
extern bool rfc822_string __P((char *));
/* full names can't have newlines */
if (strchr(FullName, '\n') != NULL)
{
FullName = full = newstr(denlstring(FullName, TRUE, TRUE));
}
/* check for characters that may have to be quoted */
if (!rfc822_string(FullName))
{
extern char *addquotes __P((char *));
/*
** Quote a full name with special characters
** as a comment so crackaddr() doesn't destroy
** the name portion of the address.
*/
FullName = addquotes(FullName);
if (full != NULL)
free(full);
}
}
/* do heuristic mode adjustment */
if (Verbose)
{
/* turn off noconnect option */
setoption('c', "F", TRUE, FALSE, CurEnv);
/* turn on interactive delivery */
setoption('d', "", TRUE, FALSE, CurEnv);
}
#ifdef VENDOR_CODE
/* check for vendor mismatch */
if (VendorCode != VENDOR_CODE)
{
extern char *getvendor __P((int));
message("Warning: .cf file vendor code mismatch: sendmail expects vendor %s, .cf file vendor is %s",
getvendor(VENDOR_CODE), getvendor(VendorCode));
}
#endif
/* check for out of date configuration level */
if (ConfigLevel < MAXCONFIGLEVEL)
{
message("Warning: .cf file is out of date: sendmail %s supports version %d, .cf file is version %d",
Version, MAXCONFIGLEVEL, ConfigLevel);
}
if (ConfigLevel < 3)
{
UseErrorsTo = TRUE;
}
/* set options that were previous macros */
if (SmtpGreeting == NULL)
{
if (ConfigLevel < 7 && (p = macvalue('e', CurEnv)) != NULL)
SmtpGreeting = newstr(p);
else
SmtpGreeting = "\201j Sendmail \201v ready at \201b";
}
if (UnixFromLine == NULL)
{
if (ConfigLevel < 7 && (p = macvalue('l', CurEnv)) != NULL)
UnixFromLine = newstr(p);
else
UnixFromLine = "From \201g \201d";
}
/* our name for SMTP codes */
expand("\201j", jbuf, sizeof jbuf, CurEnv);
MyHostName = jbuf;
if (strchr(jbuf, '.') == NULL)
message("WARNING: local host name (%s) is not qualified; fix $j in config file",
jbuf);
/* make certain that this name is part of the $=w class */
setclass('w', MyHostName);
/* the indices of built-in mailers */
st = stab("local", ST_MAILER, ST_FIND);
if (st != NULL)
LocalMailer = st->s_mailer;
else if (OpMode != MD_TEST || !warn_C_flag)
syserr("No local mailer defined");
st = stab("prog", ST_MAILER, ST_FIND);
if (st == NULL)
syserr("No prog mailer defined");
else
{
ProgMailer = st->s_mailer;
clrbitn(M_MUSER, ProgMailer->m_flags);
}
st = stab("*file*", ST_MAILER, ST_FIND);
if (st == NULL)
syserr("No *file* mailer defined");
else
{
FileMailer = st->s_mailer;
clrbitn(M_MUSER, FileMailer->m_flags);
}
st = stab("*include*", ST_MAILER, ST_FIND);
if (st == NULL)
syserr("No *include* mailer defined");
else
InclMailer = st->s_mailer;
if (ConfigLevel < 6)
{
/* heuristic tweaking of local mailer for back compat */
if (LocalMailer != NULL)
{
setbitn(M_ALIASABLE, LocalMailer->m_flags);
setbitn(M_HASPWENT, LocalMailer->m_flags);
setbitn(M_TRYRULESET5, LocalMailer->m_flags);
setbitn(M_CHECKINCLUDE, LocalMailer->m_flags);
setbitn(M_CHECKPROG, LocalMailer->m_flags);
setbitn(M_CHECKFILE, LocalMailer->m_flags);
setbitn(M_CHECKUDB, LocalMailer->m_flags);
}
if (ProgMailer != NULL)
setbitn(M_RUNASRCPT, ProgMailer->m_flags);
if (FileMailer != NULL)
setbitn(M_RUNASRCPT, FileMailer->m_flags);
}
if (ConfigLevel < 7)
{
if (LocalMailer != NULL)
setbitn(M_VRFY250, LocalMailer->m_flags);
if (ProgMailer != NULL)
setbitn(M_VRFY250, ProgMailer->m_flags);
if (FileMailer != NULL)
setbitn(M_VRFY250, FileMailer->m_flags);
}
/* MIME Content-Types that cannot be transfer encoded */
setclass('n', "multipart/signed");
/* MIME message/xxx subtypes that can be treated as messages */
setclass('s', "rfc822");
/* MIME Content-Transfer-Encodings that can be encoded */
setclass('e', "7bit");
setclass('e', "8bit");
setclass('e', "binary");
#ifdef USE_B_CLASS
/* MIME Content-Types that should be treated as binary */
setclass('b', "image");
setclass('b', "audio");
setclass('b', "video");
setclass('b', "application/octet-stream");
#endif
#if _FFR_MAX_MIME_HEADER_LENGTH
/* MIME headers which have fields to check for overflow */
setclass(macid("{checkMIMEFieldHeaders}", NULL), "content-disposition");
setclass(macid("{checkMIMEFieldHeaders}", NULL), "content-type");
/* MIME headers to check for length overflow */
setclass(macid("{checkMIMETextHeaders}", NULL), "content-description");
/* MIME headers to check for overflow and rebalance */
setclass(macid("{checkMIMEHeaders}", NULL), "content-disposition");
setclass(macid("{checkMIMEHeaders}", NULL), "content-id");
setclass(macid("{checkMIMEHeaders}", NULL), "content-transfer-encoding");
setclass(macid("{checkMIMEHeaders}", NULL), "content-type");
setclass(macid("{checkMIMEHeaders}", NULL), "mime-version");
#endif
/* operate in queue directory */
if (QueueDir == NULL)
{
if (OpMode != MD_TEST)
{
syserr("QueueDirectory (Q) option must be set");
ExitStat = EX_CONFIG;
}
}
else
{
/* test path to get warning messages */
(void) safedirpath(QueueDir, (uid_t) 0, (gid_t) 0, NULL, SFF_ANYFILE);
if (OpMode != MD_TEST && chdir(QueueDir) < 0)
{
syserr("cannot chdir(%s)", QueueDir);
ExitStat = EX_CONFIG;
}
}
/* check host status directory for validity */
if (HostStatDir != NULL && !path_is_dir(HostStatDir, FALSE))
{
/* cannot use this value */
if (tTd(0, 2))
printf("Cannot use HostStatusDirectory = %s: %s\n",
HostStatDir, errstring(errno));
HostStatDir = NULL;
}
# if QUEUE
if (queuemode && RealUid != 0 && bitset(PRIV_RESTRICTQRUN, PrivacyFlags))
{
struct stat stbuf;
/* check to see if we own the queue directory */
if (stat(".", &stbuf) < 0)
syserr("main: cannot stat %s", QueueDir);
if (stbuf.st_uid != RealUid)
{
/* nope, really a botch */
usrerr("You do not have permission to process the queue");
finis(FALSE, EX_NOPERM);
}
}
# endif /* QUEUE */
/* if we've had errors so far, exit now */
if (ExitStat != EX_OK && OpMode != MD_TEST)
finis(FALSE, ExitStat);
#if XDEBUG
checkfd012("before main() initmaps");
#endif
/*
** Do operation-mode-dependent initialization.
*/
switch (OpMode)
{
case MD_PRINT:
/* print the queue */
#if QUEUE
dropenvelope(CurEnv, TRUE);
signal(SIGPIPE, quiesce);
printqueue();
finis(FALSE, EX_OK);
#else /* QUEUE */
usrerr("No queue to print");
finis(FALSE, ExitStat);
#endif /* QUEUE */
break;
case MD_HOSTSTAT:
signal(SIGPIPE, quiesce);
mci_traverse_persistent(mci_print_persistent, NULL);
finis(FALSE, EX_OK);
break;
case MD_PURGESTAT:
mci_traverse_persistent(mci_purge_persistent, NULL);
finis(FALSE, EX_OK);
break;
case MD_INITALIAS:
/* initialize maps */
initmaps(TRUE, CurEnv);
finis(FALSE, ExitStat);
break;
case MD_SMTP:
case MD_DAEMON:
/* reset DSN parameters */
DefaultNotify = QPINGONFAILURE|QPINGONDELAY;
CurEnv->e_envid = NULL;
CurEnv->e_flags &= ~(EF_RET_PARAM|EF_NO_BODY_RETN);
/* don't open maps for daemon -- done below in child */
break;
default:
/* open the maps */
initmaps(FALSE, CurEnv);
break;
}
if (tTd(0, 15))
{
extern void printrules __P((void));
/* print configuration table (or at least part of it) */
if (tTd(0, 90))
printrules();
for (i = 0; i < MAXMAILERS; i++)
{
if (Mailer[i] != NULL)
printmailer(Mailer[i]);
}
}
/*
** Switch to the main envelope.
*/
CurEnv = newenvelope(&MainEnvelope, CurEnv);
MainEnvelope.e_flags = BlankEnvelope.e_flags;
/*
** If test mode, read addresses from stdin and process.
*/
if (OpMode == MD_TEST)
{
char buf[MAXLINE];
SIGFUNC_DECL intindebug __P((int));
if (isatty(fileno(stdin)))
Verbose = 2;
if (Verbose)
{
printf("ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)\n");
printf("Enter <ruleset> <address>\n");
}
if (setjmp(TopFrame) > 0)
printf("\n");
(void) setsignal(SIGINT, intindebug);
for (;;)
{
extern void testmodeline __P((char *, ENVELOPE *));
if (Verbose == 2)
printf("> ");
(void) fflush(stdout);
if (fgets(buf, sizeof buf, stdin) == NULL)
finis(TRUE, ExitStat);
p = strchr(buf, '\n');
if (p != NULL)
*p = '\0';
if (Verbose < 2)
printf("> %s\n", buf);
testmodeline(buf, CurEnv);
}
}
# if QUEUE
/*
** If collecting stuff from the queue, go start doing that.
*/
if (queuemode && OpMode != MD_DAEMON && QueueIntvl == 0)
{
(void) runqueue(FALSE, Verbose);
finis(TRUE, ExitStat);
}
# 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)
{
char dtype[200];
extern void getrequests __P((ENVELOPE *));
if (!run_in_foreground && !tTd(99, 100))
{
/* put us in background */
i = fork();
if (i < 0)
syserr("daemon: cannot fork");
if (i != 0)
finis(FALSE, EX_OK);
/* disconnect from our controlling tty */
disconnect(2, CurEnv);
}
dtype[0] = '\0';
if (OpMode == MD_DAEMON)
strcat(dtype, "+SMTP");
if (QueueIntvl != 0)
{
strcat(dtype, "+queueing@");
strcat(dtype, pintvl(QueueIntvl, TRUE));
}
if (tTd(0, 1))
strcat(dtype, "+debugging");
sm_syslog(LOG_INFO, NOQID,
"starting daemon (%s): %s", Version, dtype + 1);
#ifdef XLA
xla_create_file();
#endif
# if QUEUE
if (queuemode)
{
(void) runqueue(TRUE, FALSE);
if (OpMode != MD_DAEMON)
{
for (;;)
{
pause();
if (DoQueueRun)
(void) runqueue(TRUE, FALSE);
}
}
}
# endif /* QUEUE */
dropenvelope(CurEnv, TRUE);
#if DAEMON
getrequests(CurEnv);
/* drop privileges */
(void) drop_privileges(FALSE);
/* at this point we are in a child: reset state */
(void) newenvelope(CurEnv, CurEnv);
/*
** Get authentication data
*/
p = getauthinfo(fileno(InChannel), &forged);
define('_', p, &BlankEnvelope);
#endif /* DAEMON */
}
# if SMTP
/*
** If running SMTP protocol, start collecting and executing
** commands. This will never return.
*/
if (OpMode == MD_SMTP || OpMode == MD_DAEMON)
{
char pbuf[20];
extern void smtp __P((char *, ENVELOPE *));
/*
** Save some macros for check_* rulesets.
*/
if (forged)
{
char ipbuf[103];
snprintf(ipbuf, sizeof ipbuf, "[%.100s]",
inet_ntoa(RealHostAddr.sin.sin_addr));
define(macid("{client_name}", NULL),
newstr(ipbuf), &BlankEnvelope);
}
else
define(macid("{client_name}", NULL), RealHostName, &BlankEnvelope);
define(macid("{client_addr}", NULL),
newstr(anynet_ntoa(&RealHostAddr)), &BlankEnvelope);
if (RealHostAddr.sa.sa_family == AF_INET)
snprintf(pbuf, sizeof pbuf, "%d", RealHostAddr.sin.sin_port);
else
snprintf(pbuf, sizeof pbuf, "0");
define(macid("{client_port}", NULL), newstr(pbuf), &BlankEnvelope);
/* initialize maps now for check_relay ruleset */
initmaps(FALSE, CurEnv);
if (OpMode == MD_DAEMON)
{
/* validate the connection */
HoldErrs = TRUE;
nullserver = validate_connection(&RealHostAddr,
RealHostName, CurEnv);
HoldErrs = FALSE;
}
smtp(nullserver, CurEnv);
}
# endif /* SMTP */
clearenvelope(CurEnv, FALSE);
if (OpMode == MD_VERIFY)
{
CurEnv->e_sendmode = SM_VERIFY;
PostMasterCopy = NULL;
}
else
{
/* interactive -- all errors are global */
CurEnv->e_flags |= EF_GLOBALERRS|EF_LOGSENDER;
}
/*
** Do basic system initialization and set the sender
*/
initsys(CurEnv);
if (warn_f_flag != '\0' && !wordinclass(RealUserName, 't'))
auth_warning(CurEnv, "%s set sender to %s using -%c",
RealUserName, from, warn_f_flag);
setsender(from, CurEnv, NULL, '\0', FALSE);
if (macvalue('s', CurEnv) == NULL)
define('s', RealHostName, CurEnv);
if (*av == NULL && !GrabTo)
{
CurEnv->e_flags |= EF_GLOBALERRS;
usrerr("Recipient names must be specified");
/* collect body for UUCP return */
if (OpMode != MD_VERIFY)
collect(InChannel, FALSE, NULL, CurEnv);
finis(TRUE, ExitStat);
}
/*
** Scan argv and deliver the message to everyone.
*/
sendtoargv(av, CurEnv);
/* if we have had errors sofar, arrange a meaningful exit stat */
if (Errors > 0 && ExitStat == EX_OK)
ExitStat = EX_USAGE;
#if _FFR_FIX_DASHT
/*
** If using -t, force not sending to argv recipients, even
** if they are mentioned in the headers.
*/
if (GrabTo)
{
ADDRESS *q;
for (q = CurEnv->e_sendqueue; q != NULL; q = q->q_next)
q->q_flags |= QDONTSEND;
}
#endif
/*
** Read the input mail.
*/
CurEnv->e_to = NULL;
if (OpMode != MD_VERIFY || GrabTo)
{
long savedflags = CurEnv->e_flags & EF_FATALERRS;
CurEnv->e_flags |= EF_GLOBALERRS;
CurEnv->e_flags &= ~EF_FATALERRS;
collect(InChannel, FALSE, NULL, CurEnv);
/* bail out if message too large */
if (bitset(EF_CLRQUEUE, CurEnv->e_flags))
{
finis(TRUE, ExitStat);
/*NOTREACHED*/
return -1;
}
CurEnv->e_flags |= savedflags;
}
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;
if (tTd(1, 5))
{
printf("main: QDONTSEND ");
printaddr(&CurEnv->e_from, FALSE);
}
CurEnv->e_to = NULL;
CurrentLA = getla();
GrabTo = FALSE;
sendall(CurEnv, SM_DEFAULT);
/*
** All done.
** Don't send return error message if in VERIFY mode.
*/
finis(TRUE, ExitStat);
/*NOTREACHED*/
return -1;
}
/* ARGSUSED */
SIGFUNC_DECL
quiesce(sig)
int sig;
{
finis(FALSE, EX_OK);
}
/* ARGSUSED */
SIGFUNC_DECL
intindebug(sig)
int sig;
{
longjmp(TopFrame, 1);
return SIGFUNC_RETURN;
}
/*
** FINIS -- Clean up and exit.
**
** Parameters:
** drop -- whether or not to drop CurEnv envelope
** exitstat -- exit status to use for exit() call
**
** Returns:
** never
**
** Side Effects:
** exits sendmail
*/
void
finis(drop, exitstat)
bool drop;
volatile int exitstat;
{
extern void closemaps __P((void));
#ifdef USERDB
extern void _udbx_close __P((void));
#endif
if (tTd(2, 1))
{
extern void printenvflags __P((ENVELOPE *));
printf("\n====finis: stat %d e_id=%s e_flags=",
exitstat,
CurEnv->e_id == NULL ? "NOQUEUE" : CurEnv->e_id);
printenvflags(CurEnv);
}
if (tTd(2, 9))
printopenfds(FALSE);
/* if we fail in finis(), just exit */
if (setjmp(TopFrame) != 0)
{
/* failed -- just give it up */
goto forceexit;
}
/* clean up temp files */
CurEnv->e_to = NULL;
if (drop && CurEnv->e_id != NULL)
dropenvelope(CurEnv, TRUE);
/* flush any cached connections */
mci_flush(TRUE, NULL);
/* close maps belonging to this pid */
closemaps();
#ifdef USERDB
/* close UserDatabase */
_udbx_close();
#endif
# ifdef XLA
/* clean up extended load average stuff */
xla_all_end();
# endif
/* and exit */
forceexit:
if (LogLevel > 78)
sm_syslog(LOG_DEBUG, CurEnv->e_id,
"finis, pid=%d",
getpid());
if (exitstat == EX_TEMPFAIL || CurEnv->e_errormode == EM_BERKNET)
exitstat = EX_OK;
/* reset uid for process accounting */
endpwent();
setuid(RealUid);
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.
*/
/* ARGSUSED */
SIGFUNC_DECL
intsig(sig)
int sig;
{
if (LogLevel > 79)
sm_syslog(LOG_DEBUG, CurEnv->e_id, "interrupt");
FileName = NULL;
unlockqueue(CurEnv);
closecontrolsocket(TRUE);
#ifdef XLA
xla_all_end();
#endif
finis(FALSE, 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 },
/* these are RHS metasymbols */
{ '#', CANONNET }, { '@', CANONHOST }, { ':', CANONUSER },
{ '>', CALLSUBR },
/* the conditional operations */
{ '?', CONDIF }, { '|', CONDELSE }, { '.', CONDFI },
/* the hostname lookup characters */
{ '[', HOSTBEGIN }, { ']', HOSTEND },
{ '(', LOOKUPBEGIN }, { ')', LOOKUPEND },
/* miscellaneous control characters */
{ '&', MACRODEXPAND },
{ '\0' }
};
#define MACBINDING(name, mid) \
stab(name, ST_MACRO, ST_ENTER)->s_macro = mid; \
MacroName[mid] = name;
void
initmacros(e)
register ENVELOPE *e;
{
register struct metamac *m;
register int c;
char buf[5];
extern char *MacroName[256];
for (m = MetaMacros; m->metaname != '\0'; m++)
{
buf[0] = m->metaval;
buf[1] = '\0';
define(m->metaname, newstr(buf), e);
}
buf[0] = MATCHREPL;
buf[2] = '\0';
for (c = '0'; c <= '9'; c++)
{
buf[1] = c;
define(c, newstr(buf), e);
}
/* set defaults for some macros sendmail will use later */
define('n', "MAILER-DAEMON", e);
/* set up external names for some internal macros */
MACBINDING("opMode", MID_OPMODE);
/*XXX should probably add equivalents for all short macros here XXX*/
}
/*
** DISCONNECT -- remove our connection with any foreground process
**
** Parameters:
** droplev -- how "deeply" we should drop the line.
** 0 -- ignore signals, mail back errors, make sure
** output goes to stdout.
** 1 -- also, make stdout go to transcript.
** 2 -- also, disconnect from controlling terminal
** (only for daemon mode).
** e -- the current envelope.
**
** Returns:
** none
**
** Side Effects:
** Trys to insure that we are immune to vagaries of
** the controlling tty.
*/
void
disconnect(droplev, e)
int droplev;
register ENVELOPE *e;
{
int fd;
if (tTd(52, 1))
printf("disconnect: In %d Out %d, e=%lx\n",
fileno(InChannel), fileno(OutChannel), (u_long) e);
if (tTd(52, 100))
{
printf("don't\n");
return;
}
if (LogLevel > 93)
sm_syslog(LOG_DEBUG, e->e_id,
"disconnect level %d",
droplev);
/* be sure we don't get nasty signals */
(void) setsignal(SIGINT, SIG_IGN);
(void) setsignal(SIGQUIT, SIG_IGN);
/* we can't communicate with our caller, so.... */
HoldErrs = TRUE;
CurEnv->e_errormode = EM_MAIL;
Verbose = 0;
DisConnected = TRUE;
/* all input from /dev/null */
if (InChannel != stdin)
{
(void) fclose(InChannel);
InChannel = stdin;
}
if (freopen("/dev/null", "r", stdin) == NULL)
sm_syslog(LOG_ERR, e->e_id,
"disconnect: freopen(\"/dev/null\") failed: %s",
errstring(errno));
/* output to the transcript */
if (OutChannel != stdout)
{
(void) fclose(OutChannel);
OutChannel = stdout;
}
if (droplev > 0)
{
if (e->e_xfp == NULL)
{
fd = open("/dev/null", O_WRONLY, 0666);
if (fd == -1)
sm_syslog(LOG_ERR, e->e_id,
"disconnect: open(\"/dev/null\") failed: %s",
errstring(errno));
}
else
{
fd = fileno(e->e_xfp);
if (fd == -1)
sm_syslog(LOG_ERR, e->e_id,
"disconnect: fileno(e->e_xfp) failed: %s",
errstring(errno));
}
(void) fflush(stdout);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (e->e_xfp == NULL)
close(fd);
}
/* drop our controlling TTY completely if possible */
if (droplev > 1)
{
(void) setsid();
errno = 0;
}
#if XDEBUG
checkfd012("disconnect");
#endif
if (LogLevel > 71)
sm_syslog(LOG_DEBUG, e->e_id,
"in background, pid=%d",
getpid());
errno = 0;
}
static void
obsolete(argv)
char *argv[];
{
register char *ap;
register char *op;
while ((ap = *++argv) != NULL)
{
/* Return if "--" or not an option of any form. */
if (ap[0] != '-' || ap[1] == '-')
return;
/* skip over options that do have a value */
op = strchr(OPTIONS, ap[1]);
if (op != NULL && *++op == ':' && ap[2] == '\0' &&
ap[1] != 'd' &&
#if defined(sony_news)
ap[1] != 'E' && ap[1] != 'J' &&
#endif
argv[1] != NULL && argv[1][0] != '-')
{
argv++;
continue;
}
/* If -C doesn't have an argument, use sendmail.cf. */
#define __DEFPATH "sendmail.cf"
if (ap[1] == 'C' && ap[2] == '\0')
{
*argv = xalloc(sizeof(__DEFPATH) + 2);
argv[0][0] = '-';
argv[0][1] = 'C';
(void)strcpy(&argv[0][2], __DEFPATH);
}
/* If -q doesn't have an argument, run it once. */
if (ap[1] == 'q' && ap[2] == '\0')
*argv = "-q0";
/* if -d doesn't have an argument, use 0-99.1 */
if (ap[1] == 'd' && ap[2] == '\0')
*argv = "-d0-99.1";
# if defined(sony_news)
/* if -E doesn't have an argument, use -EC */
if (ap[1] == 'E' && ap[2] == '\0')
*argv = "-EC";
/* if -J doesn't have an argument, use -JJ */
if (ap[1] == 'J' && ap[2] == '\0')
*argv = "-JJ";
# endif
}
}
/*
** AUTH_WARNING -- specify authorization warning
**
** Parameters:
** e -- the current envelope.
** msg -- the text of the message.
** args -- arguments to the message.
**
** Returns:
** none.
*/
void
#ifdef __STDC__
auth_warning(register ENVELOPE *e, const char *msg, ...)
#else
auth_warning(e, msg, va_alist)
register ENVELOPE *e;
const char *msg;
va_dcl
#endif
{
char buf[MAXLINE];
VA_LOCAL_DECL
if (bitset(PRIV_AUTHWARNINGS, PrivacyFlags))
{
register char *p;
static char hostbuf[48];
extern struct hostent *myhostname __P((char *, int));
if (hostbuf[0] == '\0')
(void) myhostname(hostbuf, sizeof hostbuf);
(void) snprintf(buf, sizeof buf, "%s: ", hostbuf);
p = &buf[strlen(buf)];
VA_START(msg);
vsnprintf(p, SPACELEFT(buf, p), msg, ap);
VA_END;
addheader("X-Authentication-Warning", buf, &e->e_header);
if (LogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Authentication-Warning: %.400s",
buf);
}
}
/*
** GETEXTENV -- get from external environment
**
** Parameters:
** envar -- the name of the variable to retrieve
**
** Returns:
** The value, if any.
*/
char *
getextenv(envar)
const char *envar;
{
char **envp;
int l;
l = strlen(envar);
for (envp = ExternalEnviron; *envp != NULL; envp++)
{
if (strncmp(*envp, envar, l) == 0 && (*envp)[l] == '=')
return &(*envp)[l + 1];
}
return NULL;
}
/*
** SETUSERENV -- set an environment in the propogated environment
**
** Parameters:
** envar -- the name of the environment variable.
** value -- the value to which it should be set. If
** null, this is extracted from the incoming
** environment. If that is not set, the call
** to setuserenv is ignored.
**
** Returns:
** none.
*/
void
setuserenv(envar, value)
const char *envar;
const char *value;
{
int i;
char **evp = UserEnviron;
char *p;
if (value == NULL)
{
value = getextenv(envar);
if (value == NULL)
return;
}
i = strlen(envar);
p = (char *) xalloc(strlen(value) + i + 2);
strcpy(p, envar);
p[i++] = '=';
strcpy(&p[i], value);
while (*evp != NULL && strncmp(*evp, p, i) != 0)
evp++;
if (*evp != NULL)
{
*evp++ = p;
}
else if (evp < &UserEnviron[MAXUSERENVIRON])
{
*evp++ = p;
*evp = NULL;
}
/* make sure it is in our environment as well */
if (putenv(p) < 0)
syserr("setuserenv: putenv(%s) failed", p);
}
/*
** DUMPSTATE -- dump state
**
** For debugging.
*/
void
dumpstate(when)
char *when;
{
register char *j = macvalue('j', CurEnv);
int rs;
sm_syslog(LOG_DEBUG, CurEnv->e_id,
"--- dumping state on %s: $j = %s ---",
when,
j == NULL ? "<NULL>" : j);
if (j != NULL)
{
if (!wordinclass(j, 'w'))
sm_syslog(LOG_DEBUG, CurEnv->e_id,
"*** $j not in $=w ***");
}
sm_syslog(LOG_DEBUG, CurEnv->e_id, "CurChildren = %d", CurChildren);
sm_syslog(LOG_DEBUG, CurEnv->e_id, "--- open file descriptors: ---");
printopenfds(TRUE);
sm_syslog(LOG_DEBUG, CurEnv->e_id, "--- connection cache: ---");
mci_dump_all(TRUE);
rs = strtorwset("debug_dumpstate", NULL, ST_FIND);
if (rs > 0)
{
int stat;
register char **pvp;
char *pv[MAXATOM + 1];
pv[0] = NULL;
stat = rewrite(pv, rs, 0, CurEnv);
sm_syslog(LOG_DEBUG, CurEnv->e_id,
"--- ruleset debug_dumpstate returns stat %d, pv: ---",
stat);
for (pvp = pv; *pvp != NULL; pvp++)
sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s", *pvp);
}
sm_syslog(LOG_DEBUG, CurEnv->e_id, "--- end of state dump ---");
}
/* ARGSUSED */
SIGFUNC_DECL
sigusr1(sig)
int sig;
{
dumpstate("user signal");
return SIGFUNC_RETURN;
}
/* ARGSUSED */
SIGFUNC_DECL
sighup(sig)
int sig;
{
if (SaveArgv[0][0] != '/')
{
if (LogLevel > 3)
sm_syslog(LOG_INFO, NOQID, "could not restart: need full path");
finis(FALSE, EX_OSFILE);
}
if (LogLevel > 3)
sm_syslog(LOG_INFO, NOQID, "restarting %s on signal", SaveArgv[0]);
alarm(0);
releasesignal(SIGHUP);
closecontrolsocket(TRUE);
if (drop_privileges(TRUE) != EX_OK)
{
if (LogLevel > 0)
sm_syslog(LOG_ALERT, NOQID, "could not set[ug]id(%d, %d): %m",
RunAsUid, RunAsGid);
finis(FALSE, EX_OSERR);
}
execve(SaveArgv[0], (ARGV_T) SaveArgv, (ARGV_T) ExternalEnviron);
if (LogLevel > 0)
sm_syslog(LOG_ALERT, NOQID, "could not exec %s: %m", SaveArgv[0]);
finis(FALSE, EX_OSFILE);
}
/*
** DROP_PRIVILEGES -- reduce privileges to those of the RunAsUser option
**
** Parameters:
** to_real_uid -- if set, drop to the real uid instead
** of the RunAsUser.
**
** Returns:
** EX_OSERR if the setuid failed.
** EX_OK otherwise.
*/
int
drop_privileges(to_real_uid)
bool to_real_uid;
{
int rval = EX_OK;
GIDSET_T emptygidset[1];
if (tTd(47, 1))
printf("drop_privileges(%d): Real[UG]id=%d:%d, RunAs[UG]id=%d:%d\n",
(int)to_real_uid, (int)RealUid, (int)RealGid, (int)RunAsUid, (int)RunAsGid);
if (to_real_uid)
{
RunAsUserName = RealUserName;
RunAsUid = RealUid;
RunAsGid = RealGid;
}
/* make sure no one can grab open descriptors for secret files */
endpwent();
/* reset group permissions; these can be set later */
emptygidset[0] = (to_real_uid || RunAsGid != 0) ? RunAsGid : getegid();
if (setgroups(1, emptygidset) == -1 && geteuid() == 0)
rval = EX_OSERR;
/* reset primary group and user id */
if ((to_real_uid || RunAsGid != 0) && setgid(RunAsGid) < 0)
rval = EX_OSERR;
if ((to_real_uid || RunAsUid != 0) && setuid(RunAsUid) < 0)
rval = EX_OSERR;
if (tTd(47, 5))
{
printf("drop_privileges: e/ruid = %d/%d e/rgid = %d/%d\n",
(int)geteuid(), (int)getuid(), (int)getegid(), (int)getgid());
printf("drop_privileges: RunAsUser = %d:%d\n", (int)RunAsUid, (int)RunAsGid);
}
return rval;
}
/*
** FILL_FD -- make sure a file descriptor has been properly allocated
**
** Used to make sure that stdin/out/err are allocated on startup
**
** Parameters:
** fd -- the file descriptor to be filled.
** where -- a string used for logging. If NULL, this is
** being called on startup, and logging should
** not be done.
**
** Returns:
** none
*/
void
fill_fd(fd, where)
int fd;
char *where;
{
int i;
struct stat stbuf;
if (fstat(fd, &stbuf) >= 0 || errno != EBADF)
return;
if (where != NULL)
syserr("fill_fd: %s: fd %d not open", where, fd);
else
MissingFds |= 1 << fd;
i = open("/dev/null", fd == 0 ? O_RDONLY : O_WRONLY, 0666);
if (i < 0)
{
syserr("!fill_fd: %s: cannot open /dev/null",
where == NULL ? "startup" : where);
}
if (fd != i)
{
(void) dup2(i, fd);
(void) close(i);
}
}
/*
** TESTMODELINE -- process a test mode input line
**
** Parameters:
** line -- the input line.
** e -- the current environment.
** Syntax:
** # a comment
** .X process X as a configuration line
** =X dump a configuration item (such as mailers)
** $X dump a macro or class
** /X try an activity
** X normal process through rule set X
*/
void
testmodeline(line, e)
char *line;
ENVELOPE *e;
{
register char *p;
char *q;
auto char *delimptr;
int mid;
int i, rs;
STAB *map;
char **s;
struct rewrite *rw;
ADDRESS a;
static int tryflags = RF_COPYNONE;
char exbuf[MAXLINE];
extern bool invalidaddr __P((char *, char *));
extern char *crackaddr __P((char *));
extern void dump_class __P((STAB *, int));
extern void translate_dollars __P((char *));
extern void help __P((char *));
switch (line[0])
{
case '#':
case 0:
return;
case '?':
help("-bt");
return;
case '.': /* config-style settings */
switch (line[1])
{
case 'D':
mid = macid(&line[2], &delimptr);
if (mid == '\0')
return;
translate_dollars(delimptr);
define(mid, newstr(delimptr), e);
break;
case 'C':
if (line[2] == '\0') /* not to call syserr() */
return;
mid = macid(&line[2], &delimptr);
if (mid == '\0')
return;
translate_dollars(delimptr);
expand(delimptr, exbuf, sizeof exbuf, e);
p = exbuf;
while (*p != '\0')
{
register char *wd;
char delim;
while (*p != '\0' && isascii(*p) && isspace(*p))
p++;
wd = p;
while (*p != '\0' && !(isascii(*p) && isspace(*p)))
p++;
delim = *p;
*p = '\0';
if (wd[0] != '\0')
setclass(mid, wd);
*p = delim;
}
break;
case '\0':
printf("Usage: .[DC]macro value(s)\n");
break;
default:
printf("Unknown \".\" command %s\n", line);
break;
}
return;
case '=': /* config-style settings */
switch (line[1])
{
case 'S': /* dump rule set */
rs = strtorwset(&line[2], NULL, ST_FIND);
if (rs < 0)
{
printf("Undefined ruleset %s\n", &line[2]);
return;
}
rw = RewriteRules[rs];
if (rw == NULL)
return;
do
{
putchar('R');
s = rw->r_lhs;
while (*s != NULL)
{
xputs(*s++);
putchar(' ');
}
putchar('\t');
putchar('\t');
s = rw->r_rhs;
while (*s != NULL)
{
xputs(*s++);
putchar(' ');
}
putchar('\n');
} while ((rw = rw->r_next) != NULL);
break;
case 'M':
for (i = 0; i < MAXMAILERS; i++)
{
if (Mailer[i] != NULL)
printmailer(Mailer[i]);
}
break;
case '\0':
printf("Usage: =Sruleset or =M\n");
break;
default:
printf("Unknown \"=\" command %s\n", line);
break;
}
return;
case '-': /* set command-line-like opts */
switch (line[1])
{
case 'd':
tTflag(&line[2]);
break;
case '\0':
printf("Usage: -d{debug arguments}\n");
break;
default:
printf("Unknown \"-\" command %s\n", line);
break;
}
return;
case '$':
if (line[1] == '=')
{
mid = macid(&line[2], NULL);
if (mid != '\0')
stabapply(dump_class, mid);
return;
}
mid = macid(&line[1], NULL);
if (mid == '\0')
return;
p = macvalue(mid, e);
if (p == NULL)
printf("Undefined\n");
else
{
xputs(p);
printf("\n");
}
return;
case '/': /* miscellaneous commands */
p = &line[strlen(line)];
while (--p >= line && isascii(*p) && isspace(*p))
*p = '\0';
p = strpbrk(line, " \t");
if (p != NULL)
{
while (isascii(*p) && isspace(*p))
*p++ = '\0';
}
else
p = "";
if (line[1] == '\0')
{
printf("Usage: /[canon|map|mx|parse|try|tryflags]\n");
return;
}
if (strcasecmp(&line[1], "mx") == 0)
{
#if NAMED_BIND
/* look up MX records */
int nmx;
auto int rcode;
char *mxhosts[MAXMXHOSTS + 1];
if (*p == '\0')
{
printf("Usage: /mx address\n");
return;
}
nmx = getmxrr(p, mxhosts, FALSE, &rcode);
printf("getmxrr(%s) returns %d value(s):\n", p, nmx);
for (i = 0; i < nmx; i++)
printf("\t%s\n", mxhosts[i]);
#else
printf("No MX code compiled in\n");
#endif
}
else if (strcasecmp(&line[1], "canon") == 0)
{
char host[MAXHOSTNAMELEN];
if (*p == '\0')
{
printf("Usage: /canon address\n");
return;
}
else if (strlen(p) >= sizeof host)
{
printf("Name too long\n");
return;
}
strcpy(host, p);
(void) getcanonname(host, sizeof(host), HasWildcardMX);
printf("getcanonname(%s) returns %s\n", p, host);
}
else if (strcasecmp(&line[1], "map") == 0)
{
auto int rcode = EX_OK;
char *av[2];
if (*p == '\0')
{
printf("Usage: /map mapname key\n");
return;
}
for (q = p; *q != '\0' && !(isascii(*q) && isspace(*q)); q++)
continue;
if (*q == '\0')
{
printf("No key specified\n");
return;
}
*q++ = '\0';
map = stab(p, ST_MAP, ST_FIND);
if (map == NULL)
{
printf("Map named \"%s\" not found\n", p);
return;
}
if (!bitset(MF_OPEN, map->s_map.map_mflags))
{
printf("Map named \"%s\" not open\n", p);
return;
}
printf("map_lookup: %s (%s) ", p, q);
av[0] = q;
av[1] = NULL;
p = (*map->s_map.map_class->map_lookup)
(&map->s_map, q, av, &rcode);
if (p == NULL)
printf("no match (%d)\n", rcode);
else
printf("returns %s (%d)\n", p, rcode);
}
else if (strcasecmp(&line[1], "try") == 0)
{
MAILER *m;
STAB *s;
auto int rcode = EX_OK;
q = strpbrk(p, " \t");
if (q != NULL)
{
while (isascii(*q) && isspace(*q))
*q++ = '\0';
}
if (q == NULL || *q == '\0')
{
printf("Usage: /try mailer address\n");
return;
}
s = stab(p, ST_MAILER, ST_FIND);
if (s == NULL)
{
printf("Unknown mailer %s\n", p);
return;
}
m = s->s_mailer;
printf("Trying %s %s address %s for mailer %s\n",
bitset(RF_HEADERADDR, tryflags) ? "header" : "envelope",
bitset(RF_SENDERADDR, tryflags) ? "sender" : "recipient",
q, p);
p = remotename(q, m, tryflags, &rcode, CurEnv);
printf("Rcode = %d, addr = %s\n",
rcode, p == NULL ? "<NULL>" : p);
e->e_to = NULL;
}
else if (strcasecmp(&line[1], "tryflags") == 0)
{
if (*p == '\0')
{
printf("Usage: /tryflags [Hh|Ee][Ss|Rr]\n");
return;
}
for (; *p != '\0'; p++)
{
switch (*p)
{
case 'H':
case 'h':
tryflags |= RF_HEADERADDR;
break;
case 'E':
case 'e':
tryflags &= ~RF_HEADERADDR;
break;
case 'S':
case 's':
tryflags |= RF_SENDERADDR;
break;
case 'R':
case 'r':
tryflags &= ~RF_SENDERADDR;
break;
}
}
}
else if (strcasecmp(&line[1], "parse") == 0)
{
if (*p == '\0')
{
printf("Usage: /parse address\n");
return;
}
q = crackaddr(p);
printf("Cracked address = ");
xputs(q);
printf("\nParsing %s %s address\n",
bitset(RF_HEADERADDR, tryflags) ? "header" : "envelope",
bitset(RF_SENDERADDR, tryflags) ? "sender" : "recipient");
if (parseaddr(p, &a, tryflags, '\0', NULL, e) == NULL)
printf("Cannot parse\n");
else if (a.q_host != NULL && a.q_host[0] != '\0')
printf("mailer %s, host %s, user %s\n",
a.q_mailer->m_name, a.q_host, a.q_user);
else
printf("mailer %s, user %s\n",
a.q_mailer->m_name, a.q_user);
e->e_to = NULL;
}
else
{
printf("Unknown \"/\" command %s\n", line);
}
return;
}
for (p = line; isascii(*p) && isspace(*p); p++)
continue;
q = p;
while (*p != '\0' && !(isascii(*p) && isspace(*p)))
p++;
if (*p == '\0')
{
printf("No address!\n");
return;
}
*p = '\0';
if (invalidaddr(p + 1, NULL))
return;
do
{
register char **pvp;
char pvpbuf[PSBUFSIZE];
pvp = prescan(++p, ',', pvpbuf, sizeof pvpbuf,
&delimptr, NULL);
if (pvp == NULL)
continue;
p = q;
while (*p != '\0')
{
int stat;
rs = strtorwset(p, NULL, ST_FIND);
if (rs < 0)
{
printf("Undefined ruleset %s\n", p);
break;
}
stat = rewrite(pvp, rs, 0, e);
if (stat != EX_OK)
printf("== Ruleset %s (%d) status %d\n",
p, rs, stat);
while (*p != '\0' && *p++ != ',')
continue;
}
} while (*(p = delimptr) != '\0');
}
void
dump_class(s, id)
register STAB *s;
int id;
{
if (s->s_type != ST_CLASS)
return;
if (bitnset(id & 0xff, s->s_class))
printf("%s\n", s->s_name);
}