/* $Id: smtpd.C,v 1.70 2006/04/01 07:47:10 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 "rawnet.h"

#ifdef STARTTLS
#include "async_ssl.h"
#define AIOS aiossl
#else /* !STARTTLS */
#define AIOS aios
#endif /* !STARTTLS */

struct smtp_dispatch {
  const char *cmd_name;
  void (smtpd::*const cmd_fn) (str /*cmd*/, str /*arg*/);
  ihash_entry<smtp_dispatch> link;

  smtp_dispatch (const char *name, void (smtpd::*fn) (str, str));
private:
  PRIVDEST ~smtp_dispatch ();	// No deleting allowed
};

const str smtpd::okstr ("250 ok\r\n");
u_int smtpd::num_smtpd;
u_int smtpd::num_indata;
static ihash<const char *, smtp_dispatch, &smtp_dispatch::cmd_name,
	     &smtp_dispatch::link> dispatch_tab;
static rxx cmdarg ("^(\\S+)(\\s*|\\s+(\\S.*))$");
list<smtpd, &smtpd::link> smtplist;

bool
smtpd::tmperr (int err)
{
  switch (err) {
  case 0:
  case EINTR:
  case EIO:
  case ENOMEM:
  case ENFILE:
  case EMFILE:
  case ENOSPC:
#ifdef EPROCLIM
  case EPROCLIM:
#endif /* EPROCLIM */
  case EDQUOT:
  case ESTALE:
  case EAGAIN:
    return true;
  default:
    return false;
  }
}

str
smtpd::line_wrap (str in)
{
  if (in.len () < 79)
    return in;
  strbuf sb;
  const char *p = in.cstr () + 79;
  const char *const e = in.cstr () + in.len ();
  while (p > in.cstr () && !isspace (p[-1]))
    p--;
  while (p > in.cstr () && isspace (p[-1]))
    p--;
  if (p == in.cstr ()) {
    p = in.cstr () + 79;
    while (p < e && !isspace (*p))
      p++;
  }

  sb.tosuio ()->print (in.cstr (), p - in.cstr ());
  sb << "\n";
  while (p < e && isspace (*p))
    p++;

  for (;;) {
    const char *q = p + min<ptrdiff_t> (e - p, 75);
    if (q != e)
      while (q > p && !isspace (q[-1]))
	q--;
    while (q > p && isspace (q[-1]))
      q--;
    if (q == p) {
      q = p + min<ptrdiff_t> (e - p, 75);
      while (q < e && !isspace (*q))
	q++;
    }
    if (q == p)
      return sb;
    sb << "    ";
    sb.tosuio ()->print (p, q - p);
    sb << "\n";
    p = q;
    while (p < e && isspace (*p))
      p++;
  }
}

str
smtpd::datestr (bool includezone)
{
  char buf[80];
  time_t now;
  int n;

  time (&now);
  if (includezone)
    n = strftime (buf, sizeof (buf) - 1, "%a, %d %b %Y %H:%M:%S %z (%Z)",
		  localtime (&now));
  else
    n = strftime (buf, sizeof (buf) - 1, "%a %b %e %Y %H:%M:%S",
		  localtime (&now));
  assert (n);
  return buf;
}

void
smtpd::dispatch_tab_init ()
{
  assert (dispatch_tab.size () == 0);
#define mkdispatch(name) vNew smtp_dispatch (#name, &smtpd::cmd_##name)
  mkdispatch (rset);
  mkdispatch (mail);
  mkdispatch (rcpt);
  mkdispatch (data);
  mkdispatch (vrfy);
  mkdispatch (auth);
#undef mkdispatch
}

void
smtpd::reset ()
{
  fromaddr = NULL;
  toaddr.clear ();
  mxl = NULL;
  body_set = false;
  body_user = NULL;
  body_cmd = NULL;
  mask_spf = false;
  spfr = SPF_PASS;
  spf_expl = NULL;
  spf_mech = NULL;
  bounce_res = NULL;
  mail_error = NULL;
}

