view dcclib/ckwhite.c @ 6:c7785b85f2d2 default tip

Init scripts try to conform LSB header
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 15:15:36 +0100
parents c7f6b056b673
children
line wrap: on
line source

/* Distributed Checksum Clearinghouse
 *
 * check checksums in the local whitelist
 *
 * Copyright (c) 2008 by Rhyolite Software, LLC
 *
 * This agreement is not applicable to any entity which sells anti-spam
 * solutions to others or provides an anti-spam solution as part of a
 * security solution sold to other entities, or to a private network
 * which employs the DCC or uses data provided by operation of the DCC
 * but does not provide corresponding data to other users.
 *
 * Permission to use, copy, modify, and distribute this software without
 * changes for any purpose with or without fee is hereby granted, provided
 * that the above copyright notice and this permission notice appear in all
 * copies and any distributed versions or copies are either unchanged
 * or not called anything similar to "DCC" or "Distributed Checksum
 * Clearinghouse".
 *
 * Parties not eligible to receive a license under this agreement can
 * obtain a commercial license to use DCC by contacting Rhyolite Software
 * at sales@rhyolite.com.
 *
 * A commercial license would be for Distributed Checksum and Reputation
 * Clearinghouse software.  That software includes additional features.  This
 * free license for Distributed ChecksumClearinghouse Software does not in any
 * way grant permision to use Distributed Checksum and Reputation Clearinghouse
 * software
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.3.103-1.133 $Revision$
 */

#include "dcc_ck.h"


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 ENTRIES2SIZE0(_l)   (sizeof(DCC_WHITE_TBL)			\
			     - sizeof(DCC_WHITE_ENTRY)			\
			     + ((_l) * sizeof(DCC_WHITE_ENTRY)))
#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 */
#define ENTRIES2SIZE(_l)   ENTRIES2SIZE0(MAX_WHITE_ENTRIES)
#else
#define ENTRIES2SIZE(_l)   ENTRIES2SIZE0(_l)
#endif



void
dcc_wf_init(DCC_WF *wf,
	    u_int wf_flags)		/* DCC_WF_* */
{
	memset(wf, 0, sizeof(*wf));
	wf->ht_fd = -1;
	wf->wf_flags = wf_flags & ~DCC_WF_NOFILE;
}



/* this is needed only on systems without coherent mmap()/read()/write() */
static void
sync_white(DCC_WF *wf)
{
	if (wf->wtbl
	    && 0 > MSYNC(wf->wtbl, wf->wtbl_size, MS_SYNC))
		dcc_error_msg("msync(%s): %s", wf->ht_nm, ERROR_STR());
}



#define STILL_BROKE(wf,now) ((wf)->broken != 0				\
			     && !DCC_IS_TIME(now, (wf)->broken,		\
					  DCC_WHITE_BROKEN_DELAY))
static void
is_broken(DCC_WF *wf)
{
	wf->broken = time(0) + DCC_WHITE_BROKEN_DELAY;

	/* This is racy, but the worst that can happen is that
	 * another process races to set the retry timer */
	if (!(wf->wf_flags & DCC_WF_RO) && wf->wtbl) {
		wf->wtbl->hdr.broken = wf->broken;
		sync_white(wf);
	}
}



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

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



static u_char				/* 1=done, 0=failed */
map_white_ht(DCC_EMSG emsg, DCC_WF *wf, DCC_WHITE_INX entries)
{
	size_t size;
#ifndef DCC_WIN32
	void *p;
#endif

	unmap_white_ht(wf);

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

	size = ENTRIES2SIZE(entries);
#ifdef DCC_WIN32
	if (!wf->wtbl) {
		wf->wtbl = win32_map(emsg, &wf->ht_map, wf->ht_nm, wf->ht_fd,
				     size);
		if (!wf->wtbl)
			return 0;
	}
#else
	p = mmap(0, size,
		 (wf->wf_flags & DCC_WF_RO)
		 ? PROT_READ : (PROT_READ|PROT_WRITE),
		 MAP_SHARED, wf->ht_fd, 0);
	if (p == MAP_FAILED) {
		dcc_pemsg(EX_IOERR, emsg, "mmap(%s,%d): %s",
			  wf->ht_nm, (int)size, ERROR_STR());
		return 0;
	}
	wf->wtbl = p;
#endif
	wf->wtbl_size = size;
	wf->wtbl_entries = entries;
	wf->wtbl_flags = wf->wtbl->hdr.flags;

	return 1;
}



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

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

	unmap_white_ht(wf);

#ifdef DCC_WIN32
	/* unlock the file before closing it to keep Win95 happy */
	if (!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;
	memset(&wf->ht_sb, 0, sizeof(wf->ht_sb));
#ifndef DCC_WIN32
	wf->ht_sb.st_dev = -1;
	wf->ht_sb.st_ino = -1;
#endif
	return result;
}



static int				/* 0=ok -1=failed */
unlink_white_ht(DCC_WF *wf,
		time_t now)		/* 0=our own new file */
{
	int result;

	/* mark it bad if it is a brand new hash table */
	if (!now && wf->wtbl && !(wf->wf_flags & DCC_WF_RO))
		wf->wtbl->hdr.ascii_mtime = 0;

#ifdef DCC_WIN32
	/* racy but you cannot unlink an open file on WIN32 */
	close_white_ht(0, wf);
#endif
	result = -1;
	if (!(wf->wf_flags & DCC_WF_RO)) {
		if (0 <= unlink(wf->ht_nm)) {
			result = 0;
#ifndef DCC_WIN32
		} else if ((errno == EACCES || errno == EPERM)
			   && dcc_get_priv_home(wf->ht_nm)) {
			if (0 <= unlink(wf->ht_nm))
				result = 0;
			dcc_rel_priv();
#endif
		}
		if (result < 0)
			dcc_error_msg("unlink(%s): %s",
				      wf->ht_nm, ERROR_STR());
	}

	/* If we failed to unlink the old hash table,
	 * remember in core that things are broken but do not touch the file
	 * in case it is a link to /etc/passwd or something else dangerous */
	if (result < 0 && now)
		wf->broken = now + DCC_WHITE_BROKEN_DELAY;
#ifndef DCC_WIN32
	close_white_ht(0, wf);
#endif

	return result;
}



static u_char
new_ht_nm(DCC_EMSG emsg, DCC_WF *wf, u_char new)
{
	if (wf->ascii_nm_len >= ISZ(wf->ht_nm) - LITZ(DCC_WHITE_SUFFIX)) {
		dcc_pemsg(EX_NOINPUT, emsg, "bad whitelist file name \"%s\"",
			  wf->ascii_nm);
		is_broken(wf);
		return 0;
	}

	memcpy(wf->ht_nm, wf->ascii_nm, wf->ascii_nm_len);
	strcpy(&wf->ht_nm[wf->ascii_nm_len],
	       new ? DCC_WHITE_NEW_SUFFIX : DCC_WHITE_SUFFIX);
	return 1;
}



