diff -urpN -x ,,build greylisting-spp-1.0.1-stock/doc/README greylisting-spp-1.0.1/doc/README
--- greylisting-spp-1.0.1-stock/doc/README	2008-04-27 20:27:48.000000000 +0000
+++ greylisting-spp-1.0.1/doc/README	2008-10-30 17:37:35.000000000 +0000
@@ -81,6 +81,12 @@ logging output:
 		output ends up. The output of GL_DEBUG includes that of
 		GL_VERBOSE.
 
+Additional:
+
+ - GL_WHITELISTNEARBY  If set (to any value), the plugin will whitelist
+                       a whole /24 (if IPv4) or /112 (if IPv6) after a triple
+		       in a subnet is successful.
+
 
 Status
 ======
@@ -114,6 +120,8 @@ Version 1.0 is identical to 0.3
 Version 1.0.1 fixes a bug when GL_DEBUG and RELAYCLIENT are both set.
 Thanks to Jacek Trzcinski for the patch!
 
+Version 1.xxx adds support for SQLite version 3 and GL_WHITELISTNEARBY.
+
 
 Downloading
 ===========
@@ -142,9 +150,10 @@ name on your platform. Or not at all.
 
 By default, the flat-file database implementation will be used. If you want
 to use one of the other implementations, specify the "DB_IMPL" parameter
-to the make command, i. e. "make DB_IMPL=bdb" for Berkeley DB or
-"make DB_IMPL=sqlite" for the SQLite interface. Needless to say, the respective
-libraries and header files must be present on your system. See
+to the make command, i. e. "make DB_IMPL=bdb" for Berkeley DB,
+"make DB_IMPL=sqlite" for SQLite version 2, "make DB_IMPL=sqlite3" for SQLite
+version 3. Needless to say, the respective libraries and header files must be
+present on your system. See
 
 README.db-file, 
 README.db-bdb or
diff -urpN -x ,,build greylisting-spp-1.0.1-stock/doc/README.db-sqlite greylisting-spp-1.0.1/doc/README.db-sqlite
--- greylisting-spp-1.0.1-stock/doc/README.db-sqlite	2008-04-27 20:10:53.000000000 +0000
+++ greylisting-spp-1.0.1/doc/README.db-sqlite	2008-10-30 17:25:48.000000000 +0000
@@ -2,8 +2,7 @@ SQLite
 ======
 
 This database implementation relies on the "SQLite" library and should work
-with version 2.x of the libary (which is the "stable" branch at the time
-this was written).
+with version 2.x and 3.x of the libary.
 See http://www.sqlite.org/ for more information about SQLite.
 
 The GL_DATABASE environment variable must be set to the full path of the
@@ -11,10 +10,11 @@ file containing the greylisting database
 the plugin will automatically try to create it (which requires appropriate
 permissions on the directory).
 
-The database contents can be viewed with the "sqlite" command line utility:
+The database contents can be viewed with the "sqlite"/"sqlite3" command
+line utility:
 
-$ sqlite ,,build/test/dbdir/testdb
-SQLite version 2.8.15
+$ sqlite3 ,,build/test/dbdir/testdb
+SQLite version 3.5.9
 Enter ".help" for instructions
 sqlite> select * from gl_greylist;
 192.168.16.1|junk@tivano.de|junk@tivano.de|+|414868b9
@@ -25,6 +25,13 @@ sqlite> select * from gl_greylist;
 192.168.16.1|bugtraq-return-$-conrad=tivano.de@securityfocus.com|junk01@tivano.de|+|414868bd
 sqlite>
 
+It is recommended that you periodically vacuum the database to free up
+unused space.  Consider adding the following to your /etc/crontab or
+equivalent:
+
+# VACUUM greylisting database daily.
+00 00 * * * root sqlite3 /var/qmail/greylisting/db-file VACUUM
+
 
 Status
 ------
