diff --git Makefile Makefile
index 0f0e31a..f50929e 100644
--- Makefile
+++ Makefile
@@ -136,6 +136,11 @@ auto_usera.o: \
 compile auto_usera.c
 	./compile auto_usera.c
 
+badrcptto.o: \
+compile badrcptto.c byte.h constmap.h control.h env.h fmt.h str.h \
+stralloc.h strerr.h
+	./compile badrcptto.c
+
 binm1: \
 binm1.sh conf-qmail
 	cat binm1.sh \
@@ -237,6 +242,10 @@ case_lowers.o: \
 compile case_lowers.c case.h
 	./compile case_lowers.c
 
+case_startb2.o: \
+compile case_startb2.c case.h
+	./compile case_startb2.c
+
 case_starts.o: \
 compile case_starts.c case.h
 	./compile case_starts.c
@@ -1331,6 +1340,44 @@ fmt.h str.h scan.h open.h error.h getln.h auto_break.h auto_qmail.h \
 auto_usera.h
 	./compile qmail-pw2u.c
 
+qmail-qfilter-ofmipd-queue: \
+load qmail-qfilter-ofmipd-queue.o control.o error.a fs.a getln.a \
+open.a stralloc.a substdio.a str.a alloc.a wait.a
+	./load qmail-qfilter-ofmipd-queue control.o error.a fs.a getln.a \
+	open.a stralloc.a substdio.a str.a alloc.a wait.a
+
+qmail-qfilter-ofmipd-queue.c: \
+qmail-qfilter-smtpd-queue.c
+	cat qmail-qfilter-smtpd-queue.c \
+	| sed s}control/smtpfilters}control/ofmipfilters}g \
+	> qmail-qfilter-ofmipd-queue.c
+
+qmail-qfilter-ofmipd-queue.o: \
+compile qmail-qfilter-ofmipd-queue.c control.h stralloc.h wait.h
+	./compile qmail-qfilter-ofmipd-queue.c
+
+qmail-qfilter-smtpd-queue: \
+load qmail-qfilter-smtpd-queue.o control.o error.a fs.a getln.a \
+open.a stralloc.a substdio.a str.a alloc.a wait.a
+	./load qmail-qfilter-smtpd-queue control.o error.a fs.a getln.a \
+	open.a stralloc.a substdio.a str.a alloc.a wait.a
+
+qmail-qfilter-smtpd-queue.o: \
+compile qmail-qfilter-smtpd-queue.c control.h stralloc.h wait.h
+	./compile qmail-qfilter-smtpd-queue.c
+
+qmail-qfilter-viruscan: \
+load qmail-qfilter-viruscan.o viruscan.o case_startb2.o control.o \
+env.a error.a case.a fs.a getln.a open.a stralloc.a substdio.a \
+str.a alloc.a
+	./load qmail-qfilter-viruscan viruscan.o case_startb2.o control.o \
+	env.a error.a case.a fs.a getln.a open.a stralloc.a substdio.a \
+	str.a alloc.a
+
+qmail-qfilter-viruscan.o: \
+compile qmail-qfilter-viruscan.c viruscan.h
+	./compile qmail-qfilter-viruscan.c
+
 qmail-qmqpc: \
 load qmail-qmqpc.o slurpclose.o timeoutread.o timeoutwrite.o \
 timeoutconn.o ip.o control.o auto_qmail.o sig.a ndelay.a open.a \
@@ -1437,6 +1484,54 @@ alloc.h substdio.h datetime.h now.h datetime.h triggerpull.h extra.h \
 auto_qmail.h auto_uids.h date822fmt.h fmtqfn.h
 	./compile qmail-queue.c
 
+qmail-rcptcheck: \
+load qmail-rcptcheck.o control.o env.a error.a fs.a getln.a open.a \
+stralloc.a substdio.a str.a alloc.a wait.a
+	./load qmail-rcptcheck control.o env.a error.a fs.a getln.a open.a \
+	stralloc.a substdio.a str.a alloc.a wait.a
+
+qmail-rcptcheck-badrcptto: \
+load qmail-rcptcheck-badrcptto.o badrcptto.o control.o constmap.o \
+case.a env.a fs.a getln.a open.a stralloc.a strerr.a substdio.a \
+error.a str.a alloc.a
+	./load qmail-rcptcheck-badrcptto badrcptto.o control.o constmap.o \
+	case.a env.a fs.a getln.a open.a stralloc.a strerr.a substdio.a \
+	error.a str.a alloc.a
+
+qmail-rcptcheck-badrcptto.o: \
+compile qmail-rcptcheck-badrcptto.c badrcptto.h env.h
+	./compile qmail-rcptcheck-badrcptto.c
+
+qmail-rcptcheck-qregex: \
+load qmail-rcptcheck-qregex.o qregexrcptto.o control.o env.a \
+qregex.o stralloc.a strerr.a error.a getln.a open.a fs.a \
+substdio.a str.a alloc.a
+	./load qmail-rcptcheck-qregex qregexrcptto.o control.o env.a \
+	qregex.o stralloc.a strerr.a error.a getln.a open.a fs.a \
+	substdio.a str.a alloc.a
+
+qmail-rcptcheck-qregex.o: \
+compile qmail-rcptcheck-qregex.c env.h qregexrcptto.h
+	./compile qmail-rcptcheck-qregex.c
+
+qmail-rcptcheck-realrcptto: \
+load qmail-rcptcheck-realrcptto.o realrcptto.o auto_break.o \
+auto_usera.o control.o constmap.o timeoutwrite.o \
+case.a cdb.a env.a error.a fs.a getln.a open.a str.a stralloc.a \
+alloc.a substdio.a
+	./load qmail-rcptcheck-realrcptto realrcptto.o auto_break.o \
+	auto_usera.o control.o constmap.o timeoutwrite.o \
+	case.a cdb.a env.a error.a fs.a getln.a open.a str.a stralloc.a \
+	alloc.a substdio.a
+
+qmail-rcptcheck-realrcptto.o: \
+compile qmail-rcptcheck-realrcptto.c env.h realrcptto.h
+	./compile qmail-rcptcheck-realrcptto.c
+
+qmail-rcptcheck.o: \
+compile qmail-rcptcheck.c control.h env.h stralloc.h substdio.h wait.h
+	./compile qmail-rcptcheck.c
+
 qmail-remote: \
 load qmail-remote.o control.o constmap.o timeoutread.o timeoutwrite.o \
 timeoutconn.o tcpto.o now.o dns.o ip.o ipalloc.o ipme.o quote.o \
