20060717


NOTE:  This version allows you to set the following environment variables for 
connecting to MySQL, etc, so you don't have to recompile each time.  Tnx to 
Joshua Megerman (josh@honorablemenschen.com) for this patch!
Debugging to standard error added by Bill Shupp (hostmaster@shupp.org)


MYSQLHOST (default: "localhost")
MYSQLUSER (default: "milter")
MYSQLPASS (default: "milter")
MYSQLDB   (default: "relaydelay")
BLOCK_EXPIRE  (default: 55   /* minutes until email is accepted */)
RECORD_EXPIRE (default: 500  /* minutes until record expires */)
RECORD_EXPIRE_GOOD  (default: 36)
LOCAL_SCAN_DEBUG  (default: 0, set to 1 to enable debugging to stderr)













diff -urN ../../netqmail-1.05-orig/netqmail-1.05/dbdef.sql ./dbdef.sql
--- ../../netqmail-1.05-orig/netqmail-1.05/dbdef.sql	1969-12-31 18:00:00.000000000 -0600
+++ ./dbdef.sql	2006-07-17 13:51:19.231357400 -0500
@@ -0,0 +1,73 @@
+
+#######################################################################
+# Database definitions
+#######################################################################
+
+# Using Mysql 3.23.2 or later (required for NULLs allowed in indexed fields
+
+#CREATE DATABASE relaydelay;
+#grant select,insert,update,delete tables on relaydelay.* to milter@"localhost" identified by 'milter';
+
+USE relaydelay;
+
+# NOTE: We allow nulls in the 3 "triplet" fields for manual white/black listing.  A null indicates that that field 
+#   should not be considered when looking for a match.  Automatic entries will always have all three populated.
+# Note: Since we index the triplet fields, and allow them to be null, this requires at least Mysql 3.23.2 and MYISAM
+#   tables.
+create table relaytofrom     # Stores settings for each [relay, to, from] triplet
+(
+        id              bigint          NOT NULL        auto_increment, # unique triplet id
+        relay_ip        char(16),                                       # sending relay in IPV4 ascii dotted quad notation
+        mail_from       varchar(255),                                   # ascii address of sender
+        rcpt_to         varchar(255),                                   # the recipient address.
+        block_expires   datetime        NOT NULL,                       # the time that an initial block will/did expire
+        record_expires  datetime        NOT NULL,                       # the date after which this record is ignored
+        
+        blocked_count   bigint          default 0 NOT NULL,             # num of blocked attempts to deliver
+        passed_count    bigint          default 0 NOT NULL,             # num of passed attempts we have allowed
+        aborted_count   bigint          default 0 NOT NULL,             # num of attempts we have passed, but were later aborted
+        origin_type     enum("MANUAL","AUTO") NOT NULL,                 # indicates the origin of this record (auto, or manual)
+        create_time     datetime        NOT NULL,                       # timestamp of creation time of this record
+        last_update     timestamp       NOT NULL,                       # timestamp of last change to this record (automatic)
+
+        primary key(id),
+        key(relay_ip),
+        key(mail_from(20)),                                             # To keep the index size down, only index first 20 chars
+        key(rcpt_to(20))
+);
+
+create table dns_name        # Stores the reverse dns name lookup for records
+(
+       relay_ip      varchar(18)       NOT NULL,
+       relay_name    varchar(255)      NOT NULL,                       # dns name, stored in reversed character order (for index)
+       last_update   timestamp         NOT NULL,                       # timestamp of last change to this record (automatic)
+       primary key(relay_ip),
+       key(relay_name(20))
+);
+
+# This table is not used yet, possibly never will be
+create table mail_log        # Stores a record for every mail delivery attempt
+(
+        id              bigint          NOT NULL        auto_increment, # unique log entry id
+        relay_ip        varchar(16)     NOT NULL,                       # sending relay in IPV4 ascii dotted quad notation
+        relay_name      varchar(255),                                   # sending relay dns name
+        dns_mismatch    bool            NOT NULL,                       # true if does not match, false if matches or no dns
+        mail_from       varchar(255)    NOT NULL,                       # the mail from: address
+        rcpt_to         varchar(255)    NOT NULL,                       # the rcpt to: address
+        rcpt_host       varchar(80)     NOT NULL,                       # the id (hostname) of the host that generated this row
+        create_time     datetime        NOT NULL,                       # timestamp of inserted time, since no updates
+
+        primary key(id),
+        key(relay_ip),
+        key(mail_from(20)),
+        key(rcpt_to(20))
+);
+
+# Example wildcard whitelists for subnets
+insert into relaytofrom values (0,"127.0.0.1"   ,NULL,NULL,"0000-00-00 00:00:00","9999-12-31 23:59:59",0,0,0,"MANUAL",NOW(),NOW());
+insert into relaytofrom values (0,"192.168"     ,NULL,NULL,"0000-00-00 00:00:00","9999-12-31 23:59:59",0,0,0,"MANUAL",NOW(),NOW());
+
+# Example wildcard whitelist entry for a recieved domain or subdomain
+insert into relaytofrom values (0,NULL,NULL,"sub.domain.com","0000-00-00 00:00:00","9999-12-31 23:59:59",0,0,0,"MANUAL",NOW(),NOW());
+
+
diff -urN ../../netqmail-1.05-orig/netqmail-1.05/FILES ./FILES
--- ../../netqmail-1.05-orig/netqmail-1.05/FILES	2006-07-17 13:47:23.184242016 -0500
+++ ./FILES	2006-07-17 13:48:26.975544264 -0500
@@ -432,3 +432,7 @@
 tcp-environ.5
 constmap.h
 constmap.c