u_char
dcc_new_white_nm(DCC_EMSG emsg, DCC_WF *wf,
		 const char *new_white_nm)
{
	DCC_PATH new_path;
	const char *new;
	u_int len;
	int i;

	if (!strcmp(new_white_nm, wf->ascii_nm))
		return 1;

	close_white_ht(0, wf);
	dcc_wf_init(wf, wf->wf_flags);

	if (!fnm2rel(new_path, new_white_nm, 0)) {
		dcc_pemsg(EX_USAGE, emsg, "bad whitelist name \"%s\"",
			  new_white_nm);
		return 0;
	}

	len = strlen(new_path);
	i = len - LITZ(DCC_WHITE_SUFFIX);
	if (i > 0 && (!strcmp(&new_path[i], DCC_WHITE_SUFFIX)
		      || !strcmp(&new_path[i], DCC_WHITE_NEW_SUFFIX))) {
		len = i;
		new_path[len] = '\0';
	}

	if (len > ISZ(wf->ht_nm) - ISZ(DCC_WHITE_SUFFIX)) {
		dcc_pemsg(EX_USAGE, emsg, "long whitelist name \"%s\"",
			  new_white_nm);
		return 0;
	}

	new = path2fnm(new_path);
	len = strlen(new);
	memcpy(wf->ascii_nm, new, len+1);
	wf->ascii_nm_len = len;
	return 1;
}



/* open and shared-lock a hash table file */
static int				/* -1=fatal 0=rebuild, 1=ok */
open_white_ht(DCC_EMSG emsg, DCC_WF *wf)
{
	int size;
	DCC_WHITE_INX entries;

	close_white_ht(0, wf);

	if (!new_ht_nm(emsg, wf, 0))
		return -1;

	wf->ht_fd = dcc_lock_open(emsg, wf->ht_nm,
				  (wf->wf_flags & DCC_WF_RO)
				  ? O_RDONLY : O_RDWR,
				  DCC_LOCK_OPEN_SHARE,
				  DCC_LOCK_ALL_FILE, 0);
	if (wf->ht_fd < 0) {
		/* if this is a `wlist` command and neither -P nor -Q
		 * were specified, try to open the file read-only */
		if ((wf->wf_flags & DCC_WF_WLIST)
		    && !(wf->wf_flags & (DCC_WF_WLIST_RO | DCC_WF_WLIST_RW))) {
			if (emsg)
				emsg[0] = '\0';
			wf->wf_flags |= DCC_WF_RO;
			wf->ht_fd = dcc_lock_open(emsg, wf->ht_nm,
						  O_RDONLY,
						  DCC_LOCK_OPEN_SHARE,
						  DCC_LOCK_ALL_FILE, 0);
		}
		if (wf->ht_fd < 0)
			return 0;
	}

	if (fstat(wf->ht_fd, &wf->ht_sb) < 0) {
		dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s",
			  wf->ht_nm, ERROR_STR());
		close_white_ht(0, wf);
		is_broken(wf);
		return -1;
	}

	size = wf->ht_sb.st_size - EMPTY_WHITE_SIZE;
	if (size < 0) {
		dcc_pemsg(EX_NOINPUT, emsg,
			  "%s is too small to be a DCC whitelist hash table",
			  wf->ht_nm);
		return unlink_white_ht(wf, time(0));

	} else {
		entries = size / sizeof(DCC_WHITE_ENTRY);
		if (!map_white_ht(emsg, wf, entries))
			return unlink_white_ht(wf, time(0));
	}

	if (memcmp(&wf->wtbl->magic, &white_magic, sizeof(white_magic))) {
		/* rebuild old format files */
		if (!memcmp(&wf->wtbl->magic, WHITE_MAGIC_B_STR,
			    LITZ(WHITE_MAGIC_B_STR))) {
			if (dcc_clnt_debug)
				dcc_trace_msg("%s is obsolete %s",
					      wf->ht_nm, wf->wtbl->magic);
		} else {
			dcc_pemsg(EX_NOINPUT, emsg,
				  "%s is not a DCC whitelist hash file",
				  wf->ht_nm);
		}
		return unlink_white_ht(wf, time(0));
	}

	if ((size % sizeof(DCC_WHITE_ENTRY)) != 0
	    || entries > MAX_WHITE_ENTRIES
	    || entries < wf->wtbl->hdr.entries) {
		dcc_pemsg(EX_NOINPUT, emsg,
			  "invalid size of whitelist %s="OFF_DPAT,
			  wf->ht_nm, wf->ht_sb.st_size);
		return unlink_white_ht(wf, time(0));
	}

	if (wf->wtbl->hdr.ascii_mtime == 0) {
		close_white_ht(0, wf);
		return 0;		/* broken hash table */
	}

	/* we know the hash table is usable
	 * but we might want to rebuild it */
	wf->need_reopen = 0;
	wf->wtbl_flags = wf->wtbl->hdr.flags;

	/* wlist and dccproc work on both per-user and global whitelists,
	 * so do not change the nature of the file if it is already known */
	if (wf->wf_flags & DCC_WF_EITHER) {
		if (wf->wtbl_flags & DCC_WHITE_FG_PER_USER)
			wf->wf_flags |= DCC_WF_PER_USER;
		else
			wf->wf_flags &= ~DCC_WF_PER_USER;
	}

	return 1;
}



static void
create_white_ht_sub(DCC_EMSG emsg, DCC_WF *new_wf, const DCC_WF *wf,
		    u_char *busyp)
{
	/* do not use O_EXCL because we want to wait for any other
	 * process to finish
	 *
	 * wait if and only if we do not have a usable file open */

	new_wf->ht_fd = dcc_lock_open(emsg, new_wf->ht_nm,
			   O_RDWR | O_CREAT,
			   wf->ht_fd >= 0 ? DCC_LOCK_OPEN_NOWAIT : 0,
			   DCC_LOCK_ALL_FILE, busyp);
	if (new_wf->ht_fd < 0)
		return;

	/* a new hash table must be empty */
	if (0 > fstat(new_wf->ht_fd, &new_wf->ht_sb)) {
		dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s",
			  new_wf->ht_nm, ERROR_STR());
		close_white_ht(emsg, new_wf);
		return;
	}

	if (new_wf->ht_sb.st_size != 0) {
		dcc_pemsg(EX_IOERR, emsg, "%s has non-zero size %d",
			  new_wf->ht_nm, (int)new_wf->ht_sb.st_size);
		close_white_ht(emsg, new_wf);
		return;
	}
}



