/* @(#)src/main.c	1.24 9/20/92 12:54:53 */

/*
 *    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 <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <pwd.h>
#include <signal.h>
#include "defs.h"
#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 */
#include "config.h"
#include "smail.h"
#include "dys.h"
#include "addr.h"
#include "hash.h"
#include "main.h"
#include "log.h"
#include "transport.h"
#include "child.h"
#include "exitcodes.h"
#include "smailconf.h"
#include "alloc.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
# include "error.h"
#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 */
char *primary_name;			/* primary local name from hostnames */
FILE *errfile = NULL;			/* file to write debug messages to */
int real_uid;				/* saved real uid before ruid 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 */
int prog_euid;				/* effective uid of program */
int prog_egid;				/* effective gid of program */
int force_zero_exitvalue = FALSE;	/* if TRUE always exit with status 0 */
int call_defer_message;			/* if TRUE must call defer_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 */
#ifdef GLOTZNET
char *glotzhost;
char *getenv();
#endif /* GLOTZNET */

/* functions local to this file */
static void panic_if_null();
static void rmail_panic();
static char *escape_newline();

/* 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_alias_file = NULL;	/* alias file set by -oA */
static char *arg_debug_file = NULL;	/* debug file, defaulting to stderr */

extern int errno;


/*
 * main - what to do after being exec'd
 *
 * main decodes the argument list and then performs specified action
 */
/*ARGSUSED*/
void
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;

    save_argv = argv;

#ifdef GLOTZNET
    glotzhost = getenv("GLOTZHOST");
    if (glotzhost == NULL) {
	glotzhost = "namei";
    }
#endif /* GLOTZNET */

    /* 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 = rindex(*argv, '/');
    if (program == NULL) {
	program = *argv;
    } else {
	program++;
    }
    argv++;
    sender_program = program;

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

#ifdef	UNIX_AIX3
    /* if we don't have a login id, assume one */
    if (getuidx(ID_LOGIN) == -1)
	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;
	(void) setgroups(0, &dummy);
    }
#endif	/* HAVE_SETGROUPS */

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

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

#if defined(HAVE_RLIMIT)
    /* kill limits on file size, cpu, data segment, and unreasonable
     * limits on stack size */
    {
	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)
	rl.rlim_cur = DATA_RLIMIT;
#endif	/* DATA_RLIMIT */
	(void) setrlimit(RLIMIT_DATA, &rl);
#if defined(STACK_RLIMIT)
	rl.rlim_cur = STACK_RLIMIT;
#endif	/* STACK_RLIMIT */
	(void) setrlimit(RLIMIT_STACK, &rl);
    }
#endif	/* HAVE_RLIMIT */

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

    /* initialize per-message state information */
    initialize_state();

    /* 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_PATHTO) {
	pathto(argv);
    } else if (prog_type == PROG_OPTTO) {
	optto(argv);
    } else if (prog_type == PROG_UUPATH) {
	uupath(argv);
    } else if (prog_type == PROG_RMAIL) {
	dot_usage = NO_DOT_PROTOCOL;
    }

    /* process the args given by the user */
    process_args(argv);

    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 (prog_type == PROG_ROGUE) {
	operation_mode = ROGUE_MODE;
    }

    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_alias_file || operation_mode == REBUILD_ALIASES)
    {
	/*
	 * 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;
    }
    error = read_config_file(config_file = make_lib_fn(config_file));
    /* we need to set this again, in case it was changed in the config files */
    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);
    }
    /* we need to set this again, in case it was changed in the config files */
    if (arg_smail_lib_dir) {
	smail_lib_dir = arg_smail_lib_dir;
    }
    if (arg_smail_lib_dir) {
	smail_lib_dir = arg_smail_lib_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;
    }
    /* 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:
	    write_log(LOG_TTY|LOG_PANIC, "%s", error);
	    break;

	default:
	    panic(EX_OSFILE, "%s", error);
	    /*NOTREACHED*/
	}
    }

    /*
     * change error file to debugging file from -D option, if any
     */

    if (arg_debug_file) {
	new_errfile = fopen(arg_debug_file, "a");
	if (new_errfile == NULL) {
	    write_log(LOG_TTY, "Warning: Cannot open debug file %s: %s\n",
		      arg_debug_file, strerrno(errno));
	    arg_debug_file = NULL;
	} else {
	    errfile = new_errfile;
	    fprintf(errfile, "\n%s: Debugging started: pid=%ld\n\n",
		    program, (long)getpid());
	}
    }

    /*
     * 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) {
	    panic(EX_SOFTWARE, "main: bad stat of smail binary %s", smail);
	} else {
	    add_config_stat(smail, &statbuf);
	}
	/*FALL THROUGH*/

    case TEST_MODE:
    case VERIFY_ADDRS:
    case BATCH_SMTP_MODE:
    case SMTP_MODE:
	if ((error = read_transport_file()) ||
	    (error = read_router_file()) ||
	    (error = read_director_file()) ||
	    (error = read_qualify_file()) ||
	    (error = read_retry_file()))
	{
	    panic(EX_OSFILE, "%s", error);
	}
	break;
    }

    switch (operation_mode) {
    case NOOP_MODE:
    case DAEMON_MODE:
    case TEST_MODE:
	cache_directors();
	cache_routers();
	cache_transports();
	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 = getuid();
    prog_euid = geteuid();		/* keep a copy of the effictive id's */
    prog_egid = getegid();
    setuid(prog_euid);
    setgid(prog_egid);

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

    /*
     * 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 */
	dont_deliver = FALSE;		/* it is too dangerous to allow this */
	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 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 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:

	/*
	 * if an alias file was set explicitly with -oA, then build
	 * a DBM file appropriate for use with YP.  Otherwise, just
	 * call mkaliases to rebuild the default alias file.
	 */

	if (smail_util_dir == NULL) {
	    if (errfile) {
		fprintf(errfile, "%s: smail_util_dir attribute not set, -bi operation not supported\n", program);
	    }
	    exit(EX_UNAVAILABLE);
	}
	if (arg_alias_file) {
	    utilargs[0] = xprintf("%s/mkdbm", smail_util_dir);
	    utilargs[1] = "-v";
	    utilargs[2] = "-y";
	    utilargs[3] = "-o";
	    utilargs[4] = arg_alias_file;
	    utilargs[5] = arg_alias_file;
	    utilargs[6] = NULL;
	} else {
#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;
	}
	child = open_child(utilargs, (char *)NULL, (FILE *)NULL, (FILE *)NULL,
			   -1, CHILD_MINENV, getuid(), getgid());
	if (child == -1) {
	    if (errfile) {
		fprintf(errfile, "%s: Cannot start %s: %s\n",
			utilargs[0], strerrno(errno));
	    }
	    exit(EX_UNAVAILABLE);
	}
	close_child((FILE *)NULL, (FILE *)NULL, child);
	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);
}


