/*
#ident	"@(#)smail/src:RELEASE-3_2_0_121:main.c,v 1.141 2005/10/26 16:27:28 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.
 */

/*
 * main.c:
 *	process arguments, configure environment and process
 *	messages.
 *
 *	external functions: main, initialize_state, process_args
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>
#include <signal.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

#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(HAVE_RLIMIT)
# include <sys/resource.h>
# ifndef RLIM_T_DECLARED
typedef int rlim_t;
# endif
#endif	/* HAVE_RLIMIT */

#ifdef	UNIX_AIX3
# include <sys/id.h>
#endif	/* UNIX_AIX3 */

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

#include <pcre.h>

#include "smail.h"
#include "alloc.h"
#include "list.h"
#include "config.h"
#include "smailsock.h"
#include "smailstring.h"
#include "dys.h"
#include "parse.h"
#include "addr.h"
#include "field.h"
#include "hash.h"
#include "lookup.h"
#include "match.h"
#include "main.h"
#include "log.h"
#include "direct.h"
#include "route.h"
#include "transport.h"
#include "smailwait.h"
#include "child.h"
#include "spool.h"
#include "exitcodes.h"
#include "smailconf.h"
#include "extern.h"
#include "debug.h"
#include "error.h"
#include "smailport.h"

/*
 * XXX process_recipients() currently uses process_field(), not to break the
 * parameters down into separate addresses (in fact it checks that it does not
 * accidentally find more or fewer addresses than there were command-line
 * parameters), but rather to provide the same form of address qualification as
 * is offered when addresses are extracted from the headers using '-t'.
 *
 * See the big comment early in field.c:tokenize() for an explanation of why
 * the use of process_field() for this purpose if problematic, and what
 * long-term solution should be worked on instead.
 */
#define PROCESS_RECIPIENTS_USES_PROCESS_FIELD	1

/* exported variables */
int islocal = FALSE;			/* TRUE if mail originated locally */
int exitvalue = EX_OK;			/* call exit with this value */
char *program = NULL;			/* argv[0] from main */
char *sender = NULL;			/* sender of message */
char *local_sender = NULL;		/* local sender of message */
int error_sender = FALSE;		/* TRUE if special sender <> or <+> is given */
char *sender_name = NULL;		/* full name of sender */
int debug = 0;				/* debugging level, 0 is off */
int dont_deliver = FALSE;		/* if TRUE, don't actually deliver */
int process_queue = FALSE;		/* process spooled files */
unsigned queue_interval = 0;		/* process queues at this interval */
int hop_count = -1;			/* hop count so far for message */
int do_aliasing = FALSE;		/* do aliasing for local addresses */
int extract_addresses = FALSE;		/* get recipients from header */
int me_too = FALSE;			/* sender allowed in recipients */
enum er_proc error_processing = ERROR_DEFAULT;	/* method of displaying errors */
enum dot_usage dot_usage = DOT_ENDS_MESSAGE;	/* how do we treat . on input */
enum deliver_mode deliver_mode = DELIVER_DEFAULT; /* foreground, background or queued */
enum op_mode operation_mode = MODE_DEFAULT;	/* mode of operation */
struct addr *recipients = NULL;		/* list of cmd-line recipient addresses */
struct addr *parameters = NULL;		/* fancy argv[] list */
int num_recipients = 0;			/* number of entries in 'recipients' or 'parameters' */
char *primary_name = NULL;		/* primary local name from hostnames */
FILE *errfile = NULL;			/* file to write debug messages to */
char *arg_debug_file = NULL;		/* debug filename if not to stderr */
char *dnsbl_to_test = NULL;		/* for test_dnsbl() */
int only_testing = FALSE;		/* avoid writing sys/panic logs */
uid_t real_uid = BOGUS_USER;		/* saved real uid before ruid setup */
gid_t real_gid = BOGUS_GROUP;		/* saved real gid before rgid setup */
enum prog_type prog_type = PROG_SMAIL;	/* type of program we are running as */
char **save_argv = NULL;		/* saved pointer to arguments */
int some_deferred_addrs = FALSE;	/* don't unlink spool file */
					/* as some addrs were deferred */
uid_t prog_euid = BOGUS_USER;		/* effective uid of program */
gid_t prog_egid = BOGUS_GROUP;		/* effective gid of program */
int force_zero_exitvalue = FALSE;	/* if TRUE always exit with status 0 */
int call_freeze_message = FALSE;	/* if TRUE must call freeze_message() */
char *sender_host = NULL;		/* name of sender's host */
char *sender_host_addr = NULL;		/* inet source address of sender's host */
char *sender_host_port = NULL;		/* inet source port of sender's host */
char *sender_proto = NULL;		/* name of sender's sending protocol */
char *sender_program = NULL;		/* name of program that spooled msg */
char *smtp_service_name = NULL;		/* smtp service name from -oX */
int scan_frozen = FALSE;		/* should mailq look in the error queue? */

/* functions local to this file */
static void panic_if_null __P((char *, char *));
static void rmail_panic __P((void));
static void check_sender_perms __P((char *, char *));
static unsigned int xivaltou __P((char *));
static char *escape_newline __P((char *));
static void parse_grade_range __P((char *, int *, int *));

/* variables local to this file */
static int report_memory_usage = FALSE;	/* if TRUE, report sbrk(0) when done */
static unsigned long int startup_sbrk0 = 0;
static char *arg_second_config_file = NULL; /* second config set by args */
static char *arg_director_file = NULL;	/* director file set by args */
static char *arg_router_file = NULL;	/* router file set by args */
static char *arg_transport_file = NULL;	/* transport file set by args */
static char *arg_qualify_file = NULL;   /* domain qualification file set by args */
static char *arg_retry_file = NULL;     /* address retry file set by args */
static char *arg_smail_lib_dir = NULL;	/* smail lib dir set by args */
static char *arg_smail_util_dir = NULL;	/* smail util dir set by args */
static char *arg_alias_file = NULL;	/* alias file set by -oA */
static char *arg_runq_grades = NULL;	/* which grades are processed by runq */
#ifdef PROCESS_RECIPIENTS_USES_PROCESS_FIELD
static struct str cmdline_addrs;	/* fake "to:" field */
#endif

/*
 * main - what to do after being exec'd
 *
 * main decodes the argument list and then performs specified action
 */