/* create and write-lock a new hash table file
 *	wait for lock if we don't have existing file open */
static int				/* 1=done, 0=file busy, -1=fatal */
create_white_ht(DCC_EMSG emsg,
		DCC_WF *tmp_wf,		/* build with this */
		const DCC_WF *wf)	/* from this */
{
	u_char busy = 0;

	tmp_wf->ascii_nm_len = wf->ascii_nm_len;
	memcpy(tmp_wf->ascii_nm, wf->ascii_nm, wf->ascii_nm_len+1);
	if (!new_ht_nm(emsg, tmp_wf, 1)) {
		tmp_wf->ht_nm[0] = '\0';
		return -1;
	}

	if (tmp_wf->wf_flags & DCC_WF_RO) {
		dcc_pemsg(EX_IOERR, emsg,
			  "read only access; cannot create or update %s",
			  tmp_wf->ht_nm);
		tmp_wf->ht_nm[0] = '\0';
		return -1;
	}

#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(tmp_wf->ascii_nm, R_OK | W_OK)
	    && dcc_get_priv_home(tmp_wf->ht_nm)) {
		/* first try to open a public hash table */
		create_white_ht_sub(emsg, tmp_wf, wf, &busy);
		if (tmp_wf->ht_fd < 0 && !busy) {
			if (emsg && dcc_clnt_debug > 2)
				dcc_error_msg("%s", emsg);
			unlink(tmp_wf->ht_nm);
			create_white_ht_sub(emsg, tmp_wf, wf, &busy);
		}
		dcc_rel_priv();
	}
#endif

	if (tmp_wf->ht_fd < 0 && !busy) {
		/* try to open or create a private hash table */
		create_white_ht_sub(emsg, tmp_wf, wf, &busy);
		if (tmp_wf->ht_fd < 0 && !busy) {
			if (emsg && dcc_clnt_debug > 2)
				dcc_error_msg("%s", emsg);
			unlink(tmp_wf->ht_nm);
			create_white_ht_sub(emsg, tmp_wf, wf, &busy);
		}
	}

#ifndef DCC_WIN32
	/* try one last time with privileges in case the ASCII file has
	 * mode 666 but the directory does not */
	if (tmp_wf->ht_fd < 0 && !busy) {
		if (dcc_get_priv_home(tmp_wf->ht_nm)) {
			if (emsg && dcc_clnt_debug > 2)
				dcc_error_msg("%s", emsg);
			unlink(tmp_wf->ht_nm);
			create_white_ht_sub(emsg, tmp_wf, wf, &busy);
			dcc_rel_priv();
		}
	}
#endif
	if (tmp_wf->ht_fd < 0) {
		tmp_wf->ht_nm[0] = '\0';
		if (busy)
			return 0;
		return -1;
	}
	return 1;
}



#define FIND_WHITE_BROKEN ((DCC_WHITE_ENTRY *)-1)
static DCC_WHITE_ENTRY *
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->wtbl || wf->wtbl->hdr.ascii_mtime == 0)
		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->wtbl->bins);
	if (binp)
		*binp = bin;
	inx = wf->wtbl->bins[bin];

	for (loop_cnt = wf->wtbl->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->wtbl_entries) {
			if (inx >= wf->wtbl->hdr.entries) {
				dcc_pemsg(EX_DATAERR, emsg,
					  "bogus index %u in %s",
					  inx, wf->ht_nm);
				if (!(wf->wf_flags & DCC_WF_RO))
					wf->wtbl->hdr.ascii_mtime = 0;
				sync_white(wf);
				return FIND_WHITE_BROKEN;
			}
			if (!map_white_ht(emsg, wf, wf->wtbl->hdr.entries))
				return 0;
		}
		e = &wf->wtbl->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->wtbl->hdr.entries+1,
		  wf->ht_nm,
		  (DCC_WHITE_INX)(accum % DIM(wf->wtbl->bins)), inx,
		  dcc_type2str_err(type, 0, 0, 0),
		  dcc_ck2str_err(type, sum, 0));

	if (!(wf->wf_flags & DCC_WF_RO))
		wf->wtbl->hdr.ascii_mtime = 0;
	sync_white(wf);
	return FIND_WHITE_BROKEN;
}



static int				/* 1=quit stat_white_nms() */
stat_1white_nm(int *resultp,		/* set=1 if (supposedly) no change */
	       DCC_WF *wf, const char *nm, time_t ascii_mtime)
{
	struct stat sb;
	time_t now;

	if (stat(nm, &sb) < 0) {
		if (errno != ENOENT
		    || !wf->wtbl
		    || (wf->wf_flags & DCC_WF_RO)) {
			dcc_trace_msg("stat(%s): %s", nm, ERROR_STR());
			is_broken(wf);
			*resultp = 0;
			return 1;
		}

		/* only complain if an ASCII file disappears temporarily */
		if (wf->broken == 0) {
			dcc_trace_msg("%s disappeared: %s", nm, ERROR_STR());
			is_broken(wf);
			*resultp = 1;
			return 1;
		}
		now = time(0);
		if (STILL_BROKE(wf, now)) {
			if (dcc_clnt_debug > 1)
				dcc_trace_msg("ignoring stat(%s): %s",
					      nm, ERROR_STR());
			*resultp = 1;
		} else {
			if (dcc_clnt_debug > 1)
				dcc_trace_msg("pay attention to stat(%s): %s",
					      nm, ERROR_STR());
			*resultp = 0;
		}
		return 1;
	}

	if (sb.st_mtime != ascii_mtime) {
		*resultp = 0;
		return 1;
	}

	return 0;
}



