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

vrfy.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 "asmtpd.h"

struct addrcache_entry {
  enum res_t { LOCKED, VALID, INVALID };

  const str addr;
  res_t status;
  str msg;
  time_t last;
  vec<cbs> waitq;
  ihash_entry<addrcache_entry> hashlink;
  tailq_entry<addrcache_entry> agelink;

  addrcache_entry (const str &a);
  ~addrcache_entry ();
  void setstatus (res_t r, str msg);
};

static ihash<const str, addrcache_entry,
           &addrcache_entry::addr, &addrcache_entry::hashlink> addrcache;
static tailq<addrcache_entry, &addrcache_entry::agelink> addrcache_expire;

addrcache_entry::addrcache_entry (const str &a)
  : addr (a), status (INVALID), last (0)
{
  addrcache.insert (this);
  addrcache_expire.insert_tail (this);

  for (addrcache_entry *ac = addrcache_expire.first, *nac;
       ac != this && ac->status != LOCKED; ac = nac) {
    nac = ac->agelink.next;
    if (ac->last + opt->vrfy_cachetime <= timenow +0U)
      delete ac;
  }
}

addrcache_entry::~addrcache_entry ()
{
  assert (status != LOCKED);
  assert (waitq.empty ());

  addrcache.remove (this);
  addrcache_expire.remove (this);
}

void
addrcache_entry::setstatus (res_t r, str m)
{
  last = timenow;
  addrcache_expire.remove (this);
  addrcache_expire.insert_tail (this);
  if (r != INVALID)
    msg = NULL;
  else if (m && m.len () && (m[0] == '4' || m[0] == '5'))
    msg = m;
  else
    msg = "451 SMTP callback failure\r\n";
  status = r;
  vec<cbs> v;
  v.swap (waitq);
  while (!v.empty ())
    (*v.pop_front ()) (msg);
}

static void
addrcache_report (str addr, str msg)
{
  addrcache_entry *ac = addrcache[addr];
  if (!ac)
    ac = New addrcache_entry (addr);
  if (!msg || (msg.len () && msg[0] == '2'))
    ac->setstatus (addrcache_entry::VALID, NULL);
  else
    ac->setstatus (addrcache_entry::INVALID, msg);
}

static void
addrcache_query (str addr, cbs cb)
{
  addrcache_entry *ac = addrcache[addr];
  if (!ac)
    ac = New addrcache_entry (addr);
  if (ac->status == addrcache_entry::LOCKED) {
    ac->waitq.push_back (cb);
    return;
  }
  if (ac->last + opt->vrfy_cachetime <= timenow + 0U)
    ac->setstatus (addrcache_entry::LOCKED, NULL);
  (*cb) (ac->msg);
}

class mailserv {
public:
  typedef callback<void, str, int>::ref chatcb_t;
private:
  ptr<aios> aio;
  
  bool lineconsistent (strbuf sb, str line);
  void concb (str line, int err);
  void helocb (bool ehlo, str line, int err);
  void chatcb (ref<vec<str> > cv, chatcb_t cb, strbuf sb, str line, int err);
  void unsolicited (str line, int err);
  void touch ();

  static void cleanup ();

protected:
  bool busy;
  bool noop;
  void idle (bool clean = false);
  virtual void fail (bool temp, str error);
  virtual void ready () { busy = false; }
public:
  static u_int nmailserv;
  static str heloname () { return opt->hostname; }

  const str name;
  ihash_entry<mailserv> hlink;
  tailq_entry<mailserv> llink;

  mailserv (str n, int fd);
  virtual ~mailserv ();
  void helo ();
  void chat (const vec<str> &cmds, chatcb_t cb);
  static mailserv *lookup (str name);
};

struct mailserv_readycb : mailserv {
  timecb_t *tmo;
  cbb::ptr cb;

