/*
#ident	"@(#)smail/src:RELEASE-3_2_0_120:modes.c,v 1.176 2004/08/27 06:52:47 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, compute_nobody,
 *			    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 "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

#ifndef UID_MAX
# ifdef MAXUID
#  define UID_MAX		MAXUID
# else
#  ifdef SHRT_MAX
#   define UID_MAX		SHRT_MAX	/* XXX SHORT_MAX, LONG_MAX on 4.4bsd???? */
#  else
#   define UID_MAX		32767
#  endif
# endif
#endif

#ifndef GID_MAX
# define GID_MAX		UID_MAX
#endif

#ifndef UID_NOBODY
# define UID_NOBODY		UID_MAX
#endif
#ifndef GID_NOBODY
# define GID_NOBODY		GID_MAX
#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 */
#if defined(HAVE_BSD_NETWORKING)
static int smtpd_accept_count = 0;
static intlist_t *smtpd_children = NULL;
static int runq_child_count = 0;
static intlist_t *runq_children = NULL;
# ifndef UNIX_SYS5_2
volatile int got_sigchld;
# endif
#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));
#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 daemon_sigchld __P((int));
static void get_dead_children __P((void));
static void add_smtpd_child __P((pid_t));
static int remove_smtpd_child __P((pid_t));
static void kill_smtpd_children __P((int));
static void add_runq_child __P((pid_t));
static int remove_runq_child __P((pid_t));
static void kill_runq_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.
 */
void
build_host_strings()
{
    char *real_hostname = compute_hostname();
    char *real_domain = NULL;
    char *s;

    if (! 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';
	real_domain = s;
    }
    DEBUG2(DBG_MAIN_HI, "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, "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, "build_host_strings(): setting domains = %s\n",
	       quote(visible_domains, FALSE, TRUE));
    }

    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 {
	    register char *domain = strcolon(visible_domains);
	    struct str str;		/* build hostnames here */
	    
	    STR_INIT(&str);
	    /* XXX probably should strip whitespace first too! */
	    if (*domain) {
		str_printf(&str, "%s.%s", real_hostname, domain);
	    }
	    while ((domain = strcolon((char *) NULL))) {
		if (*domain) {
		    str_printf(&str, ":%s.%s", real_hostname, domain);
		}
	    }

	    STR_CAT(&str, ":localhost");

	    STR_NEXT(&str, '\0');
	    STR_DONE(&str);

	    hostnames = STR(&str);
	}
	DEBUG1(DBG_MAIN_HI, "build_host_strings(): setting hostnames = %s\n",
	       quote(hostnames, FALSE, TRUE));
    }

    if (! primary_name || ! *primary_name) {
	/* this will be the default if none of the rest work out */
	primary_name = real_hostname;

	/* primary_name is the first hostname value by default */
	if (hostnames && *hostnames) {
	    s = strchr(hostnames, ':');
	    if (s) {
		primary_name = strncpy(xmalloc((size_t) (s - hostnames + 1)), hostnames, (size_t) (s - hostnames));
		primary_name[s - hostnames] = '\0';
	    } else {
		primary_name = hostnames; /* no need for a copy */
	    }
	/* otherwise try appending the first of visible_domains to real_hostname */
	} else if (visible_domains) {
	    s = strchr(visible_domains, ':');
	    if (s) {
		primary_name = xprintf("%s.%S", real_hostname, (size_t) (s - visible_domains), visible_domains);
	    } else {
		primary_name = xprintf("%s.%s", real_hostname, visible_domains);
	    }
	}
	DEBUG1(DBG_MAIN_HI, "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, "build_host_strings(): setting visible_name = %v\n", visible_name);
    }

    /*
     * XXX We REALLY need to validate 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.)
     */
}

/*
 * compute_nobody - figure out the nobody uid/gid
 *
 * if `nobody_uid' and `nobody_gid' are defined, use them, otherwise
 * use the login name in `nobody' to determine nobody_uid/gid.
 *
 * XXX assumes nobody's gid == group "nobody"
 */
void
compute_nobody()
{
    if (nobody_uid != BOGUS_USER && nobody_gid != BOGUS_GROUP) {
	return;
    }

    if (nobody == NULL || nobody[0] == '\0') {
	/*
	 * nobody uid/gid not defined.  use something likely to not be
	 * in use
	 */
	nobody_uid = UID_NOBODY;
	nobody_gid = GID_NOBODY;
    } else {
	struct passwd *pw;		/* passwd entry for `nobody' */

	pw = getpwbyname((smail_bool_t) FALSE, nobody);
	if (pw == NULL) {
	    nobody_uid = UID_NOBODY;	/* XXX may still be == BOGUS_USER */
	    nobody_gid = GID_NOBODY;
	} else {
	    nobody_uid = pw->pw_uid;
	    nobody_gid = pw->pw_gid;
	}
    }
    /* reset the UIDs in any recipient address structs in case they have been
     * changed by the above code due to a new value for the "nobody" variable
     * being specified in a config file loaded since these were first set in
     * process_args()
     */
    if (recipients) {
	struct addr *cp;

	for (cp = recipients; cp; cp = cp->succ) {
	    cp->uid = nobody_uid;
	    cp->gid = nobody_gid;
	}
    }
}


