#include #include "base64.h" #include "buffer.h" #include "byte.h" #include "case.h" #include "cdbread.h" #include "close.h" #include "constmap.h" #include "dnsresolv.h" #include "env.h" #include "error.h" #include "exit.h" #include "fd.h" #include "fmt.h" #include "ip.h" #include "open.h" #include "scan.h" #include "sig.h" #include "str.h" #include "stralloc.h" #include "timeout.h" #include "wait.h" #include "commands.h" #include "control.h" #include "dns.h" #include "ipme.h" #include "mfrules.h" #include "now.h" #include "qmail.h" #include "rcpthosts.h" #include "received.h" #include "recipients.h" #include "smtpdlog.h" #include "spf.h" #include "tls_start.h" #include "wildmat.h" #ifdef USE_CONFIG #include "fehsqm-config.h" #else #include "auto_qmail.h" #endif /** * @file qmail-smtpd.c -- authenticating ESMTP/ESMTPS server * @brief requires sslserver or tcpserver */ #define PAM111421 #define AUTHSLEEP 5 #define PORT_SMTPS "465" #define MIMETYPE_LEN 9 #define LOADER_LEN 5 #define BASE64MESSAGE "content-transfer-encoding: base64" #define FDIN 0 #define FDOUT 1 #define FDLOG 2 #define FDAUTH 3 #define BUFFER_SIZE 1024 #define MAXHOPS 100 unsigned long databytes = 0; long timeout = 1200; static int modssl_info(void); ssize_t safewrite(int fd, char *buf, int len) { int r; r = timeoutwrite(timeout, fd, buf, len); if (r <= 0) _exit(1); return r; } char outbuf[BUFFER_SIZE / 2]; buffer bo = BUFFER_INIT(safewrite, FDOUT, outbuf, sizeof(outbuf)); void flush() { buffer_flush(&bo); } // this triggers writing to STDIO static ssize_t saferead(int fd, char *buf, int len) { int r; flush(); r = timeoutread(timeout, fd, buf, len); if (r == -1) if (errno == ETIMEDOUT) die_alarm(); if (r <= 0) die_read(); return r; } char inbuf[BUFFER_SIZE]; buffer bi = BUFFER_INIT(saferead, FDIN, inbuf, sizeof(inbuf)); char logbuf[256]; buffer bl = BUFFER_INIT(write, FDLOG, logbuf, sizeof(logbuf)); void out(char *s) { buffer_puts(&bo, s); } stralloc sa = {0}; ipalloc ia = {0}; int bhelocheck(void); /* this file is too long -------------------------------------- DNS helper */ static int dnsq(char *arg, char type) { unsigned int random; int at; int r = -2; int len; len = str_len(arg); if (len < 1) return r; if (arg[len - 1] == ' ') len--; /* trailing blank */ if (len < 1) return r; at = byte_rchr(arg, len, '@'); if (at < len) { if (!stralloc_copyb(&sa, arg + at + 1, len - at - 1)) die_nomem(); } else if (!stralloc_copyb(&sa, arg, len)) { die_nomem(); } random = now() + (getpid() << 16); switch (type) { /* Common for IPv4 and IPv6 */ case 'A': r = dns_ip(&ia, &sa); break; case 'M': r = dns_mxip(&ia, &sa, random); break; } switch (r) { case DNS_ERR: out("451 DNS temporary failure (#4.3.0)\r\n"); return -1; case DNS_MEM: die_nomem(); default: if (ia.len) return 0; } return 1; } /* this file is too long -------------------------------------- Greeting */ static stralloc greeting = {0}; static void smtp_greet(char *code) { buffer_puts(&bo, code); buffer_put(&bo, greeting.s, greeting.len); } static void smtp_help() { out("214 s/qmail home page: https://www.fehcom.de/sqmail.html\r\n"); } static void smtp_quit() { smtp_greet("221 "); out("\r\n"); flush(); _exit(0); } char *remoteip; char *remotehost; char *remoteinfo; char *local; char *localport; char *relayclient; int flagutf8 = 0; stralloc protocol = {0}; stralloc helohost = {0}; char *fakehelo; /* pointer into helohost, or 0 */ stralloc tlsinfo = {0}; char *helocheck; int flagbadhelo; int flagdnshelo; int seenhelo = 0; char *badmailcond; char *badhelocond; static void dohelo(char *helo) { if (!stralloc_copys(&helohost, helo)) die_nomem(); if (!stralloc_0(&helohost)) die_nomem(); fakehelo = case_diffs(remotehost, helohost.s) ? helohost.s : 0; if (helocheck) { if (str_len(helocheck) == 1) { switch (*helocheck) { case '=': flagbadhelo = bhelocheck(); if (fakehelo) { flagdnshelo = 1; badhelocond = "="; } break; case 'A': flagbadhelo = bhelocheck(); if (flagbadhelo == 0) { flagdnshelo = dnsq(helohost.s, 'A'); badhelocond = "A"; } break; case 'M': flagbadhelo = bhelocheck(); if (flagbadhelo == 0) { flagdnshelo = dnsq(helohost.s, 'M'); badhelocond = "M"; } break; case '.': flagbadhelo = bhelocheck(); if (!str_len(helo)) flagbadhelo = -2; break; case '!': if (!str_len(helo)) flagbadhelo = -2; break; } } else { flagbadhelo = bhelocheck(); } if (flagbadhelo == -3) flagbadhelo = 0; } if (!env_put("HELOHOST", helohost.s)) die_nomem(); } int liphostok = 0; stralloc liphost = {0}; int bmfok = 0; stralloc bmf = {0}; struct constmap mapbmf; int brtok = 0; stralloc brt = {0}; struct constmap mapbrt; int badhelook = 0; stralloc badhelo = {0}; struct constmap mapbhlo; static struct cdb cdbm; static struct cdb cdbl; static int fdbmt; /* -2: found white -1: found in cdb; 1: white; 2: cdb; 3: white+cdb; 4: !relay+white; 6: !relay+white+cdb; */ int flagmimetype = 0; char *badmimeinit; static int fdblt; int flagloadertype = 0; /* 1: cdb; 2: !relay+cdb; -1: found in cdb */ char *badloaderinit; static int fdmav; int flagmav = 0; int localmf = 0; /* 1: domainpart->rcpthosts; 2: ->mailformrules; 3: ->remoteinfo; 4: ->DN(Email) */ char *localmfcheck; char *mfdnscheck; char *qhpsi; char *base64; int maxrcptcount = 0; int flagerrcpts = 0; int flagnotorious = 0; int tarpitcount = 0; int tarpitdelay = 0; int greylist = 0; stralloc pgbind = {0}; char *auth; /* -1: Cert 0: none 1: login/plain 2: cram 3: login/plain/cram 11: must_login/plain 12: must_2 13: must_3 */ int smtpauth = 0; int seenauth = 0; /* 1:ESMTPA 2:~CLIENTDN */ stralloc authmethod = {0}; /* -1: TLS 0: none 1: STARTTLS 2: require_STARTTLS 3: relay_if_CLIENTDN 4: require_+_relay_if_CLIENTDN */ int starttls = 0; int seentls = 0; /* 1:~STARTTLS 2:~TLS 3:~CLIENTDN */ char *ucspitls = 0; char *tlsversion; char *cipher; char *cipherperm; char *cipherused; char *clientdn; char *clientcn; char *dnemail; stralloc mailto = {0}; stralloc deliverto = {0}; char *delivermailto; stralloc rblinfo = {0}; char *rblsmtpd; int flagspf = 0; static stralloc spfbounce = {0}; static void setup() { char *x; unsigned long u; int r; flagip6 = 1; // GCC 10 implicit int declarition if global var if (control_init() == -1) die_control(); if (control_rldef(&greeting, "control/smtpgreeting", 1, (char *)0) != 1) die_control(); liphostok = control_rldef(&liphost, "control/localiphost", 1, (char *)0); if (liphostok == -1) die_control(); if (control_readint(&timeout, "control/timeoutsmtpd") == -1) die_control(); if (timeout <= 0) timeout = 1; if (rcpthosts_init() == -1) die_control(); if (recipients_init() == -1) die_control(); bmfok = control_readfile(&bmf, "control/badmailfrom", 0); if (bmfok == -1) die_control(); if (bmfok) if (!constmap_init(&mapbmf, bmf.s, bmf.len, 0)) die_nomem(); brtok = control_readfile(&brt, "control/badrcptto", 0); if (brtok == -1) die_control(); if (brtok) if (!constmap_init(&mapbrt, brt.s, brt.len, 0)) die_nomem(); if (control_readint(&databytes, "control/databytes") == -1) die_control(); x = env_get("DATABYTES"); if (x) { scan_ulong(x, &u); databytes = u; } if (!(databytes + 1)) --databytes; if (!stralloc_copys(&protocol, "ESMTP")) die_nomem(); /* RFC 3848 */ remoteip = env_get("TCP6REMOTEIP"); /* compactified IPv6 */ if (!remoteip) remoteip = env_get("TCPREMOTEIP"); /* allow other tcpserver/sslserver */ if (remoteip && (str_chr(remoteip, ':') < str_len(remoteip))) { if (byte_equal(remoteip, 7, V4MAPPREFIX)) { remoteip = remoteip + 7; flagip6 = 0; } } else { flagip6 = 0; } if (!remoteip) { remoteip = "unknown"; flagip6 = -1; } local = env_get("TCP6LOCALHOST"); if (!local) local = env_get("TCPLOCALHOST"); if (!local) local = env_get("TCP6LOCALIP"); if (!local) local = env_get("TCPLOCALIP"); if (!local) local = "unknown"; localport = env_get("TCP6LOCALPORT"); if (!localport) localport = env_get("TCPLOCALPORT"); if (!localport) localport = "0"; remotehost = env_get("TCP6REMOTEHOST"); if (!remotehost) remotehost = env_get("TCPREMOTEHOST"); if (!remotehost) remotehost = "unknown"; remoteinfo = env_get("TCP6REMOTEINFO"); if (!remoteinfo) remoteinfo = env_get("TCPREMOTEINFO"); relayclient = env_get("RELAYCLIENT"); if (!case_diffs(localport, PORT_SMTPS)) { if (!modssl_info()) die_starttls(); starttls = -1; } mfdnscheck = env_get("MFDNSCHECK"); x = env_get("MAXRECIPIENTS"); if (x) { scan_ulong(x, &u); maxrcptcount = u; } if (!(maxrcptcount + 1)) --maxrcptcount; helocheck = env_get("HELOCHECK"); if (helocheck) { badhelook = control_readfile(&badhelo, "control/badhelo", 0); if (badhelook == -1) die_control(); if (badhelook) if (!constmap_init(&mapbhlo, badhelo.s, badhelo.len, 0)) die_nomem(); } x = env_get("TARPITCOUNT"); if (x) { scan_ulong(x, &u); tarpitcount = u; } x = env_get("TARPITDELAY"); if (x) { scan_ulong(x, &u); tarpitdelay = u; } x = env_get("POSTGREY"); // RFC 6647 if (x) { if (case_diffs(x, "-")) { greylist = 1; if (!stralloc_copys(&pgbind, x)) die_nomem(); if (!stralloc_append(&pgbind, "")) die_nomem(); } } localmfcheck = env_get("LOCALMFCHECK"); if (localmfcheck) { localmf = 1; if (str_len(localmfcheck) == 1 && *localmfcheck == '!') { localmf = 2; fdmav = open_read("control/mailfromrules.cdb"); if (fdmav == -1) localmf = 1; } if (str_len(localmfcheck) == 1 && *localmfcheck == '=') { localmf = 3; } if (str_len(localmfcheck) == 1 && *localmfcheck == '?') { localmf = 4; } } badmimeinit = env_get("BADMIMETYPE"); if (badmimeinit) { fdbmt = open_read("control/badmimetypes.cdb"); if (str_len(badmimeinit) == 1) { if (*badmimeinit == '-') flagmimetype = 0; else { if (*badmimeinit == '!') flagmimetype = 1; if (*badmimeinit == '+') flagmimetype = 4; } } if (fdbmt != -1) flagmimetype = flagmimetype + 2; } badloaderinit = env_get("BADLOADERTYPE"); if (badloaderinit) { if (str_len(badloaderinit) == 1) { if (*badloaderinit == '-') flagloadertype = 0; else { flagloadertype = 1; if (*badloaderinit == '+') flagloadertype = 2; fdblt = open_read("control/badloadertypes.cdb"); if (fdblt == -1) flagloadertype = 0; } } } base64 = env_get("BASE64"); qhpsi = env_get("QHPSI"); auth = env_get("SMTPAUTH"); if (auth) { smtpauth = 1; if (!case_diffs(auth, "-")) smtpauth = 0; if (!case_diffs(auth, "!")) smtpauth = 11; if (case_starts(auth, "cram")) smtpauth = 2; if (case_starts(auth, "+cram")) smtpauth = 3; if (case_starts(auth, "!cram")) smtpauth = 12; if (case_starts(auth, "!+cram")) smtpauth = 13; } if (!seentls) { ucspitls = env_get("UCSPITLS"); if (ucspitls) { starttls = 1; if (!case_diffs(ucspitls, "-")) starttls = 0; if (!case_diffs(ucspitls, "!")) starttls = 2; if (!case_diffs(ucspitls, "?")) starttls = 3; if (!case_diffs(ucspitls, "@")) starttls = 4; } } delivermailto = env_get("DELIVERTO"); if (delivermailto) { if (!stralloc_cats(&mailto, delivermailto)) die_nomem(); if (!stralloc_cats(&mailto, " ")) die_nomem(); } rblsmtpd = env_get("RBLSMTPD"); if (rblsmtpd) { if (!stralloc_cats(&rblinfo, rblsmtpd)) die_nomem(); if (!stralloc_0(&rblinfo)) die_nomem(); } x = env_get("SPF"); if (x) { scan_ulong(x, &u); flagspf = u; } if (flagspf < 0 || flagspf > 6) die_control(); if (flagspf) { r = control_readline(&spflocalrules, "control/spflocalrules"); if (r == -1) die_control(); if (!stralloc_0(&spflocalrules)) die_nomem(); if (control_rldef(&spfexplain, "control/spfexplain", 0, SPF_DEFEXP) == -1) die_control(); if (!stralloc_0(&spfexplain)) die_nomem(); } x = env_get("UTF8"); if (x) flagutf8 = 1; if (!stralloc_copys(&helohost, "")) die_nomem(); // helohost is initially empty if (!stralloc_0(&helohost)) die_nomem(); fakehelo = 0; } static void auth_info(char *method) { if (!env_put("AUTHPROTOCOL", method)) die_nomem(); if (!env_put("AUTHUSER", remoteinfo)) die_nomem(); if (!env_unset("TCPREMOTEINFO")) die_read(); if (!env_put("TCPREMOTEINFO", remoteinfo)) die_nomem(); if (!env_unset("TCP6REMOTEINFO")) die_read(); if (!env_put("TCP6REMOTEINFO", remoteinfo)) die_nomem(); if (!stralloc_append(&protocol, "A")) die_nomem(); } static int modssl_info() { tlsversion = env_get("SSL_PROTOCOL"); if (!tlsversion) return 0; cipher = env_get("SSL_CIPHER"); if (!cipher) cipher = "unknown"; cipherperm = env_get("SSL_CIPHER_ALGKEYSIZE"); if (!cipherperm) cipherperm = "unknown"; cipherused = env_get("SSL_CIPHER_USEKEYSIZE"); if (!cipherused) cipherused = "unknown"; clientdn = env_get("SSL_CLIENT_S_DN"); if (!clientdn) { clientdn = "none"; } else { seentls = 3; seenauth = 2; smtpauth = -1; relayclient = ""; } if (!stralloc_copys(&tlsinfo, tlsversion)) die_nomem(); if (!stralloc_cats(&tlsinfo, ": ")) die_nomem(); if (!stralloc_cats(&tlsinfo, cipher)) die_nomem(); if (!stralloc_cats(&tlsinfo, " [")) die_nomem(); if (!stralloc_cats(&tlsinfo, cipherused)) die_nomem(); if (!stralloc_cats(&tlsinfo, "/")) die_nomem(); if (!stralloc_cats(&tlsinfo, cipherperm)) die_nomem(); if (!stralloc_cats(&tlsinfo, "] \n")) die_nomem(); if (!stralloc_cats(&tlsinfo, " DN=")) die_nomem(); if (!stralloc_cats(&tlsinfo, clientdn)) die_nomem(); if (!stralloc_0(&tlsinfo)) die_nomem(); if (!stralloc_append(&protocol, "S")) die_nomem(); if (seentls == 3 && starttls == 4) { clientcn = env_get("SSL_CLIENT_S_DN_CN"); remoteinfo = clientcn ? clientcn : clientdn; dnemail = env_get("SSL_CLIENT_S_DN_Email"); if (!dnemail) dnemail = "unknown"; if (!stralloc_cats(&authmethod, "cert")) die_nomem(); auth_info(authmethod.s); } return 1; } /* this file is too long --------------------------------- Address checks */ stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */ stralloc eddr = {0}; /* extended address; used for smart address recognition */ stralloc rddr = {0}; /* test anti-spoofing host name */ stralloc mailfrom = {0}; stralloc rcptto = {0}; stralloc user = {0}; stralloc fuser = {0}; stralloc mfparms = {0}; int seenmail = 0; int flagaddr; /* defined if seenmail */ int flagrcpt; int flagdnsmf = 0; int flagsize; int rcptcount = 0; static int addrparse(char *arg) { int i; char ch; char terminator; struct ip4_address ip4s; struct ip6_address ip6s; int flagesc; int flagquoted; terminator = '>'; i = str_chr(arg, '<'); if (arg[i]) arg += i + 1; else return 0; /* strip source route */ if (*arg == '@') while (*arg) if (*arg++ == ':') break; if (!stralloc_copys(&addr, "")) die_nomem(); flagesc = 0; flagquoted = 0; for (i = 0; (ch = arg[i]); ++i) { /* copy arg to addr, stripping quotes */ if (flagesc) { if (!stralloc_append(&addr, &ch)) die_nomem(); flagesc = 0; } else { if (!flagquoted && (ch == terminator)) break; switch (ch) { case '\\': flagesc = 1; break; case '"': flagquoted = !flagquoted; break; default: if (!stralloc_append(&addr, &ch)) die_nomem(); } } } /* could check for termination failure here, but why bother? */ if (!stralloc_append(&addr, "")) die_nomem(); if (liphostok) { i = byte_rchr(addr.s, addr.len, '@'); if (i < addr.len) /* if not, partner should go read rfc 821 */ if (addr.s[i + 1] == '[') { if (byte_rchr(addr.s + i + 2, addr.len - i - 2, ':') < str_len(addr.s)) { /* @[IPv6::] */ if (!addr.s[i + 1 + ip6_scanbracket(addr.s + i + 1, (char *)&ip6s.d)]) if (ipme_is6(&ip6s)) { addr.len = i + 1; if (!stralloc_cat(&addr, &liphost)) die_nomem(); } } else { /* @[IPv4] */ if (!addr.s[i + 1 + ip4_scanbracket(addr.s + i + 1, (char *)&ip4s.d)]) if (ipme_is4(&ip4s)) { addr.len = i + 1; if (!stralloc_cat(&addr, &liphost)) die_nomem(); } } } if (!stralloc_0(&addr)) die_nomem(); } if (addr.len > 900) return 0; return 1; } int bhelocheck() { int i; int j; int k = 0; char subvalue; if (badhelook && helohost.len > 1) { /* helohost! */ if (!stralloc_copyb(&eddr, helohost.s, helohost.len - 1)) die_nomem(); if (!stralloc_append(&eddr, "!")) die_nomem(); if (!stralloc_0(&eddr)) die_nomem(); if (constmap(&mapbhlo, eddr.s, eddr.len - 1)) return -3; if (constmap(&mapbhlo, helohost.s, helohost.len - 1)) return -1; i = 0; for (j = 0; j < badhelo.len; ++j) if (!badhelo.s[j]) { subvalue = badhelo.s[i] != '!'; if (!subvalue) i++; if ((k != subvalue) && wildmat(helohost.s, badhelo.s + i)) k = subvalue; i = j + 1; } return k; } return 0; } static int bmfcheck() { int i = 0; int j = 0; int k = 0; int at = 0; int dlen; int rlen; char subvalue; if (bmfok && mailfrom.len > 1) { rlen = str_len(remotehost); at = byte_rchr(mailfrom.s, mailfrom.len, '@'); /* '?' enhanced address to skip all other tests including MFDNSCHECK */ if (!stralloc_copys(&eddr, "?")) die_nomem(); if (!stralloc_cat(&eddr, &mailfrom)) die_nomem(); case_lowerb(eddr.s, eddr.len); if (constmap(&mapbmf, eddr.s, eddr.len - 1)) return -110; /* '+' extended address for none-RELAYCLIENTS */ if (at && !relayclient) { if (!stralloc_copyb(&eddr, mailfrom.s, mailfrom.len - 1)) die_nomem(); if (!stralloc_append(&eddr, "+")) die_nomem(); if (!stralloc_0(&eddr)) die_nomem(); case_lowerb(eddr.s, eddr.len); if (constmap(&mapbmf, eddr.s + at, eddr.len - at - 1)) return -5; } /* '-' extended address from UNKNOWN */ if (at && !case_diffs(remotehost, "unknown")) { if (!stralloc_copyb(&eddr, mailfrom.s, mailfrom.len - 1)) die_nomem(); if (!stralloc_append(&eddr, "-")) die_nomem(); if (!stralloc_0(&eddr)) die_nomem(); case_lowerb(eddr.s, eddr.len); if (constmap(&mapbmf, eddr.s + at, eddr.len - at - 1)) return -4; } /* '=' extended address for WELLKNOWN senders */ else if (at && rlen >= mailfrom.len - at - 1) { dlen = mailfrom.len - at - 2; if (!stralloc_copyb(&eddr, mailfrom.s, mailfrom.len - 1)) die_nomem(); if (!stralloc_append(&eddr, "=")) die_nomem(); if (!stralloc_0(&eddr)) die_nomem(); case_lowerb(eddr.s, eddr.len); if (str_diffn(remotehost + rlen - dlen, eddr.s + at + 1, dlen)) if (constmap(&mapbmf, eddr.s + at, eddr.len - at - 1)) return -3; /* '~' extended address for MISMATCHED Domains */ if (case_diffrs(remotehost, mailfrom.s + at + 1)) { j = 0; do { if (!stralloc_copys(&eddr, "~")) die_nomem(); if (!stralloc_cats(&eddr, remotehost + j)) die_nomem(); if (!stralloc_0(&eddr)) die_nomem(); if (constmap(&mapbmf, eddr.s, eddr.len - 1)) return -2; j = byte_chr(remotehost + j, rlen - j, '.') + j + 1; } while (j > 0 && rlen - j > 0); } } /* Standard */ if (constmap(&mapbmf, mailfrom.s, mailfrom.len - 1)) return -1; if (at && at < mailfrom.len) if (constmap(&mapbmf, mailfrom.s + at, mailfrom.len - at - 1)) return -1; /* Wildmating */ i = k = 0; for (j = 0; j < bmf.len; ++j) { if (!bmf.s[j]) { subvalue = bmf.s[i] != '!'; if (!subvalue) i++; if ((k != subvalue) && wildmat(mailfrom.s, bmf.s + i)) k = subvalue; i = j + 1; } } return k; } return 0; } static int brtcheck() { int i; int j = 0; int k = 0; char subvalue; if (brtok) { if (constmap(&mapbrt, addr.s, addr.len - 1)) return -2; int at = byte_rchr(addr.s, addr.len, '@'); if (at < addr.len) if (constmap(&mapbrt, addr.s + at, addr.len - at - j)) return -1; /* '#' enhanced address to consider invalid rcptto addresses for none-relayclients */ if (!relayclient) { if (!stralloc_copys(&eddr, "+")) die_nomem(); if (!stralloc_cat(&eddr, &addr)) die_nomem(); if (!stralloc_0(&eddr)) die_nomem(); case_lowerb(eddr.s, eddr.len); if (constmap(&mapbmf, eddr.s, eddr.len - 1)) return 110; } i = 0; for (j = 0; j < brt.len; ++j) { if (!brt.s[j]) { subvalue = brt.s[i] != '!'; if (!subvalue) i++; if ((k != subvalue) && wildmat(addr.s, brt.s + i)) k = subvalue; i = j + 1; } } return k; } return 0; } static int addrallowed(char *arg) { int r; r = rcpthosts(arg, str_len(arg)); if (r == -1) die_control(); return r; } static int rcptallowed() { int r; r = recipients(addr.s, str_len(addr.s)); #ifdef PAM111421 if (r == 111) die_recipients(); #endif if (r == -3) die_recipients(); if (r == -2) die_nomem(); if (r == -1) die_control(); return r; } static int localaddr(char *mf) { int at; int mflen; mflen = str_len(mf); if (mflen < 1) return 0; if (localmf == 4) { if (!case_diffs(dnemail, mf)) return 2; return -4; } if (localmf == 3) { if (!case_diffs(remoteinfo, mf)) return 2; return -3; } else if (localmf == 2) { return mfrules(fdmav, remoteip, remotehost, remoteinfo, mf); } else { if (str_len(localmfcheck) > 1) { case_lowerb(localmfcheck, str_len(localmfcheck)); at = byte_rchr(mf, mflen, '@'); if (at < mflen) if (!str_diffn(localmfcheck, mf + at + 1, mflen - at - 1)) return 2; } if (addrallowed(mf)) return 3; return -2; } } static int spf_check(int flag6) { int r; if (mailfrom.len <= 1) { flagspf = 0; return 0; } DNS_INIT r = spf_query(remoteip, helohost.s, mailfrom.s, local, flag6); if (r == SPF_NOMEM) die_nomem(); if (!stralloc_0(&spfinfo)) die_nomem(); switch (r) { case SPF_ME: case SPF_OK: if (!env_put("SPFRESULT", "pass")) die_nomem(); flagspf = 10; break; case SPF_LOOP: case SPF_ERROR: case SPF_SYNTAX: case SPF_EXHAUST: if (!env_put("SPFRESULT", "error")) die_nomem(); if (flagspf < 2) { flagspf = 0; break; } out("451 SPF lookup failure (#4.3.0)\r\n"); return -1; case SPF_NONE: if (!env_put("SPFRESULT", "none")) die_nomem(); flagspf = 0; break; case SPF_UNKNOWN: if (!env_put("SPFRESULT", "unknown")) die_nomem(); if (flagspf < 6) break; else return 4; case SPF_NEUTRAL: if (!env_put("SPFRESULT", "neutral")) die_nomem(); if (flagspf < 5) break; else return 3; case SPF_SOFTFAIL: if (!env_put("SPFRESULT", "softfail")) die_nomem(); if (flagspf < 4) break; else return 2; case SPF_FAIL: if (!env_put("SPFRESULT", "fail")) die_nomem(); if (flagspf < 3) break; if (!spf_parse(&spfbounce, spfexpmsg.s, expdomain.s)) die_nomem(); return 1; } return 0; } /* this file is too long --------------------------------- MF parser */ static int mailfrom_size(char *arg) { unsigned long r; unsigned long sizebytes = 0; scan_ulong(arg, &r); sizebytes = r; if (databytes) if (sizebytes > databytes) return 1; return 0; } static void mailfrom_auth(char *arg, int len) { if (!stralloc_copys(&fuser, "")) die_nomem(); if (case_starts(arg, "<>")) { if (!stralloc_cats(&fuser, "unknown")) die_nomem(); } else { while (len) { if (*arg == '+') { if (case_starts(arg, "+3D")) { arg = arg + 2; len = len - 2; if (!stralloc_cats(&fuser, "=")) die_nomem(); } if (case_starts(arg, "+2B")) { arg = arg + 2; len = len - 2; if (!stralloc_cats(&fuser, "+")) die_nomem(); } } else { if (!stralloc_catb(&fuser, arg, 1)) die_nomem(); } arg++; len--; } } if (!stralloc_0(&fuser)) die_nomem(); if (!remoteinfo) { remoteinfo = fuser.s; if (!env_unset("TCPREMOTEINFO")) die_read(); if (!env_put("TCPREMOTEINFO", remoteinfo)) die_nomem(); if (!env_unset("TCP6REMOTEINFO")) die_read(); if (!env_put("TCP6REMOTEINFO", remoteinfo)) die_nomem(); } } static void mailfrom_parms(char *arg) { int len; if ((len = str_len(arg))) { if (!stralloc_copys(&mfparms, "")) die_nomem(); while (len) { arg++; len--; if (*arg == ' ' || *arg == '\0') { if (flagutf8) if (case_starts(mfparms.s, "SMTPUTF8")) flagutf8 = 2; if (case_starts(mfparms.s, "SIZE=")) if (mailfrom_size(mfparms.s + 5)) { flagsize = 1; return; } if (case_starts(mfparms.s, "AUTH=")) mailfrom_auth(mfparms.s + 5, mfparms.len - 5); if (!stralloc_copys(&mfparms, "")) die_nomem(); } else if (!stralloc_catb(&mfparms, arg, 1)) { die_nomem(); } } } } /* this file is too long --------------------------------- SMTP dialog */ static void smtp_helo(char *arg) { smtp_greet("250 "); out("\r\n"); flush(); seenmail = 0; rcptcount = 0; seenhelo++; dohelo(arg); } static void smtp_ehlo(char *arg) { char size[FMT_ULONG]; smtp_greet("250-"); out("\r\n"); out("250-PIPELINING\r\n250-8BITMIME\r\n"); if (flagutf8) out("250-SMTPUTF8\r\n"); if (starttls > 0 && !seentls) out("250-STARTTLS\r\n"); switch (smtpauth) { case 1: case 11: out("250-AUTH LOGIN PLAIN\r\n"); break; case 2: case 12: out("250-AUTH CRAM-MD5\r\n"); break; case 3: case 13: out("250-AUTH LOGIN PLAIN CRAM-MD5\r\n"); break; } size[fmt_ulong(size, (unsigned long)databytes)] = 0; out("250 SIZE "); out(size); out("\r\n"); seenhelo++; seenmail = 0; rcptcount = 0; dohelo(arg); } static void smtp_rset(char *x) { (void)x; seenmail = 0; rcptcount = 0; /* RFC 5321: seenauth + seentls stay */ if (!stralloc_copys(&mailfrom, "")) die_nomem(); if (!stralloc_copys(&rcptto, "")) die_nomem(); out("250 flushed\r\n"); } static void smtp_starttls() { if (starttls == 0) err_starttls(); out("220 Ready to start TLS (#5.7.0)\r\n"); flush(); if (!starttls_init()) die_starttls(); buffer_init(&bi, saferead, FDIN, inbuf, sizeof(inbuf)); seentls = 2; if (!starttls_info()) die_starttls(); if (!modssl_info()) die_starttls(); /* reset SMTP state */ seenhelo = 0; seenmail = 0; rcptcount = 0; if (!stralloc_copys(&addr, "")) die_nomem(); if (!stralloc_copys(&helohost, "")) die_nomem(); if (!stralloc_copys(&mailfrom, "")) die_nomem(); if (!stralloc_copys(&rcptto, "")) die_nomem(); if (seenauth == 1) seenauth = 0; /* Otherwise Auth by client Cert */ } static void smtp_mail(char *arg) { if (flagutf8) if (!stralloc_cats(&protocol, "UTF8")) die_nomem(); if (!stralloc_0(&protocol)) die_nomem(); if ((starttls > 1) && !seentls) { err_tlsreq("Reject::TLS::missing", protocol.s, remoteip, remotehost, helohost.s); return; } if (smtpauth > 10 && !seenauth) { err_authreq("Reject::AUTH::missing", protocol.s, remoteip, remotehost, helohost.s); return; } if (!addrparse(arg)) { err_syntax(); return; } flagsize = 0; rcptcount = 0; mailfrom_parms(arg); seenmail++; if (relayclient && localmf) { flagmav = localaddr(addr.s); if (flagmav > 0) if (!stralloc_append(&protocol, "M")) die_nomem(); } if (!stralloc_copys(&rcptto, "")) die_nomem(); if (!stralloc_copys(&mailfrom, addr.s)) die_nomem(); if (!stralloc_0(&mailfrom)) die_nomem(); if (!env_put("MAILFROM", mailfrom.s)) die_nomem(); flagaddr = bmfcheck(); if (flagaddr != -110) if (mfdnscheck) flagdnsmf = dnsq(mailfrom.s, 'M'); out("250 ok\r\n"); } /* this file is too long --------------------------------- Greylisting */ static int postgrey_scanner() { int child; int wstat; char *postgrey_scannerarg[] = { "bin/qmail-postgrey", pgbind.s, mailfrom.s, addr.s, remoteip, remotehost, 0}; switch (child = fork()) { case -1: return err_forkgl(); case 0: execv(*postgrey_scannerarg, postgrey_scannerarg); _exit(1); } wait_pid(&wstat, child); if (wait_crashed(wstat)) return err_postgl(); switch (wait_exitcode(wstat)) { case 10: return 1; default: return 0; } } static void smtp_rcpt(char *arg) { char *rcptok = 0; if (!seenmail) { err_wantmail(); return; } if (!addrparse(arg)) { err_syntax(); return; } rcptcount++; /* this file is too long --------------------------------- Split Horizon envelope checks */ if (!relayclient) { if (!seenhelo && helocheck) /* Helo rejects */ if (str_len(helocheck) == 1) { err_helo( "Reject::SNDR::Bad_Helo", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, "0"); return; } if (flagbadhelo) { switch (flagbadhelo) { case -2: badhelocond = "!"; break; case -1: badhelocond = "."; break; default: badhelocond = "*"; break; } err_helo( "Reject::SNDR::Bad_Helo", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, badhelocond); return; } if (flagdnshelo > 0) { err_helo( "Reject::SNDR::DNS_Helo", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, badhelocond); return; } if (flagdnsmf > 0) { /* Mail from rejects */ err_mfdns( "Reject::ORIG::DNS_MF", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } if (!addrallowed(addr.s)) { /* Relaying rejects */ err_nogateway( "Reject::SNDR::Invalid_Relay", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } if (greylist && (postgrey_scanner() == 1)) { /* Greylisting */ postgrey( "Deferred::SNDR::Grey_Listed", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } if (tarpitcount && flagerrcpts >= tarpitcount) { /* Tarpitting et al. */ if (tarpitdelay == 999) flagnotorious++; err_rcpts( "Reject::RCPT::Toomany_Rcptto", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } if (tarpitcount && rcptcount >= tarpitcount) if (tarpitdelay > 0 && tarpitdelay < 999) sleep(tarpitdelay); flagrcpt = rcptallowed(); /* Rcpt to rejects */ if (!flagrcpt) { err_recipient( "Reject::RCPT::Failed_Rcptto", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); flagerrcpts++; return; } if (flagspf) /* SPF rejects */ if (spf_check(flagip6) > 0) { if (!stralloc_0(&spfbounce)) die_nomem(); // spfbounce is 0-terminated in any case err_spf( "Reject::SPF::Fail", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, spfbounce.s); return; } } /* this file is too long --------------------------------- Local checks */ else { if (flagmimetype == 4 || flagmimetype == 6) flagmimetype = 0; if (flagloadertype == 2) flagloadertype = 0; if (flagmav < 0) { err_mav( "Reject::ORIG::Invalid_Mailfrom", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } --addr.len; if (!stralloc_cats(&addr, relayclient)) die_nomem(); if (!stralloc_0(&addr)) die_nomem(); } /* this file is too long --------------------------------- Common checks */ if (flagmimetype == 2 || flagmimetype == 3 || flagmimetype == 6) cdb_init(&cdbm, fdbmt); if (flagloadertype == 1) cdb_init(&cdbl, fdblt); if (flagaddr && flagaddr != -110) { switch (flagaddr) { case -1: badmailcond = "@"; break; case -2: badmailcond = "~"; break; case -3: badmailcond = "="; break; case -4: badmailcond = "-"; break; case -5: badmailcond = "+"; break; default: badmailcond = "*"; break; } err_bmf( "Reject::ORIG::Bad_Mailfrom", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, badmailcond); return; } flagrcpt = brtcheck(); if (flagrcpt == 110) { err_brt( "Reject::RCPT::Invalid_Rcptto", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } else if (flagrcpt > 0) { err_brt( "Reject::RCPT::Bad_Rcptto", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } if (flagsize) { err_size( "Reject::DATA::Invalid_Size", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } if (maxrcptcount && rcptcount > maxrcptcount) { err_rcpts( "Reject::RCPT::Toomany_Rcptto", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } /* this file is too long --------------------------------- Checks done; mailfrom/recipient accepted */ if (!stralloc_cats(&rcptto, "T")) die_nomem(); if (!stralloc_cats(&rcptto, addr.s)) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); if (!stralloc_cats(&mailto, addr.s)) die_nomem(); if (!stralloc_cats(&mailto, " ")) die_nomem(); if (!stralloc_copys(&deliverto, mailto.s)) die_nomem(); if (!stralloc_0(&deliverto)) die_nomem(); if (!env_put("RCPTTO", deliverto.s)) die_nomem(); /* this file is too long --------------------------------- Additional logging */ switch (flagrcpt) { case 1: rcptok = "Recipients_Cdb"; break; case 2: rcptok = "Recipients_Pam"; break; case 3: rcptok = "Recipients_Users"; break; case 4: rcptok = "Recipients_Wild"; break; default: rcptok = "Rcpthosts_Rcptto"; break; } if (seenauth) smtp_loga( "Accept::AUTH::", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, remoteinfo, authmethod.s); else if (flagmav > 0) smtp_logg( "Accept::ORIG::Local_Sender", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); else if (relayclient) smtp_logg( "Accept::SNDR::Relay_Client", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); else if (flagspf == 10) smtp_logr("Accept::SPF::", rcptok, protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); else smtp_logr( "Accept::RCPT::", rcptok, protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); out("250 ok\r\n"); } struct qmail qqt; unsigned long bytestooverflow = 0; stralloc line = {0}; stralloc base64types = {0}; stralloc badmimetype = {0}; stralloc badloadertype = {0}; unsigned int nolines = 0; unsigned int flagb64 = 0; /* lineno with BASE64MESSAGE */ unsigned int flagbase = 0; /* lineno with actual base64 content */ unsigned int flagblank = 0; static void queue_put(char *ch) { int i; if (flagmimetype > 0 || flagloadertype > 0) { if (line.len <= BUFFER_SIZE) if (!stralloc_catb(&line, ch, 1)) die_nomem(); /* Reassamble chars to line; prepend with 'L' */ if (*ch == '\n') { nolines++; if (line.len == 2) { flagblank = nolines; flagbase = 0; } if (*(line.s + 1) == 'C' || *(line.s + 1) == 'c') if (case_startb(line.s + 1, line.len - 2, BASE64MESSAGE)) flagb64 = nolines; if (flagb64 && nolines == flagblank + 1 && line.len > MIMETYPE_LEN + 2) flagbase = nolines; if (*(line.s + 1) == '-') { flagb64 = 0; flagbase = 0; } if (flagmimetype > 0 && flagbase == nolines) { /* badmimetype */ if (!stralloc_catb(&base64types, line.s + 1, MIMETYPE_LEN)) die_nomem(); if (!stralloc_0(&base64types)) die_nomem(); if (flagmimetype == 2 || flagmimetype == 3 || flagmimetype == 6) { if (cdb_find(&cdbm, line.s + 1, MIMETYPE_LEN)) { cdb_free(&cdbm); close(fdbmt); if (!stralloc_copyb(&badmimetype, line.s + 1, MIMETYPE_LEN)) die_nomem(); if (!stralloc_0(&badmimetype)) die_nomem(); if (!stralloc_cats(&rcptto, "M")) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); qmail_fail(&qqt); flagmimetype = -1; } } } if (flagbase && line.len > LOADER_LEN + 2) { if (flagloadertype >= 1 || flagmimetype >= 1) { for (i = 0; i < line.len - LOADER_LEN; ++i) { if (flagloadertype == 1 && *(line.s + i) == *badloaderinit) { /* badloadertype */ if (cdb_find(&cdbl, line.s + i, LOADER_LEN)) { cdb_free(&cdbl); close(fdbmt); if (!stralloc_copyb(&badloadertype, line.s + i, LOADER_LEN)) die_nomem(); if (!stralloc_0(&badloadertype)) die_nomem(); if (!stralloc_cats(&rcptto, "L")) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); qmail_fail(&qqt); flagloadertype = -1; } } if (flagmimetype == 1 || flagmimetype == 3 || flagmimetype == 4) { if (*(line.s + i) == ' ' || *(line.s + i) == '\t') { /* white spaces */ if (!stralloc_copyb(&badmimetype, line.s + i - 2, MIMETYPE_LEN)) die_nomem(); if (!stralloc_0(&badmimetype)) die_nomem(); if (!stralloc_cats(&rcptto, "M")) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); qmail_fail(&qqt); flagmimetype = -2; } } } } } line.len = 0; if (!stralloc_copys(&line, "L")) die_nomem(); } } if (bytestooverflow) if (!--bytestooverflow) qmail_fail(&qqt); qmail_put(&qqt, ch, 1); } static void blast(int *hops) { char ch; int state; int seencr; int flaginheader; int pos; /* number of bytes since most recent \n, if fih */ int flagmaybex; /* 1 if this line might match RECEIVED, if fih */ int flagmaybey; /* 1 if this line might match \r\n, if fih */ int flagmaybez; /* 1 if this line might match DELIVERED, if fih */ state = 1; *hops = 0; flaginheader = 1; pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; seencr = 0; for (;;) { buffer_get(&bi, &ch, 1); if (ch == '\n') { if (seencr == 0) { buffer_seek(&bi, -1); ch = '\r'; } } if (ch == '\r') seencr = 1; else seencr = 0; if (flaginheader) { if (pos < 9) { if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0; if (flagmaybez) if (pos == 8) ++*hops; if (pos < 8) if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0; if (flagmaybex) if (pos == 7) ++*hops; if (pos < 2) if (ch != "\r\n"[pos]) flagmaybey = 0; if (flagmaybey) if (pos == 1) flaginheader = 0; ++pos; } if (ch == '\n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; } } switch (state) { case 0: if (ch == '\n') straynewline(); if (ch == '\r') { state = 4; continue; } break; case 1: /* \r\n */ if (ch == '\n') straynewline(); if (ch == '.') { state = 2; continue; } if (ch == '\r') { state = 4; continue; } state = 0; break; case 2: /* \r\n + . */ if (ch == '\n') straynewline(); if (ch == '\r') { state = 3; continue; } state = 0; break; case 3: /* \r\n + .\r */ if (ch == '\n') return; queue_put("."); queue_put("\r"); if (ch == '\r') { state = 4; continue; } state = 0; break; case 4: /* + \r */ if (ch == '\n') { state = 1; break; } if (ch != '\r') { queue_put("\r"); state = 0; } } queue_put(&ch); } } char accept_buf[FMT_ULONG]; static void acceptmessage(unsigned long qp) { datetime_sec when; when = now(); out("250 ok "); accept_buf[fmt_ulong(accept_buf, (unsigned long)when)] = 0; out(accept_buf); out(" qp "); accept_buf[fmt_ulong(accept_buf, qp)] = 0; out(accept_buf); out("\r\n"); } static void smtp_data() { int hops; unsigned long qp; char *qqx; if (!seenmail) { err_wantmail(); return; } if (!rcptto.len) { err_wantrcpt(); return; } if (flagnotorious) { err_notorious(); } seenmail = 0; if (databytes) bytestooverflow = databytes + 1; if (!stralloc_copys(&addr, "")) die_nomem(); if (!stralloc_cats(&addr, rcptto.s + 1)) die_nomem(); if (!stralloc_0(&addr)) die_nomem(); if (qmail_open(&qqt) == -1) { err_qqt(); return; } qp = qmail_qp(&qqt); out("354 go ahead\r\n"); if (flagspf && !relayclient) spfheader(&qqt, spfinfo.s, local, remoteip, helohost.s, mailfrom.s); received(&qqt, protocol.s, local, remoteip, remotehost, remoteinfo, fakehelo, tlsinfo.s, rblinfo.s); blast(&hops); hops = (hops >= MAXHOPS); if (hops) qmail_fail(&qqt); if (base64 && base64types.len == 0) { if (!stralloc_cats(&rcptto, "Q")) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); } qmail_from(&qqt, mailfrom.s); qmail_put(&qqt, rcptto.s, rcptto.len); qqx = qmail_close(&qqt); if (!*qqx) { acceptmessage(qp); return; } if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; } if (databytes) if (!bytestooverflow) { err_size( "Reject::DATA::Invalid_Size", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s); return; } if (flagmimetype < 0) { err_data( "Reject::DATA::Bad_MIME", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, badmimetype.s); return; } if (flagloadertype < 0) { err_data( "Reject::DATA::Bad_Loader", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, badloadertype.s); return; } if (*qqx == 'I') { err_data( "Reject::DKIM::Signature", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, "fail"); return; } if (*qqx == 'S') { err_data( "Reject::DATA::Spam_Message", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, "spam"); return; } if (*qqx == 'A') { err_data( "Reject::DATA::MIME_Attach", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, "MIME"); return; } if (*qqx == 'V') { if (qhpsi) err_data( "Reject::DATA::Virus_Infected", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, qhpsi); else err_data( "Reject::DATA::Virus_Infected", protocol.s, remoteip, remotehost, helohost.s, mailfrom.s, addr.s, "AV scanner"); return; } if (*qqx == 'D') out("554 "); else out("451 "); out(qqx + 1); out("\r\n"); } /* this file is too long --------------------------------- SMTP Auth */ char unique[FMT_ULONG + FMT_ULONG + 3]; static stralloc authin = {0}; /* input from SMTP client */ static stralloc pass = {0}; /* plain passwd or digest */ static stralloc resp = {0}; /* b64 response */ static stralloc chal = {0}; /* CRAM-MD5 plain challenge */ static stralloc slop = {0}; /* CRAM-MD5 b64 challenge */ char **childargs; char authbuf[512]; buffer ba = BUFFER_INIT(safewrite, FDAUTH, authbuf, sizeof(authbuf)); static int authgetl() { int i; if (!stralloc_copys(&authin, "")) die_nomem(); for (;;) { if (!stralloc_readyplus(&authin, 1)) die_nomem(); /* XXX */ i = buffer_get(&bi, authin.s + authin.len, 1); if (i != 1) die_read(); if (authin.s[authin.len] == '\n') break; ++authin.len; } if (authin.len > 0) if (authin.s[authin.len - 1] == '\r') --authin.len; authin.s[authin.len] = 0; if (*authin.s == '*' && *(authin.s + 1) == 0) return err_authabort(); if (authin.len == 0) return err_authinput(); return authin.len; } static int authenticate() { int child; int wstat; int pi[2]; if (!stralloc_0(&user)) die_nomem(); if (!stralloc_0(&pass)) die_nomem(); if (!stralloc_0(&chal)) die_nomem(); if (!env_put("AUTHUSER", user.s)) die_nomem(); if (pipe(pi) == -1) return err_pipe(); switch (child = fork()) { case -1: return err_fork(); case 0: close(pi[1]); if (fd_copy(FDAUTH, pi[0]) == -1) return err_pipe(); sig_pipedefault(); execvp(*childargs, childargs); _exit(1); } close(pi[0]); buffer_init(&ba, write, pi[1], authbuf, sizeof(authbuf)); if (buffer_put(&ba, user.s, user.len) == -1) return err_write(); if (buffer_put(&ba, pass.s, pass.len) == -1) return err_write(); if (smtpauth == 2 || smtpauth == 3 || smtpauth == 12 || smtpauth == 13) if (buffer_put(&ba, chal.s, chal.len) == -1) return err_write(); if (buffer_flush(&ba) == -1) return err_write(); close(pi[1]); if (!stralloc_copys(&chal, "")) die_nomem(); if (!stralloc_copys(&slop, "")) die_nomem(); byte_zero(authbuf, sizeof(authbuf)); if (wait_pid(&wstat, child) == -1) return err_child(); if (wait_crashed(wstat)) return err_child(); if (wait_exitcode(wstat)) { sleep(AUTHSLEEP); return 1; } /* no */ return 0; /* yes */ } static int auth_login(char *arg) { int r; if (smtpauth == 2 || smtpauth == 12) return 1; /* only login/plain */ if (*arg) { if ((r = b64decode((unsigned char *)arg, str_len(arg), &user)) == 1) return err_authinput(); } else { out("334 VXNlcm5hbWU6\r\n"); flush(); /* Username: */ if (authgetl() < 0) return -1; if ((r = b64decode((unsigned char *)authin.s, authin.len, &user)) == 1) return err_authinput(); } if (r == -1) die_nomem(); out("334 UGFzc3dvcmQ6\r\n"); flush(); /* Password: */ if (authgetl() < 0) return -1; if ((r = b64decode((unsigned char *)authin.s, authin.len, &pass)) == 1) return err_authinput(); if (r == -1) die_nomem(); if (!user.len || !pass.len) return err_authinput(); return authenticate(); } static int auth_plain(char *arg) { int r, id = 0; if (smtpauth == 2 || smtpauth == 12) return 1; /* only login/plain */ if (*arg) { if ((r = b64decode((unsigned char *)arg, str_len(arg), &resp)) == 1) return err_authinput(); } else { out("334 \r\n"); flush(); if (authgetl() < 0) return -1; if ((r = b64decode((unsigned char *)authin.s, authin.len, &resp)) == 1) return err_authinput(); } if (r == -1 || !stralloc_0(&resp)) die_nomem(); while (resp.s[id]) id++; /* "authorize-id\0userid\0passwd\0" */ if (resp.len > id + 1) if (!stralloc_copys(&user, resp.s + id + 1)) die_nomem(); if (resp.len > id + user.len + 2) if (!stralloc_copys(&pass, resp.s + id + user.len + 2)) die_nomem(); if (!user.len || !pass.len) return err_authinput(); return authenticate(); } static int auth_cram() { int i, r; char *s; if (smtpauth == 1 || smtpauth == 11) return 1; /* no challenge if login/plain */ s = unique; /* generate challenge */ s += fmt_uint(s, getpid()); *s++ = '.'; s += fmt_ulong(s, (unsigned long)now()); *s++ = '@'; *s++ = 0; if (!stralloc_copys(&chal, "<")) die_nomem(); if (!stralloc_cats(&chal, unique)) die_nomem(); if (!stralloc_cats(&chal, local)) die_nomem(); if (!stralloc_cats(&chal, ">")) die_nomem(); if (b64encode(&chal, &slop) < 0) die_nomem(); if (!stralloc_0(&slop)) die_nomem(); out("334 "); /* "334 base64_challenge \r\n" */ out(slop.s); out("\r\n"); flush(); if (authgetl() < 0) return -1; /* got response */ if ((r = b64decode((unsigned char *)authin.s, authin.len, &resp)) == 1) return err_authinput(); if (r == -1 || !stralloc_0(&resp)) die_nomem(); i = str_rchr(resp.s, ' '); s = resp.s + i; while (*s == ' ') ++s; resp.s[i] = 0; if (!stralloc_copys(&user, resp.s)) die_nomem(); /* userid */ if (!stralloc_copys(&pass, s)) die_nomem(); /* digest */ if (!user.len || !pass.len) return err_authinput(); return authenticate(); } static int err_noauth_handler(char *s) { (void)s; return err_noauth(); } struct authcmd { char *text; int (*fun)(char *); } authcmds[] = { { "login", auth_login}, { "plain", auth_plain}, {"cram-md5", auth_cram}, { 0, err_noauth_handler} }; static void smtp_auth(char *arg) { int i; char *cmd = arg; /* prevent users to expose userid + password over unencrypted connection */ if ((starttls > 1) && !seentls) { if (!stralloc_append(&protocol, "A")) die_nomem(); if (!stralloc_0(&protocol)) die_nomem(); err_authsetup("Reject::TLS::required", protocol.s, remoteip, remotehost, helohost.s); return; } if ((starttls > 1) && !seenhelo) { if (!stralloc_append(&protocol, "A")) die_nomem(); if (!stralloc_0(&protocol)) die_nomem(); err_tlsreq("Reject::AUTH::invalid", protocol.s, remoteip, remotehost, helohost.s); return; } if (!smtpauth) { out("503 auth not available (#5.3.3)\r\n"); flush(); _exit(0); } if (smtpauth && !*childargs) { err_authsetup("Reject::AUTH::invalid", protocol.s, remoteip, remotehost, helohost.s); flush(); _exit(1); } if (seenauth) { err_authd(); return; } if (seenmail) { err_authmail(); return; } if (!stralloc_copys(&user, "")) die_nomem(); if (!stralloc_copys(&pass, "")) die_nomem(); if (!stralloc_copys(&resp, "")) die_nomem(); if (!stralloc_copys(&chal, "")) die_nomem(); /* only needed for CRAM-MD5 */ i = str_chr(cmd, ' '); /* get AUTH type */ arg = cmd + i; while (*arg == ' ') ++arg; cmd[i] = 0; for (i = 0; authcmds[i].text; ++i) if (case_equals(authcmds[i].text, cmd)) break; if (!authcmds[i].text) { /* invalid auth cmd */ if (!stralloc_append(&protocol, "A")) die_nomem(); if (!stralloc_0(&protocol)) die_nomem(); err_authinvalid("Reject::AUTH::Method", protocol.s, remoteip, remotehost, helohost.s); return; } if (!stralloc_copys(&authmethod, authcmds[i].text)) die_nomem(); if (!stralloc_0(&authmethod)) die_nomem(); switch (authcmds[i].fun(arg)) { case 0: seenauth = 1; relayclient = ""; remoteinfo = user.s; auth_info(authmethod.s); out("235 ok, go ahead (#2.0.0)\r\n"); break; case 1: if (!stralloc_append(&protocol, "A")) die_nomem(); if (!stralloc_0(&protocol)) die_nomem(); err_authfail("Reject::AUTH::", protocol.s, remoteip, remotehost, helohost.s, user.s, authmethod.s); return; } } /* this file is too long --------------------------------- GO ON */ static void err_noop_handler(char *s) { (void)s; err_noop(); } static void err_vrfy_handler(char *s) { (void)s; err_vrfy(); } static void err_unimpl_handler(char *s) { (void)s; err_unimpl(); } struct commands smtpcommands[] = { { "rcpt", smtp_rcpt, 0}, { "mail", smtp_mail, 0}, { "data", smtp_data, flush}, { "auth", smtp_auth, flush}, { "quit", smtp_quit, flush}, { "helo", smtp_helo, flush}, { "ehlo", smtp_ehlo, flush}, { "rset", smtp_rset, flush}, { "help", smtp_help, flush}, { "noop", err_noop_handler, flush}, { "vrfy", err_vrfy_handler, flush}, {"starttls", smtp_starttls, flush}, { 0, err_unimpl_handler, flush} }; int main(int argc, char **argv) { childargs = argv + 1; sig_pipeignore(); if (chdir(auto_qmail) == -1) die_control(); setup(); smtpdlog_init(); if (ipme_init() != 1) die_ipme(); smtp_greet("220 "); out(" ESMTP\r\n"); flush(); if (commands(&bi, smtpcommands) == 0) die_read(); die_nomem(); return 0; }