<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">diff --git Makefile Makefile
index 0f0e31a..716f726 100644
--- Makefile
+++ Makefile
@@ -4,6 +4,21 @@ SHELL=/bin/sh
 
 default: it
 
+REJECTUTILS_BIN=qmail-qfilter-queue   qmail-qfilter-viruscan \
+		qmail-rcptcheck   qmail-rcptcheck-badrcptto \
+		qmail-rcptcheck-qregex   qmail-rcptcheck-realrcptto
+REJECTUTILS_MAN=qmail-qfilter-queue.8 qmail-qfilter-viruscan.8 \
+		qmail-rcptcheck.8 qmail-rcptcheck-badrcptto.8 \
+		qmail-rcptcheck-qregex.8 qmail-rcptcheck-realrcptto.8
+
+rejectutils: \
+${REJECTUTILS_BIN} ${REJECTUTILS_MAN}
+
+rejectutils-install: \
+rejectutils
+	cp ${REJECTUTILS_BIN} `head -1 conf-qmail`/bin &amp;&amp; \
+	cp ${REJECTUTILS_MAN} `head -1 conf-qmail`/man/man8
+
 addresses.0: \
 addresses.5
 	nroff -man addresses.5 &gt; addresses.0
@@ -136,6 +151,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 +257,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
@@ -303,6 +327,7 @@ exit.h auto_spawn.h
 clean: \
 TARGETS
 	rm -f `cat TARGETS`
+	git checkout -- INSTALL SENDMAIL
 
 coe.o: \
 compile coe.c coe.h
@@ -1331,6 +1356,28 @@ 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-queue: \
+load qmail-qfilter-queue.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-qfilter-queue control.o env.a error.a fs.a getln.a \
+	open.a stralloc.a substdio.a str.a alloc.a wait.a
+
+qmail-qfilter-queue.o: \
+compile qmail-qfilter-queue.c control.h stralloc.h wait.h
+	./compile qmail-qfilter-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 substdio.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-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
 	&amp;&amp; cat uint32.h2 || cat uint32.h1 ) &gt; 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..4651807 100644
--- TARGETS
+++ TARGETS
@@ -385,3 +385,21 @@ forgeries.0
 man
 setup
 check
