// -*-c++-*-
/* $Id: asmtpd.h,v 1.62 2006/04/03 18:21:47 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 "async.h"
#include "dns.h"
#include "parseopt.h"
#include "rxx.h"
#include "aios.h"
#include "ihash.h"
#include "list.h"
#include "qhash.h"
#ifdef SASL
#include <sasl.h>
#endif /* SASL */
#define AVENGER "avenger"
#define MAX_ADDR_LEN 1024 /* RFC 821 says 256, so this should do */
enum trust_level {
TRUST_NONE = 0, // Default sanity-checking
TRUST_MAIL = 1, // Allow forged sender address (e.g., evite)
TRUST_AUTH = 2, // Allow mail relaying for authenticated user
TRUST_RCPT = 3, // Allow mail relaying from known address
TRUST_LOCAL = 4, // No mail quotas (localhost)
};
enum spf_result {
SPF_INVALID, // An invalid return state
SPF_PASS,
SPF_FAIL,
SPF_SOFTFAIL,
SPF_NEUTRAL,
SPF_NONE,
SPF_ERROR,
SPF_UNKNOWN,
};
struct spf_t;
/* asmtpd.C */
struct synfp_collect;
struct rbl_status;
struct ipinfo;
struct newcon {
const sockaddr_in sin;
const int fd;
str name;
ipinfo *ii;
str synfp;
trust_level t;
ptr<hostent> h;
ptr<rbl_status> rs;
str dns_error;
newcon (int fd, const sockaddr_in &sin);
void init ();
private:
int cbpending;
bool failed;
void ident_cb (str nn, ptr<hostent> hh, int err);
void ptr_cb (ptr<hostent> hh, int err);
void rbl_cb ();
void synfp_cb (str fp);
void maybe_start ();
};
extern int opt_verbose;
extern synfp_collect *synfpc;
extern str path_avenger;
extern str path_bindir;
extern str path_pfos;
extern bool terminated;
void toggle_listen (bool force = false);
/* rbl.C */
struct rbl {
enum {
QUERY_IP = 0x1, // Lookup reversed IP address (normal RBLs)
QUERY_PTR = 0x2, // Lookup domain name, not IP address
QUERY_ENV = 0x4, // Lookup domain name in envelope sender
TRUSTED = 0x8, // Whitelist (e.g., wl.trusted-forwarder.org)
};
const str domain; // Domain in which to perform lookups
const u_int flags;
const int score;
rbl (str domain, u_int flags, int score);
};
struct rbl_status {
struct result {
str name;
vec<in_addr,1> vals;
void addval (in_addr addr);
bool hasval (in_addr addr) const;
str tostr (bool env) const;
};
struct rblerr {
str name;
int error;
rblerr (str n, int e) : name (n), error (e) {}
};
bool trusted;
int score; // Score contributed by IP and PTR queries
vec<result> results;
vec<rblerr> errors;
rbl_status () : trusted (false), score (0) {}
void addresult (const rbl *rp, in_addr val);
};
void rbl_check_con (ref<rbl_status> rs, const vec<ref<rbl> > &rblv,
in_addr addr, str hostname, cbv cb);
void rbl_check_env (ref<rbl_status> rs, const vec<ref<rbl> > &rblv,
str hostname, cbv cb);
/* config.C */
struct ipmask {
u_int32_t net;
u_int32_t mask;
};
struct options {
struct ofunc {
const char *name;
str (options::*fn) (vec<str> &);
};
u_int64_t configno;
str hostname;
str logpriority;
u_int smtp_timeout;
u_int data_timeout;
u_int client_timeout;
u_int vrfy_delay;
u_int vrfy_cachetime;
u_int max_clients;
u_int max_revclients;
u_int max_rcpts;
u_int max_relay_rcpts;
size_t max_msgsize;
u_int con_max_per_ip;
u_int msg_max_per_ip;
u_int msg_rate_per_ip;
u_int err_max_per_ip;
u_int err_rate_per_ip;
u_int msg_max_per_user;
u_int msg_rate_per_user;
str smtp_filter;
char separator;
bool synfp;
bool netpath;
bool smtpcb;
bool debug_smtpd;
bool debug_smtpc;
bool debug_avenger;
bool user_mail;
bool user_rcpt;
bool allow_percent;
int allow_dnsfail;
vec<sockaddr_in> bindaddrv;
bhash<sockaddr_in> bindaddrh;
u_int synfp_wait;
u_int synfp_buf;
u_int osguess_mtu;
str spf_fail;
str spf_none;
str spf_local;
str spf_exp;
vec<ipmask> trustednets;
vec<str> trusteddomains;
bool mxlocal_rcpt;
bhash<str> nocheck;
vec<ref<rbl> > rbls;
vec<str> sendmail;
str emptysender;
bool sendmailpriv;
bool sendmailfromline;
bhash<str> envb;
vec<str> env;
qhash<str, time_t> warn_filter;
time_t warn_filter_clean;
passwd *av_user;
vec<GETGROUPS_T> av_groups;
u_int avenger_max_per_user;
u_int avenger_timeout;
str etcdir;
str alias_file;
str domain_file;
str spfhosts_file;
#ifdef SASL
int sasl;
#endif /* SASL */
#ifdef STARTTLS
u_int ssl_keylen;
str ssl_ca;
str ssl_crl;
str ssl_cert;
str ssl_key;
int ssl_status;
str ssl_ciphers;
int ssl;
#endif /* STARTTLS */
options ();
~options ();
private:
str do_line (vec<str> &av, str cf, int line, bool *errp, conftab *ctp);
public:
static passwd *copypw (passwd *pw);
static void delpw (passwd *pw);
str do_bindaddr (vec<str> &av);
str do_trustednet (vec<str> &av);
str do_trusteddomain (vec<str> &av);
str do_separator (vec<str> &av);
str do_maxmsgsperip (vec<str> &av);
str do_maxerrorsperip (vec<str> &av);
str do_maxmsgsperuser (vec<str> &av);
str do_smtpfilter (vec<str> &av);
str do_rbl (vec<str> &av);
str do_sendmail (vec<str> &av);
str do_avengeruser (vec<str> &av);
str do_nocheck (vec<str> &av);
str do_env (vec<str> &av);
str do_spfxxx (vec<str> &av);
friend bool parseconfig (options *op, str cf);
};
extern str config_file;
extern options *opt;
bool parseconfig (options *op, str cf);
void maybe_warn (str msg);
/* addrparse.C */
str extract_addr (const char **dpp, const char *prefix);
str extract_addr (const str &in, const char *prefix);
//str extract_relay (const char *addr);
str extract_local (const char *addr);
bool validate_local (str addr);
str extract_domain (const char *addr);
str extract_relay (const char *addr);
str domain_tolower (const char *addr);
bool validate_domain (const char *addr, bool uok = false);
/* quota.C */
struct traceroute;
void run_cmd (const char *cmd, const char *arg1, const char *arg2 = NULL);
struct quota {
enum { msgmult = 60 };
time_t last;
protected:
quota ();
virtual ~quota () {}
void decay_var (u_int &var, u_int rate, u_int interval) {
u_int d = interval * rate;
var = var > d ? var - d : 0;
}
bool check_var (u_int var, u_int maxval) {
return var + msgmult <= maxval * msgmult;
}
virtual void do_decay (bool del, u_int interval) = 0;
public:
void decay (bool del = false);
void maybe_delete () { decay (true); }
};
struct ipinfo : public quota {
const in_addr addr;
bool filt;
u_int ncon;
u_int ndeliv; // msgmult * # of successful RCPT commands
u_int nerr; // msgmult * # of errors
traceroute *trp;
time_t netpath_time;
int nhops;
str netpath;
ihash_entry<ipinfo> link;
static ipinfo *lookup (in_addr a, bool create);
ipinfo (in_addr a);
~ipinfo ();
void setfilter (bool);
void setfilter ();
void do_decay (bool del, u_int interval);
void do_netpath (in_addr src);
void netpath_cb (int total_hops, in_addr *ap, int ac);
str addcon ();
void delcon () { assert (ncon); ncon--; decay (true); }
void error () { nerr += msgmult; setfilter (); }
str rcpt ();
str status ();
};
struct userinfo : public quota {
const str user;
u_int ndeliv;
ihash_entry<userinfo> link;
userinfo (str u);
~userinfo ();
void do_decay (bool del, u_int interval);
static userinfo *lookup (str user, bool create);
str rcpt ();
static str rcpt (str u) { return lookup (u, true)->rcpt (); }
};
void clear_filters ();
void quota_dump (const strbuf &sb);
/* smtpd.C */
struct enqmsg;
struct progout;
struct avcount;
class smtpd {
ipinfo *const ii;
ref<aios> aio;
const str name;
str synfp;
str osguess;
in_addr myipaddr;
u_int16_t mytcpport;
in_addr ipaddr;
u_int16_t tcpport;
bool pipelining;
bool colonspace;
bool post;
bool encrypted;
trust_level trust;
const ptr<const rbl_status> rblcon;
str dns_error;
str helohost;
str fromaddr;
ptr<rbl_status> rblenv;
bool mask_spf;
spf_result spfr;
str spf_expl;
str spf_mech;
str bounce_res;
str mail_error;
ptr<mxlist> mxl;
str auth_user;
#ifdef SASL
sasl_conn_t *sasl;
vec<str> saslstr;
#endif /* SASL */
str quota_user;
vec<str> toaddr;
bool body_set;
str body_user;
str body_cmd;
enum data_state_t { MIDLINE, NEWLINE, CR, DOT, DOTCR } data_state;
size_t data_msgsize;
str data_err;
enqmsg *data_q;
bool cmdwait;
static str datestr (bool includezone = true);
static void dispatch_tab_init ();
void reset ();
str received ();
void getcmd (str line, int err);
void respond (str resp, bool counterr = true);
void data_1 (str data, int err);
void data_2 (bool end, str err);
void data_3 (avcount *avc, ref<progout> po);
void data_4 (str err);
void cmd_mail_2 (str addr);
void cmd_mail_3 (str addr, spf_result res, str, str);
void cmd_mail_4 (str addr, str err, ptr<mxlist> mxl);
void cmd_rcpt_0 (str cmd, str arg, int, in_addr *, int);
void cmd_rcpt_2 (str addr, int err);
void cmd_rcpt_3 (str addr, str errmsg);
void cmd_rcpt_4 (str addr, str errmsg, int local);
void cmd_rcpt_5 (str addr, str errmsg, str err);
void cmd_rcpt_6 (str addr, str err);
void cmd_rset (str, str) { reset (); respond ("250 ok\r\n"); }
void cmd_mail (str, str);
void cmd_rcpt (str, str);
void cmd_data (str, str);
void cmd_vrfy (str, str) { respond ("252 try delivery with RCPT\r\n"); }
#ifdef STARTTLS
void cmd_starttls (str, str);
str helo_starttls ();
void received_starttls (strbuf r) const;
void env_starttls (vec<str> *envp) const;
void set_quota_user ();
#endif /* STARTTLS */
void cmd_auth (str, str);
#ifdef SASL
void cmd_auth_2 (int res, const char *out, unsigned outlen);
void cmd_auth_3 (str line, int err);
#endif /* SASL */
str helo_auth ();
public:
static const str okstr;
static u_int num_smtpd;
static u_int num_indata;
const ptr<const hostent> ptr_cache;
list_entry<smtpd> link;
static bool tmperr (int err);
static str line_wrap (str in);
smtpd (ipinfo *ii, int fd, const sockaddr_in &sin,
str name, str synfp, trust_level trust,
ptr<rbl_status> rs, ptr<hostent> h, str dns_error);
~smtpd ();
str bodycheck (str user, str cmd, str defresp);
in_addr get_addr () const { return ipaddr; }
str get_name () const { return name; }
str get_helo () const { return helohost; }
str get_from () const { return fromaddr; }
spf_result get_spfr () const { return spfr; }
str get_spf_expl () const { return spf_expl; }
ptr<const mxlist> get_mxl () const { return mxl; }
ptr<rbl_status> get_rbl () const { return rblenv; }
void envinit (vec<str> *envp, struct passwd *pw) const;
void maybe_shutdown ();
};
extern list<smtpd, &smtpd::link> smtplist;
/* runprog.C */
struct progout {
int status;
vec<str> output;
progout () : status (-1) {}
explicit progout (str line)
: status (-1) { output.push_back (line); }
str response (int errcode);
};
struct rpstate;
typedef callback<void, ref<progout> >::ref runprogcb_t;
rpstate *runprog (const char *prog, const char **av, int infd,
bool collect_err, runprogcb_t cb,
cbv::ptr postforkcb, const char *const *env = NULL);
void runprog_cancel (rpstate *rps);
void runprog_timeout (rpstate *rps, int sec);
struct passwd *validuser (const char *user, bool reqshell = true);
void become_user (struct passwd *pw, bool grouplist = true);
str exitstr (int status);
/* avif.C */
struct avcount {
const uid_t uid;
int num;
vec<cbv> waiters;
bool release_lock;
ihash_entry<avcount> link;
avcount (uid_t u);
~avcount ();
bool acquire ();
void release ();
static avcount *get (uid_t u);
};
extern ihash<const uid_t, avcount, &avcount::uid, &avcount::link> avctab;
class avif {
public:
enum disp_t {
DONE, // Finish immediately with status message
NEXT, // Continue on to default rules
REDIR, // Redirect to a particular user
BODY, // Run command on body of message
};
typedef callback<void, disp_t, str>::ref cb_t;
private:
struct result {
str res;
tailq_entry<result> link;
cbv::ptr abortcb;
~result () { if (abortcb && !res) (*abortcb) (); }
};
const cb_t cb;
const smtpd *const smtp;
pid_t pid;
const ref<aios> aio;
const str name;
avcount *const avc;
tailq<result, &result::link> reslist;
str retcode;
strbuf retbuf;
static void chldinit (struct passwd *pw, int fd, bool sys, str ext);
avif (const smtpd *, str name, avcount *avc, pid_t pid, int fd, cb_t cb);
~avif ();
void init ();
result *newres ()
{ result *rp = New result; reslist.insert_tail (rp); return rp; }
void delres (result *rp) { reslist.remove (rp); delete (rp); }
void input (str line, int err);
void badinput (str);
void spf_cb (str var, result *rp, bool one, spf_t *spf);
void dns_a_cb (str var, result *rp, ptr<hostent> h, int err);
void dns_ptr_cb (str var, result *rp, ptr<hostent> h, int err);
void dns_mx_cb (str var, result *rp, ptr<mxlist> mxl, int err);
void dns_txt_cb (str var, result *rp, ptr<txtlist> t, int err);
void netpath_cb1 (str var, int hops, result *rp, ptr<hostent> h, int err);
void netpath_cb2 (str var, result *rp, int nhops, in_addr *av, int an);
void maybe_reply ();
void reap (int status);
public:
static void alloc (struct passwd *pw, const smtpd *s,
str recip, char mode, avcount *, str ext,
str avuser, cb_t cb, str extraenv = NULL);
};
/* enqmsg.C */
class enqmsg {
public:
virtual ~enqmsg () {}
virtual bool init (str from, const vec<str> &to, str received_line) = 0;
virtual void writev (suio *uiop, cbs) = 0;
virtual void commit (cbs) = 0;
virtual int getfd () { return -1; }
//virtual str name () { return ""; }
};
class enqmsg_dummy : public enqmsg {
bool init (str from, const vec<str> &to, str received_line)
{ panic ("enqmsg_dummy::init\n"); }
void writev (suio *uiop, cbs cb) { (*cb) (strerror (EINVAL)); }
void commit (cbs cb) { (*cb) (strerror (EINVAL)); }
};
class enqmsg_file : public enqmsg {
int fd;
int efd;
bool error;
bool eof;
str from;
const vec<str> *top;
rpstate *rps;
void smcb (str sm, cbs cb, ref<progout> po);
public:
static vec<const char *> mini_env;
static str spooldir;
static str get_spooldir ();
static void init_mini_env ();
str path;
enqmsg_file ();
~enqmsg_file ();
bool init (str from, const vec<str> &to, str received_line);
void writev (suio *uiop, cbs);
void commit (cbs);
int getfd ();
str name () { return path ? path : str (""); }
};
/* vrfy.C */
typedef callback<void, str, ptr<mxlist> >::ref vrfycb_t;
void vrfy (in_addr bindaddr, str addr, in_addr cli, vrfycb_t cb);
/* rcptcheck.C */
struct map_base {
qhash<str, str> table;
time_t latest;
u_int64_t loadno;
map_base () : loadno (0) {}
virtual ~map_base () {}
virtual str path () = 0;
virtual rxx &linerx ();
bool load ();
};
struct domain_map : public map_base {
str path () { return opt->domain_file; }
bool lookup (str *avuser, str recip);
int hasentry (str recip) {
if (!load ())
return -1;
str d = extract_domain (recip);
return d && table[mytolower (d)];
}
};
struct localcheck {
smtpd *const smtp;
const str recip;
const cbs cb;
char mode;
bool try_user;
str unknown_user; // If user doesn't exist
str fallback_user; // If user exists and returns NULL
bhash<str> loop;
u_int depth;
str avuser;
str execuser;
bool indefault;
localcheck (smtpd *s, str recip, char mode, cbs cb);
void init ();
private:
void reply (str res, str bodycmd = NULL);
void avenge ();
void avenge_1 ();
void dodefault ();
void avenge_2 (avif::disp_t disp, str res);
str modeenv ();
};
extern domain_map dmap;
/* mode:
'r' - Run user rcpt script or unknown, then default
'R' - Run secondary script
'm' - Run user mail script, then relay
'M' - Run relay
*/
void rcptcheck (smtpd *s, str recip, char mode, cbs cb);
/* mxcheck.C */
/* Returns:
* 0 if this host is highest-priority MX record,
* -1 if this host is an MX record
* NXDOMAIN or NXREC if this host is not an MX record
* err if there is a temporary DNS error
*/
void ismxlocal (str relay, cbi cb, ptr<mxlist> = NULL);
/* spf.C */
struct spfhosts_map : public map_base {
str path () { return opt->spfhosts_file; }
rxx &linerx ();
bool lookup (str *spfrecp, str domain);
};
struct spf_t {
typedef callback<void, spf_t *>::ref cb_t;
cb_t::ptr cb;
const in_addr addr;
const str sender;
ptr<const hostent> ptr_cache;
str helo;
str domain;
spfhosts_map *fallback;
str spfrec;
str curmech;
spf_result result;
str explain;
bool has_mx;
private:
int tracedepth;
int recdepth;
ref<bhash<str> > loopcheck;
int ptr_cache_err;
vec<str> mechv;
str expdn;
str redirect;
char prefix;
dnsreq_t *dnsrq;
spf_t *recurse;
static u_int32_t ip4_mask (u_int cidrlen) {
if (cidrlen >= 32)
return 0xffffffff;
return htonl (0xffffffffU << (32 - cidrlen));
}
static bool suffix_check (const char *targ, str suffix);
bool macro_subst_inner (const strbuf &sb, str in, bool exp);
bool macro_subst (str *out, str in, bool exp = false);
bool addr_check (int cidrlen, ref<hostent> h);
bool ptrok () { return ptr_cache || ptr_cache_err; }
void getptr (cbv cb);
void getptr_2 (cbv cb, ptr<hostent> h, int err);
friend void spf_cancel (spf_t *);
~spf_t ();
void finish (spf_result stat);
void getexp (cbv cb, ptr<txtlist> t, int err);
void gettxt (ptr<txtlist> t, int err);
void parse_spf ();
void mech_start ();
void mech_end (spf_result res);
void mech_include (str targ);
void mech_include_2 (spf_t *);
void mech_a (str targ);
void mech_a_2 (int cidrlen, ptr<hostent> h, int err);
void mech_mx (str targ);
void mech_mx_2 (int cidrlen, ptr<mxlist> mxl, int err);
void mech_mx_3 (int cidrlen, ptr<mxlist> mxl, int n,
ptr<hostent> h, int err);
void mech_ptr (str targ);
void mech_ip4 (str targ);
void mech_ip6 (str targ);
void mech_exists (str targ);
void mech_exists_2 (str targ, ptr<hostent> h, int err);
public:
spf_t (in_addr addr, str from,
ptr<bhash<str> > loopcheck = NULL, int recdepth = 0);
void init ();
};
inline void
spf_cancel (spf_t *spf)
{
delete spf;
}
typedef callback<void, spf_result, str, str>::ref spfckcb_t;
extern spfhosts_map smap;
void spf_check (in_addr a, str from, spfckcb_t cb,
str helo = NULL, ptr<const hostent> ptrc = NULL);
const char *spf_print (spf_result res);
const char *spf1_print (spf_result res);
/* starttls.C */
#ifdef STARTTLS
bool ssl_init ();
#else /* !STARTTLS */
#define ssl_init()
#endif /* !STARTTLS */
syntax highlighted by Code2HTML, v. 0.9.1