summaryrefslogtreecommitdiff
path: root/src/qmail-smtpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmail-smtpd.c')
-rwxr-xr-xsrc/qmail-smtpd.c1720
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;
+}