  mailserv_readycb (str n, int fd)
    : mailserv (n, fd), tmo (NULL) {}
  ~mailserv_readycb () {
    timecb_remove (tmo);
    if (cb) (*cb) (false);
  }
  void ready () {
    idle ();
    busy = true;
    tmo = delaycb (opt->vrfy_delay, wrap (this, &mailserv_readycb::ready_2));
  }
  void ready_2 () {
    tmo = NULL;
    busy = false;
    cbb c (cb);
    cb = NULL;
    (*c) (true);
  }
};

static ihash<const str, mailserv, &mailserv::name, &mailserv::hlink> mstab;
static tailq<mailserv, &mailserv::llink> msq;
u_int mailserv::nmailserv;

void
mailserv::cleanup ()
{
  // XXX - this will, of course, fail to close mailservs behind slow clients
  while (nmailserv > opt->max_revclients && msq.first && !msq.first->busy)
    delete msq.first;
}

mailserv::mailserv (str n, int fd)
  :  aio (aios::alloc (fd)), busy (true), name (mytolower (n))
{
  nmailserv++;
  mstab.insert (this);
  msq.insert_tail (this);
  if (opt->debug_smtpc)
    aio->setdebug (strbuf ("%s (R%d)", name.cstr (), fd));
  aio->settimeout (opt->client_timeout);
  cleanup ();
}
mailserv::~mailserv ()
{
  nmailserv--;
  mstab.remove (this);
  msq.remove (this);
}
void
mailserv::helo ()
{
  aio->readline (wrap (this, &mailserv::concb));
}
void
mailserv::fail (bool temp, str error)
{
  if (opt->debug_smtpc)
    warn << name << ": " << error << "\n";
  delete this;
}

void
mailserv::concb (str line, int err)
{
  if (!line || line.len () < 4)
    fail (true, err ? strerror (err) : "eof");
  else if (line[3] == '-')
    aio->readline (wrap (this, &mailserv::concb));
  else if (line[0] != '2')
    fail (line[0] != '5', substr (line, 4));
  else {
    aio << "EHLO " << heloname () << "\r\n";
    aio->readline (wrap (this, &mailserv::helocb, true));
  }
}

void
mailserv::helocb (bool ehlo, str line, int err)
{
  if (!line || line.len () < 4)
    fail (true, err ? strerror (err) : "eof");
  else if (line[3] == '-')
    aio->readline (wrap (this, &mailserv::helocb, ehlo));
  else if (line[0] == '5' && ehlo) {
    aio << "HELO " << heloname () << "\r\n";
    aio->readline (wrap (this, &mailserv::helocb, false));
  }
  else if (line[0] != '2')
    fail (line[0] != '5', substr (line, 4));
  else {
    busy = false;
    ready ();
    idle ();
  }
}

void
mailserv::chat (const vec<str> &cmds, mailserv::chatcb_t cb)
{
  assert (!busy);
  busy = true;
  aio->readcancel ();
  touch ();

  ref<vec<str> > cv = New refcounted<vec<str> > (cmds);
  if (cv->empty ()) {
    busy = false;
    (*cb) (NULL, 0);
    idle (true);
  }
  else {
    noop = !strncasecmp (cv->front (), "noop", 4);
    aio << cv->pop_front () << "\r\n";
    aio->readline (wrap (this, &mailserv::chatcb, cv, cb, strbuf ()));
  }
}