int
main(argc, argv)
    int argc;				/* count of arguments passed */
    char **argv;			/* vector of arguments */
{
    char *save_config_file = config_file;
    struct stat statbuf;
    char *error;
    char *utilargs[10];
    int child;
    FILE *new_errfile;

#if 0	/* I wish CPP could do sizeof()!  :-) */
    assert(sizeof(unsigned long) >= sizeof(char *));
#endif
    startup_sbrk0 = (unsigned long) ((char *) sbrk((size_t /* intptr_t */) 0));

    MALLOC_DEBUG(MALLOC_DEBUG_LEVEL);

#ifdef __STDC__
    pcre_malloc = (void *(*)(size_t)) xmalloc;
    pcre_free = (void (*)(void *)) priv_xfree;
#else
    pcre_malloc = (void (*)()) xmalloc;
    pcre_free = (void (*)()) priv_xfree;
#endif

    save_argv = argv;

    /* set up the file for interactive error and debug messages */
    if (!errfile) {
	/* is stderr a valid file descriptor? */
	if (fstat(STDERR_FILENO, &statbuf) >= 0) {
	    /* yes, use stderr */
	    errfile = stderr;
	} else {
	    /* no, can't output to stderr */
	    errfile = NULL;
	}
    }

    /*
     * In theory if we pre-initialise logstr very early here in main(), before
     * there's any possibility of SIGINT being set to call sig_unlink() or any
     * other signal handler that calls write_log(), then we should avoid any
     * possibility of anyone trying anything nasty by interrupting malloc() and
     * thus causing it to be re-entered in a signal handler....
     *
     * If anyone thinks they're smart enough to try limiting the max datasize
     * enough to cause this malloc() to fail then its xmalloc() wrapper will
     * call write_log() anyway, but since the uninitialized logstr will have a
     * NULL 'p' pointer the STR_CHECK() call early on will bail with another
     * recursive call to write_log() and the whole thing should grind to an
     * untinterruptible loop that will require a SIGQUIT or SIGKILL, which on
     * most systems can only be delivered by the real superuser, thus revealing
     * the little cracker's devious intentions to the admin.
     */
    STR_INIT(&logstr);

    /* close file descriptors left open by others */
    close_all();

    /* get the basename for the program */
    program = (program = strrchr(argv[0], '/')) ? program + 1 : argv[0];

    /* default the sender program (-oMP) to argv[0] */
    sender_program = program;

    /* skip on to the first parameter */
    argv++;

    /*
     * Save away the real UID and GID, as well as the effective IDs
     */
#if 0	/* I wish CPP could do sizeof()!  :-) */
    assert(sizeof(uid_t) == sizeof(unsigned int)); /* ...and assume gid_t is the same */
#endif
    real_uid = getuid();
    real_gid = getgid();
    prog_euid = geteuid();
    prog_egid = getegid();

    /* initialize local_sender (may be over-written) */
    compute_local_sender();

    /*
     * initialize per-message state information
     *
     * -- not really needed here, but for safety's sake....
     */
    initialize_state();

#ifdef	UNIX_SCO
    /* if we don't have a login id, assume one */
    if (getluid() == -1)
	(void) setluid(0);
#endif	/* UNIX_SCO */

#ifdef	UNIX_AIX3
    /* if we don't have a login id, assume one */
    if (getuidx(ID_LOGIN) == -1)
	(void) setuidx(ID_LOGIN, 0);
#endif	/* UNIX_AIX3 */

    /* always get a write error for a SIGPIPE, rather than the signal */
    (void) signal(SIGPIPE, SIG_IGN);

#if	defined(UNIX_AIX) && !defined(NO_AIX_CORE_DUMP)
    /* On a segmentation fault or bus error, we need a full core dump.  */
    {
	struct sigaction act;

	act.sa_handler = SIG_DFL;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_FULLDUMP;
	sigaction(SIGSEGV, &act, (struct sigaction *)NULL);
	sigaction(SIGBUS, &act, (struct sigaction *)NULL);
    }
#endif	/* UNIX_AIX && !NO_AIX_CORE_DUMP */

    /* Xenix systems must have TZ in the environment */
#ifdef	REQUIRE_TZ
    /* if no timezone specified, assume GMT */
    if (getenv("TZ") == NULL) {
	(void) putenv("TZ=GMT0");
    }
#endif

    /*
     * we will always be supplying exactly the mode we want, but just in case
     * we'd better not be giving away write permission anywhere....
     */
    (void) umask(022);			/* S_IWGRP | S_IWOTH */

    /* set the program type based on the program's basename */
    prog_type = PROG_SMAIL;
    if (EQ(program, "rmail")) {
	prog_type = PROG_RMAIL;
    } else if (EQ(program, "pathto")) {
	prog_type = PROG_PATHTO;
    } else if (EQ(program, "optto")) {
	prog_type = PROG_OPTTO;
    } else if (EQ(program, "uupath")) {
	prog_type = PROG_UUPATH;
    } else if (EQ(program, "newaliases")) {
	prog_type = PROG_NEWALIASES;
    } else if (EQ(program, "smailconf")) {
	prog_type = PROG_SMAILCONF;
    } else if (EQ(program, "mailq")) {
	prog_type = PROG_MAILQ;
    } else if (EQ(program, "runq")) {
	prog_type = PROG_RUNQUEUE;
    } else if ((EQ(program, "smtpd")) ||
	       (EQ(program, "in.smtpd"))) {
	/*
	 * if there is no file on stdout, then dup stdin to stdout.
	 * This is done because processes started from inetd will have
	 * fd 0 set to the socket, but fd 1 will not be set.  It will
	 * need to be dup'd for this to work.
	 */
	if (fstat(1, &statbuf) < 0) {
	    dup2(0, 1);
	}
	prog_type = PROG_SMTPD;
    } else if (EQ(program, "rsmtp")) {
	prog_type = PROG_RSMTP;
    } else if (EQ(program, "rogue") || EQ(program, "hack")) {
	prog_type = PROG_ROGUE;
    } else if (EQ(program, "..execmail") || /* XXX ??? ".." ??? */
	       EQ(program, "execmail")) {
	prog_type = PROG_EXECMAIL;
    }

    /* set state information which depends on program type */
    if (prog_type == PROG_NEWALIASES) {
	operation_mode = REBUILD_ALIASES;
    } else if (prog_type == PROG_SMAILCONF) {
	operation_mode = FREEZE_CONFIG;
    } else if (prog_type == PROG_MAILQ) {
	operation_mode = PRINT_QUEUE;
	only_testing = TRUE;
    } else if (prog_type == PROG_RSMTP) {
	operation_mode = BATCH_SMTP_MODE;
    } else if (prog_type == PROG_SMTPD) {
	operation_mode = SMTP_MODE;
    } else if (prog_type == PROG_ROGUE) {
	operation_mode = ROGUE_MODE;
    } else if (prog_type == PROG_PATHTO) {
	operation_mode = PATHTO_MODE;
    } else if (prog_type == PROG_OPTTO) {
	operation_mode = OPTTO_MODE;
    } else if (prog_type == PROG_UUPATH) {
	operation_mode = UUPATH_MODE;
    } else if (prog_type == PROG_RMAIL) {
	/* operation_mode = MODE_DEFAULT; (XXX or DELIVER_MAIL?) */
	dot_usage = NO_DOT_PROTOCOL;
    }

    if (getenv("SMAIL_CONFIG")) {	/* XXX is this sane!?!? */
	config_file = getenv("SMAIL_CONFIG");
    }

    switch (operation_mode) {
    case PATHTO_MODE:
    case UUPATH_MODE:
    case OPTTO_MODE:
	/* these do their own argument handling */
	break;

    default:
	/* process the args given by the user */
	process_args(argv, TRUE);
	break;
    }

    if (operation_mode == PRINT_VERSION) {
	print_version();
	exit(0);
    }

    if (prog_type == PROG_RUNQUEUE) {
	if (operation_mode == MODE_DEFAULT ||
	    operation_mode == DAEMON_MODE)
	{
	    process_queue = TRUE;
	}
    }
    if (operation_mode == MODE_DEFAULT) {
	/*
	 * when performing a queue run, no other operations are
	 * performed, by default.  Currently no other operations
	 * are allowed, either, though this may change in the
	 * future.
	 */
	if (prog_type == PROG_RUNQUEUE || process_queue) {
	    operation_mode = NOOP_MODE;
	} else {
	    operation_mode = DELIVER_MAIL;
	}
    }

    switch (operation_mode) {
    case PATHTO_MODE:
    case UUPATH_MODE:
    case OPTTO_MODE:
	/* these do their own argument handling */
	break;

    case TEST_MODE:
    case SMTP_MODE:
    case BATCH_SMTP_MODE:
    case DAEMON_MODE:
    case FREEZE_CONFIG:
    case ROGUE_MODE:
    case COPYING_MODE:
    case REBUILD_ALIASES:
	if (num_recipients != 0) {
	    if (errfile) {
		(void) fprintf(errfile, "%s: too many parameters\n", program);
	    }
	    exit(EX_USAGE);
	    /* NOTREACHED */
	}

    default:					/* to shut up gcc-2 -Wall */
	break;
    }

#ifdef	HAVE_SETGROUPS
    /* clear out all extra groups.  We don't want to have to deal with them */
    {
	gid_t dummy_grouplist[1];
	dummy_grouplist[0] = 0;

	if (setgroups(0, (const gid_t *) NULL) != 0) {
	    if (setgroups(1, dummy_grouplist) != 0) {
		if (getuid() == 0) {
		    write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
			      "setgroups(0, NULL) and setgroups(1, [0]) failed: %s",
			      strerror(errno));
		} else {
		    write_log(WRITE_LOG_TTY,
			      "setgroups(0, NULL) and setgroups(1, [0]) both failed: %s\n",
			      strerror(errno));
		}
	    }
	}
    }