str
smtpd::received ()
{
  strbuf r;

#if 1
  r << "From " << (fromaddr.len () ? fromaddr.cstr () : "MAILER-DAEMON")
    << " " << datestr (false) << "\n";
#endif

  r << "Received: ";
  if (helohost)
    r << "from " << helohost
      << " (" << name << " [" << inet_ntoa (ipaddr) << "])\n";
  else
    r << "(from " << name << " [" << inet_ntoa (ipaddr) << "])\n";

  if (auth_user)
    r << "    (authenticated-user " << auth_user << ")\n";
#ifdef STARTTLS
  received_starttls (r);
#endif /* STARTTLS */
  r << "    by " << opt->hostname << " with SMTP;\n";
  if (toaddr.size () == 1)
    r << "    for " << toaddr[0] << ";\n";
  r << "    " << datestr () << "\n";
  r << "    (envelope-from "
    << (fromaddr.len () ? fromaddr : str ("<>")) << ")\n";

  if (trust < TRUST_MAIL && !mask_spf) {
    strbuf sr ("Received-SPF: %s", spf1_print (spfr));
    sr << "; receiver=" << opt->hostname
       << "; client-ip=" << inet_ntoa (ipaddr)
       << "; envelope-from=<" << fromaddr << ">"
       << "; helo=" << helohost;
    if (spf_mech)
      sr << "; mechanism=" << spf_mech;
    sr << "\n";
    r << line_wrap (sr);
  }

  if (trust <= TRUST_AUTH) {
    strbuf xa;
    xa << "X-Avenger: version=" << VERSION
       << "; receiver=" << opt->hostname;
    xa << "; client-ip=" << inet_ntoa (ipaddr);
    xa.fmt ("; client-port=%d", tcpport);
    size_t len = xa.tosuio ()->resid ();
    if (dns_error)
      xa.fmt ("; client-dnsfail=") << dns_error;
    if (bounce_res)
      xa << "; bounce-res=" << bounce_res;
    if (!synfp && synfpc) {
      sockaddr_in sin;
      sin.sin_family = AF_INET;
      sin.sin_port = htons (tcpport);
      sin.sin_addr = ipaddr;
#if USE_SYNFP
      synfp = synfpc->lookup (sin);
#endif /* USE_SYNFP */
    }
    if (synfp) {
      xa << "; syn-fingerprint=" << synfp;
      if (osguess)
	xa << " " << osguess;
    }
    if (pipelining)
      xa << "; eager-pipelining";
    if (colonspace)
      xa << "; colon-space";
    if (post)
      xa << "; post";
    xa.fmt ("; data-bytes=%ld", long (data_msgsize));
    if (ii && ii->nhops > 0)
      xa.fmt ("; network-hops=%d", ii->nhops);
    if (ii && ii->netpath) {
      xa << "; network-path=" << ii->netpath;
      xa << "; network-path-time=" << ii->netpath_time;
    }
    if (rblenv && !rblenv->results.empty ()) {
      rbl_status::result *rp = rblenv->results.base ();
      xa << "; RBL=" << (rp++)->tostr (false);
      while (rp < rblenv->results.lim ())
	xa << ", " << (rp++)->tostr (false);
    }
    if (rblenv && !rblenv->errors.empty ()) {
      rbl_status::rblerr *ep = rblenv->errors.base ();
      xa << "; RBL-errors=" << ep->name << " ("
	 << dns_strerror (ep->error) << ")";
      while (++ep < rblenv->errors.lim ())
      xa << ", " << ep->name << " ("
	 << dns_strerror (ep->error) << ")";
    }
    if (xa.tosuio ()->resid () > len)
      r << line_wrap (xa);
  }

  return r;
}