bool
mailserv::lineconsistent (strbuf sb, str line)
{
  static rxx resprx ("^(\\d\\d\\d)(-| )(.*)$");
  if (!line || !resprx.match (line))
    return false;
  if (sb.tosuio ()->resid () > 3) {
    char buf[3];
    sb.tosuio ()->copyout (buf, 3);
    if (memcmp (buf, line.cstr (), 3))
      return false;
  }
  else if (!sb.tosuio ()->resid ())
    sb << resprx[1] << "-server " << name << " reports:\r\n";
  sb << line << "\r\n";
  return true;
}
void
mailserv::chatcb (ref<vec<str> > cv, chatcb_t cb, strbuf sb,
              str line, int err)
{
  if (!lineconsistent (sb, line)) {
#if 1
    if (line) {
      warn ("inconsistent line -------------------------------------------\n");
      warn << sb << line << "\n";
      warn ("-------------------------------------------------------------\n");
    }
#endif
    fail (true, err ? strerror (err) : "eof");
    (*cb) (NULL, err);
  }
  else if (line[3] == '-')
    aio->readline (wrap (this, &mailserv::chatcb, cv, cb, sb));
  else if ((!noop && line[0] != '2') || cv->empty ()) {
    busy = false;
    (*cb) (sb, 0);
    idle (true);
  }
  else {
    sb.tosuio ()->clear ();
    noop = !strncasecmp (cv->front (), "noop", 4);
    aio << cv->pop_front () << "\r\n";
    aio->readline (wrap (this, &mailserv::chatcb, cv, cb, sb));
  }
}

void
mailserv::idle (bool clean)
{
  if (!busy) {
    aio->readcancel ();
    aio->readline (wrap (this, &mailserv::unsolicited));
  }
  if (clean)
    cleanup ();
}

void
mailserv::unsolicited (str line, int err)
{
  fail (true, "eof");
}

void
mailserv::touch ()
{
  msq.remove (this);
  msq.insert_tail (this);
}

mailserv *
mailserv::lookup (str name)
{
  name = mytolower (name);
  for (mailserv *msp = mstab[name]; msp; msp = mstab.nextkeq (msp))
    if (!msp->busy)
      return msp;
  return NULL;
}

class mxconnect {
  typedef callback<void, mailserv *, int, ptr<mxlist> >::ref cb_t;

  str name;
  cb_t cb;
  int n;
  ptr<mxlist> mxl;
  int fallthrough_error;
  in_addr bindaddr;

  mxconnect (str nn, cb_t c, in_addr ba)
    : name (nn), cb (c), n (-1), fallthrough_error (ARERR_NXREC),
      bindaddr (ba) {}
  void start () {
    dns_mxbyname (name, wrap (this, &mxconnect::mxcb));
  }
  void mxcb (ptr<mxlist> m, int dnserr) {
    mxl = m;
    //printmxlist (name, mxl, dnserr);
    if (mxl)
      trymx ();
    else if (dnserr == ARERR_NXREC)
      trya (name);
    else {
      (*cb) (NULL, dnserr, mxl);
      delete this;
    }
  }
  void trymx () {
    if (!mxl || implicit_cast<unsigned> (++n) >= mxl->m_nmx) {
      (*cb) (NULL, fallthrough_error, mxl);
      delete this;
    }
    else if (mailserv *msp = mailserv::lookup (mxl->m_mxes[n].name)) {
      (*cb) (msp, 0, mxl);
      delete this;
    }
    else
      trya (mxl->m_mxes[n].name);
  }
  void trya (str a) {
    /* XXX - probably want a smaller timeout */
    dns_hostbyname (a, wrap (this, &mxconnect::tryip, a));
  }
  void tryip (str a, ptr<hostent> h, int err) {
    if (!h) {
      if (fallthrough_error) {
      if (dns_tmperr (err))
        fallthrough_error = err;
      else if (!dns_tmperr (fallthrough_error))
        fallthrough_error = err;
      }
      errno = dns_tmperr (err) ? EAGAIN : ENOENT;
      tcpcb (a, -1);
      return;
    }
    fallthrough_error = 0;
    u_int32_t ip = ntohl (((in_addr *) h->h_addr)->s_addr);
    if (ip >> 24 == 0 || ip >> 24 == 127 || ip == 0xffffffff) {
      errno = EACCES;
      tcpcb (a, -1);
      return;
    }

    /* XXX - this is kind of gross, and also only applies to
     * connections that are not already cached. */
    in_addr save = inet_bindaddr;
    inet_bindaddr = bindaddr;
    tcpconnect (*(in_addr *) h->h_addr, 25, wrap (this, &mxconnect::tcpcb, a));
    inet_bindaddr = save;
  }
  void tcpcb (str a, int fd) {
    if (fd < 0) {
      if (opt->debug_smtpc)
      warn << "mxconnect: " << a << " for " << name << ": "
           << strerror (errno) << "\n";;
      trymx ();
    }
    else {
      if (opt->debug_smtpc)
      warn << "mxconnect: " << a << " for " << name << ": success (fd "
           << fd << ")\n";
      mailserv_readycb *msp = New mailserv_readycb (a, fd);
      msp->cb = wrap (this, &mxconnect::ready, msp);
      msp->helo ();
    }
  }
  void ready (mailserv *msp, bool ok) {
    if (ok) {
      (*cb) (msp, 0, mxl);
      delete this;
    }
    else
      trymx ();
  }

public:
  static void alloc (str n, cb_t c, in_addr bindaddr)
    { (New mxconnect (n, c, bindaddr))->start (); }
};

