diff dcclib/ckwhite.c @ 0:c7f6b056b673

First import of vendor version
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 13:49:58 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dcclib/ckwhite.c	Tue Mar 10 13:49:58 2009 +0100
@@ -0,0 +1,1608 @@
+/* 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;
+}