/* see if the ASCII files have changed */
static int				/* 1=same 0=changed or sick -1=broken */
stat_white_nms(DCC_EMSG emsg, DCC_WF *wf)
{
	struct stat ht_sb;
	time_t now;
	int i, result;

	if (!wf->wtbl)
		return -1;

	now = time(0);
	wf->next_stat_time = now + DCC_WHITE_STAT_DELAY;

	/* Notice if the hash file has been unlinked */
	if (stat(wf->ht_nm, &ht_sb) < 0) {
		if (emsg && dcc_clnt_debug)
			dcc_error_msg("stat(%s): %s",
				      wf->ht_nm, ERROR_STR());
		return -1;
	}
#ifdef DCC_WIN32
	/* open files cannot be unlinked in WIN32, which lets us not
	 * worry about whether WIN32 files have device and i-numbers */
#else
	if (wf->ht_sb.st_dev != ht_sb.st_dev
	    || wf->ht_sb.st_ino != ht_sb.st_ino) {
		if (emsg && dcc_clnt_debug > 2)
			dcc_error_msg("%s disappeared", wf->ht_nm);
		return -1;
	}
#endif /* DCC_WIN32 */
	wf->ht_sb = ht_sb;

	/* delays on re-parsing and complaining in the file override */
	if (wf->reparse < wf->wtbl->hdr.reparse)
		wf->reparse = wf->wtbl->hdr.reparse;
	if (wf->broken < wf->wtbl->hdr.broken)
		wf->broken = wf->wtbl->hdr.broken;

	/* seriously broken hash tables are unusable */
	if (wf->wtbl->hdr.ascii_mtime == 0)
		return -1;

	/* pretend things are fine if they are recently badly broken */
	if (STILL_BROKE(wf, now))
		return 1;

	/* if the main ASCII file has disappeared,
	 * leave the hash file open and just complain,
	 * but only for a while */
	result = 1;
	if (stat_1white_nm(&result, wf, wf->ascii_nm,
			   wf->wtbl->hdr.ascii_mtime))
		return result;
	/* see if any of the included ASCII files are new */
	for (i = 0; i < DIM(wf->wtbl->hdr.white_incs); ++i) {
		if (wf->wtbl->hdr.white_incs[i].nm[0] == '\0')
			break;
		/* stop at the first missing or changed included file */
		if (stat_1white_nm(&result, wf,
				   wf->wtbl->hdr.white_incs[i].nm,
				   wf->wtbl->hdr.white_incs[i].mtime))
			return result;
	}

	/* force periodic reparsing of syntax errors to nag in system log */
	if (wf->reparse != 0
	    && DCC_IS_TIME(now, wf->reparse, DCC_WHITE_REPARSE_DELAY))
		return 0;

	if ((wf->wtbl_flags & DCC_WHITE_FG_PER_USER)
	    && !(wf->wf_flags & DCC_WF_PER_USER)) {
		dcc_error_msg("%s is a per-user whitelist"
			      " used as a global whitelist",
			      wf->ht_nm);
		return 0;
	}
	if (!(wf->wtbl_flags & DCC_WHITE_FG_PER_USER)
	    && (wf->wf_flags & DCC_WF_PER_USER)) {
		dcc_error_msg("%s is a global whitelist"
			      " used as a per-user whitelist",
			      wf->ht_nm);
		return 0;
	}

	/* Checksums of SMTP client IP addresses are compared against the
	 * checksums of the IP addresses of the hostnames in the ASCII file.
	 * Occassionaly check for changes in DNS A RR's for entries in
	 * the ASCII file, but only if there are host names or IP
	 * addresses in the file */
	if ((wf->wtbl->hdr.flags & DCC_WHITE_FG_HOSTNAMES)
	    && DCC_IS_TIME(now, wf->ht_sb.st_mtime+DCC_WHITECLNT_RESOLVE,
			   DCC_WHITECLNT_RESOLVE)) {
		if (dcc_clnt_debug > 2)
			dcc_trace_msg("time to rebuild %s", wf->ht_nm);
		return 0;
	}

	return 1;
}



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

	if (wf->wtbl) {
#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->wtbl+pos, buf, buf_len);
		return 1;
#else
		/* some UNIX systems have coherence trouble without msync() */
		if (0 > MSYNC(wf->wtbl, wf->wtbl_size, MS_SYNC)) {
			dcc_pemsg(EX_IOERR, emsg, "msync(%s): %s",
				  wf->ht_nm, ERROR_STR());
			return 0;
		}
#endif
	}

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



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

	/* find the hash chain for the new entry */
	e = find_white(emsg, wf, type, sum, &bin);
	if (e == FIND_WHITE_BROKEN)
		return -1;

	/* ignore duplicates on this, the first pass */
	if (e)
		return 1;

	memset(&new, 0, sizeof(new));
	new.type = type;
	memcpy(new.sum, sum, sizeof(DCC_SUM));
	new.tgts = tgts;
	new.fwd = wf->wtbl->bins[bin];
	new.lno = wf->lno;
	new.fno = wf->fno;

	/* Use a new entry at the end of the file
	 * It will be beyond the memory mapped window into the file */
	if (wf->wtbl->hdr.entries >= MAX_WHITE_ENTRIES) {
		dcc_pemsg(EX_DATAERR, emsg, "more than maximum %d entries%s",
			  wf->wtbl->hdr.entries, wf_fnm_lno(&fnm_buf, wf));
		return 0;
	}
	end = ENTRIES2SIZE(wf->wtbl->hdr.entries);
	wf->wtbl->bins[bin] = ++wf->wtbl->hdr.entries;
	return write_white(emsg, wf, &new, sizeof(new), end) ? 1 : -1;
}



#define MAX_CIDR_BITS	7

static int				/* 1=ok,  0=bad entry, -1=fatal */
add_white_cidr(DCC_EMSG emsg, DCC_WF *wf,
	       int bits,
	       const struct in6_addr *addrp, const struct in6_addr *maskp,
	       DCC_TGTS tgts)
{
	DCC_WHITE_CIDR_ENTRY *e;
	struct in6_addr addr;
	DCC_SUM sum;
	DCC_FNM_LNO_BUF fnm_buf;
	int result, i, j;

	/* use individual entries for MAX_CIDR_BITS and smaller blocks */
	if (128-bits <= MAX_CIDR_BITS) {
		addr = *addrp;
		result = 1;
		for (i = 1 << (128-bits); i > 0; --i) {
			dcc_ck_ipv6(sum, &addr);
			j = add_white(emsg, wf, DCC_CK_IP, sum, tgts);
			if (j <= 0) {
				if (j < 0)
					return j;
				result = j;
			}
			addr.s6_addr32[3] = ntohl(addr.s6_addr32[3]);
			++addr.s6_addr32[3];
			addr.s6_addr32[3] = htonl(addr.s6_addr32[3]);
		}
		return result;
	}

	i = wf->wtbl->hdr.cidr.len;
	for (e = wf->wtbl->hdr.cidr.e; i > 0; ++e, --i) {
		/* ignore collisions on this, the first pass */
		if (e->bits == bits
		    && !memcmp(addrp, &e->addr, sizeof(*addrp)))
			return 1;
	}

	if (wf->wtbl->hdr.cidr.len >= DIM(wf->wtbl->hdr.cidr.e)) {
		dcc_pemsg(EX_DATAERR, emsg, "too many CIDR blocks%s",
			  wf_fnm_lno(&fnm_buf, wf));
		return 0;
	}

	e->bits = bits;
	e->addr = *addrp;
	e->mask = *maskp;
	e->tgts = tgts;
	e->lno = wf->lno;
	e->fno = wf->fno;
	++wf->wtbl->hdr.cidr.len;

	return 1;
}