void
smtpd::cmd_mail (str cmd, str arg)
{
  str addr = extract_addr (arg, "from:");
  if (addr && arg[5] == ' ')
    colonspace = true;
  if (!addr)
    respond ("501 syntax error\r\n");
  else if (fromaddr)
    respond ("503 sender already set\r\n");
  else if (trust < TRUST_MAIL && addr.len ()) {
    str relay = extract_domain (addr);
    if (!relay)
      respond ("501 syntax error\r\n");
    else if (!rblcon) {
      rblenv = NULL;
      cmd_mail_2 (addr);
    }
    else {
      rblenv = New refcounted<rbl_status> (*rblcon);
      rbl_check_env (rblenv, opt->rbls, relay,
		     wrap (this, &smtpd::cmd_mail_2, addr));
    }
  }
  else if (trust < TRUST_MAIL && rblcon) {
    rblenv = New refcounted<rbl_status> (*rblcon);
    cmd_mail_2 (addr);
  }
  else {
    fromaddr = addr;
    respond (okstr);
  }
}
void
smtpd::cmd_mail_2 (str addr)
{
  if (rblenv && rblenv->score > 100) {
    strbuf sb;
    sb.fmt ("550-You cannot send email to this server because you have\r\n"
	    "550-been listed in the following RBL domain%s:\r\n",
	    rblenv->results.size () == 1 ? "" : "s");
    for (rbl_status::result *rp = rblenv->results.base ();
	 rp < rblenv->results.lim (); rp++)
      sb << "550-  " << rp->tostr (false) << "\r\n";
    sb << "550 \r\n";
    mail_error = sb;
    fromaddr = addr;
    respond (okstr);
  }
  else if (!addr.len ()) {
    fromaddr = addr;
    respond (okstr);
  }
  else
    spf_check (ipaddr, addr, wrap (this, &smtpd::cmd_mail_3, addr),
	       helohost, ptr_cache);
}
void
smtpd::cmd_mail_3 (str addr, spf_result res, str expl, str mech)
{
  spfr = res;
  spf_expl = expl;
  spf_mech = mech;
  if (spfr != SPF_FAIL && spfr != SPF_ERROR && opt->smtpcb) {
    vrfy (myipaddr, addr, ipaddr, wrap (this, &smtpd::cmd_mail_4, addr));
    return;
  }
  else if (spfr == SPF_FAIL) {
    if (expl && expl.len () && !strchr (expl, '\n'))
      mail_error = strbuf ()
	<< "550-IP address unauthorized to send from that address\r\n"
	<< "550 " << expl << "\r\n";
    else
      mail_error = "550 IP address unauthorized to send from that address\r\n";
  }
  else if (spfr == SPF_ERROR) {
    if (expl && expl.len () && !strchr (expl, '\n'))
      mail_error = strbuf ()
	<< "451-Temporary DNS error while processing SPF record\r\n"
	<< "451 " << expl << "\r\n";
    else
      mail_error = "451 Temporary DNS error while processing SPF record\r\n";
  }
  cmd_mail_4 (addr, NULL, NULL);
}
void
smtpd::cmd_mail_4 (str addr, str err, ptr<mxlist> m)
{
  if (err) {
    str code = substr (err, 0, 3);
    bounce_res = code;
    mail_error = strbuf ()
      << code << "-There is a problem with sender <" << addr << ">.\r\n"
      << code << "-It is not possible to send bounce messages"
      << " to that address.\r\n"
      << err;
  }
  else
    mxl = m;
  fromaddr = addr;
  respond (okstr);
}

