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 <qmail-smtpd-viruscan-1.2.patch


# create the signature file like this:

cat <<EOF >/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;
+  }
+}
