This patch changes qmail-smtpd so that it parses incoming emails. It looks at the first line of MIME attachments to see if they're found in control/signatures. This catches nearly all current Microsoft viruses. Apply this patch like so: cd /usr/local/src/qmail-1.03 wget http://qmail.org/qmail-smtpd-viruscan-1.2.patch patch /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: UEsDBBQAA # .GIF file found in the current Microsoft virus making the rounds. R0lGODlhaAA7APcAAP///+rp6puSp6GZrDUjUUc6Zn53mFJMdbGvvVtXh2xre8bF1x8cU4yLprOy EOF diff -u orig/Makefile ./Makefile --- orig/Makefile 1998-06-15 06:53:16.000000000 -0400 +++ ./Makefile 2003-03-10 10:49:44.000000000 -0500 @@ -217,9 +217,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 @@ -237,6 +237,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 -u orig/qmail-smtpd.c ./qmail-smtpd.c --- orig/qmail-smtpd.c 1998-06-15 06:53:16.000000000 -0400 +++ ./qmail-smtpd.c 2003-09-25 00:59:01.000000000 -0400 @@ -96,6 +96,8 @@ int bmfok = 0; stralloc bmf = {0}; struct constmap mapbmf; +int sigsok = 0; +stralloc sigs = {0}; void setup() { @@ -117,6 +119,9 @@ 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(); + if (control_readint(&databytes,"control/databytes") == -1) die_control(); x = env_get("DATABYTES"); if (x) { scan_ulong(x,&u); databytes = u; } @@ -208,6 +213,19 @@ return 0; } +int sigscheck(stralloc *line) { + int i, j; + + j = 0; + for (i = 0; i < sigs.len; i++) if (!sigs.s[i]) { + if (i-j < line->len) + if (!str_diffn(line->s,sigs.s+j,i-j)) + return 1; + j = i+1; + } + return 0; +} + int addrallowed() { int r; @@ -281,9 +299,131 @@ struct qmail qqt; unsigned int bytestooverflow = 0; +int linespastheader; /* =0 after boundary is found in body, */ + /* until blank line */ +char linetype; +int flagexecutable; + +stralloc line = {0}; +stralloc content = {0}; +stralloc boundary = {0}; +int boundary_start; + +/* + +def put(ch): + line.append(ch) + if ch == '\n': + if linepastheader == 0: + if line.beginswith('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 (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; + } + } + + 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 { + 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 (sigscheck(&line)) { + flagexecutable = 1; + qmail_fail(&qqt); + } + linespastheader = 2; + } + } + line.len = 0; + } + if (bytestooverflow) if (!--bytestooverflow) qmail_fail(&qqt); @@ -374,6 +514,12 @@ if (!rcptto.len) { err_wantrcpt(); return; } seenmail = 0; if (databytes) bytestooverflow = databytes + 1; + boundary.len = 0; + boundary_start = 0; + content.len = 0; + linespastheader = 0; + flagexecutable = 0; + linetype = ' '; if (qmail_open(&qqt) == -1) { err_qqt(); return; } qp = qmail_qp(&qqt); out("354 go ahead\r\n"); @@ -389,6 +535,7 @@ 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 (*qqx == 'D') out("554 "); else out("451 "); out(qqx + 1); out("\r\n"); --- orig/case_startb.c 2003-09-25 01:01:48.000000000 -0400 +++ case_startb.c 2003-03-06 17:53:59.000000000 -0500 @@ -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; + } +} .