--- orig/qmail-smtpd.c 1998-06-15 12:53:16.000000000 +0200 +++ baduser/qmail-smtpd.c 2004-05-26 00:50:13.000000000 +0200 @@ -1,3 +1,6 @@ +#include +#include +#include #include "sig.h" #include "readwrite.h" #include "stralloc.h" @@ -24,6 +27,8 @@ #include "timeoutwrite.h" #include "commands.h" +#define USERPATH "/srv/vpopmail/domains" + #define MAXHOPS 100 unsigned int databytes = 0; int timeout = 1200; @@ -59,6 +64,23 @@ void err_noop() { out("250 ok\r\n"); } void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); } void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); } +log_3(const char *a, const char *b, const char *c) +{ + char *buf = alloca(str_len(a) + str_len(b) + str_len(c)+1); + unsigned int i; + + i = str_copy(buf, a); + i += str_copy(buf + i, b); + i += str_copy(buf + i, c); + write(2, buf, i); +} + +void +err_control(const char *fn) +{ + out("421 unable to read controls (#4.3.5)\r\n", 38); + log_3("error: unable to open file: """, fn, """\n"); +} stralloc greeting = {0}; @@ -91,11 +113,38 @@ void dohelo(arg) char *arg; { fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0; } +int +load_list(stralloc *sa, const char *fn) +{ + int ok; + + ok = control_readfile(sa,fn,0); + if (ok == -1) { + err_control(fn); + sa->len = 0; + } else if (ok) { + unsigned int i; + + for (i=0; ilen; ++i) + if (sa->s[i]=='\n') + sa->s[i]=0; + } else + sa->len = 0; + + return ok; +} + int liphostok = 0; stralloc liphost = {0}; int bmfok = 0; stralloc bmf = {0}; struct constmap mapbmf; +int bmtok = 0; +stralloc bmt = {0}; +struct constmap mapbmt; +int fdomok = 0; +stralloc filterdom = {0}; +stralloc vpopbounce = {0}; void setup() { @@ -112,11 +161,21 @@ void setup() if (rcpthosts_init() == -1) die_control(); + if (control_readline(&vpopbounce,"control/vpopbounce") != 1) + die_control(); + while (vpopbounce.len && (vpopbounce.s[vpopbounce.len] == '\n')) + vpopbounce.s[vpopbounce.len--] = 0; 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(); + bmtok = control_readfile(&bmt,"control/badmailto",0); + if (bmtok == -1) die_control(); + if (bmtok) + if (!constmap_init(&mapbmt,bmt.s,bmt.len,0)) die_nomem(); + fdomok = load_list(&filterdom, "control/filterdomains"); + if (control_readint(&databytes,"control/databytes") == -1) die_control(); x = env_get("DATABYTES"); if (x) { scan_ulong(x,&u); databytes = u; } @@ -208,6 +267,17 @@ int bmfcheck() return 0; } +int bmtcheck() +{ + int j; + if (!bmtok) return 0; + if (constmap(&mapbmt,addr.s,addr.len - 1)) return 1; + j = byte_rchr(addr.s,addr.len,'@'); + if (j < addr.len) + if (constmap(&mapbmt,addr.s + j,addr.len - j - 1)) return 1; + return 0; +} + int addrallowed() { int r; @@ -247,10 +317,243 @@ void smtp_mail(arg) char *arg; if (!stralloc_0(&mailfrom)) die_nomem(); out("250 ok\r\n"); } + +#define REJECTED_LEN 24+8+11+IPFMT+addr.len+mailfrom.len + +unsigned int +msg_rejected(char *buf, const char *a, const char* b, const char *c) +{ + unsigned int i; + + i = str_copy(buf, "rejected message to <"); + i += str_copy(buf+i, addr.s); + i += str_copy(buf+i, "> from <"); + i += str_copy(buf+i, mailfrom.s); + i += str_copy(buf+i, "> from IP ["); + i += str_copy(buf+i, remoteip); + i += str_copy(buf+i, a); + i += str_copy(buf+i, b); + i += str_copy(buf+i, c); + + return i; +} + +void +log_blocked(const char *reason) +{ + char *buf = alloca(1+ REJECTED_LEN + 3 + str_len(reason) + 2); + unsigned int i; + + i = msg_rejected(buf, "] (", reason, ")\n"); + write(2,buf,i); +} + +void err_bmt(void) { + out("550 no such user (#5.1.1) <", 27); + out(addr.s, addr.len); + out(">\r\n", 3); + log_blocked("user unkown"); +} + +/* values for default + + (def & 1) append "default" + (def & 2) append suff1 + */ + +int +qmexists(const stralloc *dirtempl, const char *suff1, const unsigned int len, const int def) +{ + stralloc tmpfile = {0}; + int fd; + + if (! stralloc_copy(&tmpfile, dirtempl)) die_nomem(); + if (def & 2) + if (! stralloc_catb(&tmpfile, suff1, len)) die_nomem(); + + if (def & 1) + if (! stralloc_catb(&tmpfile, "default", 7)) die_nomem(); + if (! stralloc_0(&tmpfile)) die_nomem(); + + fd = open_read(tmpfile.s); + if (fd == -1) + if (errno != error_noent) + err_control(tmpfile.s); + return fd; +} + +/* Return codes: + + 0: user doesn't exist + 1: user exists + 2: mail would be catched by .qmail-default and .qmail-default != vpopbounce + 3: domain is not filtered (use for domains not local) + 4: mail would be catched by .qmail-foo-default (i.e. mailinglist) +*/ + +int +user_exists(stralloc *localpart, stralloc *domainpart, stralloc *domainpath, stralloc *userpath) +{ +#ifdef USERPATH + stralloc tmpfile = {0}; + stralloc dotqm = {0}; + char buff[2*vpopbounce.len+1]; + DIR *dirp; + int fd; + int r = 0; + unsigned int i; + const char *badchars = "\\|'`/"; + const unsigned int numbc = 5; /* str_len(badchars); */ +#endif + + /* the only address we accept without "@host" is postmaster */ + if (!domainpart->len) + if case_equals(addr.s, "postmaster") + return 1; + + if (bmtcheck()) + return 0; + +#ifdef USERPATH + if (filterdom.len) { + for (i=0; is)) { + r = 1; + break; + } + i += str_len(filterdom.s + i)+1; + } + } + if (!r) + return 3; + + /* if we don't do this someone may be able to check if + USERNAME/DIRECTORY exists if localpart is "username/directory" + Also a user with '\' or '|' and so on in it's name will not exist here + */ + for (i = 0; i < numbc; i++) { + if (str_chr(localpart->s, badchars[i]) < localpart->len ) + return 0; + } + /* usernames are limited to ASCII characters, check for < because of signedness */ + for (i = 0; i < localpart->len; i++) + if (localpart->s[i] < 0 ) + return 0; + + if (! stralloc_copy(&tmpfile, userpath)) die_nomem(); + if (! stralloc_0(&tmpfile)) die_nomem(); + + /* does directory USERPATH/DOMAIN/USER exist? */ + dirp = opendir(tmpfile.s); + if (dirp == NULL) { + /* does USERPATH/DOMAIN/.qmail-LOCALPART exist? */ + if (errno != error_noent) { + err_control(tmpfile.s); + return 3; + } + if (! stralloc_copy(&tmpfile, domainpath)) die_nomem(); + if (! stralloc_cats(&tmpfile, "/.qmail-")) die_nomem(); + if (! stralloc_copy(&dotqm, &tmpfile)) die_nomem(); + fd = qmexists(&dotqm, localpart->s, localpart->len, 2); + + if (fd == -1) { + /* if username contains '-' there may be + .qmail-partofusername-default */ + i = str_chr(localpart->s, '-'); + while (i < localpart->len) { + fd = qmexists(&dotqm, localpart->s, ++i, 3); + if (fd != -1) { + close(fd); + return 4; + } + i += str_chr(localpart->s+i, '-'); + } + + /* does USERPATH/DOMAIN/.qmail-default exist ? */ + fd = qmexists(&dotqm, (char *)0, 0, 1); + if (fd == -1) { + /* no local user with that address */ + return 0; + } else { + r = read(fd, buff, sizeof(buff)-1); + if (r == -1) { + err_control(tmpfile.s); + return 3; + } + close(fd); + buff[r] = 0; + while (r && (buff[r-1] == '\n')) + buff[--r] = 0; + if (str_equal(buff, vpopbounce.s)) + /* mail would be bounced by .qmail-default */ + return 0; + /* mail would be catched by .qmail-default */ + return 2; + } + } else { + close(fd); + } + } else { + closedir(dirp); + } +#endif + return 1; +} + +void +split_addr(stralloc *localpart, stralloc *host, char *s) +{ + unsigned int j = 0; + unsigned int len = str_len(s); + + j = byte_rchr(s,len,'@'); + if (j < len) { + if (!stralloc_copyb(localpart,s,j)) die_nomem(); + if (!stralloc_copyb(host,s+j+1,len-j-1)) die_nomem(); + case_lowerb(host->s, host->len); + } else { + if (!stralloc_copys(localpart,s)) die_nomem(); + if (!stralloc_copys(host, "")) die_nomem(); + } + + case_lowerb(localpart->s, localpart->len); + while ( (j += str_chr(localpart->s + j, '.')) < localpart->len ) + *(localpart->s + j) = ':'; +} + void smtp_rcpt(arg) char *arg; { +#ifdef USERPATH + stralloc localpart = {0}; + stralloc host = {0}; + stralloc domainpath = {0}; + stralloc userpath = {0}; +#endif /* USERPATH */ + if (!seenmail) { err_wantmail(); return; } if (!addrparse(arg)) { err_syntax(); return; } - if (flagbarf) { err_bmf(); return; } + if (flagbarf) { + err_bmf(); + log_blocked("in badmailfrom"); + return; + } + +#ifdef USERPATH + split_addr(&localpart, &host, addr.s); + if (!stralloc_copys(&domainpath, USERPATH)) die_nomem(); + if (!stralloc_cat(&domainpath, &host)) die_nomem(); + if (!stralloc_copy(&userpath, &domainpath)) die_nomem(); + if (!stralloc_cats(&userpath, "/")) die_nomem(); + if (!stralloc_cat(&userpath, &localpart)) die_nomem(); + host.s[host.len] = 0; + + /* look if local user exists */ + + if (! user_exists(&localpart, &host, &domainpath, &userpath)) { + err_bmt(); + return; + } +#endif + if (relayclient) { --addr.len; if (!stralloc_cats(&addr,relayclient)) die_nomem();