/* $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 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 (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 &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 &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 &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 &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 &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 []"; } str options::do_maxmsgsperip (vec &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 []"; return NULL; } str options::do_maxerrorsperip (vec &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 []"; return NULL; } str options::do_maxmsgsperuser (vec &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 []"; return NULL; } str options::do_smtpfilter (vec &av) { if (av.size () != 2 || !av[1].len ()) return "usage: SMTPFilter "; smtp_filter = av[1]; return NULL; } str options::do_rbl (vec &av) { static const str usage ("usage: RBL [-i] [-p] [-f] [-w]" " [-s ] "); 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 (domain, flags, score)); return NULL; } str options::do_sendmail (vec &av) { av.pop_front (); if (av.empty ()) return "usage: Sendmail [ ...]"; 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 &av) { if (av.size () != 2) return "usage: AvengerUser "; 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 &av) { if (av.size () != 2) return "usage: NoCheck user[@host]"; nocheck.insert (mytolower (av[1])); return NULL; } str options::do_env (vec &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 &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 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 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::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); } }