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

ckwhite.c

/* Distributed Checksum Clearinghouse
 *
 * check checksums in the local whitelist
 *
 * 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.78 $Revision$
 */

#include "dcc_ck.h"


#define WHITE_MAGIC_B_STR "DCC client whitelist hash table version "
#define WHITE_MAGIC_V_STR "12"
static const DCC_WHITE_MAGIC white_magic = WHITE_MAGIC_B_STR WHITE_MAGIC_V_STR;

#define EMPTY_WHITE_SIZE    (sizeof(DCC_WHITE_TBL) - sizeof(DCC_WHITE_ENTRY))
#define MAX_WHITE_ENTRIES   (DCC_WHITE_TBL_BINS*10)
#define ENTRIES2SIZE(_l)    (sizeof(DCC_WHITE_TBL)                \
                       - sizeof(DCC_WHITE_ENTRY)                  \
                       + ((_l) * sizeof(DCC_WHITE_ENTRY)))

#define BROKEN_CHECK_DELAY (5*60)

#define WHITE_STAT_DELAY 5          /* don't stat() more often */


static void
unmap_white_ht(DCC_WF *wf)
{
      if (!wf->info)
            return;

#ifdef DCC_WIN32
      win32_unmap(&wf->ht_map, wf->info, wf->ht_nm);
#else
      if (0 > munmap((void *)wf->info, wf->info_size))
            dcc_error_msg("munmap(%s,%d): %s",
                        wf->ht_nm, wf->info_size, ERROR_STR());
#endif
      wf->info = 0;
}



static u_char
close_white_ht(DCC_EMSG emsg, DCC_WF *wf)
{
      u_char result = 1;

      if (wf->ht_fd < 0)
            return result;

      wf->ht_broken = time(0)+BROKEN_CHECK_DELAY;

      unmap_white_ht(wf);
#ifdef DCC_WIN32
      /* unlock the file before closing it to keep Win95 happy */
      if (wf->ht_locked
          && !dcc_unlock_fd(emsg, wf->ht_fd, DCC_LOCK_ALL_FILE,
                        "whitelist ", wf->ht_nm))
            result = 0;
#endif
      if (0 > close(wf->ht_fd)) {
            dcc_pemsg(EX_IOERR, emsg, "close(%s): %s",
                    wf->ht_nm, ERROR_STR());
            result = 0;
      }
      wf->ht_fd = -1;
      return result;
}



/* open the hash table file
 *    The hash table mutex must be held.
 *    It is always held on exit.*/
static u_char                       /* 0=failed, 1=ok */
open_white_ht(DCC_EMSG emsg, DCC_WF *wf)
{
      close_white_ht(0, wf);

      wf->flags &= ~DCC_WF_HT_CHECKED;

#ifndef DCC_WIN32
      /* We want to create a private hash table if the ASCII file
       * is private, but a hash table owned by the DCC user if the
       * ASCII file is public */
      if (0 > access(wf->ascii_nm, R_OK | W_OK)
          && dcc_get_priv_home(wf->ht_nm)) {
            wf->ht_fd = open(wf->ht_nm,
                         (wf->flags & DCC_WF_RO)
                         ? O_RDWR
                         : (O_RDWR | O_CREAT),
                         0666);
            if (wf->ht_fd < 0 && errno == EACCES) {
                  unlink(wf->ht_nm);
                  wf->ht_fd = open(wf->ht_nm, O_RDWR | O_CREAT, 0666);
            }
            dcc_rel_priv();
      }
#endif
      if (wf->ht_fd < 0) {
            /* try to open or create a private hash table */
            wf->ht_fd = open(wf->ht_nm,
                         (wf->flags & DCC_WF_RO)
                         ? O_RDWR
                         : (O_RDWR | O_CREAT),
                         0666);
            if (wf->ht_fd < 0 && errno == EACCES) {
                  /* try to recreate it if necessary */
                  unlink(wf->ht_nm);
                  wf->ht_fd = open(wf->ht_nm,
                               (wf->flags & DCC_WF_RO)
                               ? O_RDWR
                               : (O_RDWR | O_CREAT),
                               0666);
            }
      }
#ifndef DCC_WIN32
      /* try one last time with privileges in case the ASCII file has
       * mode 666 but the directory does not */
      if (wf->ht_fd < 0 && errno == EACCES
          && dcc_get_priv_home(wf->ht_nm)) {
            unlink(wf->ht_nm);
            wf->ht_fd = open(wf->ht_nm,
                         (wf->flags & DCC_WF_RO)
                         ? O_RDWR
                         : (O_RDWR | O_CREAT),
                         0666);
            dcc_rel_priv();
      }
#endif
      if (wf->ht_fd < 0) {
            dcc_pemsg(EX_NOINPUT, emsg, "open(%s): %s",
                    wf->ht_nm, ERROR_STR());
            return 0;
      }
      return 1;
}



/* this is needed only on systems without coherent mmap()/read()/write() */
static void
clean_white(DCC_WF *wf)
{
      if (wf->flags & DCC_WF_INFO_DIRTY) {
            if (wf->info
                && 0 > MSYNC(wf->info, wf->info_size, MS_SYNC))
                  dcc_error_msg("msync(%s): %s", wf->ht_nm, ERROR_STR());
            wf->flags &= ~DCC_WF_INFO_DIRTY;
      }
}



static void
bad_white(DCC_WF *wf)
{
      /* on some systems, msync() sometimes but not always sets mtime */
      clean_white(wf);
      dcc_mmap_utime(wf->ht_nm, 0);
}



/* unlock a whitelist hash file */
u_char
dcc_white_unlock(DCC_EMSG emsg, DCC_WF *wf)
{
      u_char result = 1;

      if (wf->ht_fd >= 0) {
            clean_white(wf);
            if (wf->ht_locked
                && !dcc_unlock_fd(emsg, wf->ht_fd, DCC_LOCK_ALL_FILE,
                              "whitelist ", wf->ht_nm))
                  result = 0;
      }
      wf->flags &= ~DCC_WF_INFO_DIRTY;
      wf->ht_locked = 0;
      return result;
}



/* unlock a whitelist hash file if it is locked
 *    Keep this separate from dcc_white_unlock to detect bogus unlocking
 *    as much as possible. */
