http://code.dogmap.org./qmail/#realrcptto Changes in version 2009.01.31 (manuel mausz): - userlookup + set DTUSER environment variable for quotacheck plugin Changes in version 2006.12.10: - For QMAILRRTENYALL, use error code 554 after DATA, not 550. Thanks to ... sorry, I lost track of who found this. - Log stat() errors for .qmail files. Thanks to Chris Bensend for suggesting this. Changes in version 2006.10.26: - Logging uses substdio_puts() and substdio_flush() instead of substdio_putsflush(). This makes log entries less likely to be interleaved. Thanks to Matthew Dempsky for finding this. Changes in version 2004.09.14: - Strict syntax fix: a variable declaration was accidentally put among statements instead of at the beginning of the block. Changes in version 2004.08.30: - Added QMAILRRTDENYALL. - Cleanup: missing "break" for some switch statements (no visible behavior changes AFAICT). Thanks to Markus Stumpf for finding these. Changes in version 2004.02.05: - Short-circuit speedup when "dash" is empty. - Move duplicated code into realrcptto.c. - Add logging of rejected addresses. - Verified inter-patchability with netqmail-1.05 and Russell Nelson's qmail-smtpd-viruscan patch. diff -Naur Makefile.orig Makefile --- Makefile.orig 1998-06-15 06:52:55.000000000 -0400 +++ Makefile 2006-12-08 03:21:25.000000000 -0500 @@ -1447,15 +1447,15 @@ ./compile qmail-qmqpd.c qmail-qmtpd: \ -load qmail-qmtpd.o rcpthosts.o control.o constmap.o received.o \ +load qmail-qmtpd.o realrcptto.o slurpclose.o rcpthosts.o control.o constmap.o received.o \ date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a open.a \ getln.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a \ -str.a fs.a auto_qmail.o dns.o ip.o ipalloc.o ipme.o byte_diff.o - ./load qmail-qmtpd rcpthosts.o control.o constmap.o \ +str.a fs.a auto_qmail.o dns.o ip.o ipalloc.o ipme.o byte_diff.o auto_break.o auto_usera.o + ./load qmail-qmtpd realrcptto.o slurpclose.o rcpthosts.o control.o constmap.o \ received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \ datetime.a open.a getln.a sig.a case.a env.a stralloc.a \ - alloc.a substdio.a error.a fs.a auto_qmail.o dns.o \ + alloc.a substdio.a error.a fs.a auto_qmail.o dns.o auto_break.o auto_usera.o \ `cat dns.lib` ip.o ipalloc.o ipme.o byte_diff.o str.a qmail-qmtpd.0: \ qmail-qmtpd.8 @@ -1614,18 +1614,18 @@ ./compile qmail-showctl.c qmail-smtpd: \ -load qmail-smtpd.o rcpthosts.o qregex.o commands.o timeoutread.o \ +load qmail-smtpd.o realrcptto.o slurpclose.o rcpthosts.o qregex.o commands.o timeoutread.o \ timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \ date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \ open.a sig.a case.a env.a stralloc.a alloc.a strerr.a substdio.a error.a str.a \ -fs.a auto_qmail.o base64.o qmail-spp.o socket.lib dns.o ip.o ipalloc.o - ./load qmail-smtpd qregex.o rcpthosts.o commands.o timeoutread.o \ +fs.a auto_qmail.o base64.o qmail-spp.o auto_break.o auto_usera.o socket.lib dns.o ip.o ipalloc.o + ./load qmail-smtpd realrcptto.o slurpclose.o qregex.o rcpthosts.o commands.o timeoutread.o \ timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \ tls.o ssl_timeoutio.o ndelay.a -L/usr/local/ssl/lib -lssl -lcrypto \ received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \ datetime.a getln.a open.a sig.a case.a qmail-spp.o env.a stralloc.a \ - alloc.a strerr.a substdio.a error.a fs.a auto_qmail.o base64.o `cat \ - socket.lib` dns.o str.a `cat dns.lib` + alloc.a strerr.a substdio.a error.a fs.a auto_qmail.o base64.o auto_break.o \ + auto_usera.o `cat socket.lib` dns.o str.a `cat dns.lib` qmail-smtpd.0: \ qmail-smtpd.8 @@ -1774,6 +1774,11 @@ auto_split.h ./compile readsubdir.c +realrcptto.o: \ +compile realrcptto.c auto_break.h auto_usera.h byte.h case.h cdb.h \ +constmap.h error.h fmt.h open.h str.h stralloc.h uint32.h + ./compile realrcptto.c + received.o: \ compile received.c fmt.h qmail.h substdio.h now.h datetime.h \ datetime.h date822fmt.h received.h diff -Naur qmail-qmtpd.c.orig qmail-qmtpd.c --- qmail-qmtpd.c.orig 1998-06-15 06:52:55.000000000 -0400 +++ qmail-qmtpd.c 2006-12-08 03:21:25.000000000 -0500 @@ -14,6 +14,15 @@ void badproto() { _exit(100); } void resources() { _exit(111); } +void die_nomem() { resources(); } +void die_control() { resources(); } +void die_cdb() { resources(); } +void die_sys() { resources(); } + +extern void realrcptto_init(); +extern void realrcptto_start(); +extern int realrcptto(); +extern int realrcptto_deny(); int safewrite(fd,buf,len) int fd; char *buf; int len; { @@ -98,6 +107,8 @@ 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"); @@ -114,6 +125,7 @@ if (!local) local = "unknown"; for (;;) { + realrcptto_start(); if (!stralloc_copys(&failure,"")) resources(); flagsenderok = 1; @@ -216,6 +228,10 @@ case -1: resources(); case 0: failure.s[failure.len - 1] = 'D'; } + + if (!failure.s[failure.len - 1]) + if (!realrcptto(buf,1)) + failure.s[failure.len - 1] = 'D'; if (!failure.s[failure.len - 1]) { qmail_to(&qq,buf); @@ -231,6 +247,7 @@ 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 (!relayclient && realrcptto_deny()) result = "Dsorry, no mailbox here by that name. (#5.1.1)\r\n"; if (*result) len = str_len(result); diff -Naur qmail-smtpd.c.orig qmail-smtpd.c --- qmail-smtpd.c.orig 1998-06-15 06:52:55.000000000 -0400 +++ qmail-smtpd.c 2006-12-09 04:02:00.000000000 -0500 @@ -111,12 +111,24 @@ void die_ipme() { enew(); eout("Unable to figure out my IP addresses!\n"); out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); eflush(); _exit(1); } +void die_cdb() +{ + enew(); eout("Unable to read cdb user database!\n"); + out("421 unable to read cdb user database (#4.3.0)\r\n"); flush(); + eflush(); _exit(1); +} +void die_sys() +{ + enew(); eout("Unable to read system user database!\n"); + out("421 unable to read system user database (#4.3.0)\r\n"); flush(); + eflush(); _exit(1); +} void straynewline() { enew(); eout("Stray newline from "); eout(remoteip); eout(".\n"); out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); eflush(); _exit(1); } @@ -161,6 +173,13 @@ int err_wantstarttls() { out("530 Must issue a STARTTLS command first (#5.7.0)\r\n"); return -1; }; void err_authfail() { out("535 authentication failed (#5.7.1)\r\n"); } +extern void realrcptto_init(); +extern void realrcptto_start(); +extern int realrcptto(); +extern int realrcptto_deny(); + +int flagauth = 0; + stralloc greeting = {0}; void smtp_greet(code) char *code; @@ -259,7 +259,8 @@ fdmbrt = open_read("control/morebadrcptto.cdb"); if (fdmbrt == -1) if (errno != error_noent) die_control(); + realrcptto_init(); if (control_readint(&databytes,"control/databytes") == -1) die_control(); x = env_get("DATABYTES"); @@ -635,7 +635,8 @@ if (!stralloc_copys(&rcptto,"")) die_nomem(); if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); if (!stralloc_0(&mailfrom)) die_nomem(); + realrcptto_start(); recipcount = 0; out("250 ok\r\n"); } void smtp_rcpt(arg) char *arg; { @@ -680,5 +701,9 @@ flagbrt = 1; log_deny("BAD RCPT TO", mailfrom.s,addr.s); } + if (!flagauth && !relayclient && !realrcptto(addr.s,1)) { + out("554 sorry, no mailbox here by that name. (#5.1.1)\r\n"); + return; + } if (!(spp_val = spp_rcpt(allowed))) return; if (!relayclient && spp_val == 1) { @@ -899,6 +899,7 @@ if (mailfrom.len == 1 && recipcount > 1) { err_badbounce(); return; } if (flagbrt) { err_brt(); return; } if (!spp_data()) return; + if (!relayclient && realrcptto_deny()) { out("550 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; } @@ -961,6 +961,5 @@ static stralloc slop = {0}; /* b64 challenge */ #endif -int flagauth = 0; char **childargs; char ssauthbuf[512]; diff -Naur /dev/null realrcptto.c --- /dev/null 2006-09-14 17:39:14.000000000 +0200 +++ realrcptto.c 2009-01-31 01:01:21.000000000 +0100 @@ -0,0 +1,421 @@ +#include +#include +#include +#include +#include "auto_break.h" +#include "auto_usera.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" +#include "slurpclose.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, depth) +char *addr; +int depth; +{ + if (depth == 1) + { + 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; + } +} + +// max lookups +#define MAXRECURSION 5 + +static int handleqme(qme, depth) +stralloc *qme; +int depth; +{ + stralloc cmds = {0}; + char *username = NULL; + int fd, i, j, k, count; + + if (depth >= MAXRECURSION) return 0; + if (!stralloc_ready(&cmds,0)) die_nomem(); + cmds.len = 0; + + fd = open_read(qme->s); + if (fd == -1) return 0; + if (slurpclose(fd,&cmds,256) == -1) die_nomem(); + if (!cmds.len || (cmds.s[cmds.len - 1] != '\n')) + if (!stralloc_cats(&cmds,"\n")) die_nomem(); + + i = count = 0; + for (j = 0;j < cmds.len;++j) + { + if (cmds.s[j] == '\n') + { + cmds.s[j] = 0; + k = j; + while ((k > i) && ((cmds.s[k - 1] == ' ') || (cmds.s[k - 1] == '\t'))) + cmds.s[--k] = 0; + switch(cmds.s[i]) + { + case 0: + case '#': + case '.': + case '/': + case '|': + case '+': + break; + case '&': + ++i; + default: + count++; + username = cmds.s + i; + break; + } + i = j + 1; + } + } + + if (count == 1 && username) + realrcptto(username, ++depth); + + return 1; +} + +int realrcptto(addr, depth) +char *addr; +int depth; +{ + env_unset("DTUSER"); + return realrcptto_ex(addr, depth); +} + +int realrcptto_ex(addr, depth) +char *addr; +int depth; +{ + char *homedir, *username; + 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 denyaddr(addr, depth); + if (!i || (i == at + 1) || (i == localpart.len) || + ((i > at) && (localpart.s[i] == '.'))) { + x = constmap(&mapvdoms,localpart.s + i,localpart.len - i); + if (x && i == at + 1) { + // set QMAILQUEUE if catch-all + char *qmailqueue; + qmailqueue = env_get("QMAILQUEUE"); + if (qmailqueue) { + if (!env_put("QMAILQUEUE=bin/qmail-queue")) die_nomem(); + } + } + 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 */ + username=x; + 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, depth); + if (!stralloc_copys(&nughde,pw->pw_dir)) die_nomem(); + if (!stralloc_0(&nughde)) die_nomem(); + homedir=nughde.s; + username=pw->pw_name; + + got_nughde: + + /* qmail-local:qmesearch */ + //if (!*dash) return 1; + if (!*dash) { env_put2("DTUSER", username); 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 (stat(qme.s,&st) == 0) { handleqme(&qme, depth); 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 (stat(qme.s,&st) == 0) { handleqme(&qme, depth); return 1; } + if (errno != error_noent) { + stat_error(qme.s,errno); + return 1; + } + } + return denyaddr(addr, depth); + } +} + +int realrcptto_deny() +{ + return flagdenyall && flagdenyany; +}