nightmaremail

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit cf169fee8126a148a834165db265aad4172371d0
parent e441c8b4efcb6d8dc77e98e2f99299d5efb96023
Author: Ellenor Bjornsdottir <ellenor@umbrellix.net>
Date:   Wed, 19 Oct 2022 07:53:43 +0000

so the smtpd finally works... you have to flush every time you send something

Diffstat:
M.gitignore | 2++
MMakefile.legacy | 23+++++++++++++++++++++++
MTARGETS | 2++
Mconf-cc | 2+-
Asrc/nmail-smtpd.c | 586+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/qmail-notsmtpd.c | 581-------------------------------------------------------------------------------
6 files changed, 614 insertions(+), 582 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -382,3 +382,5 @@ qmail-send.service *.obj src/mxf-remote/mxf-remote src/netstrings.o +src/nmail-smtpd.o +nmail-smtpd diff --git a/Makefile.legacy b/Makefile.legacy @@ -1467,6 +1467,21 @@ src/case.a src/stralloc.a src/error.a socket.lib` src/dns.o `cat dns.lib` src/auto_usera.o src/auto_break.o src/realrcptto.o \ src/case.a src/stralloc.a src/error.a +nmail-smtpd: \ +load src/nmail-smtpd.o src/rcpthosts.o src/commands.o src/timeoutread.o src/ndelay.a \ +src/timeoutwrite.o src/ip.o src/ipme.o src/ipalloc.o src/control.o src/constmap.o src/received.o \ +src/date822fmt.o src/qmail.o src/fmt_ulong.o src/cdb.a src/fd.a src/wait.a src/datetime.a src/getln.a \ +src/open.a src/sig.a src/case.a src/env.a src/stralloc.a src/substdio.a src/error.a src/str.a \ +src/fs.a src/auto_qmail.o socket.lib src/dns.o dns.lib src/auto_usera.o src/auto_break.o src/realrcptto.o \ +src/case.a src/stralloc.a src/error.a + ./load nmail-smtpd src/rcpthosts.o src/commands.o src/timeoutread.o src/ndelay.a \ + src/timeoutwrite.o src/ip.o src/ipme.o src/ipalloc.o src/control.o src/constmap.o \ + src/received.o src/date822fmt.o src/qmail.o src/fmt_ulong.o src/cdb.a src/fd.a src/wait.a \ + src/datetime.a src/getln.a src/open.a src/sig.a src/case.a src/env.a src/stralloc.a \ + src/substdio.a src/error.a src/str.a src/fs.a src/auto_qmail.o `cat \ + socket.lib` src/dns.o `cat dns.lib` src/auto_usera.o src/auto_break.o src/realrcptto.o \ + src/case.a src/stralloc.a src/error.a + doc/man/qmail-smtpd.0: \ doc/man/qmail-smtpd.8 @@ -1478,6 +1493,14 @@ include/substdio.h include/str.h include/fmt.h include/scan.h include/byte.h inc include/exit.h include/rcpthosts.h include/timeoutread.h include/timeoutwrite.h include/commands.h include/dns.h ./compile src/qmail-smtpd.c +src/nmail-smtpd.o: \ +compile src/nmail-smtpd.c include/sig.h include/readwrite.h include/stralloc.h include/gen_alloc.h \ +include/substdio.h include/alloc.h include/auto_qmail.h include/control.h include/received.h include/constmap.h \ +include/error.h include/ipme.h include/ip.h include/ipalloc.h include/ip.h include/gen_alloc.h include/ip.h include/qmail.h \ +include/substdio.h include/str.h include/fmt.h include/scan.h include/byte.h include/case.h include/env.h include/now.h include/datetime.h \ +include/exit.h include/rcpthosts.h include/timeoutread.h include/timeoutwrite.h include/commands.h include/dns.h + ./compile src/nmail-smtpd.c + qmail-start: \ load src/qmail-start.o src/prot.o src/fd.a src/ids.a src/substdio.a src/error.a src/str.a ./load qmail-start src/prot.o src/fd.a src/ids.a src/substdio.a src/error.a src/str.a diff --git a/TARGETS b/TARGETS @@ -377,3 +377,5 @@ setup include/qtmp.h qmail-send.service src/netstrings.o +src/nmail-smtpd.o +nmail-smtpd diff --git a/conf-cc b/conf-cc @@ -1,3 +1,3 @@ -cc -g -ggdb3 -O3 -Iinclude/ +cc -g -ggdb3 -O3 -Wall -Iinclude/ This will be used to compile .c files. diff --git a/src/nmail-smtpd.c b/src/nmail-smtpd.c @@ -0,0 +1,586 @@ +#include "sig.h" +#include "ndelay.h" +#include "readwrite.h" +#include "stralloc.h" +#include "substdio.h" +#include "alloc.h" +#include "auto_qmail.h" +#include "control.h" +#include "datetime.h" +#include "received.h" +#include "constmap.h" +#include "error.h" +#include "ipme.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 "realrcptto.h" +#include "timeoutread.h" +#include "timeoutwrite.h" +#include "commands.h" +#include "dns.h" +#include "fd.h" + +#define MAXHOPS 100 +unsigned int databytes = 0; +int timeout = 1200; + +GEN_SAFE_TIMEOUTWRITE(safewrite,timeout,fd,_exit(10)) + +char ssoutbuf[512]; +substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof(ssoutbuf)); + +void flush() { substdio_flush(&ssout); } +void out(s) char *s; { substdio_puts(&ssout,s); } + +void die_read() { _exit(1); } +void die_alarm() { out("451 4.4.2 Timeout\r\n"); flush(); _exit(2); } +void die_nomem() { out("421 4.3.0 Out of memory\r\n"); flush(); _exit(3); } +void die_control() { out("421 4.3.0 Unable to read controls\r\n"); flush(); _exit(4); } +void die_ipme() { out("421 4.3.0 Unable to figure out my IP addresses\r\n"); flush(); _exit(5); } +void die_ip6me() { out("421 4.3.0 Unable to figure out my IPv6 addresses\r\n"); flush(); _exit(6); } +void straynewline() { out("451 You sent a stray newline. See https://cr.yp.to/docs/smtplf.html.\r\n"); flush(); _exit(1); } + +char ssebuf[1024]; +substdio sse = SUBSTDIO_FDBUF(write,2,ssebuf,sizeof(ssebuf)); +void err(char *s) { substdio_putsflush(&sse,s); } + +void err_bmf() { out("553 5.7.1 Sorry, your envelope sender is in my badmailfrom list.\r\n"); } +void err_nogateway() { out("553 5.7.1 Sorry, that domain isn't in my list of allowed rcpthosts\r\n"); } +void err_unimpl(char *arg) { out("502 5.5.1 Unimplemented\r\n"); } +void err_syntax() { out("555 5.5.4 Syntax error\r\n"); } +void die_wtf(int d, char *arg) { out("421 4.3.2 inconsistent state: "); out(arg); out(" - shutting down!\r\n"); flush(); +err("421 4.3.2 inconsistent state: "); err(arg); err(" - shutting down!\r\n"); _exit(d); } +void err_wantmail() { out("503 5.5.1 You must execute MAIL first\r\n"); } +void err_wantrcpt() { out("503 5.5.1 You must execute RCPT first\r\n"); } +void err_notlshere() { out("454 4.7.6 STARTTLS is not available at this site.\r\n"); } +void err_alreadytlshere() { out("454 5.7.6 STARTTLS is already active on this connection.\r\n"); } +void die_cdb() { out("421 4.3.0 Unable to read cdb user database\r\n"); flush(); _exit(7); } +void die_sys() { out("421 4.3.0 Unable to read system user database\r\n"); flush(); _exit(8); } +void err_noop(char *arg) { out("250 As requested, no operation performed.\r\n"); } +void err_vrfy(char *arg) { out("252 Send something and we'll give it a shot.\r\n"); } +void err_qqt() { out("451 4.3.0 Temporarily unavailable: cannot inject message into queue.\r\n"); } +void die_dnsbl(char *arg) +{ + out("421 Your IP is currently blacklisted. If available at this site, auth first. ("); out(arg); out(")\r\n"); + flush(); + _exit(9); +} + +stralloc greeting = {0}; +int starttlsready = 0, sslctlfd = -1, sslrfd = -1, sslwfd = -1, isstarttls = 0, isesmtp = 0; + +void smtp_greet(code) char *code; +{ + substdio_puts(&ssout,code); + substdio_put(&ssout,greeting.s,greeting.len); +} +void smtp_help(char *arg) +{ + out("214-NightmareMail home page: <https://umbrellix.net./software/nightmaremail/index.html>\r\n"); + out("214-NightmareMail is based on notqmail. notqmail home page: <https://notqmail.org>\r\n"); + out("214-We accept the following commands: HELO, EHLO, (SEND/SOML/SAML/MAIL) FROM:<, RCPT TO:<, DATA, RSET"); if (starttlsready) out(", STARTTLS"); out("\r\n"); + out("214 Note that SMTP is not a user interface. It should only be used thus to verify correct operation of the mail system.\r\n"); +} +void smtp_quit(char *arg) +{ + smtp_greet("221 "); out("\r\n"); flush(); _exit(0); +} + +char *remoteip; +char *remotehost; +char *remoteinfo; +char *local; +char *relayclient; +char *dnsblskip; + +stralloc helohost = {0}; +char *fakehelo; /* pointer into helohost, or 0 */ + +void dohelo(char *arg) { + if (!stralloc_copys(&helohost,arg)) die_nomem(); + if (!stralloc_0(&helohost)) die_nomem(); + fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0; +} + +int liphostok = 0; +stralloc liphost = {0}; +int bmfok = 0; +stralloc bmf = {0}; +struct constmap mapbmf; + +void setuptls() +{ + char *x; + unsigned long u; + + x = env_get("SSLCTLFD"); + if (x) { scan_ulong(x,&u); sslctlfd = u; } else return; + x = env_get("SSLREADFD"); + if (x) { scan_ulong(x,&u); sslrfd = u; } else return; + x = env_get("SSLWRITEFD"); + if (x) { scan_ulong(x,&u); sslwfd = u; } else return; + ndelay_on(sslctlfd); + //ndelay_on(sslrfd); + //ndelay_on(sslwfd); + starttlsready = 1; // if reached, then we can do starttls + return; +} + +void setup() +{ + char *x; + unsigned long u; + + if (control_init() == -1) die_control(); + if (control_rldef(&greeting,"control/smtpgreeting",1,NULL) != 1) + die_control(); + liphostok = control_rldef(&liphost,"control/localiphost",1,NULL); + 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(); + + 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(); + + // manually merged from prj's realrcptto patch + realrcptto_init(); + + 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; + setuptls(); + + remoteip = env_get("TCPREMOTEIP"); + if (!remoteip) remoteip = "unknown"; + local = env_get("TCPLOCALHOST"); + if (!local) local = env_get("TCPLOCALIP"); + if (!local) local = "unknown"; + remotehost = env_get("TCPREMOTEHOST"); + if (!remotehost) remotehost = "unknown"; + remoteinfo = env_get("TCPREMOTEINFO"); + relayclient = env_get("RELAYCLIENT"); + dnsblskip = env_get("DNSBLSKIP"); + dohelo(remotehost); +} + +extern void realrcptto_init(); +extern void realrcptto_start(); +extern int realrcptto(); +extern int realrcptto_deny(); + +stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */ + +int addrparse(char *arg) +{ + int i; + char ch; + char terminator; + struct ip_address ip; + int flagesc; + int flagquoted; + + terminator = '>'; + i = str_chr(arg,'<'); + if (arg[i]) + arg += i + 1; + else { /* partner should go read rfc 821 */ + terminator = ' '; + arg += str_chr(arg,':'); + if (*arg == ':') ++arg; + while (*arg == ' ') ++arg; + } + + /* 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 (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,&ip)]) + if (ipme_is(&ip)) { + 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 bmfcheck() +{ + int j; + if (!bmfok) return 0; + if (constmap(&mapbmf,addr.s,addr.len - 1)) return 1; + j = byte_rchr(addr.s,addr.len,'@'); + if (j < addr.len) + if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1; + return 0; +} + +int addrallowed() +{ + int r; + r = rcpthosts(addr.s,str_len(addr.s)); + if (r == -1) die_control(); + return r; +} + +int flagdnsbl = 0; +stralloc dnsblhost = {0}; + +int dnsblcheck() +{ + char *ch; + static stralloc dnsblbyte = {0}; + static stralloc dnsblrev = {0}; + static ipalloc dnsblip = {0}; + static ip6alloc dnsblip6 = {0}; + static stralloc dnsbllist = {0}; + + ch = remoteip; + if(control_readfile(&dnsbllist,"control/dnsbllist",0) != 1) return 0; + + if (!stralloc_copys(&dnsblrev,"")) return 0; + for (;;) { + if (!stralloc_copys(&dnsblbyte,"")) return 0; + while (ch[0] && (ch[0] != '.')) { + if (!stralloc_append(&dnsblbyte,ch)) return 0; + ch++; + } + if (!stralloc_append(&dnsblbyte,".")) return 0; + if (!stralloc_cat(&dnsblbyte,&dnsblrev)) return 0; + if (!stralloc_copy(&dnsblrev,&dnsblbyte)) return 0; + + if (!ch[0]) break; + ch++; + } + + flagdnsbl = 1; + ch = dnsbllist.s; + while (ch < (dnsbllist.s + dnsbllist.len)) { + if (!stralloc_copy(&dnsblhost,&dnsblrev)) return 0; + if (!stralloc_cats(&dnsblhost,ch)) return 0; + if (!stralloc_0(&dnsblhost)) return 0; + + if (!dns_ip(&dnsblip,&dnsblhost)) return 1; + while (*ch++); + } + + return 0; +} + +int seenmail = 0; +int flagbarf; /* defined if seenmail */ +stralloc mailfrom = {0}; +stralloc rcptto = {0}; + +ssize_t saferead(int fd, void *buf, size_t len) +{ + ssize_t r; + r = timeoutread(timeout,fd,buf,len); + if (r == -1) if (errno == error_timeout) die_alarm(); + if (r == 0 || r == -1) die_read(); + return r; +} + +char ssinbuf[1024]; +substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof(ssinbuf)); + + +void smtp_starttls(char *arg) +{ + char fdou[1920]; // compare skarnet s-s-proxy: OUTSIZE 1920. Me thinks I cribbed too much. + ssize_t hsread = 0, hsr = 0; + if (str_len(arg) != 0) { err_syntax(); return; } + if (!starttlsready) { err_notlshere(); return; } + if (timeoutwrite(1, sslctlfd, "Y", 1) != 1) { die_wtf(errno, "could not cut over to TLS in one second"); } + out("220 2.7.0 Ready to start TLS\r\n"); + substdio_flush(&ssout); + for (;;) { + hsread += (hsr = timeoutread(1, sslctlfd, fdou, 1920)); + if (hsr < 0) { if (errno != EAGAIN) die_wtf(errno, "could not cut over to TLS in 1 second"); } + if (!hsr) break; + isstarttls = 1; // just use the global variable (TODO: make TL when npthread) + } + if (!isstarttls) { _exit(10); } + // At this point, we are starttls. We must now cut sslwfd to fd 1 and sslrfd to fd 0. + ssout.fd = sslwfd; + ssin.fd = sslrfd; + substdio_flush(&ssout); + substdio_flush(&sse); +} +void smtp_helo(char *arg) +{ + smtp_greet("250 "); out("\r\n"); + seenmail = 0; dohelo(arg); +} +void smtp_ehlo(char *arg) +{ + isesmtp = 1; + smtp_greet("250-"); out("\r\n"); + out("250-PIPELINING\r\n"); + out("250-ENHANCEDSTATUSCODES\r\n"); + // TODO: add startTLS cutover support + if (starttlsready && !isstarttls) { + out("250-8BITMIME\r\n"); + out("250 STARTTLS\r\n"); + } else out("250 8BITMIME\r\n"); + seenmail = 0; dohelo(arg); +} +void smtp_rset(char *arg) +{ + seenmail = 0; + out("250 Envelope flushed\r\n"); +} +void smtp_mail(char *arg) +{ + if (!addrparse(arg)) { err_syntax(); return; } + flagbarf = bmfcheck(); + seenmail = 1; + if (!stralloc_copys(&rcptto,"")) die_nomem(); + if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); + if (!stralloc_0(&mailfrom)) die_nomem(); + realrcptto_start(); + out("250 Go on...\r\n"); +} +void smtp_rcpt(char *arg) { + if (!seenmail) { err_wantmail(); return; } + if (!addrparse(arg)) { err_syntax(); return; } + if (flagbarf) { err_bmf(); return; } + if (relayclient) { + --addr.len; + if (!stralloc_cats(&addr,relayclient)) die_nomem(); + if (!stralloc_0(&addr)) die_nomem(); + } + else + if (!addrallowed()) { err_nogateway(); return; } + if (!realrcptto(addr.s)) { + out("550 5.1.1 That user has elected not to receive emails.\r\n"); + return; + } + if (!(relayclient || dnsblskip || flagdnsbl)) + if (dnsblcheck()) die_dnsbl(dnsblhost.s); + if (!stralloc_cats(&rcptto,"T")) die_nomem(); + if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); + if (!stralloc_0(&rcptto)) die_nomem(); + out("250 Go on...\r\n"); +} + +struct qmail qqt; +unsigned int bytestooverflow = 0; + +void put(ch) +char *ch; +{ + if (bytestooverflow) + if (!--bytestooverflow) + qmail_fail(&qqt); + qmail_put(&qqt,ch,1); +} + +void blast(hops) +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 */ + + state = 1; + *hops = 0; + flaginheader = 1; + pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; + for (;;) { + /* This isn't very optimized -- Amelia B */ + substdio_get(&ssin,&ch,1); + if (flaginheader) { + if (pos < 9) { // something about method and madness? + 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; + put("."); + put("\r"); + if (ch == '\r') { state = 4; continue; } + state = 0; + break; + case 4: /* + \r */ + if (ch == '\n') { state = 1; break; } + if (ch != '\r') { put("\r"); state = 0; } + } + put(&ch); + } +} + +char accept_buf[FMT_ULONG]; +void acceptmessage(qp) unsigned long qp; +{ + datetime_sec when; + when = now(); + out("250 Accepted responsibility at time "); + accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0; + out(accept_buf); + out(" queue process "); + accept_buf[fmt_ulong(accept_buf,qp)] = 0; + out(accept_buf); + out(" - we'll do our best!\r\n"); + flush(); +} + +/* + unsigned long alen, plen, len; + + if (databytes && ((alen = str_len(arg)) != 0)) { + // We now accept the optional argument to DATA which is a ulong which must < databytes + 1. + plen = scan_ulong(arg, &len); + if (alen != plen) { err_syntax(); return; } // crap at the end of the argument + if (len > databytes) { out("552 5.3.4 This message is too big for me (rapid rejection).\r\n"); return; } + } + */ +void smtp_data(char *arg) { + int hops; + unsigned long qp; + char *qqx; + char *esmtps; + + if (!seenmail) { err_wantmail(); return; } + if (!rcptto.len) { err_wantrcpt(); return; } + if (realrcptto_deny()) { out("554 5.1.1 That user has elected not to receive emails.\r\n"); return; } + seenmail = 0; + if (databytes) bytestooverflow = databytes + 1; + if (qmail_open(&qqt) == -1) { err_qqt(); return; } + qp = qmail_qp(&qqt); + out("354 go ahead\r\n"); + substdio_flush(&ssout); + + switch ((isesmtp & 1)+((isstarttls & 1) << 1)+((relayclient != NULL) << 2)) { + case 7: esmtps = "ESMTPSA"; + break; + case 6: esmtps = "SMTPSA"; // Compliant clients should never do this, but it's not pathological. + break; + case 5: esmtps = "ESMTPA"; + break; + case 4: esmtps = "SMTPA"; // Clients, at all, will never do this, as we don't process SMTP AUTH. + break; + case 3: esmtps = "ESMTPS"; + break; + case 2: esmtps = "SMTPS"; // Compliant clients should never do this, but it's not pathological. + break; + case 1: esmtps = "ESMTP"; + break; + default: esmtps = "SMTP"; + } + received(&qqt,esmtps,local,remoteip,remotehost,remoteinfo,fakehelo); + blast(&hops); + hops = (hops >= MAXHOPS); + if (hops) qmail_fail(&qqt); + 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 5.4.6 This message has gone through too many SMTP, QMTP or other mail protocol servers; it may be looping. It ends with me.\r\n"); return; } + if (databytes) if (!bytestooverflow) { out("552 5.3.4 This message is too big for me.\r\n"); return; } + if (*qqx == 'D') out("554 "); else out("451 "); + out(qqx + 1); + out("\r\n"); +} + +struct commands smtpcommands[] = { + { "rcpt", smtp_rcpt, flush } +, { "mail", smtp_mail, flush } +, { "send", smtp_mail, flush } // not recommended +, { "soml", smtp_mail, flush } // not recommended +, { "saml", smtp_mail, flush } // not recommended +, { "data", smtp_data, flush } +, { "quit", smtp_quit, flush } +, { "helo", smtp_helo, flush } +, { "ehlo", smtp_ehlo, flush } +, { "rset", smtp_rset, flush } +, { "help", smtp_help, flush } +, { "starttls", smtp_starttls, flush } +, { "noop", err_noop, flush } +, { "vrfy", err_vrfy, flush } +, { 0, err_unimpl, flush } +} ; + +int main(void) +{ + //ndelay_on(0); + //ndelay_on(1); + sig_pipeignore(); + if (chdir(auto_qmail) == -1) die_control(); + setup(); + if (ipme_init() != 1) die_ipme(); + if (ip6me_init() != 1) die_ip6me(); + smtp_greet("220 "); + out(" ESMTP\r\n"); + substdio_flush(&ssout); + if (commands(&ssin,&smtpcommands) == 0) die_read(); + die_nomem(); +} diff --git a/src/qmail-notsmtpd.c b/src/qmail-notsmtpd.c @@ -1,581 +0,0 @@ -#include "sig.h" -#include "ndelay.h" -#include "readwrite.h" -#include "stralloc.h" -#include "substdio.h" -#include "alloc.h" -#include "auto_qmail.h" -#include "control.h" -#include "datetime.h" -#include "received.h" -#include "constmap.h" -#include "error.h" -#include "ipme.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 "realrcptto.h" -#include "timeoutread.h" -#include "timeoutwrite.h" -#include "commands.h" -#include "dns.h" -#include "fd.h" - -#define MAXHOPS 100 -unsigned int databytes = 0; -int timeout = 1200; - -GEN_SAFE_TIMEOUTWRITE(safewrite,timeout,fd,_exit(10)) - -char ssoutbuf[512]; -substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof(ssoutbuf)); - -void flush() { substdio_flush(&ssout); } -void out(s) char *s; { substdio_puts(&ssout,s); } - -void die_read() { _exit(1); } -void die_alarm() { out("451 4.4.2 Timeout\r\n"); flush(); _exit(2); } -void die_nomem() { out("421 4.3.0 Out of memory\r\n"); flush(); _exit(3); } -void die_control() { out("421 4.3.0 Unable to read controls\r\n"); flush(); _exit(4); } -void die_ipme() { out("421 4.3.0 Unable to figure out my IP addresses\r\n"); flush(); _exit(5); } -void die_ip6me() { out("421 4.3.0 Unable to figure out my IPv6 addresses\r\n"); flush(); _exit(6); } -void straynewline() { out("451 You sent a stray newline. See https://cr.yp.to/docs/smtplf.html.\r\n"); flush(); _exit(1); } - -ssize_t saferead(int fd, void *buf, size_t len) -{ - ssize_t r; - r = timeoutread(timeout,fd,buf,len); - if (r == -1) if (errno == error_timeout) die_alarm(); - if (r == 0 || r == -1) die_read(); - return r; -} - -char ssinbuf[1024]; -substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof(ssinbuf)); - -char ssebuf[1024]; -substdio sse = SUBSTDIO_FDBUF(write,2,ssebuf,sizeof(ssebuf)); -void err(char *s) { substdio_putsflush(&sse,s); } - -void err_bmf() { out("553 5.7.1 Sorry, your envelope sender is in my badmailfrom list.\r\n"); } -void err_nogateway() { out("553 5.7.1 Sorry, that domain isn't in my list of allowed rcpthosts\r\n"); } -void err_unimpl(char *arg) { out("502 5.5.1 Unimplemented\r\n"); } -void err_syntax() { out("555 5.5.4 Syntax error\r\n"); } -void die_wtf(int d, char *arg) { out("421 4.3.2 inconsistent state: "); out(arg); out(" - shutting down!\r\n"); flush(); -err("421 4.3.2 inconsistent state: "); err(arg); err(" - shutting down!\r\n"); _exit(d); } -void err_wantmail() { out("503 5.5.1 You must execute MAIL first\r\n"); } -void err_wantrcpt() { out("503 5.5.1 You must execute RCPT first\r\n"); } -void err_notlshere() { out("454 4.7.6 STARTTLS is not available at this site.\r\n"); } -void err_alreadytlshere() { out("454 5.7.6 STARTTLS is already active on this connection.\r\n"); } -void die_cdb() { out("421 4.3.0 Unable to read cdb user database\r\n"); flush(); _exit(7); } -void die_sys() { out("421 4.3.0 Unable to read system user database\r\n"); flush(); _exit(8); } -void err_noop(char *arg) { out("250 As requested, no operation performed.\r\n"); } -void err_vrfy(char *arg) { out("252 Send something and we'll give it a shot.\r\n"); } -void err_qqt() { out("451 4.3.0 Temporarily unavailable: cannot inject message into queue.\r\n"); } -void die_dnsbl(char *arg) -{ - out("421 Your IP is currently blacklisted. If available at this site, auth first. ("); out(arg); out(")\r\n"); - flush(); - _exit(9); -} - -stralloc greeting = {0}; -int starttlsready = 0, sslctlfd = -1, sslrfd = -1, sslwfd = -1, isstarttls = 0, isesmtp = 0; - -void smtp_greet(code) char *code; -{ - substdio_puts(&ssout,code); - substdio_put(&ssout,greeting.s,greeting.len); -} -void smtp_help(char *arg) -{ - out("214-NightmareMail home page: <https://umbrellix.net./software/nightmaremail/index.html>\r\n"); - out("214-NightmareMail is based on notqmail. notqmail home page: <https://notqmail.org>\r\n"); - out("214-We accept the following commands: HELO, EHLO, (SEND/SOML/SAML/MAIL) FROM:<, RCPT TO:<, DATA, RSET"); if (starttlsready) out(", STARTTLS"); out("\r\n"); - out("214 Note that SMTP is not a user interface. It should only be used thus to verify correct operation of the mail system.\r\n"); -} -void smtp_quit(char *arg) -{ - smtp_greet("221 "); out("\r\n"); flush(); _exit(0); -} - -char *remoteip; -char *remotehost; -char *remoteinfo; -char *local; -char *relayclient; -char *dnsblskip; - -stralloc helohost = {0}; -char *fakehelo; /* pointer into helohost, or 0 */ - -void dohelo(char *arg) { - if (!stralloc_copys(&helohost,arg)) die_nomem(); - if (!stralloc_0(&helohost)) die_nomem(); - fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0; -} - -int liphostok = 0; -stralloc liphost = {0}; -int bmfok = 0; -stralloc bmf = {0}; -struct constmap mapbmf; - -void setuptls() -{ - char *x; - unsigned long u; - - x = env_get("SSLCTLFD"); - if (x) { scan_ulong(x,&u); sslctlfd = u; } else return; - x = env_get("SSLREADFD"); - if (x) { scan_ulong(x,&u); sslrfd = u; } else return; - x = env_get("SSLWRITEFD"); - if (x) { scan_ulong(x,&u); sslwfd = u; } else return; - ndelay_on(sslctlfd); - ndelay_on(sslrfd); - ndelay_on(sslwfd); - starttlsready = 1; // if reached, then we can do starttls - return; -} - -void setup() -{ - char *x; - unsigned long u; - - if (control_init() == -1) die_control(); - if (control_rldef(&greeting,"control/smtpgreeting",1,NULL) != 1) - die_control(); - liphostok = control_rldef(&liphost,"control/localiphost",1,NULL); - if (liphostok == -1) die_control(); - if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control(); - if (timeout <= 0) timeout = 120; - - if (rcpthosts_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(); - - // manually merged from prj's realrcptto patch - realrcptto_init(); - - 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; - setuptls(); - - remoteip = env_get("TCPREMOTEIP"); - if (!remoteip) remoteip = "unknown"; - local = env_get("TCPLOCALHOST"); - if (!local) local = env_get("TCPLOCALIP"); - if (!local) local = "unknown"; - remotehost = env_get("TCPREMOTEHOST"); - if (!remotehost) remotehost = "unknown"; - remoteinfo = env_get("TCPREMOTEINFO"); - relayclient = env_get("RELAYCLIENT"); - dnsblskip = env_get("DNSBLSKIP"); - dohelo(remotehost); -} - -extern void realrcptto_init(); -extern void realrcptto_start(); -extern int realrcptto(); -extern int realrcptto_deny(); - -stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */ - -int addrparse(char *arg) -{ - int i; - char ch; - char terminator; - struct ip_address ip; - int flagesc; - int flagquoted; - - terminator = '>'; - i = str_chr(arg,'<'); - if (arg[i]) - arg += i + 1; - else { /* partner should go read rfc 821 */ - terminator = ' '; - arg += str_chr(arg,':'); - if (*arg == ':') ++arg; - while (*arg == ' ') ++arg; - } - - /* 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 (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,&ip)]) - if (ipme_is(&ip)) { - 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 bmfcheck() -{ - int j; - if (!bmfok) return 0; - if (constmap(&mapbmf,addr.s,addr.len - 1)) return 1; - j = byte_rchr(addr.s,addr.len,'@'); - if (j < addr.len) - if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1; - return 0; -} - -int addrallowed() -{ - int r; - r = rcpthosts(addr.s,str_len(addr.s)); - if (r == -1) die_control(); - return r; -} - -int flagdnsbl = 0; -stralloc dnsblhost = {0}; - -int dnsblcheck() -{ - char *ch; - static stralloc dnsblbyte = {0}; - static stralloc dnsblrev = {0}; - static ipalloc dnsblip = {0}; - static ip6alloc dnsblip6 = {0}; - static stralloc dnsbllist = {0}; - - ch = remoteip; - if(control_readfile(&dnsbllist,"control/dnsbllist",0) != 1) return 0; - - if (!stralloc_copys(&dnsblrev,"")) return 0; - for (;;) { - if (!stralloc_copys(&dnsblbyte,"")) return 0; - while (ch[0] && (ch[0] != '.')) { - if (!stralloc_append(&dnsblbyte,ch)) return 0; - ch++; - } - if (!stralloc_append(&dnsblbyte,".")) return 0; - if (!stralloc_cat(&dnsblbyte,&dnsblrev)) return 0; - if (!stralloc_copy(&dnsblrev,&dnsblbyte)) return 0; - - if (!ch[0]) break; - ch++; - } - - flagdnsbl = 1; - ch = dnsbllist.s; - while (ch < (dnsbllist.s + dnsbllist.len)) { - if (!stralloc_copy(&dnsblhost,&dnsblrev)) return 0; - if (!stralloc_cats(&dnsblhost,ch)) return 0; - if (!stralloc_0(&dnsblhost)) return 0; - - if (!dns_ip(&dnsblip,&dnsblhost)) return 1; - while (*ch++); - } - - return 0; -} - -int seenmail = 0; -int flagbarf; /* defined if seenmail */ -stralloc mailfrom = {0}; -stralloc rcptto = {0}; - -void smtp_starttls(char *arg) -{ - char fdou[1920]; // compare skarnet s-s-proxy: OUTSIZE 1920. Me thinks I cribbed too much. - ssize_t hsread = 0, hsr = 0; - if (str_len(arg) != 0) { err_syntax(); return; } - if (!starttlsready) { err_notlshere(); return; } - out("220 2.7.0 Ready to start TLS\r\n"); - substdio_flush(&ssout); - if (timeoutwrite(1, sslctlfd, "Y", 1) != 1) { die_wtf(errno, "could not cut over to TLS in one second"); } - for (;;) { - hsread += (hsr = timeoutread(1, sslctlfd, fdou, 1920)); - if (hsr < 0) { if (errno != EAGAIN) die_wtf(errno, "could not cut over to TLS in 1 second"); } - if (!hsr) break; - isstarttls = 1; // just use the global variable (TODO: make TL when npthread) - } - if (!isstarttls) { _exit(10); } - // At this point, we are starttls. We must now cut sslwfd to fd 1 and sslrfd to fd 0. - ssout.fd = sslwfd; - ssin.fd = sslrfd; - substdio_flush(&ssout); - substdio_flush(&sse); -} -void smtp_helo(char *arg) -{ - smtp_greet("250 "); out("\r\n"); - seenmail = 0; dohelo(arg); -} -void smtp_ehlo(char *arg) -{ - isesmtp = 1; - smtp_greet("250-"); out("\r\n"); - out("250-PIPELINING\r\n"); - out("250-ENHANCEDSTATUSCODES\r\n"); - // TODO: add startTLS cutover support - if (starttlsready && !isstarttls) { - out("250-8BITMIME\r\n"); - out("250 STARTTLS\r\n"); - } else out("250 8BITMIME\r\n"); - seenmail = 0; dohelo(arg); -} -void smtp_rset(char *arg) -{ - seenmail = 0; - out("250 Envelope flushed\r\n"); -} -void smtp_mail(char *arg) -{ - if (!addrparse(arg)) { err_syntax(); return; } - flagbarf = bmfcheck(); - seenmail = 1; - if (!stralloc_copys(&rcptto,"")) die_nomem(); - if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); - if (!stralloc_0(&mailfrom)) die_nomem(); - realrcptto_start(); - out("250 Go on...\r\n"); -} -void smtp_rcpt(char *arg) { - if (!seenmail) { err_wantmail(); return; } - if (!addrparse(arg)) { err_syntax(); return; } - if (flagbarf) { err_bmf(); return; } - if (relayclient) { - --addr.len; - if (!stralloc_cats(&addr,relayclient)) die_nomem(); - if (!stralloc_0(&addr)) die_nomem(); - } - else - if (!addrallowed()) { err_nogateway(); return; } - if (!realrcptto(addr.s)) { - out("550 5.1.1 That user has elected not to receive emails.\r\n"); - return; - } - if (!(relayclient || dnsblskip || flagdnsbl)) - if (dnsblcheck()) die_dnsbl(dnsblhost.s); - if (!stralloc_cats(&rcptto,"T")) die_nomem(); - if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); - if (!stralloc_0(&rcptto)) die_nomem(); - out("250 Go on...\r\n"); -} - -struct qmail qqt; -unsigned int bytestooverflow = 0; - -void put(ch) -char *ch; -{ - if (bytestooverflow) - if (!--bytestooverflow) - qmail_fail(&qqt); - qmail_put(&qqt,ch,1); -} - -void blast(hops) -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 */ - - state = 1; - *hops = 0; - flaginheader = 1; - pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; - for (;;) { - /* This isn't very optimized -- Amelia B */ - substdio_get(&ssin,&ch,1); - if (flaginheader) { - if (pos < 9) { // something about method and madness? - 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; - put("."); - put("\r"); - if (ch == '\r') { state = 4; continue; } - state = 0; - break; - case 4: /* + \r */ - if (ch == '\n') { state = 1; break; } - if (ch != '\r') { put("\r"); state = 0; } - } - put(&ch); - } -} - -char accept_buf[FMT_ULONG]; -void acceptmessage(qp) unsigned long qp; -{ - datetime_sec when; - when = now(); - out("250 Accepted responsibility at time "); - accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0; - out(accept_buf); - out(" queue process "); - accept_buf[fmt_ulong(accept_buf,qp)] = 0; - out(accept_buf); - out(" - we'll do our best!\r\n"); - flush(); -} - -/* - unsigned long alen, plen, len; - - if (databytes && ((alen = str_len(arg)) != 0)) { - // We now accept the optional argument to DATA which is a ulong which must < databytes + 1. - plen = scan_ulong(arg, &len); - if (alen != plen) { err_syntax(); return; } // crap at the end of the argument - if (len > databytes) { out("552 5.3.4 This message is too big for me (rapid rejection).\r\n"); return; } - } - */ -void smtp_data(char *arg) { - int hops; - unsigned long qp; - char *qqx; - char *esmtps; - - if (!seenmail) { err_wantmail(); return; } - if (!rcptto.len) { err_wantrcpt(); return; } - if (realrcptto_deny()) { out("554 5.1.1 That user has elected not to receive emails.\r\n"); return; } - seenmail = 0; - if (databytes) bytestooverflow = databytes + 1; - if (qmail_open(&qqt) == -1) { err_qqt(); return; } - qp = qmail_qp(&qqt); - out("354 go ahead\r\n"); - - switch ((isesmtp & 1)+((isstarttls & 1) << 1)+((relayclient != NULL) << 2)) { - case 7: esmtps = "ESMTPSA"; - break; - case 6: esmtps = "SMTPSA"; // Compliant clients should never do this, but it's not pathological. - break; - case 5: esmtps = "ESMTPA"; - break; - case 4: esmtps = "SMTPA"; // Clients, at all, will never do this, as we don't process SMTP AUTH. - break; - case 3: esmtps = "ESMTPS"; - break; - case 2: esmtps = "SMTPS"; // Compliant clients should never do this, but it's not pathological. - break; - case 1: esmtps = "ESMTP"; - break; - default: esmtps = "SMTP"; - } - received(&qqt,esmtps,local,remoteip,remotehost,remoteinfo,fakehelo); - blast(&hops); - hops = (hops >= MAXHOPS); - if (hops) qmail_fail(&qqt); - 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 5.4.6 This message has gone through too many SMTP, QMTP or other mail protocol servers; it may be looping. It ends with me.\r\n"); return; } - if (databytes) if (!bytestooverflow) { out("552 5.3.4 This message is too big for me.\r\n"); return; } - if (*qqx == 'D') out("554 "); else out("451 "); - out(qqx + 1); - out("\r\n"); -} - -struct commands smtpcommands[] = { - { "rcpt", smtp_rcpt, 0 } -, { "mail", smtp_mail, 0 } -, { "send", smtp_mail, 0 } // not recommended -, { "soml", smtp_mail, 0 } // not recommended -, { "saml", smtp_mail, 0 } // not recommended -, { "data", smtp_data, flush } -, { "quit", smtp_quit, flush } -, { "helo", smtp_helo, flush } -, { "ehlo", smtp_ehlo, flush } -, { "rset", smtp_rset, 0 } -, { "help", smtp_help, flush } -, { "starttls", smtp_starttls, flush } -, { "noop", err_noop, flush } -, { "vrfy", err_vrfy, flush } -, { 0, err_unimpl, flush } -} ; - -int main(void) -{ - sig_pipeignore(); - if (chdir(auto_qmail) == -1) die_control(); - setup(); - if (ipme_init() != 1) die_ipme(); - if (ip6me_init() != 1) die_ip6me(); - smtp_greet("220 "); - out(" ESMTP\r\n"); - if (commands(&ssin,&smtpcommands) == 0) die_read(); - die_nomem(); -}