summaryrefslogtreecommitdiffstats
path: root/src/postscreen/postscreen_dnsbl.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
commitb7c15c31519dc44c1f691e0466badd556ffe9423 (patch)
treef944572f288bab482a615e09af627d9a2b6727d8 /src/postscreen/postscreen_dnsbl.c
parentInitial commit. (diff)
downloadpostfix-b7c15c31519dc44c1f691e0466badd556ffe9423.tar.xz
postfix-b7c15c31519dc44c1f691e0466badd556ffe9423.zip
Adding upstream version 3.7.10.upstream/3.7.10
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/postscreen/postscreen_dnsbl.c')
-rw-r--r--src/postscreen/postscreen_dnsbl.c624
1 files changed, 624 insertions, 0 deletions
diff --git a/src/postscreen/postscreen_dnsbl.c b/src/postscreen/postscreen_dnsbl.c
new file mode 100644
index 0000000..7d9a5e9
--- /dev/null
+++ b/src/postscreen/postscreen_dnsbl.c
@@ -0,0 +1,624 @@
+/*++
+/* 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);
+}