/* $Id: config.C,v 1.43 2006/03/23 07:30:38 dm Exp $ */
/*
*
* Copyright (C) 2004 David Mazieres (dm@uun.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
*/
#include "asmtpd.h"
#include <grp.h>
str config_file (ETCDIR "/asmtpd.conf");
options *opt (New options);
static const options::ofunc ovec[] = {
#define mkopt(opt) { #opt, &options::do_##opt },
mkopt (bindaddr)
mkopt (trustednet)
mkopt (trusteddomain)
mkopt (separator)
mkopt (maxmsgsperip)
mkopt (maxerrorsperip)
mkopt (maxmsgsperuser)
mkopt (smtpfilter)
mkopt (rbl)
mkopt (sendmail)
mkopt (avengeruser)
mkopt (nocheck)
mkopt (env)
{ "spffail", &options::do_spfxxx },
{ "spfnone", &options::do_spfxxx },
{ "spflocal", &options::do_spfxxx },
{ "spfexp", &options::do_spfxxx },
#undef mkopt
};
static const u_int novec = sizeof (ovec) / sizeof (ovec[0]);
options::options ()
: configno (1), hostname (myname ()), logpriority ("mail.info"),
smtp_timeout (30), data_timeout (300), client_timeout (300),
vrfy_delay (2), vrfy_cachetime (300),
max_clients (60), max_revclients (60),
max_rcpts (5), max_relay_rcpts (0),
max_msgsize (100 * 1024 * 1024), con_max_per_ip (10),
msg_max_per_ip (1000000), msg_rate_per_ip (1000000),
err_max_per_ip (1000000), err_rate_per_ip (1000000),
msg_max_per_user (1000), msg_rate_per_user (300),
separator ('\0'), synfp (true), netpath (true), smtpcb (true),
debug_smtpd (false), debug_smtpc (false), debug_avenger (false),
user_mail (false), user_rcpt (true), allow_percent (false),
allow_dnsfail (0), synfp_wait (500), synfp_buf (100),
osguess_mtu (1500),
spf_exp ("See http://www.openspf.org/why.html?sender=%{S}&ip=%{I}"),
mxlocal_rcpt (false), emptysender (""),
sendmailpriv (false), sendmailfromline (false), warn_filter_clean (0),
av_user (NULL), avenger_max_per_user (5), avenger_timeout (600)
#ifdef SASL
, sasl (0)
#endif /* SASL */
#ifdef STARTTLS
, ssl_keylen (2048), ssl_status (0),
// ssl_ciphers ("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"),
ssl (1)
#endif /* STARTTLS */
{
ipmask &lm = trustednets.push_back ();
lm.net = htonl (INADDR_LOOPBACK);
lm.mask = htonl (0xffffffff);
if (str sm = find_program ("sendmail")) {
sendmail.push_back (sm);
sendmail.push_back ("-oi");
sendmail.push_back ("-os");
sendmail.push_back ("-oee");
}
}
options::~options ()
{
delpw (av_user);
}
passwd *
options::copypw (passwd *pw)
{
struct passwd *npw = static_cast<passwd *> (xmalloc (sizeof (*npw)));
bzero (npw, sizeof (*npw));
npw->pw_name = xstrdup (pw->pw_name);
//npw->pw_passwd = xstrdup (pw->pw_passwd);
npw->pw_passwd = xstrdup ("*");
npw->pw_uid = pw->pw_uid;
npw->pw_gid = pw->pw_gid;
#ifdef HAVE_PASSWD_PW_CLASS
npw->pw_class = xstrdup (pw->pw_class);
#endif /* HAVE_PASSWD_PW_CLASS */
npw->pw_gecos = xstrdup (pw->pw_gecos);
npw->pw_dir = xstrdup (pw->pw_dir);
npw->pw_shell = xstrdup (pw->pw_shell);
#ifdef HAVE_PASSWD_PW_EXPIRE
npw->pw_expire = pw->pw_expire;
#endif /* HAVE_PASSWD_PW_EXPIRE */
return npw;
}
void
options::delpw (passwd *pw)
{
xfree (pw->pw_name);
xfree (pw->pw_passwd);
#ifdef HAVE_PASSWD_PW_CLASS
xfree (pw->pw_class);
#endif /* HAVE_PASSWD_PW_CLASS */
xfree (pw->pw_gecos);
xfree (pw->pw_dir);
xfree (pw->pw_shell);
delete pw;
}
str
options::do_line (vec<str> &av, str cf, int line, bool *errp, conftab *ctp)
{
for (u_int i = 0; i < novec; i++)
if (!strcasecmp (ovec[i].name, av[0])) {
str err = (this->*(ovec[i].fn)) (av);
if (err)
*errp = true;
return err;
}
if (ctp->match (av, cf, line, errp))
return NULL;
return strbuf () << "unknown directive " << av[0];
}
str
options::do_bindaddr (vec<str> &av)
{
sockaddr_in sin;
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
u_int16_t port = 0;
if (av.size () < 2 || av.size () > 3
|| inet_aton (av[1], &sin.sin_addr) != 1
|| (av.size () == 3 && (!convertint (av[2], &port) || !port)))
return "usage: BindAddr ip-addr [port]";
sin.sin_port = htons (port ? port : 25);
if (bindaddrh.insert (sin))
bindaddrv.push_back (sin);
return NULL;
}
str
options::do_trustednet (vec<str> &av)
{
static rxx tnrx ("(\\d+\\.\\d+\\.\\d+\\.\\d+)/(\\d+)");
in_addr addr;
int bits;
if (av.size () != 2 || !tnrx.match (av[1])
|| inet_aton (tnrx[1], &addr) != 1
|| !convertint (tnrx[2], &bits)
|| bits > 32)
return "usage: TrustedNet ip-addr/preflen";
ipmask mask;
mask.net = addr.s_addr;
mask.mask = htonl ((u_int32_t) 0xffffffff << (32 - bits));
trustednets.push_back (mask);
return NULL;
}
str
options::do_trusteddomain (vec<str> &av)
{
static rxx tdrx ("([\\w_\\-]+\\.)+[\\w\\-]*[a-zA-Z]");
if (av.size () != 2 || !tdrx.match (av[1]))
return "usage: TrustedDomain domain.name";
trusteddomains.push_back (av[1]);
return NULL;
}
str
options::do_separator (vec<str> &av)
{
if (av.size () == 1) {
separator = '\0';
return NULL;
}
if (av.size () == 2 && av[1].len () < 2) {
separator = *av[1].cstr ();
return NULL;
}
return "usage: Separator [<char>]";
}
str
options::do_maxmsgsperip (vec<str> &av)
{
if (av.size () == 2 && convertint (av[1], &msg_rate_per_ip))
msg_max_per_ip = msg_rate_per_ip;
else if (av.size () != 3
|| !convertint (av[1], &msg_rate_per_ip)
|| !convertint (av[2], &msg_max_per_ip))
return "usage: MaxMsgsPerIp <messages-per-hour> [<hard-limit>]";
return NULL;
}
str
options::do_maxerrorsperip (vec<str> &av)
{
if (av.size () == 2 && convertint (av[1], &err_rate_per_ip))
err_max_per_ip = err_rate_per_ip;
else if (av.size () != 3
|| !convertint (av[1], &err_rate_per_ip)
|| !convertint (av[2], &err_max_per_ip))
return "usage: MaxErrorsPerIp <errors-per-hour> [<hard-limit>]";
return NULL;
}
str
options::do_maxmsgsperuser (vec<str> &av)
{
if (av.size () == 2 && convertint (av[1], &msg_rate_per_user))
msg_max_per_user = msg_rate_per_user;
else if (av.size () != 3
|| !convertint (av[1], &msg_rate_per_user)
|| !convertint (av[2], &msg_max_per_user))
return "usage: MaxMsgsPerUser <messages-per-hour> [<hard-limit>]";
return NULL;
}
str
options::do_smtpfilter (vec<str> &av)
{
if (av.size () != 2 || !av[1].len ())
return "usage: SMTPFilter <path-to-program>";
smtp_filter = av[1];
return NULL;
}
str
options::do_rbl (vec<str> &av)
{
static const str usage ("usage: RBL [-i] [-p] [-f] [-w]"
" [-s <score>] <domain>");
if (av.size () < 2)
return usage;
u_int flags = 0;
int score = 0;
for (u_int i = 1, e = av.size () - 1; i < e; i++) {
if (av[i].len () != 2 || av[i][0] != '-')
return usage;
switch (av[i][1]) {
case 'i':
flags |= rbl::QUERY_IP;
break;
case 'p':
flags |= rbl::QUERY_PTR;
break;
case 'f':
flags |= rbl::QUERY_ENV;
break;
case 'w':
flags |= rbl::TRUSTED;
break;
case 's':
if (i + 2 > e || !convertint (av[i+1], &score))
return usage;
i++;
break;
default:
return usage;
}
}
if (!(flags & (rbl::QUERY_IP|rbl::QUERY_PTR|rbl::QUERY_ENV)))
flags |= rbl::QUERY_IP;
str domain = av.back ();
if (!domain.len () || domain[0] == '-')
return usage;
rbls.push_back (New refcounted<rbl> (domain, flags, score));
return NULL;
}
str
options::do_sendmail (vec<str> &av)
{
av.pop_front ();
if (av.empty ())
return "usage: Sendmail <program> [<arg> ...]";
str fullpath = find_program (av[0]);
if (!fullpath)
return strbuf () << "could not find program " << av[0];
av[0] = fullpath;
swap (sendmail, av);
return NULL;
}
str
options::do_avengeruser (vec<str> &av)
{
if (av.size () != 2)
return "usage: AvengerUser <username>";
passwd *pw = getpwnam (av[1]);
if (!pw)
return av[1] << ": no such user";
if (av_user)
delpw (av_user);
av_user = copypw (pw);
return NULL;
}
str
options::do_nocheck (vec<str> &av)
{
if (av.size () != 2)
return "usage: NoCheck user[@host]";
nocheck.insert (mytolower (av[1]));
return NULL;
}
str
options::do_env (vec<str> &av)
{
static rxx var ("^([^=\\s]+)(=(.*))?$");
if (av.size () != 2 || !var.match (av[1]))
return ("usage: Env var[=value]");
if (var[1] == "PWD")
return "Env: illegal special variable name PWD";
if (var[3]) {
env.push_back (av[1]);
envb.insert (var[1]);
}
else if (char *p = getenv (var[1])) {
env.push_back (strbuf () << var[1] << "=" << p);
envb.insert (var[1]);
}
return NULL;
}
str
options::do_spfxxx (vec<str> &av)
{
str *sp = NULL;
if (!strcasecmp (av[0], "spffail"))
sp = &spf_fail;
else if (!strcasecmp (av[0], "spfnone"))
sp = &spf_none;
else if (!strcasecmp (av[0], "spflocal"))
sp = &spf_local;
else if (!strcasecmp (av[0], "spfexp"))
sp = &spf_exp;
else
panic ("unknown spfxxx %s\n", av[0].cstr ());
if (av.size () <= 1)
*sp = NULL;
else {
av.pop_front ();
*sp = join (" ", av);
}
return NULL;
}
bool
parseconfig (options *op, str cf)
{
parseargs pa (cf);
bool errors = false;
op->configno = opt->configno + 1;
conftab ct;
ct
.add ("Hostname", &op->hostname, false)
.add ("LogPriority", &op->logpriority, false)
.add ("SMTPTimeout", &op->smtp_timeout, 0, 24 * 60 * 60)
.add ("DataTimeout", &op->data_timeout, 0, 24 * 60 * 60)
.add ("ClientTimeout", &op->client_timeout, 0, 24 * 60 * 60)
.add ("VrfyDelay", &op->vrfy_delay, 0, 24 * 60 * 60)
.add ("VrfyCacheTime", &op->vrfy_cachetime, 0, 0x7fffffff)
.add ("MaxClients", &op->max_clients, 1, 0x10000)
.add ("MaxRevClients", &op->max_revclients, 0, 0x10000)
.add ("MaxRcpts", &op->max_rcpts, 1, 1024)
.add ("MaxRelayRcpts", &op->max_relay_rcpts, 0, 65536)
.add ("MaxMsgSize", &op->max_msgsize, 1024, 0x7fffffff)
.add ("SynFp", &op->synfp)
.add ("SynOsMTU", &op->osguess_mtu, 0, 0x10000)
.add ("NetPath", &op->netpath)
.add ("SMTPCB", &op->smtpcb)
.add ("DebugSMTP", &op->debug_smtpd)
.add ("DebugSMTPc", &op->debug_smtpc)
.add ("DebugAvenger", &op->debug_avenger)
.add ("UserMail", &op->user_mail)
.add ("UserRcpt", &op->user_rcpt)
.add ("AllowPercent", &op->allow_percent)
.add ("AllowDNSFail", &op->allow_dnsfail, 0, 2)
.add ("SynFpWait", &op->synfp_wait, 0, 3600000)
.add ("SynFpBuf", &op->synfp_buf, 0, 0x100000)
.add ("MXLocalRcpt", &op->mxlocal_rcpt)
.add ("SendmailPriv", &op->sendmailpriv)
.add ("SendmailFromLine", &op->sendmailfromline)
.add ("MaxConPerIp", &op->con_max_per_ip, 1, 0x10000)
.add ("EmptySender", &op->emptysender, false)
.add ("EtcDir", &op->etcdir, false)
.add ("AliasFile", &op->alias_file, false)
.add ("DomainFile", &op->domain_file, false)
.add ("SPFhostsFile", &op->spfhosts_file, false)
#ifdef SASL
.add ("SASL", &op->sasl, 0, 2)
#endif /* SASL */
#ifdef STARTTLS
.add ("SSL", &op->ssl, 0, 2)
.add ("SSLCAcert", &op->ssl_ca, false)
.add ("SSLCRL", &op->ssl_crl, false)
.add ("SSLcert", &op->ssl_cert, false)
.add ("SSLkey", &op->ssl_key, false)
.add ("SSLciphers", &op->ssl_ciphers, false)
#endif /* !STARTTLS */
.add ("AvengerMaxPerUser", &op->avenger_max_per_user, 1, 0x10000)
.add ("AvengerTimeout", &op->avenger_timeout, 0, 24 * 60 * 60)
;
int line;
vec<str> av;
while (pa.getline (&av, &line))
if (str err = op->do_line (av, cf, line, &errors, &ct))
warn << cf << ":" << line << ": " << err << "\n";
#if 0
if (!op->rcptdomains.size () && !op->mxlocal_rcpt) {
//warn ("no RcptDomain directives, enabling MXLocalRcpt\n");
op->mxlocal_rcpt = true;
}
#endif
if (!errors && op->sendmail.empty ()) {
warn ("no sendmail program specified or found\n");
errors = true;
}
if (!op->av_user) {
passwd *pw = getpwnam (AVENGER);
if (!pw) {
warn ("no " AVENGER " user found on system\n");
errors = true;
}
else
op->av_user = options::copypw (pw);
}
if (op->av_user) {
bhash <GROUPLIST_T> cache;
setgrent ();
while (group *gr = getgrent ()) {
if (cache[gr->gr_gid])
continue;
for (char **mp = gr->gr_mem; *mp; mp++)
if (!strcmp (*mp, op->av_user->pw_name)) {
cache.insert (gr->gr_gid);
op->av_groups.push_back (gr->gr_gid);
if (op->av_groups.size () > NGROUPS_MAX)
goto done;
break;
}
}
done:;
}
if (!op->etcdir)
op->etcdir = ETCDIR;
#define fixpath(field, default) \
do { \
if (!op->field) \
op->field = op->etcdir << "/" default; \
else if (op->field[0] != '/') \
op->field = op->etcdir << "/" << op->field; \
} while (0)
fixpath (alias_file, "aliases");
fixpath (domain_file, "domains");
fixpath (spfhosts_file, "spfhosts");
#ifdef STARTTLS
fixpath (ssl_ca, "cacert.pem");
fixpath (ssl_crl, "crl.pem");
fixpath (ssl_cert, "cert.pem");
fixpath (ssl_key, "privkey.pem");
#endif /* STARTTLS */
#undef fixpath
if (op->bindaddrv.empty ()) {
sockaddr_in sin;
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (25);
sin.sin_addr.s_addr = htonl (INADDR_ANY);
op->bindaddrv.push_back (sin);
op->bindaddrh.insert (sin);
}
return !errors;
}
void
maybe_warn (str msg)
{
if (opt_verbose) {
warn << msg;
return;
}
if (time_t *tp = opt->warn_filter[msg])
if (timenow < *tp + 600)
return;
opt->warn_filter.insert (msg, timenow);
warn << msg;
if (timenow < opt->warn_filter_clean)
return;
opt->warn_filter_clean = timenow + 600;
qhash<str, time_t>::slot *slp, *nslp;
for (slp = opt->warn_filter.first (); slp; slp = nslp) {
nslp = opt->warn_filter.next (slp);
if (slp->value + 600 <= timenow)
opt->warn_filter.remove (slp->key);
}
}
syntax highlighted by Code2HTML, v. 0.9.1