#endif	/* HAVE_SETGROUPS */

    if (config_file != save_config_file || arg_second_config_file ||
	arg_director_file || arg_router_file || arg_transport_file ||
	arg_qualify_file || arg_retry_file || arg_smail_lib_dir ||
	arg_smail_util_dir || arg_alias_file)
    {
	/*
	 * a config_file was set, or unset from the command args
	 * then watch out for set-uid execs;  i.e., go back to
	 * the real uid under which we were invoked.
	 */
	setgid(getgid());
	setuid(getuid());
    }

    /* read in the config files, if they exists */
    if (arg_smail_lib_dir) {
	smail_lib_dir = arg_smail_lib_dir;
    }
    if (arg_smail_util_dir) {
	smail_util_dir = arg_smail_util_dir;
    }
    error = read_config_file((config_file = make_lib_fn(config_file)));
    if (error) {
	write_log((operation_mode == DAEMON_MODE ? WRITE_LOG_PANIC : 0) | WRITE_LOG_TTY,
		  "error with config file: %s", error);
	exit(EX_CONFIG);
	/*NOTREACHED*/
    }

    /* we need to set these again, in case they were changed in the main config file */
    if (arg_smail_lib_dir) {
	smail_lib_dir = arg_smail_lib_dir;
    }
    if (arg_second_config_file) {
	second_config_file = arg_second_config_file;
    }

    second_config_file = make_lib_fn(second_config_file);
    if (second_config_file) {
	error = read_config_file(second_config_file);
	if (error) {
	    write_log((operation_mode == DAEMON_MODE ? WRITE_LOG_PANIC : 0) | WRITE_LOG_TTY,
		      "error with config file: %s", error);
	    exit(EX_CONFIG);
	    /*NOTREACHED*/
	}
    }
    /* re-compute nobody's IDs in case "nobody" was set in a config file */
    compute_nobody();

    /* we need to set these yet again in case they were changed in a config file */
    if (arg_smail_lib_dir) {
	smail_lib_dir = arg_smail_lib_dir;
    }
    if (arg_smail_util_dir) {
	smail_util_dir = arg_smail_util_dir;
    }
    if (arg_director_file) {
	director_file = arg_director_file;
    }
    if (arg_router_file) {
	router_file = arg_router_file;
    }
    if (arg_transport_file) {
	transport_file = arg_transport_file;
    }
    if (arg_qualify_file) {
	qualify_file = arg_qualify_file;
    }
    if (arg_retry_file) {
	retry_file = arg_retry_file;
    }
    if (arg_runq_grades) {
        runq_grades = arg_runq_grades;
    }

    /* all configs should have been loaded now -- we can safely do this: */
    build_host_strings();
    parse_grade_range(runq_grades, &min_runq_grade, &max_runq_grade);
    parse_grade_range(delivery_grades, &min_delivery_grade, &max_delivery_grade);

    /* check that max_message_size and message_buf_size make sense */
    if ((operation_mode == DAEMON_MODE || operation_mode == PRINT_VARS_MODE) &&
	(unsigned long int) max_message_size > message_buf_size &&
	(body_checks || body_checks_always)) {
	write_log((operation_mode == DAEMON_MODE ? WRITE_LOG_PANIC : 0) | WRITE_LOG_TTY,
		  "Note (max_message_size > message_buf_size) so body_checks and body_checks_always will be incomplete");
    }

    /* get rid of any limits on file size that might affect operation */
#if defined(HAVE_ULIMIT) && !defined(HAVE_RLIMIT) /* XXX HAVE_SETRLIMIT */
    /* kill limits on file size */
    (void) ulimit(2, ((long) 1 << (BITS_PER_LONG - 2)) / 512);
#endif	/* HAVE_ULIMIT && !HAVE_RLIMIT */

#if defined(HAVE_RLIMIT) /* XXX should be HAVE_SETRLIMIT for autoconf */
    /*
     * Kill any limits on CPU time, and set reasonable limits on data segment,
     * and on stack size to prevent Denial of Service attacks from the network.
     *
     * XXX for the latter two we should use getrusage() and choose some
     * meaningful multiplier instead of having the installer choose a limit.
     */
    {
	struct rlimit rl;

	rl.rlim_cur = (rlim_t) RLIM_INFINITY; /* XXX this might not be wise */
	rl.rlim_max = (rlim_t) RLIM_INFINITY;
	(void) setrlimit(RLIMIT_CPU, &rl);
	(void) setrlimit(RLIMIT_FSIZE, &rl);

#if defined(DATA_RLIMIT)
	rl.rlim_cur = DATA_RLIMIT + message_buf_size;
#elif defined(SMALL_MEMORY)
	rl.rlim_cur = (2 * 1024 * 1024) + message_buf_size;
#else
	rl.rlim_cur = (8 * 1024 * 1024) + message_buf_size;	/* generous, aren't we! */
#endif	/* DATA_RLIMIT */
	(void) setrlimit(RLIMIT_DATA, &rl);

#if defined(STACK_RLIMIT)
	rl.rlim_cur = STACK_RLIMIT;
#elif defined(SMALL_MEMORY)
	rl.rlim_cur = (1 * 1024 * 1024);
#else
	rl.rlim_cur = (8 * 1024 * 1024);	/* generous, aren't we! */
#endif	/* STACK_RLIMIT */
	(void) setrlimit(RLIMIT_STACK, &rl);
    }
#endif	/* HAVE_RLIMIT */

    /* check the sender address, now that we know our own hostnames */
    if (sender) {
	/* this call may not return... */
	check_sender(FALSE);
    }

    /* get the config file names within the lib directory */
    director_file = make_lib_fn(director_file);
    router_file = make_lib_fn(router_file);
    transport_file = make_lib_fn(transport_file);
    method_dir = make_lib_fn(method_dir);
    qualify_file = make_lib_fn(qualify_file);
    retry_file = make_lib_fn(retry_file);
    copying_file = make_lib_fn(copying_file);

    /* make sure `smail' points to a fully qualified pathname */
    smail = make_util_fn(smail);

    if (error) {
	/*
	 * XXX can we ever get here now that we detect per config file?
	 *
	 * error in the config file: not a good thing.
	 *
	 * Revert back to the initial values of vital attributes,
	 * and set queue_only to avoid trying to perform delivery
	 * with a potentially bad configuration.
	 */
	max_message_size = MAX_MESSAGE_SIZE;
	log_fn = LOGFILE;
	panic_fn = PANIC_LOG;
	cons_fn = CONSOLE;
	spool_dirs = SPOOL_DIRS;
	spool_mode = SPOOL_MODE;
	lock_mode = LOCK_MODE;
	log_mode = LOG_MODE;
	message_log_mode = MESSAGE_LOG_MODE;
	message_buf_size = MESSAGE_BUF_SIZE;
	queue_only = TRUE;

	/*
	 * if we are not actually going to be reading in messages,
	 * then panic.  Also, allow some trivial operations.
	 */
	switch (operation_mode) {
	case PRINT_QUEUE:
	case SMTP_MODE:
	case BATCH_SMTP_MODE:
	case DELIVER_MAIL:
	case PRINT_VARS_MODE:
	case TEST_DNSBL_MODE:
	case REBUILD_ALIASES:
	case PATHTO_MODE:
	case UUPATH_MODE:
	case OPTTO_MODE:
	    write_log(WRITE_LOG_TTY|WRITE_LOG_PANIC, "warning: %s", error);
	    break;

	default:
	    write_log(WRITE_LOG_TTY|WRITE_LOG_PANIC, "fatal: %s", error);
	    exit(EX_CONFIG);
	    /*NOTREACHED*/
	}
    }

    /*
     * read in the transport, router and director files, if needed
     *
     * NOTE: if queue_only is FALSE and mode is DELIVER_MAIL,
     *	     we will need to read these files, though do this later
     *	     to avoid wasting time on it before the spool file is
     *	     created.
     */
    switch (operation_mode) {
    case NOOP_MODE:
    case DAEMON_MODE:
	/*
	 * stat our binary so we can see if it has been touched later
	 */
	if (stat(smail, &statbuf) < 0) {
	    write_log(WRITE_LOG_TTY|WRITE_LOG_PANIC, "main: bad stat() of smail binary %s", smail);
	    exit(EX_SOFTWARE);
	    /* NOTREACHED */
	} else {
	    add_config_stat(smail, &statbuf);
	}
#if defined(HAVE_BSD_NETWORKING) && defined(HAVE_BIND)
	/*
	 * this is the easiest thing to do to avoid any resolver library
	 * caching the nameserver when it first fires up....
	 */
	if (stat(_PATH_RESCONF, &statbuf) >= 0) {
	    add_config_stat(_PATH_RESCONF, &statbuf);
	}
#endif
	/* FALLTHRU */

    case PRINT_VARS_MODE:			/* This is to allow dumping of configs */
    case TEST_MODE:
    case TEST_DNSBL_MODE:
    case VERIFY_ADDRS:
    case BATCH_SMTP_MODE:
    case SMTP_MODE:
    case PATHTO_MODE:
    case UUPATH_MODE:
    case OPTTO_MODE:
	/* note that retry_file depends on stuff in config_file */
	if ((error = read_transport_file()) ||
	    (error = read_router_file()) ||
	    (error = read_director_file()) ||
	    (error = read_qualify_file()) ||
	    (error = read_retry_file()))
	{
	    write_log(WRITE_LOG_TTY|WRITE_LOG_PANIC, "%s", error);
	    exit(EX_OSFILE);
	    /* NOTREACHED */
	}
	break;

    default:					/* to shut up gcc-2 -Wall */
	break;
    }

    switch (operation_mode) {
    case NOOP_MODE:
    case DAEMON_MODE:
    case TEST_MODE:
	cache_directors();
	cache_routers();
	cache_transports();
	break;

    default:					/* to shut up gcc-2 -Wall */
	break;
    }

    /*
     * set the real-IDs to the effective-IDs. After this point, the real UID &
     * GID are no longer at all interesting.  In BSD, if the mailer runs as
     * root, we can now freely set the real or effective UID to whatever we
     * want without worrying about swapping them.
     *
     * Also, if the mailer runs as a user other than root, we no longer have to
     * worry about child processes being able to do a setuid(getuid) to get
     * root priveledges when root itself sends mail.
     */

    if (prog_egid != real_gid) {
	setgid(prog_egid);
    }
    if (prog_euid != real_uid) {
	setuid(prog_euid);
    }

    /*
     * If the current effective UID does not match the required ID,
     * then mail can be queued (if that succeeds), but mail will
     * not be delivered.  This only applies when receiving mail,
     * and is ignored when running through the queue from a
     * queue run daemon.
     */
