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