// -*-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 #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 h; ptr rs; str dns_error; newcon (int fd, const sockaddr_in &sin); void init (); private: int cbpending; bool failed; void ident_cb (str nn, ptr hh, int err); void ptr_cb (ptr 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 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 results; vec errors; rbl_status () : trusted (false), score (0) {} void addresult (const rbl *rp, in_addr val); }; void rbl_check_con (ref rs, const vec > &rblv, in_addr addr, str hostname, cbv cb); void rbl_check_env (ref rs, const vec > &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 &); }; 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 bindaddrv; bhash 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 trustednets; vec trusteddomains; bool mxlocal_rcpt; bhash nocheck; vec > rbls; vec sendmail; str emptysender; bool sendmailpriv; bool sendmailfromline; bhash envb; vec env; qhash warn_filter; time_t warn_filter_clean; passwd *av_user; vec 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 &av, str cf, int line, bool *errp, conftab *ctp); public: static passwd *copypw (passwd *pw); static void delpw (passwd *pw); str do_bindaddr (vec &av); str do_trustednet (vec &av); str do_trusteddomain (vec &av); str do_separator (vec &av); str do_maxmsgsperip (vec &av); str do_maxerrorsperip (vec &av); str do_maxmsgsperuser (vec &av); str do_smtpfilter (vec &av); str do_rbl (vec &av); str do_sendmail (vec &av); str do_avengeruser (vec &av); str do_nocheck (vec &av); str do_env (vec &av); str do_spfxxx (vec &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 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 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 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 rblcon; str dns_error; str helohost; str fromaddr; ptr rblenv; bool mask_spf; spf_result spfr; str spf_expl; str spf_mech; str bounce_res; str mail_error; ptr mxl; str auth_user; #ifdef SASL sasl_conn_t *sasl; vec saslstr; #endif /* SASL */ str quota_user; vec 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 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 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 *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 ptr_cache; list_entry 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 rs, ptr 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 get_mxl () const { return mxl; } ptr get_rbl () const { return rblenv; } void envinit (vec *envp, struct passwd *pw) const; void maybe_shutdown (); }; extern list smtplist; /* runprog.C */ struct progout { int status; vec output; progout () : status (-1) {} explicit progout (str line) : status (-1) { output.push_back (line); } str response (int errcode); }; struct rpstate; typedef callback >::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 waiters; bool release_lock; ihash_entry link; avcount (uid_t u); ~avcount (); bool acquire (); void release (); static avcount *get (uid_t u); }; extern ihash 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::ref cb_t; private: struct result { str res; tailq_entry link; cbv::ptr abortcb; ~result () { if (abortcb && !res) (*abortcb) (); } }; const cb_t cb; const smtpd *const smtp; pid_t pid; const ref aio; const str name; avcount *const avc; tailq 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 h, int err); void dns_ptr_cb (str var, result *rp, ptr h, int err); void dns_mx_cb (str var, result *rp, ptr mxl, int err); void dns_txt_cb (str var, result *rp, ptr t, int err); void netpath_cb1 (str var, int hops, result *rp, ptr 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 &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 &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 *top; rpstate *rps; void smcb (str sm, cbs cb, ref po); public: static vec 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 &to, str received_line); void writev (suio *uiop, cbs); void commit (cbs); int getfd (); str name () { return path ? path : str (""); } }; /* vrfy.C */ typedef callback >::ref vrfycb_t; void vrfy (in_addr bindaddr, str addr, in_addr cli, vrfycb_t cb); /* rcptcheck.C */ struct map_base { qhash 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 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 = 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::ref cb_t; cb_t::ptr cb; const in_addr addr; const str sender; ptr 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 > loopcheck; int ptr_cache_err; vec 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 h); bool ptrok () { return ptr_cache || ptr_cache_err; } void getptr (cbv cb); void getptr_2 (cbv cb, ptr h, int err); friend void spf_cancel (spf_t *); ~spf_t (); void finish (spf_result stat); void getexp (cbv cb, ptr t, int err); void gettxt (ptr 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 h, int err); void mech_mx (str targ); void mech_mx_2 (int cidrlen, ptr mxl, int err); void mech_mx_3 (int cidrlen, ptr mxl, int n, ptr 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 h, int err); public: spf_t (in_addr addr, str from, ptr > loopcheck = NULL, int recdepth = 0); void init (); }; inline void spf_cancel (spf_t *spf) { delete spf; } typedef callback::ref spfckcb_t; extern spfhosts_map smap; void spf_check (in_addr a, str from, spfckcb_t cb, str helo = NULL, ptr 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 */