/*
#ident	"@(#)smail/src:RELEASE-3_2_0_114:main.c,v 1.70 2001/07/29 16:10:48 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 <stdio.h>
#include <ctype.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <pwd.h>
#include <signal.h>
#define NEED_SOCKETS			/* Required for _PATH_RESCONF from <resolv.h> */
#include "defs.h"
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#if defined(POSIX_OS) || !defined(UNIX_BSD)
# include <time.h>
#else
# include <sys/time.h>
#endif	/* UNIX_BSD */
#if defined(HAVE_RLIMIT)
# include <sys/resource.h>
#endif	/* HAVE_RLIMIT */
#ifdef	UNIX_AIX3
# include <sys/id.h>
#endif	/* UNIX_AIX3 */
#if defined(POSIX_OS)
# include <unistd.h>
#else /* not POSIX_OS */
# if defined (HAVE_UNISTD_H)
#  include <unistd.h>
# endif
#endif	/* not POSIX_OS */
#include "config.h"
#include "smail.h"
#include "dys.h"
#include "parse.h"
#include "addr.h"
#include "hash.h"
#include "main.h"
#include "log.h"
#include "direct.h"
#include "route.h"
#include "transport.h"
#include "child.h"
#include "exitcodes.h"
#include "smailconf.h"
#include "alloc.h"
#include "extern.h"
#include "debug.h"
#include "error.h"

#ifdef HAVE_BSD_NETWORKING
# ifndef OBSOLETE_RESOLVER
extern int h_errno;			/* import h_errno; many systems don't define it in <netdb.h> */
# endif
# ifdef HAVE_BIND
#  ifndef _PATH_RESCONF
#   define _PATH_RESCONF	"/etc/resolv.conf"
#  endif
# endif
#endif

/* exported variables */
int islocal;				/* TRUE if mail originated locally */
int exitvalue;				/* call exit with this value */
char *program;				/* argv[0] from main */
char *sender;				/* sender of message */
char *local_sender;			/* local sender of message */
int error_sender;			/* TRUE if special sender <> given */
char *sender_name;			/* full name of sender */
enum op_mode operation_mode;		/* mode of operation */
int debug = 0;				/* debugging level, 0 is off */
int dont_deliver = FALSE;		/* if TRUE, don't actually deliver */
int process_queue;			/* process spooled files */
unsigned queue_interval;		/* process queues at this interval */
int hop_count;				/* hop count so far for message */
int do_aliasing;			/* do aliasing for local addresses */
int extract_addresses;			/* get recipients from header */
int me_too;				/* sender allowed in aliases */
enum er_proc error_processing;		/* method of displaying errors */
enum dot_usage dot_usage;		/* how do we treat . on input */
enum deliver_mode
    deliver_mode = DELIVER_DEFAULT;	/* foreground, background or queued */
struct addr *recipients;		/* list of recipient addresses */
int num_recipients = 0;			/* number of entries in 'recipients' */
char *primary_name;			/* primary local name from hostnames */
FILE *errfile = NULL;			/* file to write debug messages to */
unsigned int real_uid;			/* saved real uid before ruid setup */
unsigned int real_gid;			/* saved real gid before rgid setup */
enum prog_type prog_type;		/* type of program we are running as */
char **save_argv;			/* saved pointer to arguments */
int some_deferred_addrs;		/* don't unlink spool file */
					/* as some addrs were deferred */
unsigned int prog_euid;			/* effective uid of program */
unsigned int prog_egid;			/* effective gid of program */
int force_zero_exitvalue = FALSE;	/* if TRUE always exit with status 0 */
int call_freeze_message;		/* if TRUE must call freeze_message() */
int sender_is_trusted;			/* TRUE if sender is a trusted user */
char *sender_host = NULL;		/* name of sender's host */
char *sender_host_addr = NULL;		/* inet address 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 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 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_debug_file = NULL;	/* debug file, defaulting to stderr */
static char *arg_runq_grades = NULL;	/* which grades are processed by runq */

