/*
#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:
*/
syntax highlighted by Code2HTML, v. 0.9.1