/*
 * 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.
 */
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.  Inbetween calls, the state will not
 *	 be inconsistent and it should be okay to call close_spool().
 */
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();
    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 GCC_UNUSED_HACK;
{

#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);
    }
    write_log(WRITE_LOG_SYS, "sig_close(): exiting on SIG%s: %s (ignoring previous exitvalue of %d)", signm, strsignal(sig), exitvalue);
    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;
    }

    only_testing = TRUE;			/* turn off logging */

    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);
	free_addr_list(recipients);
    }

    return;
}

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

    /*
     * 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, this time filling in the 
     */
    if (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;
    }

    /*
     * if a precedence: header is given
     * then change the grade for the mail message
     */
    check_grade();

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

    /*
     * make a log entry for the new message
     */
    log_incoming();

    /* log errors generated in spooling the message */
    log_spool_errors();

    /* 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;
	}
    }

    /*
     * if we are delivering in background, fork a child to perform
     * delivery and exit.  Ignore this when debugging.
     */
    if (deliver_mode ==  BACKGROUND) {
	pid_t pid;

	/* unlock the message in the parent, see lock_message() for details */
	delivery_signals();		/* disassociate from terminal */
	if (error_processing != TERMINAL) {
	    (void) fclose(stdin);
	}
	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 */
	    return;
	}
#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 */
	    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;		/* list of addrs to route */
    struct addr *fail= NULL;		/* list of failed addrs */
    struct addr *defer = NULL;		/* list of deferred addrs */
    struct addr *deliver;		/* addr structures ready to deliver */
    /* transport instances */
    struct assign_transport *assigned_transports = NULL;
    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.
     *
     * 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.
     */
    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("parse error %s", error));
	    cur->flags &= ~ADDR_FORM_MASK;
	    cur->flags |= PARSE_ERROR;
	    cur->succ = fail;
	    fail = 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;
	}
	cur->succ = route_list;
	route_list = cur;
    }

    if (extract_addresses) {
	route_list = NULL;		/* don't need them anymore */
    }

    /*
     * process_header will perform a preliminary analysis of the
     * header fields.  It will note which standard fields exist
     * and may take addresses from the header.  It will perform
     * some initial processing of the From: lines and, depending
     * upon configuration, may put `,' characters between addresses.
     * Also, some required fields which do not exist will be
     * added, (i.e., From: and To: and Message-Id:).
     */
    if (extract_addresses) {
	error = process_header(&route_list);
    } else {
	error = process_header((struct addr **) NULL);
    }
    if (error) {
	write_log(WRITE_LOG_MLOG, "Xnotice: <> error in message header, no recipients found: %s", error);
	if (extract_addresses) {
	    /* trigger notify() below... */
	    route_list = NULL;
	    return_to_sender = TRUE;
	} else {
	    write_log(WRITE_LOG_SYS, "notice: error in message header: %s", error);
	    return;
	}
    }

    /*
     * 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;
    resolve_addr_list(route_list, &deliver, &defer, &fail, TRUE);

    if (deliver == NULL && defer == NULL) {
	write_log(WRITE_LOG_MLOG, "Xnotice: <> no valid recipients were found for this message, no delivery occurred");
	return_to_sender = TRUE;
    }

    /*
     * 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;
    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 failures right away
     */
    if (fail) {
	fail_delivery(fail);
    }

    /*
     * assign instances of transports for remote addresses
     */
    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();
    call_transports(assigned_transports, &defer, &fail);

    /*
     * perform error notification for all failed and perhaps 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... */
    }
    /* assure that max connects is a sane value */
    if (smtp_accept_max < 0) {
	smtp_accept_max = 0;
    }
#if defined(SMALL_MEMORY)
    if (smtp_accept_max > 1024) {
	smtp_accept_max = 1024;
    }
#endif
    /* assure that max connects until only queueing is a sane value */
    if (smtp_accept_queue < 0) {
	smtp_accept_queue = 0;
    }
#if defined(SMALL_MEMORY)
    if (smtp_accept_queue > 1024) {
	smtp_accept_queue = 1024;	/* still exccessive! */
    }
#endif
    if (smtp_accept_queue > smtp_accept_max) {
	smtp_accept_queue = smtp_accept_max;
    }
    /* assure that max runqs is a sane value */
    if (runq_max < 0) {
	runq_max = 0;
    }