/*
 * 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;

    MALLOC_DEBUG(MALLOC_DEBUG_LEVEL);

    save_argv = argv;

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

    /* initialize per-message state information */
    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 */

#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, (gid_t *) NULL) != 0) {
	    if (setgroups(1, dummy_grouplist) != 0) {
		write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_PANIC : 0),
			  "setgroups() failed: %s", strerror(errno));
	    }
	}
    }
#endif	/* HAVE_SETGROUPS */

    /* get rid of any limits 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 file size, and 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.
     * It also might make more sense to hold off until we've loaded config
     * files too.
     */
    {
	struct rlimit rl;

	rl.rlim_cur = RLIM_INFINITY;
	rl.rlim_max = RLIM_INFINITY;
	(void) setrlimit(RLIMIT_CPU, &rl);
	(void) setrlimit(RLIMIT_FSIZE, &rl);
#if defined(DATA_RLIMIT)			/* usually in conf/os/OS_TYPE */
	rl.rlim_cur = DATA_RLIMIT;
#elif defined (SMALL_MEMORY)
	rl.rlim_cur = (4 * 1024 * 1024);
#else
	rl.rlim_cur = (64 * 1024 * 1024);	/* generous, aren't we! */
#endif	/* DATA_RLIMIT */
	(void) setrlimit(RLIMIT_DATA, &rl);
#if defined(STACK_RLIMIT)			/* usually in conf/os/OS_TYPE */
	rl.rlim_cur = STACK_RLIMIT;
#elif defined (SMALL_MEMORY)
	rl.rlim_cur = (1024 * 1024);
#else
	rl.rlim_cur = (8 * 1024 * 1024);	/* generous, aren't we! */
#endif	/* STACK_RLIMIT */
	(void) setrlimit(RLIMIT_STACK, &rl);
    }