#ifdef REQUIRED_EUID
    if (prog_euid != REQUIRED_EUID) {
	queue_only = TRUE;
    }
#endif

    /*
     * error processing can be other than TERMINAL only for mail delivery
     * modes, and if the user explicitly requested TERMINAL processing then
     * force deliver_mode to foreground so that this can work.
     */
    switch (operation_mode) {
    case DELIVER_MAIL:
    case NOOP_MODE:
    case DAEMON_MODE:
    case SMTP_MODE:
    case BATCH_SMTP_MODE:
	switch (error_processing) {
	case ERROR_DEFAULT:
	    error_processing = MAIL_BACK;
	    break;
	case TERMINAL:
	    /* XXX if (!errfile) panic()? */
	    if (deliver_mode != FOREGROUND) {
		if (deliver_mode != DELIVER_DEFAULT && errfile) {
		    fprintf(errfile, "%s: reporting errors to standard error requires foreground delivery.\n", program);
		}
		deliver_mode = FOREGROUND;
	    }
	    break;
	default:
	    break;
	}
	break;

    default:
	error_processing = TERMINAL;
	/* XXX if (!errfile) panic()? */
	break;
    }

    /*
     * if debugging to standard error, then don't do background delivery.
     * Otherwise, we might continue writing to standard error after the
     * main process has exited.
     */
    if (debug && ! arg_debug_file && deliver_mode != FOREGROUND) {
	DEBUG(DBG_MAIN_MID, "Delivery mode is forced to foreground for debugging without '-D debug_file'.\n");
	deliver_mode = FOREGROUND;
    }

    /*
     * setup the delivery mode used for delivering new messages
     */
    if (deliver_mode == DELIVER_DEFAULT) {
	/*
	 * if not set explicity in the arguments, key off the first
	 * letter of the "delivery_mode" configuration parameter
	 */
	switch (delivery_mode_string[0]) {
	case 'f':
	    deliver_mode = FOREGROUND;
	    break;
	case 'b':
	    deliver_mode = BACKGROUND;
	    break;
	default:
	    deliver_mode = QUEUE_MESSAGE;
	    break;
	}
    }

    if (process_queue &&
	operation_mode != NOOP_MODE &&
	operation_mode != DAEMON_MODE) {
	if (errfile) {
	    fprintf(errfile,
		    "%s: operation mode not compatible with queue runs\n",
		    program);
	}
	exit(EX_USAGE);
    }

    if (process_queue && num_recipients && queue_interval) {
	if (errfile) {
	    fprintf(errfile,
		    "%s: cannot have queue run interval and list of messages to process.\n",
		    program);
	}
	exit(EX_USAGE);
    }

    /*
     * change error file to debugging file from -D option, if any
     *
     * JMJ: Change location of this fragment to below the setuid/setgid
     *      calls to allow for use of fopen_as_user() instead of just
     *      fopen().
     *
     *      Side effect: -D now requires full pathname to debug file
     */
    if (arg_debug_file) {
	if (!(new_errfile = fopen_as_user(arg_debug_file, "a", real_uid, real_gid, 0600))) {
	    write_log(WRITE_LOG_TTY, "Warning: Cannot open debug file %v: %s",
		      arg_debug_file, strerror(errno));
	    arg_debug_file = NULL;
	} else {
	    errfile = new_errfile;
	    fprintf(errfile, "\n%s: Debugging started: PID# [%ld]\n\n",
		    program, (long int) getpid());
	}
    }


    /*
     * We only need to compile these PCRE lists if we're going to be accepting
     * message content into a queue file....
     */
    switch (operation_mode) {
    case DELIVER_MAIL:
    case DAEMON_MODE:
    case SMTP_MODE:
    case BATCH_SMTP_MODE:
	error = NULL;
	header_checks_list = compile_pcre_list(header_checks, &error);
	if (error) {
	    write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "error parsing RE in header_checks: %s", error);
	    exit(EX_CONFIG);
	    /*NOTREACHED*/
	}
	error = NULL;
	header_checks_always_list = compile_pcre_list(header_checks_always, &error);
	if (error) {
	    write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "error parsing RE in header_checks_always: %s", error);
	    exit(EX_CONFIG);
	    /*NOTREACHED*/
	}
	error = NULL;
	body_checks_list = compile_pcre_list(body_checks, &error);
	if (error) {
	    write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "error parsing RE in body_checks: %s", error);
	    exit(EX_CONFIG);
	    /*NOTREACHED*/
	}
	error = NULL;
	body_checks_always_list = compile_pcre_list(body_checks_always, &error);
	if (error) {
	    write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "error parsing RE in body_checks_always: %s", error);
	    exit(EX_CONFIG);
	    /*NOTREACHED*/
	}

    default:				/* to shut up gcc-2 -Wall */
	break;
    }

    /*
     * turn additional parameters into recipient addresses if needed.
     */
    switch (operation_mode) {
    case VERIFY_ADDRS:
    case DELIVER_MAIL:
	process_recipients(FALSE);
	break;

    default:				/* to shut up gcc-2 -Wall */
	break;
    }

    /*
     * invoke the correct mode of operation
     */
    switch (operation_mode) {
    case TEST_MODE:			/* test addresses from stdin */
	test_addresses();		/* read addrs from stdin, for tests */
	break;

    case NOOP_MODE:			/* generally, this means run queue */
	noop_mode();
	break;

    case PRINT_QUEUE:			/* print the mail queue */
	print_queue();
	break;

    case VERIFY_ADDRS:			/* spit out resoved addresses */
	if (num_recipients == 0 && !extract_addresses) {
	    if (errfile) {
		(void) fprintf(errfile, "Usage: %s [flags] -bv (-t [!address ...] | address ...)\n",
			       program);
	    }
	    exitvalue = EX_USAGE;
	    break;
	}
	verify_addresses();
	break;

    case SMTP_MODE:			/* read interactive SMTP requests on stdin/stdout */
	smtp_mode(stdin, stdout, (void *) NULL);
	break;

    case BATCH_SMTP_MODE:		/* batched SMTP requests on stdin */
	smtp_mode(stdin, (FILE *) NULL, (void *) NULL);
	break;

    case DAEMON_MODE:			/* be a daemon waiting for requests */
	dont_deliver = FALSE;		/* it is far too dangerous to allow this */
	daemon_mode();
	break;

    case FREEZE_CONFIG:			/* freeze the configuration */
	if (errfile) {
	    (void) fprintf(errfile,
			   "%s: operation not currently supported\n",
			   program);
	}
	exitvalue = EX_UNAVAILABLE;
	break;

    case DELIVER_MAIL:			/* deliver to all addresses found */
	if (num_recipients == 0 && !extract_addresses) {
	    if (errfile) {
		(void) fprintf(errfile, "Usage: %s [flags] (-t [!address ...] | address ...)\n",
			       program);
	    }
	    exitvalue = EX_USAGE;
	    break;
	}

	perform_deliver_mail();
	break;

    case PATHTO_MODE:
	pathto(argc, argv);
	break;

    case UUPATH_MODE:
	uupath(argc, argv);
	break;

    case OPTTO_MODE:
	optto(argc, argv);
	break;

    case ROGUE_MODE:			/* print a rogue tombstone */
	silly();
	break;

    case COPYING_MODE:
	print_copying_file();
	break;

    case PRINT_VARS_MODE:
	print_variables();
	break;

    case TEST_DNSBL_MODE:
	test_dnsbls();
	break;

    case REBUILD_ALIASES: {
	int status;
	int lastarg;

	if (smail_util_dir == NULL) {
	    if (errfile) {
		fprintf(errfile, "%s: smail_util_dir attribute not set, -bi not supported\n", program);
	    }
	    exit(EX_UNAVAILABLE);
	}
#if !defined(HAVE_HASH_BANG)
# ifdef SHELL_EXEC_PATH
	utilargs[0] = SHELL_EXEC_PATH;
# else
	utilargs[0] = "/bin/sh";
# endif
	utilargs[1] = xprintf("%s/mkaliases", smail_util_dir);
	lastarg = 2;
#else  /* HAVE_HASH_BANG */
	utilargs[0] = xprintf("%s/mkaliases", smail_util_dir);
	utilargs[1] = NULL;
	lastarg = 1;
#endif
	utilargs[2] = NULL;
	utilargs[3] = NULL;
	utilargs[4] = NULL;
	if (debug) {
	    static char debug_arg[MAXINT_B10_DIGITS + 4];

	    sprintf(debug_arg, "-v %d", debug);
	    utilargs[lastarg++] = debug_arg;
	}
	if (arg_alias_file) {
	    utilargs[lastarg++] = arg_alias_file;
	}
	DEBUG4(DBG_MAIN_MID, "main: about to run %s %s %s %s\n",
	       utilargs[0],
	       utilargs[1] ? utilargs[1] : "",
	       utilargs[2] ? utilargs[2] : "",
	       utilargs[3] ? utilargs[3] : "");
	child = open_child(utilargs, (char **) NULL, (FILE **) NULL, (FILE **) NULL, fileno(stderr),
			   CHILD_MINENV, (uid_t) getuid(), (gid_t) getgid());
	if (child == -1) {
	    if (errfile) {
		fprintf(errfile, "%s: Cannot start %s: %s\n",
			program, utilargs[0], strerror(errno));
	    }
	    exit(EX_UNAVAILABLE);
	}
	if ((status = close_child((FILE *) NULL, (FILE *) NULL, child)) != 0) {
	    if (status == -1) {
		write_log(WRITE_LOG_SYS|WRITE_LOG_TTY, "%s: failed to reap child process %d from %s: %s.",
			  program, child, utilargs[0], strerror(errno));
		exit(EX_OSERR);
	    } else if (WIFEXITED(status)) {
		if (WEXITSTATUS(status) != 0) {
		    write_log(WRITE_LOG_SYS|WRITE_LOG_TTY, "%s: %s[%d] returned status %s (%d)",
			      program, utilargs[0], child,
			      strsysexit(WEXITSTATUS(status)), WEXITSTATUS(status));
		    exit(EX_DATAERR);
		}
	    } else if (WIFSIGNALED(status)) {
		char signm[SIG2STR_MAX];

		if (sig2str(WTERMSIG(status), signm) == -1) {
		    sprintf(signm, "#%d", WTERMSIG(status));
		}
		write_log(WRITE_LOG_SYS|WRITE_LOG_TTY, "%s: %s PID# [%ld] killed by signal SIG%s %s: %s",
			  program, utilargs[0], (long int) child,
			  signm, WCOREDUMP(status) ? "and dumped core" : "(no core)",
			  strsignal(WTERMSIG(status)));
		exit(EX_UNAVAILABLE);
	    } 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_TTY, "%s: %s PID# [%ld] stopped unexpectedly by signal SIG%s: %s",
			  program, utilargs[0], (long int) child,
			  signm, strsignal(WSTOPSIG(status)));
		exit(EX_UNAVAILABLE);
	    }
	}
	exit(0);
	/*NOTREACHED*/
    }
    default:
	if (errfile) {
	    (void) fprintf(errfile, "%s: option not supported\n", program);
	}
	exitvalue = EX_UNAVAILABLE;
    }

    /*
     * all done.
     */
    if (report_memory_usage) {
	if (errfile) {
	    unsigned long int current_sbrk0 =  (unsigned long) ((char *) sbrk((size_t /* intptr_t */) 0));

	    (void) fprintf(errfile, "%s: current sbrk(0)[%lu] - initial[%lu] = %ld\n",
			   program,
			   current_sbrk0,
			   startup_sbrk0,
			   current_sbrk0 - startup_sbrk0);
	}
    }
    exit(force_zero_exitvalue ? 0 : exitvalue);
    /* NOTREACHED */
}


