1/*	$NetBSD: postscreen_dnsbl.c,v 1.4 2022/10/08 16:12:48 christos Exp $	*/
2
3/*++
4/* NAME
5/*	postscreen_dnsbl 3
6/* SUMMARY
7/*	postscreen DNSBL support
8/* SYNOPSIS
9/*	#include <postscreen.h>
10/*
11/*	void	psc_dnsbl_init(void)
12/*
13/*	int	psc_dnsbl_request(client_addr, callback, context)
14/*	char	*client_addr;
15/*	void	(*callback)(int, char *);
16/*	char	*context;
17/*
18/*	int	psc_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index,
19/*					dnsbl_ttl)
20/*	char	*client_addr;
21/*	const char **dnsbl_name;
22/*	int	dnsbl_index;
23/*	int	*dnsbl_ttl;
24/* DESCRIPTION
25/*	This module implements preliminary support for DNSBL lookups.
26/*	Multiple requests for the same information are handled with
27/*	reference counts.
28/*
29/*	psc_dnsbl_init() initializes this module, and must be called
30/*	once before any of the other functions in this module.
31/*
32/*	psc_dnsbl_request() requests a blocklist score for the
33/*	specified client IP address and increments the reference
34/*	count.  The request completes in the background. The client
35/*	IP address must be in inet_ntop(3) output format.  The
36/*	callback argument specifies a function that is called when
37/*	the requested result is available. The context is passed
38/*	on to the callback function. The callback should ignore its
39/*	first argument (it exists for compatibility with Postfix
40/*	generic event infrastructure).
41/*	The result value is the index for the psc_dnsbl_retrieve()
42/*	call.
43/*
44/*	psc_dnsbl_retrieve() retrieves the result score and reply
45/*	TTL requested with psc_dnsbl_request(), and decrements the
46/*	reference count. The reply TTL value is clamped to
47/*	postscreen_dnsbl_min_ttl and postscreen_dnsbl_max_ttl.  It
48/*	is an error to retrieve a score without requesting it first.
49/* LICENSE
50/* .ad
51/* .fi
52/*	The Secure Mailer license must be distributed with this software.
53/* AUTHOR(S)
54/*	Wietse Venema
55/*	IBM T.J. Watson Research
56/*	P.O. Box 704
57/*	Yorktown Heights, NY 10598, USA
58/*
59/*	Wietse Venema
60/*	Google, Inc.
61/*	111 8th Avenue
62/*	New York, NY 10011, USA
63/*--*/
64
65/* System library. */
66
67#include <sys_defs.h>
68#include <sys/socket.h>			/* AF_INET */
69#include <netinet/in.h>			/* inet_pton() */
70#include <arpa/inet.h>			/* inet_pton() */
71#include <stdio.h>			/* sscanf */
72#include <limits.h>
73
74/* Utility library. */
75
76#include <msg.h>
77#include <mymalloc.h>
78#include <argv.h>
79#include <htable.h>
80#include <events.h>
81#include <vstream.h>
82#include <connect.h>
83#include <split_at.h>
84#include <valid_hostname.h>
85#include <ip_match.h>
86#include <myaddrinfo.h>
87#include <stringops.h>
88
89/* Global library. */
90
91#include <mail_params.h>
92#include <mail_proto.h>
93
94/* Application-specific. */
95
96#include <postscreen.h>
97
98 /*
99  * Talking to the DNSBLOG service.
100  */
101static char *psc_dnsbl_service;
102
103 /*
104  * Per-DNSBL filters and weights.
105  *
106  * The postscreen_dnsbl_sites parameter specifies zero or more DNSBL domains.
107  * We provide multiple access methods, one for quick iteration when sending
108  * queries to all DNSBL servers, and one for quick location when receiving a
109  * reply from one DNSBL server.
110  *
111  * Each DNSBL domain can be specified more than once, each time with a
112  * different (filter, weight) pair. We group (filter, weight) pairs in a
113  * linked list under their DNSBL domain name. The list head has a reference
114  * to a "safe name" for the DNSBL, in case the name includes a password.
115  */
116static HTABLE *dnsbl_site_cache;	/* indexed by DNSBNL domain */
117static HTABLE_INFO **dnsbl_site_list;	/* flattened cache */
118
119typedef struct {
120    const char *safe_dnsbl;		/* from postscreen_dnsbl_reply_map */
121    struct PSC_DNSBL_SITE *first;	/* list of (filter, weight) tuples */
122} PSC_DNSBL_HEAD;
123
124typedef struct PSC_DNSBL_SITE {
125    char   *filter;			/* printable filter (default: null) */
126    char   *byte_codes;			/* encoded filter (default: null) */
127    int     weight;			/* reply weight (default: 1) */
128    struct PSC_DNSBL_SITE *next;	/* linked list */
129} PSC_DNSBL_SITE;
130
131 /*
132  * Per-client DNSBL scores.
133  *
134  * Some SMTP clients make parallel connections. This can trigger parallel
135  * blocklist score requests when the pre-handshake delays of the connections
136  * overlap.
137  *
138  * We combine requests for the same score under the client IP address in a
139  * single reference-counted entry. The reference count goes up with each
140  * request for a score, and it goes down with each score retrieval. Each
141  * score has one or more requestors that need to be notified when the result
142  * is ready, so that postscreen can terminate a pre-handshake delay when all
143  * pre-handshake tests are completed.
144  */
145static HTABLE *dnsbl_score_cache;	/* indexed by client address */
146
147typedef struct {
148    void    (*callback) (int, void *);	/* generic call-back routine */
149    void   *context;			/* generic call-back argument */
150} PSC_CALL_BACK_ENTRY;
151
152typedef struct {
153    const char *dnsbl_name;		/* DNSBL with largest contribution */
154    int     dnsbl_weight;		/* weight of largest contribution */
155    int     total;			/* combined allow+denylist score */
156    int     fail_ttl;			/* combined reply TTL */
157    int     pass_ttl;			/* combined reply TTL */
158    int     refcount;			/* score reference count */
159    int     pending_lookups;		/* nr of DNS requests in flight */
160    int     request_id;			/* duplicate suppression */
161    /* Call-back table support. */
162    int     index;			/* next table index */
163    int     limit;			/* last valid index */
164    PSC_CALL_BACK_ENTRY table[1];	/* actually a bunch */
165} PSC_DNSBL_SCORE;
166
167#define PSC_CALL_BACK_INIT(sp) do { \
168	(sp)->limit = 0; \
169	(sp)->index = 0; \
170    } while (0)
171
172#define PSC_CALL_BACK_INDEX_OF_LAST(sp) ((sp)->index - 1)
173
174#define PSC_CALL_BACK_CANCEL(sp, idx) do { \
175	PSC_CALL_BACK_ENTRY *_cb_; \
176	if ((idx) < 0 || (idx) >= (sp)->index) \
177	    msg_panic("%s: index %d must be >= 0 and < %d", \
178		      myname, (idx), (sp)->index); \
179	_cb_ = (sp)->table + (idx); \
180	event_cancel_timer(_cb_->callback, _cb_->context); \
181	_cb_->callback = 0; \
182	_cb_->context = 0; \
183    } while (0)
184
185#define PSC_CALL_BACK_EXTEND(hp, sp) do { \
186	if ((sp)->index >= (sp)->limit) { \
187	    int _count_ = ((sp)->limit ? (sp)->limit * 2 : 5); \
188	    (hp)->value = myrealloc((void *) (sp), sizeof(*(sp)) + \
189				    _count_ * sizeof((sp)->table)); \
190	    (sp) = (PSC_DNSBL_SCORE *) (hp)->value; \
191	    (sp)->limit = _count_; \
192	} \
193    } while (0)
194
195#define PSC_CALL_BACK_ENTER(sp, fn, ctx) do { \
196	PSC_CALL_BACK_ENTRY *_cb_ = (sp)->table + (sp)->index++; \
197	_cb_->callback = (fn); \
198	_cb_->context = (ctx); \
199    } while (0)
200
201#define PSC_CALL_BACK_NOTIFY(sp, ev) do { \
202	PSC_CALL_BACK_ENTRY *_cb_; \
203	for (_cb_ = (sp)->table; _cb_ < (sp)->table + (sp)->index; _cb_++) \
204	    if (_cb_->callback != 0) \
205		_cb_->callback((ev), _cb_->context); \
206    } while (0)
207
208#define PSC_NULL_EVENT	(0)
209
210 /*
211  * Per-request state.
212  *
213  * This implementation stores the client IP address and DNSBL domain in the
214  * DNSBLOG query/reply stream. This simplifies code, and allows the DNSBLOG
215  * server to produce more informative logging.
216  */
217static VSTRING *reply_client;		/* client address in DNSBLOG reply */
218static VSTRING *reply_dnsbl;		/* domain in DNSBLOG reply */
219static VSTRING *reply_addr;		/* address list in DNSBLOG reply */
220
221/* psc_dnsbl_add_site - add DNSBL site information */
222
223static void psc_dnsbl_add_site(const char *site)
224{
225    const char *myname = "psc_dnsbl_add_site";
226    char   *saved_site = mystrdup(site);
227    VSTRING *byte_codes = 0;
228    PSC_DNSBL_HEAD *head;
229    PSC_DNSBL_SITE *new_site;
230    char    junk;
231    const char *weight_text;
232    char   *pattern_text;
233    int     weight;
234    HTABLE_INFO *ht;
235    char   *parse_err;
236    const char  *safe_dnsbl;
237
238    /*
239     * Parse the required DNSBL domain name, the optional reply filter and
240     * the optional reply weight factor.
241     */
242#define DO_GRIPE	1
243
244    /* Negative weight means allowlist. */
245    if ((weight_text = split_at(saved_site, '*')) != 0) {
246	if (sscanf(weight_text, "%d%c", &weight, &junk) != 1)
247	    msg_fatal("bad DNSBL weight factor \"%s\" in \"%s\"",
248		      weight_text, site);
249    } else {
250	weight = 1;
251    }
252    /* Reply filter. */
253    if ((pattern_text = split_at(saved_site, '=')) != 0) {
254	byte_codes = vstring_alloc(100);
255	if ((parse_err = ip_match_parse(byte_codes, pattern_text)) != 0)
256	    msg_fatal("bad DNSBL filter syntax: %s", parse_err);
257    }
258    if (valid_hostname(saved_site, DO_GRIPE) == 0)
259	msg_fatal("bad DNSBL domain name \"%s\" in \"%s\"",
260		  saved_site, site);
261
262    if (msg_verbose > 1)
263	msg_info("%s: \"%s\" -> domain=\"%s\" pattern=\"%s\" weight=%d",
264		 myname, site, saved_site, pattern_text ? pattern_text :
265		 "null", weight);
266
267    /*
268     * Look up or create the (filter, weight) list head for this DNSBL domain
269     * name.
270     */
271    if ((head = (PSC_DNSBL_HEAD *)
272	 htable_find(dnsbl_site_cache, saved_site)) == 0) {
273	head = (PSC_DNSBL_HEAD *) mymalloc(sizeof(*head));
274	ht = htable_enter(dnsbl_site_cache, saved_site, (void *) head);
275	/* Translate the DNSBL name into a safe name if available. */
276	if (psc_dnsbl_reply == 0
277	    || (safe_dnsbl = dict_get(psc_dnsbl_reply, saved_site)) == 0)
278	    safe_dnsbl = ht->key;
279	head->safe_dnsbl = mystrdup(safe_dnsbl);
280	if (psc_dnsbl_reply && psc_dnsbl_reply->error)
281	    msg_fatal("%s:%s lookup error", psc_dnsbl_reply->type,
282		      psc_dnsbl_reply->name);
283	head->first = 0;
284    }
285
286    /*
287     * Append the new (filter, weight) node to the list for this DNSBL domain
288     * name.
289     */
290    new_site = (PSC_DNSBL_SITE *) mymalloc(sizeof(*new_site));
291    new_site->filter = (pattern_text ? mystrdup(pattern_text) : 0);
292    new_site->byte_codes = (byte_codes ? ip_match_save(byte_codes) : 0);
293    new_site->weight = weight;
294    new_site->next = head->first;
295    head->first = new_site;
296
297    myfree(saved_site);
298    if (byte_codes)
299	vstring_free(byte_codes);
300}
301
302/* psc_dnsbl_match - match DNSBL reply filter */
303
304static int psc_dnsbl_match(const char *filter, ARGV *reply)
305{
306    char    addr_buf[MAI_HOSTADDR_STRSIZE];
307    char  **cpp;
308
309    /*
310     * Run the replies through the pattern-matching engine.
311     */
312    for (cpp = reply->argv; *cpp != 0; cpp++) {
313	if (inet_pton(AF_INET, *cpp, addr_buf) != 1)
314	    msg_warn("address conversion error for %s -- ignoring this reply",
315		     *cpp);
316	if (ip_match_execute(filter, addr_buf))
317	    return (1);
318    }
319    return (0);
320}
321
322/* psc_dnsbl_retrieve - retrieve blocklist score, decrement reference count */
323
324int     psc_dnsbl_retrieve(const char *client_addr, const char **dnsbl_name,
325			           int dnsbl_index, int *dnsbl_ttl)
326{
327    const char *myname = "psc_dnsbl_retrieve";
328    PSC_DNSBL_SCORE *score;
329    int     result_score;
330    int     result_ttl;
331
332    /*
333     * Sanity check.
334     */
335    if ((score = (PSC_DNSBL_SCORE *)
336	 htable_find(dnsbl_score_cache, client_addr)) == 0)
337	msg_panic("%s: no blocklist score for %s", myname, client_addr);
338
339    /*
340     * Disable callbacks.
341     */
342    PSC_CALL_BACK_CANCEL(score, dnsbl_index);
343
344    /*
345     * Reads are destructive.
346     */
347    result_score = score->total;
348    *dnsbl_name = score->dnsbl_name;
349    result_ttl = (result_score > 0) ? score->fail_ttl : score->pass_ttl;
350    /* As with dnsblog(8), a value < 0 means no reply TTL. */
351    if (result_ttl < var_psc_dnsbl_min_ttl)
352	result_ttl = var_psc_dnsbl_min_ttl;
353    if (result_ttl > var_psc_dnsbl_max_ttl)
354	result_ttl = var_psc_dnsbl_max_ttl;
355    *dnsbl_ttl = result_ttl;
356    if (msg_verbose)
357	msg_info("%s: addr=%s score=%d ttl=%d",
358		 myname, client_addr, result_score, result_ttl);
359    score->refcount -= 1;
360    if (score->refcount < 1) {
361	if (msg_verbose > 1)
362	    msg_info("%s: delete blocklist score for %s", myname, client_addr);
363	htable_delete(dnsbl_score_cache, client_addr, myfree);
364    }
365    return (result_score);
366}
367
368/* psc_dnsbl_receive - receive DNSBL reply, update blocklist score */
369
370static void psc_dnsbl_receive(int event, void *context)
371{
372    const char *myname = "psc_dnsbl_receive";
373    VSTREAM *stream = (VSTREAM *) context;
374    PSC_DNSBL_SCORE *score;
375    PSC_DNSBL_HEAD *head;
376    PSC_DNSBL_SITE *site;
377    ARGV   *reply_argv;
378    int     request_id;
379    int     dnsbl_ttl;
380
381    PSC_CLEAR_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, context);
382
383    /*
384     * Receive the DNSBL lookup result.
385     *
386     * This is preliminary code to explore the field. Later, DNSBL lookup will
387     * be handled by an UDP-based DNS client that is built directly into some
388     * Postfix daemon.
389     *
390     * Don't bother looking up the blocklist score when the client IP address is
391     * not listed at the DNSBL.
392     *
393     * Don't panic when the blocklist score no longer exists. It may be deleted
394     * when the client triggers a "drop" action after pregreet, when the
395     * client does not pregreet and the DNSBL reply arrives late, or when the
396     * client triggers a "drop" action after hanging up.
397     */
398    if (event == EVENT_READ
399	&& attr_scan(stream,
400		     ATTR_FLAG_STRICT,
401		     RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, reply_dnsbl),
402		     RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, reply_client),
403		     RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id),
404		     RECV_ATTR_STR(MAIL_ATTR_RBL_ADDR, reply_addr),
405		     RECV_ATTR_INT(MAIL_ATTR_TTL, &dnsbl_ttl),
406		     ATTR_TYPE_END) == 5
407	&& (score = (PSC_DNSBL_SCORE *)
408	    htable_find(dnsbl_score_cache, STR(reply_client))) != 0
409	&& score->request_id == request_id) {
410
411	/*
412	 * Run this response past all applicable DNSBL filters and update the
413	 * blocklist score for this client IP address.
414	 *
415	 * Don't panic when the DNSBL domain name is not found. The DNSBLOG
416	 * server may be messed up.
417	 */
418	if (msg_verbose > 1)
419	    msg_info("%s: client=\"%s\" score=%d domain=\"%s\" reply=\"%d %s\"",
420		     myname, STR(reply_client), score->total,
421		     STR(reply_dnsbl), dnsbl_ttl, STR(reply_addr));
422	head = (PSC_DNSBL_HEAD *)
423	    htable_find(dnsbl_site_cache, STR(reply_dnsbl));
424	if (head == 0) {
425	    /* Bogus domain. Do nothing. */
426	} else if (*STR(reply_addr) != 0) {
427	    /* DNS reputation record(s) found. */
428	    reply_argv = 0;
429	    for (site = head->first; site != 0; site = site->next) {
430		if (site->byte_codes == 0
431		    || psc_dnsbl_match(site->byte_codes, reply_argv ? reply_argv :
432			 (reply_argv = argv_split(STR(reply_addr), " ")))) {
433		    if (score->dnsbl_name == 0
434			|| score->dnsbl_weight < site->weight) {
435			score->dnsbl_name = head->safe_dnsbl;
436			score->dnsbl_weight = site->weight;
437		    }
438		    score->total += site->weight;
439		    if (msg_verbose > 1)
440			msg_info("%s: filter=\"%s\" weight=%d score=%d",
441			       myname, site->filter ? site->filter : "null",
442				 site->weight, score->total);
443		}
444		/* As with dnsblog(8), a value < 0 means no reply TTL. */
445		if (site->weight > 0) {
446		    if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
447			score->fail_ttl = dnsbl_ttl;
448		} else {
449		    if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
450			score->pass_ttl = dnsbl_ttl;
451		}
452	    }
453	    if (reply_argv != 0)
454		argv_free(reply_argv);
455	} else {
456	    /* No DNS reputation record found. */
457	    for (site = head->first; site != 0; site = site->next) {
458		/* As with dnsblog(8), a value < 0 means no reply TTL. */
459		if (site->weight > 0) {
460		    if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
461			score->pass_ttl = dnsbl_ttl;
462		} else {
463		    if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
464			score->fail_ttl = dnsbl_ttl;
465		}
466	    }
467	}
468
469	/*
470	 * Notify the requestor(s) that the result is ready to be picked up.
471	 * If this call isn't made, clients have to sit out the entire
472	 * pre-handshake delay.
473	 */
474	score->pending_lookups -= 1;
475	if (score->pending_lookups == 0)
476	    PSC_CALL_BACK_NOTIFY(score, PSC_NULL_EVENT);
477    } else if (event == EVENT_TIME) {
478	msg_warn("dnsblog reply timeout %ds for %s",
479		 var_psc_dnsbl_tmout, (char *) vstream_context(stream));
480    }
481    /* Here, score may be a null pointer. */
482    vstream_fclose(stream);
483}
484
485/* psc_dnsbl_request  - send dnsbl query, increment reference count */
486
487int     psc_dnsbl_request(const char *client_addr,
488			          void (*callback) (int, void *),
489			          void *context)
490{
491    const char *myname = "psc_dnsbl_request";
492    int     fd;
493    VSTREAM *stream;
494    HTABLE_INFO **ht;
495    PSC_DNSBL_SCORE *score;
496    HTABLE_INFO *hash_node;
497    static int request_count;
498
499    /*
500     * Some spambots make several connections at nearly the same time,
501     * causing their pregreet delays to overlap. Such connections can share
502     * the efforts of DNSBL lookup.
503     *
504     * We store a reference-counted DNSBL score under its client IP address. We
505     * increment the reference count with each score request, and decrement
506     * the reference count with each score retrieval.
507     *
508     * Do not notify the requestor NOW when the DNS replies are already in.
509     * Reason: we must not make a backwards call while we are still in the
510     * middle of executing the corresponding forward call. Instead we create
511     * a zero-delay timer request and call the notification function from
512     * there.
513     *
514     * psc_dnsbl_request() could instead return a result value to indicate that
515     * the DNSBL score is already available, but that would complicate the
516     * caller with two different notification code paths: one asynchronous
517     * code path via the callback invocation, and one synchronous code path
518     * via the psc_dnsbl_request() result value. That would be a source of
519     * future bugs.
520     */
521    if ((hash_node = htable_locate(dnsbl_score_cache, client_addr)) != 0) {
522	score = (PSC_DNSBL_SCORE *) hash_node->value;
523	score->refcount += 1;
524	PSC_CALL_BACK_EXTEND(hash_node, score);
525	PSC_CALL_BACK_ENTER(score, callback, context);
526	if (msg_verbose > 1)
527	    msg_info("%s: reuse blocklist score for %s refcount=%d pending=%d",
528		     myname, client_addr, score->refcount,
529		     score->pending_lookups);
530	if (score->pending_lookups == 0)
531	    event_request_timer(callback, context, EVENT_NULL_DELAY);
532	return (PSC_CALL_BACK_INDEX_OF_LAST(score));
533    }
534    if (msg_verbose > 1)
535	msg_info("%s: create blocklist score for %s", myname, client_addr);
536    score = (PSC_DNSBL_SCORE *) mymalloc(sizeof(*score));
537    score->request_id = request_count++;
538    score->dnsbl_name = 0;
539    score->dnsbl_weight = 0;
540    /* As with dnsblog(8), a value < 0 means no reply TTL. */
541    score->pass_ttl = -1;
542    score->fail_ttl = -1;
543    score->total = 0;
544    score->refcount = 1;
545    score->pending_lookups = 0;
546    PSC_CALL_BACK_INIT(score);
547    PSC_CALL_BACK_ENTER(score, callback, context);
548    (void) htable_enter(dnsbl_score_cache, client_addr, (void *) score);
549
550    /*
551     * Send a query to all DNSBL servers. Later, DNSBL lookup will be done
552     * with an UDP-based DNS client that is built directly into Postfix code.
553     * We therefore do not optimize the maximum out of this temporary
554     * implementation.
555     */
556    for (ht = dnsbl_site_list; *ht; ht++) {
557	if ((fd = LOCAL_CONNECT(psc_dnsbl_service, NON_BLOCKING, 1)) < 0) {
558	    msg_warn("%s: connect to %s service: %m",
559		     myname, psc_dnsbl_service);
560	    continue;
561	}
562	stream = vstream_fdopen(fd, O_RDWR);
563	vstream_control(stream,
564			CA_VSTREAM_CTL_CONTEXT(ht[0]->key),
565			CA_VSTREAM_CTL_END);
566	attr_print(stream, ATTR_FLAG_NONE,
567		   SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, ht[0]->key),
568		   SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, client_addr),
569		   SEND_ATTR_INT(MAIL_ATTR_LABEL, score->request_id),
570		   ATTR_TYPE_END);
571	if (vstream_fflush(stream) != 0) {
572	    msg_warn("%s: error sending to %s service: %m",
573		     myname, psc_dnsbl_service);
574	    vstream_fclose(stream);
575	    continue;
576	}
577	PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive,
578			       (void *) stream, var_psc_dnsbl_tmout);
579	score->pending_lookups += 1;
580    }
581    return (PSC_CALL_BACK_INDEX_OF_LAST(score));
582}
583
584/* psc_dnsbl_init - initialize */
585
586void    psc_dnsbl_init(void)
587{
588    const char *myname = "psc_dnsbl_init";
589    ARGV   *dnsbl_site = argv_split(var_psc_dnsbl_sites, CHARS_COMMA_SP);
590    char  **cpp;
591
592    /*
593     * Sanity check.
594     */
595    if (dnsbl_site_cache != 0)
596	msg_panic("%s: called more than once", myname);
597
598    /*
599     * pre-compute the DNSBLOG socket name.
600     */
601    psc_dnsbl_service = concatenate(MAIL_CLASS_PRIVATE, "/",
602				    var_dnsblog_service, (char *) 0);
603
604    /*
605     * Prepare for quick iteration when sending out queries to all DNSBL
606     * servers, and for quick lookup when a reply arrives from a specific
607     * DNSBL server.
608     */
609    dnsbl_site_cache = htable_create(13);
610    for (cpp = dnsbl_site->argv; *cpp; cpp++)
611	psc_dnsbl_add_site(*cpp);
612    argv_free(dnsbl_site);
613    dnsbl_site_list = htable_list(dnsbl_site_cache);
614
615    /*
616     * The per-client blocklist score.
617     */
618    dnsbl_score_cache = htable_create(13);
619
620    /*
621     * Space for ad-hoc DNSBLOG server request/reply parameters.
622     */
623    reply_client = vstring_alloc(100);
624    reply_dnsbl = vstring_alloc(100);
625    reply_addr = vstring_alloc(100);
626}
627