Mercurial > notdcc
diff dcclib/dnsbl.c @ 0:c7f6b056b673
First import of vendor version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 13:49:58 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dcclib/dnsbl.c Tue Mar 10 13:49:58 2009 +0100 @@ -0,0 +1,1824 @@ +/* Distributed Checksum Clearinghouse + * + * reject messages contain URLs that resolve to DNS blacklisted IP addresses + * + * Copyright (c) 2008 by Rhyolite Software, LLC + * + * This agreement is not applicable to any entity which sells anti-spam + * solutions to others or provides an anti-spam solution as part of a + * security solution sold to other entities, or to a private network + * which employs the DCC or uses data provided by operation of the DCC + * but does not provide corresponding data to other users. + * + * Permission to use, copy, modify, and distribute this software without + * changes for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice appear in all + * copies and any distributed versions or copies are either unchanged + * or not called anything similar to "DCC" or "Distributed Checksum + * Clearinghouse". + * + * Parties not eligible to receive a license under this agreement can + * obtain a commercial license to use DCC by contacting Rhyolite Software + * at sales@rhyolite.com. + * + * A commercial license would be for Distributed Checksum and Reputation + * Clearinghouse software. That software includes additional features. This + * free license for Distributed ChecksumClearinghouse Software does not in any + * way grant permision to use Distributed Checksum and Reputation Clearinghouse + * software + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC + * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES + * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * Rhyolite Software DCC 1.3.103-1.100 $Revision$ + */ + +#include "helper.h" +#include "dcc_heap_debug.h" +#ifndef DCC_WIN32 +#include <sys/wait.h> +#include <arpa/inet.h> +#endif +#ifdef HAVE_RESOLV_H +#include <resolv.h> +#endif +#ifdef HAVE_ARPA_NAMESER_H +#include <arpa/nameser.h> +#endif + +/* can check MX and NS addresses only with a standard resolver library */ +#define MXNS_DNSBL +#if !defined(HAVE__RES) || !defined(HAVE_RES_INIT) +#undef HAVE__RES +#undef HAVE_RES_INIT +#undef MXNS_DNSBL +#endif +#if !defined (HAVE_RES_QUERY) || !defined(HAVE_DN_EXPAND) +#undef MXNS_DNSBL +#endif +#if !defined(HAVE_HSTRERROR) +#undef MXNS_DNSBL /* MX lookups need raw hsterror() */ +#endif +#if !defined(T_MX) || !defined(T_A) || !defined(T_AAAA) || !defined(T_NS) +#undef MXNS_DNSBL +#endif +#if !defined(C_IN) || !defined(C_IN) || !defined(PACKETSZ) +#undef MXNS_DNSBL +#endif +#if !defined(RES_DEFNAMES) || !defined(RES_DNSRCH) || !defined(RES_NOALIASES) +#undef MXNS_DNSBL +#endif + + +DNSBL *dnsbls; +static DNSBL_UNHIT dnsbl_groups; +u_char have_dnsbl_groups; + +HELPER helper; +u_char have_helpers; +DCC_PATH dnsbl_progpath; + +static u_char is_helper; +static const char *helper_str = ""; + +static u_char have_ipv4_dnsbl; +static u_char have_ipv6_dnsbl; +static u_char have_name_dnsbl; + +#define MAX_MSG_SECS 1000 +#ifndef RES_TIMEOUT +#define RES_TIMEOUT 3 +#endif +static int msg_secs = 25; /* total seconds/mail message */ +static time_t msg_us; +static int url_secs = 11; /* total seconds/host name */ +static time_t url_us; + + +/* Parse a string of the form: + * "domain[,[IPaddr][,name|ipv4|ipv6]] + * or of one of the forms: + * set:progpath path of helper program + * set:debug=X more logging + * set:msg-secs=S total seconds checking blacklists/message + * set:url-secs=S total seconds per host name + * set:[no-]client client IP address checks + * set:[no-]mail_host envelope mail_from checks + * set:[no-]URL body URL checks + * set:[no-]MX MX checks + * set:[no-]NS NS checks + * set:defaults restore defaults + * set:group=X start group of DNSBLs + * set:[no-]temp-fail timeout temporarily fails SMTP transaction + * set:max_helpers=X override dccm or dccifd max_work + * + * set:[no-]envelope obsolete mail host & client + * + * set:helper=soc,fd,X start DNS resolver process + */ +u_char /* 0=bad */ +dcc_parse_dnsbl(DCC_EMSG emsg, const char *entry, const char *progpath, + u_char tfail) +{ + static DNSBL_FGS cur_fgs = DNSBL_FG_DEFAULT; + static const REPLY_TPLT *cur_reply = 0; + static int bl_num = 0; + static int cur_group = 0; + DNSBL_GBITS gbit; + DNSBL *dp, *dp1, **dpp; + const char *tgt_ip; /* "hit" IP address of this blacklist */ + DNSBL_DOM tgt_ip_buf; + DNSBL_TYPE bl_type; + struct in6_addr tgt, tgt_mask; + int tgt_bits; + u_char tgt_use_ipv6; + DCC_EMSG addr_emsg; + int error, bl_dom_len; + int val; + char *p; +#ifdef HAVE_HELPERS + int soc; + int fd; + int total_helpers; +# define SAVE_ARG(arg) helper_save_arg("-B", arg) +#else +# define SAVE_ARG(arg) +#endif + + if (progpath && dnsbl_progpath[0] == '\0') { + const char *slash = strrchr(progpath, '/'); + if (slash) + ++slash; + else + slash = progpath; + snprintf(dnsbl_progpath, sizeof(dnsbl_progpath), + "%.*sdns-helper", (int)(slash-progpath), progpath); + } + + /* pretend to turn on temp-fail for dccproc to get + * DNSBLx(timeout) messages */ + if (tfail) + cur_fgs |= DNSBL_FG_TFAIL; + + /* handle parameter settings */ + if (!CLITCMP(entry, "set:")) { + const char *parm = entry+LITZ("set:"); +#ifdef HAVE_HELPERS + /* start running a helper process on the last, magic -B */ + if (3 == sscanf(parm, HELPER_PAT, &soc, &fd, &total_helpers)) { + helper_child(soc, fd, total_helpers); + } +#endif + if (!CLITCMP(parm, "progpath=")) { + BUFCPY(dnsbl_progpath, parm+LITZ("progpath=")); + return 1; + } + if (!strcasecmp(parm, "debug")) { + ++helper.debug; + SAVE_ARG(entry); + return 1; + } + if (1 == sscanf(parm, "debug=%d", &val)) { + helper.debug = val; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "envelope")) { /* obsolete */ + cur_fgs |= (DNSBL_FG_CLIENT | DNSBL_FG_MAIL_HOST); + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "no-envelope")) { /* obsolete */ + cur_fgs &= ~(DNSBL_FG_CLIENT | DNSBL_FG_MAIL_HOST); + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "client")) { + /* obsolete */ + cur_fgs |= DNSBL_FG_CLIENT; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "no-client")) { + /* obsolete */ + cur_fgs &= ~DNSBL_FG_CLIENT; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "mail_host")) { + /* obsolete */ + cur_fgs |= DNSBL_FG_MAIL_HOST; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "no-mail_host")) { + /* obsolete */ + cur_fgs &= ~DNSBL_FG_MAIL_HOST; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "body")) { /* obsolete */ + cur_fgs |= DNSBL_FG_URL; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "url")) { + cur_fgs |= DNSBL_FG_URL; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "no-body")) { /* obsolete */ + cur_fgs &= ~DNSBL_FG_URL; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "no-URL")) { + cur_fgs &= ~DNSBL_FG_URL; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "mx")) { +#ifdef MXNS_DNSBL + cur_fgs |= DNSBL_FG_MX; + SAVE_ARG(entry); + return 1; +#else + dcc_pemsg(EX_USAGE, emsg, + "MX DNS blacklist checks not supported"); + return 0; +#endif + } + if (!strcasecmp(parm, "no-mx")) { + cur_fgs &= ~DNSBL_FG_MX; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "ns")) { +#ifdef MXNS_DNSBL + cur_fgs |= DNSBL_FG_MX; + SAVE_ARG(entry); + return 1; +#else + dcc_pemsg(EX_USAGE, emsg, + "NS DNS blacklist checks not supported"); + return 0; +#endif + } + if (!strcasecmp(parm, "no-ns")) { + cur_fgs &= ~DNSBL_FG_NS; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "defaults")) { + cur_fgs = DNSBL_FG_DEFAULT; + SAVE_ARG(entry); + return 1; + } + if (!CLITCMP(parm, "rej-msg=")) { + parm += LITZ("rej-msg="); + if (*parm == '\0') + cur_reply = 0; + else + cur_reply = dnsbl_parse_reply(parm); + /* do not save for helpers */ + return 1; + } + if (!CLITCMP(parm, "msg-secs=")) { + parm += LITZ("msg-secs="); + val = strtoul(parm, &p, 10); + if (*p != '\0' || val < 1 || val > MAX_MSG_SECS) { + dcc_pemsg(EX_USAGE, emsg, + "bad number of seconds in \"-B %s\"", + entry); + return 0; + } + if (msg_secs != val) { + msg_secs = val; + SAVE_ARG(entry); + } + return 1; + } + if (!CLITCMP(parm, "url-secs=")) { + parm += LITZ("url-secs="); + val = strtoul(parm, &p, 10); + if (*p != '\0' || val < 1 || val > MAX_MSG_SECS) { + dcc_pemsg(EX_USAGE, emsg, + "bad number of seconds in \"-B %s\"", + entry); + return 0; + } + if (url_secs != val) { + url_secs = val; + SAVE_ARG(entry); + } + return 1; + } + if (1 == sscanf(parm, "group=%d", &val) + && val >= 1 && val <= MAX_DNSBL_GROUPS) { + cur_group = val-1; + have_dnsbl_groups = 1; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "temp-fail")) { + cur_fgs |= DNSBL_FG_TFAIL; + SAVE_ARG(entry); + return 1; + } + if (!strcasecmp(parm, "no-temp-fail")) { + cur_fgs &= ~DNSBL_FG_TFAIL; + SAVE_ARG(entry); + return 1; + } + if (1 == sscanf(parm, "max_helpers=%d", &val) + && val >= 1 && val < 1000) { + helper.max_helpers = val; + SAVE_ARG(entry); + return 1; + } + dcc_pemsg(EX_USAGE, emsg, "unrecognized \"-B %s\"", + entry); + return 0; + } + + /* we must have a DNSBL specification */ + bl_type = DNSBL_TYPE_IPV4; /* assume it is a simple IPv4 DNSBL */ + tgt_ip = strchr(entry, ','); + if (!tgt_ip) { + bl_dom_len = strlen(entry); + have_ipv4_dnsbl = 1; + } else { + bl_dom_len = tgt_ip - entry; + ++tgt_ip; + + /* notice trailing ",name" or ",IPv4" */ + p = strchr(tgt_ip, ','); + if (!p) { + have_ipv4_dnsbl = 1; + } else { + ++p; + if (!strcasecmp(p, "name")) { + bl_type = DNSBL_TYPE_NAME; + have_name_dnsbl = 1; + } else if (!strcasecmp(p, "IPV4")) { + bl_type = DNSBL_TYPE_IPV4; + have_ipv4_dnsbl = 1; + } else if (!strcasecmp(p, "IPV6")) { + bl_type = DNSBL_TYPE_IPV6; + have_ipv6_dnsbl = 1; + } else { + dcc_pemsg(EX_NOHOST, emsg, + "unknown DNSBL type in \"%s\"", + entry); + return 0; + } + STRLCPY(tgt_ip_buf.c, tgt_ip, + min(ISZ(tgt_ip_buf), p-tgt_ip)); + tgt_ip = tgt_ip_buf.c; + } + } + + if (entry[0] == '.') { + ++entry; + --bl_dom_len; + } + if (bl_dom_len < 1) { + dcc_pemsg(EX_NOHOST, emsg, + "invalid DNS blacklist \"%s\"", entry); + return 0; + } + + /* assume 127.0.0.2 if the target address is missing */ + if (!tgt_ip || *tgt_ip == '\0') + tgt_ip = "127.0.0.2"; + + if (!strcasecmp(tgt_ip, "any")) { + /* That we get a result when we lookup up an address in the + * in the DNSBL is all that matters in this case. + * Whether it is IPv6 or IPv4 does not */ + tgt_use_ipv6 = 2; + memset(&tgt, 0, sizeof(tgt)); + memset(&tgt_mask, 0, sizeof(tgt_mask)); + + } else if (0 != (tgt_bits = dcc_str2cidr(emsg, &tgt, &tgt_mask, + &tgt_use_ipv6, tgt_ip, + 0, 0))) { + if (tgt_bits < 0) { + dcc_pemsg(EX_NOHOST, addr_emsg, + "invalid DNS blacklist %s \"%s\"", + addr_emsg, entry); + return 0; + } + + } else { + dcc_host_lock(); + if (dcc_get_host(tgt_ip, 3, &error)) { + /* prefer an IPv4 target address */ + if (dcc_hostaddrs[0].sa.sa_family == AF_INET) { + tgt_use_ipv6 = 0; + dcc_ipv4toipv6(&tgt, + dcc_hostaddrs[0].ipv4.sin_addr); + } else { + tgt_use_ipv6 = 1; + tgt = dcc_hostaddrs[0].ipv6.sin6_addr; + } + dcc_host_unlock(); + dcc_bits2mask(&tgt_mask, 128); + + } else { + dcc_host_unlock(); + dcc_pemsg(EX_NOHOST, emsg, + "invalid DNSBL target IP address \"%s\": %s", + tgt_ip, DCC_HSTRERROR(error)); + return 0; + } + } + + if (bl_dom_len >= ISZ(DNSBL_DOM) - INET6_ADDRSTRLEN*2) { + dcc_host_unlock(); + /* we cannot fit the DNSBL base and the target name or address + * into blw->probe. We need to do DNS lookups of names + * like 33.22.11.10.dnsbl.example.com or + * domain.dom.bl.example.com */ + dcc_pemsg(EX_NOHOST, emsg, "DNSBL name \"%s\" too long", entry); + return 0; + } + + dp = dcc_malloc(sizeof(*dp)); + memset(dp, 0, sizeof(*dp)); + + dp->tgt_use_ipv6 = tgt_use_ipv6; + dp->tgt = tgt; + dp->tgt_mask = tgt_mask; + + dp->bl_type = bl_type; + dp->fgs = cur_fgs; + dp->reply = cur_reply; + memcpy(&dp->bl_dom.c, entry, bl_dom_len); + dp->bl_dom_len = bl_dom_len+1; /* count trailing '\0' */ + dp->bl_num = ++bl_num; + dp->group = cur_group; + gbit = DNSBL_G2B(dp->group); + dnsbl_groups.all |= gbit; + if (cur_fgs & DNSBL_FG_CLIENT) + dnsbl_groups.client |= gbit; + if (cur_fgs & DNSBL_FG_MAIL_HOST) { + dnsbl_groups.mail_host |= gbit; + if (cur_fgs & DNSBL_FG_MX) + dnsbl_groups.mail_host_mx |= gbit; + if (cur_fgs & DNSBL_FG_NS) + dnsbl_groups.mail_host_ns |= gbit; + } + if (cur_fgs & DNSBL_FG_URL) { + dnsbl_groups.url |= gbit; + if (cur_fgs & DNSBL_FG_MX) + dnsbl_groups.url_mx |= gbit; + if (cur_fgs & DNSBL_FG_NS) + dnsbl_groups.url_ns |= gbit; + } + + /* link the DNSBLs in the order they are declared so that they + * can be prioritized */ + dpp = &dnsbls; + for (;;) { + dp1 = *dpp; + if (!dp1) { + *dpp = dp; + break; + } + /* notice sufficiently duplicate DNSBLS */ + if (!(dp->fgs & DNSBL_FG_DUP) && !dp1->dup + && dp->tgt_use_ipv6 == dp1->tgt_use_ipv6 + && dp->bl_type == dp1->bl_type + && dp->bl_dom_len == dp1->bl_dom_len + && !memcmp(dp->bl_dom.c, dp1->bl_dom.c, dp->bl_dom_len)) { + dp->fgs |= DNSBL_FG_DUP; + dp1->dup = dp; + } + dpp = &dp1->fwd; + } + + SAVE_ARG(entry); + return 1; +#undef SAVE_ARG +} + + + +/* resolve inconsistencies among the -B parameters */ +static inline void +fix_url_secs(void) +{ + if (url_secs > msg_secs) + url_secs = msg_secs; + msg_us = msg_secs * DCC_US; + url_us = url_secs * DCC_US; +} + + + +static char * +type_str(char *buf, int buf_len, const DNSBL_WORK *blw, DNSBL_FGS fgs) +{ + const char *type; + char sustr[DCC_SU2STR_SIZE]; + int i; + + switch (fgs & DNSBL_FG_TYPES) { + case DNSBL_FG_CLIENT: + type = "SMTP client"; + if (blw->tgt.dom.c[0] == '\0') { + snprintf(buf, buf_len, "%s %s", + type, dcc_ipv6tostr2(sustr, sizeof(sustr), + &blw->tgt.addr)); + return buf; + } + break; + case DNSBL_FG_MAIL_HOST: + type = "mail_host"; + break; + case DNSBL_FG_URL: + type = "URL"; + break; + case DNSBL_FG_MX | DNSBL_FG_MAIL_HOST : + type = "mail_host MX"; + break; + case DNSBL_FG_MX | DNSBL_FG_URL: + type = "URL MX"; + break; + case DNSBL_FG_NS | DNSBL_FG_MAIL_HOST: + type = "mail_host NS"; + break; + case DNSBL_FG_NS | DNSBL_FG_URL: + type = "URL NS"; + break; + + default: + dcc_logbad(EX_SOFTWARE, "impossible DNSBL hit type %#x", fgs); + break; + } + + i = snprintf(buf, buf_len, "%s %s", type, blw->tgt.dom.c); + if (i >= buf_len && buf_len > 4) + strcpy(&buf[buf_len-4], "..."); + return buf; +} + + + +static void PATTRIB(4,5) +dnsbl_log(const DNSBL_WORK *blw, const DNSBL_GROUP *blg, DNSBL_FGS fgs, + const char *pat, ...) +{ + char type_buf[sizeof(blg->btype_buf)]; + const char *type0, *type1; + char gbuf[8]; + const char *gnum; + char msg[256]; + va_list args; + + va_start(args, pat); + vsnprintf(msg, sizeof(msg), pat, args); + va_end(args); + + if (fgs) { + type0 = " "; + type1 = type_str(type_buf, sizeof(type_buf), blw, fgs); + } else { + type0 = ""; + type1 = ""; + } + if (!have_dnsbl_groups) { + gnum = ""; + } else if (!blg) { + gnum = "*"; + } else { + snprintf(gbuf, sizeof(gbuf), "%d", + (int)(blg - blw->groups)+1); + gnum = gbuf; + } + + if (helper.debug) { + if (dcc_no_syslog) + thr_trace_msg(blw->log_ctxt, "DNSBL%s%s%s%s %s", + gnum, helper_str, type0, type1, msg); + else + thr_trace_msg(blw->log_ctxt, "%s DNSBL%s%s%s%s %s", + blw->id, + gnum, helper_str, type0, type1, msg); + } else { + thr_log_print(blw->log_ctxt, 1, "DNSBL%s%s%s%s %s\n", + helper_str, gnum, type0, type1, msg); + } +} + + + +void +dcc_dnsbl_result(ASK_ST *ask_stp, DNSBL_WORK *blw) +{ + DNSBL *dp; + DNSBL_GROUP *blg; + int gnum; + + if (!blw) + return; + + for (gnum = 0, blg = blw->groups; + gnum < MAX_DNSBL_GROUPS; + ++gnum, ++blg) { + if (blg->fgs & DNSBL_FG_HITS) { + *ask_stp |= (ASK_ST_DNSBL_HIT(gnum) | ASK_ST_LOGIT); + dnsbl_log(blw, blg, 0, "%s %s=%s", + blg->btype, blg->probe.c, blg->result); + } else if (blg->fgs & DNSBL_FG_TIMEO) { + *ask_stp |= ASK_ST_DNSBL_TIMEO(gnum); + for (dp = dnsbls; dp; dp = dp->fwd) { + if (dp->group != gnum) + continue; + if (dp->fgs & DNSBL_FG_TFAIL) { + *ask_stp |= ASK_ST_DNSBL_TFAIL(gnum); + break; + } + } + } + } +} + + + +/* There are several timing requirements: + * - Do not spend too much time on any single URL or envelope value. + * - At helper.debug >=1, log the first DNSBL check in a group + * unfinished for lack of time. Also log checks for single URLs that + * are partially unfinished. + * - At helper.debug >=2, log things that take a long time + * - Minimize log messages, because the noise can be deafening. + * - Mark unfinished groups */ + + +/* A check has been abandoned or timed out. */ +static void +set_timeo(DNSBL_WORK *blw, DNSBL_FGS fgs) +{ + DNSBL *dp; + DNSBL_GROUP *blg; + int gnum; + + /* groups that are marked as having suffered timeouts can be + * hit by later URLs */ + if (fgs == 0) { + /* mark all groups if we don't know the type of request */ + for (gnum = 0, blg = blw->groups; + gnum < MAX_DNSBL_GROUPS; + ++gnum, ++blg) { + /* ignore groups that have hit or timed out */ + if (!(blw->unhit.all & DNSBL_G2B(gnum)) + || (blg->fgs & DNSBL_FG_TIMEO)) + continue; + blg->fgs |= DNSBL_FG_TIMEO; + } + + } else { + /* mark only groups that still care about this type */ + for (dp = dnsbls; dp; dp = dp->fwd) { + /* this DNSBL does not care about this type + * or the group to which it belongs has been hit */ + if (!(dp->fgs & fgs & DNSBL_FG_HITS) + || !(blw->unhit.all & DNSBL_G2B(dp->group))) + continue; + blw->groups[dp->group].fgs |= DNSBL_FG_TIMEO; + } + } + + /* no complaint if time remains */ + if (blw->url_us >= blw->url_us_used) + return; + + /* no more log messages if everything has been complained about */ + if (blw->url_us < 0 || blw->msg_us < 0) + return; + + /* only one final message/URL */ + blw->url_us = -1; + if (helper.debug < 2) + return; + + if (is_helper) { + /* Messages from the helper process go to the system + * log but not the per-message log file. + * The helper works on a single URL or envelope value + * and so does not know about the time limit for the + * entire message. */ + dnsbl_log(blw, 0, fgs, "failed after %.1f url_secs used", + blw->url_us_used / (DCC_US*1.0)); + + } else if (blw->msg_us > blw->url_us_used) { + /* time remains for later URLs */ + dnsbl_log(blw, 0, fgs, + "failed after using %.1f url_secs;" + " %.1f msg-secs remain", + blw->url_us_used / (DCC_US*1.0), + (blw->msg_us - blw->url_us_used) / (DCC_US*1.0)); + + } else { + dnsbl_log(blw, 0, fgs, "failed after using %.1f sec", + blw->url_us_used / (DCC_US*1.0)); + } +} + + + +/* see if we are out of time before doing something */ +static inline u_char /* 0=too late */ +time_ck_pre(DNSBL_WORK *blw, DNSBL_FGS fgs) +{ + /* don't worry if there is plenty of time */ + if (blw->url_us >= blw->url_us_used) + return 1; + + /* There is no more time. Ether previous operations succeeded slowly + * or failed and were logged. + * In the first case, log this operation and mark the groups. */ + set_timeo(blw, fgs); + return 0; +} + + + +/* see if we ran out of time after doing something */ +static u_char /* 0=out of time */ +time_ck_post(DNSBL_WORK *blw, DNSBL_FGS fgs, + u_char timedout) /* 1=operation timed out */ +{ + struct timeval now; + time_t used_us; + + if (blw->url_us <= 0) + return 0; /* previously out of time */ + + gettimeofday(&now, 0); + used_us = tv_diff2us(&now, &blw->url_start); + + if (blw->url_us >= used_us && !timedout) { + if (helper.debug > 1 + && (used_us-blw->url_us_used) > url_us/4) + dnsbl_log(blw, 0, fgs, "%s after using %.1f url_secs", + timedout ? "failed" : "succeeded", + (used_us - blw->url_us_used) / (DCC_US*1.0)); + blw->url_us_used = used_us; + return 1; + } + + blw->url_us_used = used_us; + set_timeo(blw, fgs); + return 0; +} + + + +/* start timer before we start to check something in the DNS blacklists + * give up if we are already out of time */ +static u_char /* 0=already too much time spent */ +msg_secs_start(DNSBL_WORK *blw, DNSBL_FGS fgs) +{ + blw->url_us = url_us; + blw->url_us_used = 0; + + if (blw->msg_us <= 0) { + if (blw->msg_us == 0) { + /* out of time for next URL before we start it */ + if (helper.debug) + dnsbl_log(blw, 0, fgs, + "%d msg-secs already exhausted", + msg_secs); + blw->msg_us = -1; /* only one log message */ + blw->url_us = -1; + } + /* mark the groups but do not log anything */ + set_timeo(blw, fgs); + return 0; + } + + gettimeofday(&blw->url_start, 0); + return 1; +} + + + +/* account for time used */ +static void +msg_secs_fin(DNSBL_WORK *blw) +{ + if (blw->msg_us < 0) + return; /* prevously out of time */ + + blw->msg_us -= blw->url_us_used; + if (blw->msg_us > 0) + return; + + if (blw->url_us >= blw->url_us_used) { + /* The clock had no time for more DNS work for this + * name or address, but we finished and so it might + * not matter. + * Ensure a log message on the next check, if any */ + blw->msg_us = 0; + } +} + + + +#ifndef HAVE__RES +static void +dnsbl_res_delays(const DNSBL_WORK *blw UATTRIB) +{ + return; +} +#else +/* Limit resolver delays to as much as we are willing to wait + * We should be talking to a local caching resolver. If it does not answer + * immediately, it is unlikely to later. If it does eventually get an + * answer, the answer will probably ready the next time we ask. + * dcc_host_lock() must be held. */ +static void +dnsbl_res_delays(const DNSBL_WORK *blw) +{ + static int init_res_retrans; + static int init_res_retry; + int budget; /* seconds we can afford */ + int res_retrans; /* retransmition delay */ + int res_retry; /* # of retransmissions */ + int total; /* retrans*retry = worst case delay */ + + /* get the current value */ + if (!_res.options & RES_INIT) { + res_init(); + init_res_retry = _res.retry; + if (!init_res_retry) + init_res_retry = 4; + init_res_retrans = _res.retrans; + if (!init_res_retrans) + init_res_retrans = RES_TIMEOUT; + } + res_retry = init_res_retry; + res_retrans = init_res_retrans; + + /* assume binary exponential backoffs as in the BIND resolver */ + total = ((1<<res_retry) -1) * res_retrans; + + /* If the default values could take too long, then try 2 seconds. + * If that is still too long, go to 1 retransmission and an initial + * retransmission delay of 1/3 of the total allowed delay. + * 1/3 from one exponential backoff for a total of 2**2-1=3 times + * the initial delay. + * We should be using a local caching DNS server and so should not + * see many lost packets */ + budget = (blw->url_us - blw->url_us_used + DCC_US/2) / DCC_US; + if (budget < 1) + budget = 1; + + if (total >= budget) { + res_retry = 2; /* 2 retries are often few enough */ + total = ((1<<res_retry) -1) * res_retrans; + + /* if that is not few enough, + * then reduce the retransmission delay to fit */ + if (total >= budget) { + res_retrans = budget/3; + if (res_retrans == 0) + res_retrans = 1; + } + } + + if (_res.retry != res_retry + || _res.retrans != res_retrans) { + _res.retry = res_retry; + _res.retrans = res_retrans; + if (helper.debug > 4) + dnsbl_log(blw, 0, 0, + "budget=%d _res.retry=%d" + " _res.retrans=%d seconds", + budget, res_retry, res_retrans); + } +} +#endif /* !HAVE__RES */ + + + +static inline void +blw_clear(DNSBL_WORK *blw) +{ + DNSBL_GROUP *blg; + + blw->tgt.dom.c[0] = '\0'; + blw->tgt_dom_len = 0; + for (blg = blw->groups; blg <= LAST(blw->groups); ++blg) { + blg->fgs = 0; + blg->btype = 0; + blg->result[0] = '\0'; + blg->tgt.c[0] = '\0'; + blg->probe.c[0] = '\0'; + } +} + + + +/* get ready to handle a mail message */ +void +dcc_dnsbl_init(DCC_GOT_CKS *cks, + DCC_CLNT_CTXT *dcc_ctxt, void *log_ctxt, const char *id) +{ + DNSBL_WORK *blw; + int i; + + if (!dnsbls) + return; + + blw = cks->dnsbl; + if (!blw) { + blw = dcc_malloc(sizeof(*blw)); + memset(blw, 0, sizeof(*blw)); + cks->dnsbl = blw; + + /* general initializations on the first use of DNS blacklists */ + fix_url_secs(); + } + + blw_clear(blw); + blw->msg_us = msg_us; + blw->id = id; + blw->dcc_ctxt = dcc_ctxt; + blw->log_ctxt = log_ctxt; + blw->unhit = dnsbl_groups; + for (i = 0; i < DIM(blw->tgt_cache); ++i) + blw->tgt_cache[i].dom.c[0] = '\0'; + blw->tgt_cache_pos = 0; +} + + + +/* look for a host name or IP address in a DNS blacklist and its duplicates + * and any hit groups of DNSBLs. + * These DNS operations should be done with local default values for + * RES_DEFNAMES, RES_DNSRCH, and RES_NOALIASES because the blacklist + * might be something local and strange. */ +static u_char /* 0=no more time or blacklists */ +lookup(DNSBL_WORK *blw, + const DNSBL_DOM *probe, /* look for this */ + const DNSBL *dp, /* check this blacklist & its dups */ + DNSBL_FGS fgs, /* type of lookup */ + const void *tgt) /* IP address or whatever for tracing */ +{ +# define NUM_SUSTRS 3 + char sustrs[DCC_SU2STR_SIZE*NUM_SUSTRS+1+sizeof("...")]; + const DCC_SOCKU *hap; + struct in6_addr addr6; + const struct in6_addr *addr6p; + int error; + DNSBL_GROUP *blg; + DNSBL_GBITS gbit; + u_char hit; + char *p; + int i; + + if (!time_ck_pre(blw, fgs)) + return 0; + + /* resolve a list of IP addresses for the probe in the DNSBL */ + dcc_host_lock(); + dnsbl_res_delays(blw); + if (!dcc_get_host(probe->c, dp->tgt_use_ipv6, &error)) { + dcc_host_unlock(); + if (helper.debug > 1) + dnsbl_log(blw, 0, fgs, "gethostbyname(%s): %s", + probe->c, DCC_HSTRERROR(error)); + return time_ck_post(blw, fgs, error == TRY_AGAIN); + } + + /* check each address obtained for the probe in the DNSBL + * for a hit in the list of duplicate references to this DNSBL */ + hit = 0; + do { + /* skip this reference if it is for the wrong type of DNSBL */ + if ((dp->fgs & fgs) != fgs) + continue; + /* skip this reference if the containing group has a hit */ + blg = &blw->groups[dp->group]; + if (blg->fgs & DNSBL_FG_HITS) + continue; + /* check all of the addresses for a hit with this reference */ + for (hap = dcc_hostaddrs; hap < dcc_hostaddrs_end; ++hap) { + /* finished if any answer is good enough + * or if we get a match */ + if (hap->sa.sa_family == AF_INET6) { + addr6p = &hap->ipv6.sin6_addr; + } else { + addr6p = &addr6; + dcc_ipv4toipv6(&addr6, hap->ipv4.sin_addr); + } + if (!DCC_IN_BLOCK(*addr6p, dp->tgt, dp->tgt_mask)) + continue; + + /* got a hit */ + blg->dnsbl = dp; + blg->fgs = fgs; + if (dp->bl_type == DNSBL_TYPE_IPV4) + dcc_ipv4tostr(blg->tgt.c, sizeof(blg->tgt), + tgt); + else if (dp->bl_type == DNSBL_TYPE_IPV6) + dcc_ipv6tostr(blg->tgt.c, sizeof(blg->tgt), + tgt); + else if (dp->bl_type == DNSBL_TYPE_NAME) + BUFCPY(blg->tgt.c, tgt); + blg->btype = type_str(blg->btype_buf, + sizeof(blg->btype_buf), + blw, blg->fgs); + BUFCPY(blg->probe.c, probe->c); + dcc_su2str2(blg->result, sizeof(blg->result), hap); + if (helper.debug > 1 && (is_helper || !have_helpers)) + dnsbl_log(blw, blg, fgs, "hit %s=%s", + probe->c, + dcc_su2str2(sustrs, sizeof(sustrs), + hap)); + gbit = DNSBL_G2B(dp->group); + if (fgs & DNSBL_FG_CLIENT) + blw->unhit.client &= ~gbit; + if (fgs & DNSBL_FG_MAIL_HOST) { + blw->unhit.mail_host &= ~gbit; + if (fgs & DNSBL_FG_MX) + blw->unhit.mail_host_mx &= ~gbit; + if (fgs & DNSBL_FG_NS) + blw->unhit.mail_host_ns &= ~gbit; + } + if (fgs & DNSBL_FG_URL) { + blw->unhit.url &= ~gbit; + if (fgs & DNSBL_FG_MX) + blw->unhit.url_mx &= ~gbit; + if (fgs & DNSBL_FG_NS) + blw->unhit.url_ns &= ~gbit; + } + blw->unhit.all &= ~gbit; + if (!blw->unhit.all) { + dcc_host_unlock(); + time_ck_post(blw, fgs, 0); + return 0; + } + hit = 1; + break; + } + + /* check the results in duplicate references to the DNSBL */ + } while ((dp = dp->dup) != 0); + + if (hit || helper.debug < 2) { + dcc_host_unlock(); + return time_ck_post(blw, fgs, 0); + } + + p = sustrs; + i = 0; + do { + dcc_su2str2(p, DCC_SU2STR_SIZE, &dcc_hostaddrs[i]); + p += strlen(p); + *p++ = ' '; + *p = '\0'; + if (p >= &sustrs[DCC_SU2STR_SIZE*NUM_SUSTRS]) { + strcpy(p, "..."); + break; + } + } while (dcc_hostaddrs_end > &dcc_hostaddrs[++i]); + dcc_host_unlock(); + dnsbl_log(blw, 0, fgs, "miss; gethostbyname(%s)=%s", probe->c, sustrs); + return time_ck_post(blw, fgs, 0); + +#undef NUM_SUSTRS +} + + + +/* check one IP address against the DNS blacklists */ +static u_char /* 0=no more time or blacklists */ +ip_lookup(DNSBL_WORK *blw, + DNSBL_TYPE tgt_type, + const void *tgt, /* in_addr* or in6_addr*, not aligned */ + DNSBL_FGS fgs) /* type of lookup */ +{ + const DNSBL *dp; + const u_char *bp; + DNSBL_DOM probe; + + /* check all DNSBLs for a group without a hit */ + for (dp = dnsbls; dp; dp = dp->fwd) { + if (dp->bl_type != tgt_type) + continue; + if ((dp->fgs & fgs) != fgs) + continue; + if (dp->fgs & DNSBL_FG_DUP) + continue; + + if (!(blw->unhit.all & DNSBL_G2B(dp->group))) + continue; + + bp = (u_char *)tgt; + if (tgt_type == DNSBL_TYPE_IPV4) + snprintf(probe.c, sizeof(probe.c), + "%d.%d.%d.%d.%s", + bp[3], bp[2], bp[1], bp[0], + dp->bl_dom.c); + else + snprintf(probe.c, sizeof(probe.c), + "%d.%d.%d.%d.%d.%d.%d.%d" + ".%d.%d.%d.%d.%d.%d.%d.%d.%s", + bp[15], bp[14], bp[13], bp[12], + bp[11], bp[10], bp[9], bp[8], + bp[7], bp[6], bp[5], bp[4], + bp[3], bp[2], bp[1], bp[0], + dp->bl_dom.c); + + if (!lookup(blw, &probe, dp, fgs, tgt)) + return 0; + } + return 1; +} + + + +/* convert a name to one or more IP addresses to be checked in a DNS blacklist. + * dcc_host_lock() must be held. + * These DNS operations need RES_DEFNAMES and RES_DNSRCH off and + * RES_NOALIASES on when the name is an MX or NS server name. + */ +static u_char /* 0=failed */ +dnsbl_get_host(DNSBL_WORK *blw, + const DNSBL_DOM *dom, + u_char use_ipv6, + int *errorp, /* put error number here */ + DNSBL_FGS fgs /* 0, DNSBL_FG_MX, or DNSBL_FG_NS */ +#ifndef MXNS_DNSBL + UATTRIB +#endif + ) +{ +#ifdef MXNS_DNSBL + u_long save_options; +#endif + u_char result; + +#ifdef MXNS_DNSBL + save_options = _res.options; + if (fgs & (DNSBL_FG_MX | DNSBL_FG_NS)) { + _res.options &= ~(RES_DEFNAMES | RES_DNSRCH); + _res.options |= RES_NOALIASES; + } +#endif + dnsbl_res_delays(blw); + result = dcc_get_host(dom->c, use_ipv6, errorp); +#ifdef MXNS_DNSBL + if (fgs & (DNSBL_FG_MX | DNSBL_FG_NS)) + _res.options = save_options; +#endif + return result; +} + + + +/* look for a domain name in the DNS blacklists */ +static u_char /* 0=no more time or blacklists */ +name_lookup(DNSBL_WORK *blw, + const DNSBL_DOM *tgt, + DNSBL_FGS fgs) /* type of lookup */ +{ + const DNSBL *dp; + DNSBL_DOM probe; + const char *p; + int tgt_len, i; + + if (!have_name_dnsbl) + return 1; + + for (dp = dnsbls; dp; dp = dp->fwd) { + if (dp->bl_type != DNSBL_TYPE_NAME) + continue; + if ((dp->fgs & fgs) != fgs) + continue; + if (dp->fgs & DNSBL_FG_DUP) + continue; + + if (!(blw->unhit.all & DNSBL_G2B(dp->group))) + continue; + + /* trim trailing '.' from names */ + p = tgt->c; + tgt_len = strlen(p); + if (tgt_len > 0 && p[tgt_len-1] == '.') + --tgt_len; + + if (tgt_len != 0) { /* handle empty name */ + /* truncate long names on the left and complain */ + i = (tgt_len + dp->bl_dom_len) - (sizeof(probe.c) - 1); + if (i > 0) { + if (helper.debug) + dnsbl_log(blw, 0, 0, + "target \"%s\"" + " is %d bytes too long", + tgt->c, i); + p += i; + tgt_len -= i; + } + memcpy(&probe.c[0], p, tgt_len); + probe.c[tgt_len++] = '.'; + } + memcpy(&probe.c[tgt_len], dp->bl_dom.c, dp->bl_dom_len); + + if (!lookup(blw, &probe, dp, fgs, tgt)) + return 0; + } + + return 1; +} + + + +/* look for a domain name and its IP addresses in the DNS blacklists */ +static u_char /* 0=no more time or blacklists */ +name_ip_lookup(DNSBL_WORK *blw, + const DNSBL_DOM *tgt, + DNSBL_FGS fgs) /* type of lookup */ +{ + const DCC_SOCKU *sup; + struct in_addr ipv4[8]; + struct in6_addr ipv6[4]; + int i, error; + + /* check the name */ + if (!name_lookup(blw, tgt, fgs)) + return 0; + + /* cannot resolve a null name into an address to try in the DNSBLs */ + if (tgt->c[0] == '\0') + return 1; + + if (!time_ck_pre(blw, fgs)) + return 0; + + /* check IPv4 addresses for the name in IPv4 DNSBLs */ + if (have_ipv4_dnsbl) { + dcc_host_lock(); + /* first resolve IPv4 addresses for the URL or client name */ + if (!dnsbl_get_host(blw, tgt, 0, &error, fgs)) { + dcc_host_unlock(); + if (helper.debug > 1) + dnsbl_log(blw, 0, fgs, "gethostbyname(%s): %s", + tgt->c, DCC_HSTRERROR(error)); + if (!time_ck_post(blw, fgs, error == TRY_AGAIN)) + return 0; + } else { + /* Try several of the IP addresses for the domain. + * Save any IP addresses we want to check before we + * check them, because checking changes the array + * of addresses. */ + for (sup = dcc_hostaddrs, i = 0; + sup < dcc_hostaddrs_end && i < DIM(ipv4); + ++sup, ++i) { + ipv4[i] = sup->ipv4.sin_addr; + } + dcc_host_unlock(); + if (!time_ck_post(blw, fgs, 0)) + return 0; + /* check the addresses in all of the DNS blacklists */ + do { + if (!ip_lookup(blw, DNSBL_TYPE_IPV4, + &ipv4[--i], fgs)) + return 0; + } while (i > 0); + } + } + + /* finally try IPv6 addresses for the name */ + if (have_ipv6_dnsbl) { + dcc_host_lock(); + if (!dnsbl_get_host(blw, tgt, 1, &error, fgs)) { + dcc_host_unlock(); + if (helper.debug > 1) + dnsbl_log(blw, 0, fgs, "gethostbyname(%s): %s", + tgt->c, DCC_HSTRERROR(error)); + if (!time_ck_post(blw, fgs, error == TRY_AGAIN)) + return 0; + } else { + for (sup = dcc_hostaddrs, i = 0; + sup < dcc_hostaddrs_end && i < DIM(ipv6); + ++sup, ++i) { + ipv6[i] = sup->ipv6.sin6_addr; + } + dcc_host_unlock(); + if (!time_ck_post(blw, fgs, 0)) + return 0; + do { + if (!ip_lookup(blw, DNSBL_TYPE_IPV6, + &ipv4[--i], fgs)) + return 0; + } while (i > 0); + } + } + + return 1; +} + + + +/* check an SMTP client */ +static void +dnsbl_client(DNSBL_WORK *blw) +{ + struct in_addr ipv4; + + if (blw->tgt.dom.c[0] != '\0' + && blw->tgt.dom.c[0] != '[') + name_lookup(blw, &blw->tgt.dom, DNSBL_FG_CLIENT); + + if (have_ipv4_dnsbl + && dcc_ipv6toipv4(&ipv4, &blw->tgt.addr)) { + if (!ip_lookup(blw, DNSBL_TYPE_IPV4, + &ipv4, DNSBL_FG_CLIENT)) + return; + } + + if (have_ipv6_dnsbl + && !ip_lookup(blw, DNSBL_TYPE_IPV6, + &blw->tgt.addr, DNSBL_FG_CLIENT)) + return; +} + + + + +#ifdef MXNS_DNSBL +/* look at DNS resource records */ +static u_char /* 1=continue 0=stop looking */ +rr_lookup(DNSBL_WORK *blw, + const char *name, /* see if this domain is blacklisted */ + DNSBL_FGS fgs, + u_short req_type) /* T_A, T_AAAA, or T_MX */ +{ + union { + u_char buf[PACKETSZ+20]; + HEADER hdr; + } answer; + DNSBL_DOM dom; + const u_char *ap, *ap1, *eom; + int cnt, skip; + u_short resp_type; + u_short resp_class, rdlength; + int i; + + if (!time_ck_pre(blw, fgs)) + return 0; + + /* resolve a set of RRs of the desired type for a name */ + dnsbl_res_delays(blw); + dcc_host_lock(); + i = res_query(name, C_IN, req_type, answer.buf, sizeof(answer.buf)); + dcc_host_unlock(); + if (i < 0) { + /* use raw hstrerror() here because we are using the + * raw resolver */ + if (helper.debug > 1) + dnsbl_log(blw, 0, fgs, "res_query(%s): %s", + name, hstrerror(h_errno)); + /* stop looking after too much time or NXDOMAIN */ + return (time_ck_post(blw, fgs, h_errno == TRY_AGAIN) + && h_errno != HOST_NOT_FOUND); + } + if (!time_ck_post(blw, fgs, 0)) + return 0; + + ap = &answer.buf[HFIXEDSZ]; + if (i > ISZ(answer.buf)) + i = ISZ(answer.buf); + eom = &answer.buf[i]; + + /* skip the question */ + cnt = ntohs(answer.hdr.qdcount); + while (--cnt >= 0) { + skip = dn_skipname(ap, eom); + if (skip < 0) { + if (helper.debug > 1) + dnsbl_log(blw, 0, fgs, "dn_skipname(%s)=%d", + name, skip); + /* look for other RRs */ + return 1; + } + ap += skip+QFIXEDSZ; + } + + + /* check each name or address RR in the answer section */ + for (cnt = ntohs(answer.hdr.ancount); + --cnt >= 0 && ap < eom; + ap += rdlength) { + /* get the name */ + skip = dn_expand(answer.buf, eom, ap, dom.c, sizeof(dom.c)); + if (skip < 0) { + if (helper.debug > 1) + dnsbl_log(blw, 0, fgs, + "answer dn_expand(%s)=%d", + name, skip); + return 1; + } + ap += skip; + + /* get RR type and class and skip strange RRs */ + GETSHORT(resp_type, ap); + GETSHORT(resp_class, ap); + ap += 4; /* skip TTL */ + GETSHORT(rdlength, ap); /* get rdlength */ + + /* we care only about relevant answers */ + if (resp_type != req_type + || resp_class != C_IN) + continue; + + if (req_type == T_MX) { + /* check MX name */ + ap1 = ap + 2; /* skip MX preference */ + skip = dn_expand(answer.buf, eom, ap1, + dom.c, sizeof(dom.c)); + if (skip < 0) { + if (helper.debug > 1) + dnsbl_log(blw, 0, fgs, + "MX dn_expand(%s)=%d", + name, skip); + return 1; + } + + if (dom.c[0] == '\0' + && helper.debug > 1) + dnsbl_log(blw, 0, fgs, "null MX name"); + + if (!name_ip_lookup(blw, &dom, fgs)) + return 0; + + } else if (req_type == T_A) { + if (!ip_lookup(blw, DNSBL_TYPE_IPV4, ap, fgs)) + return 0; +#ifndef NO_IPV6 + } else if (req_type == T_AAAA) { + if (!ip_lookup(blw, DNSBL_TYPE_IPV6, ap, fgs)) + return 0; +#endif + } + } + + /* check the authority section only if we care about name servers + * and we are not looking for MX servers */ + if ((fgs & DNSBL_FG_MX) + || 0 == ((fgs & DNSBL_FG_URL) ? blw->unhit.url_ns + : blw->unhit.mail_host_ns)) + return 1; + + /* we could look at the additional section, but it can be incomplete */ + fgs |= DNSBL_FG_NS; + for (cnt = ntohs(answer.hdr.nscount); + --cnt >= 0 && ap < eom; + ap += rdlength) { + /* get the name */ + skip = dn_expand(answer.buf, eom, ap, dom.c, sizeof(dom.c)); + if (skip < 0) { + if (helper.debug > 1) + dnsbl_log(blw, 0, fgs, + "ns dn_expand(%s)=%d", + name, skip); + return 1; + } + ap += skip; + + /* get RR type and class */ + GETSHORT(resp_type, ap); + GETSHORT(resp_class, ap); + ap += 4; /* skip TTL */ + GETSHORT(rdlength, ap); /* get rdlength */ + + /* we care only about NS RRs */ + if (resp_type != T_NS + || resp_class != C_IN) + continue; + + skip = dn_expand(answer.buf, eom, ap, dom.c, sizeof(dom.c)); + if (skip < 0) { + if (helper.debug > 1) + dnsbl_log(blw, 0, fgs, + "ns answer dn_expand(%s)=%d", + name, skip); + return 1; + } + + if (dom.c[0] == '\0' + && helper.debug > 1) + dnsbl_log(blw, 0, fgs, "null NS name"); + + if (!name_ip_lookup(blw, &dom, fgs)) + return 0; + } + + return 1; +} +#endif /* MXNS_DNSBL */ + + + +/* look for a domain in the DNS blacklists, including its MX & NS servers */ +static void +dnsbl_name_host_url(DNSBL_WORK *blw, + DNSBL_FGS fg) +{ + struct in_addr ipv4; +#ifndef NO_IPV6 + struct dcc_in6_addr ipv6; +#endif + + /* recognize an ASCII IP address */ + ipv4.s_addr = inet_addr(blw->tgt.dom.c); + if (INADDR_NONE != ipv4.s_addr) { + ip_lookup(blw, DNSBL_TYPE_IPV4, &ipv4, fg); + return; + } +#ifndef NO_IPV6 + if (0 < inet_pton(AF_INET6, blw->tgt.dom.c, &ipv6)) { + ip_lookup(blw, DNSBL_TYPE_IPV6, &ipv6, fg); + return; + } +#endif + +#ifndef MXNS_DNSBL + /* check simple name and its addresses if we do not have a + * resolver library */ + name_ip_lookup(blw, &blw->tgt.dom, fg); + +#else /* MXNS_DNSBL */ + /* If we have a resolver library, check first for the name itself */ + if (!name_lookup(blw, &blw->tgt.dom, fg)) + return; + + /* Look for name servers in the authority section after asking for + * A RRs. You cannot rely on the bad guys' DNS servers to answer an + * ANY request */ + if (have_ipv4_dnsbl && !rr_lookup(blw, blw->tgt.dom.c, fg, T_A)) + return; +#ifndef NO_IPV6 + if (have_ipv6_dnsbl && !rr_lookup(blw, blw->tgt.dom.c, fg, T_AAAA)) + return; +#endif + + /* Check MX servers if allowed by at least one DNS blacklist. + * To ape the fall back to A RRs when MX RRs are missing, we need to + * check A RR for evil. However, we've already done that */ + if (0 != ((fg & DNSBL_FG_URL) ? blw->unhit.url_mx + : blw->unhit.mail_host_mx)) + rr_lookup(blw, blw->tgt.dom.c, fg | DNSBL_FG_MX, T_MX); +#endif /* MXNS_DNSBL */ +} + + + +#ifdef HAVE_HELPERS +/* do some DNSBL work in a helper process */ +u_char /* 1=try to send the response */ +dnsbl_work(const DNSBL_REQ *req, DNSBL_RESP *resp) +{ + DNSBL_WORK blw; + DNSBL_RESP_GROUP *rst; + DNSBL_GROUP *blg; + int gnum, i; + + blw_clear(&blw); + blw.url_start = req->hdr.start; + blw.msg_us = MAX_MSG_SECS*DCC_US*2; + blw.url_us = req->hdr.avail_us; + blw.url_us_used = 0; + blw.id = req->hdr.id; + blw.dcc_ctxt = 0; + blw.log_ctxt = 0; + + if (!is_helper) { + /* this must be the first job for this helper process */ + is_helper = 1; + helper_str = " helper"; + fix_url_secs(); + } + + blw.unhit = req->unhit; + for (i = 0, blg = blw.groups; i < MAX_DNSBL_GROUPS; ++i, ++blg) + blg->fgs = req->fgs[i]; + switch (req->fg) { + case DNSBL_FG_CLIENT: /* SMTP client IP address */ + blw.tgt.addr = req->tgt.addr; + BUFCPY(blw.tgt.dom.c, req->tgt.dom.c); + dnsbl_client(&blw); + break; + case DNSBL_FG_MAIL_HOST: /* envelope mail_from */ + case DNSBL_FG_URL: /* URL in body */ + BUFCPY(blw.tgt.dom.c, req->tgt.dom.c); + dnsbl_name_host_url(&blw, req->fg); + break; + default: + dcc_logbad(EX_SOFTWARE, "%s DNSBL%s unknown type %d", + req->hdr.id, helper_str, req->fg); + } + + resp->unhit = blw.unhit; + for (gnum = 0, blg = blw.groups, rst = resp->groups; + gnum < MAX_DNSBL_GROUPS; + ++gnum, ++rst, ++blg) { + rst->fgs = blg->fgs; /* copy hit & timeout flags */ + if (0 == ((resp->unhit.all ^ req->unhit.all) + & DNSBL_G2B(gnum))) { + /* no new hit here in the helper + * or had a hit in the parent */ + rst->bl_num = -1; + rst->probe.c[0] = '\0'; + rst->result[0] = '\0'; + } else { + rst->bl_num = blg->dnsbl->bl_num; + BUFCPY(rst->tgt.c, blg->tgt.c); + BUFCPY(rst->probe.c, blg->probe.c); + BUFCPY(rst->result, blg->result); + } + } + + return 1; +} + + + +/* ask a helper process to check for something in the DNS blacklists */ +static void +use_helper(DNSBL_WORK *blw, DNSBL_FGS fg) +{ + DNSBL_REQ req; + DNSBL_RESP resp; + DNSBL_RESP_GROUP *rst; + DNSBL_GROUP *blg; + const DNSBL *dp; + int gnum, i; + + memset(&req, 0, sizeof(req)); + + BUFCPY(req.hdr.id, blw->id); + req.unhit = blw->unhit; + for (gnum = 0, blg = blw->groups; + gnum < MAX_DNSBL_GROUPS; + ++gnum, ++blg) + req.fgs[gnum] = blg->fgs; + req.fg = fg; + switch (fg) { + case DNSBL_FG_CLIENT: + BUFCPY(req.tgt.dom.c, blw->tgt.dom.c); + req.tgt.addr = blw->tgt.addr; + break; + case DNSBL_FG_MAIL_HOST: + case DNSBL_FG_URL: + BUFCPY(req.tgt.dom.c, blw->tgt.dom.c); + break; + default: + dcc_logbad(EX_SOFTWARE, "unknown DNSBL type %d", fg); + } + + if (!ask_helper(blw->dcc_ctxt, blw->log_ctxt, url_us, + &req.hdr, sizeof(req), &resp.hdr, sizeof(resp))) { + time_ck_post(blw, fg, 1); + msg_secs_fin(blw); + return; + } + + blw->unhit = resp.unhit; + /* record hits and timeouts seen by the helper */ + for (gnum = 0, blg = blw->groups, rst = resp.groups; + gnum < MAX_DNSBL_GROUPS; + ++gnum, ++rst, ++blg) { + blg->fgs = rst->fgs; + if (0 == ((resp.unhit.all ^ req.unhit.all) & DNSBL_G2B(gnum))) + continue; + + /* This group has a new hit. + * Discover the right DNSBL so we can use the right + * template for the SMTP status message */ + for (i = rst->bl_num, dp = dnsbls; i > 1; --i, dp = dp->fwd) { + if (!dp) + dcc_logbad(EX_SOFTWARE, + "bad helper DNSBL #%d", + rst->bl_num); + } + blg->dnsbl = dp; + BUFCPY(blg->tgt.c, rst->tgt.c); + BUFCPY(blg->probe.c, rst->probe.c); + BUFCPY(blg->result, rst->result); + blg->btype = type_str(blg->btype_buf, sizeof(blg->btype_buf), + blw, blg->fgs); + } + + msg_secs_fin(blw); +} +#endif /* HAVE_HELPERS */ + + +void +dcc_client_dnsbl(DNSBL_WORK *blw, const struct in6_addr *addr, const char *name) +{ + /* nothing to do if no client DNS blacklists have been configured */ + if (!blw || !blw->unhit.client) + return; + + /* or if we have already spent too much time checking blacklists */ + if (!msg_secs_start(blw, DNSBL_FG_CLIENT)) + return; + + BUFCPY(blw->tgt.dom.c, name); + blw->tgt.addr = *addr; + +#ifdef HAVE_HELPERS + if (have_helpers) { + use_helper(blw, DNSBL_FG_CLIENT); + return; + } +#endif + + dnsbl_client(blw); + msg_secs_fin(blw); +} + + + +void +dcc_mail_host_dnsbl(DNSBL_WORK *blw, const char *host) +{ + /* nothing to do if no DNS blacklists have been configured + * or we do not have an envelope Mail_From host name + * or no recipients care about mail_host hits */ + if (!blw || !blw->unhit.mail_host + || !host || *host == '\0') + return; + + /* or no still active DNSBLs care about the Mail_From host name */ + + /* or if we have already spent too much time checking blacklists */ + if (!msg_secs_start(blw, DNSBL_FG_MAIL_HOST)) + return; + + BUFCPY(blw->tgt.dom.c, host); + +#ifdef HAVE_HELPERS + if (have_helpers) { + use_helper(blw, DNSBL_FG_MAIL_HOST); + return; + } +#endif + + dnsbl_name_host_url(blw, DNSBL_FG_MAIL_HOST); + msg_secs_fin(blw); +} + + + +void +url_dnsbl(DNSBL_WORK *blw) +{ + int i; + + /* nothing to do if no body URL DNSBLs have been configured + * or if we already have hits for all DNSBLs */ + if (!blw || !blw->unhit.url) + return; + + /* or if we have already spent too much time checking blacklists */ + if (!msg_secs_start(blw, DNSBL_FG_URL)) + return; + + /* avoid checking the same names */ + for (i = 0; i < DIM(blw->tgt_cache); ++i) { + if (!strcmp(blw->tgt.dom.c, blw->tgt_cache[i].dom.c)) { + if (helper.debug > 1) + dnsbl_log(blw, 0, DNSBL_FG_URL, + "tgt_cache hit"); + blw->tgt_cache_pos = i; + return; + } + } + BUFCPY(blw->tgt_cache[blw->tgt_cache_pos].dom.c, blw->tgt.dom.c); + blw->tgt_cache_pos = (blw->tgt_cache_pos + 1) % DIM(blw->tgt_cache); + +#ifdef HAVE_HELPERS + if (have_helpers) { + use_helper(blw, DNSBL_FG_URL); + return; + } +#endif + + dnsbl_name_host_url(blw, DNSBL_FG_URL); + msg_secs_fin(blw); +}