Logo Search packages:      
Sourcecode: mailavenger version File versions  Download package

asmtpd.h

// -*-c++-*-
/* $Id$ */

/*
 *
 * 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 logtag;
  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;
  int 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;

  unsigned ident_timeout;
  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;
  int insecuresasl;
#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 msgid;
  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 ();

  static str msgid_gen ();
  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 */

Generated by  Doxygen 1.6.0   Back to index