Logo Search packages:      
Sourcecode: dcc version File versions  Download package

dnsbl.c

/* Distributed Checksum Clearinghouse
 *
 * reject messages contain URLs that resolve to DNS blacklisted IP addresses
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * 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.2.74-1.17 $Revision$
 */

#include "ck.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

#ifdef DCC_WIN32
#undef HELPERS
#undef MX_DNSBL
#else
#define HELPERS
#endif /* DCC_WIN32 */

/* need BIND 4 compatible bits and pieces for MX checking */
#if !defined(T_MX) || !defined(C_IN) || !defined(PACKETSZ)
#undef MX_DNSBL
#endif


DNSBL *dnsbls;

static u_char dnsbl_have_ipv6, dnsbl_have_ipv4;
static int dnsbl_debug;
static u_char some_flags;

#define MAX_MSG_SECS 1000
#ifndef RES_TIMEOUT
#define RES_TIMEOUT 3
#endif
static int msg_secs = 20;           /* total seconds/mail message */
static int url_secs = 5;            /* total seconds/host name */


static u_char threaded;

static const char *dnsbl_progpath;


#ifdef HELPERS
static DCC_SOCKU rslvr_su;
static SOCKET rslvr_soc = INVALID_SOCKET;
static int rslvr_stop_write_fd = -1;
static int rslvr_stop_read_fd = -1;
#define RSLVR_PAT "rslvr=%d,%d"
static int rslvr_sn;
static u_char rslvrs_running;
static int idle_rslvrs, total_rslvrs, rslvr_failures;
static int max_rslvrs;
static int rslvr_argc = 1;
static char const **rslvr_argv;
#endif /* HELPERS */

typedef struct {
    u_int   magic2;
    u_int   sn;
    DNSBL_HIT     type;
    DNSBL_DOM   dom;                /* target for logging */
    char    id[24];
    union {
      DNSBL_DOM   nm;
      struct in6_addr addr;
    } tgt;
    u_int   magic1;
} DNSBL_RSLVR_REQ;

typedef struct {
    u_int   magic1;
    u_int   sn;
    DNSBL_HIT     type;
    u_char  mx;
    DNSBL_DOM     probe;
    u_int   magic2;
} DNSBL_RSLVR_RESP;
#define RSLVR_MAGIC1 0xbeefdead
#define RSLVR_MAGIC2 0xdeadbeef


#ifdef HELPERS
static void rslvr_work(SOCKET, int) NRATTRIB;
#endif



/* add to the argv list for the helper processes */
#ifndef HELPERS
#define save_arg(entry)
#else
static void
save_arg(const char *entry)
{
      char const **new_arg;
      int i;

      new_arg = dcc_malloc(sizeof(char *) * (rslvr_argc+2+1));
      if (rslvr_argv) {
            for (i = 0; i < rslvr_argc; ++i)
                  new_arg[i] = rslvr_argv[i];
            dcc_free(rslvr_argv);
      } else {
            new_arg[0] = dnsbl_progpath;
      }
      rslvr_argv = new_arg;

      rslvr_argv[rslvr_argc] = "-B";
      rslvr_argv[++rslvr_argc] = entry;
      rslvr_argv[++rslvr_argc] = 0;
}
# endif /* HELPERS */


/* Parse a string of the form "domain[,[IPaddr][,name|ipv4|ipv6]]"
 *    Strings starting with "set:" are special.
 *        set:debug           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:rslvr=soc,fd    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_FGS;
      const char *parm;
      DNSBL *dp;
      const char *ip;               /* "hit" IP address of this blacklist */
      DNSBL_DOM ip_buf;
      enum DNSBL_TYPE bl_type;
      int error1, error2, dom_len, ip_addr_len;
      char *p;
#ifdef HELPERS
      SOCKET soc;
      int fd;
