Adds Message-Ids to an external database as they flow through Postfix, keeping the most recent time seen with each. The goal is to maintain a database of all Message-Ids sent from my system in the past N days. A content-filter can look for mail delivery status notifications which do not contain a Message-Id found in this database. Those can be safely assumed to be backscatter email and rejected. A cron job could remove stale entries from the database. This patch overshoots slightly and tracks _all_ Message-Ids seen by Postfix. That's probably harmless. diff -ru postfix-2.2.8/src/cleanup/cleanup.h postfix-2.2.8-msgid/src/cleanup/cleanup.h --- postfix-2.2.8/src/cleanup/cleanup.h 2005-02-12 14:53:25.000000000 -0800 +++ postfix-2.2.8-msgid/src/cleanup/cleanup.h 2006-01-29 09:23:09.000000000 -0800 @@ -93,6 +93,11 @@ extern MAPS *cleanup_send_bcc_maps; extern MAPS *cleanup_rcpt_bcc_maps; +/* + * Message-ID database. + */ +extern DICT *cleanup_messageid_db; + /* * Address canonicalization fine control. */ diff -ru postfix-2.2.8/src/cleanup/cleanup_init.c postfix-2.2.8-msgid/src/cleanup/cleanup_init.c --- postfix-2.2.8/src/cleanup/cleanup_init.c 2004-10-25 13:59:02.000000000 -0700 +++ postfix-2.2.8-msgid/src/cleanup/cleanup_init.c 2006-01-29 10:07:57.000000000 -0800 @@ -122,6 +122,7 @@ char *var_send_bcc_maps; /* sender auto-bcc maps */ char *var_rcpt_bcc_maps; /* recipient auto-bcc maps */ char *var_remote_rwr_domain; /* header-only surrogate */ +char *var_messageid_db; /* message-id database */ CONFIG_INT_TABLE cleanup_int_table[] = { VAR_HOPCOUNT_LIMIT, DEF_HOPCOUNT_LIMIT, &var_hopcount_limit, 1, 0, @@ -165,6 +166,7 @@ VAR_SEND_BCC_MAPS, DEF_SEND_BCC_MAPS, &var_send_bcc_maps, 0, 0, VAR_RCPT_BCC_MAPS, DEF_RCPT_BCC_MAPS, &var_rcpt_bcc_maps, 0, 0, VAR_REM_RWR_DOMAIN, DEF_REM_RWR_DOMAIN, &var_remote_rwr_domain, 0, 0, + VAR_MESSAGEID_DB, DEF_MESSAGEID_DB, &var_messageid_db, 0, 0, 0, }; @@ -188,6 +190,11 @@ MAPS *cleanup_send_bcc_maps; MAPS *cleanup_rcpt_bcc_maps; +/* + * Message-ID database. + */ +DICT *cleanup_messageid_db; + /* * Address extension propagation restrictions. */ @@ -286,6 +293,11 @@ cleanup_rcpt_bcc_maps = maps_create(VAR_RCPT_BCC_MAPS, var_rcpt_bcc_maps, DICT_FLAG_LOCK); + if (*var_messageid_db) + cleanup_messageid_db = + dict_open(var_messageid_db, O_RDWR|O_CREAT|O_APPEND, + DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK + | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_PARANOID); flush_init(); } diff -ru postfix-2.2.8/src/cleanup/cleanup_message.c postfix-2.2.8-msgid/src/cleanup/cleanup_message.c --- postfix-2.2.8/src/cleanup/cleanup_message.c 2005-11-26 17:48:44.000000000 -0800 +++ postfix-2.2.8-msgid/src/cleanup/cleanup_message.c 2006-01-29 09:28:16.000000000 -0800 @@ -85,6 +85,20 @@ #include "cleanup.h" +/* cleanup_store_messageid - store Message-IDs for backscatter analysis */ + +static void cleanup_store_messageid(char *msgid, time_t time) +{ + if (cleanup_messageid_db) { + char *timestr = NULL; + asprintf(×tr, "%d", time); + if (timestr == NULL) + msg_fatal("cleanup_store_messageid: insufficient memory"); + cleanup_messageid_db->update(cleanup_messageid_db, msgid, timestr); + free(timestr); + } +} + /* cleanup_out_header - output one header as a bunch of records */ static void cleanup_out_header(CLEANUP_STATE *state, VSTRING *header_buf) @@ -533,10 +547,14 @@ */ else { state->headers_seen |= (1 << hdr_opts->type); - if (hdr_opts->type == HDR_MESSAGE_ID) + if (hdr_opts->type == HDR_MESSAGE_ID) { msg_info("%s: message-id=%s", state->queue_id, hdrval); - if (hdr_opts->type == HDR_RESENT_MESSAGE_ID) + cleanup_store_messageid(hdrval, state->time); + } + if (hdr_opts->type == HDR_RESENT_MESSAGE_ID) { msg_info("%s: resent-message-id=%s", state->queue_id, hdrval); + cleanup_store_messageid(hdrval, state->time); + } if (hdr_opts->type == HDR_RECEIVED) if (++state->hop_count >= var_hopcount_limit) state->errs |= CLEANUP_STAT_HOPS; @@ -583,14 +601,21 @@ */ if ((state->headers_seen & (1 << (state->resent[0] ? HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) { + char *messageid = NULL; tv = state->handle->ctime.tv_sec; tp = gmtime(&tv); strftime(time_stamp, sizeof(time_stamp), "%Y%m%d%H%M%S", tp); - cleanup_out_format(state, REC_TYPE_NORM, "%sMessage-Id: <%s.%s@%s>", - state->resent, time_stamp, state->queue_id, var_myhostname); - msg_info("%s: %smessage-id=<%s.%s@%s>", - state->queue_id, *state->resent ? "resent-" : "", + asprintf(&messageid, "%s.%s@%s", time_stamp, state->queue_id, var_myhostname); + if (messageid == NULL) + msg_fatal("cleanup_header_done_callback: insufficient memory"); + cleanup_out_format(state, REC_TYPE_NORM, "%sMessage-Id: <%s>", + state->resent, messageid); + msg_info("%s: %smessage-id=<%s>", + state->queue_id, *state->resent ? "resent-" : "", + messageid); + cleanup_store_messageid(messageid, state->time); + free(messageid); } /* diff -ru postfix-2.2.8/src/global/mail_params.h postfix-2.2.8-msgid/src/global/mail_params.h --- postfix-2.2.8/src/global/mail_params.h 2005-02-27 07:06:07.000000000 -0800 +++ postfix-2.2.8-msgid/src/global/mail_params.h 2006-01-29 09:22:00.000000000 -0800 @@ -2327,6 +2327,13 @@ #define DEF_LOC_RWR_CLIENTS PERMIT_INET_INTERFACES extern char *var_local_rwr_clients; +/* + * Where to store outgoing Message-IDs for backscatter analysis. + */ +#define VAR_MESSAGEID_DB "messageid_db" +#define DEF_MESSAGEID_DB "" +extern char *var_messageid_db; + /* * EHLO keyword filter. */