+badrcptto.o
+case_startb2.o
+qmail-qfilter-queue
+qmail-qfilter-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 &lt;unistd.h&gt;
+#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 &amp;&amp; constmap(&amp;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 &lt; address.len) &amp;&amp; (constmap(&amp;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(&amp;brt,"control/badrcptto",0);
+  if (brtok == -1) die_control();
+  if (!brtok) return 0;
+  if (!constmap_init(&amp;mapbrt,brt.s,brt.len,0)) die_nomem();
+
+  if (!stralloc_copys(&amp;addr,string)) die_nomem();
+  if (!stralloc_0(&amp;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 &lt;= 'Z' - 'A') y += 'a'; else y += 'A';
+    if (!y) return 1;
+    if (!len) return 0;
+    --len;
+    x = *s++ - 'A';
+    if (x &lt;= 'Z' - 'A') x += 'a'; else x += 'A';
+    if (x != y) return 0;
+  }
+}
diff --git qmail-qfilter-queue.8 qmail-qfilter-queue.8
new file mode 100644
index 0000000..192a28e
--- /dev/null
+++ qmail-qfilter-queue.8
@@ -0,0 +1,88 @@
+.TH QMAIL-QFILTER-QUEUE 8 2020-12-15
+.SH NAME
+qmail-qfilter-queue \- run a sequence of pre-queue filters
+.SH SYNOPSIS
+.B qmail-qfilter-queue
+.SH DESCRIPTION
+.B qmail-qfilter-queue
+runs an administrator-defined sequence of filters
+before calling
+.BR qmail-queue .
+Filters will be run by
+.B qmail-qfilter
+and must adhere to that interface.
+If any filter rejects, the message is rejected.
+.PP
+.B qmail-qfilter-queue
+is most commonly invoked from
+.B qmail-smtpd
+and
+.BR ofmipd ,
+so that incoming and submitted messages are filtered before
+entering the queue.
+.SH "ENVIRONMENT VARIABLES"
+To have
+.B qmail-smtpd
+and/or
+.B ofmipd
+invoke
+.B qmail-qfilter-queue
+in place of
+.BR qmail-queue ,
+set
+.B QMAILQUEUE
+to
+.I /var/qmail/bin/qmail-qfilter-queue
+in your
+.BR tcpserver 's
+tcprules (and rebuild the CDB).
+.PP
+To have
+.B qmail-qfilter-queue
+filter messages before they reach the queue, also set
+.B QMAILQUEUEFILTERS
+to the relevant control file (and rebuild the CDB).
+.SH "CONTROL FILES"
+.B qmail-qfilter-queue
+expects a sequence of filter programs.
+Typical values for
+.BR QMAILQUEUEFILTERS :
+.TP 5
+.I control/smtpfilters
+For incoming messages.
+.TP 5
+.I control/ofmipfilters
+For submitted messages.
+.P
+Filter programs must be listed one per line,
+with no arguments.
+Changing the sequence takes effect immediately, no restart required.
+If the control file is empty,
+contains only comments,
+or doesn't exist,
+messages are passed to
+.BR qmail-queue .
+.SH "ERRORS"
+Some typical SMTP error messages and their likely causes:
+.TP 5
+.I "451 mail server temporarily rejected message (#4.3.0)"
+.B qmail-qfilter
+is not installed in
+.IR "$PATH" .
+.TP 5
+.I "451 qq internal bug (#4.3.0)"
+The control file lists a program that doesn't exist
+or otherwise can't be executed.
+.SH "EXAMPLES"
+See
+.IR https://schmonz.com/qmail/rejectutils .
+.SH "AUTHOR"
+.B Amitai Schleier &lt;schmonz-web-rejectutils@schmonz.com&gt;
+.SH "SEE ALSO"
+qmail-queue(8),
+qmail-qfilter(1),
+qmail-smtpd(8),
+ofmipd(8),
+tcpserver(1),
+qmail-qfilter-viruscan(8),
+qmail-qfilter-addtlsheader(8).
diff --git qmail-qfilter-queue.c qmail-qfilter-queue.c
new file mode 100755
index 0000000..c67364f
--- /dev/null
+++ qmail-qfilter-queue.c
@@ -0,0 +1,61 @@
+#include &lt;unistd.h&gt;
+#include "alloc.h"
+#include "control.h"
+#include "env.h"
+#include "str.h"
+#include "stralloc.h"
+#include "wait.h"
+
+static void unable_to_allocate() { _exit(51); }
+static void unable_to_execute()  { _exit(71); }
+static void unable_to_verify()   { _exit(55); }
+
+static int num_lines(stralloc *lines) {
+  int num = 0;
+  int i;
+  for (i = 0; i &lt; lines-&gt;len; i++) if (lines-&gt;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 &lt; filters-&gt;len; i++) {
+    if (filters-&gt;s[i] == '\0') {
+      stralloc filter = {0};
+      stralloc_copys(&amp;filter, filters-&gt;s + linestart);
+      stralloc_0(&amp;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};
+  char *filterfile;
+
+  if ((filterfile = env_get("QMAILQUEUEFILTERS")))
+    if (control_readfile(&amp;filters,filterfile,0) != 1)
+      unable_to_verify();
+
+  run_qmail_qfilter(&amp;filters);
+}
diff --git qmail-qfilter-viruscan.8 qmail-qfilter-viruscan.8
new file mode 100644
index 0000000..49677ed
--- /dev/null
+++ qmail-qfilter-viruscan.8
@@ -0,0 +1,25 @@
+.TH QMAIL-QFILTER-VIRUSCAN 8 2020-12-15
+.SH NAME
+qmail-qfilter-viruscan \- viruscan patch as standalone program
+.SH SYNOPSIS
+.B qmail-qfilter-viruscan
+.SH DESCRIPTION
+.B qmail-qfilter-viruscan
+is Russ Nelson's viruscan patch repackaged as a
+.BR qmail-qfilter -compatible
+program.
+.SH "CONTROL FILES"
+.TP 5
+.I signatures
+For incoming messages.
+.SH "EXAMPLES"
+See
+.IR https://schmonz.com/qmail/rejectutils .
+.SH "AUTHOR"
+.B Amitai Schleier &lt;schmonz-web-rejectutils@schmonz.com&gt;
+.SH HISTORY
+Based on this code from Russ Nelson:
+.PP
+.I http://qmailorg.schmonz.com/qmail-smtpd-viruscan-1.3.patch
+.SH "SEE ALSO"
+qmail-qfilter(1).
diff --git qmail-qfilter-viruscan.c qmail-qfilter-viruscan.c
new file mode 100644
index 0000000..2c1efa2
--- /dev/null
+++ qmail-qfilter-viruscan.c
@@ -0,0 +1,27 @@
+#include &lt;unistd.h&gt;
+#include "substdio.h"
+#include "viruscan.h"
+
+static void accept_message() { _exit( 0); }
+static void reject_message() {
+#ifdef QMAIL_QUEUE_CUSTOM_ERROR
+  char buf[SUBSTDIO_OUTSIZE];
+  substdio ssmsg;
+  substdio_fdbuf(&amp;ssmsg,write,6,buf,sizeof(buf));
+  substdio_putsflush(&amp;ssmsg,"Dwe don't accept email with such content (#5.3.4)");
+  _exit(82);
+#else
+  _exit(31);
+#endif
+}
+
+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.8 qmail-rcptcheck-badrcptto.8
new file mode 100644
index 0000000..39e05ee
--- /dev/null
+++ qmail-rcptcheck-badrcptto.8
@@ -0,0 +1,25 @@
+.TH QMAIL-RCPTCHECK-BADRCPTTO 8 2020-12-15
+.SH NAME
+qmail-rcptcheck-badrcptto \- badrcptto patch as standalone program
+.SH SYNOPSIS
+.B qmail-rcptcheck-badrcptto
+.SH DESCRIPTION
+.B qmail-rcptcheck-badrcptto
+is Ward Vandewege's badrcptto patch repackaged as a
+.BR qmail-rcptcheck -compatible
+program.
+.SH "CONTROL FILES"
+.TP 5
+.I badrcptto
+For incoming messages.
+.SH "EXAMPLES"
+See
+.IR https://schmonz.com/qmail/rejectutils .
+.SH "AUTHOR"
+.B Amitai Schleier &lt;schmonz-web-rejectutils@schmonz.com&gt;
+.SH HISTORY
+Based on this code from Ward Vandewege:
+.PP
+.I http://patch.be/qmail/
+.SH "SEE ALSO"
+qmail-rcptcheck(8).
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 &lt;unistd.h&gt;
+#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.8 qmail-rcptcheck-qregex.8
new file mode 100644
index 0000000..56207b4
--- /dev/null
+++ qmail-rcptcheck-qregex.8
@@ -0,0 +1,28 @@
+.TH QMAIL-RCPTCHECK-QREGEX 8 2018-12-30
+.SH NAME
+qmail-rcptcheck-qregex \- qregex patch as standalone program
+.SH SYNOPSIS
+.B qmail-rcptcheck-qregex
+.SH DESCRIPTION
+.B qmail-rcptcheck-qregex
+is Andrew St. Jean's qregex patch repackaged as a
+.BR qmail-rcptcheck -compatible
+program.
+.SH "CONTROL FILES"
+.TP 5
+.I badmailto
+For incoming recipients.
+.TP 5
+.I badmailfrom
+For incoming senders.
+.SH "EXAMPLES"
+See
+.IR https://schmonz.com/qmail/rejectutils .
+.SH "AUTHOR"
+.B Amitai Schleier &lt;schmonz-web-rejectutils@schmonz.com&gt;
+.SH HISTORY
+Based on this code from Andrew St. Jean:
+.PP
+.I http://www.arda.homeunix.net/downloads-qmail/
+.SH "SEE ALSO"
+qmail-rcptcheck(8).
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 &lt;unistd.h&gt;
+#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 &amp;&amp; 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.8 qmail-rcptcheck-realrcptto.8
new file mode 100644
index 0000000..ebc0c47
--- /dev/null
+++ qmail-rcptcheck-realrcptto.8
@@ -0,0 +1,21 @@
+.TH QMAIL-RCPTCHECK-REALRCPTTO 8 2020-12-15
+.SH NAME
+qmail-rcptcheck-realrcptto \- realrcptto patch as standalone program
+.SH SYNOPSIS
+.B qmail-rcptcheck-realrcptto
+.SH DESCRIPTION
+.B qmail-rcptcheck-realrcptto
+is Paul Jarc's realrcptto patch repackaged as a
+.BR qmail-rcptcheck -compatible
+program.
+.SH "EXAMPLES"
+See
+.IR https://schmonz.com/qmail/rejectutils .
+.SH "AUTHOR"
+.B Amitai Schleier &lt;schmonz-web-rejectutils@schmonz.com&gt;
+.SH HISTORY
+Based on this code from Paul Jarc:
+.PP
+.I http://code.dogmap.org/qmail/#realrcptto
+.SH "SEE ALSO"
+qmail-rcptcheck(8).
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 &lt;unistd.h&gt;
+#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.8 qmail-rcptcheck.8
new file mode 100644
index 0000000..c34a694
--- /dev/null
+++ qmail-rcptcheck.8
@@ -0,0 +1,117 @@
+.TH QMAIL-RCPTCHECK 8 2020-12-15
+.SH NAME
+qmail-rcptcheck \- run a sequence of sender/recipient checks
+.SH SYNOPSIS
+.B qmail-rcptcheck
+.SH DESCRIPTION
+.B qmail-rcptcheck
+runs an administrator-defined sequence of programs
+to check SMTP envelope senders and recipients.
+Checks must adhere to the
+.B RCPTCHECK
+interface.
+If any check rejects, the message is rejected.
+.PP
+.B qmail-rcptcheck
+is most commonly invoked from
+.B qmail-smtpd
+patched with either
+.B qmail-spp
+or
+.BR RCPTCHECK .
+.SH "ENVIRONMENT VARIABLES"
+To run under
+.BR qmail-spp :
+none needed.
+.PP
+To run under
+.BR RCPTCHECK :
+set
+.B RCPTCHECK
+to
+.I /var/qmail/bin/qmail-rcptcheck
+in your
+.BR tcpserver 's
+tcprules (and rebuild the CDB).
+.PP
+To perform checks, inspect these variables:
+.TP 5
+.I SENDER
+Contains the envelope sender.
+.TP 5
+.I RECIPIENT
+Contains the envelope recipient.
+.SH "CONTROL FILES"
+To run under
+.BR qmail-spp :
+add
+.I /var/qmail/bin/qmail-rcptcheck
+in the
+.I [rcpt]
+section of
+.IR smtpplugins .
+.PP
+To run under
+.BR RCPTCHECK :
+none needed.
+.PP
+In either case, to control
+.BR qmail-rcptcheck :
+.TP 5
+.I rcptchecks
+Sequence of checks.
+.P
+RCPTCHECK-compatible programs must be listed one per line,
+with no arguments.
+Changing the sequence takes effect immediately, no restart required.
+If the control file is empty,
+contains only comments,
+or doesn't exist,
+messages are not rejected.
+.P
+To test your configuration, as
+.IR root ,
+in
+.IR /var/qmail :
+.P
+# env SENDER=a RECIPIENT=b setuidgid qmaild qmail-rcptcheck; echo $?
+.SH "EXIT CODES"
+As defined by the
+.B RCPTCHECK
+interface,
+.I 120
+is reserved.
+Recipient-checking programs should exit
+.I 111
+when unable to verify,
+.I 100
+to reject, or
+any other non-reserved code to accept.
+.SH COMPATIBILITY
+.BR RCPTCHECK -compatible
+programs can run unmodified under
+.B qmail-spp
+simply by running them from
+.BR qmail-rcptcheck .
+.SH "ERRORS"
+Some typical SMTP errors and their likely causes:
+.TP 5
+.I "421 unable to execute recipient check (#4.3.0)"
+The control file lists a program that doesn't exist
+or otherwise can't be executed.
+.SH "EXAMPLES"
+See
+.IR https://schmonz.com/qmail/rejectutils .
+.SH "AUTHOR"
+.B Amitai Schleier &lt;schmonz-web-rejectutils@schmonz.com&gt;
+.SH HISTORY
+Based on this code from Jay Soffian:
+.PP
+.I http://www.soffian.org/downloads/qmail/qmail-smtpd-doc.html
+.SH "SEE ALSO"
+qmail-rcptcheck-badrcptto(8),
+qmail-rcptcheck-qregex(8),
+qmail-rcptcheck-realrcptto(8),
+qmail-smtpd(8).
+.PP
+.I http://qmail-spp.sourceforge.net/doc/
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 &lt;unistd.h&gt;
+#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(&amp;ssout,"");
+}
+
+static void spp_reject_recipient() {
+  putsdie(&amp;ssout,"E553 sorry, no mailbox here by that name. (#5.1.1)\n");
+}
+
+static void spp_unable_to_verify() {
+  putsdie(&amp;ssout,"R421 unable to verify recipient (#4.3.0)\n");
+}
+
+static void spp_unable_to_execute() {
+  putsdie(&amp;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(&amp;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(&amp;sserr,"qmail-rcptcheck: from ");
+
+  if (sender) {
+    substdio_puts(&amp;sserr,sender);
+    if (!env_put2("SENDER", sender)) rcptcheck.unable_to_verify();
+  }
+
+  substdio_puts(&amp;sserr," to ");
+
+  if (recipient) {
+    substdio_puts(&amp;sserr,recipient);
+    if (!env_put2("RECIPIENT", recipient)) rcptcheck.unable_to_verify();
+  }
+
+  substdio_puts(&amp;sserr,"\n");
+  substdio_flush(&amp;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(&amp;rcptchecks,"control/rcptchecks",0) == -1)
+    rcptcheck.unable_to_verify();
+
+  for (linestart = 0, pos = 0; pos &lt; 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 &lt;sys/types.h&gt;
+#include &lt;regex.h&gt;
+#include "qregex.h"
+
+#define REGCOMP(X,Y)    regcomp(&amp;X, Y, REG_EXTENDED|REG_ICASE)
+#define REGEXEC(X,Y)    regexec(&amp;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(&amp;qreg);
+    return(-retval);
+  }
+
+  /* execute the regex */
+  if ((retval = REGEXEC(qreg, text)) != 0) {
+    /* did we just not match anything? */
+    if (retval == REG_NOMATCH) {
+      regfree(&amp;qreg);
+      return(0);
+    }
+    regfree(&amp;qreg);
+    return(-retval);
+  }
+
+  /* signal the match */
+  regfree(&amp;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 &lt;unistd.h&gt;
+#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(&amp;bmb,&amp;haystack)) die_nomem();
+
+  while (j &lt; bmb.len) {
+    i = j;
+    while ((i &lt; bmb.len) &amp;&amp; (bmb.s[i] != '\0')) i++;
+    if (bmb.s[j] == '!') {
+      negate = 1;
+      j++;
+    }
+    if (!stralloc_copyb(&amp;curregex,bmb.s + j,(i - j))) die_nomem();
+    if (!stralloc_0(&amp;curregex)) die_nomem();
+    x = matchregex(needle, curregex.s);
+    if ((negate) &amp;&amp; (x == 0)) {
+      if (!stralloc_copyb(&amp;matchedregex,bmb.s + j - 1,(i - j + 1))) die_nomem();
+      if (!stralloc_0(&amp;matchedregex)) die_nomem();
+      return 1;
+    }
+    if (!(negate) &amp;&amp; (x &gt; 0)) {
+      if (!stralloc_copyb(&amp;matchedregex,bmb.s + j,(i - j))) die_nomem();
+      if (!stralloc_0(&amp;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(&amp;message,"rcptcheck: qregex ");
+  stralloc_cats(&amp;message,smtpdpid);
+  stralloc_cats(&amp;message,remoteip);
+  stralloc_cats(&amp;message," ");
+  stralloc_cats(&amp;message,addr);
+  stralloc_cats(&amp;message," (");
+  stralloc_cats(&amp;message,type);
+  if (env_get("LOGREGEX")) {
+    stralloc_cats(&amp;message," pattern: ");
+    stralloc_cats(&amp;message,matchedregex.s);
+  }
+  stralloc_cats(&amp;message,")");
+  stralloc_0(&amp;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(&amp;regexes,control,0);
+  if (have_some_regexes == -1) die_control();
+
+  if (have_some_regexes &amp;&amp; _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")
+      &amp;&amp; _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")
+      &amp;&amp; _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")
+      &amp;&amp; _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 &lt;sys/types.h&gt;
+#include &lt;sys/stat.h&gt;
+#include &lt;unistd.h&gt;
+#include &lt;pwd.h&gt;
+#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(&amp;envnoathost,"control/envnoathost",1,"envnoathost") != 1)
+    die_control();
+
+  if (control_readfile(&amp;locals,"control/locals",1) != 1) die_control();
+  if (!constmap_init(&amp;maplocals,locals.s,locals.len,0)) die_nomem();
+  switch(control_readfile(&amp;percenthack,"control/percenthack",0)) {
+    case -1: die_control();
+    case 0: if (!constmap_init(&amp;mappercenthack,"",0,0)) die_nomem();
+    case 1:
+      if (!constmap_init(&amp;mappercenthack,percenthack.s,percenthack.len,0))
+        die_nomem();
+  }
+  switch(control_readfile(&amp;vdoms,"control/virtualdomains",0)) {
+    case -1: die_control();
+    case 0: if (!constmap_init(&amp;mapvdoms,"",0,1)) die_nomem();
+    case 1: if (!constmap_init(&amp;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 &lt;= 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 &lt; sizeof remoteipbuf) {
+        byte_copy(remoteipbuf,len,x);
+        remoteipbuf[len]=' ';
+        remoteipbuf[len + 1]='\0';
+      }
+    }
+  }
+
+  x = env_get("QMAILRRTDENYALL");
+  flagdenyall = (x &amp;&amp; x[0]=='1' &amp;&amp; x[1]=='\0');
+}
+
+void realrcptto_start()
+{
+  flagdenyany = 0;
+}
+
+static int denyaddr(addr)
+char *addr;
+{
+  substdio_puts(&amp;sserr,"rcptcheck: realrcptto ");
+  substdio_puts(&amp;sserr,pidbuf);
+  substdio_puts(&amp;sserr,remoteipbuf);
+  substdio_puts(&amp;sserr,addr);
+  substdio_puts(&amp;sserr,"\n");
+  substdio_flush(&amp;sserr);
+  flagdenyany = 1;
+  return flagdenyall;
+}
+
+static void stat_error(path,error)
+char* path;
+int error;
+{
+  substdio_puts(&amp;sserr,"unable to stat ");
+  substdio_puts(&amp;sserr,path);
+  substdio_puts(&amp;sserr,": ");
+  substdio_puts(&amp;sserr,error_str(error));
+  substdio_puts(&amp;sserr,"\n");
+  substdio_flush(&amp;sserr);
+}
+
+#define GETPW_USERLEN 32
+
+static int userext()
+{
+  char username[GETPW_USERLEN];
+  struct stat st;
+
+  extension = local + str_len(local);
+  for (;;) {
+    if (extension - local &lt; 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-&gt;pw_uid)
+	    if (stat(pw-&gt;pw_dir,&amp;st) == 0) {
+	      if (st.st_uid == pw-&gt;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 &amp;&amp; flagdenyany) return 1;
+
+  /* qmail-send:rewrite */
+  if (!stralloc_copys(&amp;localpart,addr)) die_nomem();
+  i = byte_rchr(localpart.s,localpart.len,'@');
+  if (i == localpart.len) {
+    if (!stralloc_cats(&amp;localpart,"@")) die_nomem();
+    if (!stralloc_cat(&amp;localpart,&amp;envnoathost)) die_nomem();
+  }
+  while (constmap(&amp;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(&amp;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 &gt; localpart.len) return 1;
+      if (!i || (i == at + 1) || (i == localpart.len) ||
+          ((i &gt; at) &amp;&amp; (localpart.s[i] == '.'))) {
+        x = constmap(&amp;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 &lt; 1 || newlen - 1 &lt; xlen || newlen &lt; 1 ||
+        !stralloc_ready(&amp;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(&amp;lower,"!")) die_nomem();
+    if (!stralloc_cats(&amp;lower,localpart.s)) die_nomem();
+    if (!stralloc_0(&amp;lower)) die_nomem();
+    case_lowerb(lower.s,lower.len);
+    if (!stralloc_copys(&amp;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,&amp;dlen);
+      if (r != 1) die_cdb();
+      if (!stralloc_ready(&amp;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 &gt; 0 */
+        if (!flagwild || (i == 1) ||
+            (byte_chr(wildchars.s,wildchars.len,lower.s[i - 1])
+             &lt; wildchars.len)) {
+          r = cdb_seek(fd,lower.s,i,&amp;dlen);
+          if (r == -1) die_cdb();
+          if (r == 1) {
+            char *x;
+            if (!stralloc_ready(&amp;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(&amp;nughde,localpart.s + i - 1)) die_nomem();
+            if (!stralloc_0(&amp;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(&amp;nughde,pw-&gt;pw_dir)) die_nomem();
+  if (!stralloc_0(&amp;nughde)) die_nomem();
+  homedir=nughde.s;
+
+  got_nughde:
+
+  /* qmail-local:qmesearch */
+  if (!*dash) return 1;
+  if (!stralloc_copys(&amp;safeext,extension)) die_nomem();
+  case_lowerb(safeext.s,safeext.len);
+  for (i = 0;i &lt; safeext.len;++i)
+    if (safeext.s[i] == '.')
+      safeext.s[i] = ':';
+  {
+    struct stat st;
+    int i;
+    if (!stralloc_copys(&amp;qme,homedir)) die_nomem();
+    if (!stralloc_cats(&amp;qme,"/.qmail")) die_nomem();
+    if (!stralloc_cats(&amp;qme,dash)) die_nomem();
+    if (!stralloc_cat(&amp;qme,&amp;safeext)) die_nomem();
+    if (!stralloc_0(&amp;qme)) die_nomem();
+    if (stat(qme.s,&amp;st) == 0) return 1;
+    if (errno != error_noent) {
+      stat_error(qme.s,errno);
+      return 1;
+    }
+    for (i = safeext.len;i &gt;= 0;--i)
+      if (!i || (safeext.s[i - 1] == '-')) {
+        if (!stralloc_copys(&amp;qme,homedir)) die_nomem();
+        if (!stralloc_cats(&amp;qme,"/.qmail")) die_nomem();
+        if (!stralloc_cats(&amp;qme,dash)) die_nomem();
+        if (!stralloc_catb(&amp;qme,safeext.s,i)) die_nomem();
+        if (!stralloc_cats(&amp;qme,"default")) die_nomem();
+        if (!stralloc_0(&amp;qme)) die_nomem();
+        if (stat(qme.s,&amp;st) == 0) return 1;
+        if (errno != error_noent) {
+          stat_error(qme.s,errno);
+          return 1;
+        }
+      }
+    return denyaddr(addr);
+  }
+}
+
+int realrcptto_deny()
+{
+  return flagdenyall &amp;&amp; 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 &lt;unistd.h&gt;
+#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(&amp;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(&amp;sserr,"qfilter: viruscan ");
+  if (smtpdpid) substdio_puts(&amp;sserr,smtpdpid);
+  else substdio_puts(&amp;sserr,qfilterpid);
+  substdio_puts(&amp;sserr," ");
+  substdio_puts(&amp;sserr,remoteip);
+  substdio_puts(&amp;sserr," ");
+  substdio_puts(&amp;sserr,signature);
+  substdio_puts(&amp;sserr,"\n");
+  substdio_flush(&amp;sserr);
+}
+
+static int viruscan_reject_line(stralloc *line)
+{
+  int sigsok, i, j;
+  stralloc sigs = {0};
+  sigsok = control_readfile(&amp;sigs,"control/signatures",0);
+  if (sigsok == -1) die_control();
+
+  j = 0;
+  for (i = 0; i &lt; sigs.len; i++) if (!sigs.s[i]) {
+    if (i-j &lt; line-&gt;len)
+      if (!str_diffn(line-&gt;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 &lt; 1024)
+    if (!stralloc_catb(&amp;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 &amp;&amp; (*cp == ' ' || *cp == '\t')) { ++cp; --len; }
+          cpstart = cp;
+          if (len &amp;&amp; *cp == '"') { /* might be commented */
+            ++cp; --len; cpstart = cp;
+            while (len &amp;&amp; *cp != '"') { ++cp; --len; }
+          } else {
+            while (len &amp;&amp; *cp != ' ' &amp;&amp; *cp != '\t' &amp;&amp; *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 &lt; cpafter &amp;&amp; (*cp == ' ' || *cp == '\t')) ++cp;
+            if (case_startb(cp,cpafter - cp,"boundary=")) {
+              cp += 9; /* after boundary= */
+              if (cp &lt; cpafter &amp;&amp; *cp == '"') {
+                ++cp;
+                cpstart = cp;
+                while (cp &lt; cpafter &amp;&amp; *cp != '"') ++cp;
+              } else {
+                cpstart = cp;
+                while (cp &lt; cpafter &amp;&amp;
+                       *cp != ';' &amp;&amp; *cp != ' ' &amp;&amp; *cp != '\t') ++cp;
+              }
+              /* push the current boundary.  Append a null and remember start. */
+              if (!stralloc_0(&amp;boundary)) die_nomem();
+              boundary_start = boundary.len;
+              if (!stralloc_cats(&amp;boundary,"--")) die_nomem();
+              if (!stralloc_catb(&amp;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(&amp;content,line.s,line.len-1)) die_nomem(); break;
+          default: break;
+          }
+        } else {
+          if (case_startb(line.s,line.len,"content-type:")) {
+            if (!stralloc_copyb(&amp;content,line.s+13,line.len-14)) die_nomem();
+            linetype = 'C';
+          } else {
+            linetype = ' ';
+          }
+        }
+      }
+    } else { /* non-header line */
+      if (boundary.len-boundary_start &amp;&amp; *line.s == '-' &amp;&amp; line.len &gt; (boundary.len-boundary_start) &amp;&amp;
+          !str_diffn(line.s,boundary.s+boundary_start,boundary.len-boundary_start)) { /* matches a boundary */
+        if (line.len &gt; boundary.len-boundary_start + 2 &amp;&amp;
+            line.s[boundary.len-boundary_start+0] == '-' &amp;&amp;
+            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' &amp;&amp; */
+                str_start(line.s,"Hi. This is the "))
+          flagqsbmf = 1;
+        else if (/*mailfrom.s[0] == '\0' &amp;&amp; */
+                str_start(line.s,"This message was created automatically by mail delivery software"))
+          flagqsbmf = 1;
+        else if (viruscan_reject_line(&amp;line)) {
+          flagexecutable = 1;
+        }
+        linespastheader = 2;
+      }
+      if (flagqsbmf &amp;&amp; str_start(line.s,"---")) {
+        linespastheader = 0;
+      }
+    }
+    line.len = 0;
+  }
+}
+
+int viruscan_reject_attachment()
+{
+  char inbuf[SUBSTDIO_INSIZE];
+  substdio ssin;
+  char ch;
+
+  substdio_fdbuf(&amp;ssin,read,0,inbuf,sizeof(inbuf));
+
+  for (;;) {
+    if (1 != substdio_get(&amp;ssin,&amp;ch,1)) break;
+    put(&amp;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
</pre></body></html>