--- orig/qmail-smtpd.c 1998-06-15 12:53:16.000000000 +0200 +++ remote-tmp/qmail-smtpd.c 2004-12-11 16:08:09.344891160 +0100 @@ -1,3 +1,6 @@ +#include +#include +#include #include "sig.h" #include "readwrite.h" #include "stralloc.h" @@ -23,6 +26,7 @@ #include "timeoutread.h" #include "timeoutwrite.h" #include "commands.h" +#include "cdb.h" #define MAXHOPS 100 unsigned int databytes = 0; @@ -59,6 +63,23 @@ 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 +112,38 @@ 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 +160,21 @@ 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/unfiltereddomains"); + if (control_readint(&databytes,"control/databytes") == -1) die_control(); x = env_get("DATABYTES"); if (x) { scan_ulong(x,&u); databytes = u; } @@ -208,6 +266,17 @@ 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 +316,319 @@ 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: + + -1: temporary error (most probably someone has f*cked up the permissions) + 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) +{ + 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); */ + + /* the only address we accept without "@host" is postmaster */ + if (!domainpart->len) + if case_equals(addr.s, "postmaster") + return 1; + + if (filterdom.len) { + for (i=0; is)) { + return 3; + } + i += str_len(filterdom.s + i) + 1; + } + } + + /* 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 DOMAINPATH/USER exist? */ + dirp = opendir(tmpfile.s); + if (dirp != NULL) { + closedir(dirp); + return 1; + } + + if (errno != error_noent) { + err_control(tmpfile.s); + return -1; + } + /* does DOMAINPATH/.qmail-LOCALPART exist? */ + if (! stralloc_copy(&tmpfile, domainpath)) die_nomem(); + if (! stralloc_cats(&tmpfile, "/.qmail-")) die_nomem(); + if (! stralloc_copy(&dotqm, &tmpfile)) die_nomem(); + while ( (i += str_chr(localpart->s + i, '.')) < localpart->len ) + *(localpart->s + i) = ':'; + fd = qmexists(&dotqm, localpart->s, localpart->len, 2); + + if (fd != -1) { + close (fd); + return 1; + } + if (errno != error_noent) + return fd; + + /* 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; + } else { + if (errno != error_noent) + return fd; + } + i += str_chr(localpart->s+i, '-'); + } + + /* does DOMAINPATH/.qmail-default exist ? */ + fd = qmexists(&dotqm, (char *)0, 0, 1); + if (fd == -1) { + if (errno != error_noent) + return fd; + /* 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; + } +} + +void +split_addr(stralloc *localpart, stralloc *host) +{ + unsigned int j = 0; + + j = byte_rchr(addr.s, addr.len, '@'); + if (j < addr.len) { + if (!stralloc_copyb(localpart, addr.s, j)) die_nomem(); + if (!stralloc_copyb(host, addr.s+j+1, addr.len-j-1)) die_nomem(); + case_lowerb(host->s, host->len); + } else { + if (!stralloc_copys(localpart, addr.s)) die_nomem(); + if (!stralloc_0(host)) die_nomem(); + } + + case_lowerb(localpart->s, localpart->len); +} + +int +vget_assign(const stralloc *domain, stralloc *domaindir) +{ + int fd; + unsigned int dlen; + int i; + + stralloc cdb_key = { 0 }; + + if (!stralloc_copyb(cdb_key, "!",1 )) die_nomem(); + if (!stralloc_cat(cdb_key, domain)) die_nomem(); + if (!stralloc_append(cdb_key, "-")) die_nomem(); + + /* try to open the cdb file */ + fd = open_read("users/cdb"); + if (fd == -1) { + if (errno != error_noent) + die_control(); + return 0; + } + + /* search the cdb file for our requested domain */ + i = cdb_seek(fd, cdb_key.s, cdb_key.len, &dlen); + + if ( i == 1 ) { + char *ptr; + stralloc buf; + unsigned int len; + + /* we found a matching record in the cdb file + * so next create a storage buffer, and then read it in */ + if (!stralloc_ready(&buf, dlen)) die_nomem(); + buf.len = dlen; + if (cdb_bread(fd, buf.s, buf.len) < 0) + die_control(); + if (!stralloc_0(&buf)) die_nomem(); + + /* format of cdb_buf is : realdomain.com\0uid\0gid\0path\0 */ + + /* get the real domain */ + ptr = buf.s; /* point to start of cdb_buf (ie realdomain) */ + + while(*ptr) ptr++; /* advance pointer past the realdomain */ + ptr++; /* skip over the null */ + + while(*ptr) ptr++; /* skip over the uid */ + ptr++; /* skip over the null */ + + while(*ptr) ptr++; /* skip over the gid */ + ptr++; /* skip over the null */ + /* get the domain directory */ + len = strlen(ptr); + while (*(ptr + len - 1) == '/') + --len; + if (!stralloc_ready(domaindir, len + 1)) die_nomem(); + stralloc_copy(domaindir, ptr, len); + stralloc_append(domaindir, "/"); + stralloc_0(domaindir); + + free(cdb_key.s); + free(buf.s); + i++; + } + close(fd); + return i; +} + void smtp_rcpt(arg) char *arg; { + int i = 0; + stralloc localpart = {0}; + stralloc host = {0}; + stralloc domainpath = {0}; + stralloc userpath = {0}; + if (!seenmail) { err_wantmail(); return; } if (!addrparse(arg)) { err_syntax(); return; } - if (flagbarf) { err_bmf(); return; } + + if (bmtcheck()) { + err_bmt(); + return; + } + + if (flagbarf) { + err_bmf(); + log_blocked("in badmailfrom"); + return; + } + + split_addr(&localpart, &host); + if (vget_assign(&host, &domainpath)) { + /* this domain is controlled by vpopmail, so look if local user exists */ + if (!stralloc_copy(&userpath, &domainpath)) die_nomem(); + if (!stralloc_append(&userpath, "/")) die_nomem(); + if (!stralloc_cat(&userpath, &localpart)) die_nomem(); + if (!stralloc_0(&host)) die_nomem(); + + i = user_exists(&localpart, &host, &domainpath, &userpath); + if (!i) { + err_bmt(); + return; + } + } + free(userpath.s); + free(domainpath.s); + free(localpart.s); + free(host.s); + + /* -1 here can only be caused by user_exists returning -1, + * user_exists writes the error messages itself so we don't need to care */ + if (i < 0) + return; + if (relayclient) { --addr.len; if (!stralloc_cats(&addr,relayclient)) die_nomem(); --- orig/Makefile 1998-06-15 12:53:16.000000000 +0200 +++ remote-tmp/Makefile 2004-12-11 15:18:50.000000000 +0100 @@ -1536,13 +1536,13 @@ 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 substdio.a error.a str.a \ -fs.a auto_qmail.o socket.lib +fs.a auto_qmail.o cdb.a socket.lib ./load qmail-smtpd rcpthosts.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 substdio.a error.a str.a fs.a auto_qmail.o `cat \ - socket.lib` + alloc.a substdio.a error.a str.a fs.a auto_qmail.o cdb.a \ + `cat socket.lib` qmail-smtpd.0: \ qmail-smtpd.8 @@ -1553,7 +1553,8 @@ substdio.h alloc.h auto_qmail.h control.h received.h constmap.h \ error.h ipme.h ip.h ipalloc.h ip.h gen_alloc.h ip.h qmail.h \ substdio.h str.h fmt.h scan.h byte.h case.h env.h now.h datetime.h \ -exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h +exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h cdb.h \ +uint32.h ./compile qmail-smtpd.c qmail-start: \