void
smtpd::cmd_rcpt_0 (str cmd, str arg, int, in_addr *, int)
{
  cmd_rcpt (cmd, arg);
}
void
smtpd::cmd_rcpt (str cmd, str arg)
{
  if (!mail_error && dns_error && opt->allow_dnsfail < 2)
    mail_error = strbuf ()
      << "451-Temporary DNS error while resolving client\r\n"
      << "451 " << dns_error << "\r\n";

  if (ii && ii->trp) {
    netpath_addcb (ii->trp, wrap (this, &smtpd::cmd_rcpt_0, cmd, arg));
    return;
  }
  str addr = extract_addr (arg, "to:");
  if (addr && arg[5] == ' ')
    colonspace = true;
  str relay;
  if (!addr || !(relay = extract_domain (addr)))
    respond ("501 syntax error\r\n");
  else if (!fromaddr)
    respond ("503 must follow mail\r\n");
  else if (toaddr.size () >= 0x10000)
    respond ("452 too many recipients\r\n");
  else if (trust >= TRUST_AUTH)
    cmd_rcpt_6 (addr, NULL);
  else if (addr[0] == '@')
    respond ("553 multi-hop forward paths disallowed\r\n");
  else if (!opt->allow_percent && strchr (addr, '%'))
    respond ("553 \"%\" character disallowed in recipients\r\n");
  else if (toaddr.size () >= opt->max_rcpts) {
    if (toaddr.size () >= opt->max_relay_rcpts)
      respond ("452 too many recipients\r\n");
    else
      cmd_rcpt_3 (addr, "452 too many recipients\n");
  }
  else if (opt->nocheck[mytolower (addr)])
    cmd_rcpt_6 (addr, bodycheck (NULL, NULL, NULL));
  else {
    int indmap = dmap.hasentry (addr);
    if (indmap < 0)
      respond ("451 temporary error processing domain file\r\n");
    else if (!indmap)
      ismxlocal (extract_domain (addr),
		 wrap (this, &smtpd::cmd_rcpt_2, addr));
    else if (opt->nocheck[mytolower (extract_local (addr))])
      cmd_rcpt_6 (addr, bodycheck (NULL, NULL, NULL));
    else
      rcptcheck (this, addr, 'r',
		 wrap (this, &smtpd::cmd_rcpt_5, addr, str (NULL)));
  }
}
void
smtpd::cmd_rcpt_2 (str addr, int err)
{
  if (err > 0 && dns_tmperr (err)) 
    respond ("451 temporary DNS error\r\n");
  else if (err <= 0 && opt->mxlocal_rcpt) {
    if (opt->nocheck[mytolower (extract_local (addr))])
      cmd_rcpt_6 (addr, bodycheck (NULL, NULL, NULL));
    else
      rcptcheck (this, addr, err < 0 ? 'R' : 'r',
		 wrap (this, &smtpd::cmd_rcpt_5, addr, str (NULL)));
  }
  else {
    str errmsg;
    if (err <= 0) {
      maybe_warn (strbuf () <<"unauthorized MX record "
		  << extract_domain (addr) << "\n");
      errmsg = "451 not relaying for unauthorized MX record\r\n";
    }
    else
      errmsg = "550 relaying denied\r\n";
    cmd_rcpt_3 (addr, errmsg);
  }
}
void
smtpd::cmd_rcpt_3 (str addr, str errmsg)
{
  int indmap = 1;
  if (fromaddr.len ())
    indmap = dmap.hasentry (fromaddr);
  if (indmap < 0)
    respond ("451 temporary error processing domain file\r\n");
  else if (!indmap && opt->mxlocal_rcpt)
    ismxlocal (extract_domain (fromaddr),
	       wrap (this, &smtpd::cmd_rcpt_4, addr, errmsg));
  else
    cmd_rcpt_4 (addr, errmsg, indmap ? 0 : NXDOMAIN);
}
void
smtpd::cmd_rcpt_4 (str addr, str errmsg, int local)
{
  if (opt->mxlocal_rcpt && local > 0 && dns_tmperr (local))
    respond ("451 temporary DNS error\r\n");
  else
    rcptcheck (this, addr, (local || !fromaddr.len ()) ? 'M': 'm',
	       wrap (this, &smtpd::cmd_rcpt_5, addr, errmsg));
}
void
smtpd::cmd_rcpt_5 (str addr, str errmsg, str err)
{
  if (errmsg && !err)
    respond (errmsg);
  else if (err && err[0] != '2' && !errmsg)
    cmd_rcpt_3 (addr, err);
  else if (mail_error && !errmsg && !err)
    cmd_rcpt_3 (addr, mail_error);
  else if (errmsg && err && err[0] == '5' && errmsg[0] == '4')
    cmd_rcpt_6 (addr, errmsg);
  else {
    if (errmsg && err && err[0] == '2')
      mask_spf = true;
    cmd_rcpt_6 (addr, err);
  }
}
void
smtpd::cmd_rcpt_6 (str addr, str err)
{
  str rerr;
  if (err && err[0] != '2')
    respond (err, fromaddr.len ());
  else if (ii && (rerr = ii->rcpt ()))
    respond (rerr);
  else if (quota_user && (err = userinfo::rcpt (quota_user)))
    respond (rerr);
#if 0
  /* Just for testing -- fromaddr not same namespace as quota_user */
  else if (!quota_user && fromaddr.len ()
	   && (err = userinfo::rcpt (fromaddr)))
    respond (rerr);
#endif
  else {
    toaddr.push_back (addr);
    respond (err ? err : str (okstr));
  }
}

void
smtpd::cmd_data (str cmd, str arg)
{
  if (!fromaddr || !toaddr.size ()) {
    respond ("503 must follow rcpt\r\n");
    return;
  }

  if (!body_set)
    warn ("XXX - cmd_data body_set not set\n");

  data_state = NEWLINE;
  data_msgsize = 0;
  data_err = NULL;

  assert (!data_q);
  data_q = New enqmsg_file;
  if (!data_q->init  (fromaddr, toaddr, received ())) {
    delete data_q;
    data_q = NULL;
    respond ("451 unable to initialize message\r\n");
    return;
  }

  num_indata++;
  aio->settimeout (opt->data_timeout);

  aio << "354 enter mail, end with \".\" on a line by itself\r\n";
  aio->readany (wrap (this, &smtpd::data_1));
}