diff -urpN -x ,,build greylisting-spp-1.0.1-stock/doc/README.html greylisting-spp-1.0.1/doc/README.html
--- greylisting-spp-1.0.1-stock/doc/README.html	2008-04-27 20:28:03.000000000 +0000
+++ greylisting-spp-1.0.1/doc/README.html	2008-10-30 17:38:05.000000000 +0000
@@ -98,6 +98,14 @@ logging output:
 		output ends up. The output of GL_DEBUG includes that of
 		GL_VERBOSE.
 </dd></dl>
+<p>
+Additional:
+</p>
+<dl>
+<dt>GL_WHITELISTNEARBY</dt><dd>If set (to any value), the plugin will whitelist
+		a whole /24 (if IPv4) or /112 (if IPv6) after a triple
+		in a subnet is successful.
+</dd></dl>
 
 <h2>Status</h2>
 <p>
@@ -129,6 +137,8 @@ Version 1.0 is identical to 0.3
 </p><p>
 Version 1.0.1 fixes a bug when GL_DEBUG and RELAYCLIENT are both set.
 Thanks to Jacek Trzcinski for the patch!
+</p><p>
+Version 1.xxx adds support for SQLite version 3 and GL_WHITELISTNEARBY.
 </p>
 
 <h2>Downloading</h2>
@@ -157,9 +167,10 @@ name on your platform. Or not at all.
 </p><p>
 By default, the flat-file database implementation will be used. If you want
 to use one of the other implementations, specify the "DB_IMPL" parameter
-to the make command, i. e. "make DB_IMPL=bdb" for Berkeley DB or
-"make DB_IMPL=sqlite" for the SQLite interface. Needless to say, the respective
-libraries and header files must be present on your system. See
+to the make command, i. e. "make DB_IMPL=bdb" for Berkeley DB,
+"make DB_IMPL=sqlite" for SQLite version 2, "make DB_IMPL=sqlite3" for SQLite
+version 3. Needless to say, the respective libraries and header files must be
+present on your system. See
 </p><p>
 <a href=README.db-file>README.db-file</a>, <br>
 <a href=README.db-bdb>README.db-bdb</a> or<br>
diff -urpN -x ,,build greylisting-spp-1.0.1-stock/src/Makefile greylisting-spp-1.0.1/src/Makefile
--- greylisting-spp-1.0.1-stock/src/Makefile	2008-04-27 20:10:53.000000000 +0000
+++ greylisting-spp-1.0.1/src/Makefile	2008-10-30 02:08:31.000000000 +0000
@@ -27,6 +27,9 @@ endif
 ifeq ($(DB_IMPL),sqlite)
 LDFLAGS += -lsqlite
 endif
+ifeq ($(DB_IMPL),sqlite3)
+LDFLAGS += -lsqlite3
+endif
 
 all: greylisting-spp
 