/*
 * initialize_state - set some parameters to their default value
 */
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;
    some_deferred_addrs = FALSE;
    call_defer_message = FALSE;
    sender_is_trusted = TRUE;
    dont_deliver = FALSE;

    /*
     * generate a new address hit table where case is ignored and
     * all data resides in memory
     */
    /* XXX - hit_table should be associated with a block */
    hit_table = new_hash_table(hit_table_len,
			       (struct block *)NULL,
			       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)
    register char **args;		/* vector of arguments */
{
    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 *error = NULL;

		    debug = (int)c_atol(arg, &error);
		    if (error || 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':
		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;

#ifdef GLOTZNET
	    case 'G':
		glotzhost = arg;
		arg = end_arg;
		/* if no string there, take next arg */
		if (sender_name[0] == '\0') {
		    glotzhost = *args++;
		    panic_if_null(glotzhost, "G");
		}
		break;
#endif /* GLOTZNET */

	    case 'V':
		operation_mode = PRINT_VERSION;
		break;

	    case 'F':			/* set full name of sender */
		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;
		/* FALL THROUGH */

	    case 'f':			/* set path to sender */
		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 {
			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]) {
		    long l = ivaltol(arg);

		    if (l < 0) {
			if (errfile) {
			    (void) fprintf(errfile,
					   "%s: %s: malformed interval\n",
					   program, arg);
			    exit(EX_USAGE);
			}
		    }
		    queue_interval = (unsigned)l;
		    if (l != queue_interval) {
			(void) fprintf(errfile, "%s: %s: interval too large\n",
				       program, arg);
			exit(EX_USAGE);
		    }
		    arg = end_arg;	/* uses rest of argument */
		}
		break;

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

		    if (arg[0]) {
			hop_count = (int)c_atol(arg, &error);
			arg = end_arg;
		    } else {
			hop_count = atoi(*args++);
		    }
		    if (error || 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;
		}
		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 'i':		/* same as -i */
		    dot_usage = NO_DOT_PROTOCOL;
		    break;

		case 'A':
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oA");
		    }
		    arg_alias_file = arg;
		    break;

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

		case 'U':
		    report_memory_usage = TRUE;
		    break;

		case 'M':
		    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 'P':
			if (*arg == '\0') {
			    arg = *args++;
			    panic_if_null(arg, "oMP");
			}
			sender_program = arg;
			break;
		    }
		    break;

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

		    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;
		    }
		    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 'f':
			deliver_mode = FOREGROUND;
			break;

		    case 'b':
			deliver_mode = BACKGROUND;
			break;

		    case 'q':
			deliver_mode = QUEUE_MESSAGE;
			break;
		    }
		    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 '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 '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 '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 '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 '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 'X':
		    if (*arg == '\0') {
			arg = *args++;
			panic_if_null(arg, "oX");
		    }
		    smtp_service_name = arg;
		    break;

		case 'm':		/* same as -m */
		    me_too = TRUE;
		    break;
		}
		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':
		    operation_mode = PRINT_VARS_MODE;
		    break;
		}
		arg = end_arg;
		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;
	    cur->gid = nobody_gid;
	    cur->succ = recipients;
	    recipients = cur;
	}
    }
}

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);
}