static u_char
white_unlock_test(DCC_EMSG emsg, DCC_WF *wf)
{
      if (!wf->ht_locked)
            return 1;
      return dcc_white_unlock(emsg, wf);
}



/* Get the shared lock on the whitelist hash file.
 *    The wf mutex must be held on entry and is kept on failure */
static u_char
white_shlock(DCC_EMSG emsg, DCC_WF *wf)
{
      /* quit now if there is no ASCII file */
      if (wf->ascii_nm[0] == '\0')
            return 1;

      /* or if the hash table file is already locked */
      if (wf->ht_locked == 1)
            return 1;

      /* try to open and check the hash table file */
      if (wf->ht_fd < 0
          && !open_white_ht(emsg, wf))
            return 0;

#ifdef DCC_WIN32
      /* You cannot upgrade a WIN32 file lock.
       * This is safe provided the wf mutex is held. */
      if (!white_unlock_test(emsg, wf))
            return 0;
#endif
      if (!dcc_shlock_fd(emsg, wf->ht_fd, DCC_LOCK_ALL_FILE,
                     "whitelist ", wf->ht_nm)) {
            close_white_ht(0, wf);
            return 0;
      }

      wf->ht_locked = 1;
      return 1;
}



/* Get the the exclusive lock on the shared hash file to make a change in
 *    the shared data.
 *
 *    On UNIX systems the mutex we hold prevents confusion by other
 *    threads in the current process.  We upgrade the fcntl() lock on the
 *    hash file to exclusive to prevent confusion in other processes.
 *
 *    On WIN32 systems we cannot upgrade an fcntl() lock but must release
 *    and then grab it.  Fortunately the mutex prevents problems in the
 *    current process when we release the shared lock.
 *    We might use WIN32 mutexes for everything, because they can be shared
 *    among processes.  However, that would require larger differences
 *    between the WIN32 and UNIX versions.  Part of the problem is that
 *    we release the lock on the file while waiting for DNS resolutions.
 *
 *    The wf mutex and the exclusive lock on rebuilding the file must
 *    be held on entry. */
static u_char
white_lock(DCC_EMSG emsg, DCC_WF *wf)
{
      /* since we hold the exclusive lock on resolving hostnames,
       * if the file is write locked, then we hold the file lock */
      if (wf->ht_locked == 2)
            return 1;

      if (wf->ht_fd < 0) {
            /* something happened in another thread */
            dcc_pemsg(EX_SOFTWARE, emsg, "%s write lock failure",
                    wf->ht_nm);
            return 0;
      }
#ifdef DCC_WIN32
      if (!white_unlock_test(emsg, wf))
            return 0;
#endif
      if (!dcc_exlock_fd(emsg, wf->ht_fd, DCC_LOCK_ALL_FILE,
                     "whitelist", wf->ht_nm)) {
            return 0;
      }

      wf->flags |= DCC_WF_INFO_DIRTY;
      wf->ht_locked = 2;
      return 1;
}



/* the wf mutex must be held.
 *    If the file is being changed, the rebuild lock on the ASCII file
 *    must also be held. */
static u_char                       /* 1=done, 0=failed */
map_white_ht(DCC_EMSG emsg, DCC_WF *wf, DCC_WHITE_INX entries)
{
      int size;
#ifdef DCC_WIN32
      u_char locked;
#else
      void *p;
#endif

      if (entries > MAX_WHITE_ENTRIES) {
            dcc_pemsg(EX_IOERR, emsg, "%s should not contain %d entries",
                    wf->ht_nm, entries);
            return 0;
      }

#ifdef DCC_WIN32
      /* Make the hash table files maximum size on WIN32.
       * You cannot change the size of a WIN32 mapping object without
       * getting all processes using it to release it so that it can be
       * recreated.  This may cause problems if the size of hash table
       * header changes.
       * Since the file does not change size, there is no need to remap it */
      size = ENTRIES2SIZE(MAX_WHITE_ENTRIES);
      if (!wf->info) {
            locked = wf->ht_locked;
            if (locked
                && !dcc_white_unlock(emsg, wf))
                  return 0;
            wf->info = win32_map(emsg, &wf->ht_map, wf->ht_nm, wf->ht_fd,
                             size);
            switch (locked) {
            case 1:
                  if (!white_shlock(emsg, wf))
                        return 0;
                  break;
            case 2:
                  if (!white_lock(emsg, wf))
                        return 0;
                  break;
            }
            if (!wf->info)
                  return 0;
      }
#else
      unmap_white_ht(wf);

      size = ENTRIES2SIZE(entries);
      p = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, wf->ht_fd, 0);
      if (p == MAP_FAILED) {
            dcc_pemsg(EX_IOERR, emsg, "mmap(whitelist %s,%d): %s",
                    wf->ht_nm, size, ERROR_STR());
            return 0;
      }
      wf->info = p;
#endif
      wf->info_size = size;
      wf->info_entries = entries;
      wf->info_flags = wf->info->hdr.flags;

      return 1;
}



/* trim a hash table
 *    The mutex and the rebuilding lock must be held */
static u_char
trim_white_ht(DCC_EMSG emsg, DCC_WF *wf, int len)
{
#ifdef DCC_WIN32
      HANDLE h;
      size_t cur_size;

      /* The underlying file should always be the same size on WIN32
       * Microsoft documentation warns against SetEndOfFile() on
       * Win95 for mapped files, but it is hard to get Win98 to
       * map a file that has been locked.
       * So compromize by avoiding SetEndOfFile(). */
      h = (HANDLE)_get_osfhandle(wf->ht_fd);
      cur_size = SetFilePointer(h, 0, 0, FILE_CURRENT);
      if (cur_size == 0xffffffff) {
            dcc_pemsg(EX_IOERR, emsg, "SetFilePointer(whitelist %s): %s",
                    DCC_NM2PATH(wf->ht_nm), ERROR_STR());
            return 0;
      }
      if (cur_size > ENTRIES2SIZE(MAX_WHITE_ENTRIES)) {
            if (0xffffffff
                == SetFilePointer(h, ENTRIES2SIZE(MAX_WHITE_ENTRIES),
                              0, FILE_BEGIN)) {
                  dcc_pemsg(EX_IOERR, emsg,
                          "SetFilePointer(whitelist %s): %s",
                          DCC_NM2PATH(wf->ht_nm), ERROR_STR());
                  return 0;
            }
            if (!SetEndOfFile(h)) {
                  dcc_pemsg(EX_IOERR, emsg,
                          "SetEndOfFile(whitelist %s): %s",
                          DCC_NM2PATH(wf->ht_nm), ERROR_STR());
                  return 0;
            }
      }
      if (len < ENTRIES2SIZE(MAX_WHITE_ENTRIES)) {
            if (!map_white_ht(emsg, wf, MAX_WHITE_ENTRIES))
                  return 0;
            memset((char *)wf->info+len, 0,
                   ENTRIES2SIZE(MAX_WHITE_ENTRIES) - len);
      }
#else
      /* do not truncate the file while it is mapped because
       * some systems including OpenBSD will trash it. */
      unmap_white_ht(wf);

      if (0 > ftruncate(wf->ht_fd, len)) {
            dcc_pemsg(EX_IOERR, emsg, "ftruncate(whitelist %s,%d): %s",
                    wf->ht_nm, len, ERROR_STR());
            return 0;
      }
#endif
      return 1;
}