#if defined(SMALL_MEMORY)
    if (runq_max > 1024) {
	runq_max = 1024;		/* still probably exccessive! */
    }
#endif
    /* 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;
		}
		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, execv(%s)", smail);
	/* XXX currently we only kill runq's on SIGHUP, not smtpd's... */
	kill_runq_children(SIGHUP);
	execv(smail, save_argv);
	panic(EX_UNAVAILABLE, "execv() of %s failed", smail);
	/* NOTREACHED */
    }
    if (got_sigalrm) {
	/* if config file have changed, recycle */
	DEBUG(DBG_MAIN_LO, "SIGALRM received, check input queue\n");
	if (is_newconf()) {
	    /* re-exec smail */
	    write_log(WRITE_LOG_SYS, "new config files, exec(%s)", smail);
	    /* XXX currently we don't kill any runq's or smtpd's... */
	    execv(smail, save_argv);
	    panic(EX_UNAVAILABLE, "execv() of %s failed", smail);
	    /* 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();
	
	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",
	};

	DEBUG3(DBG_MAIN_MID, "dup() failed for PID %ld: rejecting SMTP connection #%d: %s\n",
	       (long int) getpid(), smtpd_accept_count + 1, 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;

	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",
	};

	DEBUG3(DBG_MAIN_MID, "fork() failed for PID %ld: rejecting SMTP connection #%d: %s\n",
	       (long int) getpid(), smtpd_accept_count + 1, 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;

	break;
    } /* end of 'case -1:' */

    case 0: {		/* in the child process */
	FILE *in;			/* input channel */
	FILE *out;			/* output channel */

	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? */
	}

	/* close the listen socket, as we must */
	(void) close(ls);

	/* 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()....
	 */
	if (set_sigchld_handler(SIG_DFL) < 0) {
	    write_log(WRITE_LOG_SYS, "do_daemon_accept(): signal(SIGCHLD, SIG_DFL) failed: %s.", strerror(errno));
	    exitvalue = EX_OSERR;
	    /* XXX not fatal? */
	}
	/*
	 * 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! */
	    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(0);			/* XXX use exitvalue ?!?!?!? */

	/* NOTREACHED */
    } /* end of 'case 0:' */

    default:		/* in the parent process */
	add_smtpd_child(pid);
	break;

    } /* end of switch() */

    (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 GCC_UNUSED_HACK;
{
#if !(defined(UNIX_BSD) || defined(POSIX_OS)) || (defined(sun) && defined(UNIX_SYS5))
    /* reset the handler if we're using unreliable signals */
    if (signal(sig, daemon_sighup) == SIG_ERR) {
	char signm[SIG2STR_MAX];

	if (sig2str(sig, signm) == -1) {
	    sprintf(signm, "#%d", sig);
	}
	write_log(WRITE_LOG_SYS, "daemon_sighup(): signal(SIG%s) failed: %s.", signm, strerror(errno));
	exitvalue = EX_OSERR;
    }
#endif
    got_sighup = TRUE;

    return;
}

/*
 * daemon_sigterm - note that we received a SIGTERM signal
 */
static void
daemon_sigterm(sig)
    int sig;
{
    char signm[SIG2STR_MAX];

    if (sig2str(sig, signm) == -1) {
	sprintf(signm, "#%d", sig);
    }
    write_log(WRITE_LOG_SYS, "SIG%s received, shutting down daemon.", signm);
    /* tell any runq's to shut down */
    kill_runq_children(sig);
    /* tell any smptd's to shut down too */
    kill_smtpd_children(sig);
    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 GCC_UNUSED_HACK;
{
    got_sigalrm = TRUE;
    if (signal(SIGALRM, daemon_sigalrm) == SIG_ERR) {
	char signm[SIG2STR_MAX];

	if (sig2str(sig, signm) == -1) {
	    sprintf(signm, "#%d", sig);
	}
	write_log(WRITE_LOG_SYS, "daemon_sigalrm(): signal(SIG%s) failed: %s.", signm, strerror(errno));
	exitvalue = EX_OSERR;
    }

    return;
}


#if defined(HAVE_BSD_NETWORKING) || defined(UNIX_SYS5_2)
/*
 * daemon_sigchld - note that we received a SIGCHLD signal
 */
static void
daemon_sigchld(sig)
    int sig;
{
#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;
}
#endif


/*
 * 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(SIGHUP, 1);
    siginterrupt(SIGTERM, 1);		/* SIGTERM will be SIG_DFL */
#endif
#if defined(HAVE_BSD_NETWORKING) || defined(UNIX_SYS5_2)
    if (set_sigchld_handler(daemon_sigchld) < 0) {
	panic(EX_OSERR, "noop_mode(): sigaction(SIGCHLD) failed: %s.", strerror(errno));
	/* NOTREACHED */
    }
#endif
    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();
# ifndef 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, "SIGHUP received, exec(%s)", smail);
		execv(smail, save_argv);
		panic(EX_UNAVAILABLE, "execv() of %s failed", smail);
		/* 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, "new config files, exec(%s)", smail);
		execv(smail, save_argv);
		panic(EX_UNAVAILABLE, "execv() of %s failed", smail);
		/* 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 lots, 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 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, "%ld\n", (long int) daemon_pid) < 1) {
		panic(EX_OSFILE, "error writing 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 ? daemon_pidfile : "<NULL>");
	    /* 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? */
	    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);
	} else {
	    initialize_state();
	}