+local_scan.c
+qmail-envelope-scanner.c
+dbdef.sql
+local_scan.h 
diff -urN ../../netqmail-1.05-orig/netqmail-1.05/hier.c ./hier.c
--- ../../netqmail-1.05-orig/netqmail-1.05/hier.c	1998-06-15 05:53:16.000000000 -0500
+++ ./hier.c	2006-07-17 13:48:26.982543200 -0500
@@ -127,6 +127,7 @@
   c(auto_qmail,"bin","qmail-qmqpd",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","qmail-qmtpd",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","qmail-smtpd",auto_uido,auto_gidq,0755);
+  c(auto_qmail,"bin","qmail-envelope-scanner",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","sendmail",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","tcp-env",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","qreceipt",auto_uido,auto_gidq,0755);
diff -urN ../../netqmail-1.05-orig/netqmail-1.05/install-big.c ./install-big.c
--- ../../netqmail-1.05-orig/netqmail-1.05/install-big.c	1998-06-15 05:53:16.000000000 -0500
+++ ./install-big.c	2006-07-17 13:48:26.983543048 -0500
@@ -127,6 +127,7 @@
   c(auto_qmail,"bin","qmail-qmqpd",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","qmail-qmtpd",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","qmail-smtpd",auto_uido,auto_gidq,0755);
+  c(auto_qmail,"bin","qmail-envelope-scanner",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","sendmail",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","tcp-env",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","qreceipt",auto_uido,auto_gidq,0755);
diff -urN ../../netqmail-1.05-orig/netqmail-1.05/local_scan.c ./local_scan.c
--- ../../netqmail-1.05-orig/netqmail-1.05/local_scan.c	1969-12-31 18:00:00.000000000 -0600
+++ ./local_scan.c	2006-07-17 13:57:00.175526008 -0500
@@ -0,0 +1,287 @@
+/**************
+ * Copyright (c) mjd@digitaleveryware.com 2003 
+ * GPL Licensed see http://www.gnu.org/licenses/gpl.html
+ * Version 0.04: I don't even have it in cvs yet 
+ * ChangeHistory:
+ * Version 0.04: support for qmail
+ * Version 0.03: added whitelisting by incoming domain address
+ * Version 0.02: added relay_ip matching by /24 subnet
+ *************/
+
+#include "local_scan.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "/usr/include/mysql/mysql.h"
+
+/************************/
+#define DEFAULT_MYSQLHOST "localhost"
+#define DEFAULT_MYSQLUSER "milter"
+#define DEFAULT_MYSQLPASS "milter"
+#define DEFAULT_MYSQLDB   "relaydelay"
+#define DEFAULT_BLOCK_EXPIRE  55   /* minutes until email is accepted */
+#define DEFAULT_RECORD_EXPIRE 500  /* minutes until record expires */
+#define DEFAULT_RECORD_EXPIRE_GOOD  36 /* days until record expires after accepting email */
+#define DEFAULT_LOCAL_SCAN_DEBUG   0 /* set to 1 to enable debugging */
+
+/*************************/
+
+#define SQLCMDSIZE 1024
+
+char *mysqlhost, *mysqluser, *mysqlpass, *mysqldb;
+int block_expire, record_expire, record_expire_good, local_scan_debug;
+
+int mysql_query_wrapper(MYSQL *mysql, char *sqltext) 
+{
+  int result;
+
+  result = mysql_query(mysql,sqltext);
+  if(local_scan_debug == 1) fprintf(stderr,"local_scan: SQL: ret=%d  |%s|\n",result,sqltext);
+  return result;
+}
+
+void AddOrClause(char *subquery,char *in_ipaddr)
+{
+ int loop;
+ char *ptr;
+ char ipaddr[256];
+ 
+
+ strncpy(ipaddr, in_ipaddr, sizeof(ipaddr));
+ for (loop = 0; loop < 4; loop++) {
+   if (loop)
+     strcat(subquery, " OR ");
+   strcat(subquery , "relay_ip = '" );
+   strcat(subquery ,ipaddr);
+   strcat(subquery , "'");
+   ptr = strrchr(ipaddr,'.'); // strip off the last octet
+   if(ptr)
+     *ptr = '\0';
+ }
+}
+
+/* returns TRUE FALSE if record exists in database */
+int checkWhiteListIP( MYSQL *mysql,  int *action) 
+{
+  MYSQL_RES *myres; 
+  MYSQL_ROW myrow;
+
+  char sql[SQLCMDSIZE], sid[32];
+  int  white, black,exists = FALSE;
+
+  /* check for whitelisted sender ip address */
+     sprintf(sql, "SELECT id, block_expires > NOW(), block_expires < NOW() FROM relaytofrom WHERE record_expires > NOW()  AND mail_from IS NULL AND rcpt_to IS NULL AND (");
+    AddOrClause(sql, sender_host_address);
+    strcat(sql, ") ORDER BY length(relay_ip)");
+    //#ifdef local_scan_debug
+    //   fprintf(stderr,"local_scan: check whitelist: %s\n",sql);
+    //#endif
+    if(!mysql_query_wrapper(mysql,sql)) {
+      myres = mysql_store_result(mysql);
+      while ((myrow = mysql_fetch_row(myres)) != NULL) {
+	exists = TRUE;
+	strncpy(sid,myrow[0],sizeof(sid));
+	black = atoi(myrow[1]);
+	white = atoi(myrow[2]);
+      }
+      mysql_free_result(myres); // free memory used by result
+    }
+    if (exists && white) {
+      if(local_scan_debug == 1)  fprintf(stderr,"local_scan: %s -> xxx (%s) Whitelist IP Accept id = %s  \n",sender_address, sender_host_address,sid);
+      *action = LOCAL_SCAN_ACCEPT;
+    }
+    else if (exists && black) {
+      if(local_scan_debug == 1)  fprintf(stderr,"local_scan: %s -> xxx (%s) Blacklist IP Permanent Reject id = %s  \n",sender_address, sender_host_address,sid);
+      *action = LOCAL_SCAN_REJECT;
+    }
+
+  return exists;
+}
+
+
+/* returns TRUE FALSE if record exists in database */
+int checkWhiteListDomain( MYSQL *mysql,  int *action, int i) 
+{
+  MYSQL_RES *myres; 
+  MYSQL_ROW myrow;
+
+  char sql[SQLCMDSIZE], sid[32], *indomain;
+  int  white, black,exists = FALSE;
+
+  indomain = strrchr(recipients_list[i].address,'@');
+  if(indomain != NULL) {
+    indomain++; /* skip the '@' char */
+
+    /* check for whitelisted receiver domain address */
+    sprintf(sql, "SELECT id, block_expires > NOW(), block_expires < NOW() FROM relaytofrom WHERE record_expires > NOW()  AND mail_from IS NULL AND relay_ip IS NULL AND rcpt_to = '%s'",indomain);
+    if(!mysql_query_wrapper(mysql,sql)) {
+      myres = mysql_store_result(mysql);
+      while ((myrow = mysql_fetch_row(myres)) != NULL) {
+	exists = TRUE;
+	strncpy(sid,myrow[0],sizeof(sid));
+	black = atoi(myrow[1]);
+	white = atoi(myrow[2]);
+      }
+      mysql_free_result(myres); // free memory used by result
+    }
+    if (exists && white) {
+      if(local_scan_debug == 1)  fprintf(stderr,"local_scan: %s -> %s (%s) Whitelist Domain Accept id = %s  \n",sender_address, recipients_list[i].address, sender_host_address,sid);
+      *action = LOCAL_SCAN_ACCEPT;
+    }
+    else if (exists && black) {
+      if(local_scan_debug == 1) fprintf(stderr,"local_scan: %s -> xxx (%s) Blacklist Domain Permanent Reject id = %s  \n",sender_address, sender_host_address,sid);
+      *action = LOCAL_SCAN_REJECT;
+    }
+  }
+  return exists;
+}
+
+void  buildLookupSql(char *sql, int recipIndex) 
+{
+  char hostip[32];
+  char *ptr;
+
+  if (FALSE) {
+    /* this first sprintf does an exact match on ipaddr */
+    sprintf(sql,"SELECT id, NOW() > block_expires FROM relaytofrom WHERE record_expires > NOW() AND mail_from = '%s'  AND rcpt_to   = '%s' AND relay_ip  = '%s' order by block_expires desc",sender_address,  recipients_list[recipIndex].address,sender_host_address);
+  }
+  else {
+    /* this second version matches anything in the same /24 subnet */
+    sprintf(sql,"SELECT id, NOW() > block_expires FROM relaytofrom WHERE record_expires > NOW() AND mail_from = '%s'  AND rcpt_to   = '%s' AND relay_ip  like '",sender_address,  recipients_list[recipIndex].address);
+    strcpy(hostip,sender_host_address);
+    /* now remove the last octet and add a '%' */
+    ptr = strrchr(hostip,'.'); // strip off the last octet    
+    if(ptr)
+      *ptr = '\0';
+    strcat(sql,hostip);
+    strcat(sql, "%' order by block_expires desc");
+  }
+}
+
+
+int checkGreylist( MYSQL *mysql,  int *action, int i) 
+{
+ MYSQL_RES *myres; 
+ MYSQL_ROW myrow;
+
+ char sql[SQLCMDSIZE], sid[32];
+ int notexpired, exists = FALSE;
+
+ // lookup to see if record exists
+ buildLookupSql(sql,i);
+ if(!mysql_query_wrapper(mysql,sql)) {
+   myres = mysql_store_result(mysql);
+   while ((myrow = mysql_fetch_row(myres)) != NULL) {
+     exists = TRUE;
+     strncpy(sid,myrow[0],sizeof(sid));
+     notexpired = atoi(myrow[1]);
+     //fprintf(stderr,"local_scan: id = %s  expire = %d\n",sid,notexpired);
+   }
+   mysql_free_result(myres); // free memory used by result
+   // if it exists and is not record expired and the block_expired passes
+   if (exists && notexpired) {
+     // update expire time to 36 days, and pass count
+     sprintf(sql,"update relaytofrom set record_expires = NOW() + INTERVAL %d DAY, passed_count = passed_count + 1 where id ='%s'",record_expire_good,sid);
+     mysql_query_wrapper(mysql, sql);
+     if(local_scan_debug == 1) fprintf(stderr,"local_scan: %s -> %s (%s) Exists Accept id = %s  expire = %d\n",sender_address,recipients_list[i].address, sender_host_address,sid,notexpired);
+     *action = LOCAL_SCAN_ACCEPT;
+   }
+   else if ( exists && ! notexpired) {
+     // update fail count
+     sprintf(sql,"update relaytofrom set blocked_count = blocked_count + 1 where id = %s",sid);
+     mysql_query_wrapper(mysql, sql);
+     if(local_scan_debug == 1) fprintf(stderr,"local_scan: %s -> %s (%s) Exists Block id = %s  expire = %d\n",sender_address,recipients_list[i].address, sender_host_address,sid,notexpired);
+     *action = LOCAL_SCAN_TEMPREJECT;
+   }
+   else /* doesn't exist */ {
+     // it doesn't exist make and entry
+     sprintf(sql,"insert into relaytofrom values (0,'%s','%s','%s',NOW() + INTERVAL %d MINUTE,NOW() + INTERVAL %d MINUTE,1,0,0,'AUTO',NOW(),NOW())",sender_host_address,sender_address,  recipients_list[i].address,block_expire,record_expire);
+     mysql_query_wrapper(mysql,sql);
+     if(local_scan_debug == 1) fprintf(stderr,"local_scan: %s -> %s (%s) Doesn't Exists Block\n",sender_address,recipients_list[i].address, sender_host_address);
+     *action = LOCAL_SCAN_TEMPREJECT;
+   }
+ }
+}
+
+void get_config(void) {
+
+	char *t;
+
+	mysqlhost = getenv("MYSQLHOST");
+	if (!mysqlhost) {
+		mysqlhost = malloc(strlen(DEFAULT_MYSQLHOST)+1);
+		strcpy(mysqlhost, DEFAULT_MYSQLHOST);
+	}
+	mysqluser = getenv("MYSQLUSER");
+	if (!mysqluser) {
+		mysqluser = malloc(strlen(DEFAULT_MYSQLUSER)+1);
+		strcpy(mysqluser, DEFAULT_MYSQLUSER);
+	}
+	mysqlpass = getenv("MYSQLPASS");
+	if (!mysqlpass) {
+		mysqlpass = malloc(strlen(DEFAULT_MYSQLPASS)+1);
+		strcpy(mysqlpass, DEFAULT_MYSQLPASS);
+	}
+	mysqldb = getenv("MYSQLDB");
+	if (!mysqldb) {
+		mysqldb = malloc(strlen(DEFAULT_MYSQLDB)+1);
+		strcpy(mysqldb, DEFAULT_MYSQLDB);
+	}
+	t = getenv("BLOCK_EXPIRE");
+	if (t) {
+		block_expire = atoi(t);
+	} else {
+		block_expire = DEFAULT_BLOCK_EXPIRE;
+	}
+	t = getenv("RECORD_EXPIRE");
+	if (t) {
+		record_expire = atoi(t);
+	} else {
+		record_expire = DEFAULT_RECORD_EXPIRE;
+	}
+	t = getenv("RECORD_EXPIRE_GOOD");
+	if (t) {
+		record_expire_good = atoi(t);
+	} else {
+		record_expire_good = DEFAULT_RECORD_EXPIRE_GOOD;
+	}
+	t = getenv("LOCAL_SCAN_DEBUG");
+	if (t) {
+		local_scan_debug = atoi(t);
+	} else {
+		local_scan_debug = DEFAULT_LOCAL_SCAN_DEBUG;
+	}
+}
+
+int local_scan(int fd, uschar **return_text)
+{
+ MYSQL *mysql = NULL;
+ int i,  ret = LOCAL_SCAN_ACCEPT;
+
+ get_config();
+
+ if(local_scan_debug == 1)  fprintf(stderr,"local_scan: protocol = %s %s  \n",received_protocol,sender_address);
+
+
+ fd = fd;                      /* Keep picky compilers happy */
+ return_text = return_text;                     /* Keep picky compilers happy */
+ mysql = mysql_init(NULL);
+ if (mysql && strcmp(received_protocol,"local") ) {
+   if (mysql_real_connect(mysql,mysqlhost,mysqluser,mysqlpass,mysqldb,0,NULL,0)) {
+     if ( !checkWhiteListIP( mysql, &ret )) {      /* check for whitelisted sender ip address */
+       for(i= 0 ; i <  recipients_count; i++ ) {
+	 if(strlen(sender_host_address) + strlen(sender_address) +
+	    strlen(recipients_list[i].address) < SQLCMDSIZE - 200) { /* check to avoid buffer overflows */
+	   if(!checkWhiteListDomain( mysql, &ret,i ))
+	     checkGreylist(mysql, &ret,i);
+	 }
+       }
+     }
+   }
+ }
+ if(mysql) mysql_close(mysql);
+ if(local_scan_debug == 1) fprintf(stderr, "--------\n");
+ return ret;
+}
+
+/* End of local_scan.c */
diff -urN ../../netqmail-1.05-orig/netqmail-1.05/local_scan.h ./local_scan.h
--- ../../netqmail-1.05-orig/netqmail-1.05/local_scan.h	1969-12-31 18:00:00.000000000 -0600
+++ ./local_scan.h	2006-07-17 13:48:26.985542744 -0500
@@ -0,0 +1,33 @@
+/**************
+ * Copyright (c) mjd@digitaleveryware.com 2003 
+ * GPL Licensed see http://www.gnu.org/licenses/gpl.html
+ * Version 0.04: I don't even have it in cvs yet 
+ * ChangeHistory:
+ * Version 0.04: support for qmail
+ * Version 0.03: added whitelisting by incoming domain address
+ * Version 0.02: added relay_ip matching by /24 subnet
+ *************/
+
+#define FALSE 0
+#define TRUE 1
+#define uschar char
+
+#define LOCAL_SCAN_ACCEPT  0
+#define LOCAL_SCAN_REJECT  100
+#define LOCAL_SCAN_TEMPREJECT  101
+#define SOMETHING_BAD      102
+
+struct recipients_list {
+  char *address;
+};
+
+
+int local_scan(int fd, uschar **return_text);
+
+/* globals used by exim */
+
+extern int recipients_count;
+extern char *received_protocol;
+extern char *sender_host_address;
+extern char *sender_address;
+extern struct recipients_list recipients_list[];
diff -urN ../../netqmail-1.05-orig/netqmail-1.05/Makefile ./Makefile
--- ../../netqmail-1.05-orig/netqmail-1.05/Makefile	2006-07-17 13:47:23.188241408 -0500
+++ ./Makefile	2006-07-17 13:53:21.103829968 -0500
@@ -803,7 +803,7 @@
 predate datemail mailsubj qmail-upq qmail-showctl qmail-newu \
 qmail-pw2u qmail-qread qmail-qstat qmail-tcpto qmail-tcpok \
 qmail-pop3d qmail-popup qmail-qmqpc qmail-qmqpd qmail-qmtpd \
-qmail-smtpd sendmail tcp-env qmail-newmrh config config-fast dnscname \
+qmail-smtpd qmail-envelope-scanner sendmail tcp-env qmail-newmrh config config-fast dnscname \
 dnsptr dnsip dnsmxip dnsfq hostname ipmeprint qreceipt qsmhook qbiff \
 forward preline condredirect bouncesaying except maildirmake \
 maildir2mbox maildirwatch qail elq pinq idedit install-big install \
@@ -1544,6 +1544,18 @@
 	alloc.a substdio.a error.a str.a fs.a auto_qmail.o  `cat \
 	socket.lib`
 
+qmail-envelope-scanner: \
+load qmail-envelope-scanner.o local_scan.o /usr/lib/libmysqlclient.a
+	./load qmail-envelope-scanner -lz -lm local_scan.o /usr/lib/libmysqlclient.a
+
+qmail-envelope-scanner.o: \
+compile qmail-envelope-scanner.c local_scan.h
+	./compile qmail-envelope-scanner.c
+
+local_scan.o: \
+compile local_scan.c local_scan.h
+	./compile local_scan.c
+
 qmail-smtpd.0: \
 qmail-smtpd.8
 	nroff -man qmail-smtpd.8 > qmail-smtpd.0
@@ -1777,7 +1789,7 @@
 qmail-lspawn.c qmail-newmrh.c qmail-newu.c qmail-pop3d.c \
 qmail-popup.c qmail-pw2u.c qmail-qmqpc.c qmail-qmqpd.c qmail-qmtpd.c \
 qmail-qread.c qmail-qstat.sh qmail-queue.c qmail-remote.c \
-qmail-rspawn.c qmail-send.c qmail-showctl.c qmail-smtpd.c \
+qmail-rspawn.c qmail-send.c qmail-showctl.c qmail-smtpd.c qmail-envelope-scanner.c \
 qmail-start.c qmail-tcpok.c qmail-tcpto.c spawn.c dnscname.c dnsfq.c \
 dnsip.c dnsmxip.c dnsptr.c hostname.c ipmeprint.c tcp-env.c \
 sendmail.c qreceipt.c qsmhook.c qbiff.c forward.c preline.c predate.c \
diff -urN ../../netqmail-1.05-orig/netqmail-1.05/qmail-envelope-scanner.c ./qmail-envelope-scanner.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-envelope-scanner.c	1969-12-31 18:00:00.000000000 -0600
+++ ./qmail-envelope-scanner.c	2006-07-17 13:48:26.985542744 -0500
@@ -0,0 +1,33 @@
+/**************
+ * Copyright (c) mjd@digitaleveryware.com 2003 
+ * GPL Licensed see http://www.gnu.org/licenses/gpl.html
+ * Version 0.04: I don't even have it in cvs yet 
+ * ChangeHistory:
+ * Version 0.04: support for qmail
+ * Version 0.03: added whitelisting by incoming domain address
+ * Version 0.02: added relay_ip matching by /24 subnet
+ *************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "local_scan.h"
+
+
+char *received_protocol;
+char *sender_host_address;
+char *sender_address;
+struct recipients_list recipients_list[1];
+int recipients_count = 1;
+
+int main(int argv, char *argc[])
+{
+  received_protocol = "notneeded4qmail";
+  sender_host_address = getenv("TCPREMOTEIP");
+  recipients_list[0].address = argc[2];
+  sender_address = argc[1];
+
+  if (argv == 3 && sender_host_address != NULL)
+    return local_scan(1,NULL);
+  else
+    return SOMETHING_BAD;
+}
diff -urN ../../netqmail-1.05-orig/netqmail-1.05/qmail-smtpd.c ./qmail-smtpd.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-smtpd.c	2006-07-17 13:47:23.199239736 -0500
+++ ./qmail-smtpd.c	2006-07-17 13:48:26.987542440 -0500
@@ -19,6 +19,8 @@
 #include "env.h"
 #include "now.h"
 #include "exit.h"
+#include "fork.h"
+#include "wait.h"
 #include "rcpthosts.h"
 #include "timeoutread.h"
 #include "timeoutwrite.h"
@@ -49,6 +51,8 @@
 void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); }
 void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); }
 
+void err_tempfail() { out("421 temporary envelope failure (#4.3.0)\r\n"); }
+void err_permfail() { out("553 sorry, permanent envelope failure (#5.7.1)\r\n"); }
 void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); }
 void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }
 void err_unimpl(arg) char *arg; { out("502 unimplemented (#5.5.1)\r\n"); }
@@ -222,6 +226,37 @@
 stralloc mailfrom = {0};
 stralloc rcptto = {0};
 
+int envelope_scanner()
+{
+  int child;
+  int wstat;
+  char *envelope_scannerarg[] = { "bin/qmail-envelope-scanner", mailfrom.s, addr.s, 0 };
+
+  switch(child = vfork()) {
+    case -1:
+      return 1;
+    case 0:
+      execv(*envelope_scannerarg,envelope_scannerarg);
+      _exit(111);
+  }
+
+  wait_pid(&wstat,child);
+  if (wait_crashed(wstat)) {
+    return 1;
+  }
+
+  switch(wait_exitcode(wstat)) {
+    case 101:
+      err_tempfail();
+      return 0;
+    case 100:
+      err_permfail();
+      return 0;
+    default:
+      return 1;
+  }
+}
+
 void smtp_helo(arg) char *arg;
 {
   smtp_greet("250 "); out("\r\n");
@@ -256,8 +291,10 @@
     if (!stralloc_cats(&addr,relayclient)) die_nomem();
     if (!stralloc_0(&addr)) die_nomem();
   }
-  else
+  else {
     if (!addrallowed()) { err_nogateway(); return; }
+    if (!envelope_scanner()) return;
+  }
   if (!stralloc_cats(&rcptto,"T")) die_nomem();
   if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
   if (!stralloc_0(&rcptto)) die_nomem();
diff -urN ../../netqmail-1.05-orig/netqmail-1.05/TARGETS ./TARGETS
--- ../../netqmail-1.05-orig/netqmail-1.05/TARGETS	1998-06-15 05:53:16.000000000 -0500
+++ ./TARGETS	2006-07-17 13:48:26.979543656 -0500
@@ -385,3 +385,6 @@
 man
 setup
 check
+local_scan.o
+qmail-envelope-scanner.o
+qmail-envelope-scanner