static void
dup_msg(DCC_EMSG emsg, DCC_WF *wf, int e_fno, int e_lno,
	DCC_TGTS e_tgts, DCC_TGTS tgts)
{
	char tgts_buf[30], e_tgts_buf[30];
	const char *fname1, *fname2;

	fname1 = wf_fnm(wf, wf->fno);
	fname2 = wf_fnm(wf, e_fno);
	dcc_pemsg(EX_DATAERR, emsg,
		  "\"%s\" in line %d%s%s conflicts with \"%s\" in line"
		  " %d of %s",
		  dcc_tgts2str(tgts_buf, sizeof(tgts_buf), tgts, 0),
		  wf->lno,
		  fname1 != fname2 ? " of " : "",
		  fname1 != fname2 ? fname1 : "",
		  dcc_tgts2str(e_tgts_buf, sizeof(e_tgts_buf), e_tgts, 0),
		  e_lno,
		  fname2);
}



static DCC_TGTS
combine_white_tgts(DCC_TGTS new, DCC_TGTS old)
{
	if (new < DCC_TGTS_TOO_MANY && old == DCC_TGTS_TOO_MANY)
		return new;

	if (new == DCC_TGTS_OK || old == DCC_TGTS_OK)
		return DCC_TGTS_OK;
	if (new == DCC_TGTS_OK2 || old == DCC_TGTS_OK2)
		return DCC_TGTS_OK2;
	if (new == DCC_TGTS_OK_MX || old == DCC_TGTS_OK_MX)
		return DCC_TGTS_OK_MX;
	if (new == DCC_TGTS_OK_MXDCC || old == DCC_TGTS_OK_MXDCC)
		return DCC_TGTS_OK_MXDCC;
	if (new == DCC_TGTS_TOO_MANY || old == DCC_TGTS_TOO_MANY)
		return DCC_TGTS_TOO_MANY;
	if (new == DCC_TGTS_SUBMIT_CLIENT || old == DCC_TGTS_SUBMIT_CLIENT)
		return DCC_TGTS_SUBMIT_CLIENT;

	return new;
}



static int			/* 1=ok,  0=bad entry, -1=fatal */
ck_dup_white(DCC_EMSG emsg, DCC_WF *wf,
	     DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts, u_char complain)
{
	DCC_WHITE_ENTRY *e;
	DCC_FNM_LNO_BUF buf;
	const char *from;

	/* find the hash table entry */
	e = find_white(emsg, wf, type, sum, 0);
	if (e == FIND_WHITE_BROKEN)
		return -1;
	if (!e) {
		/* We failed to find the hash table entry.  Either the
		 * hash file is corrupt, the ASCII file is changing beneath
		 * our feet, or a host name that failed to resolve the first
		 * time we parsed the ASCII file is now resolving during
		 * this second parsing. */
		from = wf_fnm_lno(&buf, wf);
		if (type == DCC_CK_IP) {
			if (dcc_clnt_debug > 2)
				dcc_trace_msg("%s entry (dis)appeared in %s%s",
					      dcc_type2str_err(type, 0, 0, 0),
					      wf->ht_nm,
					      *from == '\0' ? "in ?" : from);
			return 0;
		}
		dcc_pemsg(EX_DATAERR, emsg, "%s entry (dis)appeared in %s%s",
			  dcc_type2str_err(type, 0, 0, 0),
			  wf->ht_nm,
			  *from == '\0' ? "in ?" : from);
		return -1;
	}

	/* ignore perfect duplicates */
	if (e->tgts == tgts)
		return 1;

	if (complain)
		dup_msg(emsg, wf, e->fno, e->lno, e->tgts, tgts);

	if (e->tgts != combine_white_tgts(tgts, e->tgts)) {
		e->tgts = tgts;
		e->lno = wf->lno;
		e->fno = wf->fno;
	}
	return 0;
}



static int			/* 1=ok,  0=bad entry, -1=fatal */
ck_dup_white_complain(DCC_EMSG emsg, DCC_WF *wf,
	     DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts)
{
	return ck_dup_white(emsg, wf, type, sum, tgts, 1);
}



/* Without brute force checks that would take too long, it is impossible to
 * check that a new CIDR block does not collide with individual entries that
 * are already in the hash table.  Without expanding a big CIDR block into
 * IP addresses and looking for each in the hash table, how can you check
 * whether it covers an entry for an individual address?  One way is to
 * parse the file twice and so check each individual IP address against
 * the CIDR blocks. */
static int				/* 1=ok,  0=bad entry, -1=fatal */
ck_dup_white_cidr(DCC_EMSG emsg UATTRIB, DCC_WF *wf,
		  int bits,
		  const struct in6_addr *addrp, const struct in6_addr *maskp,
		  DCC_TGTS tgts)
{
	DCC_WHITE_CIDR_ENTRY *e;
	struct in6_addr addr;
	DCC_SUM sum;
	int result, i, j;

	result = 1;

	/* Check for collisions between CIDR blocks and either other
	 * CIDR blocks or individual addresses.  Individual addresses
	 * are sent here as /128 blocks. */
	i = wf->wtbl->hdr.cidr.len;
	for (e = wf->wtbl->hdr.cidr.e; i > 0; ++e, --i) {
		if (!DCC_IN_BLOCK(e->addr, *addrp, *maskp)
		    && !DCC_IN_BLOCK(*addrp, e->addr, e->mask))
			continue;

		/* ignore simple duplicates */
		if (e->tgts == tgts)
			continue;

		dup_msg(emsg, wf, e->fno, e->lno, e->tgts, tgts);
		result = 0;

		/* fix direct collisions */
		if (e->bits == bits
		    && !memcmp(addrp, &e->addr, sizeof(*addrp))
		    && e->tgts != combine_white_tgts(tgts, e->tgts)) {
			e->tgts = tgts;
			e->lno = wf->lno;
			e->fno = wf->fno;
		}
	}

	/* check and fix collisions among individual entries */
	if (128-bits <= MAX_CIDR_BITS) {
		addr = *addrp;
		for (i = 1 << (128-bits); i > 0; --i) {
			dcc_ck_ipv6(sum, &addr);
			j = ck_dup_white(emsg, wf, DCC_CK_IP, sum, tgts,
					 result > 0);
			if (j < 0)
				return j;
			if (j == 0)
				result = j;
			addr.s6_addr32[3] = htonl(1+ntohl(addr.s6_addr32[3]));
		}
	}

	return result;
}