#endif	/* HAVE_RLIMIT */

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

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

    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)));
    /* 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 (error == NULL && second_config_file) {
	error = read_config_file(second_config_file);
    }
    /* 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);

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

    if (error) {
	/*
	 * 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_bufsiz = 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 PRINT_VERSION:
	case SMTP_MODE:
	case BATCH_SMTP_MODE:
	case DELIVER_MAIL:
	case PRINT_VARS_MODE:
	case REBUILD_ALIASES:
	case PATHTO_MODE:
	case UUPATH_MODE:
	case OPTTO_MODE:
	    write_log(WRITE_LOG_TTY|WRITE_LOG_PANIC, "%s", error);
	    break;

	default:
	    write_log(WRITE_LOG_TTY|WRITE_LOG_PANIC, "%s", error);
	    exit(EX_OSFILE);
	    /*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 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;
    }

    /*
     * Save away the real uid and set the real to the effective.
     * After this point, the real uid is 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
     * sends mail.
     */
    real_uid = (int) getuid();
    real_gid = (int) getgid();
    prog_euid = (int) geteuid();		/* keep a copy of the effictive id's */
    prog_egid = (int) getegid();
    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

    /*
     * 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 %s: %s\n",
		      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)getpid());
	}
    }

    /*
     * error processing can be other than TERMINAL only for
     * mail delivery modes
     */
    switch (operation_mode) {
    case DELIVER_MAIL:
    case NOOP_MODE:
    case DAEMON_MODE:
    case SMTP_MODE:
    case BATCH_SMTP_MODE:
	if (error_processing == ERROR_DEFAULT) {
	    error_processing = MAIL_BACK;
	}
	break;

    default:
	error_processing = TERMINAL;
	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 && recipients && queue_interval) {
	if (errfile) {
	    fprintf(errfile,
		    "%s: cannot have queue run interval and list of messages to process.\n",
		    program);
	}
	exit(EX_USAGE);
    }

    /*
     * 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 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 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 == NULL && deliver_mode == BACKGROUND)
	deliver_mode = FOREGROUND;

    /*
     * 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 PRINT_VERSION:
	print_version();
	break;

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

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

    case BATCH_SMTP_MODE:		/* batched SMTP requests on stdin */
	smtp_mode(stdin, (FILE *)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 (recipients == NULL && !extract_addresses) {
	    if (errfile) {
		(void) fprintf(errfile, "Usage: %s [flags] 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 REBUILD_ALIASES: {
	int status;

	if (smail_util_dir == NULL) {
	    if (errfile) {
		fprintf(errfile, "%s: smail_util_dir attribute not set, -bi not supported\n", program);
	    }
	    exit(EX_UNAVAILABLE);
	}
#ifdef SHELL_EXEC_PATH
	utilargs[0] = SHELL_EXEC_PATH;
#else
	utilargs[0] = "/bin/sh";
#endif
	utilargs[1] = xprintf("%s/mkaliases", smail_util_dir);
	utilargs[2] = NULL;
	utilargs[3] = NULL;
	utilargs[4] = NULL;
	if (debug) {
	    static char debug_arg[30];	/* space for max int digits (2^(CHAR_BIT * sizeof(int)) = 10^digits) */

	    sprintf(debug_arg, "-v %d", debug);
	    utilargs[2] = debug_arg;
	}
	if (arg_alias_file) {
	    utilargs[utilargs[2] ? 3 : 2] = arg_alias_file;
	}
	DEBUG4(DBG_MAIN_MID, "main: about to run %s %s %s %s\n",
	       utilargs[0], utilargs[1], utilargs[2] ? utilargs[2] : "", utilargs[3] ? utilargs[3] : "");
	child = open_child(utilargs, (char **) NULL, (FILE **) NULL, (FILE **) NULL, fileno(stderr),
			   CHILD_MINENV, (unsigned int) getuid(), (unsigned int) 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 from %s: %s.",
			  program, utilargs[0], strerror(errno));
		exit(EX_OSERR);
	    } else if (status & 0xff) {
		char *errstr = xprintf("%s: %s killed by signal %d",
				       program, utilargs[0], status & 0x7f);
		write_log(WRITE_LOG_SYS|WRITE_LOG_TTY, errstr);
		exit(EX_UNAVAILABLE);
	    } else {
		char *errstr = xprintf("%s: %s returned status %s (%d)",
				       program, utilargs[0], strsysexit(status>>8), status>>8);
		write_log(WRITE_LOG_SYS|WRITE_LOG_TTY, errstr);
		exit(EX_DATAERR);
	    }
	}
	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) {
	    (void) fprintf(errfile, "%s: sbrk(0) = %ld\n",
			   program, (long)sbrk(0));
	}
    }
    if (force_zero_exitvalue) {
	exit(0);
    }
    exit(exitvalue);
    /* NOTREACHED */
}


/*
 * initialize_state - set some parameters to their default value
 * 
 * NOTE:  This routine may be called at least twice in the life of any given
 * smail process.
 */
void
initialize_state()
{
    send_to_postmaster = FALSE;
    return_to_sender = FALSE;
    islocal = FALSE;
    exitvalue = EX_OK;
    sender = NULL;
    error_sender = FALSE;
    path_to_sender = NULL;
    sender_name = NULL;
    operation_mode = MODE_DEFAULT;
    process_queue = FALSE;
    queue_interval = 0;
    hop_count = -1;
    do_aliasing = TRUE;
    extract_addresses = FALSE;
    dot_usage = DOT_ENDS_MESSAGE;
    me_too = FALSE;
    error_processing = ERROR_DEFAULT;
    recipients = NULL;
    num_recipients = 0;
    some_deferred_addrs = FALSE;
    call_freeze_message = FALSE;
    sender_is_trusted = TRUE;
    dont_deliver = FALSE;

    reset_hit_table();

    /* figure out the initial nobody uid/gid off the top */
    compute_nobody();
}