/*
 * initialize_state - set some per-message parameters to their default values
 * 
 * NOTE:  This routine may be called at least twice in the life of any given
 * smail process, or many times for the likes of mailq.
 */
void
initialize_state()
{
    call_freeze_message = FALSE;
    dont_deliver = FALSE;
    do_aliasing = TRUE;
    extract_addresses = FALSE;
    error_sender = FALSE;
    islocal = FALSE;
    me_too = FALSE;
    return_to_sender = FALSE;
    send_to_postmaster = FALSE;
    some_deferred_addrs = FALSE;	/* may call unlink_spool() */

    error_processing = ERROR_DEFAULT;
    exitvalue = EX_OK;
    hop_count = -1;
    num_recipients = 0;

    parameters = NULL;			/* XXX free_addr_list()? */
    path_to_sender = NULL;		/* XXX xfree()? */
    recipients = NULL;			/* XXX free_addr_list()? */

    if (sender) {
	xfree(sender);
    }
    sender = NULL;
    if (sender_name) {
	xfree(sender_name);
    }
    sender_name = NULL;

    reset_hit_table();
}

/*
 * generate a new address hit table where case is ignored and all data resides
 * in memory
 */
void
reset_hit_table()
{
    if (hit_table_block) {
	free_block(hit_table_block);
    }
    hit_table_block = malloc_block();
    hit_table = new_hash_table(hit_table_len,
			       hit_table_block,
			       HASH_DEFAULT);
}


/*
 * process_args - process the arguments passed to the mailer
 *
 * In general use sendmail semantics, with different argument
 * processing based on name at invocation.
 */