#define FIND_WHITE_BROKEN ((DCC_WHITE_ENTRY *)-1)
static DCC_WHITE_ENTRY *            /* -1=corruption, 0=not there */
find_white(DCC_EMSG emsg, DCC_WF *wf, DCC_CK_TYPES type, const DCC_SUM sum,
         DCC_WHITE_INX *binp)
{
      u_long accum;
      DCC_WHITE_INX bin, inx;
      DCC_WHITE_ENTRY *e;
      int loop_cnt, i;

      if (!wf->info) {
            dcc_pemsg(EX_SOFTWARE, emsg, "missing map to hash file %s",
                    wf->ht_nm);
            return FIND_WHITE_BROKEN;
      }

      accum = type;
      for (i = sizeof(DCC_SUM)-1; i >= 0; --i)
            accum = (accum >> 28) + (accum << 4) + sum[i];
      bin = accum % DIM(wf->info->bins);
      if (binp)
            *binp = bin;
      inx = wf->info->bins[bin];

      for (loop_cnt = wf->info->hdr.entries+1;
           loop_cnt >= 0;
           --loop_cnt) {
            if (!inx)
                  return 0;
            --inx;
            /* if necessary, expand the mapped window into the file */
            if (inx >= wf->info_entries) {
                  if (inx >= wf->info->hdr.entries) {
                        dcc_pemsg(EX_DATAERR, emsg,
                                "bogus index %u in whitelist %s",
                                inx, wf->ht_nm);
                        wf->info->hdr.resolve = DCC_WHITE_RESOLVE_BAD;
                        wf->stat_secs = 0;
                        return FIND_WHITE_BROKEN;
                  }
                  if (!map_white_ht(emsg, wf, wf->info->hdr.entries))
                        return 0;
            }
            e = &wf->info->tbl[inx];
            if (e->type == type && !memcmp(e->sum, sum, sizeof(DCC_SUM)))
                  return e;
            inx = e->fwd;
      }
      dcc_pemsg(EX_DATAERR, emsg, "chain length %d in %s"
              " starting at %d near %d for %s %s",
              wf->info->hdr.entries+1,
              wf->ht_nm,
              (DCC_WHITE_INX)(accum % DIM(wf->info->bins)), inx,
              dcc_type2str_err(type, 0, 0), dcc_ck2str_err(type, sum));
      return FIND_WHITE_BROKEN;
}



/* check one ASCII whitelist file */
static int                    /* 0=unchanged 1=changed -1=broken */
ck_white_nm(time_t ht_st_mtime, const char *nm)
{
      struct stat file_sb;

      if (stat(nm, &file_sb) < 0)
            return -1;

      return (file_sb.st_mtime > ht_st_mtime);
}


/* see if the ASCII files have changed */
static int                    /* 0=unchanged 1=changed -1=broken */
ck_white_nms(DCC_EMSG emsg, DCC_WF *wf, time_t ht_st_mtime)
{
      time_t now;
      int result, i;

      now = time(0);
      result = ck_white_nm(ht_st_mtime, wf->ascii_nm);
      if (result) {
            if (result < 0) {
                  /* if the main ASCII file has disappeared,
                   * leave the hash file open and just complain */
                  if (DCC_IS_TIME(now, wf->ht_broken,
                              BROKEN_CHECK_DELAY)) {
                        dcc_pemsg(EX_IOERR, emsg,
                                "stat(whitelist %s): %s",
                                wf->ascii_nm, ERROR_STR());
                        wf->ht_broken = now + BROKEN_CHECK_DELAY;
                  }
                  wf->stat_secs = now + WHITE_STAT_DELAY;
            }
            return result;
      }

      /* The hash table exists and is newer than the main ASCII file.
       * If the hash table is open, see if any of the included ASCII
       * files are new.
       * If the hash table is not open, assume it is up to date. */
      if (wf->ht_fd != -1) {
            for (i = 0; i < DIM(wf->info->hdr.white_incs); ++i) {
                  if (wf->info->hdr.white_incs[i][0] == '\0')
                        break;
                  /* stop at the first missing or changed included file */
                  if (ck_white_nm(ht_st_mtime,
                              wf->info->hdr.white_incs[i]))
                        return 1;
            }
      }
      return 0;
}



/* see if the hash or database file has the right magic
 *    the wf mutex and the shared lock on the whitelist hash file
 *    must be held, and are retained on failure */
static enum HT_ST {
    HT_ST_BAD,                      /* something is badly broken */
    HT_ST_BUILD_NOW,                /* database must be rebuilt now */
    HT_ST_REBUILD,                  /* database is out of date */
    HT_ST_OK}