@@ -1655,6 +1750,14 @@ gen_alloc.h error.h gen_alloc.h gen_allocdefs.h headerbody.h exit.h \
 open.h quote.h qmail.h substdio.h
 	./compile qreceipt.c
 
+qregex.o: \
+compile qregex.c qregex.h
+	./compile qregex.c
+
+qregexrcptto.o: \
+compile qregexrcptto.c control.h env.h qregex.h stralloc.h strerr.h
+	./compile qregexrcptto.c
+
 qsmhook: \
 load qsmhook.o sig.a case.a fd.a wait.a getopt.a env.a stralloc.a \
 alloc.a substdio.a error.a str.a
@@ -1686,6 +1789,11 @@ compile readsubdir.c readsubdir.h direntry.h fmt.h scan.h str.h \
 auto_split.h
 	./compile readsubdir.c
 
+realrcptto.o: \
+compile realrcptto.c auto_break.h auto_usera.h byte.h case.h cdb.h \
+constmap.h error.h fmt.h open.h str.h stralloc.h uint32.h
+	./compile realrcptto.c
+
 received.o: \
 compile received.c fmt.h qmail.h substdio.h now.h datetime.h \
 datetime.h date822fmt.h received.h
@@ -1696,6 +1804,12 @@ compile remoteinfo.c byte.h substdio.h ip.h fmt.h timeoutconn.h \
 timeoutread.h timeoutwrite.h remoteinfo.h
 	./compile remoteinfo.c
 
+rejectutils: \
+qmail-qfilter-ofmipd-queue qmail-qfilter-smtpd-queue \
+qmail-qfilter-viruscan \
+qmail-rcptcheck \
+qmail-rcptcheck-badrcptto qmail-rcptcheck-qregex qmail-rcptcheck-realrcptto
+
 scan_8long.o: \
 compile scan_8long.c scan.h
 	./compile scan_8long.c
@@ -2128,6 +2242,10 @@ tryulong32.c compile load uint32.h1 uint32.h2
 	&& cat uint32.h2 || cat uint32.h1 ) > uint32.h
 	rm -f tryulong32.o tryulong32
 
+viruscan.o: \
+compile viruscan.c byte.h case.h control.h env.h fmt.h getln.h str.h stralloc.h substdio.h
+	./compile viruscan.c
+
 wait.a: \
 makelib wait_pid.o wait_nohang.o
 	./makelib wait.a wait_pid.o wait_nohang.o
diff --git TARGETS TARGETS
index facdad7..4a96f38 100644
--- TARGETS
+++ TARGETS
@@ -385,3 +385,24 @@ forgeries.0
 man
 setup
 check