void
process_args(args, restricted)
    register char **args;		/* vector of arguments */
    int restricted;			/* enforce rsmtp restrictions
					 * (i.e. handling command-line from
					 * main()) */
{
    struct addr *cur;			/* temp addr list entry */
    char *arg;				/* single string from args */
    int do_options = TRUE;		/* set TRUE until we see '--' */
    static char *end_arg = "";		/* trigger to swallow rest of arg */

#ifdef PROCESS_RECIPIENTS_USES_PROCESS_FIELD
    STR_INIT(&cmdline_addrs);
    STR_CAT(&cmdline_addrs, "CmdLine-Addrs: ");
#endif

    /*
     * go through the list of arguments in search of options and
     * addresses.
     */
    while ((arg = *args++)) {

	/* option arguments begin with '-', of course */
	if (arg[0] == '-' && do_options == TRUE) {

	    /* switch on each letter */
	    arg++;
	    while (*arg) switch (*arg++) {

	    case '-':			/* --: stop recognizing option flags */
		do_options = FALSE;
		if (arg[0]) {
		    if (errfile) {
			(void) fprintf(errfile, "%s: '--' option must stand alone.\n", program);
		    }
		    exit(EX_USAGE);
		}
		arg = end_arg;
		break;

	    case 'C':			/* -C: set config file name */
		if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
		    rmail_panic();
		}
		config_file = arg;
		arg = end_arg;		/* terminate args in current argv */
		/* if no string there, take next arg */
		if (config_file[0] == '\0') {
		    config_file = *args++;
		    panic_if_null(config_file, "C");
		}
		break;

	    case 'D':                   /* -D: set debugging output file */
		if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
		    rmail_panic();
		}
		arg_debug_file = arg;
		arg = end_arg;
		if (arg_debug_file[0] == '\0') {
		    arg_debug_file = *args++;
		    panic_if_null(arg_debug_file, "D");
		}
		if (debug == 0) {
		    debug = 1;
		}
		break;

	    case 'E':			/* -E: handle frozen error queue */
		scan_frozen = TRUE;
		break;

	    case 'I':			/* -I: use hidden-dot protocol on input */
		dot_usage = HIDDEN_DOTS;
		break;

	    /* XXX sendmail now uses '-N dsn' to set DSN condition */
	    case 'N':			/* -N: don't deliver message */
		dont_deliver = TRUE;
		break;

	    case 'Q':
		queue_only = TRUE;	/* -D: spool but do not deliver, yet */
		break;

	    case 'V':			/* -V: show version info */
		operation_mode = PRINT_VERSION;
		only_testing = TRUE;
		break;

	    case 'F':			/* -F: set full name of sender */
		if ((prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) && restricted) {
		    rmail_panic();
		}
		if (sender_name) {
		    xfree(sender_name);
		}
		sender_name = arg;
		arg = end_arg;		/* terminate args in current argv */
		/* if no string there, take next arg */
		if (sender_name[0] == '\0') {
		    sender_name = *args++;
		    panic_if_null(sender_name, "F");
		}
		sender_name = COPY_STRING(sender_name);
		break;

	    case 'b':			/* -b?: set operating mode */
		if (*arg == '\0') {
		    arg = *args++;
		    panic_if_null(arg, "b");
		}
		if (*arg != 'q' && operation_mode != MODE_DEFAULT) {
		    if (errfile) {
			dprintf(errfile, "%s: %v: '-b' suboption already specified!\n", program, arg);
		    }
		    exit(EX_USAGE);
		    /* NOTREACHED */
		}
		switch (*arg++) {

		case 'D':		/* -bD: test DNSBL */
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "bD"); /* XXX should we default to smtp_rbl_domains? */
		    }
		    deliver_mode = FOREGROUND;
		    operation_mode = TEST_DNSBL_MODE;
		    dnsbl_to_test = arg;
		    only_testing = TRUE;
		    break;

		case 'P':		/* -bP: print config settings */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    deliver_mode = FOREGROUND;
		    operation_mode = PRINT_VARS_MODE;
		    only_testing = TRUE;
		    break;

	        case 'R':		/* -bR: rogue tombstone mode */
		    operation_mode = ROGUE_MODE;
		    break;

		case 'S':		/* -bS: batched SMTP mode */
		    operation_mode = BATCH_SMTP_MODE;
		    break;

		case 'T':		/* -bT: header test mode, equiv. to '-bt -t' */
		    operation_mode = TEST_MODE;
		    extract_addresses = TRUE;
		    only_testing = TRUE;
		    break;

		case 'V':		/* -bV: equivalent to -V */
		    operation_mode = PRINT_VERSION;
		    only_testing = TRUE;
		    break;

		case 'c':		/* -bc: print COPYING file */
		    operation_mode = COPYING_MODE;
		    only_testing = TRUE;
		    break;

		case 'd':		/* -bd: operate as daemon */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    operation_mode = DAEMON_MODE;
		    break;

		case 'i':		/* -bi: initialize aliases database */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    operation_mode = REBUILD_ALIASES;
		    break;

		case 'm':		/* -bm: just deliver mail */
		    operation_mode = DELIVER_MAIL;
		    break;

		case 'p':		/* -bp: print the queue */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    operation_mode = PRINT_QUEUE;
		    only_testing = TRUE;
		    break;

		case 'q':		/* -bq: runq, same as '-q' with no param */
		    /* XXX assume operation_mode is MODE_DEFAULT or DAEMON_MODE */
		    process_queue = TRUE;
		    break;

		case 's':		/* -bs: process smtp on input */
		    operation_mode = SMTP_MODE;
		    break;

		case 't':		/* -bt: run in address test mode */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    operation_mode = TEST_MODE;
		    only_testing = TRUE;
		    break;

		case 'v':		/* -bv: verify addresses only */
		    operation_mode = VERIFY_ADDRS;
		    only_testing = TRUE;
		    break;

		case 'z':		/* -bz: freeze config file */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    operation_mode = FREEZE_CONFIG;
		    break;

		default:
		    if (errfile) {
			dprintf(errfile, "%s: %v: invalid '-b' suboption, see smail(8)\n", program, --arg);
		    }
		    exit(EX_USAGE);
		    /* NOTREACHED */
		}
		arg = end_arg;
		break;

	    case 'd':			/* -d: set debug level */
	    case 'v':			/* -v: verbose, currently same as debug */
		if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
		    rmail_panic();
		}
		if (arg[0]) {
		    char *errptr = NULL;

		    debug = (int)c_atol(arg, &errptr);
		    if (errptr || debug < 0) {
			if (errfile) {
			    (void) fprintf(errfile,
			"%s: -%c flag takes an optional non-negative number\n",
					   program, arg[-1]);
			}
			exit(EX_USAGE);
		    }
		    arg = end_arg;
		} else {
		    debug = 1;		/* if no number, default to 1 */
		}
		break;

	    case 'e':			/* -e?: what to do on errors */
		if (arg[0] == '\0') {
		    arg = *args++;
		    panic_if_null(arg, "e");
		}
		switch (*arg) {
		case 'e':		/* -ee: we don't support berkenet */
		case 'm':		/* -em: mail back errors */
		    error_processing = MAIL_BACK;
		    break;
		case 'p':		/* -ep: print errors on screen */
		    error_processing = TERMINAL;
		    break;
		case 'q':		/* -eq: be quiet about errors */
		    error_processing = DEV_NULL;
		    break;
		case 'w':		/* -ew: send via "write" */
		    error_processing = WRITE_BACK;
		    break;
		default:
		    if (errfile) {
			dprintf(errfile, "%s: %v: invalid '-e' suboption, see smail(8)\n", program, arg);
		    }
		    exit(EX_USAGE);
		    /* NOTREACHED */
		}
		arg = end_arg;		/* swallows complete argument */
		break;

	    case 'r':			/* -r: set path to sender */
		/* SCO Execmail '-r' option not applicable to smail */
		if (prog_type == PROG_EXECMAIL) {
		    break;
		}
		/* FALLTHRU */

	    case 'f':			/* -f: set path to sender */
		if ((prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) && restricted) {
		    rmail_panic();
		}
		if (sender) {
		    xfree(sender);
		}
		sender = arg;
		arg = end_arg;		/* terminate args in current argv */
		/* if no string there, take the next arg */
		if (sender[0] == '\0') {
		    sender = *args++;
		    panic_if_null(sender, "f");
		}
		sender = COPY_STRING(sender);
		break;

	    case 'h':			/* -h: hopcount, number is the count */
	        {
		    char *errptr = NULL;

		    if (arg[0]) {
			hop_count = (int)c_atol(arg, &errptr);
			arg = end_arg;
		    } else {
			if (*args)
			    hop_count = atoi(*args++);
			else
			    hop_count = -1;
		    }
		    if (errptr || hop_count < 0) {
			if (errfile) {
			    (void) fprintf(errfile,
				   "%s: -h flag takes a non-negative number\n",
					   program);
			}
			exit(EX_USAGE);
		    }
		}
		break;

	    case 'i':			/* -i: don't treat dots specially */
		dot_usage = NO_DOT_PROTOCOL;
		break;

	    case 'm':			/* -m: author can be included in alias */
		me_too = TRUE;
		break;

	    case 'n':			/* -n: don't do aliasing */
		if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
		    rmail_panic();
		}
		do_aliasing = FALSE;
		break;

	    case 'o':			/* -o?[?]: set various options */
		if (arg[0] == '\0') {
		    arg = *args++;
		    panic_if_null(arg, "o");
		}
		switch (*arg++) {
		case 'A':               /* -oA: name of aliases file */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oA");
		    }
		    arg_alias_file = arg;
		    break;

		case 'C':		/* -oC: name of config file */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oC");
		    }
		    config_file = arg;
		    break;

		case 'D':		/* -oD: name of director file */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {

			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oD");
		    }
		    arg_director_file = arg;
		    break;

		case 'E':		/* -oE: name of retry file */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oE");
		    }
		    arg_retry_file = arg;
		    break;

		case 'G':		/* -oG: runq grades */
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oE");
		    }
		    arg_runq_grades = arg;
		    break;

		case 'I':		/* -oI: same as -I */
		    dot_usage = HIDDEN_DOTS;
		    break;

		case 'L':		/* -oL: name of main "lib" dir */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oL");
		    }
		    arg_smail_lib_dir = arg;
		    break;

		case 'M':		/* -oM?: various message queue parameters */
		    if ((prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) && restricted) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oM");
		    }
		    switch (*arg++) {

		    case 'I':		/* -oMI */
			peer_is_localhost = TRUE;
			break;

		    case 'L':		/* -oML */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oML");
			}
			smtp_local_port = arg;
			break;

		    case 'N':		/* -oMN */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMN");
			}
			sender_host_really_invalid = arg;
			break;

		    case 'P':		/* -oMP */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMP");
			}
			sender_program = arg;
			break;

		    case 'R':		/* -oMR */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMR");
			}
			sender_host_really = arg;
			break;

		    case 'a':		/* -oMa */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMa");
			}
			sender_host_addr = arg;
			break;

		    case 'l':		/* -oMl (XXX -oMi in Exim) */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMl");
			}
			smtp_local_addr = arg;
			break;

		    case 'n':		/* -oMn */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMn");
			}
			sender_host_invalid = arg;
			break;

		    case 'p':		/* -oMp */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMp");
			}
			sender_host_port = arg;
			break;

		    case 'r':		/* -oMr */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMr");
			}
			sender_proto = arg;
			break;

		    case 's':		/* -oMs */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMs");
			}
			sender_host = arg;
			break;

		    case 'u':		/* -oMu  (XXX -oMt in Exim) */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMu");
			}
			ident_sender = arg;
			break;

		    case 'v':		/* -oMv */
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMv");
			}
			ident_method = arg;
			break;

		    default:
			if (errfile) {
			    dprintf(errfile, "%s: %v: invalid '-oM' suboption, see smail(8)\n", program, --arg);
			}
			exit(EX_USAGE);
			/* NOTREACHED */
		    }
		    break;

		case 'Q':		/* -oQ: name of qualify file */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oQ");
		    }
		    arg_qualify_file = arg;
		    break;

		case 'R':		/* -oR: name of router file */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oR");
		    }
		    arg_router_file = arg;
		    break;

		case 'S':		/* -oS: name of secondary config file */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oS");
		    }
		    arg_second_config_file = arg;
		    break;

		case 'T':		/* -oT: name of transport file */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oT");
		    }
		    arg_transport_file = arg;
		    break;


		case 'U':		/* -OU: smail util directory */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oU");
		    }
		    arg_smail_util_dir = arg;
		    break;

		case 'V':		/* -oV: */
		    report_memory_usage = TRUE;
		    break;

		case 'X':		/* -oX: smtp_service_name */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oX");
		    }
		    smtp_service_name = arg;
		    break;

		case 'd':		/* -od?: delivery mode */
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "od");
		    }
		    switch (*arg) {

		    case 'b':		/* -odb */
			deliver_mode = BACKGROUND;
			break;
		    case 'f':		/* -odf: foreground delivery */
		    case 'i':		/* -odi: sendmail "interactive delivery" */
			deliver_mode = FOREGROUND;
			break;
		    case 'd':		/* -odd: sendmail "defer map lookups as well as queue" */
		    case 'q':		/* -odq: queue message (almost like -Q) */
			deliver_mode = QUEUE_MESSAGE;
			break;
		    default:
			if (errfile) {
			    dprintf(errfile, "%s: %v: invalid '-od' suboption, see smail(8)\n", program, arg);
			}
			exit(EX_USAGE);
			/* NOTREACHED */
		    }
		    break;

		case 'e':		/* -oe?: same as -e? */
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oe");
		    }
		    switch (*arg) {
		    case 'e':		/* -oee: ancient BERKENET processing (we just do -oem) */
		    case 'm':		/* -oem: mail error meesages back to sender */
			error_processing = MAIL_BACK;
			break;
		    case 'w':		/* -oew: write errors to user's terminal */
			/* not currently implemented -- same as -oem */
			error_processing = WRITE_BACK;
			break;
		    case 'p':		/* -oep: "print" errors to stderr */
			error_processing = TERMINAL;
			break;
		    case 'q':		/* -oeq: quite, no errors, just status */
			error_processing = DEV_NULL;
			break;
		    default:
			if (errfile) {
			    dprintf(errfile, "%s: %v: invalid '-oe' suboption, see smai(8)\n", program, arg);
			}
			exit(EX_USAGE);
			/* NOTREACHED */
		    }
		    break;

		case 'i':		/* -oi: same as -i */
		    dot_usage = NO_DOT_PROTOCOL;
		    break;

		case 'm':		/* -om: same as -m */
		    me_too = TRUE;
		    break;

		case 'r':		/* -or: sendmail "read timeout" */
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "or");
		    }