check_white_ht(DCC_EMSG emsg, DCC_WF *wf, u_char msgs)
{
      struct stat fd_ht_sb;
#ifndef DCC_WIN32
      struct stat nm_ht_sb;
#endif
      int size;
      DCC_WHITE_INX entries;
      time_t now;
      int i;

      now = time(0);
      if ((wf->flags & DCC_WF_HT_CHECKED)
          && !DCC_IS_TIME(now, wf->stat_secs, WHITE_STAT_DELAY))
            return HT_ST_OK;

      if (fstat(wf->ht_fd, &fd_ht_sb) < 0) {
            dcc_pemsg(EX_IOERR, emsg, "stat(whitelist %s): %s",
                    wf->ht_nm, ERROR_STR());
            return HT_ST_BAD;
      }
      if (fd_ht_sb.st_size == 0) {
            dcc_pemsg(EX_NOINPUT, emsg, "whitelist %s empty", wf->ht_nm);
            return HT_ST_BUILD_NOW;
      }
#ifndef DCC_WIN32 /* open files cannot be unlinked in WIN32 */
      /* Notice if the open hash file has been unlinked */
      if (stat(wf->ht_nm, &nm_ht_sb) < 0
          || fd_ht_sb.st_dev != nm_ht_sb.st_dev
          || fd_ht_sb.st_ino != nm_ht_sb.st_ino) {
            if (msgs)
                  dcc_trace_msg("whitelist %s disappeared or broken",
                              wf->ht_nm);
            close_white_ht(0, wf);
            return HT_ST_BUILD_NOW;
      }
#endif

      if (!(wf->flags & DCC_WF_HT_CHECKED)) {
            /* try to not overwrite a file that is not one of
             * our hash tables */
            size = fd_ht_sb.st_size - EMPTY_WHITE_SIZE;
            if (size < 0) {
                  if (fd_ht_sb.st_size < ISZ(DCC_WHITE_MAGIC)) {
                        dcc_pemsg(EX_NOINPUT, emsg,
                                "%s is too small"
                                " to be a DCC whitelist hash table",
                                wf->ht_nm);
                        return HT_ST_BAD;
                  }
                  entries = MAX_WHITE_ENTRIES+1;
                  /* temporarily map the file to check the magic string */
                  if (!map_white_ht(emsg, wf, 0))
                        return HT_ST_BAD;
            } else {
                  entries = size / sizeof(DCC_WHITE_ENTRY);
                  if (!map_white_ht(emsg, wf, entries))
                        return HT_ST_BAD;
            }

            if (size < 0
                || memcmp(&wf->info->magic, &white_magic,
                        sizeof(white_magic))) {
                  /* rebuild old format files from scratch */
                  if (!memcmp(&wf->info->magic, WHITE_MAGIC_B_STR,
                            sizeof(WHITE_MAGIC_B_STR)-1)) {
                        if (msgs)
                              dcc_trace_msg("%s is obsolete %s",
                                          wf->ht_nm,
                                          wf->info->magic);
                        return HT_ST_BUILD_NOW;
                  }
                  dcc_pemsg(EX_NOINPUT, emsg,
                          "%s is not a DCC whitelist hash",
                          wf->ht_nm);
                  return HT_ST_BAD;
            }

            if ((size % sizeof(DCC_WHITE_ENTRY)) != 0
                || entries > MAX_WHITE_ENTRIES
                || entries < wf->info->hdr.entries) {
                  dcc_pemsg(EX_NOINPUT, emsg,
                          "size of whitelist %s, "OFF_DPAT
                          ", is impossible",
                          wf->ht_nm, fd_ht_sb.st_size);
                  return HT_ST_BUILD_NOW;
            }

            /* the wlist command works on both per-user and global
             * whitelists */
            if (wf->flags & DCC_WF_WLIST_CMD) {
                  if (wf->info_flags & DCC_WHITE_FG_PER_USER)
                        wf->flags |= DCC_WF_PER_USER;
                  else
                        wf->flags &= ~DCC_WF_PER_USER;
            }
            if ((wf->info_flags & DCC_WHITE_FG_PER_USER)
                && !(wf->flags & DCC_WF_PER_USER)) {
                  if (msgs)
                        dcc_error_msg("%s is a per-user whitelist"
                                    " used as a global whitelist",
                                    wf->ht_nm);
                  return HT_ST_BUILD_NOW;
            }
            if (!(wf->info_flags & DCC_WHITE_FG_PER_USER)
                && (wf->flags & DCC_WF_PER_USER)) {
                  if (msgs)
                        dcc_error_msg("%s is a global whitelist"
                                    " used as a per-user whitelist",
                                    wf->ht_nm);
                  return HT_ST_BUILD_NOW;
            }

            /* if we temporarily mapped a sick file above,
             * this will fail */
            if (!map_white_ht(emsg, wf, entries))
                  return HT_ST_BAD;

            wf->flags |= DCC_WF_HT_CHECKED;
      }

      /* check for changes to the ASCII files */
      i = ck_white_nms(emsg, wf, fd_ht_sb.st_mtime);

      /* if the ASCII file has disappeared,
       * leave the hash file open and just complain */
      if (i < 0)
            return HT_ST_OK;

      /* Checksums of SMTP client IP addresses are compared against
       * the checksums of the IP addresses of the hostnames in the flat
       * file, so occassionaly check for changes in DNS A RR's for entries
       * in the flat file, but only if there are host names or IP addresses
       * in the file */
      if (i == 0
          && (wf->info->hdr.resolve == DCC_WHITE_RESOLVE_NO_IP
            || !DCC_IS_TIME(now, wf->info->hdr.resolve,
                        DCC_RE_RESOLVE))) {
            wf->stat_secs = now + WHITE_STAT_DELAY;
            return HT_ST_OK;
      }

      if (dcc_clnt_debug > 2)
            dcc_trace_msg("time to rebuild %s", wf->ht_nm);
      /* try to let the specialized thread wait for the DNS chitchat */
      if (!(wf->flags & DCC_WF_PER_USER)
          && dcc_clnt_wake_resolve())
            return HT_ST_OK;
      return HT_ST_REBUILD;
}