/* bail out on creating a whiteclnt hash table file */
static DCC_WHITE_RESULT
make_white_hash_bail(DCC_WHITE_RESULT result, DCC_WF *wf, DCC_WF *tmp_wf)
{
	if (tmp_wf->ht_nm[0] != '\0') {
		unlink_white_ht(tmp_wf, 0);
		tmp_wf->ht_nm[0] = '\0';
	}

	/* we have a usable file open */
	if (wf->wtbl)
		return DCC_WHITE_CONTINUE;
	return result;
}



/* (re)create the hash table file */
static DCC_WHITE_RESULT
make_white_hash(DCC_EMSG emsg, DCC_WF *wf, DCC_WF *tmp_wf)
{
	static const u_char zero = 0;
	int ascii_fd;
	struct stat ascii_sb;
	DCC_PATH path;
	DCC_WHITE_RESULT result;
	DCC_CK_TYPES type;
	int part_result, i;

	if (dcc_clnt_debug > 2)
		dcc_trace_msg("start parsing %s", wf->ascii_nm);

	/* Do not wait to create a new, locked hash table file if we have
	 *	a usable file.  Assume some other process is re-parsing
	 *	the ASCII file.
	 * If we should wait to create a new file, then we don't have a
	 *	usable hash table and so there is no reason to unlock the
	 *	DCC_WF structure. */

#ifdef DCC_DEBUG_CLNT_LOCK
	if (tmp_wf == &cmn_tmp_wf)
		assert_cwf_locked();
#endif

	dcc_wf_init(tmp_wf, wf->wf_flags);

	if (0 > stat(wf->ascii_nm, &ascii_sb)) {
		result = ((errno == ENOENT || errno == ENOTDIR)
			  ? DCC_WHITE_NOFILE
			  : DCC_WHITE_COMPLAIN);

		dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s",
			  wf->ascii_nm, ERROR_STR());

		/* Delete the hash table if the ASCII file has disappeared.
		 * stat_white_nms() delays forcing a re-build if the ASCII
		 * file disappears only temporarily */
		if (wf->ht_nm[0] != '\0') {
			close_white_ht(0, wf);
			i = unlink(wf->ht_nm);
			if (i < 0) {
				if (errno != ENOENT && errno != ENOTDIR)
					dcc_trace_msg("%s missing,:unlink(%s):"
						      " %s",
						      wf->ascii_nm,
						      fnm2abs_err(path,
							  wf->ht_nm),
						      ERROR_STR());
			} else if (dcc_clnt_debug > 1) {
				dcc_trace_msg("delete %s after %s missing",
					      fnm2abs_err(path, wf->ht_nm),
					      wf->ascii_nm);
			}
		}
		return make_white_hash_bail(result, wf, tmp_wf);
	}

	part_result = create_white_ht(emsg, tmp_wf, wf);
	if (part_result == 0) {
		/* The new hash table file is busy.
		 * Do not complain if we have a usable open hash table file */
		if (!dcc_clnt_debug && wf->wtbl)
			return DCC_WHITE_OK;
		/* at least ignore the need to reparse */
		return DCC_WHITE_CONTINUE;
	}
	if (part_result < 0)
		return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf);

	/* clear the new hash table */
	if (!write_white(emsg, tmp_wf, white_magic, sizeof(white_magic), 0)
	    || !write_white(emsg, tmp_wf, &zero, 1, EMPTY_WHITE_SIZE-1)
	    || !map_white_ht(emsg, tmp_wf, 0))
		return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf);
	if (tmp_wf->wf_flags & DCC_WF_PER_USER)
		tmp_wf->wtbl->hdr.flags = DCC_WHITE_FG_PER_USER;
	tmp_wf->wtbl_flags = tmp_wf->wtbl->hdr.flags;
	for (type = 0; type <= DCC_CK_TYPE_LAST; ++type)
		tmp_wf->wtbl->hdr.tholds_rej[type] = DCC_THOLD_UNSET;

	ascii_fd = dcc_lock_open(emsg, tmp_wf->ascii_nm, O_RDONLY,
				 DCC_LOCK_OPEN_NOWAIT | DCC_LOCK_OPEN_SHARE,
				 DCC_LOCK_ALL_FILE, 0);
	if (ascii_fd == -1)
		return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf);

	tmp_wf->wtbl->hdr.ascii_mtime = ascii_sb.st_mtime;
	part_result = dcc_parse_whitefile(emsg, tmp_wf, ascii_fd,
					  add_white, add_white_cidr);
	if (part_result > 0) {
		/* parse again to detect colliding definitions among
		 * host names and CIDR blocks */
		if (0 != lseek(ascii_fd, 0, SEEK_SET)) {
			dcc_pemsg(EX_IOERR, emsg, "lseek(%s, 0, SEEK_SET): %s",
				  wf->ascii_nm, ERROR_STR());
			part_result = -1;
		} else {
			part_result = dcc_parse_whitefile(emsg, tmp_wf,
							ascii_fd,
							ck_dup_white_complain,
							ck_dup_white_cidr);
		}
	}

	/* if the hash table is tolerable, compute a checksum of it
	 * to detect differing per-user hash tables */
	if (part_result >= 0
	    && map_white_ht(emsg, tmp_wf, tmp_wf->wtbl->hdr.entries)) {
		MD5_CTX ctx;

		MD5Init(&ctx);
		MD5Update(&ctx, (u_char *)&tmp_wf->wtbl->hdr.cidr,
			  sizeof(tmp_wf->wtbl->hdr.cidr));
		i = tmp_wf->wtbl->hdr.entries;
		while (--i >= 0) {
			MD5Update(&ctx, &tmp_wf->wtbl->tbl[i].type,
				  sizeof(tmp_wf->wtbl->tbl[i].type));
			MD5Update(&ctx, tmp_wf->wtbl->tbl[i].sum,
				  sizeof(tmp_wf->wtbl->tbl[i].sum));
		}
		MD5Final(tmp_wf->wtbl->hdr.ck_sum, &ctx);
	}
#ifdef DCC_WIN32
	/* unlock the file before closing it to keep Win95 happy */
	dcc_unlock_fd(0, ascii_fd, DCC_LOCK_ALL_FILE,
		      "whitelist ", tmp_wf->ascii_nm);