+badrcptto.o
+case_startb2.o
+qmail-qfilter-ofmipd-queue
+qmail-qfilter-ofmipd-queue.c
+qmail-qfilter-ofmipd-queue.o
+qmail-qfilter-smtpd-queue
+qmail-qfilter-smtpd-queue.o
+qmail-qfilter-viruscan
+qmail-qfilter-viruscan.o
+qmail-rcptcheck
+qmail-rcptcheck.o
+qmail-rcptcheck-badrcptto
+qmail-rcptcheck-badrcptto.o
+qmail-rcptcheck-qregex
+qmail-rcptcheck-qregex.o
+qmail-rcptcheck-realrcptto
+qmail-rcptcheck-realrcptto.o
+qregex.o
+qregexrcptto.o
+realrcptto.o
+viruscan.o
diff --git badrcptto.c badrcptto.c
new file mode 100644
index 0000000..40a06f9
--- /dev/null
+++ badrcptto.c
@@ -0,0 +1,67 @@
+#include <unistd.h>
+#include "byte.h"
+#include "constmap.h"
+#include "control.h"
+#include "env.h"
+#include "fmt.h"
+#include "str.h"
+#include "stralloc.h"
+#include "strerr.h"
+
+extern void die_control();
+extern void die_nomem();
+
+static void _badrcptto_log_rejection(char *recipient)
+{
+  char smtpdpid[32];
+  char *remoteip = env_get("TCPREMOTEIP");
+  if (!remoteip) remoteip = "unknown";
+  str_copy(smtpdpid + fmt_ulong(smtpdpid,getppid())," ");
+  strerr_warn5("rcptcheck: badrcptto ",smtpdpid,remoteip," ",recipient,0);
+}
+
+static int _badrcptto_reject_exact_address(struct constmap map, stralloc address)
+{
+  return (1 && constmap(&map,address.s,address.len - 1));
+}
+
+static int _badrcptto_reject_whole_domain(struct constmap map, stralloc address)
+{
+  /* why not just comment out the domain in control/rcpthosts? */
+  int j = byte_rchr(address.s,address.len,'@');
+  return ((j < address.len) && (constmap(&map,address.s + j,address.len - j - 1)));
+}
+
+static int _badrcptto_reject_string(char *string)
+{
+  stralloc addr = {0};
+  stralloc brt = {0};
+  struct constmap mapbrt;
+  int brtok = control_readfile(&brt,"control/badrcptto",0);
+  if (brtok == -1) die_control();
+  if (!brtok) return 0;
+  if (!constmap_init(&mapbrt,brt.s,brt.len,0)) die_nomem();
+
+  if (!stralloc_copys(&addr,string)) die_nomem();
+  if (!stralloc_0(&addr)) die_nomem();
+
+  if (_badrcptto_reject_exact_address(mapbrt,addr)) {
+    _badrcptto_log_rejection(addr.s);
+    return 1;
+  }
+
+  if (_badrcptto_reject_whole_domain(mapbrt,addr)) {
+    _badrcptto_log_rejection(addr.s);
+    return 1;
+  }
+
+  return 0;
+}
+
+int badrcptto_reject_recipient(char *recipient)
+{
+  if (env_get("RELAYCLIENT"))
+    return 0;
+
+  return _badrcptto_reject_string(recipient);
+}
diff --git badrcptto.h badrcptto.h
new file mode 100644
index 0000000..d4ced30
--- /dev/null
+++ badrcptto.h
@@ -0,0 +1,6 @@
+#ifndef BADRCPTTO_H
+#define BADRCPTTO_H
+
+int badrcptto_reject_recipient(char *);
+
+#endif
diff --git case_startb2.c case_startb2.c
new file mode 100644
index 0000000..ee88efe
--- /dev/null
+++ case_startb2.c
@@ -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 --git qmail-qfilter-smtpd-queue.c qmail-qfilter-smtpd-queue.c
new file mode 100755
index 0000000..e642184
--- /dev/null
+++ qmail-qfilter-smtpd-queue.c
@@ -0,0 +1,61 @@
+#include <unistd.h>
+#include "alloc.h"
+#include "control.h"
+#include "str.h"
+#include "stralloc.h"
+#include "wait.h"
+
+void unable_to_allocate() { _exit(51); }
+void unable_to_execute()  { _exit(71); }
+void unable_to_verify()   { _exit(55); }
+
+static int num_lines(stralloc lines)
+{
+  int num = 0;
+  int i;
+  for (i = 0; i < lines.len; i++) if (lines.s[i] == '\0') num++;
+  return num;
+}
+
+static void run_qmail_qfilter(stralloc filters)
+{
+  int num_args;
+  char **args;
+  int arg;
+  int linestart;
+  int i;
+
+  num_args = 2 * num_lines(filters);
+  if (num_args == 0) num_args = 1;
+  if (!(args = (char **) alloc(sizeof(char *) * num_args)))
+    unable_to_allocate();
+
+  args[0] = "bin/qmail-qfilter";
+
+  arg = 0;
+  linestart = 0;
+  for (i = 0; i < filters.len; i++) {
+    if (filters.s[i] == '\0') {
+      stralloc filter = {0};
+      stralloc_copys(&filter, filters.s + linestart);
+      stralloc_0(&filter);
+      args[++arg] = filter.s;
+      args[++arg] = "--";
+      linestart = i + 1;
+    }
+  }
+  args[num_args] = 0;
+
+  execv(*args, args);
+  unable_to_execute();
+}
+
+int main(int argc, char **argv)
+{
+  stralloc filters = {0};
+
+  if (control_readfile(&filters,"control/smtpfilters",0) == -1)
+    unable_to_verify();
+
+  run_qmail_qfilter(filters);
+}
diff --git qmail-qfilter-viruscan.c qmail-qfilter-viruscan.c
new file mode 100644
index 0000000..47da307
--- /dev/null
+++ qmail-qfilter-viruscan.c
@@ -0,0 +1,16 @@
+#include <unistd.h>
+#include "viruscan.h"
+
+static void accept_message() { _exit( 0); }
+static void reject_message() { _exit(31); }
+
+void die_control()           { _exit(55); }
+void die_nomem()             { _exit(51); }
+
+int main(void)
+{
+  if (viruscan_reject_attachment())
+    reject_message();
+
+  accept_message();
+}
diff --git qmail-rcptcheck-badrcptto.c qmail-rcptcheck-badrcptto.c
new file mode 100644
index 0000000..dbdd202
--- /dev/null
+++ qmail-rcptcheck-badrcptto.c
@@ -0,0 +1,23 @@
+#include <unistd.h>
+#include "badrcptto.h"
+#include "env.h"
+
+void accept_recipient() { _exit(  0); }
+void reject_recipient() { _exit(100); }
+void unable_to_verify() { _exit(111); }
+
+void die_control() { unable_to_verify(); }
+void die_nomem()   { unable_to_verify(); }
+
+int main(void)
+{
+  char *recipient = env_get("RECIPIENT");
+
+  if (!recipient)
+    unable_to_verify();
+
+  if (badrcptto_reject_recipient(recipient))
+    reject_recipient();
+
+  accept_recipient();
+}
diff --git qmail-rcptcheck-qregex.c qmail-rcptcheck-qregex.c
new file mode 100644
index 0000000..55dd653
--- /dev/null
+++ qmail-rcptcheck-qregex.c
@@ -0,0 +1,29 @@
+#include <unistd.h>
+#include "env.h"
+#include "qregexrcptto.h"
+
+void accept_recipient() { _exit(  0); }
+void reject_recipient() { _exit(100); }
+void unable_to_verify() { _exit(111); }
+
+void die_control() { unable_to_verify(); }
+void die_nomem()   { unable_to_verify(); }
+
+int main(void)
+{
+  char *helohost  = env_get("HELOHOST");
+  char *sender    = env_get("SENDER");
+  char *recipient = env_get("RECIPIENT");
+
+  if (!sender || !recipient)
+    unable_to_verify();
+
+  if (helohost && qregex_reject_helohost(helohost))
+    reject_recipient();
+  if (qregex_reject_sender(sender))
+    reject_recipient();
+  if (qregex_reject_recipient(recipient))
+    reject_recipient();
+
+  accept_recipient();
+}
diff --git qmail-rcptcheck-realrcptto.c qmail-rcptcheck-realrcptto.c
new file mode 100644
index 0000000..54810ae
--- /dev/null
+++ qmail-rcptcheck-realrcptto.c
@@ -0,0 +1,27 @@
+#include <unistd.h>
+#include "env.h"
+#include "realrcptto.h"
+
+void accept_recipient() { _exit(  0); }
+void reject_recipient() { _exit(100); }
+void unable_to_verify() { _exit(111); }
+
+void die_cdb()     { unable_to_verify(); }
+void die_control() { unable_to_verify(); }
+void die_nomem()   { unable_to_verify(); }
+void die_sys()     { unable_to_verify(); }
+
+int main(void)
+{
+  char *recipient = env_get("RECIPIENT");
+
+  if (!recipient)
+    unable_to_verify();
+
+  realrcptto_init();
+  realrcptto_start();
+  if (!realrcptto(recipient))
+    reject_recipient();
+
+  accept_recipient();
+}
diff --git qmail-rcptcheck.c qmail-rcptcheck.c
new file mode 100755
index 0000000..e88c894
--- /dev/null
+++ qmail-rcptcheck.c
@@ -0,0 +1,167 @@
+#include <unistd.h>
+#include "control.h"
+#include "env.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "wait.h"
+
+static char outbuf[128];
+static struct substdio ssout = SUBSTDIO_FDBUF(write,1,outbuf,sizeof outbuf);
+static char errbuf[128];
+static struct substdio sserr = SUBSTDIO_FDBUF(write,2,errbuf,sizeof errbuf);
+
+struct rcptcheck_api {
+  char *(*get_sender)();
+  char *(*get_recipient)();
+  void (*accept_recipient)();
+  void (*reject_recipient)();
+  void (*unable_to_verify)();
+  void (*unable_to_execute)();
+} rcptcheck;
+
+static char *rcptcheck_get_sender() {
+  return env_get("SENDER");
+}
+
+static char *rcptcheck_get_recipient() {
+  return env_get("RECIPIENT");
+}
+
+static void rcptcheck_accept_recipient() {
+  _exit(0);
+}
+
+static void rcptcheck_reject_recipient() {
+  _exit(100);
+}
+
+static void rcptcheck_unable_to_verify() {
+  _exit(111);
+}
+
+static void rcptcheck_unable_to_execute() {
+  _exit(120);
+}
+
+static void run_rcptchecks_under_rcptcheck() {
+  rcptcheck.get_sender        = rcptcheck_get_sender;
+  rcptcheck.get_recipient     = rcptcheck_get_recipient;
+  rcptcheck.accept_recipient  = rcptcheck_accept_recipient;
+  rcptcheck.reject_recipient  = rcptcheck_reject_recipient;
+  rcptcheck.unable_to_verify  = rcptcheck_unable_to_verify;
+  rcptcheck.unable_to_execute = rcptcheck_unable_to_execute;
+}
+
+static void putsdie(substdio *ss,char *string) {
+  substdio_puts(ss,string);
+  substdio_flush(ss);
+  _exit(0);
+}
+
+static char *spp_get_sender() {
+  return env_get("SMTPMAILFROM");
+}
+
+static char *spp_get_recipient() {
+  return env_get("SMTPRCPTTO");
+}
+
+static void spp_accept_recipient() {
+  putsdie(&ssout,"");
+}
+
+static void spp_reject_recipient() {
+  putsdie(&ssout,"E553 sorry, no mailbox here by that name. (#5.1.1)\n");
+}
+
+static void spp_unable_to_verify() {
+  putsdie(&ssout,"R421 unable to verify recipient (#4.3.0)\n");
+}
+
+static void spp_unable_to_execute() {
+  putsdie(&ssout,"R421 unable to execute recipient check (#4.3.0)\n");
+}
+
+static void run_rcptchecks_under_spp() {
+  rcptcheck.get_sender        = spp_get_sender;
+  rcptcheck.get_recipient     = spp_get_recipient;
+  rcptcheck.accept_recipient  = spp_accept_recipient;
+  rcptcheck.reject_recipient  = spp_reject_recipient;
+  rcptcheck.unable_to_verify  = spp_unable_to_verify;
+  rcptcheck.unable_to_execute = spp_unable_to_execute;
+}
+
+static void run_rcptcheck(char *program)
+{
+  char *rcptcheck_program[2] = { program, 0 };
+  int pid;
+  int wstat;
+
+  switch(pid = fork()) {
+    case -1:
+      rcptcheck.unable_to_execute();
+    case 0:
+      execv(*rcptcheck_program,rcptcheck_program);
+      rcptcheck.unable_to_execute();
+  }
+
+  if (wait_pid(&wstat,pid) == -1)
+    rcptcheck.unable_to_execute();
+  if (wait_crashed(wstat))
+    rcptcheck.unable_to_execute();
+
+  switch(wait_exitcode(wstat)) {
+    case 100: rcptcheck.reject_recipient();
+    case 111: rcptcheck.unable_to_verify();
+    case 120: rcptcheck.unable_to_execute();
+    default:  return;
+  }
+}
+
+static int looks_like_spp() {
+  char *x = env_get("SMTPRCPTTO");
+  return (x != 0);
+}
+
+static void set_environment(char *sender, char *recipient) {
+  substdio_puts(&sserr,"qmail-rcptcheck: from ");
+
+  if (sender) {
+    substdio_puts(&sserr,sender);
+    if (!env_put2("SENDER", sender)) rcptcheck.unable_to_verify();
+  }
+
+  substdio_puts(&sserr," to ");
+
+  if (recipient) {
+    substdio_puts(&sserr,recipient);
+    if (!env_put2("RECIPIENT", recipient)) rcptcheck.unable_to_verify();
+  }
+
+  substdio_puts(&sserr,"\n");
+  substdio_flush(&sserr);
+}
+
+int main(void)
+{
+  stralloc rcptchecks = {0};
+  int linestart;
+  int pos;
+
+  if (looks_like_spp()) run_rcptchecks_under_spp();
+  else run_rcptchecks_under_rcptcheck();
+
+  set_environment(rcptcheck.get_sender(), rcptcheck.get_recipient());
+
+  if (control_readfile(&rcptchecks,"control/rcptchecks",0) == -1)
+    rcptcheck.unable_to_verify();
+
+  for (linestart = 0, pos = 0; pos < rcptchecks.len; pos++) {
+    if (rcptchecks.s[pos] == '\0') {
+      run_rcptcheck(rcptchecks.s + linestart);
+      linestart = pos + 1;
+    }
+  }
+
+  rcptcheck.accept_recipient();
+}
diff --git qregex.c qregex.c
new file mode 100644
index 0000000..2a758c7
--- /dev/null
+++ qregex.c
@@ -0,0 +1,57 @@
+/*
+ * qregex (v2)
+ * $Id: qregex.c,v 2.1 2001/12/28 07:05:21 evan Exp $
+ *
+ * Author  : Evan Borgstrom (evan at unixpimps dot org)
+ * Created : 2001/12/14 23:08:16
+ * Modified: $Date: 2001/12/28 07:05:21 $
+ * Revision: $Revision: 2.1 $
+ *
+ * Do POSIX regex matching on addresses for anti-relay / spam control.
+ * It logs to the maillog
+ * See the qregex-readme file included with this tarball.
+ * If you didn't get this file in a tarball please see the following URL:
+ *  http://www.unixpimps.org/software/qregex
+ *
+ * qregex.c is released under a BSD style copyright.
+ * See http://www.unixpimps.org/software/qregex/copyright.html
+ *
+ * Note: this revision follows the coding guidelines set forth by the rest of
+ *       the qmail code and that described at the following URL.
+ *       http://cr.yp.to/qmail/guarantee.html
+ *
+ */
+
+#include <sys/types.h>
+#include <regex.h>
+#include "qregex.h"
+
+#define REGCOMP(X,Y)    regcomp(&X, Y, REG_EXTENDED|REG_ICASE)
+#define REGEXEC(X,Y)    regexec(&X, Y, (size_t)0, (regmatch_t *)0, (int)0)
+
+int matchregex(char *text, char *regex) {
+  regex_t qreg;
+  int retval = 0;
+
+
+  /* build the regex */
+  if ((retval = REGCOMP(qreg, regex)) != 0) {
+    regfree(&qreg);
+    return(-retval);
+  }
+
+  /* execute the regex */
+  if ((retval = REGEXEC(qreg, text)) != 0) {
+    /* did we just not match anything? */
+    if (retval == REG_NOMATCH) {
+      regfree(&qreg);
+      return(0);
+    }
+    regfree(&qreg);
+    return(-retval);
+  }
+
+  /* signal the match */
+  regfree(&qreg);
+  return(1);
+}
diff --git qregex.h qregex.h
new file mode 100644
index 0000000..e333a2d
--- /dev/null
+++ qregex.h
@@ -0,0 +1,5 @@
+/* simple header file for the matchregex prototype */
+#ifndef _QREGEX_H_
+#define _QREGEX_H_
+int matchregex(char *text, char *regex);
+#endif
diff --git qregexrcptto.c qregexrcptto.c
new file mode 100644
index 0000000..7af7a57
--- /dev/null
+++ qregexrcptto.c
@@ -0,0 +1,126 @@
+#include <unistd.h>
+#include "control.h"
+#include "env.h"
+#include "fmt.h"
+#include "qregex.h"
+#include "str.h"
+#include "stralloc.h"
+#include "strerr.h"
+
+extern void die_control();
+extern void die_nomem();
+
+static stralloc matchedregex = {0};
+
+static int _qregex_at_least_one_match(stralloc haystack, char *needle)
+{
+  int i = 0;
+  int j = 0;
+  int x = 0;
+  int negate = 0;
+  stralloc bmb = {0};
+  stralloc curregex = {0};
+
+  if (!stralloc_copy(&bmb,&haystack)) die_nomem();
+
+  while (j < bmb.len) {
+    i = j;
+    while ((i < bmb.len) && (bmb.s[i] != '\0')) i++;
+    if (bmb.s[j] == '!') {
+      negate = 1;
+      j++;
+    }
+    if (!stralloc_copyb(&curregex,bmb.s + j,(i - j))) die_nomem();
+    if (!stralloc_0(&curregex)) die_nomem();
+    x = matchregex(needle, curregex.s);
+    if ((negate) && (x == 0)) {
+      if (!stralloc_copyb(&matchedregex,bmb.s + j - 1,(i - j + 1))) die_nomem();
+      if (!stralloc_0(&matchedregex)) die_nomem();
+      return 1;
+    }
+    if (!(negate) && (x > 0)) {
+      if (!stralloc_copyb(&matchedregex,bmb.s + j,(i - j))) die_nomem();
+      if (!stralloc_0(&matchedregex)) die_nomem();
+      return 1;
+    }
+    j = i + 1;
+    negate = 0;
+  }
+  return 0;
+}
+
+static void _qregex_log_rejection(char *type, char *addr)
+{
+  char smtpdpid[32];
+  char *remoteip = env_get("TCPREMOTEIP");
+  stralloc message = {0};
+
+  str_copy(smtpdpid + fmt_ulong(smtpdpid,getppid())," ");
+  if (!remoteip) remoteip = "unknown";
+
+  stralloc_copys(&message,"rcptcheck: qregex ");
+  stralloc_cats(&message,smtpdpid);
+  stralloc_cats(&message,remoteip);
+  stralloc_cats(&message," ");
+  stralloc_cats(&message,addr);
+  stralloc_cats(&message," (");
+  stralloc_cats(&message,type);
+  if (env_get("LOGREGEX")) {
+    stralloc_cats(&message," pattern: ");
+    stralloc_cats(&message,matchedregex.s);
+  }
+  stralloc_cats(&message,")");
+  stralloc_0(&message);
+
+  strerr_warn1(message.s,0);
+}
+
+static int _qregex_reject_string(char *type, char *control, char *string)
+{
+  stralloc regexes = {0};
+  int have_some_regexes = control_readfile(&regexes,control,0);
+  if (have_some_regexes == -1) die_control();
+
+  if (have_some_regexes && _qregex_at_least_one_match(regexes,string)) {
+    _qregex_log_rejection(type,string);
+    return 1;
+  }
+
+  return 0;
+}
+
+int qregex_reject_helohost(char *host)
+{
+  if (!env_get("NOBADHELO")
+      && _qregex_reject_string("badhelo","control/badhelo",host))
+    return 1;
+
+  return 0;
+}
+
+int qregex_reject_recipient(char *to)
+{
+  if (_qregex_reject_string("badmailto","control/badmailto",to))
+    return 1;
+
+  if (!env_get("RELAYCLIENT")
+      && _qregex_reject_string("badmailto","control/badmailtonorelay",to))
+    return 1;
+
+  return 0;
+}
+
+int qregex_reject_sender(char *from)
+{
+  if ('\0' == from[0])
+    return 0;
+
+  if (_qregex_reject_string("badmailfrom","control/badmailfrom",from))
+    return 1;
+
+  if (!env_get("RELAYCLIENT")
+      && _qregex_reject_string("badmailfrom","control/badmailfromnorelay",from))
+    return 1;
+
+  return 0;
+}
diff --git qregexrcptto.h qregexrcptto.h
new file mode 100644
index 0000000..cbdaeda
--- /dev/null
+++ qregexrcptto.h
@@ -0,0 +1,8 @@
+#ifndef QREGEXRCPTTO_H
+#define QREGEXRCPTTO_H
+
+int qregex_reject_helohost(char *);
+int qregex_reject_recipient(char *);
+int qregex_reject_sender(char *);
+
+#endif
diff --git realrcptto.c realrcptto.c
new file mode 100644
index 0000000..ee2fb12
--- /dev/null
+++ realrcptto.c
@@ -0,0 +1,336 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pwd.h>
+#include "auto_break.h"
+#include "auto_usera.h"
+#include "byte.h"
+#include "case.h"
+#include "cdb.h"
+#include "constmap.h"
+#include "error.h"
+#include "fmt.h"
+#include "open.h"
+#include "str.h"
+#include "stralloc.h"
+#include "uint32.h"
+#include "substdio.h"
+#include "env.h"
+
+extern void die_nomem();
+extern void die_control();
+extern void die_cdb();
+extern void die_sys();
+
+static stralloc envnoathost = {0};
+static stralloc percenthack = {0};
+static stralloc locals = {0};
+static stralloc vdoms = {0};
+static struct constmap mappercenthack;
+static struct constmap maplocals;
+static struct constmap mapvdoms;
+
+static char *dash;
+static char *extension;
+static char *local;
+static struct passwd *pw;
+
+static char errbuf[128];
+static struct substdio sserr = SUBSTDIO_FDBUF(write,2,errbuf,sizeof errbuf);
+
+static char pidbuf[64];
+static char remoteipbuf[64]=" ";
+
+static int flagdenyall;
+static int flagdenyany;
+
+void realrcptto_init()
+{
+  char *x;
+
+  if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1)
+    die_control();
+
+  if (control_readfile(&locals,"control/locals",1) != 1) die_control();
+  if (!constmap_init(&maplocals,locals.s,locals.len,0)) die_nomem();
+  switch(control_readfile(&percenthack,"control/percenthack",0)) {
+    case -1: die_control();
+    case 0: if (!constmap_init(&mappercenthack,"",0,0)) die_nomem();
+    case 1:
+      if (!constmap_init(&mappercenthack,percenthack.s,percenthack.len,0))
+        die_nomem();
+  }
+  switch(control_readfile(&vdoms,"control/virtualdomains",0)) {
+    case -1: die_control();
+    case 0: if (!constmap_init(&mapvdoms,"",0,1)) die_nomem();
+    case 1: if (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) die_nomem();
+  }
+
+  str_copy(pidbuf + fmt_ulong(pidbuf,getppid())," ");
+  x=env_get("PROTO");
+  if (x) {
+    static char const remoteip[]="REMOTEIP";
+    unsigned int len = str_len(x);
+    if (len <= sizeof remoteipbuf - sizeof remoteip) {
+      byte_copy(remoteipbuf,len,x);
+      byte_copy(remoteipbuf + len,sizeof remoteip,remoteip);
+      x = env_get(remoteipbuf);
+      len = str_len(x);
+      if (len + 1 < sizeof remoteipbuf) {
+        byte_copy(remoteipbuf,len,x);
+        remoteipbuf[len]=' ';
+        remoteipbuf[len + 1]='\0';
+      }
+    }
+  }
+
+  x = env_get("QMAILRRTDENYALL");
+  flagdenyall = (x && x[0]=='1' && x[1]=='\0');
+}
+
+void realrcptto_start()
+{
+  flagdenyany = 0;
+}
+
+static int denyaddr(addr)
+char *addr;
+{
+  substdio_puts(&sserr,"rcptcheck: realrcptto ");
+  substdio_puts(&sserr,pidbuf);
+  substdio_puts(&sserr,remoteipbuf);
+  substdio_puts(&sserr,addr);
+  substdio_puts(&sserr,"\n");
+  substdio_flush(&sserr);
+  flagdenyany = 1;
+  return flagdenyall;
+}
+
+static void stat_error(path,error)
+char* path;
+int error;
+{
+  substdio_puts(&sserr,"unable to stat ");
+  substdio_puts(&sserr,path);
+  substdio_puts(&sserr,": ");
+  substdio_puts(&sserr,error_str(error));
+  substdio_puts(&sserr,"\n");
+  substdio_flush(&sserr);
+}
+
+#define GETPW_USERLEN 32
+
+static int userext()
+{
+  char username[GETPW_USERLEN];
+  struct stat st;
+
+  extension = local + str_len(local);
+  for (;;) {
+    if (extension - local < sizeof(username))
+      if (!*extension || (*extension == *auto_break)) {
+	byte_copy(username,extension - local,local);
+	username[extension - local] = 0;
+	case_lowers(username);
+	errno = 0;
+	pw = getpwnam(username);
+	if (errno == error_txtbsy) die_sys();
+	if (pw)
+	  if (pw->pw_uid)
+	    if (stat(pw->pw_dir,&st) == 0) {
+	      if (st.st_uid == pw->pw_uid) {
+		dash = "";
+		if (*extension) { ++extension; dash = "-"; }
+		return 1;
+	      }
+	    }
+	    else
+	      if (error_temp(errno)) die_sys();
+      }
+    if (extension == local) return 0;
+    --extension;
+  }
+}
+
+int realrcptto(addr)
+char *addr;
+{
+  char *homedir;
+  static stralloc localpart = {0};
+  static stralloc lower = {0};
+  static stralloc nughde = {0};
+  static stralloc wildchars = {0};
+  static stralloc safeext = {0};
+  static stralloc qme = {0};
+  unsigned int i,at;
+
+  /* Short circuit, or full logging?  Short circuit. */
+  if (flagdenyall && flagdenyany) return 1;
+
+  /* qmail-send:rewrite */
+  if (!stralloc_copys(&localpart,addr)) die_nomem();
+  i = byte_rchr(localpart.s,localpart.len,'@');
+  if (i == localpart.len) {
+    if (!stralloc_cats(&localpart,"@")) die_nomem();
+    if (!stralloc_cat(&localpart,&envnoathost)) die_nomem();
+  }
+  while (constmap(&mappercenthack,localpart.s + i + 1,localpart.len - i - 1)) {
+    unsigned int j = byte_rchr(localpart.s,i,'%');
+    if (j == i) break;
+    localpart.len = i;
+    i = j;
+    localpart.s[i] = '@';
+  }
+  at = byte_rchr(localpart.s,localpart.len,'@');
+  if (constmap(&maplocals,localpart.s + at + 1,localpart.len - at - 1)) {
+    localpart.len = at;
+    localpart.s[at] = '\0';
+  } else {
+    unsigned int xlen,newlen;
+    char *x;
+    for (i = 0;;++i) {
+      if (i > localpart.len) return 1;
+      if (!i || (i == at + 1) || (i == localpart.len) ||
+          ((i > at) && (localpart.s[i] == '.'))) {
+        x = constmap(&mapvdoms,localpart.s + i,localpart.len - i);
+        if (x) break;
+      }
+    }
+    if (!*x) return 1;
+    xlen = str_len(x) + 1;  /* +1 for '-' */
+    newlen = xlen + at + 1; /* +1 for \0 */
+    if (xlen < 1 || newlen - 1 < xlen || newlen < 1 ||
+        !stralloc_ready(&localpart,newlen))
+      die_nomem();
+    localpart.s[newlen - 1] = '\0';
+    byte_copyr(localpart.s + xlen,at,localpart.s);
+    localpart.s[xlen - 1] = '-';
+    byte_copy(localpart.s,xlen - 1,x);
+    localpart.len = newlen;
+  }
+
+  /* qmail-lspawn:nughde_get */
+  {
+    int r,fd,flagwild;
+    if (!stralloc_copys(&lower,"!")) die_nomem();
+    if (!stralloc_cats(&lower,localpart.s)) die_nomem();
+    if (!stralloc_0(&lower)) die_nomem();
+    case_lowerb(lower.s,lower.len);
+    if (!stralloc_copys(&nughde,"")) die_nomem();
+    fd = open_read("users/cdb");
+    if (fd == -1) {
+      if (errno != error_noent) die_cdb();
+    } else {
+      uint32 dlen;
+      r = cdb_seek(fd,"",0,&dlen);
+      if (r != 1) die_cdb();
+      if (!stralloc_ready(&wildchars,(unsigned int) dlen)) die_nomem();
+      wildchars.len = dlen;
+      if (cdb_bread(fd,wildchars.s,wildchars.len) == -1) die_cdb();
+      i = lower.len;
+      flagwild = 0;
+      do { /* i > 0 */
+        if (!flagwild || (i == 1) ||
+            (byte_chr(wildchars.s,wildchars.len,lower.s[i - 1])
+             < wildchars.len)) {
+          r = cdb_seek(fd,lower.s,i,&dlen);
+          if (r == -1) die_cdb();
+          if (r == 1) {
+            char *x;
+            if (!stralloc_ready(&nughde,(unsigned int) dlen)) die_nomem();
+            nughde.len = dlen;
+            if (cdb_bread(fd,nughde.s,nughde.len) == -1) die_cdb();
+            if (flagwild)
+              if (!stralloc_cats(&nughde,localpart.s + i - 1)) die_nomem();
+            if (!stralloc_0(&nughde)) die_nomem();
+            close(fd);
+            x=nughde.s;
+            /* skip username */
+            x += byte_chr(x,nughde.s + nughde.len - x,'\0');
+            if (x == nughde.s + nughde.len) return 1;
+            ++x;
+            /* skip uid */
+            x += byte_chr(x,nughde.s + nughde.len - x,'\0');
+            if (x == nughde.s + nughde.len) return 1;
+            ++x;
+            /* skip gid */
+            x += byte_chr(x,nughde.s + nughde.len - x,'\0');
+            if (x == nughde.s + nughde.len) return 1;
+            ++x;
+            /* skip homedir */
+            homedir=x;
+            x += byte_chr(x,nughde.s + nughde.len - x,'\0');
+            if (x == nughde.s + nughde.len) return 1;
+            ++x;
+            /* skip dash */
+            dash=x;
+            x += byte_chr(x,nughde.s + nughde.len - x,'\0');
+            if (x == nughde.s + nughde.len) return 1;
+            ++x;
+            extension=x;
+            goto got_nughde;
+          }
+        }
+        --i;
+        flagwild = 1;
+      } while (i);
+      close(fd);
+    }
+  }
+
+  /* qmail-getpw */
+  local = localpart.s;
+  if (!userext()) {
+    extension = local;
+    dash = "-";
+    pw = getpwnam(auto_usera);
+  }
+  if (!pw) return denyaddr(addr);
+  if (!stralloc_copys(&nughde,pw->pw_dir)) die_nomem();
+  if (!stralloc_0(&nughde)) die_nomem();
+  homedir=nughde.s;
+
+  got_nughde:
+
+  /* qmail-local:qmesearch */
+  if (!*dash) return 1;
+  if (!stralloc_copys(&safeext,extension)) die_nomem();
+  case_lowerb(safeext.s,safeext.len);
+  for (i = 0;i < safeext.len;++i)
+    if (safeext.s[i] == '.')
+      safeext.s[i] = ':';
+  {
+    struct stat st;
+    int i;
+    if (!stralloc_copys(&qme,homedir)) die_nomem();
+    if (!stralloc_cats(&qme,"/.qmail")) die_nomem();
+    if (!stralloc_cats(&qme,dash)) die_nomem();
+    if (!stralloc_cat(&qme,&safeext)) die_nomem();
+    if (!stralloc_0(&qme)) die_nomem();
+    if (stat(qme.s,&st) == 0) return 1;
+    if (errno != error_noent) {
+      stat_error(qme.s,errno);
+      return 1;
+    }
+    for (i = safeext.len;i >= 0;--i)
+      if (!i || (safeext.s[i - 1] == '-')) {
+        if (!stralloc_copys(&qme,homedir)) die_nomem();
+        if (!stralloc_cats(&qme,"/.qmail")) die_nomem();
+        if (!stralloc_cats(&qme,dash)) die_nomem();
+        if (!stralloc_catb(&qme,safeext.s,i)) die_nomem();
+        if (!stralloc_cats(&qme,"default")) die_nomem();
+        if (!stralloc_0(&qme)) die_nomem();
+        if (stat(qme.s,&st) == 0) return 1;
+        if (errno != error_noent) {
+          stat_error(qme.s,errno);
+          return 1;
+        }
+      }
+    return denyaddr(addr);
+  }
+}
+
+int realrcptto_deny()
+{
+  return flagdenyall && flagdenyany;
+}
diff --git realrcptto.h realrcptto.h
new file mode 100644
index 0000000..9a50d14
--- /dev/null
+++ realrcptto.h
@@ -0,0 +1,9 @@
+#ifndef REALRCPTTO_H
+#define REALRCPTTO_H
+
+void realrcptto_init();
+void realrcptto_start();
+int realrcptto(char *);
+int realrcptto_deny();
+
+#endif
diff --git viruscan.c viruscan.c
new file mode 100644
index 0000000..0252cd7
--- /dev/null
+++ viruscan.c
@@ -0,0 +1,194 @@
+#include <unistd.h>
+#include "byte.h"
+#include "case.h"
+#include "control.h"
+#include "env.h"
+#include "fmt.h"
+#include "getln.h"
+#include "str.h"
+#include "stralloc.h"
+#include "substdio.h"
+
+extern void die_control();
+extern void die_nomem();
+
+static void viruscan_log_rejection(char *signature)
+{
+  char errbuf[SUBSTDIO_OUTSIZE];
+  substdio sserr;
+  char *remoteip;
+  char *smtpdpid;
+  char qfilterpid[FMT_ULONG];
+
+  substdio_fdbuf(&sserr,write,2,errbuf,sizeof(errbuf));
+
+  remoteip = env_get("TCPREMOTEIP");
+  if (!remoteip) remoteip = "unknown";
+  smtpdpid = env_get("QMAILPPID");
+  str_copy(qfilterpid + fmt_ulong(qfilterpid,getppid()),"");
+
+  substdio_puts(&sserr,"qfilter: viruscan ");
+  if (smtpdpid) substdio_puts(&sserr,smtpdpid);
+  else substdio_puts(&sserr,qfilterpid);
+  substdio_puts(&sserr," ");
+  substdio_puts(&sserr,remoteip);
+  substdio_puts(&sserr," ");
+  substdio_puts(&sserr,signature);
+  substdio_puts(&sserr,"\n");
+  substdio_flush(&sserr);
+}
+
+static int viruscan_reject_line(stralloc *line)
+{
+  int sigsok, i, j;
+  stralloc sigs = {0};
+  sigsok = control_readfile(&sigs,"control/signatures",0);
+  if (sigsok == -1) die_control();
+
+  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)) {
+        viruscan_log_rejection(sigs.s);
+        return 1;
+      }
+    j = i+1;
+  }
+
+  return 0;
+}
+
+static int linespastheader;
+static char linetype;
+static int flagexecutable;
+static int flagqsbmf;
+static stralloc line = {0};
+static stralloc content = {0};
+static stralloc boundary = {0};
+static int boundary_start;
+
+static 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 {
+            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 (viruscan_reject_line(&line)) {
+          flagexecutable = 1;
+        }
+        linespastheader = 2;
+      }
+      if (flagqsbmf && str_start(line.s,"---")) {
+        linespastheader = 0;
+      }
+    }
+    line.len = 0;
+  }
+}
+
+int viruscan_reject_attachment()
+{
+  char inbuf[SUBSTDIO_INSIZE];
+  substdio ssin;
+  char ch;
+
+  substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
+
+  for (;;) {
+    if (1 != substdio_get(&ssin,&ch,1)) break;
+    put(&ch);
+    if (flagexecutable)
+      return 1;
+  }
+
+  return 0;
+}
diff --git viruscan.h viruscan.h
new file mode 100644
index 0000000..aed8ced
--- /dev/null
+++ viruscan.h
@@ -0,0 +1,6 @@
+#ifndef VIRUSCAN_H
+#define VIRUSCAN_H
+
+int viruscan_reject_attachment(void);
+
+#endif