void
smtpd::data_1 (str data, int err)
{
  if (!data) {
    delete data_q;
    data_q = NULL;
    num_indata--;
    delete this;
    return;
  }

  suio newdat;
  const char *p, *np, *const eom = data.cstr () + data.len ();
  int ll;
  bool end = false;

  for (p = data;
       (np = static_cast<char *> (memchr (p, '\n', eom - p)));
       p = np + 1) {
    switch (data_state) {
    case MIDLINE:
      break;
    case NEWLINE:
      if (p[0] == '.') {
	if (p[1] == '\n' || (p[1] == '\r' && p[2] == '\n')) {
	  end = true;
	  goto done;
	}
	else if (p[1] == '.')
	  p++;
      }
      break;
    case CR:
      if (p[0] != '\n')
	newdat.print ("\r", 1);
      break;
    case DOT:
      if (p[0] == '\n' || (p[0] == '\r' && p[1] == '\n')) {
	end = true;
	goto done;
      }
      else if (p[0] != '.')
	newdat.print (".", 1);
      break;
    case DOTCR:
      if (p[0] == '\n') {
	end = true;
	goto done;
      }
      newdat.print (".\r", 2);
      break;
    }
    data_state = NEWLINE;
    ll = np - p;
    if (ll > 0 && np[-1] == '\r')
      ll--;
    newdat.print (p, ll);
    newdat.print ("\n", 1);
  }

  assert (data_state == NEWLINE || p < eom);

  switch (data_state) {
  case MIDLINE:
    break;
  case CR:
    if (p < eom)
      newdat.print ("\r", 1);
    data_state = MIDLINE;
    break;
  case NEWLINE:
    if (p >= eom)
      break;
    else if (*p == '.') {
      p++;
      data_state = DOT;
    }
    else {
      data_state = MIDLINE;
      break;
    }
    /* cascade */
  case DOT:
    if (p >= eom)
      break;
    else if (*p == '\r') {
      p++;
      data_state = DOTCR;
    }
    else {
      if (*p != '.')
	newdat.print (".", 1);
      data_state = MIDLINE;
      break;
    }
    /* cascade */
  case DOTCR:
    if (p < eom) {
      newdat.print (".\r", 2);
      data_state = MIDLINE;
    }
    break;
  }

  ll = eom - p;
  if (ll > 0 && data_state == MIDLINE && p[ll - 1] == '\r') {
    ll--;
    data_state = CR;
  }
  newdat.print ((char *) p, ll);

 done:
  data_msgsize += newdat.resid ();
  if (!data_err && data_msgsize > opt->max_msgsize) {
    delete data_q;
    data_q = New enqmsg_dummy;
    data_err = "552 Message too large\r\n";
  }
  if (end)
    aio->unread (eom - (np + 1));
  if (data_err)
    data_2 (end, NULL);
  else
    data_q->writev (&newdat, wrap (this, &smtpd::data_2, end));
}

void
smtpd::data_2 (bool end, str err)
{
  int fd;
  struct passwd *pw;

  if (err) {
    assert (err[0] == '4' || err[0] == '5');
    data_err = err;
  }
  if (!end)
    aio->readany (wrap (this, &smtpd::data_1));
  else if (data_err)
    data_4 (NULL);
  else if (body_cmd
	   && (pw = body_user ? getpwnam (body_user) : opt->av_user)
	   && (fd = data_q->getfd ()) >= 0) {
    avcount *avc = avcount::get (pw->pw_uid);
    if (!avc->acquire ()) {
      avc->waiters.push_back (wrap (this, &smtpd::data_2, true, str (NULL)));
      return;
    }

    vec<str> senv;
    envinit (&senv, pw);
    senv.push_back (strbuf ("DATA_BYTES=%ld", long (data_msgsize)));

    vec<const char *> env;
    env.reserve (senv.size () + 1);
    for (const str *sp = senv.base (); sp < senv.lim (); sp++)
      env.push_back (sp->cstr ());
    env.push_back (NULL);

    vec<const char *> av;
    av.push_back ("/bin/sh");
    av.push_back ("-c");
    av.push_back (body_cmd);
    av.push_back (NULL);

    rpstate *rp = runprog (av[0], av.base (), fd, false,
			   wrap (this, &smtpd::data_3, avc),
			   wrap (become_user, pw, body_user), env.base ());
    runprog_timeout (rp, opt->avenger_timeout);
  }
  else
    data_q->commit (wrap (this, &smtpd::data_4));
}