#endif
	if (close(ascii_fd) < 0)
		dcc_trace_msg("close(%s): %s", wf->ascii_nm, ERROR_STR());
	if (part_result < 0)
		return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf);
	result = (part_result > 0) ? DCC_WHITE_OK : DCC_WHITE_CONTINUE;

	/* ensure continued complaints about errors */
	if (result == DCC_WHITE_CONTINUE)
		tmp_wf->wtbl->hdr.reparse = time(0) + DCC_WHITE_REPARSE_DELAY;
	sync_white(tmp_wf);

#ifdef DCC_WIN32
	/* WIN32 prohibits renaming open files
	 * and there is little or no concurrency on WIN32 DCC clients
	 * So lock the original file and copy to it. */
	close_white_ht(0, wf);
	wf->ht_fd = dcc_lock_open(emsg, wf->ht_nm, O_RDWR | O_CREAT,
				  0, DCC_LOCK_ALL_FILE, 0);
	if (wf->ht_fd < 0)
		return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf);
	if (!map_white_ht(emsg, wf, tmp_wf->wtbl_entries))
		return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf);
	memcpy(wf->wtbl, tmp_wf->wtbl, tmp_wf->wtbl_size);
	close_white_ht(emsg, tmp_wf);
	unlink(tmp_wf->ht_nm);
#else
	part_result = rename(tmp_wf->ht_nm, wf->ht_nm);
	if (0 > part_result
	    && dcc_get_priv_home(wf->ht_nm)) {
		part_result = rename(tmp_wf->ht_nm, wf->ht_nm);
		dcc_rel_priv();
	}
	if (0 > part_result) {
		dcc_pemsg(EX_IOERR, emsg, "rename(%s, %s): %s",
			  tmp_wf->ht_nm, wf->ht_nm, ERROR_STR());
		return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf);
	}
#endif

	close_white_ht(0, tmp_wf);
	if (dcc_clnt_debug > 1)
		dcc_trace_msg("finished parsing %s", wf->ascii_nm);
	part_result = open_white_ht(emsg, wf);
	if (part_result < 0)
		result = DCC_WHITE_COMPLAIN;
	else if (part_result == 0 && result == DCC_WHITE_OK)
		result = DCC_WHITE_CONTINUE;

	wf->next_stat_time = time(0) + DCC_WHITE_STAT_DELAY;

	return result;
}



/* see that a local whitelist is ready
 *	on failure the file is not locked
 *	The caller must lock the DCC_WF if necessary */
DCC_WHITE_RESULT
dcc_rdy_white(DCC_EMSG emsg, DCC_WF *wf, DCC_WF *tmp_wf)
{
	time_t now;
	int i;

	if (wf->need_reopen) {
		/* The resolver thread has change the hash table in the file
		 * and possibly renamed it.
		 * We need to re-open the hash table */
		wf->broken = 0;
		wf->reparse = 0;
		close_white_ht(0, wf);
	}

	now = time(0);
	if (emsg)
		*emsg = '\0';

	if (wf->ht_fd >= 0) {
		/* The hash table is open.
		 * If we have checked recently, assume everything is good. */
		if (!DCC_IS_TIME(now, wf->next_stat_time, DCC_WHITE_STAT_DELAY))
			return DCC_WHITE_OK;
		i = stat_white_nms(emsg, wf);
		if (i > 0)
			return DCC_WHITE_OK;
		if (i < 0) {
			/* Things are broken or not open, so try to open the
			 * hash table.
			 * We may be racing here.  Be happy if another process
			 * fixes the hash table while we stall trying to open
			 * it locked. */
			i = open_white_ht(emsg, wf);
			if (i < 0)
				return DCC_WHITE_COMPLAIN;
			if (i > 0)
				i = stat_white_nms(emsg, wf);
		}

	} else {
		if (wf->ascii_nm[0] == '\0') {
			dcc_pemsg(EX_NOINPUT, emsg, "no whitelist");
			return DCC_WHITE_NOFILE;
		}

		if (STILL_BROKE(wf, now)) {
			/* only check for a missing file occassionally */
			if (wf->wf_flags & DCC_WF_NOFILE) {
				dcc_pemsg(EX_NOINPUT, emsg, "%s does not exist",
					  wf->ascii_nm);
				return DCC_WHITE_NOFILE;
			}

			/* If things are broken, and it has not been a while,
			 * then assume things are still broken. */
			dcc_pemsg(EX_DATAERR, emsg,
				  "%s still broken", wf->ascii_nm);
			return (dcc_clnt_debug > 2
				? DCC_WHITE_COMPLAIN
				: DCC_WHITE_SILENT);
		}

		i = open_white_ht(emsg, wf);
		if (i < 0)
			return DCC_WHITE_COMPLAIN;
		if (i > 0)
			i = stat_white_nms(emsg, wf);
	}

	if (i > 0)
		return DCC_WHITE_OK;

	/* Try to let the resolver thread wait for the DNS chitchat
	 * for host names that might now be in the ASCII file.
	 * To avoid racing with the resolver thread to delete the main
	 * hash files, fail if there is no hash table and this is not
	 * the resolver thread. */
	if (i == 0
	    && !(wf->wf_flags & DCC_WF_PER_USER)
	    && dcc_clnt_wake_resolve()) {
		if (wf->wtbl)
			return DCC_WHITE_OK;
		return (dcc_clnt_debug > 2
			? DCC_WHITE_COMPLAIN : DCC_WHITE_SILENT);
	}

	if (STILL_BROKE(wf, now)) {
		dcc_pemsg(EX_DATAERR, emsg, "%s still broken", wf->ascii_nm);
		if (i == 0 && wf->wtbl)
			return (dcc_clnt_debug > 2
				? DCC_WHITE_CONTINUE : DCC_WHITE_SILENT);
		return (dcc_clnt_debug > 2
			? DCC_WHITE_COMPLAIN : DCC_WHITE_SILENT);
	}

	if (emsg && *emsg != '\0') {
		if (i < 0) {
			dcc_error_msg("%s", emsg);
		} else if (dcc_clnt_debug > 2) {
			dcc_trace_msg("%s", emsg);
		}
		*emsg = '\0';
	}

	switch (make_white_hash(emsg, wf, tmp_wf)) {
	case DCC_WHITE_OK:
		wf->wf_flags &= ~DCC_WF_NOFILE;
		return DCC_WHITE_OK;	/* all is good */
	case DCC_WHITE_CONTINUE:	/* minor syntax error or bad hostname */
		wf->wf_flags &= ~DCC_WF_NOFILE;
		return DCC_WHITE_CONTINUE;
	case DCC_WHITE_SILENT:		/* no new message */
		wf->wf_flags &= ~DCC_WF_NOFILE;
		return DCC_WHITE_CONTINUE;
	case DCC_WHITE_NOFILE:
		wf->wf_flags |= DCC_WF_NOFILE;
		return DCC_WHITE_NOFILE;
	case DCC_WHITE_COMPLAIN:
	default:
		is_broken(wf);
		wf->wf_flags &= ~DCC_WF_NOFILE;
		return DCC_WHITE_COMPLAIN;
	}
}



