nightmaremail

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

commit e61e9986ca9216168ca1dd1bd14f3963800daaa0
parent 6684e176cc2b83b1c63977ffa27d2e7d9e89f6e9
Author: Amelia Bjornsdottir <amelia@chatspeed.net>
Date:   Wed, 22 Sep 2021 18:05:19 +0000

+realrcptto by Paul Jarc (anti-backscatter extension)

Diffstat:
M.gitignore | 1+
MMakefile.legacy | 17+++++++++++++----
MTARGETS | 1+
Ainclude/realrcptto.h | 10++++++++++
Msrc/qmail-qmtpd.c | 15+++++++++++++++
Asrc/qmail-remote.c | 406+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/qmail-smtpd.c | 18+++++++++++++++++-
Asrc/realrcptto.c | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 799 insertions(+), 5 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -249,6 +249,7 @@ src/received.o qmail-qmqpd src/qmail-qmtpd.o src/rcpthosts.o +src/realrcptto.o qmail-qmtpd src/qmail-smtpd.o qmail-smtpd diff --git a/Makefile.legacy b/Makefile.legacy @@ -1290,11 +1290,13 @@ qmail-qmtpd: \ load src/qmail-qmtpd.o src/rcpthosts.o src/control.o src/constmap.o src/received.o \ src/date822fmt.o src/qmail.o src/cdb.a src/fd.a src/wait.a src/datetime.a src/open.a \ src/getln.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 +src/str.a src/fs.a src/auto_qmail.o src/auto_usera.o src/auto_break.o src/realrcptto.o \ +src/case.a src/stralloc.a src/error.a ./load qmail-qmtpd src/rcpthosts.o src/control.o src/constmap.o \ src/received.o src/date822fmt.o src/qmail.o src/cdb.a src/fd.a src/wait.a \ src/datetime.a src/open.a src/getln.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 + src/substdio.a src/error.a src/str.a src/fs.a src/auto_qmail.o \ + src/auto_usera.o src/auto_break.o src/realrcptto.o src/case.a src/stralloc.a src/error.a doc/man/qmail-qmtpd.0: \ doc/man/qmail-qmtpd.8 @@ -1454,13 +1456,15 @@ load src/qmail-smtpd.o src/rcpthosts.o src/commands.o src/timeoutread.o \ 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/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/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 qmail-smtpd src/rcpthosts.o src/commands.o src/timeoutread.o \ 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/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` + 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 @@ -1591,6 +1595,11 @@ compile src/readsubdir.c include/readsubdir.h include/direntry.h include/fmt.h i include/auto_split.h ./compile src/readsubdir.c +src/realrcptto.o: \ +compile src/realrcptto.c include/auto_break.h include/auto_users.h include/byte.h include/case.h include/cdb.h \ +include/constmap.h include/error.h include/fmt.h include/open.h include/str.h include/stralloc.h include/uint32.h + ./compile src/realrcptto.c + src/received.o: \ compile src/received.c include/fmt.h include/qmail.h include/substdio.h include/now.h include/datetime.h \ include/datetime.h include/date822fmt.h include/received.h diff --git a/TARGETS b/TARGETS @@ -249,6 +249,7 @@ src/received.o qmail-qmqpd src/qmail-qmtpd.o src/rcpthosts.o +src/realrcptto.o qmail-qmtpd src/qmail-smtpd.o qmail-smtpd diff --git a/include/realrcptto.h b/include/realrcptto.h @@ -0,0 +1,10 @@ +#ifndef _MXF_REALRCPTTO_H +#define _MXF_REALRCPTTO_H + +// ld realrcptto.o +extern void realrcptto_init(); +extern void realrcptto_start(); +extern int realrcptto(); +extern int realrcptto_deny(); + +#endif diff --git a/src/qmail-qmtpd.c b/src/qmail-qmtpd.c @@ -9,6 +9,7 @@ #include "scan.h" #include "sig.h" #include "rcpthosts.h" +#include "realrcptto.h" #include "auto_qmail.h" #include "readwrite.h" #include "control.h" @@ -17,6 +18,12 @@ void _noreturn_ badproto() { _exit(100); } void _noreturn_ resources() { _exit(111); } +void die_nomem() { resources(); } +void die_control() { resources(); } +void die_cdb() { resources(); } +void die_sys() { resources(); } + +// XXX: should be in realrcptto.h ssize_t safewrite(int fd, const void *buf, size_t len) { @@ -102,6 +109,8 @@ main() if (rcpthosts_init() == -1) resources(); relayclient = env_get("RELAYCLIENT"); relayclientlen = relayclient ? str_len(relayclient) : 0; + + realrcptto_init(); if (control_readint(&databytes,"control/databytes") == -1) resources(); x = env_get("DATABYTES"); @@ -118,6 +127,7 @@ main() if (!local) local = "unknown"; for (;;) { + realrcptto_start(); if (!stralloc_copys(&failure,"")) resources(); flagsenderok = 1; @@ -220,6 +230,10 @@ main() case -1: resources(); case 0: failure.s[failure.len - 1] = 'D'; } + + if (!failure.s[failure.len - 1]) + if (!realrcptto(buf)) + failure.s[failure.len - 1] = 'D'; if (!failure.s[failure.len - 1]) { qmail_to(&qq,buf); @@ -235,6 +249,7 @@ main() result = qmail_close(&qq); if (!flagsenderok) result = "Dunacceptable sender (#5.1.7)"; if (databytes) if (!bytestooverflow) result = "Dsorry, that message size exceeds my databytes limit (#5.3.4)"; + if (realrcptto_deny()) result = "Dsorry, no mailbox here by that name. (#5.1.1)\r\n"; if (*result) len = str_len(result); diff --git a/src/qmail-remote.c b/src/qmail-remote.c @@ -0,0 +1,406 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include "sig.h" +#include "stralloc.h" +#include "substdio.h" +#include "subfd.h" +#include "scan.h" +#include "case.h" +#include "error.h" +#include "auto_qmail.h" +#include "control.h" +#include "dns.h" +#include "quote.h" +#include "ip.h" +#include "ipalloc.h" +#include "ipme.h" +#include "gen_alloc.h" +#include "gen_allocdefs.h" +#include "str.h" +#include "now.h" +#include "exit.h" +#include "constmap.h" +#include "noreturn.h" +#include "tcpto.h" +#include "readwrite.h" +#include "timeoutconn.h" +#include "timeoutread.h" +#include "timeoutwrite.h" + +#define HUGESMTPTEXT 5000 + +#define PORT_SMTP 25 /* silly rabbit, /etc/services is for users */ +unsigned long port = PORT_SMTP; + +GEN_ALLOC_typedef(saa,stralloc,sa,len,a) +GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,10,saa_readyplus) +static stralloc sauninit = {0}; + +stralloc helohost = {0}; +stralloc routes = {0}; +struct constmap maproutes; +stralloc host = {0}; +stralloc sender = {0}; + +saa reciplist = {0}; + +struct ip_address partner; + +void out(s) char *s; { if (substdio_puts(subfdoutsmall,s) == -1) _exit(0); } +void zero() { if (substdio_put(subfdoutsmall,"\0",1) == -1) _exit(0); } +void _noreturn_ zerodie() { zero(); substdio_flush(subfdoutsmall); _exit(0); } +void outsafe(sa) stralloc *sa; { int i; char ch; +for (i = 0;i < sa->len;++i) { +ch = sa->s[i]; if (ch < 33) ch = '?'; if (ch > 126) ch = '?'; +if (substdio_put(subfdoutsmall,&ch,1) == -1) _exit(0); } } + +void _noreturn_ temp_nomem() { out("ZOut of memory. (#4.3.0)\n"); zerodie(); } +void _noreturn_ temp_oserr() { out("Z\ +System resources temporarily unavailable. (#4.3.0)\n"); zerodie(); } +void _noreturn_ temp_noconn() { out("Z\ +Sorry, I wasn't able to establish an SMTP connection. (#4.4.1)\n"); zerodie(); } +void _noreturn_ temp_read() { out("ZUnable to read message. (#4.3.0)\n"); zerodie(); } +void _noreturn_ temp_dnscanon() { out("Z\ +CNAME lookup failed temporarily. (#4.4.3)\n"); zerodie(); } +void _noreturn_ temp_dns() { out("Z\ +Sorry, I couldn't find any host by that name. (#4.1.2)\n"); zerodie(); } +void _noreturn_ temp_chdir() { out("Z\ +Unable to switch to home directory. (#4.3.0)\n"); zerodie(); } +void _noreturn_ temp_control() { out("Z\ +Unable to read control files. (#4.3.0)\n"); zerodie(); } +void _noreturn_ perm_partialline() { out("D\ +SMTP cannot transfer messages with partial final lines. (#5.6.2)\n"); zerodie(); } +void _noreturn_ perm_usage() { out("D\ +I (qmail-remote) was invoked improperly. (#5.3.5)\n"); zerodie(); } +void _noreturn_ perm_dns() { out("D\ +Sorry, I couldn't find any host named "); +outsafe(&host); +out(". (#5.1.2)\n"); zerodie(); } +void _noreturn_ perm_nomx() { out("D\ +Sorry, I couldn't find a mail exchanger or IP address. (#5.4.4)\n"); +zerodie(); } +void _noreturn_ perm_ambigmx() { out("D\ +Sorry. Although I'm listed as a best-preference MX or A for that host,\n\ +it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n"); +zerodie(); } + +void outhost() +{ + char x[IPFMT]; + if (substdio_put(subfdoutsmall,x,ip_fmt(x,&partner)) == -1) _exit(0); +} + +int flagcritical = 0; + +void _noreturn_ dropped() { + out("ZConnected to "); + outhost(); + out(" but connection died. "); + if (flagcritical) out("Possible duplicate! "); + out("(#4.4.2)\n"); + zerodie(); +} + +int timeoutconnect = 60; +int smtpfd; +int timeout = 1200; + +GEN_SAFE_TIMEOUTREAD(saferead,timeout,smtpfd,dropped()) +GEN_SAFE_TIMEOUTWRITE(safewrite,timeout,smtpfd,dropped()) + +char inbuf[1024]; +substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf)); +char smtptobuf[1024]; +substdio smtpto = SUBSTDIO_FDBUF(safewrite,-1,smtptobuf,sizeof(smtptobuf)); +char smtpfrombuf[128]; +substdio smtpfrom = SUBSTDIO_FDBUF(saferead,-1,smtpfrombuf,sizeof(smtpfrombuf)); + +stralloc smtptext = {0}; + +static void get(unsigned char *uc) +{ + char *ch = (char *)uc; + substdio_get(&smtpfrom,ch,1); + if (*ch != '\r') + if (smtptext.len < HUGESMTPTEXT) + if (!stralloc_append(&smtptext,ch)) temp_nomem(); +} + +unsigned long smtpcode() +{ + unsigned char ch; + unsigned long code; + + if (!stralloc_copys(&smtptext,"")) temp_nomem(); + + get(&ch); code = ch - '0'; + get(&ch); code = code * 10 + (ch - '0'); + get(&ch); code = code * 10 + (ch - '0'); + for (;;) { + get(&ch); + if (ch != '-') break; + while (ch != '\n') get(&ch); + get(&ch); + get(&ch); + get(&ch); + } + while (ch != '\n') get(&ch); + + return code; +} + +void outsmtptext() +{ + int i; + if (smtptext.s) if (smtptext.len) { + out("Remote host said: "); + for (i = 0;i < smtptext.len;++i) + if (!smtptext.s[i]) smtptext.s[i] = '?'; + if (substdio_put(subfdoutsmall,smtptext.s,smtptext.len) == -1) _exit(0); + smtptext.len = 0; + } +} + +void quit(prepend,append) +char *prepend; +char *append; +{ + substdio_putsflush(&smtpto,"QUIT\r\n"); + /* waiting for remote side is just too ridiculous */ + out(prepend); + outhost(); + out(append); + out(".\n"); + outsmtptext(); + zerodie(); +} + +void blast() +{ + int r; + char ch; + + for (;;) { + r = substdio_get(&ssin,&ch,1); + if (r == 0) break; + if (r == -1) temp_read(); + if (ch == '.') + substdio_put(&smtpto,".",1); + while (ch != '\n') { + if (ch == '\r') { + r = substdio_get(&ssin, &ch, 1); + if (r == 0) + break; + if (r == -1) temp_read(); + if (ch != '\n') { + substdio_put(&smtpto, "\r\n", 2); + } else + break; + } + substdio_put(&smtpto,&ch,1); + r = substdio_get(&ssin,&ch,1); + if (r == 0) perm_partialline(); + if (r == -1) temp_read(); + } + substdio_put(&smtpto,"\r\n",2); + } + + flagcritical = 1; + substdio_put(&smtpto,".\r\n",3); + substdio_flush(&smtpto); +} + +stralloc recip = {0}; + +void smtp() +{ + unsigned long code; + int flagbother; + int i; + + if (smtpcode() != 220) quit("ZConnected to "," but greeting failed"); + + substdio_puts(&smtpto,"HELO "); + substdio_put(&smtpto,helohost.s,helohost.len); + substdio_puts(&smtpto,"\r\n"); + substdio_flush(&smtpto); + if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected"); + + substdio_puts(&smtpto,"MAIL FROM:<"); + substdio_put(&smtpto,sender.s,sender.len); + substdio_puts(&smtpto,">\r\n"); + substdio_flush(&smtpto); + code = smtpcode(); + if (code >= 500) quit("DConnected to "," but sender was rejected"); + if (code >= 400) quit("ZConnected to "," but sender was rejected"); + + flagbother = 0; + for (i = 0;i < reciplist.len;++i) { + substdio_puts(&smtpto,"RCPT TO:<"); + substdio_put(&smtpto,reciplist.sa[i].s,reciplist.sa[i].len); + substdio_puts(&smtpto,">\r\n"); + substdio_flush(&smtpto); + code = smtpcode(); + if (code >= 500) { + out("h"); outhost(); out(" does not like recipient.\n"); + outsmtptext(); zero(); + } + else if (code >= 400) { + out("s"); outhost(); out(" does not like recipient.\n"); + outsmtptext(); zero(); + } + else { + out("r"); zero(); + flagbother = 1; + } + } + if (!flagbother) quit("DGiving up on ",""); + + substdio_putsflush(&smtpto,"DATA\r\n"); + code = smtpcode(); + if (code >= 500) quit("D"," failed on DATA command"); + if (code >= 400) quit("Z"," failed on DATA command"); + + blast(); + code = smtpcode(); + flagcritical = 0; + if (code >= 500) quit("D"," failed after I sent the message"); + if (code >= 400) quit("Z"," failed after I sent the message"); + quit("K"," accepted message"); +} + +stralloc canonhost = {0}; +stralloc canonbox = {0}; + +void addrmangle(stralloc *saout, char *s) +{ + int j; + + j = str_rchr(s,'@'); + if (!s[j]) { + if (!stralloc_copys(saout,s)) temp_nomem(); + return; + } + if (!stralloc_copys(&canonbox,s)) temp_nomem(); + canonbox.len = j; + /* box has to be quoted */ + if (!quote(saout,&canonbox)) temp_nomem(); + if (!stralloc_cats(saout,"@")) temp_nomem(); + + if (!stralloc_copys(&canonhost,s + j + 1)) temp_nomem(); + + if (!stralloc_cat(saout,&canonhost)) temp_nomem(); +} + +void getcontrols() +{ + if (control_init() == -1) temp_control(); + if (control_readint(&timeout,"control/timeoutremote") == -1) temp_control(); + if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1) + temp_control(); + if (control_rldef(&helohost,"control/helohost",1,NULL) != 1) + temp_control(); + switch(control_readfile(&routes,"control/smtproutes",0)) { + case -1: + temp_control(); + case 0: + if (!constmap_init(&maproutes,"",0,1)) temp_nomem(); break; + case 1: + if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break; + } +} + +int main(int argc, char **argv) +{ + static ipalloc ip = {0}; + int i; + unsigned long random; + char **recips; + unsigned long prefme; + char *relayhost; + + sig_pipeignore(); + if (argc < 4) perm_usage(); + if (chdir(auto_qmail) == -1) temp_chdir(); + getcontrols(); + + + if (!stralloc_copys(&host,argv[1])) temp_nomem(); + + relayhost = 0; + for (i = 0;i <= host.len;++i) + if ((i == 0) || (i == host.len) || (host.s[i] == '.')) + if ((relayhost = constmap(&maproutes,host.s + i,host.len - i))) + break; + if (relayhost && !*relayhost) relayhost = 0; + + if (relayhost) { + i = str_chr(relayhost,':'); + if (relayhost[i]) { + scan_ulong(relayhost + i + 1,&port); + relayhost[i] = 0; + } + if (!stralloc_copys(&host,relayhost)) temp_nomem(); + } + + + addrmangle(&sender,argv[2]); + + if (!saa_readyplus(&reciplist,0)) temp_nomem(); + if (ipme_init() != 1) temp_oserr(); + + recips = argv + 3; + while (*recips) { + if (!saa_readyplus(&reciplist,1)) temp_nomem(); + reciplist.sa[reciplist.len] = sauninit; + addrmangle(reciplist.sa + reciplist.len,*recips); + ++reciplist.len; + ++recips; + } + + + random = now() + (getpid() << 16); + switch (relayhost ? dns_ip(&ip,&host) : dns_mxip(&ip,&host,random)) { + case DNS_MEM: temp_nomem(); + case DNS_SOFT: temp_dns(); + case DNS_HARD: perm_dns(); + case 1: + if (ip.len <= 0) temp_dns(); + } + + if (ip.len <= 0) perm_nomx(); + + prefme = 100000; + for (i = 0;i < ip.len;++i) + if (ipme_is(&ip.ix[i].ip)) + if (ip.ix[i].pref < prefme) + prefme = ip.ix[i].pref; + + if (relayhost) prefme = 300000; + + for (i = 0;i < ip.len;++i) + if (ip.ix[i].pref < prefme) + break; + + if (i >= ip.len) + perm_ambigmx(); + + for (i = 0;i < ip.len;++i) if (ip.ix[i].pref < prefme) { + if (tcpto(&ip.ix[i].ip)) continue; + + smtpfd = socket(AF_INET,SOCK_STREAM,0); + if (smtpfd == -1) temp_oserr(); + + if (timeoutconn(smtpfd,&ip.ix[i].ip,(unsigned int) port,timeoutconnect) == 0) { + tcpto_err(&ip.ix[i].ip,0); + partner = ip.ix[i].ip; + smtp(); /* does not return */ + } + tcpto_err(&ip.ix[i].ip,errno == error_timeout); + close(smtpfd); + } + + temp_noconn(); +} diff --git a/src/qmail-smtpd.c b/src/qmail-smtpd.c @@ -21,6 +21,7 @@ #include "now.h" #include "exit.h" #include "rcpthosts.h" +#include "realrcptto.h" #include "timeoutread.h" #include "timeoutwrite.h" #include "commands.h" @@ -51,6 +52,8 @@ void err_unimpl(char *arg) { out("502 unimplemented (#5.5.1)\r\n"); } void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); } void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); } void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); } +void die_cdb() { out("421 unable to read cdb user database (#4.3.0)\r\n"); flush(); _exit(1); } +void die_sys() { out("421 unable to read system user database (#4.3.0)\r\n"); flush(); _exit(1); } void err_noop(char *arg) { out("250 ok\r\n"); } void err_vrfy(char *arg) { out("252 send some mail, i'll try my best\r\n"); } void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); } @@ -118,7 +121,10 @@ void setup() 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; } @@ -137,6 +143,10 @@ void setup() 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 */ @@ -290,6 +300,7 @@ void smtp_mail(char *arg) if (!stralloc_copys(&rcptto,"")) die_nomem(); if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); if (!stralloc_0(&mailfrom)) die_nomem(); + realrcptto_start(); out("250 ok\r\n"); } void smtp_rcpt(char *arg) { @@ -303,6 +314,10 @@ void smtp_rcpt(char *arg) { } else if (!addrallowed()) { err_nogateway(); return; } + if (!realrcptto(addr.s)) { + out("550 That user has elected not to receive emails. (#5.1.1)\r\n"); + return; + } if (!(relayclient || dnsblskip || flagdnsbl)) if (dnsblcheck()) die_dnsbl(dnsblhost.s); if (!stralloc_cats(&rcptto,"T")) die_nomem(); @@ -419,6 +434,7 @@ void smtp_data(char *arg) { if (!seenmail) { err_wantmail(); return; } if (!rcptto.len) { err_wantrcpt(); return; } + if (realrcptto_deny()) { out("554 sorry, no mailbox here by that name. (#5.1.1)\r\n"); return; } seenmail = 0; if (databytes) bytestooverflow = databytes + 1; if (qmail_open(&qqt) == -1) { err_qqt(); return; } diff --git a/src/realrcptto.c b/src/realrcptto.c @@ -0,0 +1,336 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <pwd.h> +#include "auto_break.h" +#include "auto_users.h" +#include "byte.h" +#include "case.h" +#include "cdb.h" +#include "constmap.h" +#include "error.h" +#include "fmt.h" +#include "open.h" +#include "str.h" +#include "stralloc.h" +#include "uint32.h" +#include "substdio.h" +#include "env.h" + +extern void die_nomem(); +extern void die_control(); +extern void die_cdb(); +extern void die_sys(); + +static stralloc envnoathost = {0}; +static stralloc percenthack = {0}; +static stralloc locals = {0}; +static stralloc vdoms = {0}; +static struct constmap mappercenthack; +static struct constmap maplocals; +static struct constmap mapvdoms; + +static char *dash; +static char *extension; +static char *local; +static struct passwd *pw; + +static char errbuf[128]; +static struct substdio sserr = SUBSTDIO_FDBUF(write,2,errbuf,sizeof errbuf); + +static char pidbuf[64]; +static char remoteipbuf[64]=" "; + +static int flagdenyall; +static int flagdenyany; + +void realrcptto_init() +{ + char *x; + + if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1) + die_control(); + + if (control_readfile(&locals,"control/locals",1) != 1) die_control(); + if (!constmap_init(&maplocals,locals.s,locals.len,0)) die_nomem(); + switch(control_readfile(&percenthack,"control/percenthack",0)) { + case -1: die_control(); + case 0: if (!constmap_init(&mappercenthack,"",0,0)) die_nomem(); + case 1: + if (!constmap_init(&mappercenthack,percenthack.s,percenthack.len,0)) + die_nomem(); + } + switch(control_readfile(&vdoms,"control/virtualdomains",0)) { + case -1: die_control(); + case 0: if (!constmap_init(&mapvdoms,"",0,1)) die_nomem(); + case 1: if (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) die_nomem(); + } + + str_copy(pidbuf + fmt_ulong(pidbuf,getpid())," "); + x=env_get("PROTO"); + if (x) { + static char const remoteip[]="REMOTEIP"; + unsigned int len = str_len(x); + if (len <= sizeof remoteipbuf - sizeof remoteip) { + byte_copy(remoteipbuf,len,x); + byte_copy(remoteipbuf + len,sizeof remoteip,remoteip); + x = env_get(remoteipbuf); + len = str_len(x); + if (len + 1 < sizeof remoteipbuf) { + byte_copy(remoteipbuf,len,x); + remoteipbuf[len]=' '; + remoteipbuf[len + 1]='\0'; + } + } + } + + x = env_get("QMAILRRTDENYALL"); + flagdenyall = (x && x[0]=='1' && x[1]=='\0'); +} + +void realrcptto_start() +{ + flagdenyany = 0; +} + +static int denyaddr(addr) +char *addr; +{ + substdio_puts(&sserr,"realrcptto "); + substdio_puts(&sserr,pidbuf); + substdio_puts(&sserr,remoteipbuf); + substdio_puts(&sserr,addr); + substdio_puts(&sserr,"\n"); + substdio_flush(&sserr); + flagdenyany = 1; + return flagdenyall; +} + +static void stat_error(path,error) +char* path; +int error; +{ + substdio_puts(&sserr,"unable to stat "); + substdio_puts(&sserr,path); + substdio_puts(&sserr,": "); + substdio_puts(&sserr,error_str(error)); + substdio_puts(&sserr,"\n"); + substdio_flush(&sserr); +} + +#define GETPW_USERLEN 32 + +static int userext() +{ + char username[GETPW_USERLEN]; + struct stat st; + + extension = local + str_len(local); + for (;;) { + if (extension - local < sizeof(username)) + if (!*extension || (*extension == *auto_break)) { + byte_copy(username,extension - local,local); + username[extension - local] = 0; + case_lowers(username); + errno = 0; + pw = getpwnam(username); + if (errno == error_txtbsy) die_sys(); + if (pw) + if (pw->pw_uid) + if (stat(pw->pw_dir,&st) == 0) { + if (st.st_uid == pw->pw_uid) { + dash = ""; + if (*extension) { ++extension; dash = "-"; } + return 1; + } + } + else + if (error_temp(errno)) die_sys(); + } + if (extension == local) return 0; + --extension; + } +} + +int realrcptto(addr) +char *addr; +{ + char *homedir; + static stralloc localpart = {0}; + static stralloc lower = {0}; + static stralloc nughde = {0}; + static stralloc wildchars = {0}; + static stralloc safeext = {0}; + static stralloc qme = {0}; + unsigned int i,at; + + /* Short circuit, or full logging? Short circuit. */ + if (flagdenyall && flagdenyany) return 1; + + /* qmail-send:rewrite */ + if (!stralloc_copys(&localpart,addr)) die_nomem(); + i = byte_rchr(localpart.s,localpart.len,'@'); + if (i == localpart.len) { + if (!stralloc_cats(&localpart,"@")) die_nomem(); + if (!stralloc_cat(&localpart,&envnoathost)) die_nomem(); + } + while (constmap(&mappercenthack,localpart.s + i + 1,localpart.len - i - 1)) { + unsigned int j = byte_rchr(localpart.s,i,'%'); + if (j == i) break; + localpart.len = i; + i = j; + localpart.s[i] = '@'; + } + at = byte_rchr(localpart.s,localpart.len,'@'); + if (constmap(&maplocals,localpart.s + at + 1,localpart.len - at - 1)) { + localpart.len = at; + localpart.s[at] = '\0'; + } else { + unsigned int xlen,newlen; + char *x; + for (i = 0;;++i) { + if (i > localpart.len) return 1; + if (!i || (i == at + 1) || (i == localpart.len) || + ((i > at) && (localpart.s[i] == '.'))) { + x = constmap(&mapvdoms,localpart.s + i,localpart.len - i); + if (x) break; + } + } + if (!*x) return 1; + xlen = str_len(x) + 1; /* +1 for '-' */ + newlen = xlen + at + 1; /* +1 for \0 */ + if (xlen < 1 || newlen - 1 < xlen || newlen < 1 || + !stralloc_ready(&localpart,newlen)) + die_nomem(); + localpart.s[newlen - 1] = '\0'; + byte_copyr(localpart.s + xlen,at,localpart.s); + localpart.s[xlen - 1] = '-'; + byte_copy(localpart.s,xlen - 1,x); + localpart.len = newlen; + } + + /* qmail-lspawn:nughde_get */ + { + int r,fd,flagwild; + if (!stralloc_copys(&lower,"!")) die_nomem(); + if (!stralloc_cats(&lower,localpart.s)) die_nomem(); + if (!stralloc_0(&lower)) die_nomem(); + case_lowerb(lower.s,lower.len); + if (!stralloc_copys(&nughde,"")) die_nomem(); + fd = open_read("users/cdb"); + if (fd == -1) { + if (errno != error_noent) die_cdb(); + } else { + uint32 dlen; + r = cdb_seek(fd,"",0,&dlen); + if (r != 1) die_cdb(); + if (!stralloc_ready(&wildchars,(unsigned int) dlen)) die_nomem(); + wildchars.len = dlen; + if (cdb_bread(fd,wildchars.s,wildchars.len) == -1) die_cdb(); + i = lower.len; + flagwild = 0; + do { /* i > 0 */ + if (!flagwild || (i == 1) || + (byte_chr(wildchars.s,wildchars.len,lower.s[i - 1]) + < wildchars.len)) { + r = cdb_seek(fd,lower.s,i,&dlen); + if (r == -1) die_cdb(); + if (r == 1) { + char *x; + if (!stralloc_ready(&nughde,(unsigned int) dlen)) die_nomem(); + nughde.len = dlen; + if (cdb_bread(fd,nughde.s,nughde.len) == -1) die_cdb(); + if (flagwild) + if (!stralloc_cats(&nughde,localpart.s + i - 1)) die_nomem(); + if (!stralloc_0(&nughde)) die_nomem(); + close(fd); + x=nughde.s; + /* skip username */ + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + /* skip uid */ + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + /* skip gid */ + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + /* skip homedir */ + homedir=x; + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + /* skip dash */ + dash=x; + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + extension=x; + goto got_nughde; + } + } + --i; + flagwild = 1; + } while (i); + close(fd); + } + } + + /* qmail-getpw */ + local = localpart.s; + if (!userext()) { + extension = local; + dash = "-"; + pw = getpwnam(auto_usera); + } + if (!pw) return denyaddr(addr); + if (!stralloc_copys(&nughde,pw->pw_dir)) die_nomem(); + if (!stralloc_0(&nughde)) die_nomem(); + homedir=nughde.s; + + got_nughde: + + /* qmail-local:qmesearch */ + if (!*dash) return 1; + if (!stralloc_copys(&safeext,extension)) die_nomem(); + case_lowerb(safeext.s,safeext.len); + for (i = 0;i < safeext.len;++i) + if (safeext.s[i] == '.') + safeext.s[i] = ':'; + { + struct stat st; + int i; + if (!stralloc_copys(&qme,homedir)) die_nomem(); + if (!stralloc_cats(&qme,"/.qmail")) die_nomem(); + if (!stralloc_cats(&qme,dash)) die_nomem(); + if (!stralloc_cat(&qme,&safeext)) die_nomem(); + if (!stralloc_0(&qme)) die_nomem(); + if (stat(qme.s,&st) == 0) return 1; + if (errno != error_noent) { + stat_error(qme.s,errno); + return 1; + } + for (i = safeext.len;i >= 0;--i) + if (!i || (safeext.s[i - 1] == '-')) { + if (!stralloc_copys(&qme,homedir)) die_nomem(); + if (!stralloc_cats(&qme,"/.qmail")) die_nomem(); + if (!stralloc_cats(&qme,dash)) die_nomem(); + if (!stralloc_catb(&qme,safeext.s,i)) die_nomem(); + if (!stralloc_cats(&qme,"default")) die_nomem(); + if (!stralloc_0(&qme)) die_nomem(); + if (stat(qme.s,&st) == 0) return 1; + if (errno != error_noent) { + stat_error(qme.s,errno); + return 1; + } + } + return denyaddr(addr); + } +} + +int realrcptto_deny() +{ + return flagdenyall && flagdenyany; +}