void
smtpd::data_3 (avcount *avc, ref<progout> po)
{
  avc->release ();

  if (!po->status) {
    data_q->commit (wrap (this, &smtpd::data_4));
    return;
  }

  if (!WIFEXITED (po->status)) {
    data_4 (strbuf () << "451 body filter exited with "
	    << exitstr (po->status) << "\r\n");
    return;
  }

  int code = 451;
  switch (WEXITSTATUS (po->status)) {
  case 99:
    data_4 (NULL);
    return;
  case 64:
  case 65:
  case 70:
  case 76:
  case 77:
  case 78:
  case 100:
  case 112:
    code = 554;
    break;
  }
  if (po->output.empty ())
    data_4 (strbuf ("%03d message contents rejected\r\n", code));
  else
    data_4 (po->response (code));
}

void
smtpd::data_4 (str err)
{
  str resp;
  if (err && (err[0] == '2' || err[0] == '4' || err[0] == '5'))
    resp = err;
  else if (data_err)
    resp = data_err;
  else
    resp = okstr;

  delete data_q;
  data_q = NULL;
  num_indata--;
  aio->settimeout (opt->smtp_timeout);
  reset ();
  respond (resp);
}

smtp_dispatch::smtp_dispatch (const char *name, void (smtpd::*fn) (str, str))
  : cmd_name (name), cmd_fn (fn)
{
  //warn ("inserting %s\n", name);
  dispatch_tab.insert (this);
}