#endif
      int i;

      dnsbl_progpath = progpath;

      if (!CSTRCMP(entry, "set:")) {
            parm = entry+STRZ("set ");
            if (!CSTRCMP(parm, "debug")) {
                  ++dnsbl_debug;
                  save_arg(entry);
                  return 1;

            } else if (!CSTRCMP(parm, "envelope")) {
                  cur_flags |= DNSBL_FG_ENVELOPE;
                  save_arg(entry);
                  return 1;

            } else if (!CSTRCMP(parm, "no-envelope")
                     || !CSTRCMP(parm, "no_envelope")) {
                  cur_flags &= ~DNSBL_FG_ENVELOPE;
                  save_arg(entry);
                  return 1;

            } else if (!CSTRCMP(parm, "body")) {
                  cur_flags |= DNSBL_FG_BODY;
                  save_arg(entry);
                  return 1;

            } else if (!CSTRCMP(parm, "no-body")
                     || !CSTRCMP(parm, "no_body")) {
                  cur_flags &= ~DNSBL_FG_BODY;
                  save_arg(entry);
                  return 1;

            } else if (!CSTRCMP(parm, "mx")) {
#ifdef MX_DNSBL
                  cur_flags |= DNSBL_FG_MX;
                  save_arg(entry);
                  return 1;
#else
                  dcc_pemsg(EX_USAGE, emsg,
                          "MX DNS blacklists not supported");
                  return 0;
#endif

            } else if (!CSTRCMP(parm, "no-mx")
                     || !CSTRCMP(parm, "no_mx")) {
                  cur_flags &= ~DNSBL_FG_MX;
                  save_arg(entry);
                  return 1;

#ifdef HELPERS
            } else if (2 == sscanf(parm, RSLVR_PAT, &soc, &fd)) {
                  rslvr_work(soc, fd);
#endif

            } else if (!CSTRCMP(parm, "msg_secs=")
                     || !CSTRCMP(parm, "msg-secs=")) {
                  parm += STRZ("msg_secs=");
                  i = strtoul(parm, &p, 0);
                  if (i < 1 || i > MAX_MSG_SECS || *p != '\0') {
                        dcc_pemsg(EX_USAGE, emsg,
                                "bad number of seconds in \"-B %s\"",
                                entry);
                        return 0;
                  }
                  if (msg_secs != i) {
                        msg_secs= i;
                        save_arg(entry);
                  }
                  return 1;


            } else if (!CSTRCMP(parm, "url-secs=")
                     || !CSTRCMP(parm, "url_secs=")
                     || !CSTRCMP(parm, "dom_secs=")) {
                  parm += STRZ("url_secs=");
                  i = strtoul(parm, &p, 0);
                  if (i < 1 || i > MAX_MSG_SECS || *p != '\0') {
                        dcc_pemsg(EX_USAGE, emsg,
                                "bad number of seconds in \"-B %s\"",
                                entry);
                        return 0;
                  }
                  url_secs = i;
                  save_arg(entry);
                  return 1;

            } else {
                  dcc_pemsg(EX_USAGE, emsg, "unrecongized  \"-B %s\"",
                          entry);
                  return 0;
            }
      }

      bl_type = DNSBL_TYPE_IPV4;
      ip = strchr(entry, ',');
      if (!ip) {
            dom_len = strlen(entry);
      } else {
            dom_len = ip - entry;
            ++ip;

            /* notice trailing ",name" or ",addr" */
            p = strchr(ip, ',');
            if (!p) {
                  dnsbl_have_ipv4 = 1;
            } else {
                  ++p;
                  if (!CSTRCMP(p, "name")) {
                        bl_type = DNSBL_TYPE_NAME;
                  } else if (!CSTRCMP(p, "IPV4")) {
                        bl_type = DNSBL_TYPE_IPV4;
                        dnsbl_have_ipv4 = 1;
                  } else if (!CSTRCMP(p, "IPV6")) {
                        bl_type = DNSBL_TYPE_IPV6;
                        dnsbl_have_ipv6 = 1;
                  } else {
                        dcc_pemsg(EX_NOHOST, emsg,
                                "unknown blacklist type in \"%s\"",
                                entry);
                        return 0;
                  }
                  STRLIMCPY(ip_buf, min(ISZ(ip_buf), p-ip), ip);
                  ip = ip_buf;
            }
      }
      if (!ip || *ip == '\0') {
            ip = "127.0.0.2";
            dnsbl_have_ipv4 = 1;
      }
      if (dom_len < 1) {
            dcc_pemsg(EX_NOHOST, emsg,
                    "invalid DNS blacklist \"%s\"", entry);
            return 0;
      }

      /* first try to interpret the target address as IPv4 */
      dcc_host_lock();
      if (dcc_get_host(ip, 0, &error1)) {
            ip_addr_len = sizeof(struct in_addr)*4;
      } else if (dcc_get_host(ip, 1, &error2)) {
            ip_addr_len = sizeof(struct in6_addr)*4;
      } else {
            dcc_host_unlock();
            dcc_pemsg(EX_NOHOST, emsg,
                    "invalid DNS blacklist IP address \"%s\": %s",
                    ip, DCC_HSTRERROR(error1));
            return 0;
      }

      if (dom_len >= ISZ(dp->dom) - ip_addr_len) {
            dcc_host_unlock();
            dcc_pemsg(EX_NOHOST, emsg,
                    "DNS blacklist name \"%s\" too long", entry);
            return 0;
      }

      dp = dcc_malloc(sizeof(*dp));
      memset(dp, 0, sizeof(*dp));
      dp->fwd = dnsbls;
      dnsbls = dp;

      dp->su = dcc_hostaddrs[0];
      dcc_host_unlock();

      dp->bl_type = bl_type;
      dp->flags = cur_flags;
      some_flags |= cur_flags;
      memcpy(dp->dom, entry, dom_len);
      dp->dom_len = dom_len;

      if (!threaded)
            threaded = dcc_dnsbl_lock_init();

      save_arg(entry);
      return 1;
}



u_char                              /* 1=logging indicated */
dcc_dnsbl_log_print(const DNSBL_WORK *bl,
                void(lp)(void *, const char*, ...), void *ctxtp)
{
      const char *mx;

      if (!bl)
            return 0;

      mx = bl->mx ? " MX" : "";

      if (bl->timeouts)
            lp(ctxtp, "insufficent time for %d DNS blacklist checks;"
               " %s first not checked\n",
               bl->timeouts,
               bl->timeout_dom);

      switch (bl->hit) {
      case DNSBL_HIT_NONE:
            return 0;
      case DNSBL_HIT_MTA:
            if (dnsbl_debug)
                  lp(ctxtp, "DNSBL check foreclosed by MTA");
            return 0;
      case DNSBL_HIT_CLIENT:
            lp(ctxtp, "SMTP client blacklist hit %s\n", bl->probe);
            return 1;
      case DNSBL_HIT_MAIL_HOST:
            lp(ctxtp, "SMTP envelope sender%s blacklist hit %s\n",
               mx, bl->probe);
            return 1;
      case DNSBL_HIT_URL:
            lp(ctxtp, "body URL%s %s blacklist hit %s\n",
               mx, bl->dom, bl->probe);
            return 1;
      }

      return 0;
}