/*
 * 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, restrict)
    register char **args;		/* vector of arguments */
    int restrict;                       /* enforce rsmtp restrictions */
{
    struct addr *cur;			/* temp addr list entry */
    char *arg;				/* single string from args */
    char *error;			/* error message */
    char *newsender;			/* temp for process sender */
    static char *end_arg = "";		/* swallow remaining chars in arg */

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

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

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

	    case 'd':			/* set debug level */
	    case 'v':			/* verbose, 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 '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':
		scan_frozen = TRUE;
		break;

	    case 'V':
		operation_mode = PRINT_VERSION;
		break;

	    case 'F':			/* set full name of sender */
		if ((prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) && restrict) {
		    rmail_panic();
		}
		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");
		}
		break;

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

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

		/*
		 * don't use this sender if it has been determined that
		 * the local originator of mail is not a user expected
		 * to receive remote mail.
		 */
		if (sender_is_trusted) {
		    sender = newsender;
		    /* special form of sender for SMTP error mail */
		    if (EQ(sender, "<>")) {
			sender = "MAILER-DAEMON";
			error_sender = TRUE;
			islocal = FALSE;
		    } else if (EQ(sender, "<+>")) {
			sender = "MAILER-DAEMON";
			error_sender = TRUE;
			islocal = TRUE;
		    } else if (*sender == '<') {
			/*
			 * If in <foo@mumble.com> form, strip enclosing
			 * angle brackets.
			 */
			char *c;

			c = sender + strlen(sender) - 1;
			if (*c == '>') {
			    *c = '\0';
			    sender++;
			}
		    } else {
			newsender = preparse_address(sender, &error);
			if (newsender == NULL) {
			    if (errfile) {
				(void)fprintf(errfile,
					      "%s: error in sender name: %s",
					      program, error);
			    }
			    sender = "MAILER-DAEMON";
			    islocal = FALSE;
			}
			islocal =
			    (parse_address(sender, (char **)0, &error, (int *)0)
				   == LOCAL);
		    }
		}
		break;

	    case 'N':			/* don't deliver message */
		dont_deliver = TRUE;
		break;

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

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

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

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

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

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

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

	    case 'o':			/* set various option */
		if (arg[0] == '\0') {
		    arg = *args++;
		    panic_if_null(arg, "o");
		}
		switch (*arg++) {
		case 'A':               /* 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':		/* 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':		/* 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':		/* 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':		/* runq grades */
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oE");
		    }
		    arg_runq_grades = arg;
		    break;

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

		case 'L':
		    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':
		    if ((prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) && restrict) {
			rmail_panic();
		    }
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oM");
		    }
		    switch (*arg++) {
		    case 's':
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMs");
			}
			sender_host = arg;
			break;

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

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

		    case 'u':
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMu");
			}
			ident_sender = arg;
			break;

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

		    case 'P':
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMP");
			}
			sender_program = arg;
			break;
		    default:
			if (errfile) {
			    (void) fprintf(errfile, "%s: %s: invalid '-oM' suboption\n", program, --arg);
			}
			exit(EX_USAGE);
			/* NOTREACHED */
		    }
		    break;

		case 'Q':		/* 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':		/* 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':		/* 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':		/* 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':
		    fprintf(stderr, "warning: this option will be changed from -oU to something else in the next release.\n");
		    report_memory_usage = TRUE;
		    break;

#if notyet
		case 'U':
		    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;
#endif

		case 'X':
		    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':		/* 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':
			deliver_mode = BACKGROUND;
			break;

		    case 'f':
		    case 'i':		/* sendmail "interactive delivery" */
			deliver_mode = FOREGROUND;
			break;

		    case 'd':		/* sendmail "defer map lookups as well as queue" */
		    case 'q':
			deliver_mode = QUEUE_MESSAGE;
			break;

		    default:
			if (errfile) {
			    (void) fprintf(errfile, "%s: %s: invalid '-od' suboption\n", program, arg);
			}
			exit(EX_USAGE);
			/* NOTREACHED */
		    }
		    break;

		case 'e':		/* same as -eX */
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oe");
		    }
		    switch (*arg) {

		    case 'e':		/* used by 4.4bsd rmail for: no errors, just status */
		    case 'm':
			error_processing = MAIL_BACK;
			break;
		    case 'w':
			error_processing = WRITE_BACK;
			break;
		    case 'p':
			error_processing = TERMINAL;
			break;
		    case 'q':
			error_processing = DEV_NULL;
			break;
		    default:
			if (errfile) {
			    (void) fprintf(errfile, "%s: %s: invalid '-oe' suboption\n", program, arg);
			}
			exit(EX_USAGE);
			/* NOTREACHED */
		    }
		    break;

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

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

		case 'r':		/* 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) {
			(void) fprintf(errfile, "%s: %s: invalid '-o' suboption\n", program, --arg);
		    }
#ifdef NO_SENDMAIL_COMPATABILITY
		    exit(EX_USAGE);
		    /* NOTREACHED */
