/* $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