* Copyright (c) 1983, 1995 Eric P. Allman
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)readcf.c 8.113 (Berkeley) %G%";
** READCF -- read control file.
** This routine reads the control file and builds the internal
** The file is formatted as a sequence of lines, each taken
** atomically. The first character of each line describes how
** the line is to be interpreted. The lines are:
** Dxval Define macro x to have value val.
** Cxword Put word into class x.
** Fxfile [fmt] Read file for lines to put into
** class x. Use scanf string 'fmt'
** or "%s" if not present. Fmt should
** only produce one string-valued result.
** Hname: value Define header with field-name 'name'
** and value as specified; this will be
** macro expanded immediately before
** Sn Use rewriting set n.
** Rlhs rhs Rewrite addresses that match lhs to
** Mn arg=val... Define mailer. n is the internal name.
** Args specify mailer parameters.
** Oxvalue Set option x to value.
** Pname=value Set precedence name to value.
** Vversioncode[/vendorcode]
** Version level/vendor name of
** Kmapname mapclass arguments....
** Define keyed lookup of a given class.
** Arguments are class dependent.
** Eenvar=value Set the environment value to the given value.
** cfname -- control file name.
** safe -- TRUE if this is the system config file;
** e -- the main envelope.
** Builds several internal tables.
struct rewrite
*rwp
= NULL
;
extern char **copyplist();
char pvpbuf
[MAXLINE
+ MAXATOM
];
static char *null_list
[1] = { NULL
};
extern char *munchstring
__P((char *, char **));
extern void fileclass
__P((int, char *, char *, bool, bool));
extern void toomany
__P((int, int));
if (fstat(fileno(cf
), &statb
) < 0)
if (!S_ISREG(statb
.st_mode
))
syserr("not a plain file");
if (OpMode
!= MD_TEST
&& bitset(S_IWGRP
|S_IWOTH
, statb
.st_mode
))
if (OpMode
== MD_DAEMON
|| OpMode
== MD_INITALIAS
)
fprintf(stderr
, "%s: WARNING: dangerous write permissions\n",
syslog(LOG_CRIT
, "%s: WARNING: dangerous write permissions",
while ((bp
= fgetfolded(buf
, sizeof buf
, cf
)) != NULL
)
/* do macro expansion mappings */
for (p
= bp
; *p
!= '\0'; p
++)
if (*p
== '#' && p
> bp
&& ConfigLevel
>= 3)
/* this is an on-line comment */
/* it's from $# -- let it go through */
/* it's backslash escaped */
/* delete preceeding white space */
while (isascii(*p
) && isspace(*p
) && p
> bp
)
if ((e
= strchr(++p
, '\n')) != NULL
)
if (*p
!= '$' || p
[1] == '\0')
/* actual dollar sign.... */
/* convert to macro expansion character */
/* special handling for $=, $~, and $& */
if (*p
== '=' || *p
== '~' || *p
== '&')
/* convert macro name to code */
/* interpret this line */
case 'R': /* rewriting rule */
for (p
= &bp
[1]; *p
!= '\0' && *p
!= '\t'; p
++)
syserr("invalid rewrite line \"%s\" (tab expected)", bp
);
/* allocate space for the rule header */
RewriteRules
[ruleset
] = rwp
=
(struct rewrite
*) xalloc(sizeof *rwp
);
rwp
->r_next
= (struct rewrite
*) xalloc(sizeof *rwp
);
/* expand and save the LHS */
expand(&bp
[1], exbuf
, sizeof exbuf
, e
);
rwp
->r_lhs
= prescan(exbuf
, '\t', pvpbuf
,
sizeof pvpbuf
, NULL
, NULL
);
rwp
->r_lhs
= copyplist(rwp
->r_lhs
, TRUE
);
/* count the number of fuzzy matches in LHS */
for (ap
= rwp
->r_lhs
; *ap
!= NULL
; ap
++)
syserr("Inappropriate use of %s on LHS",
syserr("R line: null LHS");
/* expand and save the RHS */
while (*p
!= '\0' && *p
!= '\t')
expand(q
, exbuf
, sizeof exbuf
, e
);
rwp
->r_rhs
= prescan(exbuf
, '\t', pvpbuf
,
sizeof pvpbuf
, NULL
, NULL
);
rwp
->r_rhs
= copyplist(rwp
->r_rhs
, TRUE
);
/* check no out-of-bounds replacements */
for (ap
= rwp
->r_rhs
; *ap
!= NULL
; ap
++)
if ((*ap
)[1] <= '0' || (*ap
)[1] > nfuzzy
)
syserr("replacement $%c out of bounds",
syserr("Inappropriate use of %s on RHS",
syserr("R line: null RHS");
case 'S': /* select rewriting set */
ruleset
= strtorwset(&bp
[1], NULL
, ST_ENTER
);
rwp
= RewriteRules
[ruleset
];
while (rwp
->r_next
!= NULL
)
fprintf(stderr
, "WARNING: Ruleset %s redefined\n",
case 'D': /* macro definition */
mid
= macid(&bp
[1], &ep
);
p
= munchstring(ep
, NULL
);
define(mid
, newstr(p
), e
);
case 'H': /* required header line */
(void) chompheader(&bp
[1], TRUE
, NULL
, e
);
case 'C': /* word class */
case 'T': /* trusted user (set class `t') */
mid
= macid(&bp
[1], &ep
);
expand(ep
, exbuf
, sizeof exbuf
, e
);
while (*p
!= '\0' && isascii(*p
) && isspace(*p
))
while (*p
!= '\0' && !(isascii(*p
) && isspace(*p
)))
case 'F': /* word class from file */
mid
= macid(&bp
[1], &ep
);
for (p
= ep
; isascii(*p
) && isspace(*p
); )
if (p
[0] == '-' && p
[1] == 'o')
while (*p
!= '\0' && !(isascii(*p
) && isspace(*p
)))
while (isascii(*p
) && isspace(*p
))
while (*p
!= '\0' && !(isascii(*p
) && isspace(*p
)))
while (isascii(*++p
) && isspace(*p
))
fileclass(bp
[1], file
, p
, safe
, optional
);
case 'L': /* extended load average description */
case 'M': /* define mailer */
case 'O': /* set option */
setoption(buf
[1], &buf
[2], FALSE
);
case 'P': /* set precedence */
if (NumPriorities
>= MAXPRIORITIES
)
toomany('P', MAXPRIORITIES
);
for (p
= &bp
[1]; *p
!= '\0' && *p
!= '='; p
++)
Priorities
[NumPriorities
].pri_name
= newstr(&bp
[1]);
Priorities
[NumPriorities
].pri_val
= atoi(++p
);
case 'V': /* configuration syntax version */
for (p
= &bp
[1]; isascii(*p
) && isspace(*p
); p
++)
if (!isascii(*p
) || !isdigit(*p
))
syserr("invalid argument to V line: \"%.20s\"",
ConfigLevel
= strtol(p
, &ep
, 10);
** Do heuristic tweaking for back compatibility.
/* level 5 configs have short name in $w */
if (p
!= NULL
&& (p
= strchr(p
, '.')) != NULL
)
printf("redefine('w' as %s)\n",
/* extract vendor code */
for (p
= ep
; isascii(*p
) && isalpha(*p
); )
syserr("invalid V line vendor code: \"%s\"",
(void) makemapentry(&bp
[1]);
syserr("unknown control line \"%s\"", bp
);
syserr("I/O read error", cfname
);
/* initialize host maps from local service tables */
/* determine if we need to do special name-server frotz */
char *maptype
[MAXMAPSTACK
];
short mapreturn
[MAXMAPACTIONS
];
nmaps
= switch_map_find("hosts", maptype
, mapreturn
);
if (nmaps
> 0 && nmaps
<= MAXMAPSTACK
)
for (mapno
= 0; mapno
< nmaps
&& !UseNameServer
; mapno
++)
if (strcmp(maptype
[mapno
], "dns") == 0)
nmaps
= switch_map_find("passwd", maptype
, mapreturn
);
if (nmaps
> 0 && nmaps
<= MAXMAPSTACK
)
for (mapno
= 0; mapno
< nmaps
&& !UseHesiod
; mapno
++)
if (strcmp(maptype
[mapno
], "hesiod") == 0)
** TOOMANY -- signal too many of some option
** id -- the id of the error line
** maxcnt -- the maximum possible values
syserr("too many %c lines, %d max", id
, maxcnt
);
** FILECLASS -- read members of a class from a file
** class -- class to define.
** filename -- name of file to read.
** fmt -- scanf string to use for match.
** safe -- if set, this is a safe read.
** optional -- if set, it is not an error for the file to
** puts all lines in filename that match a scanf into
fileclass(class, filename
, fmt
, safe
, optional
)
printf("fileclass(%s, fmt=%s)\n", filename
, fmt
);
for (p
= strtok(&filename
[1], " \t"); p
!= NULL
; p
= strtok(NULL
, " \t"))
pid
= prog_open(argv
, &fd
, CurEnv
);
f
= safefopen(filename
, O_RDONLY
, 0, sff
);
syserr("fileclass: cannot open %s", filename
);
while (fgets(buf
, sizeof buf
, f
) != NULL
)
if (sscanf(buf
, fmt
, wordbuf
) != 1)
** Break up the match into words.
/* strip leading spaces */
while (isascii(*p
) && isspace(*p
))
/* find the end of the word */
while (*p
!= '\0' && !(isascii(*p
) && isspace(*p
)))
/* enter the word in the symbol table */
** MAKEMAILER -- define a new mailer.
** line -- description of mailer. This is in labeled
** fields. The fields are:
** A -- the argv for this mailer
** C -- the character set for MIME conversions
** D -- the directory to run in
** F -- the flags associated with the mailer
** L -- the maximum line length
** M -- the maximum message size
** N -- the niceness at which to run
** P -- the path to the mailer
** R -- the recipient rewriting set
** S -- the sender rewriting set
** T -- the mailer type (for DSNs)
** U -- the uid to run as
** The first word is the canonical name of the mailer.
** enters the mailer into the mailer table.
register struct mailer
*m
;
extern char **makeargv();
extern char *munchstring();
/* allocate a mailer and set up defaults */
m
= (struct mailer
*) xalloc(sizeof *m
);
bzero((char *) m
, sizeof *m
);
/* collect the mailer name */
for (p
= line
; *p
!= '\0' && *p
!= ',' && !(isascii(*p
) && isspace(*p
)); p
++)
m
->m_name
= newstr(line
);
/* now scan through and assign info from the fields */
while (*p
!= '\0' && (*p
== ',' || (isascii(*p
) && isspace(*p
))))
/* p now points to field code */
while (*p
!= '\0' && *p
!= '=' && *p
!= ',')
syserr("mailer %s: `=' expected", m
->m_name
);
while (isascii(*p
) && isspace(*p
))
/* p now points to the field body */
p
= munchstring(p
, &delimptr
);
/* install the field into the mailer struct */
if (!(isascii(*p
) && isspace(*p
)))
case 'S': /* sender rewriting ruleset */
case 'R': /* recipient rewriting ruleset */
i
= strtorwset(p
, &endp
, ST_ENTER
);
m
->m_sh_rwset
= m
->m_se_rwset
= i
;
m
->m_rh_rwset
= m
->m_re_rwset
= i
;
i
= strtorwset(p
, NULL
, ST_ENTER
);
case 'E': /* end of line string */
case 'A': /* argument vector */
case 'M': /* maximum message size */
case 'L': /* maximum line length */
m
->m_linelimit
= atoi(p
);
case 'N': /* run niceness */
case 'D': /* working directory */
m
->m_execdir
= newstr(p
);
case 'C': /* default charset */
m
->m_defcharset
= newstr(p
);
case 'T': /* MTA-Name/Address/Diagnostic types */
/* extract MTA name type; default to "dns" */
m
->m_mtatype
= newstr(p
);
p
= strchr(m
->m_mtatype
, '/');
if (*m
->m_mtatype
== '\0')
/* extract address type; default to "rfc822" */
if (m
->m_addrtype
== NULL
|| *m
->m_addrtype
== '\0')
m
->m_addrtype
= "rfc822";
/* extract diagnostic type; default to "smtp" */
if (m
->m_diagtype
== NULL
|| *m
->m_diagtype
== '\0')
if (isascii(*p
) && !isdigit(*p
))
while (isascii(*p
) && isalnum(*p
))
while (isascii(*p
) && isspace(*p
))
syserr("readcf: mailer U= flag: unknown user %s", q
);
m
->m_uid
= strtol(p
, &q
, 0);
while (isascii(*p
) && isspace(*p
))
if (isascii(*p
) && !isdigit(*p
))
while (isascii(*p
) && isalnum(*p
))
syserr("readcf: mailer U= flag: unknown group %s", q
);
m
->m_gid
= strtol(p
, NULL
, 0);
/* do some rationality checking */
syserr("M%s: A= argument required", m
->m_name
);
syserr("M%s: P= argument required", m
->m_name
);
if (NextMailer
>= MAXMAILERS
)
syserr("too many mailers defined (%d max)", MAXMAILERS
);
/* do some heuristic cleanup for back compatibility */
if (bitnset(M_LIMITS
, m
->m_flags
))
m
->m_linelimit
= SMTPLINELIM
;
setbitn(M_7BITS
, m
->m_flags
);
(strcmp(m
->m_mailer
, "[IPC]") == 0 ||
strcmp(m
->m_mailer
, "[TCP]") == 0))
if (m
->m_mtatype
== NULL
)
if (m
->m_addrtype
== NULL
)
m
->m_addrtype
= "rfc822";
if (m
->m_diagtype
== NULL
)
/* enter the mailer into the symbol table */
s
= stab(m
->m_name
, ST_MAILER
, ST_ENTER
);
Mailer
[i
] = s
->s_mailer
= m
;
** MUNCHSTRING -- translate a string into internal form.
** p -- the string to munch.
** delimptr -- if non-NULL, set to the pointer of the
** field delimiter character.
static char buf
[MAXLINE
];
for (q
= buf
; *p
!= '\0'; p
++)
/* everything is roughly literal */
case 'r': /* carriage return */
case 'f': /* form feed */
case 'b': /* backspace */
else if (quotemode
|| *p
!= ',')
** MAKEARGV -- break up a string into words
** p -- the string to break up.
** a char **argv (dynamically allocated)
/* take apart the words */
while (*p
!= '\0' && i
< MAXPV
)
while (*p
!= '\0' && !(isascii(*p
) && isspace(*p
)))
while (isascii(*p
) && isspace(*p
))
/* now make a copy of the argv */
avp
= (char **) xalloc(sizeof *avp
* i
);
bcopy((char *) argv
, (char *) avp
, sizeof *avp
* i
);
** PRINTRULES -- print rewrite rules (for debugging)
register struct rewrite
*rwp
;
for (ruleset
= 0; ruleset
< 10; ruleset
++)
if (RewriteRules
[ruleset
] == NULL
)
printf("\n----Rule Set %d:", ruleset
);
for (rwp
= RewriteRules
[ruleset
]; rwp
!= NULL
; rwp
= rwp
->r_next
)
** PRINTMAILER -- print mailer structure (for debugging)
** m -- the mailer to print
printf("mailer %d (%s): P=%s S=%d/%d R=%d/%d M=%ld U=%d:%d F=",
m
->m_mailer
, m
->m_se_rwset
, m
->m_sh_rwset
,
m
->m_re_rwset
, m
->m_rh_rwset
, m
->m_maxsize
,
for (j
= '\0'; j
<= '\177'; j
++)
if (bitnset(j
, m
->m_flags
))
printf(" L=%d E=", m
->m_linelimit
);
if (m
->m_defcharset
!= NULL
)
printf(" C=%s", m
->m_defcharset
);
m
->m_mtatype
== NULL
? "<undefined>" : m
->m_mtatype
,
m
->m_addrtype
== NULL
? "<undefined>" : m
->m_addrtype
,
m
->m_diagtype
== NULL
? "<undefined>" : m
->m_diagtype
);
** SETOPTION -- set global processing option
** val -- option value (as a text string).
** sticky -- if set, don't let other setoptions override
** e -- the main envelope.
** Sets options as implied by the arguments.
static BITMAP StickyOpt
; /* set if option is stuck */
extern void settimeout
__P((char *, char *));
char *rf_name
; /* name of the flag */
long rf_bits
; /* bits to set/clear */
"defnames", RES_DEFNAMES
,
"stayopen", RES_STAYOPEN
,
"true", 0, /* to avoid error on old syntax */
char *o_name
; /* long name of option */
u_char o_code
; /* short name of option */
bool o_safe
; /* safe for random people to use */
"SevenBitInput", '7', TRUE
,
"EightBitMode", '8', TRUE
,
"MinFreeBlocks", 'b', TRUE
,
"CheckpointInterval", 'C', TRUE
,
"HoldExpensive", 'c', FALSE
,
"AutoRebuildAliases", 'D', FALSE
,
"DeliveryMode", 'd', TRUE
,
"ErrorHeader", 'E', FALSE
,
"TempFileMode", 'F', FALSE
,
"SaveFromLine", 'f', FALSE
,
"MatchGECOS", 'G', FALSE
,
"MaxHopCount", 'h', FALSE
,
"ResolverOptions", 'I', FALSE
,
"ForwardPath", 'J', FALSE
,
"SendMimeErrors", 'j', TRUE
,
"ConnectionCacheSize", 'k', FALSE
,
"ConnectionCacheTimeout", 'K', FALSE
,
"UseErrorsTo", 'l', FALSE
,
"CheckAliases", 'n', FALSE
,
"OldStyleHeaders", 'o', TRUE
,
"DaemonPortOptions", 'O', FALSE
,
"PrivacyOptions", 'p', TRUE
,
"PostmasterCopy", 'P', FALSE
,
"QueueFactor", 'q', FALSE
,
"QueueDirectory", 'Q', FALSE
,
"DontPruneRoutes", 'R', FALSE
,
"StatusFile", 'S', FALSE
,
"QueueTimeout", 'T', FALSE
,
"TimeZoneSpec", 't', FALSE
,
"UserDatabaseSpec", 'U', FALSE
,
"DefaultUser", 'u', FALSE
,
"FallbackMXhost", 'V', FALSE
,
"TryNullMXList", 'w', TRUE
,
"RecipientFactor", 'y', FALSE
,
"ForkEachJob", 'Y', FALSE
,
"ClassFactor", 'z', FALSE
,
"RetryFactor", 'Z', FALSE
,
#define O_QUEUESORTORD 0x81
"QueueSortOrder", O_QUEUESORTORD
, TRUE
,
"HostsFile", O_HOSTSFILE
, FALSE
,
"MinQueueAge", O_MQA
, TRUE
,
"MaxHostStatAge", O_MHSA, TRUE,
#define O_DEFCHARSET 0x85
"DefaultCharSet", O_DEFCHARSET
, TRUE
,
"ServiceSwitchFile", O_SSFILE
, FALSE
,
"DialDelay", O_DIALDELAY
, TRUE
,
#define O_NORCPTACTION 0x88
"NoRecipientAction", O_NORCPTACTION
, TRUE
,
#define O_SAFEFILEENV 0x89
"SafeFileEnvironment", O_SAFEFILEENV
, FALSE
,
#define O_MAXMSGSIZE 0x8a
"MaxMessageSize", O_MAXMSGSIZE
, FALSE
,
#define O_COLONOKINADDR 0x8b
"ColonOkInAddr", O_COLONOKINADDR
, TRUE
,
#define O_MAXQUEUERUN 0x8c
"MaxQueueRunSize", O_MAXQUEUERUN
, TRUE
,
#define O_MAXCHILDREN 0x8d
"MaxDaemonChildren", O_MAXCHILDREN, FALSE,
#define O_KEEPCNAMES 0x8e
"DontExpandCnames", O_KEEPCNAMES
, FALSE
,
setoption(opt
, val
, sticky
)
register struct optioninfo
*o
;
extern time_t convtime();
extern bool Warn_Q_option
;
syserr("readcf: null option name");
subopt
= strchr(val
, '.');
for (o
= OptionTab
; o
->o_name
!= NULL
; o
++)
if (strncasecmp(o
->o_name
, val
, strlen(val
)) != 0)
if (strlen(o
->o_name
) == strlen(val
))
/* completely specified -- this must be it */
if (sel
!= NULL
&& o
->o_name
== NULL
)
else if (o
->o_name
== NULL
)
syserr("readcf: unknown option name %s", val
);
syserr("readcf: ambiguous option name %s (matches %s and %s)",
val
, sel
->o_name
, o
->o_name
);
if (strlen(val
) != strlen(o
->o_name
))
bool oldVerbose
= Verbose
;
message("Option %s used as abbreviation for %s",
for (o
= OptionTab
; o
->o_name
!= NULL
; o
++)
printf(isascii(opt
) && isprint(opt
) ?
"setoption %s (%c).%s=" :
"setoption %s (0x%x).%s=",
o
->o_name
== NULL
? "<unknown>" : o
->o_name
,
subopt
== NULL
? "" : subopt
);
** See if this option is preset for us.
if (!sticky
&& bitnset(opt
, StickyOpt
))
case '!': /* extended options */
setextoption(val
, safe
, sticky
, e
);
case '7': /* force seven-bit input */
SevenBitInput
= atobool(val
);
case '8': /* handling of 8-bit input */
case 'm': /* convert 8-bit, convert MIME */
MimeMode
= MM_CVTMIME
|MM_MIME8BIT
;
case 'p': /* pass 8 bit, convert MIME */
MimeMode
= MM_CVTMIME
|MM_PASS8BIT
;
case 's': /* strict adherence */
case 'r': /* reject 8-bit, don't convert MIME */
case 'j': /* "just send 8" */
case 'a': /* encode 8 bit if available */
MimeMode
= MM_MIME8BIT
|MM_PASS8BIT
|MM_CVTMIME
;
case 'c': /* convert 8 bit to MIME, never 7 bit */
syserr("Unknown 8-bit mode %c", *val
);
case 'A': /* set default alias file */
case 'a': /* look N minutes for "@:@" in alias file */
SafeAlias
= 5 * 60; /* five minutes */
SafeAlias
= convtime(val
, 'm');
case 'B': /* substitution for blank character */
case 'b': /* min blocks free on queue fs/max msg size */
MaxMessageSize
= atol(p
);
MinBlocksFree
= atol(val
);
case 'c': /* don't connect to "expensive" mailers */
NoConnect
= atobool(val
);
case 'C': /* checkpoint every N addresses */
CheckpointInterval
= atoi(val
);
case 'd': /* delivery mode */
e
->e_sendmode
= SM_DELIVER
;
case SM_QUEUE
: /* queue only */
syserr("need QUEUE to set -odqueue");
case SM_DELIVER
: /* do everything */
case SM_FORK
: /* fork after verification */
syserr("Unknown delivery mode %c", *val
);
case 'D': /* rebuild alias database as needed */
AutoRebuild
= atobool(val
);
case 'E': /* error message header/header file */
ErrMsgFile
= newstr(val
);
case 'e': /* set error processing mode */
case EM_QUIET
: /* be silent about it */
case EM_MAIL
: /* mail back */
case EM_BERKNET
: /* do berknet error processing */
case EM_WRITE
: /* write back (or mail) */
case EM_PRINT
: /* print errors normally (default) */
case 'F': /* file mode */
FileMode
= atooct(val
) & 0777;
case 'f': /* save Unix-style From lines on front */
case 'G': /* match recipients against GECOS field */
MatchGecos
= atobool(val
);
case 'g': /* default gid */
if (isascii(*val
) && isdigit(*val
))
register struct group
*gr
;
syserr("readcf: option %c: unknown group %s",
case 'H': /* help file */
HelpFile
= "sendmail.hf";
case 'h': /* maximum hop count */
case 'I': /* use internet domain name server */
struct resolverflags
*rfp
;
while (*p
!= '\0' && !(isascii(*p
) && isspace(*p
)))
if (strcasecmp(q
, "HasWildcardMX") == 0)
HasWildcardMX
= !clearmode
;
for (rfp
= ResolverFlags
; rfp
->rf_name
!= NULL
; rfp
++)
if (strcasecmp(q
, rfp
->rf_name
) == 0)
if (rfp
->rf_name
== NULL
)
syserr("readcf: I option value %s unrecognized", q
);
_res
.options
&= ~rfp
->rf_bits
;
_res
.options
|= rfp
->rf_bits
;
printf("_res.options = %x, HasWildcardMX = %d\n",
_res
.options
, HasWildcardMX
);
usrerr("name server (I option) specified but BIND not compiled in");
case 'i': /* ignore dot lines in message */
case 'j': /* send errors in MIME (RFC 1341) format */
SendMIMEErrors
= atobool(val
);
case 'J': /* .forward search path */
ForwardPath
= newstr(val
);
case 'k': /* connection cache size */
case 'K': /* connection cache timeout */
MciCacheTimeout
= convtime(val
, 'm');
case 'l': /* use Errors-To: header */
UseErrorsTo
= atobool(val
);
case 'L': /* log level */
if (safe
|| LogLevel
< atoi(val
))
case 'M': /* define macro */
cleanstrcpy(p
, p
, MAXNAME
);
define(val
[0], p
, CurEnv
);
case 'm': /* send to me too */
case 'n': /* validate RHS in newaliases */
CheckAliases
= atobool(val
);
/* 'N' available -- was "net name" */
case 'O': /* daemon options */
case 'o': /* assume old style headers */
CurEnv
->e_flags
|= EF_OLDSTYLE
;
CurEnv
->e_flags
&= ~EF_OLDSTYLE
;
case 'p': /* select privacy level */
register struct prival
*pv
;
extern struct prival PrivacyValues
[];
while (isascii(*p
) && (isspace(*p
) || ispunct(*p
)))
while (isascii(*p
) && isalnum(*p
))
for (pv
= PrivacyValues
; pv
->pv_name
!= NULL
; pv
++)
if (strcasecmp(val
, pv
->pv_name
) == 0)
syserr("readcf: Op line: %s unrecognized", val
);
PrivacyFlags
|= pv
->pv_flag
;
case 'P': /* postmaster copy address for returned mail */
PostMasterCopy
= newstr(val
);
case 'q': /* slope of queue only function */
case 'Q': /* queue directory */
if (RealUid
!= 0 && !safe
)
case 'R': /* don't prune routes */
DontPruneRoutes
= atobool(val
);
case 'r': /* read timeout */
case 'S': /* status file */
StatFile
= "sendmail.st";
case 's': /* be super safe, even if expensive */
SuperSafe
= atobool(val
);
case 'T': /* queue timeout */
settimeout("queuewarn", p
);
settimeout("queuereturn", val
);
case 't': /* time zone name */
TimeZoneSpec
= newstr(val
);
case 'U': /* location of user database */
case 'u': /* set default uid */
for (p
= val
; *p
!= '\0'; p
++)
if (*p
== '.' || *p
== '/' || *p
== ':')
if (isascii(*val
) && isdigit(*val
))
register struct passwd
*pw
;
syserr("readcf: option u: unknown user %s", val
);
/* handle the group if it is there */
case 'V': /* fallback MX host */
FallBackMX
= newstr(val
);
case 'v': /* run in verbose mode */
case 'w': /* if we are best MX, try host directly */
TryNullMXList
= atobool(val
);
/* 'W' available -- was wizard password */
case 'x': /* load avg at which to auto-queue msgs */
case 'X': /* load avg at which to auto-reject connections */
case 'y': /* work recipient factor */
case 'Y': /* fork jobs during queue runs */
ForkQueueRuns
= atobool(val
);
case 'z': /* work message class factor */
case 'Z': /* work time factor */
case O_QUEUESORTORD
: /* queue sorting order */
case 'h': /* Host first */
QueueSortOrder
= QS_BYHOST
;
case 'p': /* Priority order */
QueueSortOrder
= QS_BYPRIORITY
;
syserr("Invalid queue sort order \"%s\"", val
);
case O_HOSTSFILE
: /* pathname of /etc/hosts file */
case O_MQA
: /* minimum queue age between deliveries */
MinQueueAge
= convtime(val
, 'm');
case O_MHSA
: /* maximum age of cached host status */
MaxHostStatAge
= convtime(val
, 'm');
case O_DEFCHARSET
: /* default character set for mimefying */
DefaultCharSet
= newstr(denlstring(val
, TRUE
, TRUE
));
case O_SSFILE
: /* service switch file */
ServiceSwitchFile
= newstr(val
);
case O_DIALDELAY
: /* delay for dial-on-demand operation */
DialDelay
= convtime(val
, 's');
case O_NORCPTACTION
: /* what to do if no recipient */
if (strcasecmp(val
, "none") == 0)
NoRecipientAction
= NRA_NO_ACTION
;
else if (strcasecmp(val
, "add-to") == 0)
NoRecipientAction
= NRA_ADD_TO
;
else if (strcasecmp(val
, "add-apparently-to") == 0)
NoRecipientAction
= NRA_ADD_APPARENTLY_TO
;
else if (strcasecmp(val
, "add-bcc") == 0)
NoRecipientAction
= NRA_ADD_BCC
;
else if (strcasecmp(val
, "add-to-undisclosed") == 0)
NoRecipientAction
= NRA_ADD_TO_UNDISCLOSED
;
syserr("Invalid NoRecipientAction: %s", val
);
case O_SAFEFILEENV
: /* chroot() environ for writing to files */
SafeFileEnv
= newstr(val
);
case O_MAXMSGSIZE
: /* maximum message size */
MaxMessageSize
= atol(val
);
case O_COLONOKINADDR
: /* old style handling of colon addresses */
ColonOkInAddr
= atobool(val
);
case O_MAXQUEUERUN
: /* max # of jobs in a single queue run */
case O_MAXCHILDREN
: /* max # of children of daemon */
case O_KEEPCNAMES
: /* don't expand CNAME records */
DontExpandCnames
= atobool(val
);
if (isascii(opt
) && isprint(opt
))
printf("Warning: option %c unknown\n", opt
);
printf("Warning: option 0x%x unknown\n", opt
);
** SETEXTOPTION -- set extended option
** This is a bogus attempt to do what sendmail should have done
** in the first place. Parses "name=value" options.
** opt -- pointer to the string option.
** safe -- from setoption.
** sticky -- from setoption.
char *xo_name
; /* option name */
short xo_code
; /* option code */
short xo_flags
; /* flag bits */
#define XOF_SAFE 0x0001 /* this option is safe */
#define XOF_STICKY 0x0100 /* this option has been given & is sticky */
struct extopts ExtOpts
[] =
#define XO_TRYNULLMXLIST 1
"trynullmxlist", XO_TRYNULLMXLIST
, XOF_SAFE
,
setextoption(opt
, safe
, sticky
, e
)
register struct extopts
*xo
;
for (xo
= ExtOpts
; xo
->xo_name
!= NULL
; xo
++)
if (strcasecmp(xo
->xo_name
, opt
) == 0)
if (!sticky
&& bitset(XOF_STICKY
, xo
->xo_flags
))
if (!safe
&& !bitset(XOF_SAFE
, xo
->xo_flags
))
if (RealUid
!= geteuid())
TryNullMXList
= atobool(val
);
syserr("Unknown extended option \"%s\"", opt
);
xo
->xo_flags
|= XOF_STICKY
;
** SETCLASS -- set a string into a class
** class -- the class to put the string in.
** str -- the string to enter
** puts the word into the symbol table.
printf("setclass(%s, %s)\n", macname(class), str
);
s
= stab(str
, ST_CLASS
, ST_ENTER
);
setbitn(class, s
->s_class
);
** MAKEMAPENTRY -- create a map entry
** line -- the config file line
** A pointer to the map that has been created.
** NULL if there was a syntax error.
** Enters the map into the dictionary.
for (p
= line
; isascii(*p
) && isspace(*p
); p
++)
if (!(isascii(*p
) && isalnum(*p
)))
syserr("readcf: config K line: no map name");
while ((isascii(*++p
) && isalnum(*p
)) || *p
== '.')
while (isascii(*p
) && isspace(*p
))
if (!(isascii(*p
) && isalnum(*p
)))
syserr("readcf: config K line, map %s: no map class", mapname
);
while (isascii(*++p
) && isalnum(*p
))
while (isascii(*p
) && isspace(*p
))
class = stab(classname
, ST_MAPCLASS
, ST_FIND
);
syserr("readcf: map %s: class %s not available", mapname
, classname
);
s
= stab(mapname
, ST_MAP
, ST_ENTER
);
s
->s_map
.map_class
= &class->s_mapclass
;
s
->s_map
.map_mname
= newstr(mapname
);
if (class->s_mapclass
.map_parse(&s
->s_map
, p
))
s
->s_map
.map_mflags
|= MF_VALID
;
printf("map %s, class %s, flags %x, file %s,\n",
s
->s_map
.map_mname
, s
->s_map
.map_class
->map_cname
,
s
->s_map
.map_file
== NULL
? "(null)" : s
->s_map
.map_file
);
printf("\tapp %s, domain %s, rebuild %s\n",
s
->s_map
.map_app
== NULL
? "(null)" : s
->s_map
.map_app
,
s
->s_map
.map_domain
== NULL
? "(null)" : s
->s_map
.map_domain
,
s
->s_map
.map_rebuild
== NULL
? "(null)" : s
->s_map
.map_rebuild
);
** STRTORWSET -- convert string to rewriting set number
** p -- the pointer to the string to decode.
** endp -- if set, store the trailing delimiter here.
** stabmode -- ST_ENTER to create this entry, ST_FIND if
** it must already exist.
** The appropriate ruleset number.
** -1 if it is not valid (error already printed)
strtorwset(p
, endp
, stabmode
)
static int nextruleset
= MAXRWSETS
;
while (isascii(*p
) && isspace(*p
))
syserr("invalid ruleset name: \"%.20s\"", p
);
ruleset
= strtol(p
, endp
, 10);
if (ruleset
>= MAXRWSETS
/ 2 || ruleset
< 0)
syserr("bad ruleset %d (%d max)",
while (*p
!= '\0' && isascii(*p
) &&
(isalnum(*p
) || strchr("-_$", *p
) != NULL
))
if (q
== p
|| !isalpha(*q
))
/* no valid characters */
syserr("invalid ruleset name: \"%.20s\"", q
);
while (isascii(*p
) && isspace(*p
))
s
= stab(q
, ST_RULESET
, stabmode
);
syserr("unknown ruleset %s", q
);
if (stabmode
== ST_ENTER
&& delim
== '=')
ruleset
= strtol(++p
, endp
, 10);
if (ruleset
>= MAXRWSETS
/ 2 || ruleset
< 0)
syserr("bad ruleset %s = %d (%d max)",
q
, ruleset
, MAXRWSETS
/ 2);
else if ((ruleset
= --nextruleset
) < MAXRWSETS
/ 2)
syserr("%s: too many named rulesets (%d max)",
if (s
->s_ruleset
> 0 && ruleset
>= 0 && ruleset
!= s
->s_ruleset
)
syserr("%s: ruleset changed value (old %d, new %d)",
q
, ruleset
, s
->s_ruleset
);
** INITTIMEOUTS -- parse and set timeout values
** val -- a pointer to the values. If NULL, do initial
** Initializes the TimeOuts structure
extern time_t convtime();
TimeOuts
.to_initial
= (time_t) 5 MINUTES
;
TimeOuts
.to_helo
= (time_t) 5 MINUTES
;
TimeOuts
.to_mail
= (time_t) 10 MINUTES
;
TimeOuts
.to_rcpt
= (time_t) 1 HOUR
;
TimeOuts
.to_datainit
= (time_t) 5 MINUTES
;
TimeOuts
.to_datablock
= (time_t) 1 HOUR
;
TimeOuts
.to_datafinal
= (time_t) 1 HOUR
;
TimeOuts
.to_rset
= (time_t) 5 MINUTES
;
TimeOuts
.to_quit
= (time_t) 2 MINUTES
;
TimeOuts
.to_nextcommand
= (time_t) 1 HOUR
;
TimeOuts
.to_miscshort
= (time_t) 2 MINUTES
;
TimeOuts
.to_ident
= (time_t) 30 SECONDS
;
TimeOuts
.to_ident
= (time_t) 0 SECONDS
;
TimeOuts
.to_fileopen
= (time_t) 60 SECONDS
;
while (isascii(*val
) && isspace(*val
))
for (p
= val
; *p
!= '\0' && *p
!= ','; p
++)
if (isascii(*val
) && isdigit(*val
))
/* old syntax -- set everything */
TimeOuts
.to_mail
= convtime(val
, 'm');
TimeOuts
.to_rcpt
= TimeOuts
.to_mail
;
TimeOuts
.to_datainit
= TimeOuts
.to_mail
;
TimeOuts
.to_datablock
= TimeOuts
.to_mail
;
TimeOuts
.to_datafinal
= TimeOuts
.to_mail
;
TimeOuts
.to_nextcommand
= TimeOuts
.to_mail
;
register char *q
= strchr(val
, ':');
if (q
== NULL
&& (q
= strchr(val
, '=')) == NULL
)
** SETTIMEOUT -- set an individual timeout
** name -- the name of the timeout.
** val -- the value of the timeout.
extern time_t convtime();
if (strcasecmp(name
, "initial") == 0)
TimeOuts
.to_initial
= to
;
else if (strcasecmp(name
, "mail") == 0)
else if (strcasecmp(name
, "rcpt") == 0)
else if (strcasecmp(name
, "datainit") == 0)
TimeOuts
.to_datainit
= to
;
else if (strcasecmp(name
, "datablock") == 0)
TimeOuts
.to_datablock
= to
;
else if (strcasecmp(name
, "datafinal") == 0)
TimeOuts
.to_datafinal
= to
;
else if (strcasecmp(name
, "command") == 0)
TimeOuts
.to_nextcommand
= to
;
else if (strcasecmp(name
, "rset") == 0)
else if (strcasecmp(name
, "helo") == 0)
else if (strcasecmp(name
, "quit") == 0)
else if (strcasecmp(name
, "misc") == 0)
TimeOuts
.to_miscshort
= to
;
else if (strcasecmp(name
, "ident") == 0)
else if (strcasecmp(name
, "fileopen") == 0)
TimeOuts
.to_fileopen
= to
;
else if (strcasecmp(name
, "queuewarn") == 0)
if (p
== NULL
|| strcmp(p
, "*") == 0)
TimeOuts
.to_q_warning
[TOC_NORMAL
] = to
;
TimeOuts
.to_q_warning
[TOC_URGENT
] = to
;
TimeOuts
.to_q_warning
[TOC_NONURGENT
] = to
;
else if (strcasecmp(p
, "normal") == 0)
TimeOuts
.to_q_warning
[TOC_NORMAL
] = to
;
else if (strcasecmp(p
, "urgent") == 0)
TimeOuts
.to_q_warning
[TOC_URGENT
] = to
;
else if (strcasecmp(p
, "non-urgent") == 0)
TimeOuts
.to_q_warning
[TOC_NONURGENT
] = to
;
syserr("settimeout: invalid queuewarn subtimeout %s", p
);
else if (strcasecmp(name
, "queuereturn") == 0)
if (p
== NULL
|| strcmp(p
, "*") == 0)
TimeOuts
.to_q_return
[TOC_NORMAL
] = to
;
TimeOuts
.to_q_return
[TOC_URGENT
] = to
;
TimeOuts
.to_q_return
[TOC_NONURGENT
] = to
;
else if (strcasecmp(p
, "normal") == 0)
TimeOuts
.to_q_return
[TOC_NORMAL
] = to
;
else if (strcasecmp(p
, "urgent") == 0)
TimeOuts
.to_q_return
[TOC_URGENT
] = to
;
else if (strcasecmp(p
, "non-urgent") == 0)
TimeOuts
.to_q_return
[TOC_NONURGENT
] = to
;
syserr("settimeout: invalid queuereturn subtimeout %s", p
);
syserr("settimeout: invalid timeout %s", name
);