diff options
author | Jannis Hoffmann <jannis@fehcom.de> | 2024-07-09 11:44:11 +0200 |
---|---|---|
committer | Jannis Hoffmann <jannis@fehcom.de> | 2024-07-09 11:44:11 +0200 |
commit | f1b71c9fe7dbb4886588a036399cf5ebe16b7c47 (patch) | |
tree | e07786aa479c9fb6ee3e537078470aaab5454f80 /src/qmail-smtpd.c | |
parent | a293489ee83c8b05d845a162dc2a4de026f3775d (diff) |
removed top level directory
Diffstat (limited to 'src/qmail-smtpd.c')
-rwxr-xr-x | src/qmail-smtpd.c | 1720 |
1 files changed, 1720 insertions, 0 deletions
diff --git a/src/qmail-smtpd.c b/src/qmail-smtpd.c new file mode 100755 index 0000000..b629948 --- /dev/null +++ b/src/qmail-smtpd.c @@ -0,0 +1,1720 @@ +#include <unistd.h> +#include "wildmat.h" +#include "buffer.h" +#include "stralloc.h" +#include "genalloc.h" +#include "alloc.h" +#include "qmail.h" +#include "auto_qmail.h" +#include "control.h" +#include "received.h" +#include "constmap.h" +#include "logmsg.h" +#include "ipme.h" +#include "fd.h" +#include "ip.h" +#include "qmail.h" +#include "str.h" +#include "fmt.h" +#include "scan.h" +#include "byte.h" +#include "case.h" +#include "env.h" +#include "now.h" +#include "exit.h" +#include "rcpthosts.h" +#include "recipients.h" +#include "mfrules.h" +#include "tls_start.h" +#include "smtpdlog.h" +#include "timeout.h" +#include "commands.h" +#include "cdbread.h" +#include "dns.h" +#include "wait.h" +#include "sig.h" +#include "close.h" +#include "open.h" +#include "base64.h" +#include "spf.h" + +/** @file qmail-smtpd.c -- authenticating ESMTP/ESMTPS server + @brief requires sslserver or tcpserver */ + +#define PAM111421 +#define AUTHSLEEP 5 +#define PORT_SMTPS "465" +#define SMTP_TIMEOUT 1200 + +#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 MAXHOPS 100 +unsigned long databytes = 0; +int timeout = SMTP_TIMEOUT; + +int modssl_info(); + +ssize_t safewrite(int fd,char *buf,int len) +{ + int r; + r = timeoutwrite(timeout,fd,buf,len); + if (r <= 0) _exit(1); + return r; +} + +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[BUFSIZE_LINE]; +buffer bi = BUFFER_INIT(saferead,FDIN,inbuf,sizeof(inbuf)); + +char outbuf[BUFSIZE_LINE]; +buffer bo = BUFFER_INIT(safewrite,FDOUT,outbuf,sizeof(outbuf)); + +char buflog[BUFSIZE_LOG]; +buffer bl = BUFFER_INIT(write,FDLOG,buflog,sizeof(buflog)); + +void flush() { buffer_flush(&bo); } // this triggers writing to STDIO +void out(char *s) { buffer_puts(&bo,s); } + +stralloc sa = {0}; +ipalloc ia = {0}; + +int bhelocheck(void); + +/* this file is too long -------------------------------------- DNS helper */ + +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}; + +void smtp_greet(char *code) +{ + buffer_puts(&bo,code); + buffer_put(&bo,greeting.s,greeting.len); +} +void smtp_help() +{ + out("214 s/qmail home page: https://www.fehcom.de/sqmail.html\r\n"); +} +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; + +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; +int flagmimetype = 0; /* 1: white; 2: cdb; 3: white+cdb; 4: !relay+white; 6: !relay+white+cdb; -1: found in cdb; -2: found white */ +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; +int smtpauth = 0; /* -1:Cert 0:none 1:login/plain 2:cram 3:login/plain/cram 11:must_login/plain 12:must_2 13:must_3 */ +int seenauth = 0; /* 1:ESMTPA 2:~CLIENTDN */ +stralloc authmethod = {0}; + +int starttls = 0; /* -1:TLS 0:none 1:STARTTLS 2:require_STARTTLS 3:relay_if_CLIENTDN 4:require_+_relay_if_CLIENTDN */ +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}; + +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; + +} + +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(); +} + +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; + +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; +} + +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; +} + +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; +} + +int addrallowed(char *arg) +{ + int r; + r = rcpthosts(arg,str_len(arg)); + if (r == -1) die_control(); + return r; +} + +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; +} + +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; + } +} + +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 */ + +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; +} + +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(); + } +} + +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 */ + +void smtp_helo(char *arg) +{ + smtp_greet("250 "); out("\r\n"); flush(); + seenmail = 0; rcptcount = 0; seenhelo++; + dohelo(arg); +} + +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); +} + +void smtp_rset(void) +{ + 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"); +} + +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 */ +} + +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 */ + +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; + } +} + +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(); + 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 <= BUFSIZE_LINE) + 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); +} + +void blast(int *hops) +{ + char ch; + int state; + 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 */ +#ifdef BARELF + int seencr = 0; +#endif + + state = 1; + *hops = 0; + flaginheader = 1; + pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; + for (;;) { + buffer_get(&bi,&ch,1); +#ifdef BARELF + if (ch == '\n') { + if (seencr == 0) { buffer_seek(&bi,-1); ch = '\r'; } + } + if (ch == '\r') seencr = 1; else seencr = 0; +#endif + 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]; + +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"); +} + +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[BUFSIZE_AUTH]; +buffer ba = BUFFER_INIT(safewrite,FDAUTH,authbuf,sizeof(authbuf)); + +int authgetl(void) +{ + 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; +} + +int authenticate(void) +{ + 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 */ +} + +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(); +} + +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(); +} + +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(); +} + +struct authcmd { + char *text; + int (*fun)(); +} authcmds[] = { + { "login", auth_login } +, { "plain", auth_plain } +, { "cram-md5", auth_cram } +, { 0, err_noauth } +}; + +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::setup",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 */ + +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, flush } +, { "vrfy", err_vrfy, flush } +, { "starttls", smtp_starttls, flush } +, { 0, err_unimpl, 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; +} |