diff -urpN -x ,,build greylisting-spp-1.0.1-stock/src/db-sqlite3.c greylisting-spp-1.0.1/src/db-sqlite3.c
--- greylisting-spp-1.0.1-stock/src/db-sqlite3.c	1970-01-01 00:00:00.000000000 +0000
+++ greylisting-spp-1.0.1/src/db-sqlite3.c	2008-11-06 05:16:42.000000000 +0000
@@ -0,0 +1,406 @@
+/* greylisting-spp - A qmail-spp plugin implementing greylisting
+ *
+ * db-sqlite3.c
+ *
+ *  Copyright (C) 2004 Peter Conrad <conrad@tivano.de>
+ *
+ *  Copyright (C) 2008 Chris Caputo <ccaputo@alt.net>
+ *  - updated for sqlite3
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License (version 2) as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <sqlite3.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "db-api.h"
+#include "commonstuff.h"
+
+static sqlite3	*db = NULL;
+
+static int  result_count;
+static char result_time[8];
+static char result_flag;
+
+static char *parameter[4];
+
+#define ERR_CANT_OPEN_DB	"Can't open database file \""
+#define ERR_CANT_CREATE_DB_FN	"Can't create DB function!\n"
+#define ERR_VERSION_MISMATCH	"Version mismatch in file \""
+#define ERR_WRITING_ENTRY	"Couldn't write entry: "
+#define ERR_READING_ENTRY	"Couldn't read entry: "
+
+/* This file contains functions for manipulating the greylisting database.
+ * The database is an SQLite style database (see http://sqlite.org/ ).
+ * It consists of two simple tables:
+ *  - GL_MGMT contains some key/value pairs for internal use
+ *  - GL_GREYLIST contains greylisting data (i. e. IP, sender, recipient,
+ *    timestamp and flag).
+ */
+
+/** Closes cursor, transaction, db and handle.
+ */
+void closedb() {
+    if (db) {
+	sqlite3_close(db);
+	db = NULL;
+    }
+}
+
+/** Write the read error message to stderr and exit with status 1. */
+static void read_error(char *error) {
+    write(STDERR_FILENO, progname, strlen(progname));
+    write(STDERR_FILENO, ": ", 2);
+    write(STDERR_FILENO, ERR_READING_ENTRY, strlen(ERR_READING_ENTRY));
+    if (error) {
+	write(STDERR_FILENO, error, strlen(error));
+	free(error);
+    } else {
+	write(STDERR_FILENO, "NULL", 4);
+    }
+    write(STDERR_FILENO, "\n" , 1);
+    closedb();
+    exit(1);
+}
+
+/** Write the write error message to stderr and exit with status 1. */
+static void write_error(char *error) {
+    write(STDERR_FILENO, progname, strlen(progname));
+    write(STDERR_FILENO, ": ", 2);
+    write(STDERR_FILENO, ERR_WRITING_ENTRY, strlen(ERR_WRITING_ENTRY));
+    if (error) {
+	write(STDERR_FILENO, error, strlen(error));
+	free(error);
+    } else {
+	write(STDERR_FILENO, "<<NULL>>", 4);
+    }
+    write(STDERR_FILENO, "\n" , 1);
+    closedb();
+    exit(1);
+}
+
+/** Writes to the given buffer 8 hex digits representing the given time.
+ */
+static void timeAsString(char *ts, time_t time) {
+int i = 7;
+
+    while (time) {
+	int digit = time & 0xf;
+	if (digit < 10) {
+	    ts[i] = '0' + digit;
+	} else {
+	    ts[i] = 'a' + digit - 10;
+	}
+	i--;
+	time >>= 4;
+    }
+    while (i >= 0) { ts[i--] = '0'; }
+}
+
+/** Writes to the given buffer 8 hex digits representing the current time.
+ */
+static void currentTimeAsString(char *ts) {
+    timeAsString(ts, time(NULL));
+}
+
+/** Parses the given buffer as a hex string representing a timestamp.
+ *  The buffer must contain at least 8 hex digits.
+ *  Returns -1 in case of an error.
+ */
+static time_t parseTimeString(char *buf) {
+int i;
+time_t ts = 0;
+
+    for (i = 0; i < 8; i++) {
+	ts <<= 4;
+	if (buf[i] >= '0' && buf[i] <= '9') {
+	    ts += buf[i] - '0';
+	} else if (buf[i] >= 'a' && buf[i] <= 'f') {
+	    ts += buf[i] - 'a' + 10;
+	} else {
+	    return -1;
+	}
+    }
+
+    return ts;
+}
+
+static int lck_callback(void *pArg, int argc, char **argv,
+			char **columnNames) {
+    if (!argv) {
+	result_time[0] = 0;
+	return 0;
+    }
+    if (argc != 1) {
+	return -1; /* Expecting 1 result column, the timestamp */
+    }
+    memcpy(result_time, argv[0], 8);
+    return 0;
+}
+
+static int count_callback(void *pArg, int argc, char **argv,
+			  char **columnNames) {
+    result_count = atoi(argv[0]);
+    return 0;
+}
+
+static void gl_parameter(sqlite3_context *cx, int argc, sqlite3_value **argv) {
+    const char *z = (char*)sqlite3_value_text(argv[0]);
+    
+    sqlite3_result_text(cx, parameter[*z - '1'], -1, SQLITE_STATIC);
+}
+
+/** Opens the given database file. Checks if required tables exist and creates
+ *  them if not. Checks the value of 'Version' and 'LastCleanup' and executes
+ * a cleanup statement if it's more than max_wait seconds in the past.
+ */
+void opendb(char *path) {
+time_t now, then;
+char buf[9], *errmsg = NULL;
+int err;
+
+    if (SQLITE_OK != (err = sqlite3_open(path, &db))) {
+	write(STDERR_FILENO, progname, strlen(progname));
+	write(STDERR_FILENO, ": ", 2);
+	write(STDERR_FILENO, ERR_CANT_OPEN_DB, strlen(ERR_CANT_OPEN_DB));
+	errmsg = (char *)sqlite3_errmsg(db);
+	write(STDERR_FILENO, errmsg, strlen(errmsg));
+	write(STDERR_FILENO, "\n" , 1);
+	exit(1);
+    }
+
+    /* Locking + transactions are handled by sqlite - or so the documentation
+     * says... */
+    sqlite3_busy_timeout(db, 5000); /* 5 seconds should be more than enough. */
+
+    /* Hack^H^H^H^HWorkaround for missing parameter binding functions */
+    if (sqlite3_create_function(db, "gl_parameter", 1, SQLITE_UTF8, NULL,
+				&gl_parameter, NULL, NULL)
+	    != SQLITE_OK) {
+	write(STDERR_FILENO, progname, strlen(progname));
+	write(STDERR_FILENO, ": ", 2);
+	write(STDERR_FILENO, ERR_CANT_CREATE_DB_FN,
+	      strlen(ERR_CANT_CREATE_DB_FN));
+	closedb();
+    }
+
+    /* Check if table GL_MGMT exists */
+    if (sqlite3_exec(db, "SELECT COUNT(*) FROM SQLite_Master "
+			 "WHERE Type = 'table' AND name = 'GL_MGMT'",
+		    &count_callback, NULL, &errmsg) != SQLITE_OK) {
+	read_error(errmsg);
+    }
+    if (result_count == 0) {
+	/* If not -> create tables */
+	if (sqlite3_exec(db, "CREATE TABLE GL_MGMT ("
+			     "Name TEXT NOT NULL PRIMARY KEY, "
+			     "Value TEXT NOT NULL)",
+			NULL, NULL, &errmsg) != SQLITE_OK) {
+	    write_error(errmsg);
+	}
+	if (sqlite3_exec(db, "INSERT INTO GL_MGMT (Name, Value) "
+			     "VALUES ('Version', '1.0')",
+			NULL, NULL, &errmsg) != SQLITE_OK) {
+	    write_error(errmsg);
+	}
+	currentTimeAsString(buf);
+	buf[8] = 0;
+	parameter[0] = buf;
+	if (sqlite3_exec(db, "INSERT INTO GL_MGMT (Name, Value) "
+			     "VALUES ('LastCleanup', gl_parameter(1))",
+			NULL, NULL, &errmsg) != SQLITE_OK) {
+	    write_error(errmsg);
+	}
+	if (sqlite3_exec(db, "CREATE TABLE GL_GREYLIST ("
+			     "IP TEXT NOT NULL, "
+			     "Sender TEXT NOT NULL, "
+			     "Recipient TEXT NOT NULL, "
+			     "Flag CHAR(1) NOT NULL, "
+			     "Timestamp CHAR(8) NOT NULL, "
+			     "PRIMARY KEY (IP, Sender, Recipient))",
+			NULL, NULL, &errmsg) != SQLITE_OK) {
+	    write_error(errmsg);
+	}
+	if (sqlite3_exec(db, "CREATE INDEX I_GL_FLAG_TIME "
+			     "ON GL_GREYLIST (Flag, Timestamp)",
+			NULL, NULL, &errmsg) != SQLITE_OK) {
+	    write_error(errmsg);
+	}
+    } else {
+	/* else check Version, check LastCleanupRun */
+	if (sqlite3_exec(db, "SELECT COUNT(*) FROM GL_MGMT "
+			     "WHERE Name = 'Version' AND Value = '1.0'",
+			&count_callback, NULL, &errmsg) != SQLITE_OK) {
+	    read_error(errmsg);
+	}
+	if (result_count == 0) {
+	    write(STDERR_FILENO, progname, strlen(progname));
+	    write(STDERR_FILENO, ": ", 2);
+	    write(STDERR_FILENO, ERR_VERSION_MISMATCH,
+		  strlen(ERR_VERSION_MISMATCH));
+	    write(STDERR_FILENO, path, strlen(path));
+	    write(STDERR_FILENO, "\n" , 1);
+	    closedb();
+	    exit(1);
+	}
+	result_time[0] = 0;
+	if (sqlite3_exec(db, "SELECT Value FROM GL_MGMT "
+			     "WHERE Name = 'LastCleanup'",
+			&lck_callback, NULL, &errmsg) != SQLITE_OK) {
+	    read_error(errmsg);
+	}
+	if (!result_time[0]) {
+	    currentTimeAsString(buf);
+	    buf[8] = 0;
+	    parameter[0] = buf;
+	    if (sqlite3_exec(db, "INSERT INTO GL_MGMT (Name, Value) "
+				 "VALUES ('LastCleanup', gl_parameter(1))",
+			    NULL, NULL, &errmsg) != SQLITE_OK) {
+		write_error(errmsg);
+	    }
+	} else {
+	    then = parseTimeString(result_time);
+	    now = time(NULL);
+	    if (now - then > max_wait) {
+		buf[8] = 0;
+		timeAsString(buf, now - max_wait);
+		parameter[0] = buf;
+		if (sqlite3_exec(db, "DELETE FROM GL_GREYLIST "
+				     "WHERE Flag = '-' "
+				       "AND Timestamp < gl_parameter(1)",
+				NULL, NULL, &errmsg) != SQLITE_OK) {
+		    write_error(errmsg);
+		}
+		timeAsString(buf, now - accept_good);
+		if (sqlite3_exec(db, "DELETE FROM GL_GREYLIST "
+				     "WHERE Flag = '+' "
+				       "AND Timestamp < gl_parameter(1)",
+				NULL, NULL, &errmsg) != SQLITE_OK) {
+		    write_error(errmsg);
+		}
+		timeAsString(buf, now);
+		if (sqlite3_exec(db, "UPDATE GL_MGMT "
+				     "SET Value = gl_parameter(1) "
+				     "WHERE Name = 'LastCleanup'",
+				NULL, NULL, &errmsg) != SQLITE_OK) {
+		    write_error(errmsg);
+		}
+	    }
+	}
+    }
+}
+
+static int find_callback(void *pArg, int argc, char **argv,
+			 char **columnNames) {
+    if (!argv) {
+	result_time[0] = 0;
+	return 0;
+    }
+    if (argc != 2) {
+	return -1; /* Expecting 2 result columns, Timestamp and Flag */
+    }
+    memcpy(result_time, argv[0], 8);
+    result_flag = argv[1][0];
+    return 0;
+}
+
+/** Searches the database for the given entry.
+ *  Returns a negative number if no matching entry was found.
+ *  Returns 0 if a matching entry was found but the min_reject interval has
+ *  not yet expired.
+ *  Otherwise, a positive number is returned.
+ */
+int find_entry(char *ip, char *sender, char *recipient) {
+time_t now, then;
+char *errmsg = NULL;
+
+    parameter[0] = ip;
+    parameter[1] = sender;
+    parameter[2] = recipient;
+    result_time[0] = 0;
+    if (sqlite3_exec(db, "SELECT Timestamp, Flag FROM GL_GREYLIST "
+			 "WHERE IP = gl_parameter(1) "
+			   "AND Sender = gl_parameter(2) "
+			   "AND Recipient = gl_parameter(3) ",
+		    &find_callback, NULL, &errmsg) != SQLITE_OK) {
+	read_error(errmsg);
+    }
+    if (!result_time[0]) { return -1; }
+
+    then = parseTimeString(result_time);
+    now = time(NULL);
+    if ((now - then > max_wait && result_flag == '-')
+            || (now - then > accept_good && result_flag == '+')) {
+        /* Expired -> delete */
+        delete_entry();
+        return -1;
+    }
+
+    return result_flag == '+' || now - then >= min_reject;
+}
+
+/** Updates the entry for our msg_key.
+ */
+void update_entry() {
+char *errmsg = NULL;
+char buf[9];
+
+    currentTimeAsString(buf);
+    buf[8] = 0;
+    parameter[3] = buf;
+    if (sqlite3_exec(db, "UPDATE GL_GREYLIST "
+			 "SET Timestamp = gl_parameter(4), Flag = '+' "
+			 "WHERE IP = gl_parameter(1) "
+			   "AND Sender = gl_parameter(2) "
+			   "AND Recipient = gl_parameter(3) ",
+		    NULL, NULL, &errmsg) != SQLITE_OK) {
+	write_error(errmsg);
+    }
+}
+
+/** Deletes the entry for our msg_key.
+ */
+void delete_entry() {
+char *errmsg = NULL;
+    if (sqlite3_exec(db, "DELETE FROM GL_GREYLIST "
+			 "WHERE IP = gl_parameter(1) "
+			   "AND Sender = gl_parameter(2) "
+			   "AND Recipient = gl_parameter(3) ",
+		    NULL, NULL, &errmsg) != SQLITE_OK) {
+	write_error(errmsg);
+    }
+}
+
+/** Adds a new entry to the file.
+ */
+void add_entry(char *ip, char *sender, char *recipient) {
+char *errmsg = NULL;
+char buf[9];
+
+    parameter[0] = ip;
+    parameter[1] = sender;
+    parameter[2] = recipient;
+    currentTimeAsString(buf);
+    buf[8] = 0;
+    parameter[3] = buf;
+    if (sqlite3_exec(db, "INSERT INTO GL_GREYLIST "
+			 "(IP, Sender, Recipient, Timestamp, Flag) "
+			 "VALUES (gl_parameter(1), gl_parameter(2), "
+				 "gl_parameter(3), gl_parameter(4), '-')",
+		    NULL, NULL, &errmsg) != SQLITE_OK) {
+	write_error(errmsg);
+    }
+}
diff -urpN -x ,,build greylisting-spp-1.0.1-stock/src/greylisting-spp.c greylisting-spp-1.0.1/src/greylisting-spp.c
--- greylisting-spp-1.0.1-stock/src/greylisting-spp.c	2008-04-27 20:18:17.000000000 +0000
+++ greylisting-spp-1.0.1/src/greylisting-spp.c	2008-11-04 20:30:15.000000000 +0000
@@ -16,6 +16,7 @@
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
@@ -33,6 +34,7 @@
 #define	ENV_MAIL_FROM	"SMTPMAILFROM"
 #define ENV_RCPT_TO	"SMTPRCPTTO"
 #define	ENV_REMOTEIP	"TCPREMOTEIP"