inline void
vrfy_mkcb (str addr, vrfycb_t cb, str msg, ptr<mxlist> mxl)
{
  addrcache_report (addr, msg);
  (*cb) (msg, mxl);
}
static void
vrfy_4 (str relay, str addr, vrfycb_t cb, ptr<mxlist> mxl, str line, int err)
{
  if (line && line[0] == '2')
    vrfy_mkcb (addr, cb, NULL, mxl);
  else if (line && (line[0] == '4' || line[0] == '5')) {
    if (line.len () > 4)
      vrfy_mkcb (addr, cb, line, mxl);
    else if (line[0] == '5')
      vrfy_mkcb (addr, cb, "550 bad user \r\n", mxl);
    else
      vrfy_mkcb (addr, cb, "451 bad user \r\n", mxl);
  }
  else {
    if (err) {
      str msg (strbuf ("451 %s: %s\r\n", relay.cstr (), strerror (err)));
      vrfy_mkcb (addr, cb, msg, mxl);
    }
    else
      vrfy_mkcb (addr, cb,
             strbuf ("451 %s: SMTP protocol failure\r\n", relay.cstr ()),
             mxl);
  }
}
static void
vrfy_3 (str relay, str addr, in_addr cli, vrfycb_t cb,
      mailserv *msp, int dnserr, ptr<mxlist> mxl)
{
  if (dnserr)
    vrfy_mkcb (addr, cb,
             strbuf ("%03d %s\r\n", dns_tmperr (dnserr) ? 451 : 553,
                   dns_strerror (dnserr)), NULL);
  else if (!msp)
    vrfy_mkcb (addr, cb,
             strbuf () << "451 could not connect to server for "
             << relay << "\r\n", NULL);
  else {
    vec<str> cmds;
    cmds.push_back ("RSET");
    cmds.push_back (strbuf ("NOOP %s is sending mail from <%s>; if forged, "
                      "consider an SPF record (http://www.openspf.org/)",
                      inet_ntoa (cli), addr.cstr ()));
    cmds.push_back ("MAIL FROM:<>");
    cmds.push_back (strbuf () << "RCPT TO:<" << addr << ">");
    msp->chat (cmds, wrap (vrfy_4, relay, addr, cb, mxl));
  }
}
static void
vrfy_2 (in_addr bindaddr, str relay, str addr,
      in_addr cli, vrfycb_t cb, str msg)
{
  if (msg)
    (*cb) (msg, NULL);
  else
    mxconnect::alloc (relay, wrap (vrfy_3, relay, addr, cli, cb),
                  bindaddr);
}
void
vrfy (in_addr bindaddr, str addr, in_addr cli, vrfycb_t cb)
{
  str relay = extract_relay (addr);
  if (!relay)
    (*cb) ("501 syntax error\r\n", NULL);
  addr = domain_tolower (addr);
  addrcache_query (addr, wrap (vrfy_2, bindaddr, relay, addr, cli, cb));
}

Generated by  Doxygen 1.6.0   Back to index