/* * Copyright (c) 1983, 1995-1997 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.258 (Berkeley) 10/20/97"; #endif /* not lint */ #define _DEFINE #include "sendmail.h" #include #if NAMED_BIND #include #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). ** 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(); 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; 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; 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)); /* ** 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 /* 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 running non-setuid binary, pretend we are the RunAsUid */ if (geteuid() == RealUid) { if (tTd(47, 1)) printf("Non-setuid binary: RunAsUid = RealUid = %d\n", RealUid); RunAsUid = RealUid; } if (getegid() == RealGid) RunAsGid = RealGid; /* 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; 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) { 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 } /* 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; (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': 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 */ (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: 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); /* 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); 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_PURGESTAT) && RealUid != 0) { if (LogLevel > 1) sm_syslog(LOG_ALERT, NOQID, "user %d attempted to %s", RealUid, OpMode == MD_DAEMON ? "run daemon" : "purge host status"); 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; Verbose = 2; CurEnv->e_errormode = EM_PRINT; 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; /* 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 (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"); 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 = 2; if (Verbose) { printf("ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)\n"); printf("Enter
\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) 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 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) 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"); 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)); define('_', p, &BlankEnvelope); /* validate the connection */ HoldErrs = TRUE; nullserver = !validate_connection(&RealHostAddr, RealHostName, CurEnv); HoldErrs = FALSE; #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, 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) { 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(); /*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(); 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); /* 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 (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 */ 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. */ SIGFUNC_DECL intsig(sig) int sig; { if (LogLevel > 79) sm_syslog(LOG_DEBUG, CurEnv->e_id, "interrupt"); 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; } 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; } (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 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(); 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 ? "" : 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 ---"); } SIGFUNC_DECL sigusr1(sig) int sig; { dumpstate("user signal"); return SIGFUNC_RETURN; } SIGFUNC_DECL sighup(sig) int sig; { if (SaveArgv[0][0] != '/') { if (LogLevel > 3) sm_syslog(LOG_INFO, NOQID, "could not restart: need full path"); exit(EX_OSFILE); } if (LogLevel > 3) sm_syslog(LOG_INFO, NOQID, "restarting %s on signal", SaveArgv[0]); alarm(0); releasesignal(SIGHUP); if (drop_privileges(TRUE) != EX_OK) { if (LogLevel > 0) sm_syslog(LOG_ALERT, NOQID, "could not set[ug]id(%d, %d): %m", RunAsUid, RunAsGid); exit(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]); exit(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; #ifdef NGROUPS_MAX GIDSET_T emptygidset[NGROUPS_MAX]; #endif if (tTd(47, 1)) printf("drop_privileges(%d): Real[UG]id=%d:%d, RunAs[UG]id=%d:%d\n", to_real_uid, RealUid, RealGid, RunAsUid, RunAsGid); if (to_real_uid) { RunAsUserName = RealUserName; RunAsUid = RealUid; RunAsGid = RealGid; } /* make sure no one can grab open descriptors for secret files */ endpwent(); #ifdef NGROUPS_MAX /* reset group permissions; these can be set later */ emptygidset[0] = (to_real_uid || RunAsGid != 0) ? RunAsGid : getegid(); (void) setgroups(1, emptygidset); #endif /* 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; 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; 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; } 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); 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 ? "" : 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); }