/* Distributed Checksum Clearinghouse * * reject messages contain URLs that resolve to DNS blacklisted IP addresses * * Copyright (c) 2006 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 for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * Parties not eligible to receive a license under this agreement can * obtain a commercial license to use DCC and permission to use * U.S. Patent 6,330,590 by contacting Commtouch at http://www.commtouch.com/ * or by email to nospam@commtouch.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.50-1.72 $Revision$ */ #include "helper.h" #include "dcc_heap_debug.h" #ifndef DCC_WIN32 #include #include #endif #ifdef HAVE_RESOLV_H #include #endif #ifdef HAVE_ARPA_NAMESER_H #include #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 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(C_IN) || !defined(PACKETSZ) #undef MXNS_DNSBL #endif #if !defined(RES_DEFNAMES) || !defined(RES_DNSRCH) || !defined(RES_NOALIASES) #undef MXNS_DNSBL #endif DNSBL *dnsbls; HELPER helper; u_char have_helpers; DCC_PATH dnsbl_progpath; static u_char is_helper; static u_char dnsbl_have_ipv6; /* have DNSBL to check IPv6 addresses */ static u_char dnsbl_have_ipv4; /* have DNSBL to check IPv4 addresses */ static u_char all_dnsbl_flags; #define MAX_MSG_SECS 1000 #ifndef RES_TIMEOUT #define RES_TIMEOUT 3 #endif static int dnsbl_msg_secs = 25; /* total seconds/mail message */ static time_t dnsbl_msg_us; static int dnsbl_url_secs = 11; /* total seconds/host name */ static time_t dnsbl_url_us; static inline const char * htype2str(const DNSBL_WORK *blw) { switch (blw->htype) { case DNSBL_HTYPE_STD: return ""; case DNSBL_HTYPE_MX: return " MX"; case DNSBL_HTYPE_NS: return " NS"; default: dcc_logbad(EX_SOFTWARE, "impossible htype"); } } /* Parse a string of the form: * "domain[,[IPaddr][,name|ipv4|ipv6]] * or of one of the forms: * 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-]envelope envelope sender client IP address checks * set:[no-]body body URL checks * set:[no-]MX MX checks * set:[no-]NS NS checks * set:progpath path of helper program * * 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) { static u_char cur_flags = (DNSBL_FG_ENVELOPE | DNSBL_FG_BODY | DNSBL_FG_MX | DNSBL_FG_NS); static const void *cur_reply = 0; static int bl_num = 0; DNSBL *dp; const char *ip; /* "hit" IP address of this blacklist */ DNSBL_DOM ip_buf; enum DNSBL_TYPE bl_type; u_char result_use_ipv6; int error, bl_dom_len, ip_addr_len; int val; char *p; #ifdef HAVE_HELPERS SOCKET 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); } /* handle parameter settings */ if (!CSTRCMP(entry, "set:")) { const char *parm = entry+STRZ("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 (!CSTRCMP(parm, "progpath=")) { BUFCPY(dnsbl_progpath, parm+STRZ("progpath=")); return 1; } if (1 == sscanf(parm, "max_helpers=%u", &val)) { helper.max_helpers = val; SAVE_ARG(entry); return 1; } if (!strcasecmp(parm, "debug")) { ++helper.debug; SAVE_ARG(entry); return 1; } if (1 == sscanf(parm, "debug=%u", &val)) { helper.debug = val; SAVE_ARG(entry); return 1; } if (!strcasecmp(parm, "envelope")) { cur_flags |= DNSBL_FG_ENVELOPE; SAVE_ARG(entry); return 1; } if (!strcasecmp(parm, "no-envelope") || !strcasecmp(parm, "no_envelope")) { cur_flags &= ~DNSBL_FG_ENVELOPE; SAVE_ARG(entry); return 1; } if (!strcasecmp(parm, "body")) { cur_flags |= DNSBL_FG_BODY; SAVE_ARG(entry); return 1; } if (!strcasecmp(parm, "no-body") || !strcasecmp(parm, "no_body")) { cur_flags &= ~DNSBL_FG_BODY; SAVE_ARG(entry); return 1; } if (!strcasecmp(parm, "mx")) { #ifdef MXNS_DNSBL cur_flags |= 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") || !strcasecmp(parm, "no_mx")) { cur_flags &= ~DNSBL_FG_MX; SAVE_ARG(entry); return 1; } if (!strcasecmp(parm, "ns")) { #ifdef MXNS_DNSBL cur_flags |= 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") || !strcasecmp(parm, "no_ns")) { cur_flags &= ~DNSBL_FG_NS; SAVE_ARG(entry); return 1; } if (!CSTRCMP(parm, "rej-msg=") || !CSTRCMP(parm, "rej_msg=")) { parm += STRZ("rej-msg="); if (*parm == '\0') cur_reply = 0; else cur_reply = dnsbl_parse_reply(parm); /* do not save for helpers */ return 1; } if (!CSTRCMP(parm, "msg-secs=") || !CSTRCMP(parm, "msg_secs=")) { parm += STRZ("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 (dnsbl_msg_secs != val) { dnsbl_msg_secs = val; SAVE_ARG(entry); } return 1; } if (!CSTRCMP(parm, "url-secs=") || !CSTRCMP(parm, "url_secs=")) { parm += STRZ("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 (dnsbl_url_secs != val) { dnsbl_url_secs = 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; ip = strchr(entry, ','); if (!ip) { bl_dom_len = strlen(entry); dnsbl_have_ipv4 = 1; } else { bl_dom_len = ip - entry; ++ip; /* notice trailing ",name" or ",IPv4" */ p = strchr(ip, ','); if (!p) { dnsbl_have_ipv4 = 1; } else { ++p; if (!strcasecmp(p, "name")) { bl_type = DNSBL_TYPE_NAME; } else if (!strcasecmp(p, "IPV4")) { bl_type = DNSBL_TYPE_IPV4; dnsbl_have_ipv4 = 1; } else if (!strcasecmp(p, "IPV6")) { bl_type = DNSBL_TYPE_IPV6; dnsbl_have_ipv6 = 1; } else { dcc_pemsg(EX_NOHOST, emsg, "unknown DNSBL type in \"%s\"", entry); return 0; } STRLCPY(ip_buf, ip, min(ISZ(ip_buf), p-ip)); ip = ip_buf; } } if (!ip || *ip == '\0') ip = "127.0.0.2"; if (bl_dom_len < 1) { dcc_pemsg(EX_NOHOST, emsg, "invalid DNS blacklist \"%s\"", entry); return 0; } dcc_host_lock(); if (!strcasecmp(ip, "any")) { /* Only the non-zero address family matters in this case. * Whether it is IPv6 or IPv4 does not */ memset(&dcc_hostaddrs[0], 0, sizeof(dcc_hostaddrs[0])); ip_addr_len = INET6_ADDRSTRLEN; result_use_ipv6 = 2; } else if (dcc_get_host(ip, 3, &error)) { /* prefer an IPv4 target address */ if (dcc_hostaddrs[0].sa.sa_family == AF_INET) { ip_addr_len = INET_ADDRSTRLEN; result_use_ipv6 = 0; } else { ip_addr_len = INET6_ADDRSTRLEN; result_use_ipv6 = 1; } } else { dcc_host_unlock(); dcc_pemsg(EX_NOHOST, emsg, "invalid DNSBL IP address \"%s\": %s", ip, DCC_HSTRERROR(error)); return 0; } if (bl_dom_len >= ISZ(dp->bl_dom) - ip_addr_len) { dcc_host_unlock(); /* we cannot fit the DNSBL base and the probe IP address * into blw->tgt_dom. We need to do DNS loops of names * like 33.22.11.10.dnsbl.example.com where maximum length * of "33.22.11.10" depends on ip_addr_len */ dcc_pemsg(EX_NOHOST, emsg, "DNSBL name \"%s\" too long", entry); return 0; } dp = dcc_malloc(sizeof(*dp)); memset(dp, 0, sizeof(*dp)); dp->result_su = dcc_hostaddrs[0]; dp->result_use_ipv6 = result_use_ipv6; dcc_host_unlock(); dp->bl_type = bl_type; dp->flags = cur_flags; dp->reply = cur_reply; all_dnsbl_flags |= cur_flags; memcpy(dp->bl_dom, entry, bl_dom_len); dp->bl_dom_len = bl_dom_len; dp->bl_num = ++bl_num; dp->fwd = dnsbls; dnsbls = dp; SAVE_ARG(entry); return 1; #undef SAVE_ARG } static inline void fix_url_secs(void) { if (dnsbl_url_secs > dnsbl_msg_secs) dnsbl_url_secs = dnsbl_msg_secs; dnsbl_msg_us = dnsbl_msg_secs * DCC_US; dnsbl_url_us = dnsbl_url_secs * DCC_US; } void dcc_dnsbl_result(ASK_ST *ask_stp, const REPLY **reply, const DNSBL_WORK *blw) { const DNSBL *dp; if (!blw) return; if (blw->timeouts) thr_log_print(blw->log_ctxt, 1, "insufficient time for %d DNSBL checks\n", blw->timeouts); switch (blw->hit) { case DNSBL_HIT_NONE: return; case DNSBL_HIT_CLIENT: thr_log_print(blw->log_ctxt, 1, "DNSBL SMTP client hit %s\n", blw->probe); break; case DNSBL_HIT_MAIL_HOST: thr_log_print(blw->log_ctxt, 1, "DNSBL SMTP envelope sender%s hit %s\n", htype2str(blw), blw->probe); break; case DNSBL_HIT_URL: thr_log_print(blw->log_ctxt, 1, "DNSBL body URL%s %s hit %s\n", htype2str(blw), blw->tgt_dom, blw->probe); break; } *ask_stp |= (ASK_ST_DNSBL_ISSPAM | ASK_ST_LOGIT); if (reply) { for (dp = dnsbls; dp; dp = dp->fwd) { if (blw->bl_num == dp->bl_num) { *reply = dp->reply; break; } } } } /* start timer before we start to check DNS blacklists * give up if it has already expired */ static u_char /* 0=already too much time spent */ msg_secs_start(DNSBL_WORK *blw) { if (blw->msg_us < 0) { ++blw->timeouts; return 0; } if (blw->msg_us == 0) { /* out of time for the new domain before we start */ blw->msg_us = -1; if (helper.debug) thr_trace_msg(blw->log_ctxt, "%s DNSBL" " exhausted %d msg-secs before %s", blw->id, dnsbl_msg_secs, blw->tgt_dom); else if (!is_helper) thr_log_print(blw->log_ctxt, 1, "%s DNSBL" " exhausted %d msg-secs before %s", blw->id, dnsbl_msg_secs, blw->tgt_dom); return 0; } gettimeofday(&blw->url_start, 0); blw->url_us = dnsbl_url_us; return 1; } static void trace_secs(DNSBL_WORK *blw) { if (is_helper) { if (helper.debug) thr_trace_msg(blw->log_ctxt, "%s DNSBL helper exhausted " "%d url-secs for %s", blw->id, dnsbl_url_secs, blw->tgt_dom); return; } if (blw->msg_us > 0) { if (helper.debug) thr_trace_msg(blw->log_ctxt, "%s DNSBL failed for %s," " %.1f msg-secs remaining", blw->id, blw->tgt_dom, blw->msg_us/(DCC_US*1.0)); else thr_log_print(blw->log_ctxt, 1, "%s DNSBL failed for %s," " %.1f msg-secs remaining\n", blw->id, blw->tgt_dom, blw->msg_us/(DCC_US*1.0)); } else { if (helper.debug) thr_trace_msg(blw->log_ctxt, "%s DNSBL exhausted %d msg-secs for %s", blw->id, dnsbl_msg_secs, blw->tgt_dom); else thr_log_print(blw->log_ctxt, 1, "%s DNSBL exhausted %d msg-secs for %s\n", blw->id, dnsbl_msg_secs, blw->tgt_dom); } } /* see if we have run out of time */ static u_char /* 0=timeout, 1=keep looking */ msg_secs_ck(DNSBL_WORK *blw) { struct timeval now; time_t used; if (blw->msg_us < 0) return 0; gettimeofday(&now, 0); used = tv_diff2us(&now, &blw->url_start); if (used <= blw->url_us) return 1; blw->url_us = 0; blw->msg_us -= used; if (helper.debug > 1 || (!is_helper && helper.debug > 0)) trace_secs(blw); return 0; } /* account for time used */ static void msg_secs_fin(DNSBL_WORK *blw, u_char timeout) /* 1=ran out of time */ { struct timeval now; time_t used; gettimeofday(&now, 0); used = tv_diff2us(&now, &blw->url_start); if (used < 0) /* handle clock jumps */ return; blw->msg_us -= used; if (blw->msg_us <= 0) { if (!timeout) { /* ensure a log message on the next check, if any */ blw->msg_us = 0; return; } blw->msg_us = -1; } if (blw->msg_us < 0 || timeout) trace_secs(blw); } static void dnsbl_res_init(DNSBL_WORK *blw UATTRIB) { #if defined(HAVE__RES) && defined(HAVE_RES_INIT) int res_retrans; /* _res.retrans, retransmition delay */ int res_retry; /* _res.retry, # of retransmissions */ int total; /* retrans*retry = worst case delay */ /* limit resolver delays to as much as we are willing to wait */ if (!_res.options & RES_INIT) res_init(); res_retry = _res.retry; res_retrans = _res.retrans; if (!res_retry) res_retry = 4; if (!res_retrans) res_retrans = RES_TIMEOUT; /* assume binary exponential backoffs as in the BIND resolver */ total = ((1<= dnsbl_url_secs) { res_retry = 2; /* 2 seconds is usually enough */ total = ((1<= dnsbl_url_secs) { res_retrans = dnsbl_url_secs/3; if (res_retrans == 0) res_retrans = 1; } } if (helper.debug >= 4) thr_trace_msg(blw->log_ctxt, "DNSBL _res.retry=%d _res.retrans=%d", res_retry, res_retrans); _res.retry = res_retry; _res.retrans = res_retrans; #endif /* !HAVE__RES && !HAVE_RES_INIT */ } /* get ready to handle a mail message */ void dcc_dnsbl_init(DCC_GOT_CKS *cks, DCC_CLNT_CTXT *dcc_ctxt, CMN_WORK *log_ctxt, const char *id) { DNSBL_WORK *blw; if (!dnsbls) return; blw = cks->dnsbl; if (!blw) { blw = dcc_malloc(sizeof(*cks->dnsbl)); memset(blw, 0, sizeof(*cks->dnsbl)); cks->dnsbl = blw; /* general initializations on the first use of DNS blacklists */ fix_url_secs(); /* reduce resolver timeouts for dccproc */ #ifdef HAVE_HELPERS if (!have_helpers) #endif dnsbl_res_init(blw); } blw->hit = DNSBL_HIT_NONE; blw->msg_us = dnsbl_msg_us; blw->tgt_dom[blw->tgt_dom_len = 0] = '\0'; blw->timeouts = 0; blw->probe[0] = '\0'; blw->bl_num = 0; blw->id = id; blw->dcc_ctxt = dcc_ctxt; blw->log_ctxt = log_ctxt; } /* look for a host name or IP address in a DNS blacklist. * 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 int /* -1=no time, 0=miss, or hit DNSBL # */ lookup(DNSBL_WORK *blw, const char *probe, /* check this name */ const DNSBL *dp) /* in this blacklist */ { char sustr[DCC_SU2STR_SIZE]; int error; if (!msg_secs_ck(blw)) return -1; dcc_host_lock(); if (!dcc_get_host(probe, dp->result_use_ipv6, &error)) { if (!msg_secs_ck(blw)) return -1; if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL%s %s gethostbyname(%s): %s", blw->id, htype2str(blw), blw->tgt_dom, probe, DCC_HSTRERROR(error)); dcc_host_unlock(); return 0; } if (dp->result_su.sa.sa_family == 0 || DCC_SU_EQ(&dcc_hostaddrs[0], &dp->result_su)) { if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL%s hit %s gethostbyname(%s)=%s", blw->id, htype2str(blw), blw->tgt_dom, probe, dcc_su2str2(sustr, sizeof(sustr), &dcc_hostaddrs[0])); dcc_host_unlock(); return dp->bl_num; } if (helper.debug > 1) { thr_trace_msg(blw->log_ctxt, "%s DNSBL%s miss %s gethostbyname(%s)=%s", blw->id, htype2str(blw), blw->tgt_dom, probe, dcc_su2str2(sustr, sizeof(sustr), &dcc_hostaddrs[0])); } dcc_host_unlock(); return 0; } /* check one IPv4 address against the DNS blacklists */ static int /* -1=no time, 0=miss, or hit DNSBL # */ dnsbl_ipv4(DNSBL_WORK *blw, char *probe, int probe_len, const u_char *bp, u_char flags) { const DNSBL *dp; int ret; for (dp = dnsbls; dp; dp = dp->fwd) { if (dp->bl_type != DNSBL_TYPE_IPV4) continue; if (!(dp->flags & flags)) continue; snprintf(probe, probe_len, "%d.%d.%d.%d.%s", bp[3], bp[2], bp[1], bp[0], dp->bl_dom); ret = lookup(blw, probe, dp); if (ret) return ret; } return 0; } /* check one IPv6 address against the DNS blacklists */ static int /* -1=no time, 0=miss, or hit DNSBL # */ dnsbl_ipv6(DNSBL_WORK *blw, char *probe, int probe_len, const u_char *bp, u_char flags) { const DNSBL *dp; int ret; for (dp = dnsbls; dp; dp = dp->fwd) { if (dp->bl_type != DNSBL_TYPE_IPV6) continue; if (!(dp->flags & flags)) continue; snprintf(probe, probe_len, "%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); ret = lookup(blw, probe, dp); if (ret) return ret; } return 0; } /* convert a name to an IP address so that the IP address can 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(const char *dom, u_char use_ipv6, int *errorp, /* put error number here */ u_char mx_ns /* no domain search for MX or 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 (mx_ns) { _res.options &= ~(RES_DEFNAMES | RES_DNSRCH); _res.options |= RES_NOALIASES; } #endif result = dcc_get_host(dom, use_ipv6, errorp); #ifdef MXNS_DNSBL if (mx_ns) _res.options = save_options; #endif return result; } /* look for a domain name in the DNS blacklists */ static int /* -1=no time, 0=miss, or hit DNSBL # */ dnsbl_name_sub(DNSBL_WORK *blw, char *probe, int probe_len, /* put DNS blacklist probe here */ const char *dom, /* see if this domain is blacklisted */ u_char flags) /* type of lookup */ { const DNSBL *dp; const DCC_SOCKU *sup; struct in_addr ipv4[4]; struct in6_addr ipv6[4]; int ret, i, error; /* give up if none of the DNS blacklists allow this kind of search */ if (!(all_dnsbl_flags & flags)) return 0; /* check the name in any host name DNS blacklists we have */ for (dp = dnsbls; dp; dp = dp->fwd) { if (dp->bl_type != DNSBL_TYPE_NAME) continue; if (!(dp->flags & flags)) continue; /* truncate on the left */ i = probe_len-1-1-dp->bl_dom_len; if (i > 0) { i = strlen(dom) - i; if (i < 0) i = 0; snprintf(probe, probe_len, "%s.%s", dom+i, dp->bl_dom); ret = lookup(blw, probe, dp); if (ret) return ret; if (!msg_secs_ck(blw)) return -1; } } /* try IPv4 second */ if (dnsbl_have_ipv4) { if (!msg_secs_ck(blw)) return -1; dcc_host_lock(); if (!dnsbl_get_host(dom, 0, &error, blw->htype != DNSBL_HTYPE_STD)) { dcc_host_unlock(); if (!msg_secs_ck(blw)) return -1; if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL%s gethostbyname(%s):" " %s", blw->id, htype2str(blw), dom, DCC_HSTRERROR(error)); } else { /* Try several of the IP addresses for the domain. * gethostbyname() often returns pointers to static * buffers that are changed by the next call. * That forces us to save any IP addresses we want to * check before we check them */ for (sup = dcc_hostaddrs, i = 0; sup < dcc_hostaddrs_end && i < DIM(ipv4); ++sup, ++i) { ipv4[i] = sup->ipv4.sin_addr; } dcc_host_unlock(); /* check the addresses in all of the DNS blacklists */ do { ret = dnsbl_ipv4(blw, probe, probe_len, (u_char *)&ipv4[--i], flags); if (ret) return ret; } while (i > 0); } } /* try IPv6 if we have any */ if (dnsbl_have_ipv6) { if (!msg_secs_ck(blw)) return -1; dcc_host_lock(); if (!dnsbl_get_host(dom, 1, &error, blw->htype != DNSBL_HTYPE_STD)) { dcc_host_unlock(); if (!msg_secs_ck(blw)) return -1; if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL%s gethostbyname(%s):" " %s", blw->id, htype2str(blw), dom, DCC_HSTRERROR(error)); } 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(); do { ret = dnsbl_ipv6(blw, probe, probe_len, (u_char *)&ipv6[--i], flags); if (ret) return ret; } while (i > 0); } } return 0; } static int /* -1=no time, 0=miss, or hit DNSBL # */ dnsbl_addr(DNSBL_WORK *blw, char *probe, int probe_len, /* build DNS blacklist probe here */ const struct in6_addr *addr, u_char flags) { struct in_addr ipv4; int ret; /* try IPv4 first */ if (dnsbl_have_ipv4 && dcc_ipv6toipv4(&ipv4, addr)) { ret = dnsbl_ipv4(blw, probe, probe_len, (u_char *)&ipv4, flags); if (ret) return ret; } if (dnsbl_have_ipv6) return dnsbl_ipv6(blw, probe, probe_len, (u_char *)addr, flags); return 0; } #ifdef MXNS_DNSBL static int /* -1=no time, 0=miss, or hit DNSBL # */ dnsbl_name_rr(DNSBL_WORK *blw, char *probe, int probe_len, /* put DNS blacklist probe here */ const char *dom, /* see if this domain is blacklisted */ u_char flags) /* DNSBL_FG_ENVELOPE, ... */ { union { u_char buf[PACKETSZ+20]; HEADER hdr; } answer; DNSBL_DOM resp_dom; u_char *ap, *eom; int cnt, skip, req_type, resp_type, ret, i; switch (blw->htype) { case DNSBL_HTYPE_MX: req_type = T_MX; break; case DNSBL_HTYPE_NS: req_type = T_NS; break; case DNSBL_HTYPE_STD: default: dcc_logbad(EX_SOFTWARE, "impossible htype"); } i = res_query(dom, C_IN, req_type, answer.buf, sizeof(answer.buf)); if (i < 0) { /* use raw hstrerror() here because we are using the * raw resolver */ if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL res_query(%s,%s): %s", blw->id, dom, htype2str(blw), hstrerror(h_errno)); 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) thr_trace_msg(blw->log_ctxt, "%s DNSBL MX dn_skipname(%s)=%d", blw->id, dom, skip); return 0; } ap += skip+QFIXEDSZ; } cnt = ntohs(answer.hdr.ancount); while (--cnt >= 0 && ap < eom) { skip = dn_expand(answer.buf, eom, ap, resp_dom, sizeof(resp_dom)); if (skip < 0) { if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL MX dn_expand(%s)=%d", blw->id, dom, skip); return 0; } ap += skip; GETSHORT(resp_type, ap); ap += 2+4; /* skip class and TTL */ GETSHORT(skip, ap); /* get rdlength */ if (resp_type != req_type) { ap += skip; continue; } if (blw->htype == DNSBL_HTYPE_MX) ap += 2; /* skip MX preference */ skip = dn_expand(answer.buf, eom, ap, resp_dom, sizeof(resp_dom)-1); if (skip < 0) { if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL MX dn_expand(%s)=%d", blw->id, dom, skip); return 0; } ap += skip; ret = dnsbl_name_sub(blw, probe, probe_len, resp_dom, flags); if (ret) return ret; } return 0; } static u_char /* -1=no time, 0=miss, hit DNSBL # */ dnsbl_name_ns(DNSBL_WORK *blw, char *probe, int probe_len, /* put DNS blacklist probe here */ const char *dom, /* see if this domain is blacklisted */ u_char flags) /* DNSBL_FG_ENVELOPE, ... */ { const char *pdom; int level; int ret; blw->htype = DNSBL_HTYPE_NS; level = 0; pdom = dom + strlen(dom)-1; /* look for a lower level domain */ for (;;) { if (pdom == dom) { ret = dnsbl_name_rr(blw, probe, probe_len, pdom, flags); return ret; } if (*--pdom == '.' && ++level >= 2) { ret = dnsbl_name_rr(blw, probe, probe_len,pdom+1,flags); if (ret) return ret; } } while (pdom > dom); return 0; } #endif /* MXNS_DNSBL */ /* look for a domain name in the DNS blacklists, including MX & NS servers */ static int /* -1=no time, 0=miss, or hit DNSBL # */ dnsbl_name(DNSBL_WORK *blw, char *probe, int probe_len, /* put DNS blacklist probe here */ const char *dom, /* see if this domain is blacklisted */ u_char flags) /* type of lookup */ { #ifdef MXNS_DNSBL u_char is_addr; #ifndef NO_IPV6 struct dcc_in6_addr sin6_addr; #endif /* NO_IPV6 */ #endif /* MXNS_DNSBL */ int ret; #ifdef MXNS_DNSBL /* First try NS records because they are quickest * but only if at least one DNS blacklist cares * and if the address is a name and not numeric * Start with the parent domains up to the second level * and in the fastest order. */ is_addr = (INADDR_NONE != inet_addr(dom)); #ifndef NO_IPV6 if (!is_addr && 0 < inet_pton(AF_INET6, dom, &sin6_addr)) is_addr = 1; #endif if (!is_addr && (all_dnsbl_flags & DNSBL_HTYPE_NS) != 0) { ret = dnsbl_name_ns(blw, probe, probe_len, dom, flags); if (ret) return ret; } #endif /* MXNS_DNSBL */ ret = dnsbl_name_sub(blw, probe, probe_len, dom, flags); #ifdef MXNS_DNSBL if (ret) return ret; /* check MX & NS servers if allowed by at least one DNS blacklist */ blw->htype = DNSBL_HTYPE_MX; if (!is_addr && (all_dnsbl_flags & DNSBL_FG_MX)) ret = dnsbl_name_rr(blw, probe, probe_len, dom, flags); #endif /* MXNS_DNSBL */ return ret; } #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; int bl_num; memset(&blw, 0, sizeof(blw)); blw.url_start = req->hdr.start; blw.msg_us = MAX_MSG_SECS*DCC_US*2; blw.url_us = req->hdr.total_us; BUFCPY(blw.tgt_dom, req->tgt_dom); blw.id = req->hdr.id; blw.hit = DNSBL_HIT_NONE; if (!is_helper) { /* this must be the first job for this helper process */ is_helper = 1; fix_url_secs(); dnsbl_res_init(&blw); } switch (req->hit) { case DNSBL_HIT_CLIENT: bl_num = dnsbl_addr(&blw, resp->probe, ISZ(resp->probe), &req->tgt_addr, DNSBL_FG_ENVELOPE); if (0 < bl_num) { resp->hit = req->hit; resp->bl_num = bl_num; } else { resp->hit = DNSBL_HIT_NONE; } break; case DNSBL_HIT_MAIL_HOST: /* envelope mail_from */ bl_num = dnsbl_name(&blw, resp->probe, ISZ(resp->probe), req->tgt_dom, DNSBL_FG_ENVELOPE); if (0 < bl_num) { resp->hit = req->hit; resp->bl_num = bl_num; resp->htype = blw.htype; } else { resp->hit = DNSBL_HIT_NONE; } break; case DNSBL_HIT_URL: /* URL in body */ bl_num = dnsbl_name(&blw, resp->probe, ISZ(resp->probe), req->tgt_dom, DNSBL_FG_BODY); if (0 < bl_num) { resp->hit = req->hit; resp->bl_num = bl_num; resp->htype = blw.htype; } else { resp->hit = DNSBL_HIT_NONE; } break; case DNSBL_HIT_NONE: dcc_logbad(EX_SOFTWARE, "%s DNSBL helper unknown type %d", req->hdr.id, req->hit); } return 1; } /* ask a helper process to check for something in the DNS blacklists */ static void use_helper(DNSBL_WORK *blw, DNSBL_HIT hit, /* look for this */ const struct in6_addr *tgt_addr) /* or this */ { DNSBL_REQ req; DNSBL_RESP resp; memset(&req, 0, sizeof(req)); BUFCPY(req.hdr.id, blw->id); strcpy(req.tgt_dom, blw->tgt_dom); req.hit = hit; switch (hit) { case DNSBL_HIT_CLIENT: memcpy(&req.tgt_addr, tgt_addr, sizeof(req.tgt_addr)); break; case DNSBL_HIT_MAIL_HOST: BUFCPY(req.tgt_dom, blw->tgt_dom); break; case DNSBL_HIT_URL: strcpy(req.tgt_dom, blw->tgt_dom); break; case DNSBL_HIT_NONE: dcc_logbad(EX_SOFTWARE, "unknown DNSBL type %d", hit); } if (!ask_helper(blw->dcc_ctxt, blw->log_ctxt, &blw->url_start, blw->url_us, &req.hdr, sizeof(req), &resp.hdr, sizeof(resp))) { msg_secs_fin(blw, 1); return; } if (helper.debug >= 4) thr_trace_msg(blw->log_ctxt, "%s DNSBL answer %d for %s", blw->id, resp.hit, blw->tgt_dom); switch (resp.hit) { case DNSBL_HIT_NONE: break; case DNSBL_HIT_CLIENT: BUFCPY(blw->probe, resp.probe); if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "DNSBL client hit %s", blw->probe); blw->hit = resp.hit; blw->bl_num = resp.bl_num; break; case DNSBL_HIT_MAIL_HOST: BUFCPY(blw->probe, resp.probe); if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "DNSBL envelope sender%s hit %s with %s", htype2str(blw), blw->tgt_dom, blw->probe); blw->hit = resp.hit; blw->bl_num = resp.bl_num; blw->htype = resp.htype; break; case DNSBL_HIT_URL: BUFCPY(blw->probe, resp.probe); if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "DNSBL body URL%s %s hit %s", htype2str(blw), blw->tgt_dom, blw->probe); blw->hit = resp.hit; blw->bl_num = resp.bl_num; blw->htype = resp.htype; break; } msg_secs_fin(blw, 0); } #endif /* HAVE_HELPERS */ void dcc_dnsbl_url(DNSBL_WORK *blw) { int bl_num; /* nothing to do if no DNS blacklists have been configured * or if we already have a hit */ if (!blw || blw->hit != DNSBL_HIT_NONE || blw->tgt_dom_len == 0 || !(all_dnsbl_flags & DNSBL_FG_BODY)) return; /* or if the domain name is impossibly long */ if (blw->tgt_dom_len >= ISZ(blw->tgt_dom)) return; blw->tgt_dom[blw->tgt_dom_len] = '\0'; /* or if we have already spent too much time checking blacklists */ if (!msg_secs_start(blw)) return; #ifdef HAVE_HELPERS if (have_helpers) { use_helper(blw, DNSBL_HIT_URL, 0); return; } #endif bl_num = dnsbl_name(blw, blw->probe, sizeof(blw->probe), blw->tgt_dom, DNSBL_FG_BODY); if (0 < bl_num) { blw->hit = DNSBL_HIT_URL; blw->bl_num = bl_num; if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL body URL%s %s hit %s", blw->id, htype2str(blw), blw->tgt_dom, blw->probe); } msg_secs_fin(blw, 0); } void dcc_sender_dnsbl(DNSBL_WORK *blw, const struct in6_addr *addr) { int bl_num; char *p; /* nothing to do if no DNS blacklists have been configured * or if we already have a hit */ if (!blw || blw->hit != DNSBL_HIT_NONE || !(all_dnsbl_flags & DNSBL_FG_ENVELOPE)) return; p = blw->tgt_dom; strcpy(p, "sender "); p += STRZ("sender "); dcc_ipv6tostr2(p, sizeof(blw->tgt_dom)-STRZ("sender "), addr); /* or if we have already spent too much time checking blacklists */ if (!msg_secs_start(blw)) return; #ifdef HAVE_HELPERS if (have_helpers) { use_helper(blw, DNSBL_HIT_CLIENT, addr); return; } #endif bl_num = dnsbl_addr(blw, blw->probe, sizeof(blw->probe), addr, DNSBL_FG_ENVELOPE); if (0 < bl_num) { blw->hit = DNSBL_HIT_CLIENT; blw->bl_num = bl_num; if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL envelope sender%s %s hit %s", blw->id, htype2str(blw), blw->tgt_dom, blw->probe); } msg_secs_fin(blw, 0); } void dcc_mail_host_dnsbl(DNSBL_WORK *blw, const char *host) { int bl_num; /* nothing to do if no DNS blacklists have been configured * or if we already have a hit * or if we have already spent too much time checking blacklists */ if (!blw || blw->hit != DNSBL_HIT_NONE || !(all_dnsbl_flags & DNSBL_FG_ENVELOPE) || !host || *host == '\0') return; /* or if we have already spent too much time checking blacklists */ BUFCPY(blw->tgt_dom, host); if (!msg_secs_start(blw)) return; #ifdef HAVE_HELPERS if (have_helpers) { use_helper(blw, DNSBL_HIT_MAIL_HOST, 0); return; } #endif bl_num = dnsbl_name(blw, blw->probe, sizeof(blw->probe), host, DNSBL_FG_ENVELOPE); if (0 < bl_num) { blw->hit = DNSBL_HIT_MAIL_HOST; blw->bl_num = bl_num; if (helper.debug > 1) thr_trace_msg(blw->log_ctxt, "%s DNSBL client hit %s", blw->id, blw->probe); } msg_secs_fin(blw, 0); }