/* 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;
}