static u_char
write_white(DCC_EMSG emsg, DCC_WF *wf, const void *buf, int buf_len, off_t pos)
{
      int i;

      if (wf->info) {
#ifdef DCC_WIN32
            /* Windows disclaims coherence between ordinary writes
             * and memory mapped writing.  The hash tables are
             * fixed size on Windows because of problems with WIN32
             * mapping objects, so we do not need to worry about
             * extending the hash table file. */
            memcpy((char *)wf->info+pos, buf, buf_len);
            return 1;
#else
            /* some UNIX systems have coherence trouble without msync() */
            if (0 > MSYNC(wf->info, wf->info_size, MS_SYNC)) {
                  dcc_pemsg(EX_IOERR, emsg, "msync(whitelist %s): %s",
                          wf->ht_nm, ERROR_STR());
                  bad_white(wf);
                  return 0;
            }
#endif
      }

      i = lseek(wf->ht_fd, pos, SEEK_SET);
      if (i < 0) {
            dcc_pemsg(EX_IOERR, emsg, "lseek(whitelist %s,"OFF_DPAT"): %s",
                    wf->ht_nm, pos, ERROR_STR());
            bad_white(wf);
            return 0;
      }
      i = write(wf->ht_fd, buf, buf_len);
      if (i != buf_len) {
            if (i < 0)
                  dcc_pemsg(EX_IOERR, emsg, "write(whitelist %s,%d): %s",
                          wf->ht_nm, buf_len, ERROR_STR());
            else
                  dcc_pemsg(EX_IOERR, emsg, "write(whitelist %s,%d): %d",
                          wf->ht_nm, buf_len, i);
            bad_white(wf);
            return 0;
      }
      return 1;
}



static int                    /* 1=ok,  0=bad entry, -1=fatal */
add_white(DCC_EMSG emsg, DCC_WF *wf, const char *fnm, int lineno,
        DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts)
{
      DCC_WHITE_ENTRY *e, new;
      DCC_WHITE_INX bin, new_inx;
      off_t end;

      if (!white_lock(emsg, wf))
            return -1;

      e = find_white(emsg, wf, type, sum, &bin);
      if (e == FIND_WHITE_BROKEN)
            return -1;
      if (e) {
            /* update an existing entry */
            if (e->gen != wf->info->hdr.gen+1) {
                  e->tgts = tgts;
                  e->gen = wf->info->hdr.gen+1;
                  return 1;
            }
            /* allow identical duplicates */
            if (e->tgts == tgts)
                  return 1;
            dcc_pemsg(EX_DATAERR, emsg,
                    "conflicting count for duplicate entry%s",
                    fnm_lineno(fnm, lineno));
            return 0;
      }

      memset(&new, 0, sizeof(new));
      new.type = type;
      memcpy(new.sum, sum, sizeof(DCC_SUM));
      new.tgts = tgts;
      new.gen = wf->info->hdr.gen+1;
      new.fwd = wf->info->bins[bin];

      /* Look for and try to use an existing free entry */
      while (wf->free < wf->info->hdr.entries) {
            if (wf->free > wf->info_entries
                && !map_white_ht(emsg, wf, wf->info->hdr.entries))
                  return -1;
            new_inx = wf->free++;
            e = &wf->info->tbl[new_inx];
            if (e->type == DCC_CK_INVALID) {
                  memcpy(e, &new, sizeof(*e));
                  wf->info->bins[bin] = new_inx+1;
                  return 1;
            }
      }

      /* add and use an entry at the end of the file */
      wf->free = MAX_WHITE_ENTRIES;
      if (wf->info->hdr.entries >= MAX_WHITE_ENTRIES) {
            dcc_pemsg(EX_DATAERR, emsg, "already maximum %d entries%s",
                    wf->info->hdr.entries,
                    fnm_lineno(fnm, lineno));
            return -1;
      }
      end = ENTRIES2SIZE(wf->info->hdr.entries);
      wf->info->bins[bin] = ++wf->info->hdr.entries;
      return write_white(emsg, wf, &new, sizeof(new), end) ? 1 : -1;
}



static int                    /* 1=ok,  0=bad entry, -1=fatal */
add_white_cidr(DCC_EMSG emsg, DCC_WHITE_TBL *tbl, const char *fnm, int lineno,
             int bits,
             const struct in6_addr *addrp, const struct in6_addr *maskp,
             DCC_TGTS tgts)
{
      int i;
      DCC_WHITE_CIDR_ENTRY *e;

      i = tbl->hdr.cidr_new.len;
      if (i >= DIM(tbl->hdr.cidr_new.e)) {
            dcc_pemsg(EX_DATAERR, emsg, "too many CIDR blocks%s",
                    fnm_lineno(fnm, lineno));
            return 0;
      }

      for (e = tbl->hdr.cidr_new.e; i > 0; ++e, --i) {
            if (e->bits == bits
                && !memcmp(addrp, &e->addr, sizeof(*addrp))) {
                  dcc_pemsg(EX_DATAERR, emsg, "duplicate CIDR block%s",
                          fnm_lineno(fnm, lineno));
                  return 0;
            }
      }

      e->bits = bits;
      e->addr = *addrp;
      e->mask = *maskp;
      e->tgts = tgts;
      ++tbl->hdr.cidr_new.len;

      return 1;
}



/* get permission to (re)build the hash table
 *    The hash table lock must be held on entry.
 *    Neither the resolving mutex nor the shared lock on the hash file
 *    are held on error exit.  The ASCII file is locked only on success.
 *    The mutex for the wf is held on entry and exit. */
static u_char                       /* 0=failed, 1=got it, 2=too busy */
lock_rebuild(DCC_EMSG emsg,
           DCC_WF *wf,
           u_char nowait)           /* 0=block on lock, 1=fail if busy */
{
      int white_ascii_fd;
      u_char busy;

      /* We need the right to use the gethostbyname() if the whitelist
       * might contain host names. */
      if (!(wf->flags & DCC_WF_PER_USER)) {
            if (nowait) {
                  if (!dcc_resolve_mutex_lock(1))
                        return 2;
            } else {
                  dcc_wf_unlock(wf);
                  dcc_resolve_mutex_lock(0);
                  dcc_wf_lock(wf);
            }
      }
      /* Use an exclusive lock on the ASCII file to serialize rebuilding
       * the hash table file. */
      white_ascii_fd = dcc_lock_open(emsg, wf->ascii_nm, O_RDWR,
                               nowait
                               ? DCC_LOCK_OPEN_NOWAIT
                               : DCC_LOCK_OPEN_WAIT,
                               DCC_LOCK_ALL_FILE, &busy);
      if (white_ascii_fd == -1) {
            if (!(wf->flags & DCC_WF_PER_USER))
                  dcc_resolve_mutex_unlock();
            return busy ? 2 : 0;
      }
      wf->ascii_file = fdopen(white_ascii_fd, "r");
      if (!wf->ascii_file) {
            dcc_pemsg(EX_IOERR, emsg, "fdopen(whitelist %s): %s",
                    wf->ascii_nm, ERROR_STR());
            close(white_ascii_fd);
            if (!(wf->flags & DCC_WF_PER_USER))
                  dcc_resolve_mutex_unlock();
            return 0;
      }
      return 1;
}



