Fri Feb 6 2004 David Harris Apply this patch like so: cd /usr/local/src wget http://johnst.org/sw/patches/qmail-smtpd-auth-0.31.tar.gz wget http://www.qmail.org/qmail-1.03.tar.gz mkdir newstuff tar xzfC qmail-1.03.tar.gz newstuff mv newstuff/qmail-1.03 qmail-1.03-custom-smtpd-1.09 rmdir newstuff cd qmail-1.03-custom-smtpd-1.09 tar xzf ../qmail-smtpd-auth-0.31.tar.gz cp qmail-smtpd-auth-0.31/{README.auth,base64.c,base64.h} . patch < qmail-smtpd-auth-0.31/auth.patch patch -p1 < ../qmail-1.03-hivelocity-smtpd-1.09.patch make qmail-smtpd cp qmail-smtpd /var/qmail/bin/qmail-smtpd-custom-1.09 chown root:qmail /var/qmail/bin/qmail-smtpd-custom-1.09 chmod 755 /var/qmail/bin/qmail-smtpd-custom-1.09 cp /var/qmail/bin/qmail-smtpd-custom{,.old} ln -sf qmail-smtpd-custom-1.09 /var/qmail/bin/qmail-smtpd-custom Install signature files like so: cat </var/qmail/control/signatures # Windows executables seen in active virii TVqQAAMAA TVpQAAIAA # Additional windows executable signatures not yet seen in virii TVpAALQAc TVpyAXkAX TVrmAU4AA TVrhARwAk TVoFAQUAA TVoAAAQAA TVoIARMAA TVouARsAA TVrQAT8AA # .ZIPfile signature seen in SoBig.E and mydoom: #UEsDBBQAA #UEsDBAoAAA # .GIF file found in a previous Microsoft virus making the rounds. R0lGODlhaAA7APcAAP///+rp6puSp6GZrDUjUUc6Zn53mFJMdbGvvVtXh2xre8bF1x8cU4yLprOy EOF cat </var/qmail/control/signatures.zip # .ZIPfile signature seen in SoBig.E and mydoom: UEsDBBQAA UEsDBAoAAA EOF cat </var/qmail/control/signatures.zip.subjects # Subjects from mydoom.a and mydoom.b # note: matching is case-insensitive in this file Delivery Error Error hello hi Mail Delivery System Mail Transaction Failed Returned mail Server Report Status test EOF diff -rNu3 qmail-1.03-orig/Makefile qmail-1.03-hivelocity-smtpd/Makefile --- qmail-1.03-orig/Makefile Fri Feb 6 16:43:47 2004 +++ qmail-1.03-hivelocity-smtpd/Makefile Fri Feb 6 17:17:43 2004 @@ -221,9 +221,9 @@ case.a: \ makelib case_diffb.o case_diffs.o case_lowerb.o case_lowers.o \ -case_starts.o +case_starts.o case_startb.o ./makelib case.a case_diffb.o case_diffs.o case_lowerb.o \ - case_lowers.o case_starts.o + case_lowers.o case_starts.o case_startb.o case_diffb.o: \ compile case_diffb.c case.h @@ -241,6 +241,10 @@ compile case_lowers.c case.h ./compile case_lowers.c +case_startb.o: \ +compile case_startb.c case.h + ./compile case_startb.c + case_starts.o: \ compile case_starts.c case.h ./compile case_starts.c diff -rNu3 qmail-1.03-orig/case_startb.c qmail-1.03-hivelocity-smtpd/case_startb.c --- qmail-1.03-orig/case_startb.c Wed Dec 31 19:00:00 1969 +++ qmail-1.03-hivelocity-smtpd/case_startb.c Fri Feb 6 17:17:43 2004 @@ -0,0 +1,21 @@ +#include "case.h" + +int case_startb(s,len,t) +register char *s; +unsigned int len; +register char *t; +{ + register unsigned char x; + register unsigned char y; + + for (;;) { + y = *t++ - 'A'; + if (y <= 'Z' - 'A') y += 'a'; else y += 'A'; + if (!y) return 1; + if (!len) return 0; + --len; + x = *s++ - 'A'; + if (x <= 'Z' - 'A') x += 'a'; else x += 'A'; + if (x != y) return 0; + } +} diff -rNu3 qmail-1.03-orig/qmail-smtpd.c qmail-1.03-hivelocity-smtpd/qmail-smtpd.c --- qmail-1.03-orig/qmail-smtpd.c Fri Feb 6 16:43:47 2004 +++ qmail-1.03-hivelocity-smtpd/qmail-smtpd.c Fri Feb 6 17:17:43 2004 @@ -26,7 +26,7 @@ #include "wait.h" #include "fd.h" -#define AUTHCRAM +#undef AUTHCRAM #define MAXHOPS 100 unsigned int databytes = 0; int timeout = 1200; @@ -42,6 +42,9 @@ char ssoutbuf[512]; substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf); +char sserrbuf[512]; +substdio sserr = SUBSTDIO_FDBUF(write,2,sserrbuf,sizeof(sserrbuf)); + void flush() { substdio_flush(&ssout); } void out(s) char *s; { substdio_puts(&ssout,s); } @@ -53,6 +56,7 @@ void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); } void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); } +void err_bmfd() { out("553 sorry, you are not allowed to relay through this server unless your from address is a valid domain on this server (#5.7.1)\r\n"); } void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); } void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); } void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); } @@ -72,6 +76,71 @@ int err_authabrt() { out("501 auth exchange cancelled (#5.0.0)\r\n"); return -1; } int err_input() { out("501 malformed auth input (#5.5.4)\r\n"); return -1; } +char pidstr_buf[FMT_ULONG]; + +void log_prefix() +{ + if(substdio_puts(&sserr,"qmail-smtpd[") == -1) _exit(1); + pidstr_buf[fmt_ulong(pidstr_buf,getpid())] = 0; + if(substdio_puts(&sserr,pidstr_buf) == -1) _exit(1); + if(substdio_puts(&sserr,"]: ") == -1) _exit(1); +} + +/* moved up for access by log_smtpauthuser */ +char *remoteip; +char *remoteinfo; +int authd = 0; + +void log_smtpauthuser() +{ + if (remoteip) { + if(substdio_puts(&sserr,"; ip ") == -1) _exit(1); + if(substdio_puts(&sserr,remoteip) == -1) _exit(1); + } + if (authd) { + if(substdio_puts(&sserr,"; smtp-auth user <") == -1) _exit(1); + if(substdio_puts(&sserr,remoteinfo) == -1) _exit(1); + if(substdio_puts(&sserr,">") == -1) _exit(1); + } +} + +void log_bmfd(testmode_bool, mailfrom_char_ref, addr_char_ref) +int testmode_bool; +char *mailfrom_char_ref; +char *addr_char_ref; +{ + log_prefix(); + if (testmode_bool) { + if(substdio_puts(&sserr,"(test mode; no actual rejection) ") == -1) _exit(1); + } + if(substdio_puts(&sserr,"bmfd rejection for mail from <") == -1) _exit(1); + if(substdio_puts(&sserr,mailfrom_char_ref) == -1) _exit(1); + if(substdio_puts(&sserr,"> and rcpt to <") == -1) _exit(1); + if(substdio_puts(&sserr,addr_char_ref) == -1) _exit(1); + if(substdio_puts(&sserr,">") == -1) _exit(1); + log_smtpauthuser(); + if(substdio_puts(&sserr,"\n") == -1) _exit(1); + if(substdio_flush(&sserr) == -1) _exit(1); +} + +char rcptto_count_buf[FMT_ULONG]; + +void log_mail(rcptto_count_int, mailfrom_char_ref) +int rcptto_count_int; +char *mailfrom_char_ref; +{ + log_prefix(); + if(substdio_puts(&sserr,"accepted mail from <") == -1) _exit(1); + if(substdio_puts(&sserr,mailfrom_char_ref) == -1) _exit(1); + if(substdio_puts(&sserr,"> to ") == -1) _exit(1); + rcptto_count_buf[fmt_ulong(rcptto_count_buf,rcptto_count_int)] = 0; + if(substdio_puts(&sserr,rcptto_count_buf) == -1) _exit(1); + if(substdio_puts(&sserr," recipient(s)") == -1) _exit(1); + log_smtpauthuser(); + if(substdio_puts(&sserr,"\n") == -1) _exit(1); + if(substdio_flush(&sserr) == -1) _exit(1); +} + stralloc greeting = {0}; void smtp_greet(code) char *code; @@ -88,11 +157,10 @@ smtp_greet("221 "); out("\r\n"); flush(); _exit(0); } -char *remoteip; char *remotehost; -char *remoteinfo; char *local; char *relayclient; +char *bmfd_testmode; stralloc helohost = {0}; char *fakehelo; /* pointer into helohost, or 0 */ @@ -108,15 +176,42 @@ int bmfok = 0; stralloc bmf = {0}; struct constmap mapbmf; +int sigsok = 0; +stralloc sigs = {0}; +int sigzipsok = 0; +stralloc sigzips = {0}; +int sigzipsubjsok = 0; +stralloc sigzipsubjs = {0}; +struct constmap mapsigzipsubjs; + +int mapgreet_ok = 0; +stralloc mapgreet_str = {0}; +struct constmap mapgreet; void setup() { char *x; unsigned long u; + char *mapgreet_found = 0; + char *tcplocalip; if (control_init() == -1) die_control(); - if (control_rldef(&greeting,"control/smtpgreeting",1,(char *) 0) != 1) - die_control(); + + tcplocalip = env_get("TCPLOCALIP"); + mapgreet_ok = control_readfile(&mapgreet_str,"control/smtpgreeting_bylocalip",0); + if (mapgreet_ok == -1) die_control(); + if (mapgreet_ok) + if (!constmap_init(&mapgreet,mapgreet_str.s,mapgreet_str.len,1)) die_nomem(); + + if (mapgreet_ok && tcplocalip) + mapgreet_found = constmap(&mapgreet,tcplocalip,str_len(tcplocalip)); + + if (mapgreet_found) + stralloc_copys(&greeting,mapgreet_found); + else + if (control_rldef(&greeting,"control/smtpgreeting",1,(char *) 0) != 1) + die_control(); + liphostok = control_rldef(&liphost,"control/localiphost",1,(char *) 0); if (liphostok == -1) die_control(); if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control(); @@ -129,6 +224,19 @@ if (bmfok) if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem(); + sigsok = control_readfile(&sigs,"control/signatures",0); + if (sigsok == -1) die_control(); + + sigzipsok = control_readfile(&sigzips,"control/signatures.zip",0); + if (sigzipsok == -1) die_control(); + + sigzipsubjsok = control_readfile(&sigzipsubjs,"control/signatures.zip.subjects",0); + if (sigzipsubjsok == -1) die_control(); + if (sigzipsubjsok) { + case_lowerb(sigzipsubjs.s,sigzipsubjs.len); /* make matching case-insensetive */ + if (!constmap_init(&mapsigzipsubjs,sigzipsubjs.s,sigzipsubjs.len,0)) die_nomem(); + } + if (control_readint(&databytes,"control/databytes") == -1) die_control(); x = env_get("DATABYTES"); if (x) { scan_ulong(x,&u); databytes = u; } @@ -143,6 +251,7 @@ if (!remotehost) remotehost = "unknown"; remoteinfo = env_get("TCPREMOTEINFO"); relayclient = env_get("RELAYCLIENT"); + bmfd_testmode = env_get("BMFD_TESTMODE"); dohelo(remotehost); } @@ -220,6 +329,32 @@ return 0; } +int matchline(stralloc *line, stralloc *controlfile) { + int i, j; + + j = 0; + for (i = 0; i < controlfile->len; i++) if (!controlfile->s[i]) { + if (i-j < line->len) + if (!str_diffn(line->s,controlfile->s+j,i-j)) + return 1; + j = i+1; + } + return 0; +} + +int sigscheck(stralloc *line) { + if (!sigsok) return 0; + return matchline(line, &sigs); +} + +int sigzipscheck(stralloc *line, stralloc *subject) { + if (!sigzipsok) return 0; + if (!sigzipsubjsok) return 0; + if (!constmap(&mapsigzipsubjs,subject->s,subject->len)) return 0; + if (!matchline(line, &sigzips)) return 0; + return 1; +} + int addrallowed() { int r; @@ -231,8 +366,10 @@ int seenmail = 0; int flagbarf; /* defined if seenmail */ +int flagbarf_bmfd; stralloc mailfrom = {0}; stralloc rcptto = {0}; +int rcptto_count = 0; void smtp_helo(arg) char *arg; { @@ -261,6 +398,7 @@ { if (!addrparse(arg)) { err_syntax(); return; } flagbarf = bmfcheck(); + flagbarf_bmfd = relayclient && !addrallowed(); seenmail = 1; if (!stralloc_copys(&rcptto,"")) die_nomem(); if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); @@ -271,6 +409,14 @@ if (!seenmail) { err_wantmail(); return; } if (!addrparse(arg)) { err_syntax(); return; } if (flagbarf) { err_bmf(); return; } + if (flagbarf_bmfd && !addrallowed()) { + if (!bmfd_testmode) { + log_bmfd(0,mailfrom.s,addr.s); + err_bmfd(); return; + } else { + log_bmfd(1,mailfrom.s,addr.s); + } + } if (relayclient) { --addr.len; if (!stralloc_cats(&addr,relayclient)) die_nomem(); @@ -281,6 +427,7 @@ if (!stralloc_cats(&rcptto,"T")) die_nomem(); if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); + rcptto_count++; out("250 ok\r\n"); } @@ -301,9 +448,161 @@ struct qmail qqt; unsigned int bytestooverflow = 0; +int linespastheader; /* =0 after boundary is found in body, */ + /* until blank line */ +char linetype; +int flagexecutable; +int flagbadzip; +int flagqsbmf; + +stralloc line = {0}; +stralloc content = {0}; +stralloc subject = {0}; +stralloc boundary = {0}; +int boundary_start; + +/* + +def put(ch): + line.append(ch) + if ch == '\n': + if linepastheader == 0: + if line.startswith('Content-Type:'): + content = + + put() puts characters into the queue. We remember those characters + and form them into a line. When we get a newline, we examine the + line. If we're currently in a header (0 linespastheader), we look + for Content-Type. If we're at the newline that ends a header, we + look to see if the content is multipart. If it is, then we push + the current boundary, remember the boundary, otherwise we set the + boundary to the empty string. Set the linespastheader to 1. When + linespastheader is 1, and the boundary is empty, scan the line for + signatures. If the boundary is non-empty, look for a match against + the boundary. If it matches and is followed by two dashes, pop the + boundary, otherwise set linespastheader to 0. +*/ + void put(ch) char *ch; { + char *cp, *cpstart, *cpafter; + unsigned int len; + + if (line.len < 1024) + if (!stralloc_catb(&line,ch,1)) die_nomem(); + + if (*ch == '\n') { + if (linespastheader == 0) { + if (line.len == 1) { + linespastheader = 1; + if (flagqsbmf) { + flagqsbmf = 0; + linespastheader = 0; + } + if (content.len) { /* MIME header */ + cp = content.s; + len = content.len; + while (len && (*cp == ' ' || *cp == '\t')) { ++cp; --len; } + cpstart = cp; + if (len && *cp == '"') { /* might be commented */ + ++cp; --len; cpstart = cp; + while (len && *cp != '"') { ++cp; --len; } + } else { + while (len && *cp != ' ' && *cp != '\t' && *cp != ';') { + ++cp; --len; + } + } + if (!case_diffb(cpstart,cp-cpstart,"message/rfc822")) + linespastheader = 0; + + cpafter = content.s+content.len; + while((cp += byte_chr(cp,cpafter-cp,';')) != cpafter) { + ++cp; + while (cp < cpafter && (*cp == ' ' || *cp == '\t')) ++cp; + if (case_startb(cp,cpafter - cp,"boundary=")) { + cp += 9; /* after boundary= */ + if (cp < cpafter && *cp == '"') { + ++cp; + cpstart = cp; + while (cp < cpafter && *cp != '"') ++cp; + } else { + cpstart = cp; + while (cp < cpafter && + *cp != ';' && *cp != ' ' && *cp != '\t') ++cp; + } + /* push the current boundary. Append a null and remember start. */ + if (!stralloc_0(&boundary)) die_nomem(); + boundary_start = boundary.len; + if (!stralloc_cats(&boundary,"--")) die_nomem(); + if (!stralloc_catb(&boundary,cpstart,cp-cpstart)) + die_nomem(); + break; + } + } + } + } else { /* non-blank header line */ + if ((*line.s == ' ' || *line.s == '\t')) { + switch(linetype) { + case 'C': if (!stralloc_catb(&content,line.s,line.len-1)) die_nomem(); break; + default: break; + } + } else { + if (case_startb(line.s,line.len,"content-type:")) { + if (!stralloc_copyb(&content,line.s+13,line.len-14)) die_nomem(); + linetype = 'C'; + } else if (case_startb(line.s,line.len,"subject:")) { + cp = line.s+8; + len = line.len-9; + while (len && (*cp == ' ' || *cp == '\t')) { ++cp; --len; } + if (!stralloc_copyb(&subject,cp,len)) die_nomem(); + /* this subject line parser is broken in that it does not handle heards that + continue onto new lines, but it will do for now */ + case_lowerb(subject.s,subject.len); /* make matching case-insensetive */ + } else { + linetype = ' '; + } + } + } + } else { /* non-header line */ + if (boundary.len-boundary_start && *line.s == '-' && line.len > (boundary.len-boundary_start) && + !str_diffn(line.s,boundary.s+boundary_start,boundary.len-boundary_start)) { /* matches a boundary */ + if (line.len > boundary.len-boundary_start + 2 && + line.s[boundary.len-boundary_start+0] == '-' && + line.s[boundary.len-boundary_start+1] == '-') { + /* XXXX - pop the boundary here */ + if (boundary_start) boundary.len = boundary_start - 1; + boundary_start = boundary.len; + while(boundary_start--) if (!boundary.s[boundary_start]) break; + boundary_start++; + linespastheader = 2; + } else { + linespastheader = 0; + } + } else if (linespastheader == 1) { /* first line -- match a signature? */ + if (/*mailfrom.s[0] == '\0' && */ + str_start(line.s,"Hi. This is the ")) + flagqsbmf = 1; + else if (/*mailfrom.s[0] == '\0' && */ + str_start(line.s,"This message was created automatically by mail delivery software")) + flagqsbmf = 1; + else if (sigscheck(&line)) { + flagexecutable = 1; + qmail_fail(&qqt); + } + else if (sigzipscheck(&line, &subject)) { + flagbadzip = 1; + qmail_fail(&qqt); + } + linespastheader = 2; + } + if (flagqsbmf && str_start(line.s,"---")) { + linespastheader = 0; + } + } + line.len = 0; + } + if (bytestooverflow) if (!--bytestooverflow) qmail_fail(&qqt); @@ -383,6 +682,7 @@ accept_buf[fmt_ulong(accept_buf,qp)] = 0; out(accept_buf); out("\r\n"); + log_mail(rcptto_count, mailfrom.s); } void smtp_data() { @@ -394,6 +694,15 @@ if (!rcptto.len) { err_wantrcpt(); return; } seenmail = 0; if (databytes) bytestooverflow = databytes + 1; + boundary.len = 0; + boundary_start = 0; + content.len = 0; + subject.len = 0; + linespastheader = 0; + flagexecutable = 0; + flagbadzip = 0; + flagqsbmf = 0; + linetype = ' '; if (qmail_open(&qqt) == -1) { err_qqt(); return; } qp = qmail_qp(&qqt); out("354 go ahead\r\n"); @@ -409,6 +718,8 @@ if (!*qqx) { acceptmessage(qp); return; } if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; } if (databytes) if (!bytestooverflow) { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); return; } + if (flagexecutable) { out("552 we don't accept email with executable content (#5.3.4)\r\n"); return; } + if (flagbadzip) { out("552 we don't accept zip files with this subject; looks like a virus (#5.3.4)\r\n"); return; } if (*qqx == 'D') out("554 "); else out("451 "); out(qqx + 1); out("\r\n"); @@ -425,7 +736,6 @@ char **childargs; substdio ssup; char upbuf[128]; -int authd = 0; int authgetl(void) { int i; @@ -458,15 +768,20 @@ if (!stralloc_0(&pass)) die_nomem(); if (!stralloc_0(&resp)) die_nomem(); - if (fd_copy(2,1) == -1) return err_pipe(); - close(3); if (pipe(pi) == -1) return err_pipe(); - if (pi[0] != 3) return err_pipe(); switch(child = fork()) { case -1: return err_fork(); case 0: + if (pi[0] != 3) { + if (fd_copy(3,pi[0]) == -1) _exit(1); + close(pi[0]); + } close(pi[1]); + close(0); + close(1); + open_read("/dev/null"); /* ignore errors */ + open_write("/dev/null"); /* ignore errors */ sig_pipedefault(); execvp(*childargs, childargs); _exit(1);