Search
j0ke.net Open Build Service
>
Projects
>
server:mail
>
qmail-spp-plugins-greylisting
> greylisting-spp-1.0.1.patch
Sign Up
|
Log In
Username
Password
Cancel
Overview
Repositories
Revisions
Requests
Users
Advanced
Attributes
Meta
File greylisting-spp-1.0.1.patch of Package qmail-spp-plugins-greylisting
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();