/* the whitelist hash file and the wf mutex must be locked */
static void
unlock_rebuild(DCC_WF *wf)
{
      if (wf->ascii_file) {
#ifdef DCC_WIN32
            /* unlock the file before closing it to keep Win95 happy */
            dcc_unlock_fd(0, fileno(wf->ascii_file), DCC_LOCK_ALL_FILE,
                        "whitelist ", wf->ascii_nm);
#endif
            fclose(wf->ascii_file);
            wf->ascii_file = 0;
      }
      if (!(wf->flags & DCC_WF_PER_USER))
            dcc_resolve_mutex_unlock();
}




/* remove hash table entries that are not in the ASCII file
 *    The hash table must be write-locked */
static u_char
hash_garbage(DCC_EMSG emsg, DCC_WF *wf)
{
      DCC_WHITE_ENTRY *e;
      DCC_WHITE_INX last_used, inx, *inxp;
      int loop_cnt, i;
      u_char result;

      if (!map_white_ht(emsg, wf, wf->info->hdr.entries))
            return 0;

      /* clear old entries */
      last_used = 0;
      result = 1;
      for (i = 0; i < DIM(wf->info->bins); ++i) {
            inxp = &wf->info->bins[i];
            loop_cnt = wf->info->hdr.entries+1;
            while ((inx = *inxp) != 0) {
                  if (inx > wf->info->hdr.entries) {
                        dcc_pemsg(EX_DATAERR, emsg,
                                "invalid garbage index %d in %s",
                                inx, wf->ht_nm);
                        result = 0;
                        goto done;
                  }
                  if (--loop_cnt < 0) {
                        dcc_pemsg(EX_DATAERR, emsg,
                                "chain length %d in %s starting at %d"
                                " near %d while rebuilding",
                                wf->info->hdr.entries+1,
                                wf->ht_nm, i, inx);
                        result = 0;
                        goto done;
                  }
                  e = &wf->info->tbl[inx-1];
                  if (e->gen == wf->info->hdr.gen) {
                        if (last_used < inx)
                              last_used = inx;
                        inxp = &e->fwd;
                  } else {
                        *inxp = e->fwd;
                        memset(e, 0, sizeof(*e));
                  }
            }
      }

done:;
      /* trim unneeded entries from the end of the file */
      if (!result || wf->info->hdr.entries > last_used) {
            wf->info->hdr.entries = last_used;
            if (!result)
                  emsg = 0;
            if (!trim_white_ht(emsg, wf, ENTRIES2SIZE(last_used))) {
                  emsg = 0;
                  result = 0;
            }
            if (!result) {
                  close_white_ht(0, wf);
                  return 0;
            }
            if (!map_white_ht(emsg, wf, last_used))
                  return 0;
      } else {
            /* update the mtime of the file for systems including BSD/OS */
            if (wf->info
                && 0 > MSYNC(wf->info, wf->info_size, MS_SYNC)) {
                  dcc_pemsg(EX_IOERR, emsg, "msync(whitelist %s): %s",
                          wf->ht_nm, ERROR_STR());
                  return 0;
            }
      }
      return 1;
}



/* (re)create the hash table file, which is already open and known to be ours
 *    The wf mutex must be held.  The hash table file must be checked
 *    and opened for writing.
 *    The main ASCII file must be locked and open. */
static u_char                       /* 1=done, 0=failed */
parse_white_file(DCC_EMSG emsg, DCC_WF *wf, u_char empty)
{
      static u_char zero = 0;
      time_t start;
      int resolved;

      if (dcc_clnt_debug > 1)
            dcc_trace_msg("start %sparsing %s",
                        empty ? "" : "re-", wf->ascii_nm);

      /* lock and then clear the hash table
       *    Win98 requires we release the lock to map the hash table
       *    That is safe provided we hold the wf mutex and the rebuilding
       *    lock on the ASCII file and if the hash table is not nonense
       *    while it is unlocked. */
      if (!white_lock(emsg, wf))
            return 0;
      if (!empty) {
            /* If we are recreating the file instead of emptying it and
             * starting from scratch, mark its current contents.
             * We want to keep the file valid for other threads and
             * processes as we spend time waiting for DNS resolutions. */
            if (!hash_garbage(emsg, wf))
                  return 0;
      } else {
            /* create the hash table from scratch */
            if (!trim_white_ht(emsg, wf, 0)
                || !write_white(emsg, wf,
                            white_magic, sizeof(white_magic), 0)
                || !write_white(emsg, wf,
                            &zero, 1, EMPTY_WHITE_SIZE-1))
                  return 0;
      }
      if (!map_white_ht(emsg, wf, 0)) {
            bad_white(wf);
            return 0;
      }
      wf->free = 0;
      memset(wf->info->hdr.white_incs, 0,
             sizeof(wf->info->hdr.white_incs));
      memset(&wf->info->hdr.cidr_new, 0,
             sizeof(wf->info->hdr.cidr_new));
      if (wf->flags & DCC_WF_PER_USER)
            wf->new_info_flags = DCC_WHITE_FG_PER_USER;
      else
            wf->new_info_flags  = 0;

      start = time(0);

      /* set delay now to rate limit failures */
      wf->info->hdr.resolve = start+DCC_RE_RESOLVE;

      resolved = dcc_parse_whitefile(emsg, wf,
                               wf->ascii_nm, wf->ascii_file,
                               &wf->info,
                               add_white, add_white_cidr,
                               empty ? 0 : white_unlock_test);
      if (!resolved) {
            bad_white(wf);
            return 0;
      }

      /* recover the lock on the file we may have released while
       * waiting for DNS resolution */
      if (!white_lock(emsg, wf))
            return 0;

      wf->stat_secs = start + WHITE_STAT_DELAY;

      /* don't worry about checking for changed A RRs if there are no
       * IP addresses in the whitelist */
      if (resolved == 1)
            wf->info->hdr.resolve = DCC_WHITE_RESOLVE_NO_IP;
      ++wf->info->hdr.gen;
      if (!hash_garbage(emsg, wf))
            return 0;
      memcpy(&wf->info->hdr.cidr_cur, &wf->info->hdr.cidr_new,
             sizeof(wf->info->hdr.cidr_cur));
      wf->info->hdr.flags = wf->info_flags = wf->new_info_flags;

      if (dcc_clnt_debug > 1)
            dcc_trace_msg("finished %sparsing %s",
                        empty ? "" : "re-", wf->ascii_nm);

      dcc_mmap_utime(wf->ht_nm, 1);
      return 1;
}