#ifdef notyet
		    read_message_timeout = xivaltou(arg);
#endif
		    break;

		default:
		    if (errfile) {
			dprintf(errfile, "%s: %v: invalid '-o' suboption, see smail(8)\n", program, --arg);
		    }
#ifdef NO_SENDMAIL_COMPATABILITY
		    exit(EX_USAGE);
		    /* NOTREACHED */
#endif
		}
		arg = end_arg;
		break;

	    case 'q':			/* -q: check queue (at interval) */
		if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
		    rmail_panic();
		}
		process_queue = TRUE;
		if (arg[0]) {
		    queue_interval =  xivaltou(arg);
		    arg = end_arg;	/* uses rest of argument */
		}
		break;

	    case 's':			/* -s: mailq summary */
		mailq_summary_only = TRUE;
		break;

	    case 't':			/* -t: take recipients from message */
		extract_addresses = TRUE;
		break;

	    case 'x':			/* -x?: special internal hacks */
		if ((prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) && restricted) {
		    rmail_panic();
		}
		if (arg[0] == '\0') {
		    arg = *args++;
		    panic_if_null(arg, "x");
		}
		switch (*arg++) {
		case 'D':		/* -xD: convert interval to days only, for checkerr.sh */
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "xD");
		    }
		    printf("%u\n", xivaltou(arg) / (60*60*24));
		    exit(EX_OK);
		    /* NOTREACHED */
		case 'S':		/* -xS: convert interval to seconds only, for checkerr.sh */
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "xS");
		    }
		    printf("%u\n", xivaltou(arg));
		    exit(EX_OK);
		    /* NOTREACHED */
		default:
		    if (errfile) {
			dprintf(errfile, "%s: %v: invalid '-x' suboption, see smail(8)\n", program, --arg);
		    }
		    exit(EX_USAGE);
		    /* NOTREACHED */
		}
		arg = end_arg;		/* swallows complete argument */
		break;

	    default:
		if (errfile) {
		    dprintf(errfile, "%s: -%v: invalid option flag, see smail(8)\n", program, --arg);
		}
		exit(EX_USAGE);
		/* NOTREACHED */
	    }
	} else {
	    /*
	     * arg is not an option flag, so it must be a recipient address
	     */
#ifdef PROCESS_RECIPIENTS_USES_PROCESS_FIELD
	    /* append it to our fake "to" field, preceding with a comma
	     * separator if not the first recipient and if the first one did
	     * not already end with its own comma...
	     */
	    if (num_recipients != 0 && STR(&cmdline_addrs)[STR_LEN(&cmdline_addrs) - 1] != ',') {
		STR_CAT(&cmdline_addrs, ", ");
	    }
	    STR_CAT(&cmdline_addrs, escape_newline(arg));
#endif
	    /*
	     * fake up a list of the raw parameters for use by print_queue(), etc.
	     */
	    cur = alloc_addr();
	    cur->in_addr = escape_newline(arg);
	    cur->succ = parameters;
	    parameters = cur;
	    
	    num_recipients++;
	}
    }
#ifdef PROCESS_RECIPIENTS_USES_PROCESS_FIELD
    STR_NEXT(&cmdline_addrs, '\0');
#endif

    if (scan_frozen && operation_mode != PRINT_QUEUE) {
	if (errfile) {
	    (void) fprintf(errfile, "%s: cannot process frozen message (error) queue.  (try `unfreezemail')\n", program);
	}
	exit(EX_USAGE);
	/* NOTREACHED */
    }

    return;
}

/*
 * check_sender -- check that the address in the global `sender' variable is of
 * valid syntax and form, and that if it's not been read from a spool file that
 * the invoking user is permitted to use that sender address.
 *
 * Note that if the parameter was read from a spool file then the invoking
 * user's credentials (i.e. the ones used here, real_uid & prog_euid) have
 * already been overwitten by those stored in the spool file.
 *
 * For now everyone is allowed to use a plain null return path, but only
 * trusted_users are allowed to use the magic internal "<+>" form.
 *
 * XXX for command-line addresses, even from trusted users, we really need to
 * do the equivalent of verify_sender() -- it needs to be refactored, gain a
 * new API, and be moved into someplace more appropriate, so that the same
 * logic can be used here and in smtprecv.c
 */
void
check_sender(from_spool)
    int from_spool;			/* is sender from the spoolfile args? */
{
    char *newsender;
    char *remainder;			/* newsender's local-part, or an error message */
    char *error;

    DEBUG2(DBG_MAIN_MID, "check_sender(): checking %s sender address: '%v'\n",
	   from_spool ? "spool" : "command-line",
	   sender);

    if (EQ(sender, "<>")) {
	error_sender = TRUE;
	islocal = FALSE;		/* assumptions... */
	return;
    }
    if (EQ(sender, "<+>")) {
	if (!from_spool && !trusted_invoker()) {
	    write_log(WRITE_LOG_TTY | (from_spool ? WRITE_LOG_SYS : 0),
		      "%s: special sender address '%v' not permitted on command-line by an untrusted user (%s)",
		      program, sender, local_sender);
	    exit(EX_USAGE);
	}
	error_sender = TRUE;
	islocal = TRUE;
	return;
    }

    /*
     * preparse_address() is destructive but we want it to be to strip
     * unnecessary angle brackets, etc.
     */
    newsender = preparse_address(sender, &error);
    if (! newsender) {
	write_log(WRITE_LOG_TTY | (from_spool ? WRITE_LOG_SYS : 0),
		  "validation error in sender address '%v' (rewriting to 'postmaster'): %s",
		  sender, error);
	if (!from_spool) {
	    exit(EX_USAGE);
	}
	/*
	 * This is a rather horrible thing to do, but the alternative is to
	 * freeze the message in the error queue after which it would likely
	 * require manual editing to correct the error.
	 *
	 * Of course it _should_ be impossible to get a bogus sender address
	 * into a spool file in the first place.
	 */
	if (sender) {
	    xfree(sender);
	}
	sender = xprintf("postmaster", sender);
	islocal = FALSE;		/* assumptions... */
    } else {
	char *target;
	int sender_type;
	char *tmpaddr;

	if (sender) {
	    xfree(sender);
	}
	sender = newsender;		/* choose preparse result by default */
	tmpaddr = COPY_STRING(sender);	/* parse_address() is destructive */
	sender_type = parse_address(tmpaddr, &target, &remainder, (int *) NULL);
	switch (sender_type) {
	case FAIL:
	    write_log(WRITE_LOG_TTY | (from_spool ? WRITE_LOG_SYS : 0),
		      "syntax error in sender address '%v' (rewriting to 'postmaster'): %v",
		      sender, remainder);
	    if (!from_spool) {
		exit(EX_USAGE);
	    }
	    /*
	     * This is a rather horrible thing to do, but the alternative is to
	     * freeze the message in the error queue after which it would
	     * likely require manual editing to correct the error.
	     *
	     * Of course it _should_ be impossible to get a syntactically
	     * incorrect sender address into a spool file in the first place.
	     */
	    if (sender) {
		xfree(sender);
	    }
	    sender = COPY_STRING("postmaster");
	    islocal = FALSE;		/* assumptions... */
	    break;
	case LOCAL:
	    islocal = TRUE;
	    if (!from_spool) {
		/* the next call may not return... */
		check_sender_perms(sender, remainder);
	    }
	    break;
	case MAILBOX:
	case UUCP_ROUTE:
	    /*
	     * trusted users must be allowed to set non-local addresses,
	     * e.g. for the UUCP rmail interface; and as well non-local sender
	     * addresses given by remote clients and already placed in the
	     * queue via the likes of SMTP must also be allowed.
	     */
	    islocal = islocalhost(target);
	    if (!from_spool && !trusted_invoker()) {
		if (!islocal) {
		    if (errfile) {
			dprintf(errfile,
				"%s: sender address '%v' is not a local address!\n",
				program, sender);
		    }
		    exit(EX_USAGE);
		}
		/* the next call may not return... */
		check_sender_perms(sender, remainder);
	    }
	    break;
	default:
	    write_log(WRITE_LOG_TTY | (from_spool ? WRITE_LOG_SYS : 0),
		      "%s: sender address '%v' has invalid form",
		      program, sender);
	    if (!from_spool) {
		exit(EX_USAGE);
	    }
	    /*
	     * XXX should we add to our horrible behaviour and re-write it, or
	     * should we freeze this message for manual fixing?  Is leaving it
	     * alone not the worst possible course of action?
	     *
	     * Of course it _should_ be impossible to get such a bogus sender
	     * address into a spool file in the first place!
	     */
	    islocal = FALSE;		/* assumptions... */
	    break;
	}
	xfree(tmpaddr);
    }
}