+#define	ENV_WHITELISTNEARBY "GL_WHITELISTNEARBY"
 
 #define DEFAULT_MIN_REJECT	300
 #define DEFAULT_MAX_WAIT	86400
@@ -89,7 +91,8 @@ static void reject(char *sender) {
 /** main function of the greylisting plugin */
 int main(int argc, char **argv) {
 char *db, *remoteip, *sender, *ezmlm_sender = NULL, *recipient, *ezmlm_ret;
-int found, i;
+int found, i, wlnearby, wlnew;
+char triple[256];
 
     progname = argv[0];
 
@@ -113,10 +116,29 @@ int found, i;
     sender = get_required_env(ENV_MAIL_FROM);
     recipient = get_required_env(ENV_RCPT_TO);
 
+    snprintf(triple, sizeof(triple), "%s:%s:%s", remoteip, sender, recipient);
+
     min_reject = get_numeric_option(ENV_MIN_REJECT, DEFAULT_MIN_REJECT);
     max_wait = get_numeric_option(ENV_MAX_WAIT, DEFAULT_MAX_WAIT);
     accept_good = get_numeric_option(ENV_ACCEPT_GOOD, DEFAULT_ACCEPT_GOOD);
 
+    if (NULL == getenv(ENV_WHITELISTNEARBY)) {
+	wlnearby = 0;
+    } else {
+        char *p;
+
+	wlnearby = 1;
+
+	/* IPv4: zero-terminate at last '.' */
+	if (NULL != (p = strrchr(remoteip, '.'))) {
+	    *p = 0;
+	}
+	/* IPv6: zero-terminate at last ':' */
+	if (NULL != (p = strrchr(remoteip, ':'))) {
+	    *p = 0;
+	}
+    }
+
     if ((ezmlm_ret = strstr(sender, "-return-"))) {
 	/* special handling for ezmlm's VERPs:
 	 * check if sender address matches '^.*-return-\d+-.*$', if yes
@@ -142,28 +164,52 @@ int found, i;
     }
 
     opendb(db);
-    found = find_entry(remoteip, ezmlm_sender == NULL ? sender : ezmlm_sender,
-		       recipient);
+    if (wlnearby) {
+	found = find_entry(remoteip, "", "");
+	if (found >= 0)
+	    wlnew = 0;
+	else {
+	    wlnew = 1;
+	    found = find_entry(remoteip,
+			       ezmlm_sender == NULL ? sender : ezmlm_sender,
+			       recipient);
+	}
+    } else {
+	found = find_entry(remoteip,
+			   ezmlm_sender == NULL ? sender : ezmlm_sender,
+			   recipient);
+    }
+
     if (found < 0) {
 	/* No match found -> reject and add entry */
-	VERBOSE("Rejecting new triple")
+	VERBOSE2("Rejecting new triple: ", triple)
 	reject(sender);
 	add_entry(remoteip, ezmlm_sender == NULL ? sender : ezmlm_sender,
 		  recipient);
     } else if (found == 0) {
 	/* Match found, but min_reject is not expired -> reject again */
-	VERBOSE("Rejecting young triple")
+	VERBOSE2("Rejecting young triple: ", triple)
 	reject(sender);
     } else {
 	/* Match found and min_reject expired and max_wait or accept_good not
 	 * expired */
-	DEBUG("Accepting known triple")
-	if (!*sender) {
+	if (wlnearby) {
+	    VERBOSE3("Accepting known ",
+	             wlnew ? "triple: " : "approximate IP subnet: ",
+		     triple)
+	} else {
+	    VERBOSE2("Accepting known triple: ", triple)
+	}
+	if (!wlnearby && !*sender) {
 	    /* Empty sender (bounce message) -> delete entry */
 	    delete_entry();
 	} else {
 	    /* not a bounce -> update entry [and accept] */
 	    update_entry();
+	    if (wlnearby && wlnew) {
+		add_entry(remoteip, "", "");
+		update_entry();
+	    }
 	}
     }
     closedb();