/* Get ready to consult the whitelist.
 *    Gets the shared lock on the hash file.
 *    The mutex must already be held and is kept even on failure. */
static u_char
white_start(DCC_EMSG emsg, DCC_WF *wf)
{
      enum HT_ST st;
      u_char nowait = 0;
      u_char result;

      if (!white_shlock(emsg, wf))
            return 0;

      st = check_white_ht(emsg, wf, 1);
      switch (st) {
      case HT_ST_BAD:
            dcc_white_unlock(0, wf);
            close_white_ht(0, wf);
            return 0;
      case HT_ST_OK:
            return 1;
      case HT_ST_BUILD_NOW:
            if (wf->flags & DCC_WF_RO) {
                  dcc_white_unlock(0, wf);
                  close_white_ht(0, wf);
                  return 0;
            }
            break;
      case HT_ST_REBUILD:
            if (wf->flags & DCC_WF_RO)
                  return 1;
            nowait = 1;
            break;
      }

      if (!dcc_white_unlock(emsg, wf))
            return 0;
      switch (lock_rebuild(emsg, wf, nowait)) {
      case 0:                       /* problem */
            return 0;
      case 1:                       /* got the lock */
            break;
      case 2:
            /* if we failed to get the (re)building lock because
             * it is busy, check to see if the file still needs work
             * and continue without building the file if possible */
            if (!white_shlock(emsg, wf))
                  return 0;
            st = check_white_ht(emsg, wf, dcc_clnt_debug > 2);
            switch (st) {
            case HT_ST_BAD:
                  dcc_white_unlock(0, wf);
                  close_white_ht(0, wf);
                  return 0;
            case HT_ST_OK:
                  return 1;
            case HT_ST_REBUILD:
                  if (dcc_clnt_debug > 1)
                        dcc_trace_msg("whitelist %s"
                                    " too busy to rebuild",
                                    wf->ascii_nm);
                  return 1;
            case HT_ST_BUILD_NOW:
                  dcc_pemsg(EX_SOFTWARE, emsg,
                          "impossible build lock failure");
                  dcc_white_unlock(0, wf);
                  close_white_ht(0, wf);
                  return 0;
            }
            return 1;
      }

      /* after getting the rebuilding lock, see if we still need it */
      if (!white_shlock(emsg, wf)) {
            unlock_rebuild(wf);
            return 0;
      }
      result = 0;
      st = check_white_ht(emsg, wf, dcc_clnt_debug > 2);
      switch (st) {
      case HT_ST_BAD:
            dcc_white_unlock(0, wf);
            close_white_ht(0, wf);
            unlock_rebuild(wf);
            return 0;
      case HT_ST_OK:
            /* no long need to work on the hash file */
            unlock_rebuild(wf);
            return 1;
      case HT_ST_BUILD_NOW:
            /* rebuild the hash file */
            result = parse_white_file(emsg, wf, 1);
            break;
      case HT_ST_REBUILD:
            /* rebuild the hash file */
            result = parse_white_file(emsg, wf, 0);
            break;
      }
      if (!result)
            emsg = 0;

      if (!white_unlock_test(emsg, wf))
            result = 0;
      unlock_rebuild(wf);
      if (result)
            result = white_shlock(emsg, wf);
      return result;
}



/* see that a local whitelist is ready
 *    on failure the file is not locked but the mutex is always locked */
u_char                              /* 1=all ok, 0=problems but continue */
dcc_white_rdy(DCC_EMSG emsg, DCC_WF *wf,
            const char *new_white_nm,     /* this pathname */
            u_char locking,         /* DCC_WHITE_RDY_LOCK_* */
            DCC_WHITE_RESULT *resultp)
{
      time_t now;
      int i;

      if (!(locking & DCC_WHITE_RDY_LOCK_KEEP_BOTH))
            dcc_wf_lock(wf);

      /* notice a new whitelist file we're supposed to use */
      if (new_white_nm && strcmp(new_white_nm, wf->ascii_nm)) {
            close_white_ht(0, wf);
            wf->ht_broken = 1;
            wf->stat_secs = 0;
            wf->info_flags = 0;
            dcc_fnm2path(wf->ascii_nm, new_white_nm);
            i = strlen(wf->ascii_nm) - STRZ(DCC_WHITE_SUFFIX);
            if (i > 0
                && !strcmp(wf->ascii_nm+i, DCC_WHITE_SUFFIX))
                  wf->ascii_nm[i] = '\0';
            snprintf(wf->ht_nm, sizeof(wf->ht_nm),
                   "%s"DCC_WHITE_SUFFIX, wf->ascii_nm);
      }

      /* If things are broken, the flat file has not changed since
       * things were broken, and it has not been at least 5 minutes,
       * then assume things are still broken. */
      if (wf->ht_fd == -1) {
            if (wf->ht_broken == 0) {
                  if (wf->ascii_nm[0] == '\0') {
                        dcc_pemsg(EX_NOINPUT, emsg, "no whitelist");
                        /* no file means nothing is listed */
                        if (resultp)
                              *resultp = DCC_WHITE_UNLISTED;
                        return 0;
                  }
                  wf->ht_broken = 1;
            }
            now = time(0);
            if (DCC_IS_TIME(now, wf->ht_broken, BROKEN_CHECK_DELAY)
                && ck_white_nms(emsg, wf, 0) < 0) {
                  /* fail conservatively */
                  if (resultp)
                        *resultp = DCC_WHITE_LISTED;
                  return 0;
            }
      }

      if (!white_start(emsg, wf)) {
            if (wf->ht_fd <0) {
                  if (resultp)
                        *resultp = DCC_WHITE_ERROR;
                  return 0;
            }
            /* only complain occassionally about being unable
             * to re-open the file */
            now = time(0);
            if (DCC_IS_TIME(now, wf->ht_broken, BROKEN_CHECK_DELAY)) {
                  wf->ht_broken = now+BROKEN_CHECK_DELAY;
                  if (resultp)
                        *resultp = DCC_WHITE_ERROR;
                  return 0;
            }
            if (dcc_clnt_debug > 2)
                  dcc_trace_msg(emsg);
            return 1;
      }

      if (!(locking & (DCC_WHITE_RDY_LOCK_NEED_BOTH
                   | DCC_WHITE_RDY_LOCK_KEEP_BOTH))
          && !dcc_white_unlock(emsg, wf)) {
            if (resultp)
                  *resultp = DCC_WHITE_ERROR;
            return 0;
      }

      if (resultp)
            *resultp = DCC_WHITE_UNLISTED;
      return 1;
}