/* 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 *bl)
{
      time_t now;

      if (bl->msg_secs < 0)
            return 0;

      if (bl->msg_secs == 0) {
            bl->msg_secs = -1;
            if (bl->timeout_dom[0] == '\0')
                  strcpy(bl->timeout_dom, bl->dom);
            if (dnsbl_debug > 0)
                  dcc_trace_msg("%s DNSBL"
                              " exhausted %d msg_secs before %s",
                              bl->id, msg_secs, bl->dom);
            return 0;
      }

      now = time(0);
      bl->start = now;
      bl->stop = now + url_secs;
      return 1;
}



/* see if we have run out of time */
static u_char                       /* 0=timeout, 1=keep looking */
msg_secs_ck(DNSBL_WORK *bl)
{
      time_t now;

      if (bl->msg_secs < 0)
            return 0;

      now = time(0);
      if (now <= bl->stop)
            return 1;

      /* we are out of time for at least the current domain */
      if (bl->timeout_dom[0] == '\0')
            strcpy(bl->timeout_dom, bl->dom);
      ++bl->timeouts;
      if (bl->msg_secs <= now - bl->start) {
            bl->msg_secs = -1;
            if (dnsbl_debug > 0)
                  dcc_trace_msg("%s DNSBL exhausted %d msg_secs for %s",
                              bl->id, msg_secs, bl->dom);
      } else {
            if (dnsbl_debug > 0)
                  dcc_trace_msg("%s DNSBL exhausted %d url_secs for %s",
                              bl->id, url_secs, bl->dom);
      }
      return 0;
}



/* account for time used */
static void
msg_secs_fin(DNSBL_WORK *bl)
{
      time_t used;

      if (bl->hit != DNSBL_HIT_NONE)
            return;

      if (bl->msg_secs > 0) {
            used = time(0) - bl->start;
            if (used > 0)           /* handle clock jumps */
                  bl->msg_secs -= used;
            if (bl->msg_secs < 0)
                  bl->msg_secs = 0;
      }
}



static void
dnsbl_res_init(void)
{
#ifdef HAVE__RES
      int res_retrans, res_retry;
      int retry_retrans, ratio;
#endif

      if (url_secs > msg_secs)
            url_secs =  msg_secs;

#ifdef HAVE__RES
      /* 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;

      /* Arrange to have the resolver library take a little longer than
       * our timeout so that can recognize a resolver timeout.
       * Some versions of some wrappers such as getipnodebyname()
       * answer with HOST_NOT_FOUND when they mean TRY_AGAIN. */
      retry_retrans = res_retry * res_retrans;
      if (retry_retrans > url_secs+2) {
            ratio = retry_retrans/(url_secs+1);
            res_retry /= ratio;
            if (res_retry < 1)
                  res_retry = 1;
            res_retrans = (url_secs+2 + res_retry-1)/res_retry;
            if (res_retrans < RES_TIMEOUT)
                  res_retrans = RES_TIMEOUT;
            retry_retrans = res_retry * res_retrans;
      }

      _res.retry = res_retry;
      _res.retrans = res_retrans;
#endif /* !HAVE__RES */
}



/* get ready to handle a mail message */
void
dcc_dnsbl_init(DCC_GOT_CKS *cks, DCC_CLNT_CTXT *ctxt, const char *id,
             int max_work UATTRIB)
{
      DNSBL_WORK *bl;

      if (!dnsbls)
            return;

      if ((bl = cks->dnsbl) == 0) {
#ifdef HELPERS
            max_rslvrs = max_work;
#endif
            bl = dcc_malloc(sizeof(*cks->dnsbl));
            memset(bl, 0, sizeof(*cks->dnsbl));
            cks->dnsbl = bl;
            if (!threaded)
                  dnsbl_res_init();
      }

      bl->hit = DNSBL_HIT_NONE;
      bl->msg_secs = msg_secs;
      bl->dom[bl->dom_len = 0] = '\0';
      bl->timeouts = 0;
      bl->timeout_dom[0] = '\0';
      bl->probe[0] = '\0';
      bl->id = id ? id : "";

       bl->ctxt = 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=out of time, 0=miss, 1=hit */
lookup(DNSBL_WORK *bl,
       const char *probe,           /* check this name in blacklisting */
       const DNSBL *dp,
       u_char mx)
{
      char str[INET6_ADDRSTRLEN+1+6+1];
      int error;

      if (!msg_secs_ck(bl))
            return -1;

      dcc_host_lock();
      if (!dcc_get_host(probe, dp->su.sa.sa_family == AF_INET ? 0 : 1,
                    &error)) {
            if (!msg_secs_ck(bl))
                  return -1;
            if (dnsbl_debug > 1)
                  dcc_trace_msg("%s DNSBL%s %s gethostbyname(%s): %s",
                              bl->id, mx ? " MX" : "",
                              bl->dom, probe, DCC_HSTRERROR(error));
            dcc_host_unlock();
            return 0;
      }

      if (DCC_SU_SA_EQ(&dcc_hostaddrs[0], &dp->su)) {
            dcc_host_unlock();
            if (dnsbl_debug > 1)
                  dcc_trace_msg("%s DNSBL%s hit %s gethostbyname(%s)=%s",
                              bl->id, mx ? " MX" : "", bl->dom, probe,
                              dcc_su2str_opt(&dp->su, 0, 0));
            return 1;
      }

      if (dnsbl_debug > 1) {
            if (dcc_hostaddrs[0].sa.sa_family == AF_INET) {
                  if (!DCC_INET_NTOP(AF_INET,
                                 &dcc_hostaddrs[0].ipv4,
                                 str, sizeof(str)))
                        strcpy(str, "???");
            } else {
                  if (!DCC_INET_NTOP(AF_INET6,
                                 &dcc_hostaddrs[0].ipv6,
                                 str, sizeof(str)))
                        strcpy(str, "???");
            }
            dcc_trace_msg("%s DNSBL%s miss %s gethostbyname(%s)=%s",
                        bl->id, mx ? " MX" : "", bl->dom, probe, str);
      }
      dcc_host_unlock();
      return 0;
}



/* check one IPv4 address against the DNS blacklists */
static int                    /* -1=out of time, 0=miss, 1=hit */
dnsbl_ipv4(DNSBL_WORK *bl,
         char *probe, int probe_len,
         const u_char *bp,
         u_char flags,
         u_char mx)
{
      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->dom);
            ret = lookup(bl, probe, dp, mx);
            if (ret)
                  return ret;
      }
      return 0;
}



