/* $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 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 dispatch_tab; static rxx cmdarg ("^(\\S+)(\\s*|\\s+(\\S.*))$"); list 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 (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 (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 (*rblcon); rbl_check_env (rblenv, opt->rbls, relay, wrap (this, &smtpd::cmd_mail_2, addr)); } } else if (trust < TRUST_MAIL && rblcon) { rblenv = New refcounted (*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 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 (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 senv; envinit (&senv, pw); senv.push_back (strbuf ("DATA_BYTES=%ld", long (data_msgsize))); vec 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 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 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 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 rs, ptr 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 *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 *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 (&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; }