static void
relaunch (int __XXX_gcc_2_95_3_bug,
	  int myfd, str name, in_addr addr, ptr<hostent> h, int err)
{
  static rxx userrx ("^((.*)@)?[^@]+$");
  if (userrx.match (name))
    name = userrx[2];
  else
    name = NULL;

  strbuf sb ("[test");
  if (name)
    sb << "=" << name;
  sb << "]@";
  if (h)
    sb << h->h_name;
  else
    sb << inet_ntoa (addr);

  name = sb;

  sockaddr_in sin;
  bzero (&sin, sizeof (sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons (0);
  sin.sin_addr = addr;

  newcon *nc = New newcon (myfd, sin);
  nc->name = name;
  nc->h = h;
  nc->init ();
}

void
smtpd::getcmd (str line, int err)
{
  cmdwait = false;

  if (!dispatch_tab.size ())
    dispatch_tab_init ();

  if (!line) {
    if (err == ETIMEDOUT)
      aio << "421 timeout\r\n";
    delete this;
    return;
  }

  if (terminated) {
    aio << "421 shutdown\r\n";
    delete this;
    return;
  }

  if (ii)
    if (str err = ii->status ()) {
      aio << err;
      delete this;
      return;
    }

  if (!cmdarg.match (line)) {
    respond ("502 command not implemented\r\n");
    return;
  }

  str cmd = mytolower (cmdarg[1]);
  line = cmdarg [3];
  if (!line)
    line = "";

  if (!helohost && aio->getrbufbytes ())
    pipelining = true;

  if (cmd == "helo" || cmd == "ehlo") {
    /* We wait until here to call netpath, so as to avoid sending out
     * a bunch of UDP packets in response to a forged SYN packet.  (If
     * we get as far as receiving the HELO command, at least the
     * client has acked our ISN.) */ 
    if (!helohost && ii)
      ii->do_netpath (myipaddr);
    if (line && !strchr (line, ' ')) {
      helohost = line;
      strbuf sb;
      sb << "250-" << opt->hostname << "\r\n"
	 << helo_auth ()
#ifdef STARTTLS
	 << helo_starttls ()
#endif /* STARTTLS */
	 << "250 PIPELINING\r\n";
      respond (sb);
    }
    else
      respond ("501 syntax error\r\n");
  }
  else if (cmd == "post") {
    post = true;
    respond ("502 command not implemented\r\n");
  }
  else if (cmd == "quit") {
    aio << "221 " << opt->hostname << "\r\n";
    delete this;
  }
  else if (cmd == "noop")
    respond (okstr);
#ifdef STARTTLS
  else if (cmd == "starttls")
    cmd_starttls (cmd, line);
#endif /* STARTTLS */
  else if (trust >= TRUST_LOCAL && cmd == "stat") {
    quota_dump (aiosout (aio));
    cmdwait = true;
    aio->readline (wrap (this, &smtpd::getcmd));
  }
  else if (trust >= TRUST_LOCAL && cmd == "addr") {
    in_addr a;
    if (!line || inet_aton (line, &a) != 1)
      respond ("501 syntax error\r\n");
    else {
      str nn (name);
      int myfd = dup (aio->fdno ());
      delete this;
      dns_hostbyaddr (a, wrap (relaunch, myfd, myfd, nn, a));
      return;
    }
  }
  else {
    const smtp_dispatch *dp = dispatch_tab[cmd];
    if (!dp) {
      respond ("502 command not implemented\r\n");
      //dp = dispatch_tab.first ();
      //warn ("%s %p %s\n", cmd.cstr (), dp, dp ? dp->cmd_name : "NULL");
    }
    else if (!helohost)
      respond ("503 hello?\r\n");
    else
      (this->*(dp->cmd_fn)) (cmd, line);
  }
}

void
smtpd::respond (str resp, bool counterr)
{
  if (counterr && (resp[0] == '4' || resp[0] == '5') && ii)
    ii->error ();
  aio << resp;
  if (terminated) {
    aio << "421 shutdown\r\n";
    delete this;
  }
  else {
    cmdwait = true;
    aio->readline (wrap (this, &smtpd::getcmd));
  }
}

smtpd::smtpd (ipinfo *ii, int fd, const sockaddr_in &sin,
	      str n, str fp, trust_level t,
	      ptr<rbl_status> rs, ptr<hostent> h, str dnserr)
  : ii (ii), aio (AIOS::alloc (fd)), name (n),
    synfp (fp), osguess (synos_guess (fp)), pipelining (false),
    colonspace (false), post (false), encrypted (false), trust (t),
    rblcon (rs), dns_error (dnserr), mask_spf (false), spfr (SPF_PASS),
#ifdef SASL
    sasl (NULL),
#endif /* SASL */
    body_set (false), data_q (NULL), cmdwait (false), ptr_cache (h)
{
  smtplist.insert_head (this);
  num_smtpd++;

  ipaddr = sin.sin_addr;
  tcpport = ntohs (sin.sin_port);

  sockaddr_in lsin;
  socklen_t sinlen = sizeof (lsin);
  bzero (&lsin, sizeof (lsin));
  getsockname (fd, (sockaddr *) &lsin, &sinlen);
  myipaddr = lsin.sin_addr;
  mytcpport = ntohs (lsin.sin_port);

  if (opt->debug_smtpd) {
    aio->setdebug (strbuf ("%s (%d)", name.cstr (), fd));
#if 0
    if (synfp)
      warnx ("%s (%d) === SYN fingerprint %s\n", name.cstr (), fd,
	     synfp.cstr ());
#endif
  }
  aio->settimeout (opt->smtp_timeout);

  str trusted;
  switch (trust) {
  case TRUST_MAIL:
    trusted = " any sender ok";
    break;
  case TRUST_RCPT:
  case TRUST_AUTH:
    trusted = " mail relaying ok";
    break;
  case TRUST_LOCAL:
    trusted = " trusted local client";
    break;
  default:
    trusted = "";
    break;
  }
  respond (strbuf () << "220 " << opt->hostname << " ESMTP"
	   << trusted << "\r\n");
}

smtpd::~smtpd ()
{
  assert (!data_q);
  num_smtpd--;
  if (ii)
    ii->delcon ();
  smtplist.remove (this);
#ifdef SASL
  if (sasl)
    sasl_dispose (&sasl);
#endif /* SASL */
  toggle_listen ();
}

inline bool
null_str_eq (const str &s1, const str &s2)
{
  return s1 && s2 ? s1 == s2 : !s1 && !s2;
}
str
smtpd::bodycheck (str user, str cmd, str defresp)
{
  if (!body_set) {
    body_set = true;
    body_user = user;
    body_cmd = cmd;
    return defresp;
  }
  if (null_str_eq (body_user, user) && null_str_eq (body_cmd, cmd))
    return defresp;
  return "452 send a separate copy of the message to this user\r\n";
}

inline void
preserve (vec<str> *e, const char *var)
{
  if (!opt->envb[var])
    if (const char *p = getenv (var))
      e->push_back (strbuf ("%s=%s", var, p));
}
void
smtpd::envinit (vec<str> *envp, struct passwd *pw = NULL) const
{
  preserve (envp, "PWD");
  preserve (envp, "PATH");
  preserve (envp, "TZ");
  preserve (envp, "TMPDIR");
  preserve (envp, "DMALLOC_OPTIONS");
  preserve (envp, "STKTRACE");

  for (const str *sp = opt->env.base (); sp < opt->env.lim (); sp++)
    envp->push_back (*sp);
  envp->push_back (strbuf () << "HOST=" << opt->hostname);
  envp->push_back (strbuf () << "ETCDIR=" << opt->etcdir);
  if (opt->separator)
    envp->push_back (strbuf ("SEPARATOR=%c", opt->separator));
  if (mytcpport) {
    envp->push_back (strbuf ("MYIP=%s", inet_ntoa (myipaddr)));
    envp->push_back (strbuf ("MYPORT=%d", mytcpport));
  }

  if (mail_error)
    envp->push_back (strbuf () << "MAIL_ERROR=" << mail_error);
  if (auth_user)
    envp->push_back (strbuf () << "AUTH_USER=" << auth_user);
#ifdef STARTTLS
  env_starttls (envp);
#endif /* STARTTLS */
  envp->push_back (strbuf () << "CLIENT=" << name);
  envp->push_back (strbuf ("CLIENT_IP=%s", inet_ntoa (ipaddr)));
  {
    const u_char *c = reinterpret_cast<const u_char *> (&ipaddr);
    envp->push_back (strbuf ("CLIENT_REVIP=%d.%d.%d.%d",
			    c[3], c[2], c[1], c[0]));
  }
  if (dns_error)
    envp->push_back (strbuf ("CLIENT_DNSFAIL=") << dns_error);
  if (tcpport)
    envp->push_back (strbuf ("CLIENT_PORT=%d", tcpport));
  if (ptr_cache)
    envp->push_back (strbuf ("CLIENT_NAME=%s", ptr_cache->h_name));
  if (synfp)
    envp->push_back (strbuf () << "CLIENT_SYNFP=" << synfp);
  if (osguess)
    envp->push_back (strbuf () << "CLIENT_SYNOS=" << osguess);
  envp->push_back (strbuf () << "CLIENT_HELO=" << helohost);
  if (pipelining)
    envp->push_back ("CLIENT_PIPELINING=1");
  if (colonspace)
    envp->push_back ("CLIENT_COLONSPACE=1");
  if (post)
    envp->push_back ("CLIENT_POST=1");
  if (ii && ii->nhops > 0)
    envp->push_back (strbuf ("CLIENT_NETHOPS=%d", ii->nhops));
  if (ii && ii->netpath)
    envp->push_back (strbuf () << "CLIENT_NETPATH=" << ii->netpath);
  envp->push_back (strbuf () << "SENDER=" << fromaddr);
  if (str s = extract_domain (fromaddr)) {
    envp->push_back (strbuf () << "SENDER_HOST=" << mytolower (s));
    if ((s = extract_local (fromaddr)))
      envp->push_back (strbuf () << "SENDER_LOCAL=" << mytolower (s));
  }
  if (mxl) {
    strbuf sb ("SENDER_MXES=%s", mxl->m_mxes[0].name);
    for (u_int i = 1; i < mxl->m_nmx; i++)
      sb.fmt (" %s", mxl->m_mxes[i].name);
    envp->push_back (sb);
  }
  if (bounce_res)
    envp->push_back (strbuf () << "SENDER_BOUNCERES=" << bounce_res);
  envp->push_back (strbuf ("SPF=%s", spf_print (spfr)));
  envp->push_back (strbuf ("SPF0=%s", spf_print (spfr)));
  envp->push_back (strbuf ("SPF1=%s", spf1_print (spfr)));
  if (spf_expl)
    envp->push_back (strbuf () << "SPF_EXPL=" << spf_expl);
  if (rblenv)
    for (rbl_status::result *rp = rblenv->results.base ();
	 rp < rblenv->results.lim (); rp++)
      envp->push_back (rp->tostr (true));
  envp->push_back (strbuf () << "UFLINE=From "
		   << (fromaddr.len () ? fromaddr.cstr () : "MAILER-DAEMON")
		   << " " << datestr (false));

  if (pw) {
    if (pw->pw_dir)
      envp->push_back (strbuf ("HOME=%s", pw->pw_dir));
    if (pw->pw_shell)
      envp->push_back (strbuf ("SHELL=%s", pw->pw_shell));
    envp->push_back (strbuf ("USER=%s", pw->pw_name));
    envp->push_back (strbuf ("LOGNAME=%s", pw->pw_name));
  }
}

void
smtpd::maybe_shutdown ()
{
  if (!cmdwait)
    return;
  aio->readcancel ();
  aio << "421 shutdown\r\n";
  delete this;
}


syntax highlighted by Code2HTML, v. 0.9.1