Mercurial > notdcc
view 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 source
/* 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); }