NetBSD/usr.sbin/sendmail/src/main.c

2446 lines
52 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) 1983, 1995, 1996 Eric P. Allman
* Copyright (c) 1988, 1993
* The 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
static char copyright[] =
"@(#) 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.230 (Berkeley) 1/17/97";
#endif /* not lint */
#define _DEFINE
#include "sendmail.h"
#include <arpa/inet.h>
#if NAMED_BIND
#include <resolv.h>
#endif
# ifdef lint
char edata, end;
# endif /* lint */
/*
** 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 (11/81 - 10/88).
** International Computer Science Institute
** (11/88 - 9/89).
** UCB/Mammoth Project (10/89 - 7/95).
** InReference, Inc. (8/95 - 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 */
static void obsolete();
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 7 /* 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;
typedef int (*fnptr)();
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 stat stb;
struct hostent *hp;
bool nullserver = FALSE;
char jbuf[MAXHOSTNAMELEN]; /* holds MyHostName */
static char rnamebuf[MAXNAME]; /* holds RealUserName */
char *emptyenviron[1];
extern int DtableSize;
extern int optind;
extern int opterr;
extern char *optarg;
extern char **environ;
extern time_t convtime();
extern SIGFUNC_DECL intsig __P((int));
extern struct hostent *myhostname();
extern char *getauthinfo();
extern char *getcfname();
extern SIGFUNC_DECL sigusr1 __P((int));
extern SIGFUNC_DECL sighup __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));
extern void drop_privileges __P((void));
/*
** 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);
#ifdef SIGUSR1
/* arrange to dump state on user-1 signal */
setsignal(SIGUSR1, sigusr1);
#endif
/* 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.
*/
i = open("/dev/null", O_RDWR, 0);
if (fstat(STDIN_FILENO, &stb) < 0 && errno != EOPNOTSUPP)
(void) dup2(i, STDIN_FILENO);
if (fstat(STDOUT_FILENO, &stb) < 0 && errno != EOPNOTSUPP)
(void) dup2(i, STDOUT_FILENO);
if (fstat(STDERR_FILENO, &stb) < 0 && errno != EOPNOTSUPP)
(void) dup2(i, STDERR_FILENO);
if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO)
(void) close(i);
i = DtableSize;
while (--i > 0)
{
if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO)
(void) close(i);
}
errno = 0;
#ifdef LOG
# ifdef LOG_MAIL
openlog("sendmail", LOG_PID, LOG_MAIL);
# else
openlog("sendmail", LOG_PID);
# endif
#endif
tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
/* drop group id privileges (RunAsUser not yet set) */
drop_privileges();
/* 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;
/* 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;
/* initialize for setproctitle */
initsetproctitle(argc, argv, envp);
/* clear sendmail's environment */
ExternalEnviron = environ;
emptyenviron[0] = NULL;
environ = emptyenviron;
/* 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;
# 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)
{
register int i;
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
}
/* probe interfaces and locate any additional names */
load_if_names();
/* current time */
define('b', arpadate((char *) NULL), CurEnv);
/*
** 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;
endpwent();
(void) setgid(RealGid);
(void) setuid(RealUid);
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':
QueueLimitId = newstr(&optarg[1]);
break;
case 'R':
QueueLimitRecipient = newstr(&optarg[1]);
break;
case 'S':
QueueLimitSender = newstr(&optarg[1]);
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 */
endpwent();
setgid(RealGid);
setuid(RealUid);
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:
ExitStat = EX_USAGE;
finis();
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);
/* avoid denial-of-service attacks */
resetlimits();
if (OpMode != MD_DAEMON && OpMode != MD_FGDAEMON)
{
/* drop privileges -- daemon mode done after socket/bind */
drop_privileges();
}
/*
** Find our real host name for future logging.
*/
p = getauthinfo(STDIN_FILENO);
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);
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;
/* 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();
/* 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_PURGESTAT) && RealUid != 0)
{
#ifdef LOG
if (LogLevel > 1)
syslog(LOG_ALERT, "user %d attempted to %s",
RealUid,
OpMode == MD_DAEMON ? "run daemon"
: "purge host status");
#endif
usrerr("Permission denied");
exit(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;
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 */
#ifdef LOG
if (SaveArgv[0] == NULL || SaveArgv[0][0] != '/')
syslog(LOG_WARNING, "daemon invoked without full pathname; kill -1 won't work");
#endif
setsignal(SIGHUP, sighup);
/* workaround: can't seem to release the signal in the parent */
releasesignal(SIGHUP);
break;
case MD_INITALIAS:
Verbose = TRUE;
/* 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;
}
/* full names can't have newlines */
if (FullName != NULL && strchr(FullName, '\n') != NULL)
FullName = newstr(denlstring(FullName, TRUE, TRUE));
/* 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);
}
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
/* operate in queue directory */
if (OpMode == MD_TEST)
/* nothing -- just avoid further if clauses */ ;
else if (QueueDir == NULL)
{
syserr("QueueDirectory (Q) option must be set");
ExitStat = EX_CONFIG;
}
else if (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");
exit (EX_NOPERM);
}
}
# endif /* QUEUE */
/* if we've had errors so far, exit now */
if (ExitStat != EX_OK && OpMode != MD_TEST)
{
endpwent();
setuid(RealUid);
exit(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);
printqueue();
endpwent();
setuid(RealUid);
exit(EX_OK);
#else /* QUEUE */
usrerr("No queue to print");
finis();
#endif /* QUEUE */
case MD_HOSTSTAT:
mci_traverse_persistent(mci_print_persistent, NULL);
exit(EX_OK);
break;
case MD_PURGESTAT:
mci_traverse_persistent(mci_purge_persistent, NULL);
exit(EX_OK);
break;
case MD_INITALIAS:
/* initialize alias database */
initmaps(TRUE, CurEnv);
endpwent();
setuid(RealUid);
exit(ExitStat);
case MD_SMTP:
nullserver = FALSE;
/* fall through... */
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 alias database -- done in srvrsmtp */
break;
default:
/* open the alias database */
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 = TRUE;
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)
printf("> ");
(void) fflush(stdout);
if (fgets(buf, sizeof buf, stdin) == NULL)
finis();
p = strchr(buf, '\n');
if (p != NULL)
*p = '\0';
if (!Verbose)
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) unsetenv("HOSTALIASES");
(void) runqueue(FALSE, Verbose);
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)
{
char dtype[200];
extern bool 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)
exit(0);
/* 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");
#ifdef LOG
syslog(LOG_INFO, "starting daemon (%s): %s", Version, dtype + 1);
#endif
#ifdef XLA
xla_create_file();
#endif
# if QUEUE
if (queuemode)
{
(void) runqueue(TRUE, FALSE);
if (OpMode != MD_DAEMON)
for (;;)
pause();
}
# endif /* QUEUE */
dropenvelope(CurEnv, TRUE);
#if DAEMON
nullserver = getrequests(CurEnv);
/* drop privileges */
drop_privileges();
/* at this point we are in a child: reset state */
(void) newenvelope(CurEnv, CurEnv);
/*
** Get authentication data
*/
p = getauthinfo(fileno(InChannel));
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((bool, ENVELOPE *));
/*
** Save some macros for check_* rulesets.
*/
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);
smtp(nullserver, CurEnv);
}
# endif /* SMTP */
clearenvelope(CurEnv, FALSE);
if (OpMode == MD_VERIFY)
{
CurEnv->e_sendmode = SM_VERIFY;
CurEnv->e_errormode = EM_PRINT;
PostMasterCopy = NULL;
HoldErrs = FALSE;
}
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, FALSE, NULL, CurEnv);
finis();
}
/*
** 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;
/*
** Read the input mail.
*/
CurEnv->e_to = NULL;
if (OpMode != MD_VERIFY || GrabTo)
{
CurEnv->e_flags |= EF_GLOBALERRS;
collect(InChannel, FALSE, FALSE, NULL, CurEnv);
}
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;
sendall(CurEnv, SM_DEFAULT);
/*
** All done.
** Don't send return error message if in VERIFY mode.
*/
finis();
/*NOTREACHED*/
return -1;
}
SIGFUNC_DECL
intindebug(sig)
int sig;
{
longjmp(TopFrame, 1);
return SIGFUNC_RETURN;
}
/*
** FINIS -- Clean up and exit.
**
** Parameters:
** none
**
** Returns:
** never
**
** Side Effects:
** exits sendmail
*/
void
finis()
{
if (tTd(2, 1))
{
extern void printenvflags();
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);
/* clean up temp files */
CurEnv->e_to = NULL;
if (CurEnv->e_id != NULL)
dropenvelope(CurEnv, TRUE);
/* flush any cached connections */
mci_flush(TRUE, NULL);
# ifdef XLA
/* clean up extended load average stuff */
xla_all_end();
# endif
/* and exit */
# ifdef LOG
if (LogLevel > 78)
syslog(LOG_DEBUG, "finis, pid=%d", getpid());
# endif /* LOG */
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.
*/
SIGFUNC_DECL
intsig(sig)
int sig;
{
#ifdef LOG
if (LogLevel > 79)
syslog(LOG_DEBUG, "%s: interrupt",
CurEnv->e_id == NULL ? "[NOQUEUE]" : CurEnv->e_id);
#endif
FileName = NULL;
unlockqueue(CurEnv);
#ifdef XLA
xla_all_end();
#endif
/* reset uid for process accounting */
endpwent();
setuid(RealUid);
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 },
/* 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;
}
#ifdef LOG
if (LogLevel > 93)
syslog(LOG_DEBUG, "%s: disconnect level %d",
e->e_id == NULL ? "[NOQUEUE]" : e->e_id, droplev);
#endif
/* 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 = FALSE;
DisConnected = TRUE;
/* 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 (droplev > 0)
{
if (e->e_xfp == NULL)
fd = open("/dev/null", O_WRONLY, 0666);
else
fd = fileno(e->e_xfp);
(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
# ifdef LOG
if (LogLevel > 71)
syslog(LOG_DEBUG, "in background, pid=%d", getpid());
# endif /* LOG */
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();
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);
#ifdef LOG
if (LogLevel > 3)
syslog(LOG_INFO, "%s: Authentication-Warning: %.400s",
e->e_id == NULL ? "[NOQUEUE]" : e->e_id, buf);
#endif
}
}
/*
** 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;
{
#ifdef LOG
register char *j = macvalue('j', CurEnv);
int rs;
syslog(LOG_DEBUG, "--- dumping state on %s: $j = %s ---",
when,
j == NULL ? "<NULL>" : j);
if (j != NULL)
{
if (!wordinclass(j, 'w'))
syslog(LOG_DEBUG, "*** $j not in $=w ***");
}
syslog(LOG_DEBUG, "CurChildren = %d", CurChildren);
syslog(LOG_DEBUG, "--- open file descriptors: ---");
printopenfds(TRUE);
syslog(LOG_DEBUG, "--- 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);
syslog(LOG_DEBUG,
"--- ruleset debug_dumpstate returns stat %d, pv: ---",
stat);
for (pvp = pv; *pvp != NULL; pvp++)
syslog(LOG_DEBUG, "%s", *pvp);
}
syslog(LOG_DEBUG, "--- end of state dump ---");
#endif
}
SIGFUNC_DECL
sigusr1(sig)
int sig;
{
dumpstate("user signal");
return SIGFUNC_RETURN;
}
SIGFUNC_DECL
sighup(sig)
int sig;
{
if (SaveArgv[0][0] != '/')
{
#ifdef LOG
if (LogLevel > 3)
syslog(LOG_INFO, "could not restart: need full path");
#endif
exit(EX_OSFILE);
}
#ifdef LOG
if (LogLevel > 3)
syslog(LOG_INFO, "restarting %s on signal", SaveArgv[0]);
#endif
releasesignal(SIGHUP);
if (setgid(RealGid) < 0 || setuid(RealUid) < 0)
{
#ifdef LOG
if (LogLevel > 0)
syslog(LOG_ALERT, "could not set[ug]id(%d, %d): %m",
RealUid, RealGid);
#endif
exit(EX_OSERR);
}
execv(SaveArgv[0], (ARGV_T) SaveArgv);
#ifdef LOG
if (LogLevel > 0)
syslog(LOG_ALERT, "could not exec %s: %m", SaveArgv[0]);
#endif
exit(EX_OSFILE);
}
/*
** DROP_PRIVILEGES -- reduce privileges to those of the RunAsUser option
**
** Parameters:
** none.
**
** Returns:
** none.
*/
void
drop_privileges()
{
#ifdef NGROUPS_MAX
/* reset group permissions; these can be set later */
GIDSET_T emptygidset[NGROUPS_MAX];
emptygidset[0] = RunAsGid == 0 ? getegid() : RunAsGid;
(void) setgroups(1, emptygidset);
#endif
if (RunAsGid != 0)
(void) setgid(RunAsGid);
if (RunAsUid != 0)
(void) setuid(RunAsUid);
}
/*
** 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;
if (*p == '\0')
{
printf("Usage: /map mapname key\n");
return;
}
for (q = p; *q != '\0' && !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;
}
printf("map_lookup: %s (%s) ", p, q);
p = (*map->s_map.map_class->map_lookup)
(&map->s_map, q, NULL, &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;
int 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);
}