/* check one IPv6 address against the DNS blacklists */
static int                    /* -1=out of time, 0=miss, 1=hit */
dnsbl_ipv6(DNSBL_WORK *bl,
         char *probe, int probe_len,
         const u_char *bp,
         u_char flags,
         u_char mx)
{
      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->dom);
            ret = lookup(bl, probe, dp, mx);
            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.
 *    These DNS operations need RES_DEFNAMES and RES_DNSRCH off and
 *    RES_NOALIASES on when the name is an MX host.
 */
static u_char                       /* 0=failed */
dnsbl_get_host(const char *dom, u_char use_ipv6, int *errorp, u_char no_search)
{
      u_long save_options;
      u_char result;

      dcc_host_lock();
#ifdef HAVE__RES
      save_options = _res.options;
      if (no_search) {
            _res.options &= ~(RES_DEFNAMES | RES_DNSRCH);
            _res.options |= RES_NOALIASES;
      }
#endif
      result = dcc_get_host(dom, use_ipv6, errorp);
#ifdef HAVE__RES
      if (no_search)
            _res.options = save_options;
#endif
      return result;
}



/* look for a domain name in the DNS blacklists */
static int                    /* -1=out of time, 0=miss, 1=hit */
dnsbl_name_sub(DNSBL_WORK *bl,
             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 */
             u_char mx)       /* 1=MX server */
{
      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 (!(some_flags & flags))
            return -1;

      /* 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->dom_len;
            if (i > 0) {
                  i = strlen(dom) - i;
                  if (i < 0)
                        i = 0;
                  snprintf(probe, probe_len, "%s.%s",
                         dom+i, dp->dom);
                  ret = lookup(bl, probe, dp, mx);
                  if (ret)
                        return ret;
                  if (!msg_secs_ck(bl))
                        return -1;
            }
      }

      /* try IPv4 second */
      if (dnsbl_have_ipv4) {
            if (!msg_secs_ck(bl))
                  return -1;
            if (!dnsbl_get_host(dom, 0, &error, mx)) {
                  dcc_host_unlock();
                  if (!msg_secs_ck(bl))
                        return -1;
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("%s DNSBL%s"
                                    " gethostbyname(%s): %s",
                                    bl->id, mx ? " MX" : "",
                                    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(bl, probe, probe_len,
                                     (u_char *)&ipv4[--i],
                                     flags, mx);
                        if (ret)
                              return ret;
                  } while (i > 0);
            }
      }

      /* try IPv6 if we have any */
      if (dnsbl_have_ipv6) {
            if (!msg_secs_ck(bl))
                  return -1;
            if (!dnsbl_get_host(dom, 1, &error, mx)) {
                  if (!msg_secs_ck(bl))
                        return -1;
                  dcc_host_unlock();
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("%s DNSBL%s"
                                    " gethostbyname(%s): %s",
                                    bl->id, mx ? " MX" : "",
                                    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(bl, probe, probe_len,
                                     (u_char *)&ipv6[--i],
                                     flags, mx);
                        if (ret)
                              return ret;
                  } while (i > 0);
            }
      }

      return 0;
}



static int                    /* -1=timeout, 0=miss, 1=hit */
dnsbl_addr(DNSBL_WORK *bl,
         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(bl, probe, probe_len, (u_char *)&ipv4,
                         flags, 0);
            if (ret)
                  return ret;
      }

      if (dnsbl_have_ipv6)
            return dnsbl_ipv6(bl, probe, probe_len, (u_char *)addr,
                          flags, 0);
      return 0;
}



