Quelle postscreen_dnsbl.c
Sprache: C
/*++
/* NAME
/* postscreen_dnsbl 3
/* SUMMARY
/* postscreen DNSBL support
/* SYNOPSIS
/* #include <postscreen.h>
/*
/* void psc_dnsbl_init(void)
/*
/* int psc_dnsbl_request(client_addr, callback, context)
/* char *client_addr;
/* void (*callback)(int, char *);
/* char *context;
/*
/* int psc_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index,
/* dnsbl_ttl)
/* char *client_addr;
/* const char **dnsbl_name;
/* int dnsbl_index;
/* int *dnsbl_ttl;
/* DESCRIPTION
/* This module implements preliminary support for DNSBL lookups.
/* Multiple requests for the same information are handled with
/* reference counts.
/*
/* psc_dnsbl_init() initializes this module, and must be called
/* once before any of the other functions in this module.
/*
/* psc_dnsbl_request() requests a blocklist score for the
/* specified client IP address and increments the reference
/* count. The request completes in the background. The client
/* IP address must be in inet_ntop(3) output format. The
/* callback argument specifies a function that is called when
/* the requested result is available. The context is passed
/* on to the callback function. The callback should ignore its
/* first argument (it exists for compatibility with Postfix
/* generic event infrastructure).
/* The result value is the index for the psc_dnsbl_retrieve()
/* call.
/*
/* psc_dnsbl_retrieve() retrieves the result score and reply
/* TTL requested with psc_dnsbl_request(), and decrements the
/* reference count. The reply TTL value is clamped to
/* postscreen_dnsbl_min_ttl and postscreen_dnsbl_max_ttl. It
/* is an error to retrieve a score without requesting it first.
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <sys/socket.h> /* AF_INET */
#include <netinet/in.h> /* inet_pton() */
#include <arpa/inet.h> /* inet_pton() */
#include <stdio.h> /* sscanf */
#include <limits.h>
/* Utility library. */
#include <msg.h>
#include <mymalloc.h>
#include <argv.h>
#include <htable.h>
#include <events.h>
#include <vstream.h>
#include <connect.h>
#include <split_at.h>
#include <valid_hostname.h>
#include <ip_match.h>
#include <myaddrinfo.h>
#include <stringops.h>
/* Global library. */
#include <mail_params.h>
#include <mail_proto.h>
/* Application-specific. */
#include <postscreen.h>
/*
* Talking to the DNSBLOG service .
*/
static char *psc_dnsbl_service;
/*
* Per - DNSBL filters and weights .
*
* The postscreen_dnsbl_sites parameter specifies zero or more DNSBL domains .
* We provide multiple access methods , one for quick iteration when sending
* queries to all DNSBL servers , and one for quick location when receiving a
* reply from one DNSBL server .
*
* Each DNSBL domain can be specified more than once , each time with a
* different ( filter , weight ) pair . We group ( filter , weight ) pairs in a
* linked list under their DNSBL domain name . The list head has a reference
* to a " safe name " for the DNSBL , in case the name includes a password .
*/
static HTABLE *dnsbl_site_cache; /* indexed by DNSBNL domain */
static HTABLE_INFO **dnsbl_site_list; /* flattened cache */
typedef struct {
const char *safe_dnsbl; /* from postscreen_dnsbl_reply_map */
struct PSC_DNSBL_SITE *first; /* list of (filter, weight) tuples */
} PSC_DNSBL_HEAD;
typedef struct PSC_DNSBL_SITE {
char *filter; /* printable filter (default: null) */
char *byte_codes; /* encoded filter (default: null) */
int weight; /* reply weight (default: 1) */
struct PSC_DNSBL_SITE *next; /* linked list */
} PSC_DNSBL_SITE;
/*
* Per - client DNSBL scores .
*
* Some SMTP clients make parallel connections . This can trigger parallel
* blocklist score requests when the pre - handshake delays of the connections
* overlap .
*
* We combine requests for the same score under the client IP address in a
* single reference - counted entry . The reference count goes up with each
* request for a score , and it goes down with each score retrieval . Each
* score has one or more requestors that need to be notified when the result
* is ready , so that postscreen can terminate a pre - handshake delay when all
* pre - handshake tests are completed .
*/
static HTABLE *dnsbl_score_cache; /* indexed by client address */
typedef struct {
void (*callback) (int , void *); /* generic call-back routine */
void *context; /* generic call-back argument */
} PSC_CALL_BACK_ENTRY;
typedef struct {
const char *dnsbl_name; /* DNSBL with largest contribution */
int dnsbl_weight; /* weight of largest contribution */
int total; /* combined allow+denylist score */
int fail_ttl; /* combined reply TTL */
int pass_ttl; /* combined reply TTL */
int refcount; /* score reference count */
int pending_lookups; /* nr of DNS requests in flight */
int request_id; /* duplicate suppression */
/* Call-back table support. */
int index; /* next table index */
int limit; /* last valid index */
PSC_CALL_BACK_ENTRY table[1 ]; /* actually a bunch */
} PSC_DNSBL_SCORE;
#define PSC_CALL_BACK_INIT(sp) do { \
(sp)->limit = 0 ; \
(sp)->index = 0 ; \
} while (0 )
#define PSC_CALL_BACK_INDEX_OF_LAST(sp) ((sp)->index - 1 )
#define PSC_CALL_BACK_CANCEL(sp, idx) do { \
PSC_CALL_BACK_ENTRY *_cb_; \
if ((idx) < 0 || (idx) >= (sp)->index) \
msg_panic("%s: index %d must be >= 0 and < %d" , \
myname, (idx), (sp)->index); \
_cb_ = (sp)->table + (idx); \
event_cancel_timer(_cb_->callback, _cb_->context); \
_cb_->callback = 0 ; \
_cb_->context = 0 ; \
} while (0 )
#define PSC_CALL_BACK_EXTEND(hp, sp) do { \
if ((sp)->index >= (sp)->limit) { \
int _count_ = ((sp)->limit ? (sp)->limit * 2 : 5 ); \
(hp)->value = myrealloc((void *) (sp), sizeof (*(sp)) + \
_count_ * sizeof ((sp)->table)); \
(sp) = (PSC_DNSBL_SCORE *) (hp)->value; \
(sp)->limit = _count_; \
} \
} while (0 )
#define PSC_CALL_BACK_ENTER(sp, fn, ctx) do { \
PSC_CALL_BACK_ENTRY *_cb_ = (sp)->table + (sp)->index++; \
_cb_->callback = (fn); \
_cb_->context = (ctx); \
} while (0 )
#define PSC_CALL_BACK_NOTIFY(sp, ev) do { \
PSC_CALL_BACK_ENTRY *_cb_; \
for (_cb_ = (sp)->table; _cb_ < (sp)->table + (sp)->index; _cb_++) \
if (_cb_->callback != 0 ) \
_cb_->callback((ev), _cb_->context); \
} while (0 )
#define PSC_NULL_EVENT (0 )
/*
* Per - request state .
*
* This implementation stores the client IP address and DNSBL domain in the
* DNSBLOG query / reply stream . This simplifies code , and allows the DNSBLOG
* server to produce more informative logging .
*/
static VSTRING *reply_client; /* client address in DNSBLOG reply */
static VSTRING *reply_dnsbl; /* domain in DNSBLOG reply */
static VSTRING *reply_addr; /* address list in DNSBLOG reply */
/* psc_dnsbl_add_site - add DNSBL site information */
static void psc_dnsbl_add_site(const char *site)
{
const char *myname = "psc_dnsbl_add_site" ;
char *saved_site = mystrdup(site);
VSTRING *byte_codes = 0 ;
PSC_DNSBL_HEAD *head;
PSC_DNSBL_SITE *new_site;
char junk;
const char *weight_text;
char *pattern_text;
int weight;
HTABLE_INFO *ht;
char *parse_err;
const char *safe_dnsbl;
/*
* Parse the required DNSBL domain name , the optional reply filter and
* the optional reply weight factor .
*/
#define DO_GRIPE 1
/* Negative weight means allowlist. */
if ((weight_text = split_at(saved_site, '*' )) != 0 ) {
if (sscanf(weight_text, "%d%c" , &weight, &junk) != 1 )
msg_fatal("bad DNSBL weight factor \" %s\" in \" %s\"" ,
weight_text, site);
} else {
weight = 1 ;
}
/* Reply filter. */
if ((pattern_text = split_at(saved_site, '=' )) != 0 ) {
byte_codes = vstring_alloc(100 );
if ((parse_err = ip_match_parse(byte_codes, pattern_text)) != 0 )
msg_fatal("bad DNSBL filter syntax: %s" , parse_err);
}
if (valid_hostname(saved_site, DO_GRIPE) == 0 )
msg_fatal("bad DNSBL domain name \" %s\" in \" %s\"" ,
saved_site, site);
if (msg_verbose > 1 )
msg_info("%s: \" %s\" -> domain=\" %s\" pattern=\" %s\" weight=%d" ,
myname, site, saved_site, pattern_text ? pattern_text :
"null" , weight);
/*
* Look up or create the ( filter , weight ) list head for this DNSBL domain
* name .
*/
if ((head = (PSC_DNSBL_HEAD *)
htable_find(dnsbl_site_cache, saved_site)) == 0 ) {
head = (PSC_DNSBL_HEAD *) mymalloc(sizeof (*head));
ht = htable_enter(dnsbl_site_cache, saved_site, (void *) head);
/* Translate the DNSBL name into a safe name if available. */
if (psc_dnsbl_reply == 0
|| (safe_dnsbl = dict_get(psc_dnsbl_reply, saved_site)) == 0 )
safe_dnsbl = ht->key;
head->safe_dnsbl = mystrdup(safe_dnsbl);
if (psc_dnsbl_reply && psc_dnsbl_reply->error)
msg_fatal("%s:%s lookup error" , psc_dnsbl_reply->type,
psc_dnsbl_reply->name);
head->first = 0 ;
}
/*
* Append the new ( filter , weight ) node to the list for this DNSBL domain
* name .
*/
new_site = (PSC_DNSBL_SITE *) mymalloc(sizeof (*new_site));
new_site->filter = (pattern_text ? mystrdup(pattern_text) : 0 );
new_site->byte_codes = (byte_codes ? ip_match_save(byte_codes) : 0 );
new_site->weight = weight;
new_site->next = head->first;
head->first = new_site;
myfree(saved_site);
if (byte_codes)
vstring_free(byte_codes);
}
/* psc_dnsbl_match - match DNSBL reply filter */
static int psc_dnsbl_match(const char *filter, ARGV *reply)
{
char addr_buf[MAI_HOSTADDR_STRSIZE];
char **cpp;
/*
* Run the replies through the pattern - matching engine .
*/
for (cpp = reply->argv; *cpp != 0 ; cpp++) {
if (inet_pton(AF_INET, *cpp, addr_buf) != 1 )
msg_warn("address conversion error for %s -- ignoring this reply" ,
*cpp);
if (ip_match_execute(filter, addr_buf))
return (1 );
}
return (0 );
}
/* psc_dnsbl_retrieve - retrieve blocklist score, decrement reference count */
int psc_dnsbl_retrieve(const char *client_addr, const char **dnsbl_name,
int dnsbl_index, int *dnsbl_ttl)
{
const char *myname = "psc_dnsbl_retrieve" ;
PSC_DNSBL_SCORE *score;
int result_score;
int result_ttl;
/*
* Sanity check .
*/
if ((score = (PSC_DNSBL_SCORE *)
htable_find(dnsbl_score_cache, client_addr)) == 0 )
msg_panic("%s: no blocklist score for %s" , myname, client_addr);
/*
* Disable callbacks .
*/
PSC_CALL_BACK_CANCEL(score, dnsbl_index);
/*
* Reads are destructive .
*/
result_score = score->total;
*dnsbl_name = score->dnsbl_name;
result_ttl = (result_score > 0 ) ? score->fail_ttl : score->pass_ttl;
/* As with dnsblog(8), a value < 0 means no reply TTL. */
if (result_ttl < var_psc_dnsbl_min_ttl)
result_ttl = var_psc_dnsbl_min_ttl;
if (result_ttl > var_psc_dnsbl_max_ttl)
result_ttl = var_psc_dnsbl_max_ttl;
*dnsbl_ttl = result_ttl;
if (msg_verbose)
msg_info("%s: addr=%s score=%d ttl=%d" ,
myname, client_addr, result_score, result_ttl);
score->refcount -= 1 ;
if (score->refcount < 1 ) {
if (msg_verbose > 1 )
msg_info("%s: delete blocklist score for %s" , myname, client_addr);
htable_delete(dnsbl_score_cache, client_addr, myfree);
}
return (result_score);
}
/* psc_dnsbl_receive - receive DNSBL reply, update blocklist score */
static void psc_dnsbl_receive(int event, void *context)
{
const char *myname = "psc_dnsbl_receive" ;
VSTREAM *stream = (VSTREAM *) context;
PSC_DNSBL_SCORE *score;
PSC_DNSBL_HEAD *head;
PSC_DNSBL_SITE *site;
ARGV *reply_argv;
int request_id;
int dnsbl_ttl;
PSC_CLEAR_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, context);
/*
* Receive the DNSBL lookup result .
*
* This is preliminary code to explore the field . Later , DNSBL lookup will
* be handled by an UDP - based DNS client that is built directly into some
* Postfix daemon .
*
* Don ' t bother looking up the blocklist score when the client IP address is
* not listed at the DNSBL .
*
* Don ' t panic when the blocklist score no longer exists . It may be deleted
* when the client triggers a " drop " action after pregreet , when the
* client does not pregreet and the DNSBL reply arrives late , or when the
* client triggers a " drop " action after hanging up .
*/
if (event == EVENT_READ
&& attr_scan(stream,
ATTR_FLAG_STRICT,
RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, reply_dnsbl),
RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, reply_client),
RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id),
RECV_ATTR_STR(MAIL_ATTR_RBL_ADDR, reply_addr),
RECV_ATTR_INT(MAIL_ATTR_TTL, &dnsbl_ttl),
ATTR_TYPE_END) == 5
&& (score = (PSC_DNSBL_SCORE *)
htable_find(dnsbl_score_cache, STR(reply_client))) != 0
&& score->request_id == request_id) {
/*
* Run this response past all applicable DNSBL filters and update the
* blocklist score for this client IP address .
*
* Don ' t panic when the DNSBL domain name is not found . The DNSBLOG
* server may be messed up .
*/
if (msg_verbose > 1 )
msg_info("%s: client=\" %s\" score=%d domain=\" %s\" reply=\" %d %s\"" ,
myname, STR(reply_client), score->total,
STR(reply_dnsbl), dnsbl_ttl, STR(reply_addr));
head = (PSC_DNSBL_HEAD *)
htable_find(dnsbl_site_cache, STR(reply_dnsbl));
if (head == 0 ) {
/* Bogus domain. Do nothing. */
} else if (*STR(reply_addr) != 0 ) {
/* DNS reputation record(s) found. */
reply_argv = 0 ;
for (site = head->first; site != 0 ; site = site->next) {
if (site->byte_codes == 0
|| psc_dnsbl_match(site->byte_codes, reply_argv ? reply_argv :
(reply_argv = argv_split(STR(reply_addr), " " )))) {
if (score->dnsbl_name == 0
|| score->dnsbl_weight < site->weight) {
score->dnsbl_name = head->safe_dnsbl;
score->dnsbl_weight = site->weight;
}
score->total += site->weight;
if (msg_verbose > 1 )
msg_info("%s: filter=\" %s\" weight=%d score=%d" ,
myname, site->filter ? site->filter : "null" ,
site->weight, score->total);
}
/* As with dnsblog(8), a value < 0 means no reply TTL. */
if (site->weight > 0 ) {
if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
score->fail_ttl = dnsbl_ttl;
} else {
if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
score->pass_ttl = dnsbl_ttl;
}
}
if (reply_argv != 0 )
argv_free(reply_argv);
} else {
/* No DNS reputation record found. */
for (site = head->first; site != 0 ; site = site->next) {
/* As with dnsblog(8), a value < 0 means no reply TTL. */
if (site->weight > 0 ) {
if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
score->pass_ttl = dnsbl_ttl;
} else {
if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
score->fail_ttl = dnsbl_ttl;
}
}
}
/*
* Notify the requestor ( s ) that the result is ready to be picked up .
* If this call isn ' t made , clients have to sit out the entire
* pre - handshake delay .
*/
score->pending_lookups -= 1 ;
if (score->pending_lookups == 0 )
PSC_CALL_BACK_NOTIFY(score, PSC_NULL_EVENT);
} else if (event == EVENT_TIME) {
msg_warn("dnsblog reply timeout %ds for %s" ,
var_psc_dnsbl_tmout, (char *) vstream_context(stream));
}
/* Here, score may be a null pointer. */
vstream_fclose(stream);
}
/* psc_dnsbl_request - send dnsbl query, increment reference count */
int psc_dnsbl_request(const char *client_addr,
void (*callback) (int , void *),
void *context)
{
const char *myname = "psc_dnsbl_request" ;
int fd;
VSTREAM *stream;
HTABLE_INFO **ht;
PSC_DNSBL_SCORE *score;
HTABLE_INFO *hash_node;
static int request_count;
/*
* Some spambots make several connections at nearly the same time ,
* causing their pregreet delays to overlap . Such connections can share
* the efforts of DNSBL lookup .
*
* We store a reference - counted DNSBL score under its client IP address . We
* increment the reference count with each score request , and decrement
* the reference count with each score retrieval .
*
* Do not notify the requestor NOW when the DNS replies are already in .
* Reason : we must not make a backwards call while we are still in the
* middle of executing the corresponding forward call . Instead we create
* a zero - delay timer request and call the notification function from
* there .
*
* psc_dnsbl_request ( ) could instead return a result value to indicate that
* the DNSBL score is already available , but that would complicate the
* caller with two different notification code paths : one asynchronous
* code path via the callback invocation , and one synchronous code path
* via the psc_dnsbl_request ( ) result value . That would be a source of
* future bugs .
*/
if ((hash_node = htable_locate(dnsbl_score_cache, client_addr)) != 0 ) {
score = (PSC_DNSBL_SCORE *) hash_node->value;
score->refcount += 1 ;
PSC_CALL_BACK_EXTEND(hash_node, score);
PSC_CALL_BACK_ENTER(score, callback, context);
if (msg_verbose > 1 )
msg_info("%s: reuse blocklist score for %s refcount=%d pending=%d" ,
myname, client_addr, score->refcount,
score->pending_lookups);
if (score->pending_lookups == 0 )
event_request_timer(callback, context, EVENT_NULL_DELAY);
return (PSC_CALL_BACK_INDEX_OF_LAST(score));
}
if (msg_verbose > 1 )
msg_info("%s: create blocklist score for %s" , myname, client_addr);
score = (PSC_DNSBL_SCORE *) mymalloc(sizeof (*score));
score->request_id = request_count++;
score->dnsbl_name = 0 ;
score->dnsbl_weight = 0 ;
/* As with dnsblog(8), a value < 0 means no reply TTL. */
score->pass_ttl = -1 ;
score->fail_ttl = -1 ;
score->total = 0 ;
score->refcount = 1 ;
score->pending_lookups = 0 ;
PSC_CALL_BACK_INIT(score);
PSC_CALL_BACK_ENTER(score, callback, context);
(void ) htable_enter(dnsbl_score_cache, client_addr, (void *) score);
/*
* Send a query to all DNSBL servers . Later , DNSBL lookup will be done
* with an UDP - based DNS client that is built directly into Postfix code .
* We therefore do not optimize the maximum out of this temporary
* implementation .
*/
for (ht = dnsbl_site_list; *ht; ht++) {
if ((fd = LOCAL_CONNECT(psc_dnsbl_service, NON_BLOCKING, 1 )) < 0 ) {
msg_warn("%s: connect to %s service: %m" ,
myname, psc_dnsbl_service);
continue ;
}
stream = vstream_fdopen(fd, O_RDWR);
vstream_control(stream,
CA_VSTREAM_CTL_CONTEXT(ht[0 ]->key),
CA_VSTREAM_CTL_END);
attr_print(stream, ATTR_FLAG_NONE,
SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, ht[0 ]->key),
SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, client_addr),
SEND_ATTR_INT(MAIL_ATTR_LABEL, score->request_id),
ATTR_TYPE_END);
if (vstream_fflush(stream) != 0 ) {
msg_warn("%s: error sending to %s service: %m" ,
myname, psc_dnsbl_service);
vstream_fclose(stream);
continue ;
}
PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive,
(void *) stream, var_psc_dnsbl_tmout);
score->pending_lookups += 1 ;
}
return (PSC_CALL_BACK_INDEX_OF_LAST(score));
}
/* psc_dnsbl_init - initialize */
void psc_dnsbl_init(void )
{
const char *myname = "psc_dnsbl_init" ;
ARGV *dnsbl_site = argv_split(var_psc_dnsbl_sites, CHARS_COMMA_SP);
char **cpp;
/*
* Sanity check .
*/
if (dnsbl_site_cache != 0 )
msg_panic("%s: called more than once" , myname);
/*
* pre - compute the DNSBLOG socket name .
*/
psc_dnsbl_service = concatenate(MAIL_CLASS_PRIVATE, "/" ,
var_dnsblog_service, (char *) 0 );
/*
* Prepare for quick iteration when sending out queries to all DNSBL
* servers , and for quick lookup when a reply arrives from a specific
* DNSBL server .
*/
dnsbl_site_cache = htable_create(13 );
for (cpp = dnsbl_site->argv; *cpp; cpp++)
psc_dnsbl_add_site(*cpp);
argv_free(dnsbl_site);
dnsbl_site_list = htable_list(dnsbl_site_cache);
/*
* The per - client blocklist score .
*/
dnsbl_score_cache = htable_create(13 );
/*
* Space for ad - hoc DNSBLOG server request / reply parameters .
*/
reply_client = vstring_alloc(100 );
reply_dnsbl = vstring_alloc(100 );
reply_addr = vstring_alloc(100 );
}
Messung V0.5 in Prozent C=86 H=97 G=91
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland
2026-08-23
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=141584
#Domains=738142