/*
#ident "@(#)smail/src:RELEASE-3_2_0_121:modes.c,v 1.209 2005/11/14 01:31:16 woods Exp"
*/
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
* Copyright (C) 1992 Ronald S. Karr
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* modes.c:
* routines to handle the various modes of operation. Typically,
* these are major functions called directly from main.
*
* external functions: build_host_strings,
* input_signals, processing_signals,
* delivery_signals, test_addresses,
* perform_deliver_mail, deliver_mail,
* daemon_mode, noop_mode, verify_addresses,
* print_version, print_copying_file,
* print_variables, print_queue, smtp_mode,
* fork_wait
*/
#include "defs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include <signal.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
# include <memory.h>
# endif
# define _GNU_SOURCE /* to see decl. of strsignal() */
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#ifdef HAVE_PATHS_H
# include <paths.h>
#endif
#ifndef ECONNRESET
# include <net/errno.h>
#endif
#include <sys/param.h>
#if defined(POSIX_OS) || defined(UNIX_BSD) || defined(WAIT_USE_UNION)
# include <sys/wait.h>
#endif
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#if defined(UNIX_BSD) && !defined(POSIX_OS)
# include <sys/ioctl.h>
#endif
#if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_FCNTL)
# include <fcntl.h>
#else
# if defined(UNIX_BSD)
# include <sys/file.h>
# endif
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#ifdef HAVE_LIBWHOSON
# include <whoson.h>
#endif
#if defined(HAVE_RFC1413)
# include <ident.h> /* declarations for ident protocol lookups */
#endif
#include <pcre.h>
#include "smail.h"
#include "alloc.h"
#include "list.h"
#include "smailsock.h"
#include "config.h"
#include "parse.h"
#include "addr.h"
#include "field.h"
#include "hash.h"
#include "main.h"
#include "match.h"
#include "log.h"
#include "direct.h"
#include "route.h"
#include "smailwait.h"
#include "child.h"
#include "spool.h"
#include "smailstring.h"
#include "dys.h"
#include "transport.h"
#include "smailconf.h"
#include "error.h"
#include "exitcodes.h"
#include "version.h"
#include "debug.h"
#include "extern.h"
#include "smailport.h"
#ifndef PATH_DEVNULL
# ifdef _PATH_DEVNULL /* from <paths.h> */
# define PATH_DEVNULL _PATH_DEVNULL
# else
# define PATH_DEVNULL "/dev/null"
# endif
#endif
/* Check your kernel for the configured value, and also verify it has been set
* appropriately in <sys/socket.h>. There's no way to check automatically. (?)
*/
#ifndef SOMAXCONN
# define SOMAXCONN 5
#endif
/* variables exported from this file */
pid_t daemon_pid = 0;
int mailq_summary_only = FALSE;
/* variables local to this file */
static int runq_child_count = 0;
static intlist_t *runq_children = NULL;
#if defined(HAVE_BSD_NETWORKING)
static int smtpd_accept_count = 0;
static intlist_t *smtpd_children = NULL;
#endif
#ifndef UNIX_SYS5_2
volatile int got_sigchld;
#endif
volatile int got_sighup;
volatile int got_sigalrm;
/* functions local to this file */
static pid_t start_daemon __P((void));
static void bg_run_queue __P((int));
static void print_addr_info __P((struct addr *, struct addr *, struct addr *));
static void do_run_queue __P((void));
static void process_one_spool_file __P((char *));
static void read_and_process_message __P((void));
static void sig_unlink __P((int));
static void sig_close __P((int));
static void set_queue_only __P((int));
static void do_smtp __P((FILE *, FILE *, void *));
static void daemon_sighup __P((int));
static void daemon_sigterm __P((int));
static void daemon_sigalrm __P((int));
static void error_resolve_timeout __P((struct addr **, struct addr **));
static void hash_predelivered_addresses __P((void));
static void kill_some_children __P((int, intlist_t *, char *));
static void add_runq_child __P((pid_t));
static int remove_runq_child __P((pid_t));
static void kill_runq_children __P((int));
static void daemon_sigchld __P((int));
static void get_dead_children __P((void));
#if defined(HAVE_BSD_NETWORKING)
static void check_daemon_signals __P((int));
static void do_daemon_accept __P((int, int, struct sockaddr_in *));
static void add_smtpd_child __P((pid_t));
static int remove_smtpd_child __P((pid_t));
static void kill_smtpd_children __P((int));
#endif
fd_set fds_used;
fd_set fds_read;
/*
* build_host_strings - build the various types of hostnames
*
* always build primary_name. Build, if NULL, uucp_name, hostnames,
* and visible_name.
*
* XXX this should probably either be in smailconf.c
*/
void
build_host_strings()
{
char *real_hostname = compute_hostname();
char *real_domain = NULL;
char *s;
if (! real_hostname || ! *real_hostname) {
/* the machine doesn't know who he is */
panic(EX_SOFTWARE,
"build_host_strings: Configuration error: hostname unknown");
/* NOTREACHED */
}
if ((s = strchr(real_hostname, '.'))) {
*s++ = '\0';
if (*s) {
real_domain = COPY_STRING(s);
}
}
DEBUG2(DBG_MAIN_HI*2, "build_host_strings(): starting with real_hostname = %v, real_domain = %v.\n",
real_hostname, real_domain);
if (! uucp_name) {
/* uucp_name is exactly the unqualified real hostname by default */
uucp_name = real_hostname;
DEBUG1(DBG_MAIN_HI*2, "build_host_strings(): setting uucp_name = %v\n", uucp_name);
}
if (! visible_domains || ! *visible_domains) {
/* try to get a domain name, preferring real_domain */
if (real_domain) {
visible_domains = real_domain;
} else {
visible_domains = compute_domain(real_hostname);
}
DEBUG1(DBG_MAIN_HI*2, "build_host_strings(): setting domains = %v\n", visible_domains);
}
if (! hostnames) { /* do not override an explicitly set empty string */
/*
* by default hostnames is constructed from the real hostname
* and the visible_domains list. If visible_domains is NULL,
* then hostnames is exactly the real hostname.
*/
if (! visible_domains || ! *visible_domains) {
hostnames = real_hostname;
} else {
struct str str; /* build hostnames here */
int first = TRUE;
STR_INIT(&str);
for (s = strcolon(visible_domains); s; s = strcolon((char *) NULL)) {
s = chop(s);
if (*s) {
str_printf(&str, "%s%s.%s", first ? "" : ":", real_hostname, s);
first = FALSE;
}
}
STR_CAT(&str, ":localhost");
STR_NEXT(&str, '\0');
STR_DONE(&str);
hostnames = STR(&str);
}
DEBUG1(DBG_MAIN_HI*2, "build_host_strings(): setting hostnames = %v\n", hostnames);
}
/* primary_name must be non-empty */
if (! primary_name || ! *primary_name) {
if (hostnames && *hostnames) {
/* primary_name is the first non-empty hostname value by default */
for (s = strcolon(hostnames); s; s = strcolon((char *) NULL)) {
s = chop(s);
if (*s) {
primary_name = COPY_STRING(s);
break;
}
}
} else if (visible_domains) {
/* otherwise try appending the first of visible_domains to real_hostname */
for (s = strcolon(visible_domains); s; s = strcolon((char *) NULL)) {
s = chop(s);
if (*s) {
primary_name = xprintf("%s.%s", real_hostname, s);
break;
}
}
} else {
/* the default */
primary_name = real_hostname;
}
DEBUG1(DBG_MAIN_HI*2, "build_host_strings(): setting primary_name = %v\n", primary_name);
}
/* visible_name is the primary_name by default */
if (! visible_name || ! *visible_name) {
visible_name = primary_name;
DEBUG1(DBG_MAIN_HI*2, "build_host_strings(): setting visible_name = %v\n", visible_name);
}
/*
* XXX We REALLY need to validate all of the hostnames here, especially if
* we're doing any outbound SMTP as RFC 821 et al put certain limitations
* on the syntax of valid hostnames..... (eg. no underscores or other
* funny stuff, various total and label length limits, etc.)
*/
}
/*
* input_signals - setup signals to use when reading a message from stdin
*
* when reading in a message (for DELIVER_MAIL mode), the spool file should
* be removed if a SIGHUP or SIGINT comes in, as this supposedly indicates
* that the user did not complete his input message. If a SIGTERM comes
* in then set the queue_only flag, to avoid taking up lots of time.
*
* SIGALRM is turned off here as only those input modes which specify timeouts
* will use alarm() directly.
*/
void
input_signals()
{
if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
if (signal(SIGHUP, sig_unlink) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "input_signals(): signal(SIGHUP) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
}
if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
if (signal(SIGINT, sig_unlink) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "input_signals(): signal(SIGINT) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
}
if (signal(SIGALRM, SIG_IGN) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "input_signals(): signal(SIGALRM) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
if (signal(SIGTERM, set_queue_only) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "input_signals(): signal(SIGTERM) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
}
/*
* processing_signals - signals to use when processing a message
*
* in this case, ignore hangups but still allow the user to send an
* interrupt (if mode is DELIVER_MAIL), up until the time delivery is
* started. SIGTERM will close the spool file for now.
*/
void
processing_signals()
{
if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "processing_signals(): signal(SIGHUP, SIG_IGN) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
if (signal(SIGINT, (operation_mode == DELIVER_MAIL) ? sig_unlink : sig_close) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "processing_signals(): signal(SIGINT) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
}
if (signal(SIGALRM, SIG_IGN) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "processing_signals(): signal(SIGALRM, SIG_IGN) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
if (signal(SIGTERM, sig_close) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "processing_signals(): signal(SIGTERM) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
}
/*
* delivery_signals - signals to use when delivering a message
*
* in this case, ignore everything to avoid stopping in awkward states.
*
* TODO: perhaps SIGTERM should set a flag to cause smail to exit between calls
* to transport drivers. In between each driver call the message state
* will be consistent and it would be okay to call close_spool() just as
* sig_close() does.
*/
void
delivery_signals()
{
if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "delivery_signals(): signal(SIGHUP, SIG_IGN) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
if (signal(SIGINT, SIG_IGN) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "delivery_signals(): signal(SIGINT, SIG_IGN) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
if (signal(SIGALRM, SIG_IGN) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "delivery_signals(): signal(SIGALRM, SIG_IGN) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
if (signal(SIGTERM, SIG_IGN) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "delivery_signals(): signal(SIGTERM, SIG_IGN) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
}
/*
* sig_unlink - handle a signal by unlinking the spool file.
*
* we assume this means that a user didn't really want to send a message
* after all so we remove the spooled message and exit.
*/
static void
sig_unlink(sig)
int sig;
{
(void) signal(sig, SIG_IGN);
unlink_spool();
#if 0
/* normal message is 57 or 60 chars long, depending on argv[0], STR_BUMP is 64 */
assert((strlen("interrupt: mail message removed") + strlen(argv[0]) + sizeof("m") + SPOOL_FN_LEN) < STR_BUMP);
#endif
write_log(WRITE_LOG_TTY, "interrupt: mail message removed");
exit(EX_OSERR);
}
/*
* set_queue_only - handle a signal by setting the flag queue_only
*
* this will cause the message to be read in, but not processed. Thus,
* the amount of time spent on processing the message is minimized, while
* full message processing can be attempted later.
*/
static void
set_queue_only(sig)
int sig __attribute__((unused));
{
#if !(defined(UNIX_BSD) || defined(POSIX_OS)) || (defined(sun) && defined(UNIX_SYS5))
/* reset the handler if we're using unreliable signals */
if (signal(sig, set_queue_only) == SIG_ERR) {
char signm[SIG2STR_MAX];
if (sig2str(sig, signm) == -1) {
sprintf(signm, "#%d", sig);
}
write_log(WRITE_LOG_SYS, "set_queue_only(): signal(SIG%s) failed: %s.", signm, strerror(errno));
exitvalue = EX_OSERR;
}
#endif
queue_only = TRUE;
}
/*
* sig_close - handle a signal by closing the spool file.
*
* this will cause processing to stop for the current message. However,
* it should be restartable later from a queue run.
*/
static void
sig_close(sig)
int sig;
{
char signm[SIG2STR_MAX];
(void) signal(sig, SIG_IGN);
if (sig2str(sig, signm) == -1) {
sprintf(signm, "#%d", sig);
}
#if 0
/* message is max of 61 chars long, STR_BUMP is 64 */
assert((strlen("sig_close(): exiting on SIG") + SIG2STR_MAX) < STR_BUMP);
#endif
write_log(WRITE_LOG_SYS, "sig_close(): exiting on SIG%s", signm);
close_spool();
exit(0); /* this is never an error */
}
/*
* test_addresses - read addrs from stdin and route them, for fun
*
* Call parse_address and route_remote_addrs to determine which transport
* is going to be used, and what it will be given, for addresses given on
* stdin.
*/
void
test_addresses()
{
if (!isatty(STDIN_FILENO)) {
if (errfile) {
(void) fprintf(errfile, "%s: test_addresses: standard input is not a tty, exiting\n", program);
} else {
DEBUG(DBG_MAIN_LO, "test_addresses(): there's no tty on stdin!\n");
}
return;
}
X_PANIC_OKAY();
while (fputs("> ", stdout) != EOF) {
struct addr *cur;
struct addr *next;
struct addr **last_addr;
struct addr *deliver;
struct addr *defer;
struct addr *fail;
char *clean;
char *error;
char *lp;
if (!(lp = read_line(stdin))) {
break;
}
/*
* treat the input as if it were the content of an address-list header
*/
error = NULL;
lp = xprintf("Test: %s", lp); /* yes, use %s here! */
clean = process_field(lp,
strchr(lp, ':') + 1,
visible_name,
uucp_name,
&recipients,
(F_LOCAL | F_STRICT),
&error);
if (error) {
write_log(WRITE_LOG_TTY, "error in address: %s\n", error);
continue;
}
DEBUG2(DBG_MAIN_MID, "test_addresses(): process_field(%v)\n returns -> '%s'\n",
lp, clean);
if (!clean || !*clean) {
continue;
}
/*
* preparse all of the recipient addresses given as command-line
* parameters.
*/
last_addr = &recipients;
for (cur = recipients; cur; cur = next) {
char *errptr; /* error from preparse_address */
next = cur->succ;
if ((cur->work_addr = preparse_address(cur->in_addr, &errptr)) == NULL) {
if (errfile) {
dprintf(errfile,
"%v ... syntax error in address: %s\n",
cur->in_addr, errptr);
}
/* unhook the failed address from the list of recipients */
*last_addr = next;
free_addr(cur);
continue;
}
last_addr = &cur->succ;
}
/*
* given the list of recipient addresses, turn those
* addresses into more specific destinations, including
* the transport that is to be used, in the case of
* addresses destined remote
*/
deliver = NULL;
defer = NULL;
fail = NULL;
resolve_addr_list(recipients, &deliver, &defer, &fail, TRUE);
print_addr_info(deliver, defer, fail);
#if 0
/*
* XXX this seems to be failing from what may be either a double-free,
* or perhaps an incorrect assumption about where struct field values
* sometimes come from....
*/
free_addr_list(recipients);
#endif
}
return;
}
/*
* test_dnsbls - test specified type of DNSBL
*
* Names of targets to test are stored in the list of parameters.
*/
void
test_dnsbls()
{
#ifdef HAVE_BSD_NETWORKING
register struct addr *cur;
struct addr *new;
struct addr *next;
char *dnsbl;
int doflip = FALSE;
char *target = NULL;
char *smtp_dnsbl_match = NULL; /* full domain name matched by a DNSBL */
char *smtp_dnsbl_addr = NULL; /* ascii formatted address value of DNSBL A RR */
char *smtp_sess_deny_msg = NULL; /* details for reject (may contain newlines!) */
/* first reverse the list */
new = NULL;
for (cur = parameters; cur; cur = next) {
next = cur->succ;
cur->succ = new;
new = cur;
}
if (EQIC(dnsbl_to_test, "smtp_rbl_domains")) {
doflip = TRUE;
dnsbl = smtp_rbl_domains;
} else if (EQIC(dnsbl_to_test, "smtp_sender_rhsbl_domains")) {
dnsbl = smtp_sender_rhsbl_domains;
} else if (EQIC(dnsbl_to_test, "smtp_hello_dnsbl_domains")) {
dnsbl = smtp_hello_dnsbl_domains;
} else if (EQIC(dnsbl_to_test, "smtp_host_dnsbl_domains")) {
dnsbl = smtp_host_dnsbl_domains;
} else /* if (errfile) */ {
fprintf(errfile, "%s: invalid DNSBL attribute name\n", program, dnsbl_to_test);
exit(EX_USAGE);
/* NOTREACHED */
}
for (cur = new; cur; cur = cur->succ) {
if (doflip) {
target = flip_inet_addr(cur->in_addr);
/* XXX should canonicalize it too to get rid of extra zeros or whatever... */
} else {
target = xprintf("%s", cur->in_addr);
}
if (match_dnsbl(target, dnsbl, dnsbl_to_test, &smtp_dnsbl_match, &smtp_dnsbl_addr, &smtp_sess_deny_msg)) {
dprintf(stdout, "%s: '%v' matched DNSBL: %s [%s]%s%v%s\n",
program,
target,
smtp_dnsbl_match,
smtp_dnsbl_addr,
smtp_sess_deny_msg ? ": ``" : "",
smtp_sess_deny_msg ? smtp_sess_deny_msg : "",
smtp_sess_deny_msg ? "''" : "");
}
xfree(target);
}
#else
/* if (errfile) */
fprintf(errfile, "%s: DNSBLs testing is not supported\n", program);
exit(EX_USAGE);
/* NOTREACHED */
#endif
}
/*
* perform_deliver_mail - read in a message and call deliver_mail()
*
* Build a queue file using a message on stdin. Then, if we are
* performing immediate delivery of messages, call deliver_mail() to
* deliver the message.
*/
void
perform_deliver_mail()
{
char **argv; /* args from read_message() */
char *errmsg;
X_NO_PANIC();
/* setup signals to remove the spool file on errors */
input_signals();
/* read the message in */
errno = 0; /* XXX using errno to pass status is an ugly hack */
if (queue_message(stdin, dot_usage, recipients, &errmsg) == FAIL) {
int oerrno = errno;
open_system_logs();
log_spool_errors();
panic(EX_OSFILE, "error queuing message: %s: %s", errmsg, oerrno ? strerror(oerrno) : "rejected");
/* NOTREACHED */
}
X_PANIC_OKAY();
/*
* up til now keyboard signals would have caused mail to be
* removed. Now that we actually have the message, setup
* signals appropriate for guarranteeing delivery or panics
* on errors.
*/
processing_signals();
/*
* open the system and per message log files.
* Do this after spool_message so that panic errors while opening
* the log files do not dump the mail on the floor.
*/
open_system_logs();
/*
* if we are running as rmail or rsmtp, then always return a zero
* exitstatus for errors that occur after successfully spooling
* the message. Otherwise, the UUCP subsystem (which calls rmail
* or rsmtp for mail delivery) may return error messages to the
* sender, even though smail will now be in complete control of
* error handling on this message.
*/
if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
force_zero_exitvalue = TRUE;
}
/*
* read through the message again, but without resetting args, and without
* re-checking the sender address -- this routine is invoked only from the
* command-line for stdin delivery.
*/
if ((argv = read_message()) == NULL) {
/* see if there was actually a message collected */
if (msg_size == 0) {
write_log(WRITE_LOG_SYS, "discarding empty spool file!");
unlink_spool();
exitvalue = EX_OK;
} else {
open_system_logs();
log_spool_errors();
/* XXX should perhaps be a panic(EX_OSFILE, ...)? */
write_log(WRITE_LOG_SYS, "failed to read queued message!");
exitvalue = EX_OSFILE;
}
return;
}
/*
* make a log entry for the new message
*/
log_incoming();
/* log errors generated in spooling the message */
log_spool_errors();
/*
* if a precedence: header is given
* then change the grade for the mail message
*/
check_grade();
/* if we are only queuing, we have gone as far as we need to */
if (queue_only || deliver_mode == QUEUE_MESSAGE
|| (msg_grade < min_delivery_grade)
|| (msg_grade > max_delivery_grade)) {
if (debug && dont_deliver) {
/* unless we are debugging as well */
DEBUG(DBG_MAIN_LO,
"debugging is on, -Q (queue_only) flag ignored\n");
} else {
if (debug) {
DEBUG(DBG_MAIN_LO,
"-Q (queue_only) specified, or message grade out of delivery range, and message is queued\n");
}
close_spool();
return;
}
}
/* reprocess the arguments as read from the spool file */
process_args(argv, FALSE);
if (sender_is_trusted) {
check_sender(TRUE);
}
process_recipients(TRUE);
/* XXX free_argv(argv); */
/*
* if we are delivering in background, and if we haven't been asked to
* report errors to the terminal, fork a child to perform delivery and
* exit.
*/
if (deliver_mode == BACKGROUND && error_processing != TERMINAL) {
pid_t pid;
delivery_signals(); /* disassociate from terminal */
(void) fclose(stdin);
/* unlock the message in the parent, first */
unlock_message();
pid = fork();
if (pid < 0) {
/* fork failed, just leave the queue file there and exit */
write_log(WRITE_LOG_TTY, "fork failed: %s, message queued", strerror(errno));
close_spool();
return;
}
if (pid > 0) {
/* in parent process, just return */
/* XXX close_system_logs() */
return;
}
/* XXX don't worry about cleaning PID lists -- they cannot be used */
#ifdef POSIX_OS
(void) setsid();
#else /* not POSIX_OS */
(void) setpgrp(0, getpid());
#endif /* POSIX_OS */
if (lock_message() == FAIL) {
/* somebody else grabbed the lock, let them deliver */
/* XXX close_system_logs() */
return;
}
}
/* read the various configuration files */
if ((errmsg = read_transport_file()) ||
(errmsg = read_router_file()) ||
(errmsg = read_director_file()) ||
(errmsg = read_qualify_file()) ||
(errmsg = read_retry_file()))
{
panic(EX_OSFILE, "%s", errmsg);
/* NOTREACHED */
}
/*
* process the message, find all of the recipients and
* perform delivery.
*/
deliver_mail();
/*
* close the system-wide log files
*/
close_system_logs();
}
/*
* deliver_mail - oversee the delivery of mail (default mailer operation)
*
* Spool the mail, process the header, process the addresses, route,
* alias expand, deliver remote mail, deliver local mail, process errors.
*/
void
deliver_mail()
{
struct addr *cur; /* addr being processed */
struct addr *next; /* next addr to process */
struct addr *route_list; /* output list of addrs to route */
struct addr *fail = NULL; /* output list of failed addrs */
struct addr *defer = NULL; /* output list of deferred addrs */
struct addr *deliver; /* output list of addresses to deliver */
struct assign_transport *assigned_transports = NULL; /* transport instances */
char *error;
struct identify_addr *sent_errors; /* addresses previously sent errors */
/*
* This code attempts to optimise the reprocessing of queued mail
* by putting sucessfully delivered addresses into the hash table
* to prevent them being put through the routers.
*/
hash_predelivered_addresses();
open_msg_log(); /* open msglog in case needed */
/*
* preparse all of the recipient addresses given as command-line
* parameters.
*
*/
route_list = recipients;
for (cur = route_list, route_list = NULL; cur; cur = next) {
next = cur->succ;
if (! (cur->work_addr = preparse_address(cur->in_addr, &error))) {
/*
* ERR_147 - parse error in input address
*
* DESCRIPTION
* preparse_address() encountered a parsing error in one of
* the addresses supplied by the sender. The specific
* error was returned in `error'.
*
* ACTIONS
* Fail the address and send an error to the sender.
*
* RESOLUTION
* The sender should supply a valid address.
*/
cur->error = note_error(ERR_NSENDER|ERR_147,
xprintf("%v: parse error: %s", cur->in_addr, error));
cur->flags &= ~ADDR_FORM_MASK;
cur->flags |= PARSE_ERROR;
cur->succ = fail;
fail = cur;
continue;
}
/*
* If we are extracting addresses from the header(s) (-t was given on
* the command-line), then these command-line specified addresses are
* NOT to receive the mail. To accomplish this, add them to the global
* hit_table hash table so they will be ignored later.
*/
if (extract_addresses) {
(void) add_to_hash(cur->work_addr, (char *) NULL, (size_t) 0, hit_table);
free_addr(cur); /* don't need it anymore */
continue;
}
cur->succ = route_list;
route_list = cur;
}
if (extract_addresses) {
route_list = NULL; /* clear list-head pointer for reuse */
}
/*
* process_header() will perform a preliminary analysis of the header
* fields. It will note which standard fields exist and if we pass
* route_list to it then it will return a list of recipient addresses from
* the appropriate header fields. It will perform some initial processing
* of the "From:" field and, depending upon configuration, may put `,'
* characters between addresses in other fields. Also, some required
* fields will be added if they do not exist, (i.e., From: and To: and
* Message-Id:).
*/
error = process_header(extract_addresses ? &route_list : (struct addr **) NULL);
/* any error in the headers is fatal */
if (error) {
char *errmsg = xprintf("error found in message headers: %s", error);
if (extract_addresses) {
route_list = NULL;
}
if (dont_deliver) {
/* don't create a bounce for no reason! */
DEBUG1(DBG_MAIN_LO, "%s\n", errmsg);
xfree(errmsg);
} else {
struct addr *tmpfail = alloc_addr();
/* we need a fake addr to trigger notify() below... */
tmpfail->next_addr = COPY_STRING("(none)");
/* XXX ERR_100 is a hack */
tmpfail->error = note_error(ERR_NSENDER | ERR_100, errmsg);
tmpfail->succ = fail;
fail = tmpfail;
}
}
/*
* given the list of recipient addresses, turn those
* addresses into more specific destinations, including
* the transport that is to be used in the case of
* addresses destined for remote delivery
*/
deliver = NULL;
defer = NULL;
if (route_list) {
resolve_addr_list(route_list, &deliver, &defer, &fail, TRUE);
}
if (deliver == NULL && defer == NULL && fail == NULL) {
if (dont_deliver) {
/* don't create a bounce for no reason! */
DEBUG(DBG_MAIN_LO, "no valid recipients were found, no delivery occurred\n");
} else {
struct addr *tmpfail = alloc_addr();
/*
* we need a fake addr to trigger notify() below...
*
* we use the next_addr field to avoid having notify() fail by
* starting BSMTP to the error handler and then sending no input to
* it.
*/
/* XXX should in_addr be "" instead of NULL? */
tmpfail->next_addr = COPY_STRING("(none)");
/* XXX ERR_100 is a hack */
tmpfail->error = note_error(ERR_NSENDER | ERR_100, "no valid recipients were found, no delivery occurred");
tmpfail->succ = fail;
fail = tmpfail;
}
}
/*
* check to see if there's a already a message log for this message.
*
* if so then remove addresses from the delivery list if we have already
* delivered a copy of the message to them, add the error info from latest
* of any deferral messages in the log, and find the list of addresses for
* which we have already returned notifications, if any.
*/
sent_errors = NULL;
if (deliver) {
deliver = process_msg_log(deliver, &sent_errors);
}
/*
* If we've sent an error for a given recipient address then we've
* obviously also already logged a deferral or failure so don't bother with
* logging another or doing any other further processing of it....
*/
while (sent_errors) {
defer = remove_addr(defer, sent_errors->address, sent_errors->parent);
fail = remove_addr(fail, sent_errors->address, sent_errors->parent);
sent_errors = sent_errors->succ;
}
if (defer != NULL) {
long message_age = (long) (time((time_t *) NULL) - message_date());
if (message_age > resolve_timeout) {
/*
* This message has been waiting for delivery longer than the
* resolve_timeout, so we convert all defers into errors
*/
error_resolve_timeout(&defer, &fail);
return_to_sender = TRUE;
}
}
/*
* log all failed addresses right away
*/
if (fail) {
fail_delivery(fail);
}
/*
* assign instances of transports for remote addresses
*/
if (deliver) {
assigned_transports = assign_transports(deliver);
}
/*
* deliver all of the assigned mail. Note: call_transport
* will already have handled log entries for failed addresses.
*/
delivery_signals();
if (assigned_transports) {
call_transports(assigned_transports, &defer, &fail);
}
/*
* perform error notification for all failed addresses, and perhaps also
* for some deferred addresses.
*/
notify(defer, fail);
/*
* tidy up before going away
*/
if (call_freeze_message) {
/*
* leave a file in an error/ directory for the system administrator to
* look at. This is done for failed error mail (double bounces) and
* for problems resulting from configuration errors.
*/
freeze_message();
} else if (some_deferred_addrs) {
/*
* leave the file around to be processed by a later queue run.
* Use this for temporary problems such as being blocked by a
* locked file, or timeouts waiting for a response from a
* remote system.
*/
close_spool();
} else {
/*
* if no requests for deferring of addresses or of the message
* occured, then we are done with the message. Thus, unlink
* the message and the per-message log file.
*/
write_log(WRITE_LOG_SYS, "Completed.");
unlink_spool();
unlink_msg_log();
}
}
/*
* This code attempts to optimise the reprocessing of queued mail by putting
* sucessfully delivered remote addresses into the hash table to prevent them
* being put through the routers. (We can't effectively pre-hash locally
* deliverable addresses because of course they are transformed into something
* else before they are added to the hash table.)
*
* This cuts down the time taken doing DNS lookups dramatically especially for
* mailing list mail -- it is often far cheaper to read the MLOG file twice
* (which doing this will indeed cause to happen) than it is to do the
* additional DNS queries.
*/
static void
hash_predelivered_addresses()
{
char *s; /* working string */
struct str work_address;
char *address; /* the address to remove */
char *parent; /* parent of address to remove */
unsigned long ecode; /* needed by decode_x_line() */
char *target; /* Working info from parsed address */
char *remainder; /* Working info from parsed address */
int parse_flag; /* user by parse_address */
char *error; /* error returned by preparse_address() */
char *preparsed_address; /* address stripped of non-essencials*/
DEBUG(DBG_NOTIFY_HI, "hash_predelivered_addresses called...\n");
STR_INIT(&work_address);
for (s = scan_msg_log(TRUE); s; s = scan_msg_log(FALSE)) {
if (HDREQ("Xfail", s) ||
HDREQ("Xsucceed", s)) {
char *content;
content = strchr(s, ':') + 2;
/* get the address (and the parent address) */
(void) decode_x_line(content, &address, &parent, &ecode);
/* Strip off any <> etc. */
preparsed_address = preparse_address(address, &error);
if (preparsed_address == NULL) {
DEBUG1(DBG_NOTIFY_LO, "Oops, log entry found without a valid address:\n\t%v\n", s);
continue;
}
/*
* don't even think about hashing out addresses to pipes
*/
if (*preparsed_address != '|') {
/*
* Find if address is local or not
*/
STR_CLEAR(&work_address);
STR_CAT(&work_address, preparsed_address);
parse_flag = 0;
if ((parse_address(STR(&work_address),
&target,
&remainder,
&parse_flag) != LOCAL) && !islocalhost(target)) {
DEBUG2(DBG_NOTIFY_MID, "Hashing out %v (%v) ... previously processed.\n", address, preparsed_address);
/*
* and if it is not local then arrange for it to be ignored
* during reprocessing so as to avoid the costly remote DNS
* lookups associated with such reprocessing
*/
(void) add_to_hash(preparsed_address, (char *) NULL, (size_t) 0, hit_table);
}
}
}
}
}
#if defined(HAVE_BSD_NETWORKING)
/*
* daemon_mode - be a daemon waiting for requests
*
* Listen on the smtp port for connections. Accept these connections and
* read smtp commands from them.
*/
void
daemon_mode()
{
int ls; /* listen socket */
int as; /* accept socket */
struct sockaddr_in sockin; /* the listen socket address */
struct sockaddr_in from; /* the accept socket address */
struct servent *smtp_service; /* smtp service file entry */
struct hostent *hostentp; /* host file entry */
in_port_t port;
int accept_err_cnt = 0;
int nsel;
int optval = 1;
#if defined(O_NONBLOCK)
int flags;
#endif
X_PANIC_OKAY();
/*
* don't use background delivery mode. Since forked smtp connection
* handlers are in background anyway, the extra child process level
* could only serve to mess up the count of child processes kept for
* comparison with the smtp_accept_max value.
*/
if (deliver_mode == BACKGROUND) {
deliver_mode = FOREGROUND;
}
/*
* we aren't interested in the old stdin or stdout, substitute
* /dev/null
*/
if (close(0) < 0) {
panic(EX_OSERR, "close(0): %s", strerror(errno));
/* NOTREACHED */
}
if (close(1) < 0) {
panic(EX_OSERR, "close(1): %s", strerror(errno));
/* NOTREACHED */
}
if (open(PATH_DEVNULL, 0) < 0) {
panic(EX_OSERR, "open(%s): %s", PATH_DEVNULL, strerror(errno));
/* NOTREACHED */
}
if (dup(0) < 0) {
panic(EX_OSERR, "dup(0): %s", strerror(errno));
/* NOTREACHED */
}
/* setup the listen socket */
if (smtp_service_name == NULL) {
smtp_service_name = "smtp";
}
if (isdigit((int) smtp_service_name[0])) {
port = htons((in_port_t) atoi(smtp_service_name));
} else {
if ((smtp_service = getservbyname(smtp_service_name, "tcp")) == NULL) {
panic(EX_UNAVAILABLE, "%v/tcp: unknown service", smtp_service_name);
/* NOTREACHED */
}
port = smtp_service->s_port;
}
(void) memset((char *) &sockin, '\0', sizeof(sockin));
ls = socket(AF_INET, SOCK_STREAM, 0);
if (ls < 0) {
panic(EX_OSERR, "socket(AF_INET, SOCK_STREAM, 0) failed: %s", strerror(errno));
/* NOTREACHED */
}
#if defined(O_NONBLOCK)
/*
* if possible make our listen socket non-blocking so accept() doesn't
* block but only select()....
*/
if ((flags = fcntl(ls, F_GETFL, 0)) == -1) {
panic(EX_OSERR, "fcntl(ls, F_GETFL, 0): failed: %s", strerror(errno));
/* NOTREACHED */
}
if (fcntl(ls, F_SETFL, flags | O_NONBLOCK) == -1) {
panic(EX_OSERR, "fcntl(ls, F_SETFL, flags | O_NONBLOCK): failed: %s", strerror(errno));
/* NOTREACHED */
}
#endif
sockin.sin_family = AF_INET;
if (listen_name) {
hostentp = gethostbyname(listen_name);
if (!hostentp) {
open_system_logs();
log_spool_errors();
panic(EX_OSFILE, "listen_name config error: host '%v' not found: %s", listen_name, strerror(errno));
/* NOTREACHED */
}
memcpy(&sockin.sin_addr, hostentp->h_addr_list[0], sizeof(struct in_addr));
DEBUG1(DBG_MAIN_LO, "listen on ip addr [%s]\n", inet_ntoa(sockin.sin_addr));
} else {
sockin.sin_addr.s_addr = INADDR_ANY;
}
sockin.sin_port = port;
/*
* set SO_REUSEADDR so that the daemon can be restarted while
* a connection is being handled. Without this, a connection
* alone will prevent reuse of the smtp port number for listening
* purposes.
*/
/* XXX: optval argument is (const char *) on Solaris-2 and (void *) in Net/3 */
if (setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (char *) &optval, (socklen_t) sizeof(optval)) < 0) {
write_log(WRITE_LOG_SYS, "SO_REUSEADDR on failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
return;
}
if (bind(ls, (struct sockaddr *) &sockin, (socklen_t) sizeof(sockin)) < 0) {
write_log(WRITE_LOG_SYS, "bind() failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
return;
}
/*
* start smail as a background daemon process.
*/
if (start_daemon() != 0) {
return; /* the parent process returns... */
}
/* now running in the new background daemon process */
/* assure that max connects is a sane value */
if (smtp_accept_max < 0) {
smtp_accept_max = 0;
}
/* assure that max connects until only queueing is a sane value */
if (smtp_accept_queue < 0) {
smtp_accept_queue = 0;
}
if (smtp_accept_queue > smtp_accept_max) {
static int have_warned_accept_queue = FALSE;
if (! have_warned_accept_queue) {
write_log(WRITE_LOG_PANIC, "smtp_accept_queue is greater than smtp_accept_max");
have_warned_accept_queue = TRUE;
}
smtp_accept_queue = smtp_accept_max;
}
/* assure that max runqs is a sane value */
if (runq_max < 0) {
runq_max = 0;
}
/* assure that smtp_greeting_delay is a sane value */
if ((long int) smtp_greeting_delay > (smtp_receive_command_timeout / 2)) {
static int have_warned_greeting_delay = FALSE;
if (! have_warned_greeting_delay) {
write_log(WRITE_LOG_PANIC, "smtp_greeting_delay is greater than one-half of smtp_receive_command_timeout");
have_warned_greeting_delay = TRUE;
}
smtp_greeting_delay = (smtp_receive_command_timeout / 2);
}
/* assure that smtp_receive_command_timeout is a sane value */
if (smtp_receive_command_timeout < (5 * 60)) { /* 5 min. minimum required by RFC [2]821 */
static int have_warned_receive_command_timeout = FALSE;
if (! have_warned_receive_command_timeout) {
write_log(WRITE_LOG_PANIC, "smtp_receive_command_timeout must be >= 5 minutes");
have_warned_receive_command_timeout = TRUE;
}
smtp_receive_command_timeout = 5 * 60;
}
/* assure that smtp_receive_message_timeout is a sane value */
if (smtp_receive_message_timeout < (2 * 60 * 60)) { /* 2 hour minimum required by RFC [2]821 */
static int have_warned_receive_message_timeout = FALSE;
if (! have_warned_receive_message_timeout) {
write_log(WRITE_LOG_PANIC, "smtp_receive_message_timeout must be >= 2 hours");
have_warned_receive_message_timeout = TRUE;
}
smtp_receive_message_timeout = 2 * 60 * 60;
}
/* if we are doing queue runs, do one queue run first */
if (process_queue) {
bg_run_queue(ls); /* close listen socket in child */
if (queue_interval == 0) {
/* Already done this, so can switch it out */
process_queue = FALSE;
}
}
/* Even if we are not doing regular queue runs, we need to
* wake up periodically to make sure that the config files
* are still valid.
* Hence my nasty hard wiring of queue_interval
*/
if (queue_interval == 0) {
/* Set to 10 minutes */
queue_interval = 600;
}
# if defined(UNIX_BSD4_3) || defined(USE_SIGINTERRUPT)
/*
* We need to interrupt the select(), so ask for interrupted
* system calls from SIGALRMs, SIGHUPs & SIGTERMs.
*/
siginterrupt(SIGALRM, 1);
siginterrupt(SIGHUP, 1);
siginterrupt(SIGTERM, 1);
# endif
/* SIGCHLD/SIGCLD means an smtpd or runq child has exited */
# ifndef UNIX_SYS5_2
got_sigchld = FALSE;
# endif
if (set_sigchld_handler(daemon_sigchld) < 0) {
panic(EX_OSERR, "daemon_mode(): set_sigchld_handler() failed: %s.", strerror(errno));
/* NOTREACHED */
}
/* SIGHUP means re-exec */
got_sighup = FALSE;
if (signal(SIGHUP, daemon_sighup) == SIG_ERR) {
panic(EX_OSERR, "daemon_mode(): signal(SIGHUP) failed: %s.", strerror(errno));
/* NOTREACHED */
}
/* daemon_sigterm() never returns.... */
if (signal(SIGTERM, daemon_sigterm) == SIG_ERR) {
panic(EX_OSERR, "daemon_mode(): signal(SIGTERM) failed: %s.", strerror(errno));
/* NOTREACHED */
}
/* set the alarm for wakeup time */
got_sigalrm = FALSE;
if (signal(SIGALRM, daemon_sigalrm) == SIG_ERR) {
panic(EX_OSERR, "daemon_mode(): signal(SIGALRM) failed: %s.", strerror(errno));
/* NOTREACHED */
}
(void) alarm(queue_interval);
/* fire up the smtp socket */
if (listen(ls, SOMAXCONN) == -1) {
panic(EX_OSERR, "listen(): %s", strerror(errno));
/* NOTREACHED */
}
/* loop processing connect requests or alarm signals */
for (;;) {
size_t len;
DEBUG1(DBG_MAIN_MID, "listening for a new connection on port %u...\n", (unsigned int) ntohs((in_port_t) port));
if (ls >= ((int) sizeof(fds_used) * 8)) {
panic(EX_OSERR, "the listen file descriptor [%d] is too big for fd_set [%d bits]",
ls, sizeof(fds_used) * 8);
}
FD_ZERO(&fds_used);
FD_SET(ls, &fds_used);
memcpy(&fds_read, &fds_used, sizeof(&fds_read));
nsel = select(ls + 1, &fds_read, (fd_set *) NULL, (fd_set *) NULL, (struct timeval *) NULL);
if (nsel < 0) {
if (errno != EINTR) {
write_log(WRITE_LOG_PANIC, "select failed: %s", strerror(errno));
check_daemon_signals(ls);
continue;
}
} else {
if (nsel != 1) {
panic(EX_OSERR, "select(): returned impossible number of descriptors ready: %d", nsel);
/* NOTREACHED */
}
if (!FD_ISSET(ls, &fds_read)) {
panic(EX_OSERR, "select(): returned without the listen socket[%d] marked ready!", ls);
/* NOTREACHED */
}
/*
* clear these again -- the child process started by
* do_daemon_accept() now has its own copy....
*/
len = sizeof(from);
(void) memset((char *) &from, '\0', len);
DEBUG(DBG_MAIN_MID, "select returned with our listen socket descriptor ready...\n");
/* get the first waiting connection... */
as = accept(ls, (struct sockaddr *) &from, (socklen_t *) &len);
/* XXX should we verify as != ls? */
/*
* WARNING: On some systems len might end up as zero!
*/
/*
* ECONNABORTED is specified by POSIX-1g but according to Stevens'
* UNPv1(2ndEd) and TCPv2, it should be impossible from *BSD
* implementations because the socket will have been removed from
* the listen queue and freed before accept() is called. On the
* other hand on TCPv2 p. 458 it seems accept() could return
* ECONNABORTED if the socket gets into the state where the
* SS_CANTRCVMORE bit is set and there are no more connections in
* the queue. I don't know if that could ever happen though. In
* any case ECONNABORTED, along with EINTR of course, is non-fatal,
* so we'll just loop back up and call accept() again (after first
* clearing accept_err_cnt and handling any signal flags).
*/
if (as < 0 && errno != ECONNABORTED && errno != EWOULDBLOCK && errno != EINTR
# if defined(EPROTO)
&& errno != EPROTO /* STREAMS-based sockets */
# endif
# if (defined(LINUX) || defined(HAVE_LINUX_ACCEPT_BUG)) && !defined(NO_HAVE_LINUX_ACCEPT_BUG)
/* According to Alan Cox the accept() interface will revert to
* offering only POSIX/BSD compatible behaviour in Linux 2.1.x
*/
/* XXX stupid incompatability -- Unless compiled with
* _BSD_SOURCE the Linux 2.0.x accept() will return before the
* three-way handshake is complete. As such it may return any
* error code that might be returned by recvmsg(2) as well as
* the normal errors. Unfortunately this means any remote
* network user can perform a form of DoS on any traditional
* BSD application that handles multiple clients since the
* traditional failures of accept() are due to local resource
* starvation problems. On Linux systems we must therefore
* ignore some of the possible connection setup errors to
* prevent such attacks from totally disabling SMTP services.
* An exploit has been posted to BUGTRAQ (search for
* "Sendmail/Qmail DoS"). Whether or not this "fix" will
* result in pushing the SYN attack into the realm of the
* application, or not, is yet to be determined.
*/
&& errno != ECONNRESET && errno != ENETUNREACH && errno != EHOSTUNREACH && errno != ENOTCONN && errno != ENETDOWN && errno != EHOSTDOWN && errno != errno != ECONNREFUSED
# endif
) {
unsigned int oldalarm;
write_log(WRITE_LOG_PANIC, "accept(): failed: %s", strerror(errno));
/*
* for some reason, accept() fails badly (and repeatedly) on
* some systems. To prevent the paniclog from filling up, exit
* if this happens too many times in a row.
*
* For example POSIX-1g fixed a botch in SysVr4 which could
* return EPROTO for connections which received a RST before
* the server calls accept(). However EPROTO could also
* represent a fatal error in the STREAMS subsystem. Since we
* won't allow accept() to return too many errors in a row
* we'll just have to treat ignore all causes of EPROTO as
* potentially fatal and hope we don't get more than
* MAX_ACCEPT_ERR_COUNT non-fatal ones in a row because if we
* do then this could lead to what effectively might amount to
* a denial of service attack. Don't run Smail on broken old
* SysVr4's if you want to avoid this. Stevens says SunOS-5.6
* fixed this bug and implemented the POSIX-1g change to
* ECONNABORTED.
*/
oldalarm = alarm(0); /* pause alarm during this sleep() */
sleep(5);
accept_err_cnt++;
if (accept_err_cnt == MAX_ACCEPT_ERR_COUNT) {
panic(EX_OSERR, "too many accept errors, quitting");
/* NOTREACHED */
}
alarm(oldalarm); /* reset alarm */
check_daemon_signals(ls);
continue;
}
accept_err_cnt = 0; /* one non-fatal return resets the counter */
if (as >= 0) {
# ifndef NDEBUG
if (from.sin_family != AF_INET
# if 0 /* */
&& from.sin_family != AF_INET6
# endif
) {
write_log(WRITE_LOG_PANIC, "accept() returned impossible address family: %d", from.sin_family);
check_daemon_signals(ls);
continue;
}
/* XXX should we also check from.sin_len, if it is implemented? */
if (len < (size_t) ((from.sin_family == AF_INET) ? 4 : 20) || len > sizeof(from)) {
write_log(WRITE_LOG_PANIC, "accept() returned impossible address length: %d", len);
check_daemon_signals(ls);
continue;
}
# endif /* NDEBUG */
if (smtp_accept_max > 0 && smtpd_accept_count >= smtp_accept_max) {
static char *reject[2] = {
"421-",
/* primary_name */
" Too many connections; try again later.\r\n"
"421 Try again later.\r\n",
};
DEBUG1(DBG_MAIN_MID, "rejecting SMTP connection #%d...\n",
smtpd_accept_count + 1);
/* use write(2) because if fdopen() failed we'd have to anyway! */
(void) write(as, reject[0], strlen(reject[0]));
(void) write(as, primary_name, strlen(primary_name));
(void) write(as, reject[1], strlen(reject[1]));
sleep(1);
(void) write(as, reject[2], strlen(reject[2]));
(void) close(as);
/* XXX hope this doesn't get too noisy! */
write_log(WRITE_LOG_SYS,
"connection %d (from [%s]) deferred, too many connections!",
smtpd_accept_count + 1,
inet_ntoa(from.sin_addr));
check_daemon_signals(ls);
continue;
}
# ifndef NO_LOG_ACCEPT
else {
/*
* this log entry should show the time the connection was
* accepted, vs. the "remote connection" log entry which
* may be delayed by DNS lookups for the peer address PTR
*/
write_log(WRITE_LOG_SYS,
"accepted connection %d from [%s]",
smtpd_accept_count + 1,
inet_ntoa(from.sin_addr));
}
# endif
do_daemon_accept(ls, as, &from);
} else {
/* we may see EINTR, EAGAIN, or ECONNABORTED here */
DEBUG1(DBG_MAIN_MID, "accept(): returned 'normal' errno = %s\n", strerror(errno));
}
}
check_daemon_signals(ls);
}
/* NOTREACHED */
}
static void
check_daemon_signals(ls)
int ls;
{
# ifndef UNIX_SYS5_2
if (got_sigchld) {
got_sigchld = FALSE; /* this first... */
get_dead_children(); /* ... then this */
}
# endif
if (got_sighup) {
write_log(WRITE_LOG_SYS, "SIGHUP received, restarting, execv(%s, '%s')", smail, quote_args(save_argv));
kill_runq_children(SIGHUP); /* clean, quick, exit */
kill_smtpd_children(SIGHUP); /* sets queue_only */
execv(smail, save_argv);
panic(EX_UNAVAILABLE, "execv() of %s failed: %s: %s", smail, strerror(errno));
/* NOTREACHED */
}
if (got_sigalrm) {
/* if config file have changed, recycle */
DEBUG(DBG_MAIN_LO, "SIGALRM received, check for new config, then check input queue\n");
if (is_newconf()) {
/* re-exec smail, just as if SIGHUP received */
write_log(WRITE_LOG_SYS, "new config files, execv(%s, '%s')", smail, quote_args(save_argv));
kill_runq_children(SIGHUP); /* clean, quick, exit */
kill_smtpd_children(SIGHUP); /* sets queue_only */
execv(smail, save_argv);
panic(EX_UNAVAILABLE, "execv() of %s failed: %s", smail, strerror(errno));
/* NOTREACHED */
}
/* reopen the log files so that they can be moved and removed */
close_system_logs();
open_system_logs();
/*
* re-cache any of the cached internal driver info, to get any changes
* in files not monitored by is_newconf()
*/
cache_directors();
cache_routers();
cache_transports();
if (process_queue) {
bg_run_queue(ls); /* do a queue run in a child process */
}
got_sigalrm = FALSE; /* first this... */
(void) alarm(queue_interval); /* ... then this */
}
return;
}
/*
* do_daemon_accept - perform processing for an accepted SMTP connection
* (i.e. read, parse, & execute SMTP commands) in a separate process.
*/
static void
do_daemon_accept(ls, fd, from)
int ls; /* listen socket, must be closed in child */
int fd; /* connected channel */
struct sockaddr_in *from; /* address of peer */
{
int fd2; /* dup of connected channel */
pid_t pid;
#if defined(O_NONBLOCK)
int flags;
#endif
/*
* Don't do enhanced status codes on SMTP replies here since we're
* too early in the game for that.
*/
DEBUG1(DBG_MAIN_LO, "connection request from [%s]\n",
inet_ntoa(from->sin_addr));
#if defined(O_NONBLOCK)
/*
* on SunOS-5.9 at least the accept() socket seems to inherit non-blocking
* mode from the listen socket
*/
if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
panic(EX_OSERR, "fcntl(ls, F_GETFL, 0): failed: %s", strerror(errno));
/* NOTREACHED */
}
if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1) {
panic(EX_OSERR, "fcntl(ls, F_SETFL, flags & ~O_NONBLOCK): failed: %s", strerror(errno));
/* NOTREACHED */
}
#endif
fd2 = dup(fd);
if (fd2 < 0) {
int oerrno = errno;
static char *reject[] = {
"421-",
/* primary_name */
" Connection temporarily refused -- system resources exausted [dup]\r\n"
"421 Try again later.\r\n",
};
DEBUG4(DBG_MAIN_MID, "dup() failed for PID# %ld: defering SMTP connection #%d from [%s]: %s\n",
(long int) getpid(), smtpd_accept_count + 1, inet_ntoa(from->sin_addr), strerror(oerrno));
/* use write(2) because if fdopen() failed we'd have to anyway! */
(void) write(fd, reject[0], strlen(reject[0]));
(void) write(fd, primary_name, strlen(primary_name));
(void) write(fd, reject[1], strlen(reject[1]));
sleep(1);
(void) write(fd, reject[2], strlen(reject[2]));
(void) close(fd);
fd = -1;
/* XXX hope this doesn't get too noisy, and hope it works! */
write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
"connection %d (from [%s]) deferred, dup() failed: %s",
smtpd_accept_count + 1,
inet_ntoa(from->sin_addr),
strerror(oerrno));
exitvalue = EX_OSERR; /* XXX could be confusing? */
return;
}
/* don't worry about retries here -- the remote should retry for us... */
switch (pid = fork()) {
case -1: { /* fork() failed, in the parent process */
int oerrno = errno;
static char *reject[] = {
"421-",
/* primary_name */
" Connection temporarily refused -- system resources exausted [fork]\r\n"
"421 Try again later.\r\n",
};
DEBUG4(DBG_MAIN_MID, "fork() failed for PID# %ld: defering SMTP connection #%d from [%s]: %s\n",
(long int) getpid(), smtpd_accept_count + 1, inet_ntoa(from->sin_addr), strerror(oerrno));
/* use write(2) because if fdopen() failed we'd have to anyway! */
(void) write(fd, reject[0], strlen(reject[0]));
(void) write(fd, primary_name, strlen(primary_name));
(void) write(fd, reject[1], strlen(reject[1]));
sleep(1);
(void) write(fd, reject[2], strlen(reject[2]));
(void) close(fd);
fd = -1;
/* XXX hope this doesn't get too noisy, and hope it works! */
write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
"connection %d (from [%s]) deferred, fork() failed: %s",
smtpd_accept_count + 1,
inet_ntoa(from->sin_addr),
strerror(oerrno));
exitvalue = EX_OSERR; /* XXX could be confusing? */
break;
} /* end of 'case -1:' */
case 0: { /* in the child process */
FILE *in; /* input channel */
FILE *out; /* output channel */
DEBUG2(DBG_MAIN_LO, "running in PID# %ld to handle connection from [%s]\n",
(long int) getpid(), inet_ntoa(from->sin_addr));
if (smtp_debug_pause) {
/* NOTE: the pid is included in the text so it can be seen when using -v999 too! */
write_log(WRITE_LOG_SYS | WRITE_LOG_TTY,
"remote: pausing %d seconds for debugger startup on PID# [%ld] ....",
smtp_debug_pause, (long int) getpid());
sleep(smtp_debug_pause); /* XXX what about interactions with
* SIGALRM on systems without a kernel
* [nano]sleep(2)? */
}
if (smtpd_children) {
free_intlist(smtpd_children);
smtpd_children = NULL;
}
if (runq_children) {
free_intlist(runq_children);
runq_children = NULL;
}
/* close the listen socket, as we must */
(void) close(ls); /* XXX error check this! */
/* setup the channels */
in = fdopen(fd, "r"); /* XXX error check this! */
out = fdopen(fd2, "w"); /* XXX error check this! */
/*
* the child process may have to fork() again....
*/
if (set_sigchld_handler(SIG_DFL) < 0) {
write_log(WRITE_LOG_SYS, "do_daemon_accept(): set_sigchld_handler(SIGCHLD, SIG_DFL) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
/* XXX not fatal? (would be rude to drop conn without any I/O, and so long as old handler not set...) */
}
/*
* if the number of outstanding child processes exceeds
* smtp_accept_queue, then turn on queue_only in the child, so that
* mail will not be delivered immediately, hopefully reducing the load
* somewhat (esp. on systems which need to fork delivery processes, but
* even for direct delivery the immediate copying of message data is
* avoided).
*/
if (smtp_accept_queue > 0 && smtpd_accept_count >= smtp_accept_queue) {
/*
* XXX hope this doesn't get too noisy, but we'll keep it for now
* for the sake of tracking how many messages were queued because
* of the incomming connection overload and thus may suffer delayed
* delivery as a result....
*/
write_log(WRITE_LOG_SYS, "using queue_only in child process, many connections now (#%d)",
smtpd_accept_count + 1);
queue_only = TRUE;
}
/* do the actual work */
do_smtp(in, out, (void *) from);
/* done with that transaction */
exit(exitvalue);
/* NOTREACHED */
} /* end of 'case 0:' */
default: /* in the parent process */
add_smtpd_child(pid);
break;
} /* end of switch() */
/* parent process closes the input channels */
(void) close(fd);
(void) close(fd2);
return;
}
#else /* not defined(HAVE_BSD_NETWORKING) */
/*
* For systems that don't have sockets, turn daemon mode into
* a call to noop_mode(). This will have the desired affect if a
* queue run was also requested. Otherwise, it will simply return.
*/
void
daemon_mode()
{
if (errfile) {
(void) fprintf(errfile, "%s: daemon mode not supported\n", program);
exitvalue = EX_UNAVAILABLE;
}
noop_mode();
}
#endif /* not defined(HAVE_BSD_NETWORKING) */
/*
* daemon_sighup - note that we received a SIGHUP signal
*/
/*ARGSUSED*/
static void
daemon_sighup(sig)
int sig __attribute__((unused));
{
#if 0
assert(sig == SIGHUP);
#endif
write_log(WRITE_LOG_SYS, "SIGHUP received...");
#if !(defined(UNIX_BSD) || defined(POSIX_OS)) || (defined(sun) && defined(UNIX_SYS5))
/* reset the handler if we're using unreliable signals */
if (signal(SIGHUP, daemon_sighup) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "daemon_sighup(): signal(SIGHUP, daemon_sighup) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
#endif
got_sighup = TRUE;
return;
}
/*
* daemon_sigterm - note that we received a SIGTERM signal and then commit
* infanticide and suicide
*/
static void
daemon_sigterm(sig)
int sig __attribute__((unused));
{
#if 0
assert(sig == SIGTERM);
#endif
write_log(WRITE_LOG_SYS, "SIGTERM received, shutting down daemon & children.");
/* tell any runq's to stop now */
kill_runq_children(SIGTERM);
#ifdef HAVE_BSD_NETWORKING
/* tell any smptd's to shut down too */
kill_smtpd_children(SIGTERM);
#endif
if (daemon_pid == getpid()) {
(void) unlink(daemon_pidfile);
}
exit(EX_OK);
/* NOTREACHED */
}
/*
* daemon_sigalrm - note that we received a SIGALRM signal
*/
static void
daemon_sigalrm(sig)
int sig __attribute__((unused));
{
#if 0
assert(sig == SIGALRM);
#endif
#ifndef NDEBUG
if (debug) {
write_log(WRITE_LOG_SYS, "SIGALRM received.");
}
#endif
got_sigalrm = TRUE;
#if !(defined(UNIX_BSD) || defined(POSIX_OS)) || (defined(sun) && defined(UNIX_SYS5))
/* reset the handler if we're using unreliable signals */
if (signal(SIGALRM, daemon_sigalrm) == SIG_ERR) {
write_log(WRITE_LOG_SYS, "daemon_sigalrm(): signal(SIGALRM, daemon_sigalrm) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
#endif
return;
}
/*
* daemon_sigchld - note that we received a SIGCHLD signal
*/
static void
daemon_sigchld(sig)
int sig;
{
#ifndef NDEBUG
if (debug) {
write_log(WRITE_LOG_SYS, "SIGCHLD received.");
}
#endif
#ifdef UNIX_SYS5_2
/*
* Pending SIGCLD's arriving while the handler is executing are queued and
* the handler is re-entered automatically until all are handled.
*/
get_dead_children(); /* first this... */
(void) signal(SIGCLD, daemon_sigchld); /* ... then this */
#else
got_sigchld = sig;
#endif
return;
}
/*
* noop_mode - perform queue runs once or at intervals
*
* When the -q flag is specified, or smail is invoked as runq, but -bd
* is not specified, then noop_mode() is invoked, which does nothing but
* execute run_queue() in background at intervals. If no sleep interval
* is specified, run_queue() is called only once.
*/
void
noop_mode()
{
X_PANIC_OKAY();
if (! process_queue) {
/* queue procesing not requested, nothing to do */
return;
}
/*
* Turn smail process into a daemon (if not in debug)...
*/
if (start_daemon() != 0) {
return; /* the parent process returns... */
}
/* arrange signals */
got_sighup = FALSE;
got_sigalrm = FALSE;
if (debug && queue_interval == 0) {
/*
* a hack to simulate same in daemon_mode(), but this time it is to
* ensure the default retry interval is not just 1 second unless one or
* more message-IDs were explicitly specified in which case we assume
* the manual run is explicitly intended to happen now despite when the
* last attempt was made. This allows ordinary users to bypass the
* retry file, but that's probably OK...
*/
queue_interval = num_recipients ? 0 : 600;
do_run_queue(); /* a single run in the foreground */
return;
}
#if defined(UNIX_BSD4_3) || defined(USE_SIGINTERRUPT)
/*
* We need to interrupt immediately on SIGHUP and SIGTERM, so ask for
* interrupted system calls from SIGHUPs and SIGTERMs.
*/
siginterrupt(SIGALRM, 1);
siginterrupt(SIGHUP, 1);
siginterrupt(SIGTERM, 1); /* SIGTERM will be SIG_DFL */
#endif
if (set_sigchld_handler(daemon_sigchld) < 0) {
panic(EX_OSERR, "noop_mode(): set_sigchld_handler(SIGCHLD) failed: %s.", strerror(errno));
/* NOTREACHED */
}
/* daemon_sigterm() never returns.... */
if (signal(SIGTERM, daemon_sigterm) == SIG_ERR) {
panic(EX_OSERR, "noop_mode(): signal(SIGTERM) failed: %s.", strerror(errno));
/* NOTREACHED */
}
if (signal(SIGHUP, daemon_sighup) == SIG_ERR) {
panic(EX_OSERR, "noop_mode(): signal(SIGHUP) failed: %s.", strerror(errno));
/* NOTREACHED */
}
if (signal(SIGALRM, daemon_sigalrm) == SIG_ERR) {
panic(EX_OSERR, "noop_mode(): signal(SIGALRM) failed: %s.", strerror(errno));
/* NOTREACHED */
}
/* always run the queue at least once.... */
bg_run_queue(-1);
if (queue_interval > 0) {
/* get an alarm at intervals */
(void) alarm(queue_interval);
for (;;) {
pause();
#if !defined(UNIX_SYS5_2) /* SysVr2.x does the wait() in the handler... */
if (got_sigchld) {
got_sigchld = FALSE; /* this first... */
get_dead_children(); /* ... then this */
}
#endif
/* watch for SIGHUP to indicate a recycle */
if (got_sighup) {
write_log(WRITE_LOG_SYS, "noop_mode(): SIGHUP received, execv(%s, '%s')", smail, quote_args(save_argv));
kill_runq_children(SIGHUP); /* clean, quick, exit */
execv(smail, save_argv);
panic(EX_UNAVAILABLE, "noop_mode(): SIGHUP execv() of %s failed: %s", smail, strerror(errno));
/* NOTREACHED */
}
if (! got_sigalrm) {
continue;
}
/* reset the alarm condition */
got_sigalrm = FALSE;
/* if config file have changed, recycle */
if (is_newconf()) {
write_log(WRITE_LOG_SYS, "noop_mode(): new config files, execv(%s, '%s')", smail, quote_args(save_argv));
execv(smail, save_argv);
panic(EX_UNAVAILABLE, "noop_mode(): newconf execv() of %s failed: %s", smail, strerror(errno));
/* NOTREACHED */
}
/* reopen the log files so that they can be moved and removed */
close_system_logs();
open_system_logs();
/* re-cache all of the driver info, to get any changes */
cache_directors();
cache_routers();
cache_transports();
/* do another queue run */
bg_run_queue(-1);
(void) alarm(queue_interval);
}
}
}
/*
* start_daemon - start a daemon smail process for noop_mode() or
* daemon_mode()
*
* open system logs, get some system information we can use for
* processing each message, and put ourselves in background.
*
* Return the pid of the child process in the parent, and 0 in the
* child.
*
* Always returns 0 if debug is set.
*/
static pid_t
start_daemon()
{
pid_t pid;
#if defined(UNIX_BSD) && !defined(POSIX_OS)
int fd;
#endif
/* cache some interesting things */
open_system_logs();
/* disconnect from the controlling terminal, if we are not debugging */
if (debug == 0) {
/*
* XXX we should probably try to use daemon(3) if available....
*/
pid = fork();
if (pid < 0) {
write_log(WRITE_LOG_SYS|WRITE_LOG_TTY, "fork() failed: %s", strerror(errno));
exitvalue = EX_OSERR;
return pid;
}
if (pid > 0) {
/* just return child PID in the parent process */
return pid;
}
#ifdef HAVE_BSD_NETWORKING
assert(smtpd_children == NULL);
#endif
assert(runq_children == NULL);
#ifdef POSIX_OS
(void) setsid();
#else
# ifdef UNIX_BSD /* only pre-4.4BSD... */
(void) setpgrp(0, getpid());
fd = open("/dev/tty", O_RDWR);
if (fd >= 0) {
ioctl(fd, TIOCNOTTY, 0);
close(fd);
}
# else
(void) setpgrp();
# endif /* UNIX_BSD */
#endif /* POSIX_OS */
}
/* go to the closest place we can call home (for core dumps, etc.) */
(void) chdir("/");
/* record the daemon PID for logging purposes */
daemon_pid = getpid();
if (queue_interval > 0) {
FILE *f;
if (daemon_pidfile && *daemon_pidfile == '/') {
unsigned int oumask = umask(0022); /* S_IWGRP | S_IWOTH */
(void) unlink(daemon_pidfile);
if ((f = fopen(daemon_pidfile, "w")) == NULL) {
panic(EX_OSFILE, "fopen(%v): %s", daemon_pidfile, strerror(errno));
/* NOTREACHED */
}
if (fprintf(f, "%d\n", (int) daemon_pid) < 1) {
panic(EX_OSFILE, "error writing my daemon PID to %v: %s", daemon_pidfile, strerror(errno));
/* NOTREACHED */
}
if (fclose(f) < 0) {
panic(EX_OSFILE, "fclose(%v): %s", daemon_pidfile, strerror(errno));
/* NOTREACHED */
}
(void) umask(oumask);
} else {
panic(EX_CONFIG, "invalid daemon_pidfile setting: %v", daemon_pidfile);
/* NOTREACHED */
}
write_log(WRITE_LOG_SYS, "%s daemon started", smail);
} else {
write_log(WRITE_LOG_SYS, "independent %sground queue run initiated", debug ? "fore" : "back");
}
/* grab the real uid under which smail was executed */
real_uid = getuid();
/* now that we're past the point of no return we can get rid of any tty
* attachment that might remain if we've been started by hand...
*/
if (debug == 0) {
if (errfile) {
(void) fclose(errfile); /* XXX why do we do this? -- should only close it if it is stderr!!! */
errfile = NULL;
}
if (isatty(fileno(stdout))) {
(void) fclose(stdout);
}
if (isatty(fileno(stdin))) {
(void) fclose(stdin);
}
}
return 0;
}
/*
* bg_run_queue - perform a queue run in a child process
*/
static void
bg_run_queue(ls)
int ls; /* if >=0, close this descriptor in child */
{
pid_t pid;
if (runq_max > 0 && runq_child_count >= runq_max) {
write_log(WRITE_LOG_SYS, "bg_run_queue(): there are already %d runq daemons running!", runq_child_count);
return;
}
switch ((pid = fork())) {
case 0: /* child */
if (ls >= 0) {
close(ls);
}
(void) alarm((unsigned int) 0);
(void) signal(SIGALRM, SIG_DFL);
/*
* XXX these should really be set to a "pass-it-on before doing it to
* myself" function that signals the child process started by
* process_spool_file() so that it terminates as soon as possible, if
* possible.
*/
(void) signal(SIGHUP, SIG_DFL);
(void) signal(SIGINT, SIG_DFL);
(void) signal(SIGTERM, SIG_DFL);
#ifdef HAVE_BSD_NETWORKING
if (smtpd_children) {
free_intlist(smtpd_children);
smtpd_children = NULL;
}
#endif
if (runq_children) {
free_intlist(runq_children);
runq_children = NULL;
}
#ifdef POSIX_OS
(void) setsid();
#else /* not POSIX_OS */
(void) setpgrp(0, getpid());
#endif /* POSIX_OS */
/*
* in the child process we want dying kids to be reaped right away
*/
#if defined(HAVE_BSD_NETWORKING) || defined(UNIX_SYS5_2)
if (set_sigchld_handler(SIG_DFL) < 0) {
write_log(WRITE_LOG_SYS, "bg_run_queue(): set_sigchld_handler(SIGCHLD, SIG_DFL) failed: %s.", strerror(errno));
exitvalue = EX_OSERR;
}
#endif
do_run_queue();
exit(0);
/* NOTREACHED */
case -1: /* error */
panic(EX_OSERR, "fork() failed when trying to start background queue run daemon.");
/* NOTREACHED */
default: /* parent */
add_runq_child(pid);
break;
}
}
/*
* verify_addresses - print resolved addresses
*
* Get a list of addresses and return the output of resolve_addr_list() on
* that list.
*/
void
verify_addresses()
{
char *error;
struct addr *cur; /* temp recipient addr list element */
struct addr *fail; /* list of failed addrs */
struct addr *defer; /* list of deferred addrs */
struct addr *deliver; /* addr structures ready to deliver */
struct addr **last_addr; /* pointer to current addr pointer */
struct addr *next;
DEBUG1(DBG_MAIN_MID, "verify_addresses() called%s...\n",
extract_addresses ? " (with extract_addresses set)" : "");
X_PANIC_OKAY();
if (extract_addresses) {
char **argv; /* args from read_message() */
/*
* read in the message from stdin, if the -t flag was set.
*/
input_signals(); /* prepare to remove message */
if (queue_message(stdin, dot_usage, recipients, &error) == FAIL) {
if (errfile) {
dprintf(errfile,
"%s: incoming message dropped: %v: %s\n",
program,
error,
strerror(errno));
}
exitvalue = EX_OSFILE;
return;
}
if (! (argv = read_message())) {
if (msg_size == 0) {
write_log(WRITE_LOG_SYS, "discarding empty spool file!");
unlink_spool();
exitvalue = EX_OK;
} else {
write_log(WRITE_LOG_SYS, "failed to read queued message!");
exitvalue = EX_OSFILE;
}
return;
}
/* process arguments from the spool file */
process_args(argv, FALSE);
if (sender_is_trusted) {
check_sender(TRUE);
}
process_recipients(TRUE);
/* XXX free_argv(argv); */
/* don't actually need the message anymore */
unlink_spool();
}
/*
* preparse all of the recipient addresses given as command-line
* parameters.
*
* If we are extracting addresses from the header(s) (-t was given on the
* command-line), then these addresses are NOT to receive the mail. To
* accomplish this, add them to the global hit_table hash table so they
* will be ignored later.
*/
last_addr = &recipients;
for (cur = recipients; cur; cur = next) {
char *errptr; /* error from preparse_address */
next = cur->succ;
if ((cur->work_addr = preparse_address(cur->in_addr, &errptr)) == NULL) {
if (errfile) {
dprintf(errfile,
"%v ... parse error in address: %s\n",
cur->in_addr, errptr);
}
/* unhook the failed address from the list of recipients */
*last_addr = next;
xfree((char *) cur);
continue;
}
if (extract_addresses) {
(void) add_to_hash(cur->work_addr, (char *) NULL, (size_t) 0, hit_table);
xfree(cur->work_addr); /* don't need it anymore */
xfree((char *) cur);
continue;
}
last_addr = &cur->succ;
}
if (extract_addresses) {
recipients = NULL; /* don't need them anymore XXX LEAKS! */
/*
* process_header will get the recipients from the header,
* among other things we aren't really interested in here.
*/
error = process_header(&recipients);
if (error && errfile) {
dprintf(errfile, "error in header: %s\n", error);
}
}
/*
* given the list of recipient addresses, turn those
* addresses into more specific destinations, including
* the transport that is to be used, in the case of
* addresses destined remote
*/
deliver = NULL;
defer = NULL;
fail = NULL;
resolve_addr_list(recipients, &deliver, &defer, &fail, TRUE);
if (deliver == NULL && defer == NULL && fail == NULL) {
dprintf(errfile, "no valid recipients were found%s, no addresses to verify\n",
extract_addresses ? " in message headers" : "");
}
print_addr_info(deliver, defer, fail);
close_system_logs();
return;
}
static void
print_addr_info(deliver, defer, fail)
struct addr *deliver;
struct addr *defer;
struct addr *fail;
{
struct addr *cur;
for (cur = deliver; cur; cur = cur->succ) {
char *in_addr = COPY_STRING(cur->in_addr);
register struct addr *top; /* the ultimate parent address to compare */
/* find the top parent to log the original in_addr */
for (top = cur; top->parent && top->parent->in_addr; top = top->parent) {
;
}
if (top == cur) {
top = NULL;
}
if ((strncmpic(in_addr, ":defer:", sizeof(":defer:")-1) == 0) ||
(strncmpic(in_addr, ":error:", sizeof(":error:")-1) == 0) ||
(strncmpic(in_addr, ":fail:", sizeof(":fail:")-1) == 0)) {
char *p = strchr(in_addr + 1, ':') + 1;
*p = '\0';
}
if (cur->next_host) {
dprintf(stdout,
"<%v>%s%v%s routes to <%s> at %s via %s ... deliverable\n",
in_addr,
top ? " (orig-to <" : "",
top ? top->in_addr : "",
top ? ">)" : "",
cur->next_addr,
cur->next_host ? cur->next_host : "localhost",
cur->transport->name);
} else {
dprintf(stdout,
"<%v>%s%v%s ... deliverable via %v\n",
in_addr,
top ? " (orig-to <" : "",
top ? top->in_addr : "",
top ? ">)" : "",
cur->transport->name);
}
xfree(in_addr);
#ifndef NDEBUG
if (debug >= DBG_MAIN_MID) {
fputs("full dump deliverable ", errfile);
dump_addr(cur, "");
}
#endif
}
for (cur = defer; cur; cur = cur->succ) {
char *in_addr = COPY_STRING(cur->in_addr);
register struct addr *top; /* the ultimate parent address to compare */
/* find the top parent to log the original in_addr */
for (top = cur; top->parent && top->parent->in_addr; top = top->parent) {
;
}
if (top == cur) {
top = NULL;
}
if ((strncmpic(in_addr, ":defer:", sizeof(":defer:")-1) == 0) ||
(strncmpic(in_addr, ":error:", sizeof(":error:")-1) == 0) ||
(strncmpic(in_addr, ":fail:", sizeof(":fail:")-1) == 0)) {
char *p = strchr(in_addr + 1, ':') + 1;
*p = '\0';
}
dprintf(stdout,
"<%v>%s%v%s ... temporary failure: %s\n",
in_addr,
top ? " (orig-to <" : "",
top ? top->in_addr : "",
top ? ">)" : "",
cur->error->message);
xfree(in_addr);
#ifndef NDEBUG
if (debug >= DBG_MAIN_MID) {
fputs("full dump deferral ", errfile);
dump_addr(cur, "");
}
#endif
}
for (cur = fail; cur; cur = cur->succ) {
char *in_addr = COPY_STRING(cur->in_addr);
register struct addr *top; /* the ultimate parent address to compare */
/* find the top parent to log the original in_addr */
for (top = cur; top->parent && top->parent->in_addr; top = top->parent) {
;
}
if (top == cur) {
top = NULL;
}
if ((strncmpic(in_addr, ":defer:", sizeof(":defer:")-1) == 0) ||
(strncmpic(in_addr, ":error:", sizeof(":error:")-1) == 0) ||
(strncmpic(in_addr, ":fail:", sizeof(":fail:")-1) == 0)) {
char *p = strchr(in_addr + 1, ':') + 1;
*p = '\0';
}
dprintf(stdout,
"<%v>%s%v%s ... not deliverable: %s\n",
in_addr,
top ? " (orig-to <" : "",
top ? top->in_addr : "",
top ? ">)" : "",
cur->error->message);
xfree(in_addr);
#ifndef NDEBUG
if (debug >= DBG_MAIN_MID) {
fputs("full dump failed ", errfile);
dump_addr(cur, "");
}
#endif
}
return;
}
/*
* do_run_queue - queue run assuming initial setup has been done
*/
static void
do_run_queue()
{
char **work; /* vector of jobs */
if (!num_recipients) { /* actually they're message-ids */
write_log(WRITE_LOG_SYS, "run_queue %s #%d started",
(getppid() == daemon_pid) ? "daemon" : "process",
runq_child_count + 1); /* count is always one less in child process */
} else {
DEBUG2(DBG_MAIN_HI, "run_queue started work on %d message%s\n", num_recipients, (num_recipients > 1) ? "s" : "");
}
/*
* This exceedingly silly hack of special-casing the handling of a single
* queue entry is here to make debugging easier (no forking to follow)....
*/
if (num_recipients == 1 && parameters) {
/*
* If it wasn't for the fact that there might be more than one spool
* directory we could avoid reading the whole queue just to see if this
* one spool file exists....
*/
work = scan_spool_dirs(FALSE);
if (! *work) {
if (errfile) {
dprintf(errfile,
"%s: scan_spool_dirs() could not find spool file for: '%v'\n",
program, parameters->in_addr);
(void) fflush(errfile);
}
} else {
process_one_spool_file(*work);
}
DEBUG(DBG_MAIN_HI, "do_run_queue: finished processing one queue entry\n");
return;
}
/*
* If we're either handling all the queue entries, or several queue
* entries, then build the work list, and do it with child processes....
*/
work = scan_spool_dirs(FALSE);
while (*work) {
if (errfile) {
(void) fflush(errfile);
}
if (process_spool_file(*work) == FAIL) {
/* fork failed, error logged in subroutine, don't continue */
return;
}
/* message processed, go on to the next message */
work++;
if (*work && debug && errfile) {
(void) putc('\n', errfile);
}
}
if (!num_recipients) { /* actually they're message-ids */
write_log(WRITE_LOG_SYS, "run_queue %s #%d finished",
(getppid() == daemon_pid) ? "daemon" : "process",
runq_child_count + 1); /* count is always one less in child process */
} else {
DEBUG2(DBG_MAIN_HI, "run_queue finished work on %d message%s\n", num_recipients, (num_recipients > 1) ? "s" : "");
}
return;
}
/*
* print_version - display the current version string on stdout
*/
void
print_version()
{
if (debug) {
if (debug > 2) {
puts(copyright);
}
/* XXX this call should not fail, and if it does, we *are* in debug mode */
puts(expand_string("release:\t$version_string\n\
compilation:\t#$compile_num on $compile_date",
(struct addr *) NULL, (char *) NULL, (char *) NULL));
printf("libpcre:\t%s\n", pcre_version());
if (debug > 1) {
int mi;
(void) pcre_config(PCRE_CONFIG_LINK_SIZE, (void *) &mi);
printf("PCRE LINK_SIZE =\t%d\n", mi);
(void) pcre_config(PCRE_CONFIG_MATCH_LIMIT, (void *) &mi);
printf("PCRE MATCH_LIMIT =\t%d\n", mi);
}
#ifdef HAVE_LIBEFENCE
printf("libefence:\t%s\n", "unknown version");
#endif
#ifdef HAVE_LIBWHOSON
printf("libwhoson:\t%s\n", wso_version());
#endif
#ifdef HAVE_RFC1413
printf("libident:\t%s\n", id_version);
#endif
} else {
puts(version());
}
}
/*
* print_copying_file - print the COPYING file, detailing distribution rights
*/
void
print_copying_file()
{
register FILE *f;
register int c;
if (copying_file == NULL || (f = fopen(copying_file, "r")) == NULL) {
dprintf(stderr, "The file `%v' does not exist.\n\
Consult the file COPYING in the smail source directory for information\n\
on copying restrictions and warranty information from the authors\n",
copying_file ? copying_file : "<copying_file unset>");
exitvalue = EX_UNAVAILABLE;
return;
}
while ((c = getc(f)) != EOF) {
putchar(c);
}
(void) fclose(f);
}
/*
* print_variables - write configuration variable values to stdout
*
* Names of variables are stored in the list of parameters.
*/
void
print_variables()
{
register struct addr *cur;
struct addr *new, *next;
/* first reverse the list */
new = NULL;
for (cur = parameters; cur; cur = next) {
next = cur->succ;
cur->succ = new;
new = cur;
}
for (cur = new; cur; cur = cur->succ) {
print_config_variable(cur->in_addr);
}
}
/*
* print_queue - list the current messages in the mail queue
*
* If debugging is enabled, print msglog associated with each message.
*/
void
print_queue()
{
char **work; /* vector of jobs to process */
int col = 0; /* current print column */
unsigned int numjobs = 0;
unsigned long jobsize = 0;
unsigned int altjobs = 0;
unsigned long altsize = 0;
struct addr *queue_files = parameters; /* save any IDs from argv */
unsigned int num_queue_files = num_recipients;
struct addr *cur;
X_PANIC_OKAY();
/*
* we don't need a big buffer -- we will just be reading the spool-file
* header and the messages headers....
*/
if (message_buf_size > 4096) {
message_buf_size = 4096;
}
work = scan_spool_dirs(scan_frozen);
while (*work) {
char **argv; /* arguments from spool file */
int first = TRUE;
/* open without locking */
if (open_spool(*work, FALSE, WRITE_LOG_TTY) == FAIL) {
/* just ignore the file if it's already gone missing... */
if (exitvalue != EX_NOINPUT && exitvalue != EX_SOFTWARE && exitvalue != EX_OSFILE && exitvalue != EX_NOPERM) {
write_log(WRITE_LOG_TTY|WRITE_LOG_SYS,
"print_queue: %s/%s: lock failed (this can't happen -- we didn't ask to lock!): [%d] %s",
spool_dir, input_spool_fn, exitvalue,
exitvalue == EX_TEMPFAIL ? "already locked" : "(unknown error)");
}
work++;
continue;
}
if (!(argv = read_message())) {
if (msg_size == 0 && debug) {
printf("%s/%s: empty spool file, ignored...\n", spool_dir, input_spool_fn);
} else if (msg_size != 0) {
printf("%s/%s: incomplete spool file, ignored...\n", spool_dir, input_spool_fn);
}
close_spool();
work++; /* next assignment */
continue;
}
numjobs++;
jobsize += msg_size;
if (mailq_summary_only) {
close_spool();
work++; /* next assignment */
continue;
}
(void) printf("%s\tLocal-Sender: %s (in %s/%s)\n",
message_id,
sender, /* yes, sender, as read from spool, not local_sender */
spool_dir,
scan_frozen ? "error" : "input");
(void) printf("\t\tInvoker-ID: %u:%u\n", (unsigned int) real_uid, (unsigned int) prog_euid);
(void) printf("\t\tDate: %s\n", get_arpa_date(message_date()));
(void) printf("\t\tQueueSize: %ld\n", (unsigned long int) msg_size);
(void) printf("\t\tBodySize: %ld\n", msg_body_size);
/*
* print the argument vectors several to a line, trying not to
* go past the 78'th column, but without wrapping a long address.
*/
process_args(argv, FALSE);
if (sender_is_trusted) {
check_sender(TRUE);
}
process_recipients(TRUE);
(void) printf("\t\tArgs: %s", *argv);
col = 8 + 8 + sizeof("Args: ") - 1 + strlen(*argv++);
while (*argv) {
size_t arglen = strlen(*argv);
if ((col + arglen) >= 78) {
col = 8 + 8 + sizeof("Args: ") - 1;
(void) fputs(" \\\n\t\t ", stdout);
} else {
(void) putchar(' ');
col++;
}
col += arglen;
/* quote empty operands and ones containing whitespace */
if (arglen == 0 || strcspn(*argv, " \t\n") != arglen) {
col += 2;
dprintf(stdout, "'%v'", *argv++);
} else {
dprintf(stdout, "%v", *argv++);
}
}
/* XXX free_argv(argv); */
(void) printf("\n\t\tEnvelope-Sender: %s", sender);
if (recipients) {
(void) printf("\n\t\tRcpts: ");
col = 8 + 8 + sizeof("Rcpts: ") - 1;
first = TRUE;
}
for (cur = recipients; cur; cur= cur->succ) {
size_t addrlen = strlen(cur->in_addr);
if ((col + addrlen) >= 78) {
col = 8 + 8 + sizeof("Rcpts: ") - 1;
(void) fputs(",\n\t\t ", stdout);
} else if (!first) {
(void) fputs(", ", stdout);
col++;
col++;
}
first = FALSE;
col += addrlen;
/* quote empty recipients and ones containing whitespace */
if (addrlen == 0 || strcspn(cur->in_addr, " \t\n") != addrlen) {
col += 2;
dprintf(stdout, "'%v'", cur->in_addr);
} else {
dprintf(stdout, "%v", cur->in_addr);
}
}
if (recipients) {
free_addr_list(recipients);
}
if (extract_addresses) { /* was -t in argv? */
char *error;
struct addr *msg_rcpts = NULL;
/*
* process_header will get the recipients from the headers, among
* other things we aren't really interested in here.
*/
error = process_header(&msg_rcpts);
if (error) {
dprintf(stdout, "\n\t\t(error in header: %s)", error);
}
if (msg_rcpts) {
(void) fputs("\n\t\tHdr-Rcpts: ", stdout);
col = 8 + 8 + sizeof("Hdr-Rcpts: ") - 1;
first = TRUE;
}
for (cur = msg_rcpts; cur; cur = cur->succ) {
size_t addrlen = strlen(cur->in_addr);
if ((col + addrlen) > 78) {
col = 8 + 8 + sizeof("Hdr-Rcpts: ") - 1;
(void) fputs(",\n\t\t ", stdout);
} else if (!first) {
(void) fputs(", ", stdout);
col++;
col++;
}
first = FALSE;
col += addrlen;
/* quote empty recipients and ones containing whitespace */
if (addrlen == 0 || strcspn(cur->in_addr, " \t\n") != addrlen) {
col += 2;
dprintf(stdout, "'%v'", cur->in_addr);
} else {
dprintf(stdout, "%v", cur->in_addr);
}
}
free_addr_list(msg_rcpts);
}
(void) putchar('\n');
if (debug > 0) {
send_log(stdout, "Log of transactions:\n");
}
close_spool();
work++; /* next assignment */
if (*work) {
putchar('\n');
}
}
if (numjobs) {
if (num_queue_files == 0 || num_queue_files > 1) {
printf("%sSmail %s queue %s: %u job%s, %lu byte%s.\n",
mailq_summary_only ? "" : "\n",
scan_frozen ? "frozen (error)" : "mail",
queue_files ? "matching entries" : "stats",
numjobs, numjobs > 1 ? "s" : "",
jobsize, jobsize > 1 ? "s" : "");
}
} else if (queue_files) { /* only if !*work */
printf("No matching entries in Smail %s queue.\n", scan_frozen ? "frozen (error)" : "mail");
} else {
printf("Smail %s queue is empty.\n", scan_frozen ? "frozen (error)" : "mail");
}
if (queue_files && numjobs == num_queue_files) {
/* we found everything we were looking for, so quit now */
return;
}
/*
* now we'll scan the other queue just to get the sub-totals for it
*/
parameters = queue_files; /* reset initial state */
num_recipients = num_queue_files;
scan_frozen = !scan_frozen; /* ARGH! I hate global variables! */
work = scan_spool_dirs(scan_frozen);
while (*work) {
/* open without locking */
if (open_spool(*work, FALSE, WRITE_LOG_TTY) == FAIL) {
/* just ignore the file if it's already gone missing... */
if (exitvalue != EX_NOINPUT && exitvalue != EX_SOFTWARE && exitvalue != EX_OSFILE && exitvalue != EX_NOPERM) {
write_log(WRITE_LOG_TTY|WRITE_LOG_SYS,
"print_queue: %s/%s: lock failed (this can't happen -- we didn't ask to lock!): [%d] %s",
spool_dir, input_spool_fn, exitvalue,
exitvalue == EX_TEMPFAIL ? "already locked" : "(unknown error)");
}
work++;
continue;
}
if (!read_message()) { /* just counting -- toss argv */
if (msg_size == 0 && debug) {
printf("%s/%s: empty spool file, ignored...\n", spool_dir, input_spool_fn);
} else if (msg_size != 0) {
printf("%s/%s: incomplete spool file, ignored...\n", spool_dir, input_spool_fn);
}
close_spool();
work++;
continue;
}
altjobs++;
altsize += msg_size;
/* we're just counting, not processing args, recipients, etc.... */
close_spool();
work++; /* next assignment */
}
if (altjobs) {
printf("Smail %s queue %s: %u job%s, %lu byte%s. (print with%s -E)\n",
scan_frozen ? "frozen (error)" : "mail",
queue_files ? "matching entries" : "stats",
altjobs, altjobs > 1 ? "s" : "",
altsize, altsize > 1 ? "s" : "",
scan_frozen ? "" : "out" );
} else if (queue_files) { /* were we looking in the wrong queue? */
printf("No matching entries in Smail %s queue.\n", scan_frozen ? "frozen (error)" : "mail");
} else {
printf("Smail %s queue is empty.\n", scan_frozen ? "frozen (error)" : "mail");
}
return;
}
/*
* smtp_mode - receive and processes smtp transpactions
*
* Call receive_smtp() to get incoming messages. Then, if queue_only mode
* is not set, deliver those messages.
*/
void
smtp_mode(in, out, peer)
FILE *in; /* stream of SMTP commands */
FILE *out; /* channel for responses */
void *peer;
{
assert(peer == NULL); /* current implementation guarantees this */
input_signals(); /* XXX for batch mode too? */
open_system_logs();
/* do the real work */
do_smtp(in, out, peer);
}
/*
* do_smtp - common routine used by smtp_mode() and daemon_mode() for SMTP
*
* NOTE: When receive_smtp is finished, in and out are closed.
*/
static void
do_smtp(in, out, peer)
FILE *in;
FILE *out;
void *peer;
{
char **files; /* files returned by receive_smtp() */
int cnt; /* count of files */
int entry_grade;
int i;
/* cache some interesting things */
/* send out to process the SMTP transactions */
if (out) {
X_PANIC_OKAY();
} else {
X_NO_PANIC();
}
/* receive the message */
files = receive_smtp(in, out, peer);
X_PANIC_OKAY();
processing_signals();
(void) fclose(in);
if (out) {
(void) fclose(out);
}
for (cnt = 0; files[cnt] != NULL; cnt++) {
; /* NO-OP: just counting */
}
if (!cnt && !exitvalue) {
exitvalue = EX_NOINPUT;
}
/* if we are just queuing input, close and be done with it */
if (queue_only || deliver_mode == QUEUE_MESSAGE) {
close_spool();
return;
}
/* if delivering more than one mail message, cache driver info */
if (cnt > 1) {
if (! cached_directors) {
cache_directors();
}
if (! cached_routers) {
cache_routers();
}
if (! cached_transports) {
cache_transports();
}
}
/*
* Note: receive_smtp() has already called log_incoming() and
* log_spool_errors for each message received.
*/
/*
* process the files last first (if the last file is still open) and
* then first to the second to last This ordering is used because the
* last one remains open and it requires less overhead if the last
* file does not have to be reopened.
*/
if (spool_fn) {
/* last file still open, finish processing it */
/* Check to see if this grade should be delivered immediately */
entry_grade = spool_fn[strlen(spool_fn) - 1];
if ((entry_grade >= min_delivery_grade)
&& (entry_grade <= max_delivery_grade)) {
pid_t pid; /* pid of child process */
/* unlock the message in the parent process (see lock_message()) */
unlock_message();
/* make a child process */
pid = fork_wait();
if (pid < 0) {
/* can't fork(), try again later for all messages */
DEBUG1(DBG_MAIN_LO, "do_smtp(): fork_wait() failed: %s.\n", strerror(errno));
if (errfile) {
(void) fprintf(errfile,
"%s: fork() failed: %s, try again later\n",
program, strerror(errno));
(void) fflush(errfile);
}
return;
}
if (pid == 0) { /* in child process */
/* the master daemon must clear these before calling do_smtp() */
#ifdef HAVE_BSD_NETWORKING
assert(smtpd_children == NULL);
#endif
assert(runq_children == NULL);
/* process the message */
if (lock_message() == FAIL) {
/* somebody else grabbed the lock, assume they will deliver */
exit(0);
}
/* do the job */
read_and_process_message();
/* close the system-wide log files */
close_system_logs();
/* all done with the message */
exit(exitvalue);
/* NOTREACHED */
}
}
/*
* in the parent after child is done - or just continuing on if the
* message was queued without immediate delivery as per the
* entry_grade...
*
* XXX - we need to close the open spool file, but going through
* routines in spool.c would duplicate efforts to be
* done in the child process, so just close it ourselves.
*/
(void) close(spoolfile);
/*
* decrement the count to avoid seeing this filename again
*/
--cnt;
}
/*
* process any remaining files, respecting {min,max}_delivery_grade
*/
for (i = 0; i < cnt; i++) {
/* Check to see if this grade should be delivered immediately */
entry_grade = (files[i])[strlen(files[i]) - 1];
if ((entry_grade >= min_delivery_grade)
&& (entry_grade <= max_delivery_grade)) {
/* process_spool_file only returns FAIL on fork() failures */
if (process_spool_file(files[i]) == FAIL) {
return ;
}
}
}
}
/*
* process_spool_file - open read and process a spool file in a child process
*
* fork a child to open read and process an input spool file. Wait for
* the child and return when the child has completed processing.
*
* Return FAIL if the fork() failed, otherwise return SUCCEED.
*/
int
process_spool_file(spfn)
char *spfn; /* spool file name */
{
pid_t pid;
struct stat statbuf;
DEBUG1(DBG_MAIN_MID, "process_spool_file(%s): called\n", spfn);
/*
* Verify the spool file exists before we try to process it
*/
if (stat(spfn, &statbuf) < 0) {
DEBUG2(DBG_MAIN_MID, "process_spool_file: %s: File possibly already processed: %s\n",
spfn, strerror(errno));
return SUCCEED; /* we only fail if fork_wait() fails! */
}
/*
* Fork to spawn a child process
*/
if ((pid = fork_wait()) < 0) {
/* can't fork(), try again later */
DEBUG1(DBG_MAIN_LO, "process_spool_file: fork_wait() failed: %s.\n", strerror(errno));
if (errfile) {
(void)fprintf(errfile,
"%s: fork() failed: %s, try again later\n",
program, strerror(errno));
(void)fflush(errfile);
}
return FAIL;
}
if (pid == 0) { /* in the child process... */
process_one_spool_file(spfn);
/* close the sytem-wide log files */
close_system_logs();
exit(exitvalue);
/* NOTREACHED */
}
return SUCCEED;
}
/*
* process_one_spool_file - open, read, and process a spool file
*
* return FAIL if anything goes wrong, else return SUCCEED.
*/
static void
process_one_spool_file(spfn)
char *spfn;
{
/* in child process */
DEBUG1(DBG_MAIN_MID, "process_one_spool_file(%s): called\n", spfn);
/* message grade is encoded in the last char of the filename */
/* XXX this should probably be done in open_spool(), not here! */
msg_grade = spfn[strlen(spfn) - 1];
/* do-or-die... */
processing_signals();
/* attempt to open and lock the message */
if (open_spool(spfn, TRUE, WRITE_LOG_SYS) == FAIL) {
if (exitvalue == EX_TEMPFAIL || exitvalue == EX_NOINPUT) {
DEBUG2(DBG_MAIN_LO, "process_one_spool_file: %s: file %s\n", spfn,
(exitvalue == EX_TEMPFAIL) ? "already locked; skipping" : "not found (already processed?)");
} else {
panic(exitvalue, "process_one_spool_file(): could not process %s -- see log.", spfn);
/* NOTREACHED */
}
return;
}
read_and_process_message();
/* all done with the message */
return;
}
static void
read_and_process_message()
{
char **argv; /* args from read_message() */
/* read the spool file header to get the delivery args */
if (!(argv = read_message())) {
if (msg_size == 0) {
write_log(WRITE_LOG_SYS, "discarding empty spool file!");
unlink_spool();
exitvalue = EX_OK;
} else {
open_system_logs();
log_spool_errors();
/* XXX should perhaps be a panic(EX_OSFILE, ...)? */
write_log(WRITE_LOG_SYS, "failed to read queued message!");
exitvalue = EX_OSFILE;
}
return;
}
/* process arguments from the spool file */
process_args(argv, FALSE);
if (sender_is_trusted) {
check_sender(TRUE);
}
process_recipients(TRUE);
/* XXX free_argv(argv); */
/* perform delivery */
deliver_mail();
return;
}
/*
* fork_wait - fork and have the parent wait for the child to complete
*
* Return with 0 in the child process.
* Return with -1 if fork() fails.
* Return with the pid in the parent, though the wait() will already
* have been done.
*/
int
fork_wait()
{
pid_t pid;
int i;
switch ((pid = fork())) {
case 0:
return 0;
case -1:
return -1;
default:
while ((i = wait((STATUS_TYPE *) NULL)) >= 0 && i != pid) {
;
}
break;
}
return pid;
}
/*
* get_dead_children() - handle exiting child process(es)
*/
static void
get_dead_children()
{
pid_t pid;
STATUS_TYPE status;
#ifdef UNIX_SYS5_2
if ((pid = wait(&status)) > 0) /* one at a time.... */
#else
while ((pid = SMAIL_WAITPID(&status)) > 0)
#endif
{
char *child_type = NULL;
DEBUG1(DBG_MAIN_LO, "get_dead_children(): reaped PID# %ld...\n", (long int) pid);
if (remove_runq_child(pid)) {
child_type = "runq";
}
#ifdef HAVE_BSD_NETWORKING
if (remove_smtpd_child(pid)) {
if (child_type) {
write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
"PID# [%ld] was both an %s and a smtpd process!",
(long int) pid,
child_type);
child_type = "smtpd-or-runq"; /* XXX xprintf("smtpd-or-%s", child_type) would leak */
} else {
child_type = "smtpd";
}
}
#endif /* HAVE_BSD_NETWORKING */
if (!child_type) {
child_type = "unknown";
DEBUG1(DBG_MAIN_LO,
"PID# %ld was not an smtpd or a runq, probably smtpd from before a reload", /* XXX smtpd vs HAVE_BSD_NETWORKING */
(long int) pid);
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0) {
write_log(WRITE_LOG_SYS,
"%s PID# [%ld] exit status %d",
child_type,
(long int) pid,
WEXITSTATUS(status));
}
} else if (WIFSIGNALED(status)) {
char signm[SIG2STR_MAX];
if (sig2str(WTERMSIG(status), signm) == -1) {
sprintf(signm, "#%d", WTERMSIG(status));
}
write_log(((WTERMSIG(status) != SIGTERM) ? WRITE_LOG_PANIC : 0) | WRITE_LOG_SYS,
"%s PID# [%ld] killed by signal SIG%s %s: %s",
child_type,
(long int) pid,
signm,
WCOREDUMP(status) ? "and dumped core" : "(no core)",
strsignal(WTERMSIG(status)));
} else if (WIFSTOPPED(status)) {
char signm[SIG2STR_MAX];
/* in theory we'll hopefully never see stopped processes... */
if (sig2str(WSTOPSIG(status), signm) == -1) {
sprintf(signm, "#%d", WSTOPSIG(status));
}
write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
"%s PID# [%ld] stopped unexpectedly by signal SIG%s: %s",
child_type,
(long int) pid,
signm,
strsignal(WSTOPSIG(status)));
}
}
}
#if defined(HAVE_BSD_NETWORKING)
/*
* add_smtpd_pid - add a child process-ID to the list of smtpd_children
*/
static void
add_smtpd_child(pid)
pid_t pid;
{
/*
* keep track of the number of active smtpds...
*/
smtpd_accept_count++;
/* XXX should use longlist_t? */
smtpd_children = add_intlist(smtpd_children, (long int) pid);
DEBUG2(DBG_MAIN_MID, "added the # %d smtpd PID# %ld to smtpd_children\n",
smtpd_accept_count,
(long int) pid);
return;
}
/*
* remove_smtpd_pid - remove a child process-ID from the list of smtpd children
*/
static int
remove_smtpd_child(pid)
pid_t pid;
{
if (remove_intlist_matching(smtpd_children, (long int) pid)) {
DEBUG2(DBG_MAIN_MID, "removed the # %d smtpd PID# %ld from smtpd_children\n",
smtpd_accept_count,
(long int) pid);
smtpd_accept_count--;
return TRUE;
}
/* note this function is called for every child, not just smtpd's... */
return FALSE;
}
/*
* kill_smtpd_children - kill all the running smtpd's
*
* Kill all the processes in the list headed by smtpd_children. Normally we
* exit() or execv() right away, but just to be clean we'll set each entry to
* be "unused" (note this might be called from a signal context so we don't
* want to try to free any allocated memory).
*/
static void
kill_smtpd_children(sig)
int sig;
{
kill_some_children(sig, smtpd_children, "smtpd");
return;
}
#endif /* HAVE_BSD_NETWORKING */
/*
* add_runq_child - add a new child process-ID to the list of running runq's
*/
static void
add_runq_child(pid)
pid_t pid;
{
/*
* keep track of the number of active runq daemons...
*/
runq_child_count++;
/* XXX should use longlist_t? */
runq_children = add_intlist(runq_children, (long int) pid);
DEBUG2(DBG_MAIN_MID, "added the # %d runq, PID# %ld to runq_children\n",
runq_child_count,
(long int) pid);
return;
}
/*
* remove_runq_child - remove a child process-ID from the list of running runq's
*/
static int
remove_runq_child(pid)
pid_t pid;
{
if (remove_intlist_matching(runq_children, (long int) pid)) {
DEBUG2(DBG_MAIN_MID, "removed the # %d runq PID# %ld from runq_children\n",
runq_child_count,
(long int) pid);
runq_child_count--;
return TRUE;
}
/* note this function is called for every child, not just runq's... */
return FALSE;
}
/*
* kill_runq_children - kill all the running runq's
*
* Kill all the processes in the list headed by runq_children. Normally we
* exit() or execv() right away, but just to be clean we'll set each entry to
* be "unused" (note this might be called from a signal context so we don't
* want to try to free any allocated memory).
*/
static void
kill_runq_children(sig)
int sig;
{
kill_some_children(sig, runq_children, "runq");
return;
}
/*
* kill_some_children - kill all the children in the given list
*/
static void
kill_some_children(sig, list, listname)
int sig;
intlist_t *list;
char *listname;
{
register intlist_t *nc;
pid_t mypid = getpid();
for (nc = list; nc; nc = nc->succ) {
if (nc->i_used) {
if (nc->i_val == 0) {
/* XXX should keep max message len under STR_BUMP bytes (64) */
write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
"PID# [0] is zero, not a %s process, accidental group suicide avoided!",
listname);
} else if (nc->i_val == mypid) {
/* XXX should keep max message len under STR_BUMP bytes (64) */
write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
"PID# [%ld] is me, not a %s process, accidental suicide avoided!",
(long int) mypid, listname);
} else {
DEBUG3(DBG_MAIN_MID, "killing %s PID# %ld with signal %d\n", listname, nc->i_val, sig);
if (kill((pid_t) nc->i_val, sig) == -1) {
/* XXX should keep max message len under STR_BUMP bytes (64) */
write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
"%s PID# [%ld] seems to have disappeared before it could be sent signal %d!",
listname, nc->i_val, sig);
}
nc->i_used = FALSE; /* mark this entry unused */
}
}
}
return;
}
/*
* error_resolve_timeout
*
* Things have hung up in the directors/routers for too long
* so we are converting all defers to fails, and modifying the
* error message along the way.
*/
static void
error_resolve_timeout(deferp, failp)
struct addr **deferp;
struct addr **failp;
{
static struct str wkstr;
static int initialised = FALSE;
struct addr *failed_addr;
int base_len;
DEBUG(DBG_MAIN_LO, "error_resolve_timeout(): converting timeout defers to fails\n");
if (!initialised) {
STR_INIT(&wkstr);
initialised = TRUE;
} else {
STR_CLEAR(&wkstr);
STR_CHECK(&wkstr);
}
STR_CAT(&wkstr, "Address resolve timeout exceeded.\nUnable to route or direct message after ");
STR_CAT(&wkstr, ltoival((time_t) resolve_timeout));
/* STR_CAT() NUL terminates string but also allows subsequent STR_CAT() */
STR_CAT(&wkstr, ".\n");
base_len = STR_LEN(&wkstr);
while (*deferp) {
failed_addr = *deferp;
*deferp = (*deferp)->succ;
if (failed_addr->error) {
failed_addr->error->info = ERR_184 | ERR_NSOWNER;
STR_CAT(&wkstr, "Previous error message:\n");
STR_CAT(&wkstr, failed_addr->error->message);
/* XXX old message storage may leak... */
failed_addr->error->message = COPY_STRING(STR(&wkstr));
/* reset to just include the basic timeout message */
STR_TRIM(&wkstr, base_len);
STR_NEXT(&wkstr, '\0');
} else {
failed_addr->error = note_error(ERR_184 | ERR_NSOWNER,
COPY_STRING(STR(&wkstr)));
}
failed_addr->succ = *failp;
*failp = failed_addr;
}
return;
}
/*
* Local Variables:
* c-file-style: "smail"
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1