#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(): signal(SIGCHLD, SIG_DFL) failed: %s.", strerror(errno));
	    exitvalue = EX_OSERR;
	}
#endif
	(void) alarm(0);
	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)" : "");

    only_testing = TRUE;			/* turn off logging */

    X_PANIC_OKAY();

    if (extract_addresses) {
	/*
	 * 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 (! 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;
	}
	/* 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 ... syntax 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);
    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: %v\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: %v\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", 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", num_recipients, (num_recipients > 1) ? "s" : "");
    }

    return;
}


/*
 * print_version - display the current version string on stdout
 */
void
print_version()
{
    only_testing = TRUE;			/* turn off logging */

    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;

    only_testing = TRUE;			/* turn off logging */

    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;

    only_testing = TRUE;		/* turn off logging */

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

    only_testing = TRUE;		/* turn off logging */

    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_bufsiz > 4096) {
	message_bufsiz = 4096;
    }
    work = scan_spool_dirs(scan_frozen);

    while (*work) {
	char **argv;			/* arguments from spool file */
	int first = TRUE;

	/*
	 * reset any side-effects caused by process_args() or process_header()
	 */
	initialize_state();
	operation_mode = PRINT_QUEUE;	/* no matter how we got here, this is where we are */

	/* 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\tFrom: %s  (in %s/%s)\n",
		      message_id,
		      sender, spool_dir,
		      scan_frozen ? "error" : "input");

	(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.
	 */
	if (*argv) {
	    process_args(argv, FALSE);	/* set things like extract_addresses */
	    process_recipients();		/* XXX ??? */
	    (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++);
	    }
	}
	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: %v)", 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;
	}
	sender = NULL;
	if (!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++;
	    continue;
	}
	altjobs++;
	altsize += msg_size;
	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;
{
    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();
	}
    }

    /*
     * 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, 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);
		/* NOREACHED */
	    }
	}
	/*
	 * in the parent - or if queued instead
	 *
	 * 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);
	/* NOREACHED */
    }

    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];

    /* initialize state before reading state from the spool file */
    initialize_state();			/* XXX probably a second call */

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

    /* 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;
}


#if defined(HAVE_BSD_NETWORKING)

/*
 * 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";
	}
	if (remove_smtpd_child(pid)) {
	    if (child_type) {
		write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
			  "PID# %d was both an %s and a smtpd child!",
			  child_type, pid);
		child_type = "smtpd-or-runq"; /* XXX xprintf() would leak */
	    } else {
		child_type = "smtpd";
	    }
	}
	if (!child_type) {
	    child_type = "unknown";
	    write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
		      "PID# %ld was not an smtpd or a runq!",
		      (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)));
	}
    }
}


/*
 * save_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++;

    smtpd_children = add_intlist(smtpd_children, 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, pid)) {
	smtpd_accept_count--;

	return TRUE;
    }
    /* note this function is called for every child, not just smtpd'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_smtpd_children(sig)
    int sig;
{
    register intlist_t *nc;

    for (nc = smtpd_children; nc; nc = nc->succ) {
	if (nc->i_used) {
	    kill(sig, nc->i_val);
	    nc->i_used = FALSE;		/* mark this entry unused */
	}
    }

    return;
}


/*
 * 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++;

    runq_children = add_intlist(runq_children, 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, 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;
{
    register intlist_t *nc;

    for (nc = runq_children; nc; nc = nc->succ) {
	if (nc->i_used) {
	    kill(sig, nc->i_val);
	    nc->i_used = FALSE;		/* mark this entry unused */
	}
    }

    return;
}

#endif /* HAVE_BSD_NETWORKING */


/*
 * 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_NPOSTMAST | 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_NPOSTMAST | ERR_NSOWNER,
					    COPY_STRING(STR(&wkstr)));
	}
	failed_addr->succ = *failp;
	*failp = failed_addr;
    }

    return;
}

/* 
 * Local Variables:
 * c-file-style: "smail"
 * End:
 */