/*
 * check_sender_perms -- Check to see if the user running smail has permission
 * to set the given sender address, and exit after complaining if not.
 *
 * (must only be called if !from_spool)
 */
static void
check_sender_perms(address, username)
    char *address;			/* pre-parsed address, only used in errors */
    char *username;			/* username parsed from address */
{
    if (EQ(local_sender, username)) {
	DEBUG2(DBG_MAIN_HI, "user %s specified his own sender address: <%s>\n", local_sender, address);
	return;
    }

    /*
     * only allow users with real_uid in trusted_users, or prog_egid in
     * trusted_groups, to specify anyone other than themselves!!!
     */
    if (!trusted_invoker()) {
	if (errfile) {
	    dprintf(errfile,
		    "sender address <%v> cannot be used by %s (UID=%u, GID=%u)\n",
		    address,
		    local_sender,
		    (unsigned int) real_uid,
		    (unsigned int) real_gid);
	}
	exit(EX_NOPERM);
    } else {
	DEBUG2(DBG_MAIN_HI, "Trusted user '%s' specified possibly non-local sender address: <%s>\n", username, address);
    }

    return;
}

/*
 * process_recipients -- converts cmdline_addrs into recipients
 *
 * Maybe it would make for better context-specific error handling and reporting
 * if this routing could return an error....
 */
void
process_recipients(from_spool)
    int from_spool;			/* are cmdline_addrs from the spoolfile args? */
{
#ifdef PROCESS_RECIPIENTS_USES_PROCESS_FIELD
    char *clean;			/* result of process_field() */
    int second_count = 0;		/* should match num_recipients */
    struct addr *cur;
    char *error = NULL;

    DEBUG1(DBG_MAIN_MID, "process_recipients() called to convert %s cmdline_addrs into recipients...\n",
	   from_spool ? "spool file" : "user supplied");

    clean = process_field(STR(&cmdline_addrs),
			  strchr(STR(&cmdline_addrs), ':') + 1,
			  visible_name,
			  uucp_name,
			  &recipients,
			  (operation_mode == VERIFY_ADDRS ||
			   operation_mode == DELIVER_MAIL) ? (F_LOCAL | F_STRICT) : 0,
			  &error);
    DEBUG3(DBG_MAIN_HI, "process_recipients(%s): process_field(%v)\n\treturns -> '%v'\n",
	   from_spool ? "spool file" : "user supplied",
	   STR(&cmdline_addrs), clean);
    if (error) {
	/* we want logfile entries too so panic() won't work... */
	write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC, "ERROR: invalid %s command line parameters: %s\n",
		  from_spool ? "spool file" : "user supplied",
		  error);
	if (from_spool) {
	    freeze_message();
	    close_spool();
	}
	exit(EX_USAGE);
	/* NOTREACHED */
    }
    for (cur = recipients; cur; cur = cur->succ) {
	cur->uid = nobody_uid;
	cur->gid = nobody_gid;
	second_count++;
    }
    /* make sure process_field() found exactly the expected number of addrs */
    if (second_count != num_recipients) {
	/* we want logfile entries too so panic() won't work... */
	write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC,
		  "ERROR: found %s recipients (%d) than %s command line parameters (%d)!\n",
		  num_recipients < second_count ? "more" : "fewer",
		  second_count,
		  from_spool ? "spool file" : "user supplied",
		  num_recipients);
	if (from_spool) {
	    freeze_message();
	    close_spool();
	}
	exit(EX_USAGE);
	/* NOTREACHED */
    }

#else  /* !PROCESS_RECIPIENTS_USES_PROCESS_FIELD */

    struct addr *cur;
    char *error = NULL;

    DEBUG1(DBG_MAIN_MID, "process_recipients() called to convert %s command-line parameters into recipients...\n",
	   from_spool ? "spool file" : "user supplied");

    for (cur = parameters; cur; cur = cur->succ) {
	struct addr *new;
	int form;

	new = alloc_addr();
	new->in_addr = COPY_STRING(cur->in_addr);
	new->work_addr = preparse_address(new->in_addr, &error);
	if (!new->work_addr) {
	    free_addr(new);
	    break;
	}
	form = parse_address(new->work_addr, &new->target,
			     &new->remainder, &new->parseflags);
	if (form == FAIL) {
	    error = COPY_STRING(new->remainder);
	    free_addr(new);
	    break;
	}
	new->uid = nobody_uid;      /* may be reset in compute_nobody() */
	new->gid = nobody_gid;      /* may be reset in compute_nobody() */
	new->succ = recipients;
	recipients = new;
    }

    if (error) {
	/* we want logfile entries too so panic() won't work... */
	write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC, "ERROR: invalid %s command line parameters: %s\n",
		  from_spool ? "spool file" : "user supplied",
		  error);
	if (from_spool) {
	    freeze_message();
	    close_spool();
	}
	exit(EX_USAGE);
	/* NOTREACHED */
    }
#endif

    return;
}

static unsigned int
xivaltou(arg)
    char *arg;
{
    long l = ivaltol(arg);

    if (l < 0) {
	if (errfile) {
	    dprintf(errfile, "%s: %v: invalid interval (too large?)\n", program, arg);
	}
	exit(EX_USAGE);
    }
    if ((unsigned int) l > UINT_MAX) { /* if sizeof(long) != sizeof(unsigned int) */
	if (errfile) {
	    dprintf(errfile, "%s: %v: interval is too large\n", program, arg);
	}
	exit(EX_USAGE);
    }

    return (unsigned int) l;
}

static char *
escape_newline(s)
    register char *s;
{
    struct str str;
    register struct str *sp = &str;
    register int c;

    if (strchr(s, '\n') == NULL) {
	return s;
    }
    STR_INIT(sp);			/* always start with fresh storage */

    while ((c = *s++)) {
	if (c == '\n') {
	    STR_CAT(sp, "\\n");
	} else {
	    STR_NEXT(sp, c);
	}
    }
    STR_NEXT(sp, '\0');
    STR_DONE(sp);

    return STR(sp);
}

/*
 * panic_if_null - complain with a usage message if the given pointer is NULL
 */
static void
panic_if_null(p, fl)
    char *p;
    char *fl;				/* name of flag to give usage for */
{
    if (p == NULL) {
	if (errfile) {
	    (void) fprintf(errfile, "%s: argument expected after -%s\n",
			   program, fl);
	}
	exit(EX_USAGE);
    }
}

/*
 * rmail_panic - complain about an option not allowed with rmail or rsmtp
 */
static void
rmail_panic()
{
    if (errfile) {
	(void) fprintf(errfile,
		       "%s: usage with rmail and rsmtp is restricted\n",
		       program);
    }
    exit(EX_USAGE);
}

/*
 * parse_grade_range - parse a delivery range into a min & max value
 * No error checking/reporting.
 * Code mostly stolen from read_methods_file() in src/route.c
 */
static void 
parse_grade_range(range, min, max)
     char * range;
     int * min;
     int * max;
{
    int mn, mx;

    mn = 0;
    mx = 255;
    if (range != NULL) {
	if (isalnum((int) *range)) {
	    mn = *range++;
	    mx = mn;
	}
	if (*range == '-') {
	    range++;
	    mx = 255;
	    if (isalnum((int) *range)) {
		mx = *range;
	    }
	}
    }

    /* Make sure that min <= max - make life easier! */
    if (mx >= mn) {
	*min = mn;
	*max = mx;
    } else {
	*min = mx;
	*max = mn;
    }
}

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