/* look for a domain name in the DNS blacklists, including its MX servers */
#ifndef MX_DNSBL
#define dnsbl_name(bl,p,pl,dom,flags) dnsbl_name_sub(bl,p,pl,dom,flags,0)
#else /* MX_DNSBL */
static int                    /* -1=out of time, 0=miss, 1=hit */
dnsbl_name(DNSBL_WORK *bl,
             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 */
{
      union {
          u_char  buf[PACKETSZ+20];
          HEADER  hdr;
      } answer;
      DNSBL_DOM mx;
      u_char *ap, *eom;
      int cnt, ret, skip, type;

      ret = dnsbl_name_sub(bl, probe, probe_len, dom, flags, 0);
      if (ret)
            return ret;

      /* check MX servers if supported by at least one DNS blacklist */
      if (!(some_flags & DNSBL_FG_MX))
            return 0;

      ret = res_query(dom, C_IN, T_MX, answer.buf, sizeof(answer.buf));
      if (ret < 0) {
            if (dnsbl_debug > 1)
                  dcc_trace_msg("DNSBL MX res_query(%s): %s",
                              dom, hstrerror(h_errno));
            return 0;
      }

      ap = &answer.buf[HFIXEDSZ];
      if (ret > ISZ(answer.buf))
            ret = ISZ(answer.buf);
      eom = &answer.buf[ret];

      /* skip the question */
      cnt = ntohs(answer.hdr.qdcount);
      while (--cnt >= 0) {
            skip = dn_skipname(ap, eom);
            if (skip < 0) {
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("DNSBL MX dn_skipname(%s)=%d",
                                    dom, skip);
                  return 0;
            }
            ap += skip+QFIXEDSZ;
      }

      cnt = ntohs(answer.hdr.ancount);
      while (--cnt >= 0 && ap < eom) {
            skip = dn_expand(answer.buf, eom, ap, mx, sizeof(mx));
            if (skip < 0) {
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("DNSBL MX dn_expand(%s)=%d",
                                    dom, skip);
                  return 0;
            }

            ap += skip;
            GETSHORT(type, ap);
            ap += 2+4;        /* skip class and TTL */
            GETSHORT(skip, ap);     /* get rdlength */
            if (type != T_MX) {
                  ap += skip;
                  continue;
            }
            ap += 2;          /* skip preference */
            skip = dn_expand(answer.buf, eom, ap, mx, sizeof(mx)-1);
            if (skip < 0) {
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("DNSBL MX dn_expand(%s)=%d",
                                    dom, skip);
                  return 0;
            }
            ap += skip;

            ret = dnsbl_name_sub(bl, probe, probe_len, mx, flags, 1);
            if (ret) {
                  if (ret > 0)
                        bl->mx = 1;
                  return ret;
            }
      }

      return 0;
}
#endif /* MX_DNSBL */



#ifdef HELPERS
/* resolver helper processes to deal with typical single threaded
 *    resolver libraries */
static void
stop_rslvrs(u_char locked)
{
      if (!locked)
            dcc_dnsbl_lock();

      if (rslvr_stop_write_fd >= 0) {
            close(rslvr_stop_write_fd);
            rslvr_stop_write_fd = -1;
      }
      if (rslvr_stop_read_fd >= 0) {
            close(rslvr_stop_read_fd);
            rslvr_stop_read_fd = -1;
      }
      if (rslvr_soc != INVALID_SOCKET) {
            closesocket(rslvr_soc);
            rslvr_soc = INVALID_SOCKET;
      }
      if (rslvrs_running) {
            int status;

            while (0 <= waitpid(0, &status, WNOHANG))
                  continue;
            rslvrs_running = 0;
            idle_rslvrs = 0;
      }

      rslvr_failures = 0;

      if (!locked)
            dcc_dnsbl_unlock();
}



/* must be called with the mutex */
static u_char
start_rslvrs(const DNSBL_WORK *bl)
{
      int fds[2];
      struct in6_addr ipv6_loopback;
      struct in_addr ipv4_loopback;
      int soc_len;

      stop_rslvrs(1);

      /* create a new socket with the same choice of IPv4 or IPv6
       * as the context's socket */
      if (bl->ctxt->soc == INVALID_SOCKET
          && dcc_clnt_soc_open(0, bl->ctxt))
            return 0;
      if (bl->ctxt->use_ipv6) {
            memset(&ipv6_loopback, 0, sizeof(ipv6_loopback));
            ipv6_loopback.s6_addr32[3] = ntohl(1);
            dcc_mk_su(&rslvr_su, AF_INET6, &ipv6_loopback, 0);
      } else {
            ipv4_loopback.s_addr = ntohl(0x7f000001);
            dcc_mk_su(&rslvr_su, AF_INET, &ipv4_loopback, 0);
      }
      if (!dcc_udp_bind(0, &rslvr_soc, &rslvr_su, 0)) {
            dcc_error_msg("DNSBL bind(): %s", ERROR_STR());
            return 0;
      }
      soc_len = sizeof(rslvr_su);
      if (0 > getsockname(rslvr_soc, &rslvr_su.sa, &soc_len)) {
            dcc_error_msg("DNSBL getsockname(): %s", ERROR_STR());
            stop_rslvrs(1);
            return 0;
      }

      /* give the resolver child processes an FD that will go dead
       * if the parent dies or otherwise closes the other end */
      if (0 > pipe(fds)) {
            dcc_error_msg("DNSBL pipe(): %s", ERROR_STR());
            stop_rslvrs(1);
            return 0;
      }
      rslvr_stop_write_fd = fds[1];
      rslvr_stop_read_fd = fds[0];

      if (-1 == fcntl(rslvr_stop_write_fd, F_SETFL,
                  fcntl(rslvr_stop_write_fd, F_GETFL, 0) | O_NONBLOCK)) {
            dcc_error_msg("DNSBL rslvr fcntl(O_NONBLOCK): %s", ERROR_STR());
            stop_rslvrs(1);
            return 0;
      }
      if (0 > fcntl(rslvr_stop_write_fd, F_SETFD, FD_CLOEXEC)) {
            dcc_error_msg("DNSBL rslvr fcntl(FD_CLOEXEC): %s", ERROR_STR());
            stop_rslvrs(1);
            return 0;
      }

      rslvrs_running = 1;
      return 1;
}



/* Start a new resolver helper process.
 *    The mutex must be locked.  It is unlocked on failure */
static u_char
new_rslvr(const DNSBL_WORK *bl)
{
      pid_t pid;
      char buf[sizeof("set:")+sizeof(RSLVR_PAT)+8+8];

      if (!rslvrs_running
          && !start_rslvrs(bl)) {
            dcc_dnsbl_unlock();
            return 0;
      }

      pid = fork();
      if (pid < 0) {
            dcc_error_msg("DNSBL fork(): %s", ERROR_STR());
            stop_rslvrs(1);
            dcc_dnsbl_unlock();
            return 0;
      }

      if (pid != 0) {
            /* this is the parent */
            ++total_rslvrs;
            return 1;
      }

      /* This is the child
       * exec() to get rid of the other threads */
      rslvr_soc = dup(rslvr_soc);   /* turn off FD_CLOEXEC */
      snprintf(buf, sizeof(buf), "set:"RSLVR_PAT,
             rslvr_soc, rslvr_stop_read_fd);
      save_arg(buf);
      execv(dnsbl_progpath, (char * const *)rslvr_argv);
      /* This process should continue eventually at rslvr_work() */
      dcc_logbad(EX_UNAVAILABLE, "exec(%s -B %s): %s",
               dnsbl_progpath, buf, ERROR_STR());
}



/* the resolver helper processes run this forever */
static void
rslvr_work(SOCKET soc, int fd)
{
      fd_set rfds;
      DCC_SOCKLEN_T su_len;
      DCC_SOCKU su;
      DNSBL_RSLVR_REQ req;
      DNSBL_RSLVR_RESP resp;
      DNSBL_WORK bl;
      int soc_len;
      int i;

      /* Ensure FDs are small enough for select() */
      if (soc >= (int)FD_SETSIZE) {
            rslvr_soc = dup(soc);
            close(soc);
      } else {
            rslvr_soc = soc;
      }
      if (fd >= (int)FD_SETSIZE) {
            rslvr_stop_read_fd = dup(fd);
            close(fd);
      } else {
            rslvr_stop_read_fd = fd;
      }

      soc_len = sizeof(rslvr_su);
      if (0 > getsockname(rslvr_soc, &rslvr_su.sa, &soc_len))
            dcc_logbad(EX_IOERR, "DNSBL getsockname(): %s", ERROR_STR());

      if (dnsbl_debug > 1)
            dcc_trace_msg("DNSBL resolver starting on %s",
                        dcc_su2str(&rslvr_su));

      dnsbl_res_init();
      url_secs = MAX_MSG_SECS;

      FD_ZERO(&rfds);
      for (;;) {
            FD_SET(rslvr_soc, &rfds);
            FD_SET(rslvr_stop_read_fd, &rfds);
            i = select(max(rslvr_soc,rslvr_stop_read_fd)+1, &rfds, 0, 0, 0);
            if (i < 0) {
                  if (DCC_SELECT_NERROR())
                        continue;
                  dcc_logbad(EX_OSERR, "DNSBL wokr select(): %s",
                           ERROR_STR());
            }

            if (FD_ISSET(rslvr_stop_read_fd, &rfds)) {
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("DNSBL resolver on %s stopping",
                                    dcc_su2str(&rslvr_su));
                  exit(0);
            }

            if (!FD_ISSET(rslvr_soc, &rfds))
                  continue;

            /* process a request */
            su_len = sizeof(su);
            i = recvfrom(rslvr_soc, &req, ISZ(req), 0, &su.sa, &su_len);
            if (i < 0) {
                  if (DCC_BLOCK_ERROR())
                        continue;
                  dcc_logbad(EX_IOERR, "DNSBL work recvfrom(): %s",
                           ERROR_STR());
            }

            /* we might get stray packets because we are usng UDP */
            if (!DCC_SU_SA_EQ(&rslvr_su, &su)) {
                  if (dnsbl_debug != 0)
                        dcc_trace_msg("DNSBL from %s instead of %s",
                                    dcc_su2str(&su),
                                    dcc_su2str(&rslvr_su));
                  continue;
            }
            if (i != ISZ(req)) {
                  if (dnsbl_debug != 0)
                        dcc_trace_msg("DNSBL work recvfrom()=%d", i);
                  continue;
            }
            if (req.magic1 != RSLVR_MAGIC1
                || req.magic2 != RSLVR_MAGIC2) {
                  if (dnsbl_debug != 0)
                        dcc_trace_msg("DNSBL work recvfrom()"
                                    " magic1=%#08x magic2=%#08x",
                                    req.magic1, req.magic2);
                  continue;
            }

            resp.magic1 = RSLVR_MAGIC1;
            resp.magic2 = RSLVR_MAGIC2;
            resp.sn = req.sn;

            bl.msg_secs = MAX_MSG_SECS;
            strcpy(bl.dom, req.dom);
            bl.id = req.id;
            bl.hit = DNSBL_HIT_NONE;
            bl.mx = 0;
            bl.start = time(0);
            bl.stop = bl.start+MAX_MSG_SECS;

            switch (req.type) {
            case DNSBL_HIT_CLIENT:
                  if (0 < dnsbl_addr(&bl, resp.probe, ISZ(resp.probe),
                                 &req.tgt.addr,
                                 DNSBL_FG_ENVELOPE))
                        resp.type = req.type;
                  else
                        resp.type = DNSBL_HIT_NONE;
                  break;
            case DNSBL_HIT_MAIL_HOST:   /* envelope mail_from */
                  if (0 < dnsbl_name(&bl, resp.probe, ISZ(resp.probe),
                                 req.tgt.nm, DNSBL_FG_ENVELOPE))
                        resp.type = req.type;
                  else
                        resp.type = DNSBL_HIT_NONE;
                  break;
            case DNSBL_HIT_URL:     /* URL in body */
                  if (0 < dnsbl_name(&bl, resp.probe, ISZ(resp.probe),
                                 req.tgt.nm, DNSBL_FG_BODY))
                        resp.type = req.type;
                  else
                        resp.type = DNSBL_HIT_NONE;
                  break;
            default:
                  dcc_logbad(EX_SOFTWARE,"%s DNSBL work unknown type %d",
                           bl.id, req.type);
                  continue;
            }

            i = sendto(rslvr_soc, &resp, ISZ(resp), 0,
                     &su.sa, DCC_SU_LEN(&su));
            if (i != ISZ(resp)) {
                  if (i < 0)
                        dcc_error_msg("%s DNSBL work sendto(): %s",
                                    bl.id, ERROR_STR());
                  else
                        dcc_error_msg("%s DNSBL work sendto()=%d",
                                    bl.id, i);
                  continue;
            }
      }
}



/* ask a helper process to do some DNS work */
static u_char                       /* seem to have sick helper process */
ask_rslvr(DCC_SOCKU *send_su, DNSBL_WORK *bl,
        DNSBL_RSLVR_REQ *req, const void *data)
{
      DCC_SOCKLEN_T su_len;
      DCC_SOCKU recv_su;
      DNSBL_RSLVR_RESP resp;
      int i;

      /* skip it if we have spent too much time checking blacklists */
      if (!msg_secs_ck(bl))
            return 1;

#ifdef DCC_UDP_CONNECT
      dcc_clnt_disconnect(0, bl->ctxt);
#endif
      if (bl->ctxt->soc == INVALID_SOCKET
          && dcc_clnt_soc_open(0, bl->ctxt))
            return 0;
      i = sendto(bl->ctxt->soc, req, ISZ(*req),
               0, &send_su->sa, DCC_SU_LEN(send_su));
      if (i != ISZ(*req)) {
            if (dnsbl_debug > 1) {
                  if (i < 0)
                        dcc_trace_msg("%s DNSBL ask sendto(): %s",
                                    bl->id, ERROR_STR());
                  else
                        dcc_trace_msg("%s DNSBL ask sendto()=%d",
                                    bl->id, i);
                  return 0;
            }
      }

      for (;;) {
            i = bl->stop - time(0);
            if (i < 0)
                  i = 0;
            i = dcc_select_poll(0, bl->ctxt->soc, 1,
                            i*DCC_USECS + DCC_USECS/4);
            if (i < 0) {
                  dcc_error_msg("%s DNSBL select_poll: %s",
                              bl->id, ERROR_STR());
                  return 0;
            }
            if (i == 0) {
                  /* give up if resolver did not answer */
                  if (dnsbl_debug > 2)
                        dcc_trace_msg("%s DNSBL no resolver answer",
                                    bl->id);
                  bl->stop = 0;
                  msg_secs_ck(bl);
                  return 0;
            }

            su_len = sizeof(recv_su);
            i = recvfrom(bl->ctxt->soc, &resp, ISZ(resp),
                       0, &recv_su.sa, &su_len);
            /* because we are usng UDP, we might get stray packets */
            if (i != ISZ(resp)) {
                  if (i < 0) {
                        dcc_trace_msg("%s DNSBL ask recvfrom(): %s",
                                    bl->id, ERROR_STR());
                        if (DCC_BLOCK_ERROR())
                              continue;
                        msg_secs_ck(bl);
                        return 0;
                  }
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("%s DNSBL work recvfrom()=%d",
                                    bl->id, i);
                  continue;
            }
            if (!DCC_SU_SA_EQ(send_su, &recv_su)) {
                  if (dnsbl_debug != 0)
                        dcc_trace_msg("DNSBL work recvfrom(%s)"
                                    " instead of %s",
                                    dcc_su2str(&recv_su),
                                    dcc_su2str(send_su));
                  continue;
            }
            if (resp.magic1 != RSLVR_MAGIC1
                || resp.magic2 != RSLVR_MAGIC2
                || resp.sn != req->sn) {
                  if (dnsbl_debug >1 )
                        dcc_trace_msg("DNSBL work recvfrom(%s)"
                                    " magic1=%#08x magic2=%#08x"
                                    " sn=%d",
                                    dcc_su2str(&recv_su),
                                    resp.magic1, resp.magic2,
                                    resp.sn);
                  continue;
            }

            switch (resp.type) {
            case DNSBL_HIT_NONE:
                  break;
            case DNSBL_HIT_MTA:
                  dcc_logbad(EX_SOFTWARE, "unknown DNSBL hit");
                  break;
            case DNSBL_HIT_CLIENT:
                  STRLIMCPY(bl->probe, sizeof(bl->probe), resp.probe);
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("%s DNSBL"
                                    " client blacklist hit %s\n",
                                    bl->id, bl->probe);
                  bl->hit = resp.type;
                  break;
            case DNSBL_HIT_MAIL_HOST:
                  STRLIMCPY(bl->dom, sizeof(bl->dom), data);
                  STRLIMCPY(bl->probe, sizeof(bl->probe), resp.probe);
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("%s DNSBL envelope sender"
                                    " blacklist hit %s with %s\n",
                                    bl->id, bl->dom, bl->probe);
                  bl->hit = resp.type;
                  break;
            case DNSBL_HIT_URL:
                  STRLIMCPY(bl->probe, sizeof(bl->probe), resp.probe);
                  if (dnsbl_debug > 1)
                        dcc_trace_msg("%s DNSBL body URL"
                                    " %s blacklist hit %s\n",
                                    bl->id, bl->dom, bl->probe);
                  bl->hit = resp.type;
                  break;
            }
            return 1;
      }
}



/* find a resolver process to check for something in the DNS blacklists */
static void
use_rslvr(DNSBL_WORK *bl,
        DNSBL_HIT type,       /* look for this */
        const void *data)
{
      DNSBL_RSLVR_REQ req;
      DCC_SOCKU send_su;
      u_char result;
      int i;

      req.magic1 = RSLVR_MAGIC1;
      req.magic2 = RSLVR_MAGIC2;
      dcc_dnsbl_lock();
      req.sn = ++rslvr_sn;
      if (idle_rslvrs > 0) {
            --idle_rslvrs;
            send_su = rslvr_su;
            dcc_dnsbl_unlock();
      } else if (total_rslvrs >= max_rslvrs) {
            send_su = rslvr_su;
            dcc_dnsbl_unlock();
            if (dnsbl_debug > 0)
                  dcc_trace_msg("%s DNSBL no idle resolvers", bl->id);
      } else {
            if (!new_rslvr(bl))
                  return;
            send_su = rslvr_su;
            dcc_dnsbl_unlock();
      }

      strcpy(req.dom, bl->dom);
      STRLIMCPY(req.id, sizeof(req.id), bl->id);
      req.type = type;
      switch (type) {
      case DNSBL_HIT_CLIENT:
            memcpy(&req.tgt.addr, data, sizeof(req.tgt.addr));
            break;
      case DNSBL_HIT_MAIL_HOST:
            i = strlen(data);
            if (i > ISZ(req.tgt.nm)-1)
                  i = ISZ(req.tgt.nm)-1;
            memcpy(req.tgt.nm, data, i);
            req.tgt.nm[i] = '\0';
            break;
      case DNSBL_HIT_URL:
            strcpy(req.tgt.nm, bl->dom);
            break;
      default:
            dcc_logbad(EX_SOFTWARE, "unknown DNSBL type %d", type);
      }

      result = ask_rslvr(&send_su, bl, &req, data);

      dcc_dnsbl_lock();
      ++idle_rslvrs;
      if (result) {
            rslvr_failures = 0;
      } else if (++rslvr_failures > 5) {
            if (dnsbl_debug)
                  dcc_trace_msg("%s DNSBL restarting resolvers", bl->id);
            stop_rslvrs(1);
      }
      dcc_dnsbl_unlock();

      msg_secs_fin(bl);
}
#endif /* HELPERS */



void
dcc_dnsbl_url(DNSBL_WORK *bl)
{
      /* nothing to do if no DNS blacklists have been configured
       * or if we already have a hit */
      if (!bl || bl->hit != DNSBL_HIT_NONE
          || bl->dom_len == 0
          || !(some_flags & DNSBL_FG_BODY))
            return;

      bl->dom[bl->dom_len] = '\0';

      /* or if we have already spent too much time checking blacklists */
      if (!msg_secs_start(bl))
            return;

#ifdef HELPERS
      if (threaded) {
            use_rslvr(bl, DNSBL_HIT_URL, 0);
            return;
      }
#endif

      if (0 < dnsbl_name(bl, bl->probe, sizeof(bl->probe), bl->dom,
                     DNSBL_FG_BODY)) {
            bl->hit = DNSBL_HIT_URL;
            if (dnsbl_debug > 1)
                  dcc_trace_msg("%s DNSBL"
                              " body URL %s blacklist hit %s\n",
                              bl->id, bl->dom, bl->probe);
      }
      msg_secs_fin(bl);
}



void
dcc_sender_dnsbl(DNSBL_WORK *bl, const struct in6_addr *addr)
{
      char *p;

      /* nothing to do if no DNS blacklists have been configured
       * or if we already have a hit */
      if (!bl || bl->hit != DNSBL_HIT_NONE
          || !(some_flags & DNSBL_FG_ENVELOPE))
            return;

      p = bl->dom;
      strcpy(p, "sender ");
      p += STRZ("sender ");
      if (!DCC_INET_NTOP(AF_INET6, addr, p, sizeof(bl->dom)-STRZ("sender ")))
            strcpy(p, "???");

      /* or if we have already spent too much time checking blacklists */
      if (!msg_secs_start(bl))
            return;

#ifdef HELPERS
      if (threaded) {
            use_rslvr(bl, DNSBL_HIT_CLIENT, addr);
            return;
      }
#endif

      if (0 < dnsbl_addr(bl, bl->probe, sizeof(bl->probe), addr,
                     DNSBL_FG_ENVELOPE)) {
            bl->hit = DNSBL_HIT_CLIENT;
            if (dnsbl_debug > 1)
                  dcc_trace_msg("%s DNSBL envelope"
                              " sender %s blacklist hit %s\n",
                              bl->id, bl->dom, bl->probe);
      }
      msg_secs_fin(bl);
}



void
dcc_mail_host_dnsbl(DNSBL_WORK *bl, const char *host)
{
      /* 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 (!bl || bl->hit != DNSBL_HIT_NONE
          || !(some_flags & DNSBL_FG_ENVELOPE)
          || !host || *host == '\0')
            return;

      /* or if we have already spent too much time checking blacklists */
      STRLIMCPY(bl->dom, sizeof(bl->dom), host);
      if (!msg_secs_start(bl))
            return;

#ifdef HELPERS
      if (threaded) {
            use_rslvr(bl, DNSBL_HIT_MAIL_HOST, host);
            return;
      }
#endif

      if (0 < dnsbl_name(bl, bl->probe, sizeof(bl->probe), host,
                     DNSBL_FG_ENVELOPE)) {
            bl->hit = DNSBL_HIT_MAIL_HOST;
            if (dnsbl_debug > 1)
                  dcc_trace_msg("%s DNSBL"
                              " client blacklist hit %s\n",
                              bl->id, bl->probe);
      }
      msg_secs_fin(bl);
}

Generated by  Doxygen 1.6.0   Back to index