#endif
		}
		arg = end_arg;
		break;

	    case 'b':			/* set operating mode */
		if (*arg == '\0') {
		    arg = *args++;
		    panic_if_null(arg, "b");
		}
		switch (*arg) {

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

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

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

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

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

		case 'v':		/* verify addresses only */
		    operation_mode = VERIFY_ADDRS;
		    break;

		case 'V':
		    operation_mode = PRINT_VERSION;
		    break;

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

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

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

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

		case 'c':		/* print COPYING file */
		    operation_mode = COPYING_MODE;
		    break;

		case 'P':
		    if (prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) {
			rmail_panic();
		    }
		    operation_mode = PRINT_VARS_MODE;
		    break;
		default:
		    if (errfile) {
			(void) fprintf(errfile, "%s: %s: invalid '-b' suboption\n", program, arg);
		    }
		    exit(EX_USAGE);
		    /* NOTREACHED */
		}
		arg = end_arg;
		break;

	    case 'x':			/* special internal hacks */
		if ((prog_type == PROG_RMAIL || prog_type == PROG_RSMTP) && restrict) {
		    rmail_panic();
		}
		if (arg[0] == '\0') {
		    arg = *args++;
		    panic_if_null(arg, "x");
		}
		switch (*arg++) {
		case 'D':		/* 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':		/* 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) {
			(void) fprintf(errfile, "%s: %s: invalid '-x' suboption\n", program, --arg);
		    }
		    exit(EX_USAGE);
		    /* NOTREACHED */
		}
		arg = end_arg;		/* swallows complete argument */
		break;
	    }
	} else {
	    /*
	     * not a flag, arg must be a recipient address so insert
	     * it in the list.
	     */
	    cur = alloc_addr();
	    cur->in_addr = escape_newline(arg);
	    cur->uid = nobody_uid;	/* may be reset in compute_nobody() */
	    cur->gid = nobody_gid;	/* may be reset in compute_nobody() */
	    cur->succ = recipients;
	    recipients = cur;
	    num_recipients++;
	}
    }
    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 */
    }
}

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

    if (l < 0) {
	if (errfile) {
	    (void) fprintf(errfile, "%s: %s: invalid interval (too large?)\n", program, arg);
	}
	exit(EX_USAGE);
    }
    if (l > UINT_MAX) {			/* if sizeof(long) != sizeof(unsigned int) */
	if (errfile) {
	    (void) fprintf(errfile, "%s: %s: 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);

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

    return sp->p;
}

/*
 * 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(*range)) {
	    mn = *range++;
	    mx = mn;
	}
	if (*range == '-') {
	    range++;
	    mx = 255;
	    if (isalnum(*range)) {
		mx = *range;
	    }
	}
    }

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