diff dccd/dccd.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/dccd/dccd.c	Tue Mar 10 13:49:58 2009 +0100
@@ -0,0 +1,2263 @@
+/* Distributed Checksum Clearinghouse
+ *
+ * server
+ *
+ * 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.297 $Revision$
+ */
+
+#include "dccd_defs.h"
+#include <signal.h>			/* for Linux and SunOS */
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include "dcc_ifaddrs.h"
+
+DCC_EMSG dcc_emsg;
+
+static const char *homedir;
+static char *aargs[10];
+static char **aarg = &aargs[0];
+static DCC_PATH dbclean_def = DCC_LIBEXECDIR"/dbclean";
+static char *dbclean = dbclean_def;
+static pid_t dbclean_pid = -1;
+static char *addr_str;			/* an IP address for dbclean */
+static char dbclean_str[] = "dbclean";
+#define MAX_DBCLEAN_FLAGS 50
+static char dbclean_flags[MAX_DBCLEAN_FLAGS+1] = "-Pq";
+static int dbclean_flags_len = LITZ("-Pq");
+static const char *dbclean_argv[20] = {dbclean_str, dbclean_flags};
+static int dbclean_argc = 2;
+static char dbclean_args_str[200];
+static int dbclean_args_str_len;
+
+/* do not try to run dbclean too often */
+time_t dbclean_limit_secs = DBCLEAN_LIMIT_SECS;
+static time_t dbclean_failed;
+
+static double wfix_quiet_rate = 1.0;	/* max load that allows window fixing */
+static double wfix_busy_rate;		/* measured active rate */
+static double wfix_rate_change = 0.4;	/* rate reduction to this allows fix */
+static DB_PTR wfix_size;
+static u_char wfix_size_set = 0;
+DBCLEAN_WFIX_STATE dbclean_wfix_state = WFIX_DELAY;
+static double wfix_ops;
+static time_t wfix_check_start;
+static time_t dbclean_wfix_secs;	/* window fixing timer */
+#define WFIX_POST_CLEAN_SECS (6*60*60)	/* do not fix it soon after cleaning */
+#define WFIX_PRE_CLEAN_SECS (2*60*60)	/*	or just before cleaning */
+#define WFIX_RECHECK_SECS   (15*60)	/* between checks for window overflow */
+#define WFIX_QUIET_SECS	    (5*60)	/* waiting for clients to flee */
+#define	WFIX_MEASURE_SECS   (5*60)	/* counting clients */
+#define WFIX_MAX_SECS	(WFIX_PRE_CLEAN_SECS+WFIX_POST_CLEAN_SECS)
+
+const char *need_del_dbclean;
+time_t del_dbclean_next;
+time_t dbclean_limit;
+static time_t clean_fake_secs;		/* timer for missing cron job */
+static time_t clean_last_secs;
+
+u_long dccd_tracemask = DCC_TRACE_ON_DEF_BITS;
+
+u_char background = 1;
+int stopint;				/* !=0 if stopping or received signal */
+
+static time_t flods_ck_secs = FLODS_CK_SECS;
+
+const char *brand = "";
+
+static char *my_srvr_id_str;
+DCC_SRVR_ID my_srvr_id;
+
+u_char use_ipv6 = 0;
+u_int16_t def_port;			/* default port #, network byte order */
+typedef struct port_list {
+    struct port_list *fwd;
+    u_int16_t	port;			/* network byte order */
+} PORT_LIST;
+static PORT_LIST *ports;
+SRVR_SOC *srvr_socs;
+static SRVR_SOC **srvr_socs_end = &srvr_socs;
+int srvr_rcvbuf = 1024*1024;
+
+#ifdef USE_DBCLEAN_F
+static u_char db_mode = DB_OPEN_MMAP_WRITE;
+#else
+static u_char db_mode = 0;
+#endif
+
+int grey_embargo = DEF_GREY_EMBARGO;	/* seconds to delay new traffic */
+int grey_window = DEF_GREY_WINDOW;	/* wait as long as this */
+int grey_white = DEF_GREY_WHITE;	/* remember this long */
+
+char our_hostname[MAXHOSTNAMELEN];
+DCC_SUM host_id_sum;			/* advertised with our server-ID */
+time_t host_id_next, host_id_last;	/* when to advertise */
+
+u_char anon_off;			/* turn off anonymous access */
+time_t anon_delay_us = DCC_ANON_DELAY_US_DEF;   /* delay anonymous clients */
+u_int anon_delay_inflate = DCC_ANON_INFLATE_OFF;
+
+u_char stop_mode;			/* 0=normal 1=reboot 2=with/DB clean */
+
+static int total_ops;
+static QUEUE *queue_free;
+static QUEUE *queue_head;
+
+/* assume or hope we can handle 500 requests/second */
+int queue_max = 500*DCC_MAX_RTT_SECS;	/* ultimate bound on queue size */
+static int queue_max_cur;		/* current upper bound */
+static int queue_cur;			/* current queue size */
+
+struct timeval wake_time;		/* when we awoke from select() */
+struct timeval req_recv_time;		/* when request arrived */
+
+DCC_TS future;				/* timestamp sanity */
+
+
+static DB_NOKEEP_CKS set_new_nokeep_cks, reset_new_nokeep_cks;
+
+DCC_TGTS flod_tholds[DCC_DIM_CKS];	/* flood at or after these thresholds */
+
+static const char *parse_rl_rate(RL_RATE *, float, const char *, const char *);
+static void add_dbclean_flag(char);
+static void add_dbclean_arg(const char *);
+static void check_dbclean(int);
+static void run_dbclean(const char *, const char *);
+static void sigterm(int);
+static void sighup(int);
+static void stop_children(void);
+static u_char get_if_changes(u_char);
+static SRVR_SOC *add_srvr_soc(u_char, int, const void *, u_int16_t);
+static int open_srvr_socs(int);
+static void wfix_later(time_t);
+static void recv_job(void) NRATTRIB;
+static u_char new_job(SRVR_SOC *);
+static void NRATTRIB dccd_quit(int, const char *, ...) PATTRIB(2,3);
+
+static void
+usage(const char* barg)
+{
+	static const char str[] = {
+	    "usage: [-64dVbfFQ] -i server-ID [-n brand] [-h homedir]\n"
+	    "   [-I [host-ID][,user]] [-a [server-addr][,server-port]]"
+	    " [-q qsize]\n"
+	    "   [-G [on,][weak-body,][weak-IP,]embargo],[window],[white]]\n"
+	    "   [-W [rate][,chg][,dbsize]] [-K [no-]type] [-T tracemode]\n"
+	    "   [-u anon-delay[,inflate] [-C dbclean]"
+	    " [-L ltype,facility.level]\n"
+	    "   [-R [RL_SUB],[RL_FREE],[RL_ALL_FREE],[RL_BUGS]]"
+	};
+	static u_char complained;
+
+	if (!complained) {
+		if (barg)
+			dcc_error_msg("unrecognized \"%s\"\nusage: %s\n..."
+				      " continuing",
+				      barg, str);
+		else
+			dcc_error_msg("%s\n... continuing", str);
+		complained = 1;
+	}
+}
+
+
+int NRATTRIB
+main(int argc, char **argv)
+{
+	char *p;
+	const char *rest;
+	u_char print_version = 0;
+	DCC_CK_TYPES type;
+	DCC_SOCKU *sup;
+	u_int16_t port;
+	int new_embargo, new_window, new_white;
+	int error, i;
+	const char *cp;
+	double d1, d2;
+	u_long l;
+
+	dcc_syslog_init(1, argv[0], 0);
+
+	if (DCC_DIM_CKS != DCC_COMP_DIM_CKS)
+		dcc_logbad(EX_SOFTWARE,
+			   "DCC_DIM_CKS != DCC_COMP_DIM_CKS;"
+			   " check uses of both");
+
+	/* get first bytes of our hostname to name our server-ID */
+	memset(our_hostname, 0, sizeof(our_hostname));
+	if (0 > gethostname(our_hostname, sizeof(our_hostname)-1))
+		dcc_logbad(EX_NOHOST, "gethostname(): %s", ERROR_STR());
+	our_hostname[sizeof(our_hostname)-1] = '\0';
+	if (our_hostname[0] == '\0')
+		dcc_logbad(EX_NOHOST, "null hostname from gethostname()");
+	strncpy((char *)host_id_sum, our_hostname, sizeof(host_id_sum));
+
+	parse_rl_rate(&rl_sub_rate, 0.5, "RL_SUB", "400");
+	parse_rl_rate(&rl_anon_rate, RL_AVG_SECS, "RL_ANON", "50");
+	parse_rl_rate(&rl_all_anon_rate, 0.5, "RL_ALL_ANON", "600");
+	parse_rl_rate(&rl_bugs_rate, RL_AVG_SECS, "RL_BUGS", "0.1");
+
+	/* this must match DCCD_GETOPTS in cron-dccd.in */
+	while ((i = getopt(argc, argv,
+			   "64dVbfFQi:n:h:a:I:q:G:t:W:K:T:u:C:L:R:")) != -1) {
+		switch (i) {
+		case '6':
+#ifndef NO_IPV6
+			use_ipv6 = 2;
+#endif
+			break;
+		case '4':
+			use_ipv6 = 0;
+			break;
+
+		case 'd':
+			add_dbclean_flag('d');
+			++db_debug;
+			break;
+
+		case 'V':
+			fprintf(stderr, DCC_VERSION"\n");
+			print_version = 1;
+			break;
+
+		case 'b':
+			background = 0;
+			break;
+
+		case 'f':
+			db_mode &= ~DB_OPEN_MMAP_WRITE;
+			add_dbclean_flag('f');
+			break;
+
+		case 'F':
+			db_mode |= DB_OPEN_MMAP_WRITE;
+			add_dbclean_flag('F');
+			break;
+
+		case 'Q':
+			query_only = 1;
+			break;
+
+		case 'i':
+			my_srvr_id_str = optarg;
+			if (!dcc_get_srvr_id(dcc_emsg, &my_srvr_id,
+					     my_srvr_id_str, 0, 0, 0))
+				dcc_logbad(dcc_ex_code, "%s", dcc_emsg);
+			add_dbclean_arg("-i");
+			add_dbclean_arg(my_srvr_id_str);
+			break;
+
+		case 'n':
+			/* RFC 2822 says "values between 33 and 126" */
+			cp = optarg;
+			while (*cp >= 33 && *cp <= 126 && *cp != ':')
+				++cp;
+			if (cp == optarg
+			    || (cp - optarg) > ISZ(DCC_BRAND)
+			    || *cp != '\0') {
+				dcc_error_msg("invalid brand name \"-n %s\"",
+					      optarg);
+			} else {
+				brand = optarg;
+			}
+			break;
+
+		case 'h':
+			homedir = optarg;
+			/* tell dbclean "-h ." because we will already
+			 * be there and that allows our -h to be relative */
+			add_dbclean_arg("-h.");
+			break;
+
+		case 'I':
+			p = strchr(optarg, ',');
+			if (p) {
+				*p++ = '\0';
+				dcc_daemon_su(p);
+				if (*optarg == '\0')
+					break;
+			}
+			if (*optarg != '\0')
+				strncpy((char *)host_id_sum, optarg,
+					sizeof(host_id_sum));
+			break;
+
+		case 'a':
+			/* postpone checking host names until we know -6 */
+			if (aarg > LAST(aargs)) {
+				dcc_error_msg("too many -a args");
+				break;
+			}
+			optarg += strspn(optarg, DCC_WHITESPACE);
+			*aarg++ = optarg;
+			break;
+
+		case 'q':
+			l = strtoul(optarg, &p, 10);
+			if (*p != '\0' || l < 2 || l > 1000) {
+				dcc_error_msg("invalid queue length \"%s\"",
+					      optarg);
+			} else {
+				queue_max = l;
+			}
+			break;
+
+		case 'G':
+			grey_on = 1;
+			dcc_syslog_init(1, argv[0], " grey");
+			add_dbclean_arg("-Gon");
+			/* handle leading "on" "weak-body", and "weak-IP" */
+			rest = optarg;
+			while (*rest) {
+				if (dcc_ck_word_comma(&rest, "weak-body")
+				    || dcc_ck_word_comma(&rest, "weak_body")
+				    || dcc_ck_word_comma(&rest, "weak")) {
+					grey_weak_body = 1;
+					continue;
+				}
+				if (dcc_ck_word_comma(&rest, "weak-IP")
+				    || dcc_ck_word_comma(&rest, "weak_IP")) {
+					grey_weak_ip = 1;
+					continue;
+				}
+				if (!dcc_ck_word_comma(&rest, "on"))
+					break;
+			}
+			new_embargo = dcc_get_secs(rest, &rest,
+						   0, MAX_GREY_EMBARGO,
+						   grey_embargo);
+			if (new_embargo < 0) {
+				dcc_error_msg("invalid greylist embargo"
+					      " \"-G %s\"", optarg);
+				break;
+			}
+			new_window = dcc_get_secs(rest, &rest,
+						  new_embargo, MAX_GREY_WINDOW,
+						  max(new_embargo,grey_window));
+			if (new_window < 0) {
+				dcc_error_msg("invalid greylist wait time"
+					      " \"-G %s\"", optarg);
+				break;
+			}
+			new_white = dcc_get_secs(rest, &rest,
+						 new_window, MAX_GREY_WHITE,
+						 max(new_window, grey_white));
+			if (new_white < 0 || *rest != '\0') {
+				dcc_error_msg("invalid greylist whitelist time"
+					      " \"-G %s\"", optarg);
+				break;
+			}
+			grey_embargo = new_embargo;
+			grey_window = new_window;
+			grey_white = new_white;
+			break;
+
+		case 't':		/* obsolete */
+			break;
+
+		case 'W':
+			p = optarg;
+			if (*p == '\0') {
+				dcc_error_msg("unrecognized"
+					      " \"-W %s\"", optarg);
+				break;
+			}
+			d1 = wfix_quiet_rate;
+			if (*p != '\0' && *p != ',') {
+				d1 = strtod(p, &p);
+				if ((*p != '\0' && *p != ',')
+				    || d1 < 0.0 || d1 > 1000*1000.0) {
+					dcc_error_msg("bad quiet rate in"
+						      " \"-W %s\"", optarg);
+					break;
+				}
+			}
+			if (*p == ',')
+				++p;
+			d2 = wfix_rate_change;
+			if (*p != '\0' && *p != ',') {
+				d1 = strtod(p, &p);
+				if ((*p != '\0' && *p != ',')
+				    || d1 < 0.0 || d1 > 1000*1000.0) {
+					dcc_error_msg("bad rate change in"
+						      " \"-W %s\"", optarg);
+					break;
+				}
+			}
+			if (*p == ',')
+				++p;
+			l = wfix_size/(1024*1024);
+			if (*p != '\0') {
+				l = strtoul(p, &p, 10);
+				if ((*p != '\0' || l < DB_MIN_MIN_MBYTE
+				     || l > MAX_MAX_DB_MBYTE)
+				    && l != wfix_size) {
+					dcc_error_msg("bad database size in"
+						      " \"-W %s\"", optarg);
+					break;
+				}
+			}
+			wfix_quiet_rate = d1;
+			wfix_rate_change = d2;
+			if (wfix_size/(1024*1024) != l) {
+				wfix_size = ((DB_PTR)l)*(1024*1024);
+				wfix_size_set = 1;
+			}
+			break;
+
+		case 'K':
+			if (!strcasecmp(optarg, "all")) {
+				reset_new_nokeep_cks = -1;
+				break;
+			}
+			if (!CLITCMP(optarg, "no-")) {
+				optarg += LITZ("no-");
+				i = 0;
+			} else {
+				i = 1;
+			}
+			type = dcc_str2type_db(optarg, -1);
+			if (type == DCC_CK_INVALID) {
+				dcc_error_msg("bad checksum type in"
+					      " \"-K %s\"", optarg);
+				break;
+			}
+			if (i)
+				DB_SET_NOKEEP(reset_new_nokeep_cks, type);
+			else
+				DB_SET_NOKEEP(set_new_nokeep_cks, type);
+			break;
+
+		case 'T':
+			if (!strcasecmp(optarg, "ADMN")) {
+				dccd_tracemask |= DCC_TRACE_ADMN_BIT;
+			} else if (!strcasecmp(optarg, "ANON")) {
+				dccd_tracemask |= DCC_TRACE_ANON_BIT;
+			} else if (!strcasecmp(optarg, "CLNT")) {
+				dccd_tracemask |= DCC_TRACE_CLNT_BIT;
+			} else if (!strcasecmp(optarg, "RLIM")) {
+				dccd_tracemask |= DCC_TRACE_RLIM_BIT;
+			} else if (!strcasecmp(optarg, "QUERY")) {
+				dccd_tracemask |= DCC_TRACE_QUERY_BIT;
+			} else if (!strcasecmp(optarg, "RIDC")) {
+				dccd_tracemask |= DCC_TRACE_RIDC_BIT;
+			} else if (!strcasecmp(optarg, "FLOOD")) {
+				dccd_tracemask |= DCC_TRACE_FLOD_BIT;
+			} else if (!strcasecmp(optarg, "FLOOD2")) {
+				dccd_tracemask |= DCC_TRACE_FLOD2_BIT;
+			} else if (!strcasecmp(optarg, "IDS")) {
+				dccd_tracemask |= DCC_TRACE_IDS_BIT;
+			} else if (!strcasecmp(optarg, "BL")) {
+				dccd_tracemask |= DCC_TRACE_BL_BIT;
+			} else if (!strcasecmp(optarg, "DB")) {
+				dccd_tracemask |= DCC_TRACE_DB_BIT;
+			} else if (!strcasecmp(optarg, "WLIST")) {
+				dccd_tracemask |= DCC_TRACE_WLIST_BIT;
+			} else {
+				dcc_error_msg("invalid trace mode \"%s\"",
+					      optarg);
+			}
+			break;
+
+		case 'u':
+			i = parse_dccd_delay(dcc_emsg, &anon_delay_us,
+					     &anon_delay_inflate, optarg,
+					     0, 0);
+			if (!i) {
+				dcc_error_msg("%s", dcc_emsg);
+			} else if (i == 2) {
+				anon_off = 1;
+			} else {
+				anon_off = 0;
+			}
+			break;
+
+		case 'C':
+			if (*optarg == '\0') {
+				dcc_error_msg("no path to dbclean \"-C %s\"",
+					      optarg);
+				break;
+			}
+			/* capture the path to the dbclean program */
+			dbclean = optarg;
+			/* capture any args following the program */
+			for (p = strpbrk(optarg, DCC_WHITESPACE);
+			     p != 0;
+			     p = strpbrk(p, DCC_WHITESPACE)) {
+				*p++ = '\0';
+				p += strspn(p, DCC_WHITESPACE);
+				if (*p == '\0')
+					break;
+				add_dbclean_arg(p);
+			}
+			break;
+
+		case 'L':
+			if (dcc_parse_log_opt(optarg)) {
+				add_dbclean_arg("-L");
+				add_dbclean_arg(optarg);
+			}
+			break;
+
+		case 'R':
+			rest = parse_rl_rate(&rl_sub_rate, -1.0,
+					     "RL_SUB", optarg);
+			rest = parse_rl_rate(&rl_anon_rate, -1.0,
+					     "RL_ANON", rest);
+			rest = parse_rl_rate(&rl_all_anon_rate, -1.0,
+					     "RL_ALL_ANON", rest);
+			rest = parse_rl_rate(&rl_bugs_rate, -1.0,
+					     "RL_BUGS", rest);
+			break;
+
+		default:
+			usage(optopt2str(optopt));
+		}
+	}
+	argc -= optind;
+	argv += optind;
+	if (argc != 0)
+		usage(argv[0]);
+
+	if (my_srvr_id == DCC_ID_INVALID) {
+		if (print_version)
+			exit(EX_OK);
+		dcc_logbad(EX_USAGE, "server-ID not set with -i");
+	}
+
+	if (grey_on) {
+		anon_off = 1;
+		dccd_tracemask |= DCC_TRACE_IDS_BIT;
+	}
+
+	/* parse addresses after we know whether -6 was among the args */
+	for (aarg = &aargs[0]; aarg <= LAST(aargs) && *aarg; ++aarg) {
+		char hostname[DCC_MAXDOMAINLEN];
+
+		addr_str = *aarg;
+		rest = dcc_parse_nm_port(dcc_emsg, *aarg, 0,
+					 hostname, sizeof(hostname),
+					 &port, 0, 0, 0, 0);
+		if (!rest) {
+			dcc_error_msg("%s", dcc_emsg);
+			continue;
+		}
+		rest += strspn(rest, DCC_WHITESPACE);
+		if (*rest != '\0')
+			dcc_error_msg("unrecognized port number in"
+				      "\"-a %s\"", *aarg);
+		if (hostname[0] == '\0') {
+			PORT_LIST *pport = dcc_malloc(sizeof(*pport));
+			pport->port = port;
+			pport->fwd = ports;
+			ports = pport;
+			continue;
+		}
+		if (!strcmp(hostname, "@")) {
+			++addr_str;	/* "" but not a const */
+			add_srvr_soc(SRVR_SOC_ADDR, AF_UNSPEC, 0, port);
+			continue;
+		}
+		dcc_host_lock();
+		if (!dcc_get_host(hostname, use_ipv6 ? 2 : 0, &error)) {
+			dcc_host_unlock();
+			dcc_error_msg("%s: %s",
+				      *aarg, DCC_HSTRERROR(error));
+			continue;
+		}
+		for (sup = dcc_hostaddrs; sup < dcc_hostaddrs_end; ++sup) {
+			if (sup->sa.sa_family == AF_INET)
+				add_srvr_soc(SRVR_SOC_ADDR, AF_INET,
+					     &sup->ipv4.sin_addr, port);
+			else
+				add_srvr_soc(SRVR_SOC_ADDR, AF_INET6,
+					     &sup->ipv6.sin6_addr, port);
+		}
+		dcc_host_unlock();
+	}
+	if (addr_str) {
+		/* tell dbclean about one "-a addr" */
+		add_dbclean_arg("-a");
+		add_dbclean_arg(addr_str);
+	}
+
+	dcc_clnt_unthread_init();
+	if (!dcc_cdhome(dcc_emsg, homedir, 0))
+		dcc_logbad(dcc_ex_code, "%s", dcc_emsg);
+
+	i = check_load_ids(1);
+	if (i < 0)
+		dcc_logbad(dcc_ex_code, "%s", dcc_emsg);
+	else if (!i)
+		dcc_error_msg("%s", dcc_emsg);
+
+	if (!def_port)
+		def_port = DCC_GREY2PORT(grey_on);
+	if (!srvr_socs && !ports) {
+		ports = dcc_malloc(sizeof(*ports));
+		ports->fwd = 0;
+		ports->port = def_port;
+	}
+	get_if_changes(db_debug != 0);
+
+	/* make initial attempt to open the server UDP sockets
+	 * This also sets use_ipv6 to 0 or 1 if it is still 2 */
+	if (open_srvr_socs(45) <= 0)
+		dcc_logbad(EX_OSERR, "failed to open any server sockets");
+
+	add_dbclean_flag(use_ipv6 == 0 ? '4' : '6');
+
+	if (background) {
+		if (0 > daemon(1, 0))
+			dcc_logbad(EX_OSERR, "daemon(): %s", ERROR_STR());
+	}
+
+	if (!background)
+		signal(SIGHUP, sigterm);    /* SIGHUP fatal during debugging */
+	else
+		signal(SIGHUP, sighup);	/* speed configuration check */
+	signal(SIGTERM, sigterm);
+	signal(SIGINT, sigterm);
+	signal(SIGPIPE, SIG_IGN);
+#ifdef SIGXFSZ
+	signal(SIGXFSZ, SIG_IGN);
+#endif
+
+	atexit(stop_children);
+
+	gettimeofday(&db_time, 0);
+	wake_time = db_time;
+
+	flod_mmap_path_set();
+
+	/* open the database, and try once to fix it */
+	if (!dccd_db_open(DB_OPEN_LOCK_NOWAIT)) {
+		dcc_error_msg("%s", dcc_emsg);
+		run_dbclean("SRbad", "database initially broken");
+		check_dbclean(0);	/* stall until dbclean finishes */
+		if (!dccd_db_open(DB_OPEN_LOCK_NOWAIT)) {
+			dcc_error_msg("%s", dcc_emsg);
+			dcc_logbad(EX_NOINPUT,
+				   "could not start database %s", db_nm);
+		}
+	}
+	/* do not start if dbclean is running */
+	if (!lock_dbclean(dcc_emsg, db_nm))
+		dcc_logbad(dcc_ex_code, "%s: dbclean already running?",
+			   dcc_emsg);
+	unlock_dbclean();
+
+	flod_trace_gen = db_time.tv_sec;
+	host_id_next = db_time.tv_sec + DCC_SRVR_ID_SECS_ST;
+	check_blacklist_file();
+	flods_init();
+	clients_load();
+	stats_clear();
+	if (flod_mmaps != 0
+	    && flod_mmaps->dccd_stats.reset.tv_sec != 0) {
+		memcpy(&dccd_stats, &flod_mmaps->dccd_stats,
+		       sizeof(dccd_stats));
+	}
+	dcc_trace_msg(DCC_VERSION" listening to port %d  %s  %s",
+		      ntohs(srvr_socs->arg_port),
+		      dcc_homedir, db_window_size_str);
+
+	recv_job();
+}
+
+
+
+static SRVR_SOC *			/* 0 or new entry */
+add_srvr_soc(u_char flags,
+	     int family,		/* AF_UNSPEC or 0 if addrp==0 */
+	     const void *addrp,		/* 0, *in_addr, or *in6_addr */
+	     u_int16_t port)
+{
+	DCC_SOCKU su;
+	SRVR_SOC *sp;
+
+	dcc_mk_su(&su, family, addrp, port);
+	for (sp = srvr_socs; sp; sp = sp->fwd) {
+		if (sp->arg_family == family
+		    && sp->arg_port == port
+		    && !memcmp(&sp->arg_addr, addrp,
+			       ((family == AF_INET)
+				? sizeof(sp->arg_addr.in4)
+				: sizeof(sp->arg_addr.in6))))
+			return sp;
+	}
+
+	sp = dcc_malloc(sizeof(*sp));
+	memset(sp, 0, sizeof(*sp));
+	sp->flags = flags;
+	sp->udp = -1;
+	sp->listen = -1;
+	sp->su = su;
+	sp->arg_family = family;
+	if (addrp)
+		memcpy(&sp->arg_addr, addrp,
+		       ((family == AF_INET)
+			? sizeof(sp->arg_addr.in4)
+			: sizeof(sp->arg_addr.in6)));
+	sp->arg_port = port;
+
+	*srvr_socs_end = sp;
+	srvr_socs_end = &sp->fwd;
+
+	return sp;
+}
+
+
+
+static int				/* # of sockets opened */
+open_srvr_socs(int retry_secs)
+{
+	static u_char srvr_rcvbuf_set = 0;
+	int *retry_secsp;
+	u_char family;
+	SRVR_SOC *sp;
+	int i;
+	int num_socs = 0;
+
+	if (stopint)
+		return -1;
+
+	retry_secsp = retry_secs ? &retry_secs : 0;
+
+	for (sp = srvr_socs; sp; sp = sp->fwd) {
+		if (sp->udp >= 0) {
+			++num_socs;
+			continue;
+		}
+
+		/* resolve default port number if we finally know it */
+		if (!sp->arg_port)
+			sp->arg_port = def_port;
+
+		family = sp->arg_family;
+
+		/* create the UDP socket
+		 * If we are using INADDR_ANY
+		 * and do not yet know if IPv6 works, just try it */
+		if (family == AF_UNSPEC
+		    && use_ipv6 == 2) {
+			dcc_mk_su(&sp->su, AF_INET6,
+				  &sp->arg_addr, sp->arg_port);
+			i = dcc_udp_bind(dcc_emsg, &sp->udp,
+					 &sp->su, retry_secsp);
+			if (i == 0) {
+				dcc_error_msg("%s", dcc_emsg);
+				/* still don't know about IPv6 */
+				continue;
+			}
+			if (i > 0) {
+				/* we finished an INADDR_ANY socket
+				 * and learned that IPv6 works */
+				use_ipv6 = 1;
+				continue;
+			}
+			/* we know or guess that IPv6 does not work */
+			use_ipv6 = 0;
+		}
+
+		if (family == AF_UNSPEC) {
+			/* using INADDR_ANY but now know whether IPv6 works */
+			family = use_ipv6 ? AF_INET6 : AF_INET;
+		} else if (family == AF_INET6 && use_ipv6 == 2) {
+			/* don't know if IPv6 works but have an IPv6 address */
+			use_ipv6 = 1;
+		}
+
+		dcc_mk_su(&sp->su, family, &sp->arg_addr, sp->arg_port);
+		if (0 >= dcc_udp_bind(dcc_emsg, &sp->udp,
+				      &sp->su, retry_secsp)) {
+			dcc_error_msg("%s", dcc_emsg);
+			continue;
+		}
+
+		/* set socket receive buffer size as large as possible */
+		for (;;) {
+			if (!setsockopt(sp->udp, SOL_SOCKET, SO_RCVBUF,
+					&srvr_rcvbuf, sizeof(srvr_rcvbuf)))
+				break;
+			if (srvr_rcvbuf_set || srvr_rcvbuf <= 4096) {
+				dcc_error_msg("setsockopt(%s,SO_RCVBUF=%d): %s",
+					      dcc_su2str_err(&sp->su),
+					      srvr_rcvbuf,
+					      ERROR_STR());
+				break;
+			}
+			srvr_rcvbuf -= 4096;
+		}
+		srvr_rcvbuf_set = 1;
+
+		++num_socs;
+	}
+
+	/* Finally decide the IPv6 issue if we found no sign of IPv6 */
+	if (use_ipv6 == 2)
+		use_ipv6 = 0;
+
+	return num_socs;
+}
+
+
+
+/* get ready to bind to all local IP addreses */
+static u_char				/* 1=added an interface */
+add_ifs(u_char not_quiet)
+{
+	u_char added;
+	SRVR_SOC *sp;
+	PORT_LIST *pport;
+#ifdef HAVE_GETIFADDRS
+	struct ifaddrs *ifap0, *ifap;
+	int num_ifs;
+#endif
+
+	if (!ports)
+		return 0;
+
+	added = 0;
+
+#ifdef HAVE_GETIFADDRS
+	if (0 > getifaddrs(&ifap0)) {
+		dcc_error_msg("getifaddrs(): %s", ERROR_STR());
+		ifap0 = 0;
+	}
+
+	num_ifs = 0;
+	for (pport = ports; pport; pport = pport->fwd) {
+		const SRVR_SOC *listener = 0;
+
+		for (ifap = ifap0; ifap; ifap = ifap->ifa_next) {
+			if (!(ifap->ifa_flags & IFF_UP))
+				continue;
+			if (!ifap->ifa_addr)
+				continue;
+			switch (ifap->ifa_addr->sa_family) {
+			case AF_INET:
+				++num_ifs;
+				sp = add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_NEW,
+						  ifap->ifa_addr->sa_family,
+						  &((struct sockaddr_in *)ifap
+						    ->ifa_addr)->sin_addr,
+						  pport->port);
+				break;
+			case AF_INET6:
+				if (use_ipv6 == 0)
+					continue;
+				if (use_ipv6 == 2)
+					use_ipv6 = 1;
+				++num_ifs;
+				sp = add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_NEW,
+						  ifap->ifa_addr->sa_family,
+						  &((struct sockaddr_in6*)ifap
+						    ->ifa_addr)->sin6_addr,
+						  pport->port);
+				break;
+			default:
+				continue;
+			}
+			if (sp->flags & SRVR_SOC_NEW) {
+				added = 1;
+				if (not_quiet)
+					dcc_trace_msg("start listening on %s",
+						      dcc_su2str_err(&sp->su));
+			}
+			sp->flags &= ~(SRVR_SOC_MARK | SRVR_SOC_NEW);
+
+			/* interfaces can have duplicate addresses */
+			if (listener == sp)
+				continue;
+			if (!listener) {
+				listener = sp;
+				if (!(sp->flags & SRVR_SOC_LISTEN)) {
+					sp->flags |= SRVR_SOC_LISTEN;
+					added = 1;
+				}
+			} else {
+				if (sp->flags & SRVR_SOC_LISTEN) {
+					sp->flags &= ~SRVR_SOC_LISTEN;
+					added = 1;
+				}
+			}
+		}
+	}
+#ifdef HAVE_FREEIFADDRS
+	/* since this is done only a few times when HAVE_FREEIFADDRS is not
+	 * defined, don't worry if we cannot release the list of interfaces */
+	freeifaddrs(ifap0);
+#endif
+
+	if (num_ifs > 0)
+		return added;
+#endif /* HAVE_GETIFADDRS */
+
+	/* if we got no joy from getifaddrs(), use INADDR_ANY */
+	for (pport = ports; pport; pport = pport->fwd) {
+		sp = add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_LISTEN | SRVR_SOC_NEW,
+				  AF_UNSPEC, 0, pport->port);
+		if (sp->flags & SRVR_SOC_NEW) {
+			added = 1;
+			if (not_quiet)
+				dcc_trace_msg("fallback listen on %s",
+					      dcc_su2str_err(&sp->su));
+		}
+		sp->flags &= ~(SRVR_SOC_MARK | SRVR_SOC_NEW);
+	}
+
+	return added;
+}
+
+
+
+/* deal with changes to network interfaces */
+static u_char				/* 1=something changed */
+get_if_changes(u_char not_quiet)
+{
+	SRVR_SOC *sp, **spp;
+	u_char changed;
+
+	for (sp = srvr_socs; sp; sp = sp->fwd) {
+		if (sp->flags & SRVR_SOC_IF)
+			sp->flags |= SRVR_SOC_MARK;
+	}
+
+	changed = add_ifs(not_quiet);
+
+	spp = &srvr_socs;
+	while ((sp = *spp) != 0) {
+		/* an interface recognized by add_srvr_soc() will have
+		 * its SRVR_SOC_MARK cleared */
+		if (!(sp->flags & SRVR_SOC_MARK)) {
+			spp = &sp->fwd;
+			continue;
+		}
+
+		/* forget interfaces that have disappeared */
+		dcc_trace_msg("stop listening on %s", dcc_su2str_err(&sp->su));
+		changed = 1;
+		if (srvr_socs_end == &sp->fwd)
+			srvr_socs_end = spp;
+		*spp = sp->fwd;
+		if (sp->udp >= 0)
+			close(sp->udp);
+		if (sp->listen >= 0)
+			close(sp->listen);
+		dcc_free(sp);
+	}
+
+	return changed;
+}
+
+
+
+static const char *
+parse_rl_rate(RL_RATE *rate, float penalty_secs,
+	      const char *label, const char *arg)
+{
+	char *p;
+	int per_sec, hi;
+
+	if (penalty_secs >= 0.0)
+		rate->penalty_secs = penalty_secs;
+
+	if (*arg == '\0')
+		return arg;
+
+	if (*arg == ',')
+		return ++arg;
+
+	per_sec = strtod(arg, &p) * RL_SCALE;
+	hi = per_sec*RL_AVG_SECS;
+	if ((*p != '\0' && *p != ',')
+	    || hi < RL_SCALE || per_sec > RL_MAX_CREDITS) {
+		dcc_error_msg("invalid %s value in \"%s\"",
+			      label, arg);
+		return "";
+	}
+
+	/* maximum events/second * RL_SCALE */
+	rate->per_sec = per_sec;
+
+	/* maximum allowed accumulated credits */
+	rate->hi = hi;
+
+	/* minimum credit account balance */
+	rate->lo = -per_sec * rate->penalty_secs;
+
+	return (*p == ',') ? p+1 : p;
+}
+
+
+
+static void
+add_dbclean_flag(char flag)
+{
+	if (dbclean_flags_len >= MAX_DBCLEAN_FLAGS)
+		dcc_logbad(EX_SOFTWARE, "too many flags for dbclean");
+	dbclean_flags[dbclean_flags_len++] = flag;
+}
+
+
+
+static void
+add_dbclean_arg(const char *arg)
+{
+	int i;
+
+	if (dbclean_argc >= DIM(dbclean_argv)-2)
+		dcc_logbad(EX_SOFTWARE, "too many args for dbclean");
+	dbclean_argv[dbclean_argc++] = arg;
+	i = snprintf(dbclean_args_str+dbclean_args_str_len,
+		     sizeof(dbclean_args_str)-dbclean_args_str_len,
+		     " %s", arg);
+	dbclean_args_str_len += i;
+	if (dbclean_args_str_len >= ISZ(dbclean_args_str)-2)
+		dcc_logbad(EX_SOFTWARE, "too many args for dbclean");
+}
+
+
+
+/* check effort to repair database */
+static void
+check_dbclean(int options)
+{
+	int status;
+	pid_t pid;
+	u_char ok;
+
+	if (dbclean_pid < 0)
+		return;
+
+	pid = waitpid(dbclean_pid, &status, options);
+	if (pid != dbclean_pid)
+		return;
+
+	dbclean_pid = -1;
+
+	/* do not try failing dbclean too often */
+#if defined(WIFEXITED) && defined(WEXITSTATUS) && defined(WTERMSIG) && defined(WIFSIGNALED)
+	ok = 1;
+	if (WIFSIGNALED(status)) {
+		dcc_error_msg("dbclean exited with signal %d",
+			      WTERMSIG(status));
+		ok = 0;
+	} else if (WIFEXITED(status)) {
+		status = WEXITSTATUS(status);
+		if (status != EX_OK) {
+			if (status > 100 && status < 130)
+				dcc_error_msg("dbclean stopped after signal %d",
+					      status-100);
+			else
+				dcc_error_msg("dbclean exited with status %d",
+					      status);
+			ok = 0;
+		}
+	}
+#else
+	ok = (status == EX_OK);
+#endif
+	if (ok) {
+		dbclean_failed = 0;
+		dbclean_limit_secs = DBCLEAN_LIMIT_SECS;
+	} else {
+		dbclean_failed = db_time.tv_sec;
+		dbclean_limit_secs *= 2;
+		if (dbclean_limit_secs > DEL_DBCLEAN_SECS)
+			dbclean_limit_secs = DEL_DBCLEAN_SECS;
+	}
+
+	/* don't restart dbclean until after it has stopped running
+	 * and cooled for a while */
+	dbclean_limit = db_time.tv_sec + dbclean_limit_secs;
+}
+
+
+
+/* try to repair the database */
+static void
+run_dbclean(const char *mode,		/* combination of '', R, S, and W */
+	    const char *reason)
+{
+	int i;
+
+	check_dbclean(0);		/* wait until previous ends */
+
+	wfix_later(WFIX_RECHECK_SECS);
+
+	i = snprintf(&dbclean_flags[dbclean_flags_len],
+		     ISZ(dbclean_flags)-dbclean_flags_len, "%s",
+		     mode);
+	if (i+dbclean_flags_len >= ISZ(dbclean_flags))
+		dcc_logbad(EX_SOFTWARE, "too many flags for dbclean");
+
+	dbclean_pid = fork();
+	if (dbclean_pid < 0) {
+		dcc_error_msg("dbclean fork(): %s", ERROR_STR());
+	} else if (dbclean_pid == 0) {
+		dcc_trace_msg("%s; starting `%s %s%s`",
+			      reason, dbclean, dbclean_flags, dbclean_args_str);
+		execv(dbclean, (char **)dbclean_argv);
+		dcc_error_msg("execv(%s %s%s): %s",
+			      dbclean, dbclean_flags, dbclean_args_str,
+			      ERROR_STR());
+		exit(-1);
+	}
+
+	need_del_dbclean = 0;
+	dbclean_limit = db_time.tv_sec + dbclean_limit_secs;
+}
+
+
+
+static void
+close_srvr_socs(void)
+{
+	SRVR_SOC *sp;
+
+	for (sp = srvr_socs; sp; sp = sp->fwd) {
+		if (sp->udp >= 0) {
+			close(sp->udp);
+			sp->udp = -1;
+		}
+		iflod_listen_close(sp);
+	}
+}
+
+
+
+/* close files and otherwise clean up after being forked as a helper */
+void
+after_fork(void)
+{
+	IFLOD_INFO *ifp;
+	OFLOD_INFO *ofp;
+
+	resolve_hosts_pid = -1;
+	dbclean_pid = -1;
+
+	close_srvr_socs();
+	for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) {
+		if (ifp->soc >= 0)
+			close(ifp->soc);
+	}
+	for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) {
+		if (ofp->soc >= 0)
+			close(ofp->soc);
+	}
+
+	signal(SIGHUP, SIG_DFL);
+	signal(SIGTERM, SIG_DFL);
+	signal(SIGINT, SIG_DFL);
+	signal(SIGPIPE, SIG_DFL);
+#ifdef SIGXFSZ
+	signal(SIGXFSZ, SIG_DFL);
+#endif
+}
+
+
+
+/* do not worry about cleaning to fix a window overflow for a while */
+static void
+wfix_later(time_t delay)
+{
+	dbclean_wfix_state = WFIX_DELAY;
+	dbclean_wfix_secs = db_time.tv_sec + delay;
+}
+
+
+
+static double
+wfix_measure(u_char force)
+{
+	double secs;
+
+	secs = db_time.tv_sec - wfix_check_start;
+	if (force || secs <= 0.0 || secs > WFIX_MEASURE_SECS*2
+	    || total_ops < wfix_ops) {
+		wfix_check_start = db_time.tv_sec;
+		dbclean_wfix_secs = db_time.tv_sec + WFIX_MEASURE_SECS;
+		wfix_ops = total_ops;
+		return -1.0;
+	}
+	return (total_ops - wfix_ops) / secs;
+}
+
+
+
+static u_char				/* 1=need dbclean now */
+wfix(char *reason, u_int reason_len)
+{
+	double rate;
+	struct timeval sn;
+	time_t next_clean;
+
+	/* stop everything if dbclean is running */
+	if (db_minimum_map || wfix_quiet_rate <= 0.0) {
+		wfix_later(WFIX_RECHECK_SECS);
+		return 0;
+	}
+
+	switch (dbclean_wfix_state) {
+	case WFIX_DELAY:		/* just checking */
+		if (!DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS))
+			return 0;
+
+		/* no quick cleaning soon after the database was created,
+		 * cleaned or repaired */
+		dcc_ts2timeval(&sn, &db_parms.sn);
+		if (sn.tv_sec > db_time.tv_sec)
+			sn.tv_sec = 0;
+		if (sn.tv_sec < dbclean_failed
+		    && dbclean_failed <= db_time.tv_sec)
+			sn.tv_sec = dbclean_failed;
+		if (sn.tv_sec <= db_time.tv_sec) {
+			dbclean_wfix_secs = sn.tv_sec + WFIX_POST_CLEAN_SECS;
+			if (!DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS))
+				return 0;
+		}
+
+		/* check later if dbclean might run soon */
+		next_clean = clean_fake_secs;
+		if (db_time.tv_sec >= next_clean - WFIX_PRE_CLEAN_SECS) {
+			dbclean_wfix_secs = next_clean + WFIX_POST_CLEAN_SECS;
+			if (!DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS))
+				return 0;
+		}
+		next_clean = clean_last_secs + 24*60*60;
+		if (db_time.tv_sec >= next_clean - WFIX_PRE_CLEAN_SECS) {
+			dbclean_wfix_secs = next_clean + WFIX_POST_CLEAN_SECS;
+			if (!DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS))
+				return 0;
+		}
+
+		/* check later if the database is not too large now */
+		if (db_fsize + db_hash_fsize
+		    < (wfix_size_set ? wfix_size : db_max_rss)) {
+			wfix_later(WFIX_RECHECK_SECS);
+			break;
+		}
+		/* the database is too big, so try to chase the clients
+		 * to the other DCC server (if any) */
+		dbclean_wfix_state = WFIX_BUSY;
+		wfix_measure(1);
+		break;
+
+	case WFIX_BUSY:
+		rate = wfix_measure(0);
+		if (rate >= 0.0) {
+			wfix_busy_rate = rate;
+			dbclean_wfix_state = WFIX_QUIET;
+			dbclean_wfix_secs = db_time.tv_sec + WFIX_QUIET_SECS;
+		}
+		break;
+
+	case WFIX_QUIET:		/* waiting for clients to flee */
+		dbclean_wfix_state = WFIX_CHECK;
+		wfix_measure(1);
+		break;
+
+	case WFIX_CHECK:		/* counting clients */
+		rate = wfix_measure(0);
+		if (rate < 0.0)
+			break;
+		if (rate <= wfix_quiet_rate
+		    || rate <= wfix_busy_rate * wfix_rate_change) {
+			snprintf(reason, reason_len,
+				 "database size "L_DPAT">"L_DPAT
+				 "; load changed from %0.1f to %0.1f",
+				 db_fsize + db_hash_fsize,
+				 wfix_size_set ? wfix_size : db_max_rss,
+				 wfix_busy_rate, rate);
+			return 1;
+		}
+		/* Other DCC servers did not take over the load. Maybe later */
+		if (db_debug)
+			dcc_trace_msg("database size "L_DPAT" > "L_DPAT
+				      ", but load changed from %0.1f to %0.1f",
+				      db_fsize + db_hash_fsize,
+				      wfix_size_set ? wfix_size : db_max_rss,
+				      wfix_busy_rate, rate);
+		wfix_later(WFIX_RECHECK_SECS);
+		break;
+	}
+
+	return 0;
+}
+
+
+
+/* check for changes or other interesting events when the flood timer expires */
+static time_t				/* microseconds to wait */
+check_changes(void)
+{
+	static time_t misc_timer, files_timer;
+	time_t secs;
+	DB_HADDR hash_free;
+	const char *mode;
+	const char *reason;
+	char reason_buf[100];
+
+	reason = 0;
+	mode = 0;
+
+	if (db_failed_line) {
+		snprintf(reason_buf, sizeof(reason_buf),
+			 "database broken at line %d in %s "DCC_VERSION,
+			 db_failed_line, db_failed_file);
+		reason = reason_buf;
+		mode = "Rbad";
+
+	} else if ((hash_free
+		    = db_hash_len - db_hash_used) < MIN_CLEAN_HASH_ENTRIES
+		   || hash_free < db_hash_len/20) {
+		/* try to expand the hash table when there are only
+		 * a few free slots or the load factor rises above .95 */
+		if (hash_free < MIN_HASH_ENTRIES)
+			snprintf(reason_buf, sizeof(reason_buf),
+				 "%d free hash entries",
+				 hash_free);
+		else
+			snprintf(reason_buf, sizeof(reason_buf),
+				 "%d free hash entries among %d total",
+				 hash_free, db_hash_len);
+		reason = reason_buf;
+		mode = "Rhash";
+
+	} else {
+		/* check nothing else if it is not yet time */
+		secs = next_flods_ck - db_time.tv_sec;
+		if (secs > 0 && secs <= flods_ck_secs)
+			return secs * DCC_US;
+	}
+
+	next_flods_ck = db_time.tv_sec + flods_ck_secs;
+
+	/* do not make some checks too often even when flood checking is
+	 * being rushed */
+	if (DB_IS_TIME(misc_timer, MISC_CK_SECS)) {
+		misc_timer = db_time.tv_sec + MISC_CK_SECS;
+
+		/* shrink our memory footprint
+		 * if we are so idle that we don't have anything to flush */
+		if (db_need_flush_secs == 0) {
+			if (db_unload(0, 1) == 1) {
+				rel_db_states();
+				db_unload(0, 1);
+			}
+		}
+
+		if (DB_IS_TIME(files_timer, 30)) {
+			files_timer = db_time.tv_sec + 30;
+
+			if (0 >= check_load_ids(0))
+				dcc_error_msg("check/reload: %s", dcc_emsg);
+
+			check_blacklist_file();
+
+#ifdef HAVE_GETIFADDRS
+			/* check for network interface changes, but only
+			 * if we can release the result of getifaddrs() */
+			if (get_if_changes(1)) {
+				int socs = open_srvr_socs(0);
+				if (!socs)
+					bad_stop("no server sockets");
+				else if (socs > 0)
+					flods_restart("new network interfaces",
+						      1);
+			}
+#endif
+		}
+
+		if (DB_IS_TIME(need_clients_save, CLIENTS_SAVE_SECS))
+			clients_save();
+
+		/* sound a claim to our server-ID if the database is locked */
+		if (DB_IS_TIME(host_id_next, DCC_SRVR_ID_SECS)
+		    && DB_IS_LOCKED()
+		    &&!db_failed_line ) {
+			refresh_srvr_rcd(host_id_sum, my_srvr_id,
+					 "adding server-ID claim");
+
+			if (!db_failed_line ) {
+				host_id_last = db_time.tv_sec;
+				host_id_next = host_id_last + DCC_SRVR_ID_SECS;
+			}
+		}
+	}
+
+	/* note when hash expansion finishes and collect a zombie */
+	check_dbclean(WNOHANG);
+
+	if (reason != 0) {
+		;
+
+	} else if (DB_IS_TIME(clean_fake_secs, 3*24*60*60)
+		   && (!db_minimum_map
+		       || DB_IS_TIME(clean_last_secs+2*24*60*60, 4*24*60*60))) {
+		reason = "work around broken cron job";
+		mode = "Rcron";
+
+	} else if (need_del_dbclean != 0
+		   && DB_IS_TIME(del_dbclean_next, DEL_DBCLEAN_SECS)) {
+		/* the deletion of a report needs to be cleaned up */
+		reason = need_del_dbclean;
+		mode = "Rdel";
+
+	} else if (DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS)
+		   && wfix(reason_buf, sizeof(reason_buf))) {
+		reason = reason_buf;
+		mode = "Rquick";
+	}
+
+	if (reason) {
+		if (!DB_IS_TIME(dbclean_limit, dbclean_limit_secs)) {
+			if (next_flods_ck > dbclean_limit)
+				next_flods_ck = dbclean_limit;
+		} else if (dbclean_pid < 0) {
+			run_dbclean(mode, reason);
+		} else {
+			RUSH_NEXT_FLODS_CK();
+		}
+	} else {
+		flods_ck(0);
+	}
+
+	/* we probably delayed */
+	gettimeofday(&db_time, 0);
+
+	secs = next_flods_ck - db_time.tv_sec;
+	return secs >= 0 ? secs*DCC_US : 0;
+}
+
+
+
+static void NRATTRIB
+recv_job(void)
+{
+	fd_set rfds, *prfds, wfds, *pwfds;
+#	define PFD_SET(_fd0,_fds) {int _fd = _fd0;	    \
+		p##_fds = &_fds; FD_SET(_fd,p##_fds);	    \
+		if (max_fd < _fd) max_fd = _fd;}
+	int max_fd, nfds;
+	IFLOD_INFO *ifp;
+	OFLOD_INFO *ofp;
+	struct timeval delay;
+	time_t delay_us, slept_us, was_too_busy, us;
+	struct timeval iflods_read, busy_time, extra_time;
+	u_char db_has_failed;
+#	define QUANTUM (DCC_US/10)	/* try to use only ~50% of the system */
+	SRVR_SOC *sp;
+	QUEUE *q;
+	int bad_select;
+	u_char worked;
+	int fd, i;
+
+	bad_select = 3;
+	was_too_busy = 0;
+	gettimeofday(&db_time, 0);
+	iflods_read = db_time;
+	busy_time = db_time;
+	extra_time = db_time;
+	delay_us = flods_ck_secs*DCC_US;
+	db_has_failed = 0;
+	for (;;) {
+		dcc_emsg[0] = '\0';
+
+		if (stopint) {
+			flods_ck_secs = SHUTDOWN_DELAY;
+			if (flods_off < 100000) {
+				flods_off = 100000;
+				flods_stop("server stopping", 0);
+				check_dbclean(WNOHANG);
+				if (dbclean_pid > 0) {
+					kill(dbclean_pid, SIGINT);
+					usleep(100*1000);
+				}
+				/* get started flushing the database while
+				 * we wait for flooding to stop */
+				rel_db_states();
+				db_minimum_map = 1;
+				db_unload(0, 2);
+			}
+			/* get serious when the floods have stopped */
+			if (oflods.total == 0) {
+				if (stopint < 0)
+					dccd_quit(0, "gracefully stopping%s",
+						  stop_mode == 1
+						  ? " for reboot"
+						  : stop_mode == 2
+						  ? " cleanly"
+						  : "");
+				dccd_quit(stopint | 128,
+					  "exiting with signal %d", stopint);
+			}
+		}
+
+		if (db_has_failed != db_failed_line) {
+			db_has_failed = db_failed_line;
+			if (db_failed_line) {
+				++flods_off;
+				flods_stop("database corrupt", 1);
+				rel_db_states();
+				db_unload(0, 2);
+				db_unlock();
+			}
+		}
+
+		FD_ZERO(&rfds);
+		prfds = 0;
+		FD_ZERO(&wfds);
+		pwfds = 0;
+		max_fd = -1;
+
+		/* look for client requests */
+		for (sp = srvr_socs; sp; sp = sp->fwd) {
+			if (sp->udp >= 0)
+				PFD_SET(sp->udp, rfds);
+		}
+
+		if (was_too_busy > 0)
+			was_too_busy = QUANTUM - tv_diff2us(&db_time,
+							&busy_time);
+		if (was_too_busy > 0
+		    && (tv_diff2us(&db_time, &extra_time)
+			< min(30, min(KEEPALIVE_IN, KEEPALIVE_OUT)/2))
+		    && FLODS_OK()) {
+			/* if we have been too busy,
+			 * then do nothing extra for a while */
+			if (delay_us > was_too_busy)
+				delay_us = was_too_busy;
+		} else {
+			extra_time = db_time;
+
+			/* Accept new incoming flood connections
+			 * if flooding is on
+			 * and we don't already have too many floods. */
+			if (iflods.open < DCCD_MAX_FLOODS) {
+				for (sp = srvr_socs; sp; sp = sp->fwd) {
+					if (sp->listen >= 0)
+					    PFD_SET(sp->listen, rfds);
+				}
+			}
+
+			/* pump floods out */
+			for (ofp = oflods.infos, i = 0;
+			     i < oflods.open;
+			     ++ofp) {
+				if (ofp->soc < 0)
+					continue;
+				++i;
+				if (ofp->flags & OFLOD_FG_CONNECTED) {
+					PFD_SET(ofp->soc, rfds);
+					if (!(ofp->flags & OFLOD_FG_EAGAIN)
+					    && ofp->obuf_len != 0)
+					    PFD_SET(ofp->soc, wfds);
+				} else {
+					PFD_SET(ofp->soc, wfds);
+				}
+			}
+
+			/* pump floods in */
+			for (ifp = iflods.infos, i = 0;
+			     i < iflods.open;
+			     ++ifp) {
+				if (ifp->soc < 0)
+					continue;
+				++i;
+				if (ifp->flags & IFLOD_FG_CONNECTED) {
+					PFD_SET(ifp->soc, rfds);
+				} else {
+					PFD_SET(ifp->soc, wfds);
+				}
+			}
+		}
+
+		/* push data to the disk */
+		if (db_need_flush_secs != 0) {
+			if (DB_IS_TIME(db_need_flush_secs,
+				       max(DB_URGENT_NEED_FLUSH_SECS,
+					   DB_NEED_FLUSH_SECS))) {
+				db_flush_needed();
+				gettimeofday(&db_time, 0);
+			}
+			us = db_need_flush_secs - db_time.tv_sec;
+			if (us >= 0) {
+				us *= DCC_US;
+				if (delay_us > us)
+					delay_us = us;
+			}
+		}
+
+		/* let dbclean run if we have run out of work
+		 * or if we have been holding the lock for 0.1 seconds*/
+		if (db_minimum_map
+		    && DB_IS_LOCKED()
+		    && (delay_us != 0
+			|| tv_diff2us(&db_time, &db_locked) >= DCC_US/10)) {
+			db_unlock();
+		}
+
+		/* delay until it is time to answer the oldest anonymous
+		 * request or something else that needs doing */
+		delay.tv_sec = delay_us/DCC_US;
+		delay.tv_usec = delay_us%DCC_US;
+		nfds = select(max_fd+1, prfds, pwfds, 0, &delay);
+		if (nfds < 0) {
+			if (errno != EINTR) {
+				if (--bad_select < 0)
+					bad_stop("give up after select(): %s",
+						 ERROR_STR());
+				else
+					dcc_error_msg("select(): %s",
+						      ERROR_STR());
+			}
+			/* ignore EINTR but recompute timers */
+			FD_ZERO(&rfds);
+			FD_ZERO(&wfds);
+		} else {
+			bad_select = 3;
+		}
+
+		gettimeofday(&wake_time, 0);
+		slept_us = tv_diff2us(&wake_time, &db_time);
+		if (slept_us >= 500) {
+			/* If select() paused for at least 0.5 millisecond,
+			 * then the waiting request has just now arrived. */
+			req_recv_time = wake_time;
+		} else {
+			/* If select() did not pause, then assume the waiting
+			 * requests arrived when we were half finished working
+			 * on flooding and other work besides ordinary requests
+			 * before calling select(). */
+			tv_add_us(&req_recv_time,
+				  tv_diff2us(&wake_time, &req_recv_time) / 2);
+		}
+		db_time = wake_time;
+
+		worked = 0;
+		for (sp = srvr_socs; sp; sp = sp->fwd) {
+			/* queue a new anonymous request
+			 * or answer a new authenticated request */
+			fd = sp->udp;
+			if (fd >= 0 && FD_ISSET(fd, &rfds)) {
+				--nfds;
+				worked = 1;
+				while (new_job(sp))
+					continue;
+			}
+
+			/* start a new incoming flood */
+			fd = sp->listen;
+			if (fd >= 0 && FD_ISSET(fd, &rfds)) {
+				--nfds;
+				worked = 1;
+				iflod_start(sp);
+			}
+		}
+		if (worked)
+			gettimeofday(&db_time, 0);
+		/* reset request receipt clock for next time */
+		req_recv_time = db_time;
+
+		/* Accept new flood data or start new SOCKS floods.
+		 * Listen to all peers to prevent starvation */
+		worked = 0;
+		for (ifp = iflods.infos, i = 0;
+		     nfds > 0 && i < iflods.open;
+		     ++ifp) {
+			if (ifp->soc < 0)
+				continue;
+			++i;
+			if (FD_ISSET(ifp->soc, &rfds)
+			    || FD_ISSET(ifp->soc, &wfds)) {
+				--nfds;
+				iflod_read(ifp);
+				worked = 1;
+			}
+		}
+		if (worked) {
+			gettimeofday(&db_time, 0);
+			iflods_read = db_time;
+		} else if (was_too_busy <= 0) {
+			/* if incoming floods have been quiet for
+			 * awhile, then assume flooding has caught up
+			 * after having been turned off */
+			if (tv_diff2us(&wake_time, &iflods_read) > 2*DCC_US)
+				iflods_ok_timer = db_time.tv_sec;
+		}
+
+		/* pump output flood data and receive confirmations
+		 * talk to all peers to prevent starvation */
+		worked = 0;
+		for (ofp = oflods.infos, i = 0;
+		     nfds > 0 && i < oflods.open;
+		     ++ofp) {
+			if (ofp->soc < 0)
+				continue;
+			++i;
+			if (FD_ISSET(ofp->soc, &rfds)) {
+				--nfds;
+				oflod_read(ofp);
+				if (ofp->soc < 0)
+					continue;
+			}
+			if (FD_ISSET(ofp->soc, &wfds)) {
+				--nfds;
+				oflod_write(ofp);
+				worked = 1;
+			}
+		}
+		if (worked)
+			gettimeofday(&db_time, 0);
+
+		/* process delayed jobs when their times arrive */
+		worked = 0;
+		for (;;) {
+			q = queue_head;
+			if (!q) {
+				delay_us = flods_ck_secs*DCC_US;
+				break;
+			}
+
+			/* decide whether this job's time has come
+			 * while defending against time jumps */
+			delay_us = tv_diff2us(&q->answer, &db_time);
+			if (delay_us >= 1000
+			    && delay_us <= DCC_MAX_RTT
+			    && !stopint)
+				break;  /* not yet time for next job */
+
+			queue_head = q->later;
+			if (queue_head)
+				queue_head->earlier = 0;
+			--queue_cur;
+			do_work(q);
+			worked = 1;
+			free_q(q);
+		}
+		if (worked)
+			gettimeofday(&db_time, 0);
+
+		/* check configuration changes etc. */
+		us = check_changes();
+		if (delay_us >= us)
+			delay_us = us;
+
+		us = tv_diff2us(&db_time, &wake_time);
+		if (us >= QUANTUM && !stopint) {
+			gettimeofday(&db_time, 0);
+			busy_time = db_time;
+			was_too_busy = QUANTUM;
+		}
+	}
+}
+
+
+
+static void
+add_queue(QUEUE *q)
+{
+	QUEUE *qnext, **qp;
+
+	TMSG1(QUERY, "received %s", op_id_ip(q));
+	if (!ck_clnt_id(q)) {
+		free_q(q);
+		return;
+	}
+
+	++total_ops;
+
+	/* immediately process requests from authenticated clients
+	 * if flooding is working */
+	if (q->delay_us == 0) {
+		do_work(q);
+		free_q(q);
+		return;
+	}
+
+	/* don't let the queue of delayed requests get too large */
+	if (queue_cur >= queue_max) {
+		clnt_msg(q, "drop excess queued %s", op_id_ip(q));
+		free_q(q);
+		return;
+	}
+
+	tv_add_us(&q->answer, q->delay_us);
+
+	/* add the new job to the queue */
+	++queue_cur;
+	qp = &queue_head;
+	for (;;) {
+		qnext = *qp;
+		if (!qnext) {
+			*qp = q;
+			break;
+		}
+		if (qnext->answer.tv_sec > q->answer.tv_sec
+		    || (qnext->answer.tv_sec == q->answer.tv_sec
+			&& qnext->answer.tv_usec > q->answer.tv_usec)) {
+			q->later = qnext;
+			qnext->earlier = q;
+			*qp = q;
+			break;
+		}
+		q->earlier = qnext;
+		qp = &qnext->later;
+	}
+}
+
+
+
+/* get a new job in a datagram */
+static u_char				/* 1=call again */
+new_job(SRVR_SOC *sp)
+{
+	QUEUE *q;
+	static struct iovec iov = {0, sizeof(q->pkt)};
+	static struct msghdr msg;
+	int i, j;
+
+	/* Find a free queue entry for the job.
+	 * Because we don't check for incoming jobs unless we think the
+	 * queue is not full, there must always be a free entry or
+	 * permission to make more entries. */
+	q = queue_free;
+	if (q) {
+		queue_free = q->later;
+	} else {
+		i = 16;
+		q = dcc_malloc(i * sizeof(*q));
+		if (!q)
+			dcc_logbad(EX_UNAVAILABLE,
+				   "malloc(%d queue entries) failed", i);
+		queue_max_cur += i;
+		/* put all but the last new queue entry on the free list */
+		while (--i > 0) {
+			q->later = queue_free;
+			queue_free = q;
+			++q;
+		}
+	}
+
+	memset(q, 0, sizeof(*q));
+	q->sp = sp;
+	iov.iov_base = (char *)&q->pkt;
+	msg.msg_name = (void *)&q->clnt_su;
+	msg.msg_namelen = sizeof(q->clnt_su);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	i = recvmsg(sp->udp, &msg, 0);
+	if (i < 0) {
+		/* ignore some results of ICMP unreachables for UDP
+		 * retransmissions seen on some platforms */
+		if (DCC_BLOCK_ERROR()) {
+			;
+		} else if (UNREACHABLE_ERRORS()) {
+			TMSG2(QUERY, "recvmsg(%s): %s",
+			      dcc_su2str_err(&sp->su), ERROR_STR());
+		} else {
+			dcc_error_msg("recvmsg(%s): %s",
+				      dcc_su2str_err(&sp->su), ERROR_STR());
+		}
+		free_q(q);
+		return 0;
+	}
+	if (q->clnt_su.sa.sa_family != sp->su.sa.sa_family
+	    && !dcc_ipv4sutoipv6(&q->clnt_su, &q->clnt_su)) {
+		dcc_error_msg("recvmsg address family %d instead of %d",
+			      q->clnt_su.sa.sa_family,
+			      sp->su.sa.sa_family);
+		free_q(q);
+		return 1;
+	}
+
+	if (DCC_SU_PORT(&q->clnt_su) == 0) {
+		drop_msg(q, "source port 0");
+		free_q(q);
+		return 1;
+	}
+
+	q->pkt_len = i;
+	if (i < ISZ(DCC_HDR)) {
+		drop_msg(q, "short request of %d bytes", i);
+		free_q(q);
+		return 1;
+	}
+	j = ntohs(q->pkt.hdr.len);
+	if (j != i) {
+		drop_msg(q, "request with header length %d instead of %d",
+			 j, i);
+		free_q(q);
+		return 1;
+	}
+
+	if (q->pkt.hdr.pkt_vers > DCC_PKT_VERSION_MAX_VALID
+	    || q->pkt.hdr.pkt_vers < DCC_PKT_VERSION_MIN_VALID
+	    || ((q->pkt.hdr.pkt_vers > DCC_PKT_VERSION_MAX
+		 || q->pkt.hdr.pkt_vers < DCC_PKT_VERSION_MIN)
+		&& q->pkt.hdr.op != DCC_OP_NOP)) {
+		drop_msg(q, "%s in unrecognized protocol version #%d",
+			 qop2str(q), q->pkt.hdr.pkt_vers);
+		free_q(q);
+		return 1;
+	}
+
+	q->answer = req_recv_time;
+
+	switch ((DCC_OPS)q->pkt.hdr.op) {
+	case DCC_OP_NOP:
+		do_nop(q);
+		free_q(q);
+		return 1;
+
+	case DCC_OP_REPORT:
+		if (db_parms.flags & DB_PARM_FG_GREY)
+			break;		/* not valid for greylist servers */
+		add_queue(q);
+		return 1;
+
+	case DCC_OP_QUERY:
+		add_queue(q);
+		return 1;
+
+	case DCC_OP_ADMN:
+		do_admn(q);
+		free_q(q);
+		return 1;
+
+	case DCC_OP_DELETE:
+		do_delete(q);
+		free_q(q);
+		return 1;
+
+	case DCC_OP_GREY_REPORT:
+	case DCC_OP_GREY_QUERY:
+	case DCC_OP_GREY_WHITE:
+		if (!(db_parms.flags & DB_PARM_FG_GREY))
+			break;		/* valid only for greylist servers */
+		do_grey(q);
+		free_q(q);
+		return 1;
+
+	case DCC_OP_GREY_SPAM:
+		if (!(db_parms.flags & DB_PARM_FG_GREY))
+			break;		/* valid only for greylist servers */
+		do_grey_spam(q);
+		free_q(q);
+		return 1;
+
+	case DCC_OP_INVALID:
+	case DCC_OP_ANSWER:
+	case DCC_OP_OK:
+	case DCC_OP_ERROR:
+		break;
+	}
+
+	drop_msg(q, "invalid %s", op_id_ip(q));
+	free_q(q);
+	return 1;
+}
+
+
+
+void
+free_q(QUEUE *q)
+{
+	if (q->rl)
+		--q->rl->ref_cnt;
+	q->later = queue_free;
+	queue_free = q;
+}
+
+
+
+u_char
+dccd_db_open(u_char lock_mode)
+{
+	DCC_CK_TYPES type;
+	time_t clean_secs;
+	DCC_TGTS tgts;
+	int i;
+
+	if (!db_open(dcc_emsg, -1, 0, 0, lock_mode | db_mode))
+		return 0;
+
+	if (grey_on) {
+		/* for greylisting, ignore the args an silently impose our
+		 * notion of which checksums to keep and flooding thresholds */
+		db_parms.nokeep_cks = def_nokeep_cks();
+		if (grey_weak_ip)
+			DB_RESET_NOKEEP(db_parms.nokeep_cks, DCC_CK_IP);
+
+		for (type = DCC_CK_TYPE_FIRST;
+		     type <= DCC_CK_TYPE_LAST;
+		     ++type) {
+			if (type == DCC_CK_SRVR_ID) {
+				flod_tholds[type] = 1;
+				continue;
+			}
+
+			if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type))
+				flod_tholds[type] = DCC_TGTS_INVALID;
+			else
+				flod_tholds[type] = 1;
+
+			if (DCC_CK_IS_GREY_TRIPLE(grey_on,type)
+			    || type == DCC_CK_IP) {
+				db_parms.ex_secs[type].all = grey_window;
+				db_parms.ex_secs[type].spam = grey_white;
+			} else if (type == DCC_CK_BODY
+				   || DCC_CK_IS_GREY_MSG(grey_on,type)) {
+				db_parms.ex_secs[type].all = grey_window;
+				db_parms.ex_secs[type].spam = grey_window;
+			} else {
+				db_parms.ex_secs[type].all = 1;
+				db_parms.ex_secs[type].spam = 1;
+			}
+		}
+
+		summarize_delay_secs = grey_embargo - FLODS_CK_SECS*2;
+
+	} else {
+		/* impose our notion of which normal checksums to keep */
+		DB_SET_NOKEEP(set_new_nokeep_cks, DCC_CK_FLOD_PATH);
+		DB_SET_NOKEEP(set_new_nokeep_cks, DCC_CK_INVALID);
+		DB_SET_NOKEEP(set_new_nokeep_cks, DCC_CK_REP_TOTAL);
+		DB_SET_NOKEEP(set_new_nokeep_cks, DCC_CK_REP_BULK);
+		db_parms.nokeep_cks = ((def_nokeep_cks()
+					& ~reset_new_nokeep_cks)
+				       | set_new_nokeep_cks);
+
+		for (type = DCC_CK_TYPE_FIRST;
+		     type <= DCC_CK_TYPE_LAST;
+		     ++type) {
+			if (type == DCC_CK_SRVR_ID) {
+				flod_tholds[type] = 1;
+				continue;
+			}
+
+			if (type == DCC_CK_REP_TOTAL)
+				tgts = DCC_TGTS_INVALID;
+			if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type))
+				tgts = DCC_TGTS_INVALID;
+			else if (DCC_CK_IS_REP_CMN(0, type))
+				tgts = DCC_TGTS_INVALID;
+			else
+				tgts = BULK_THRESHOLD;
+			flod_tholds[type] = tgts;
+		}
+
+		/* We should not delay reports or summaries so much that
+		 * dbclean might expire them before we can summarize them. */
+		summarize_delay_secs = DCC_OLD_SPAM_SECS;
+		for (type = DCC_CK_TYPE_FIRST;
+		     type <= DCC_CK_TYPE_LAST;
+		     ++type) {
+			if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type))
+				continue;
+			i = db_parms.ex_secs[type].spam;
+			if (i != 0 && summarize_delay_secs > i)
+				summarize_delay_secs = i;
+		}
+	}
+	if (summarize_delay_secs < 1)
+		summarize_delay_secs = 1;
+
+	/* adjust the thresholds after possible changes to kept checksums */
+	set_db_tholds(db_parms.nokeep_cks);
+
+	/* If we instead of cron asked for the last cleaning, make a note
+	 * to clean the database during the graveyard shift.
+	 * Otherwise the database will bloat while the cron job is broken.
+	 *
+	 * Compute 1 day + 45 minutes after cron should have last cleaned the
+	 *	database, if it has been cleaned by cron within the last 3 days
+	 * or the quarter hour when it was last cleaned by this mechanism
+	 *	provided that was between local midnight and 05:00
+	 * or 3 minutes past a random quarter hour beteen midnight and 05:00 */
+	if (db_parms.cleaned_cron >= db_time.tv_sec - 3*24*60*60
+	    && db_parms.cleaned_cron <= db_time.tv_sec) {
+		clean_last_secs = db_parms.cleaned_cron;
+		/* failsafe cleaning for the greylist database starts 15 minutes
+		 * before the main database */
+		clean_secs = clean_last_secs + 45*60 - grey_on*15*60;
+	} else {
+		struct tm tm;
+
+		clean_last_secs = db_parms.cleaned;
+		if (clean_last_secs >= db_time.tv_sec)
+			clean_last_secs = 0;
+		clean_secs = clean_last_secs;
+
+		/* if the previous time for this mechanism is not good,
+		 * pick a new random time */
+		dcc_localtime(clean_secs, &tm);
+		if (clean_secs == 0 || tm.tm_hour < 1 || tm.tm_hour >= 5) {
+			int rnum = (u_int)(db_time.tv_sec + db_time.tv_usec
+					   + my_srvr_id - grey_on) % 23;
+			tm.tm_hour = ((rnum / 4) % 4) + 1;
+			tm.tm_min = (rnum % 4) * 15;
+			clean_secs = mktime(&tm);
+		}
+	}
+	/* round down to a quarter hour to prevent creep due to inevitiable
+	 * delays in cron or this mechanism starting dbclean */
+	clean_secs -= clean_secs % (15*60);
+	clean_secs %= (24*60*60);	/* failsafe cleaning target minute */
+
+	/* compute the next scheduled failsafe cleaning. */
+	clean_fake_secs = db_time.tv_sec - (db_time.tv_sec % (24*60*60));
+	clean_fake_secs += clean_secs;
+	if (clean_fake_secs <= db_time.tv_sec)
+		clean_fake_secs += 24*60*60;
+
+	/* The next failsafe cleaning should happen a day after the
+	 * most recent cron or failsafe cleaning, modulo the delay before
+	 * dbclean starts */
+	while (clean_fake_secs <= clean_last_secs + 24*60*60 - 60*60)
+		clean_fake_secs += 24*60*60;
+
+	/* Do not failsafe clean during the first 48 hours after the
+	 * database was created to give the cron job a chance.
+	 * We do not want failsafe cleaning to ever be running when the
+	 * cron job tries to start. */
+	while (clean_fake_secs <= db_parms.cleared + 2*24*60*60
+	       && db_parms.cleared <= db_time.tv_sec)
+		clean_fake_secs += 24*60*60;
+
+	total_ops = 0;
+
+	/* push our thresholds and flags to the file */
+	return db_flush_parms(0);
+}
+
+
+
+/* clean shut down */
+static void NRATTRIB
+dccd_quit(int exitcode, const char *p, ...)
+{
+	va_list args;
+
+	if (stop_mode == 1)
+		db_stop();
+	else if (stop_mode == 2)
+		make_clean(2);
+	db_unlock();
+
+	va_start(args, p);
+	if (exitcode)
+		dcc_verror_msg(p, args);
+	else
+		dcc_vtrace_msg(p, args);
+	va_end(args);
+
+	/* db_close() can take a long time, so close some things early. */
+	stop_children();
+	check_dbclean(WNOHANG);
+	clients_save();
+
+#ifdef HAVE_COHERENT_MMAP
+	/* If mmap() is not coherent, do not call close_srvr_socs() but
+	 * keep the UDP sockets open to prevent another server from starting
+	 * until we have flushed our buffers to prevent problems on systems
+	 * that lack inter-process coherent mmap() */
+	if (!(db_mode & DB_OPEN_MMAP_WRITE))
+		close_srvr_socs();
+#endif
+	db_close(1);
+
+	if (exitcode)
+		dcc_error_msg("stopped");
+	else
+		dcc_trace_msg("stopped");
+	exit(exitcode);
+}
+
+
+
+/* watch for fatal signals */
+static void
+sigterm(int sig)
+{
+	stopint = sig;
+	stop_mode = 1;
+	next_flods_ck = 0;
+	(void)signal(sig, SIG_DFL);	/* catch it only once */
+}
+
+
+
+/* SIGHUP hurries checking the configuration files */
+static void
+sighup(int sig UATTRIB)
+{
+	next_flods_ck = 0;
+}
+
+
+
+/* emergency shutdown but close the database cleanly */
+void
+bad_stop(const char *pat, ...)
+{
+	va_list args;
+
+	if (stopint)
+		return;
+
+	va_start(args, pat);
+	dcc_verror_msg(pat, args);
+	va_end(args);
+
+	stopint = -1;
+	next_flods_ck = 0;
+}
+
+
+
+static void
+stop_children(void)
+{
+	if (resolve_hosts_pid > 0)
+		kill(resolve_hosts_pid, SIGKILL);
+
+	if (dbclean_pid > 0)
+		kill(dbclean_pid, SIGKILL);
+}