static u_char
lookup_white(DCC_EMSG emsg,
	     DCC_TGTS *tgtsp,		/* value if hit else DCC_TGTS_INVALID */
	     DCC_WF *wf,
	     const DCC_GOT_CKS *cks, const DCC_GOT_SUM *g)
{
	const DCC_WHITE_ENTRY *e;
	const DCC_WHITE_CIDR_ENTRY *cidrp;
	int bits;

	e = find_white(emsg, wf, g->type, g->sum, 0);
	if (e == FIND_WHITE_BROKEN) {
		*tgtsp = DCC_TGTS_OK;
		return 0;
	}

	if (!e) {
		if (g->type != DCC_CK_IP) {
			*tgtsp = DCC_TGTS_INVALID;
			return 1;
		}

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

	*tgtsp = e->tgts;
	return 1;
}



/* check a local whitelist for a single checksum
 *	on exit the file is locked except after an error */
DCC_WHITE_RESULT
dcc_white_sum(DCC_EMSG emsg,
	      DCC_WF *wf,		/* in this whitelist */
	      DCC_CK_TYPES type, const DCC_SUM sum, /* look for this checksum */
	      DCC_TGTS *tgtsp,		/* set only if we find the checksum */
	      DCC_WHITE_LISTING *listingp)
{
	DCC_WHITE_ENTRY *e;
	DCC_WHITE_RESULT result;

	result = dcc_rdy_white(emsg, wf, &cmn_tmp_wf);
	switch (result) {
	case DCC_WHITE_OK:
	case DCC_WHITE_CONTINUE:
		break;
	case DCC_WHITE_SILENT:
	case DCC_WHITE_NOFILE:
	case DCC_WHITE_COMPLAIN:
		*listingp = DCC_WHITE_RESULT_FAILURE;
		return result;
	}

	e = find_white(emsg, wf, type, sum, 0);
	if (e == FIND_WHITE_BROKEN) {
		*listingp = DCC_WHITE_RESULT_FAILURE;
		return DCC_WHITE_COMPLAIN;
	}

	if (!e) {
		*listingp = DCC_WHITE_UNLISTED;
	} else if (e->tgts == DCC_TGTS_OK2
		   && type == DCC_CK_ENV_TO) {
		/* deprecated mechanism for turn DCC checks on and off for
		 * individual targets */
		*tgtsp = e->tgts;
		*listingp = DCC_WHITE_USE_DCC;
	} else if (e->tgts == DCC_TGTS_OK) {
		*tgtsp = e->tgts;
		*listingp = DCC_WHITE_LISTED;
	} else if (e->tgts == DCC_TGTS_TOO_MANY) {
		*tgtsp = e->tgts;
		*listingp = DCC_WHITE_BLACK;
	} else {
		*listingp = DCC_WHITE_UNLISTED;
	}

	return result;
}



/* see if an IP addess is that of one of our MX servers
 *	the caller must lock cmn_wf if necessray */
u_char					/* 0=problems */
dcc_white_mx(DCC_EMSG emsg,
	     DCC_TGTS *tgtsp,		/* !=0 if listed */
	     const DCC_GOT_CKS *cks)	/* this IP address checksum */
{
	u_char result;

	result = 1;
	switch (dcc_rdy_white(emsg, &cmn_wf, &cmn_tmp_wf)) {
	case DCC_WHITE_OK:
		break;
	case DCC_WHITE_CONTINUE:
		result = 0;
		break;
	case DCC_WHITE_NOFILE:
		*tgtsp = 0;
		return 1;
	case DCC_WHITE_SILENT:
	case DCC_WHITE_COMPLAIN:
		*tgtsp = 0;
		return 0;
	}

	if (cks->sums[DCC_CK_IP].type != DCC_CK_IP) {
		*tgtsp = 0;
		return result;
	}

	if (!lookup_white(emsg, tgtsp, &cmn_wf, cks, &cks->sums[DCC_CK_IP]))
		return 0;

	return result;
}



/* See what a local whitelist file says about the checksums for a message.
 *	The message 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.
 *	The caller must lock the DCC_WF if necessary. */
DCC_WHITE_RESULT			/* whether the lookup worked */
dcc_white_cks(DCC_EMSG emsg, DCC_WF *wf,
	      DCC_GOT_CKS *cks,		/* these checksums */
	      DCC_CKS_WTGTS wtgts,	/* whitelist targets, each cks->sums */
	      DCC_WHITE_LISTING *listingp)  /* the answer found */
{
	const DCC_GOT_SUM *g;
	int inx;
	DCC_TGTS tgts, prev_tgts;
	DCC_WHITE_RESULT result;

	result = dcc_rdy_white(emsg, wf, &cmn_tmp_wf);
	switch (result) {
	case DCC_WHITE_OK:
	case DCC_WHITE_CONTINUE:
		break;
	case DCC_WHITE_NOFILE:
	case DCC_WHITE_COMPLAIN:
	case DCC_WHITE_SILENT:
		*listingp = DCC_WHITE_RESULT_FAILURE;
		return result;
	}

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

		if (!lookup_white(emsg, &tgts, wf, cks, g)) {
			*listingp = DCC_WHITE_RESULT_FAILURE;
			return DCC_WHITE_COMPLAIN;
		}
		if (tgts == DCC_TGTS_INVALID) {
			/* report any body checksums as spam for a spam trap */
			if ((wf->wtbl_flags & DCC_WHITE_FG_TRAPS)
			    && DCC_CK_IS_BODY(g->type))
				tgts = DCC_TGTS_TOO_MANY;
			else
				continue;
		}

		if (wtgts)
			wtgts[inx] = tgts;

		if (tgts == DCC_TGTS_OK) {
			/* found the checksum in our whitelist,
			 * so we have the answer */
			*listingp = DCC_WHITE_LISTED;

		} else if (tgts == DCC_TGTS_OK2) {
			if (prev_tgts == DCC_TGTS_OK2) {
				/* two half-white checksums count the same
				 * as a single pure white checksum
				 * and gives the answer */
				*listingp = DCC_WHITE_LISTED;
				continue;
			}
			prev_tgts = DCC_TGTS_OK2;

		} else if (tgts == DCC_TGTS_TOO_MANY) {
			if (*listingp == DCC_WHITE_UNLISTED)
				*listingp = DCC_WHITE_BLACK;
		}
	}

	return result;
}