/* check a local whitelist for a single checksum
 *    The file is locked except after an error */
DCC_WHITE_RESULT
dcc_white_sum(DCC_EMSG emsg, DCC_WF *wf,
            DCC_CK_TYPES type, const DCC_SUM sum,
            u_char wf_locked)       /* 1=mutex held */
{
      DCC_WHITE_ENTRY *e;
      DCC_WHITE_RESULT result;

      if (!dcc_white_rdy(emsg, wf, 0,
                     wf_locked
                     ? DCC_WHITE_RDY_LOCK_KEEP_BOTH
                     : DCC_WHITE_RDY_LOCK_NEED_BOTH,
                     &result))
            return result;

      e = find_white(emsg, wf, type, sum, 0);
      if (e == FIND_WHITE_BROKEN) {
            dcc_white_unlock(0, wf);
            close_white_ht(0, wf);
            return DCC_WHITE_ERROR;
      }

      if (!e)
            result = DCC_WHITE_UNLISTED;
      else if (e->tgts == DCC_TGTS_OK2)
            result = DCC_WHITE_HALF_LISTED;
      else if (e->tgts == DCC_TGTS_OK)
            result = DCC_WHITE_LISTED;
      else if (e->tgts == DCC_TGTS_TOO_MANY)
            result = DCC_WHITE_BLACK;

      return result;
}



/* See what a local whitelist file says about the checksums for a message.
 *    The answer is that it is whitelisted if at least one checksum
 *    is in the local whitelist or if there are two or more OK2 values.
 *    Otherwise it is blacklisted if at least one checksum is. */
DCC_WHITE_RESULT
dcc_white_cks(DCC_EMSG emsg, DCC_WF *wf,
            const DCC_GOT_CKS *cks, /* these checksums */
            DCC_CKS_WTGTS wtgts,    /* whitelist targets */
            const char *white_nm,   /* optional */
            u_char wf_locked,       /* 1=already have wf lock */
            u_char keep_locks)      /* 1=don't unlock either */
{
      const DCC_GOT_SUM *g;
      const DCC_WHITE_ENTRY *e;
      DCC_TGTS tgts, prev_tgts;
      DCC_WHITE_RESULT result;
      const DCC_WHITE_CIDR_ENTRY *cidrp;
      int bits;

      if (!(cks->flags & DCC_CKS_HAVE_SUM)) {
            result = DCC_WHITE_UNLISTED;
            if (wf_locked
                && !keep_locks) {
                  if (!dcc_white_unlock(emsg, wf))
                        result = DCC_WHITE_ERROR;
                  dcc_wf_unlock(wf);
            }
            return result;
      }

      if (!dcc_white_rdy(emsg, wf, white_nm,
                     wf_locked
                     ? DCC_WHITE_RDY_LOCK_KEEP_BOTH
                     : DCC_WHITE_RDY_LOCK_NEED_BOTH,
                     &result)) {
            if (!keep_locks)
                  dcc_wf_unlock(wf);
            return result;
      }

      /* look for each checksum in the hash file */
      result = DCC_WHITE_UNLISTED;
      prev_tgts = tgts = DCC_TGTS_INVALID;
      for (g = &cks->sums[DCC_CK_TYPE_FIRST]; g <= LAST(cks->sums); ++g) {
            /* ignore checksums we don't have */
            if (g->type == DCC_CK_INVALID)
                  continue;

            e = find_white(emsg, wf, g->type, g->sum, 0);
            if (!e) {
                  if (g->type != DCC_CK_IP)
                        continue;

                  /* if we had no hit and it is an IP address,
                   * check the CIDR blocks */
                  bits = 0;
                  cidrp = &wf->info->hdr.cidr_cur.e[wf->info->hdr.
                                          cidr_cur.len];
                  while (cidrp != wf->info->hdr.cidr_cur.e) {
                        --cidrp;
                        /* look for the longest match */
                        if (cidrp->bits <= bits)
                              continue;
                        if (DCC_IN_BLOCK(cks->ip_addr,
                                     cidrp->addr, cidrp->mask)) {
                              tgts = cidrp->tgts;
                              bits = cidrp->bits;
                        }
                  }
                  if (bits == 0)
                        continue;

            } else {
                  if (e == FIND_WHITE_BROKEN) {
                        if (!keep_locks) {
                              dcc_white_unlock(emsg, wf);
                              dcc_wf_unlock(wf);
                        }
                        return DCC_WHITE_ERROR;
                  }
                  tgts = e->tgts;
            }

            if (wtgts)
                  wtgts[g->type] = tgts;

            if (tgts == DCC_TGTS_OK) {
                  /* found the checksum in our whitelist,
                   * so tell the caller to forget it. */
                  result = DCC_WHITE_LISTED;
                  continue;
            }
            if (tgts == DCC_TGTS_OK2) {
                  if (prev_tgts == DCC_TGTS_OK2) {
                        /* two half-white checksums count the same
                         * as a single pure white checksum, so
                         * tell the caller to forget it */
                        result = DCC_WHITE_LISTED;
                        continue;
                  }
                  prev_tgts = DCC_TGTS_OK2;
            } else if (tgts == DCC_TGTS_TOO_MANY) {
                  /* report spam to DCC server */
                  if (result == DCC_WHITE_UNLISTED)
                        result = DCC_WHITE_BLACK;
            }
      }

      if (!keep_locks) {
            if (!dcc_white_unlock(emsg, wf))
                  result = DCC_WHITE_ERROR;
            dcc_wf_unlock(wf);
      }
      return result;
}

Generated by  Doxygen 1.6.0   Back to index