/* Distributed Checksum Clearinghouse server
 *
 * 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.162 $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 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";
static char dbclean_mode[8];
static const char *dbclean_argv[20] = {dbclean_str, dbclean_mode};
static int dbclean_argc = 2;
static char dbclean_arg_str[200];
static int dbclean_arg_len;
time_t dbclean_limit_secs = DBCLEAN_LIMIT_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;

#if defined(NO_IPV6) || !defined(USE_IPV6)
u_char use_ipv6 = 0;
#else
u_char use_ipv6 = 2;
#endif
u_int16_t def_port;			/* default port #, network byte order */
SRVR_SOC *srvr_socs;
static SRVR_SOC **srvr_socs_end = &srvr_socs;
int srvr_rcvbuf;

static u_char db_mode;

DB_RCD host_id_rcd;			/* our host's globally unique ID */
time_t host_id_next, host_id_last;

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;

static QUEUE *queue_free;
static QUEUE *queue_anon_head;

/* assume or hope we can handle 200 requests/second */
int queue_max = 200*DCC_MAX_RTT_SEC;	/* ultimate bound on queue size */
static int queue_max_cur;		/* current upper bound */
static int queue_anon_cur;		/* current queue size */

struct timeval wake_time;		/* when we awoke from select() */
DCC_TS future_ts;			/* the distant future */

const char *need_del_dbclean;
time_t del_dbclean_next;
time_t dbclean_limit;
static time_t dbclean_cron_sub;

static DB_NOKEEP_CKS new_nokeep_cks;

DB_FLOD_THOLDS oflod_thold;		/* flood at or after these thresholds */

static void parse_thold(const char *);
static const char *parse_rl_rate(RL_RATE *, const char *, const char *);
static void add_dbclean_arg(const char *);
static void ck_dbclean(int);
static void run_dbclean(u_char, const char *);
static void sigterm(int);
static void sighup(int);
static void stop_children(void);
static SRVR_SOC *add_srvr_soc(u_char, int, const void *, u_int16_t);
static void open_srvr_socs(int *);
static u_char get_ifs(u_char, u_int16_t);
static void recv_job(void) NRATTRIB;
static u_char new_job(SRVR_SOC *);
static void NRATTRIB db_quit(int, const char *, ...) PATTRIB(2,3);

static void
usage(void)
{
	static const char str[] = {
	    "usage: [-64dVbfFQ] -i server-ID [-n brand] [-h homedir]\n"
	    "   [-a [server-addr][,server-port]] [-I host-ID] [-q qsize]\n"
	    "   [-G [on,][weak-body,][weak-IP,]embargo],[wait],[white]]\n"
	    "   [-t type,threshhold] [-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) {
		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;
	char hostname[MAXHOSTNAMELEN];
	DCC_SOCKU *sup;
	u_int16_t port;
	int new_embargo, new_window, new_white;
	int bind_retries, error, i;
	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 hostname to name our server-ID */
	memset(hostname, 0, sizeof(hostname));
	if (0 > gethostname(hostname, sizeof(hostname)-1))
		dcc_logbad(EX_NOHOST, "gethostname(): %s", ERROR_STR());
	if (hostname[0] == '\0')
		dcc_logbad(EX_NOHOST, "null hostname from gethostname()");
	memcpy(host_id_rcd.cks[0].sum, hostname,
	       sizeof(host_id_rcd.cks[0].sum));

	new_nokeep_cks = def_nokeep_cks();
	for (type = 0; type < DIM(oflod_thold); type++) {
		/* flood server-ID declarations immediately,
		 * body checksums by the bulk threshold,
		 * and nothing else */
		oflod_thold[type] = (DCC_CK_IS_BODY(type) ? BULK_THRESHOLD
				     : type == DCC_CK_SRVR_ID ? 1
				     : DCC_TGTS_INVALID);
	}

	parse_rl_rate(&rl_sub_rate, "RL_SUB", "200");
	parse_rl_rate(&rl_anon_rate, "RL_ANON", "50");
	parse_rl_rate(&rl_all_anon_rate, "RL_ALL_ANON", "200");
	parse_rl_rate(&rl_bugs_rate, "RL_BUGS", "0.1");

	while ((i = getopt(argc, argv,
			   "64dVbfFQi:n:h:a:I:q:G:t:K:T:u:C:L:R:")) != EOF) {
		switch (i) {
		case '6':
#ifndef NO_IPV6
			use_ipv6 = 2;
#endif
			break;
		case '4':
			use_ipv6 = 0;
			break;

		case '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_NO_MMAP;
			break;

		case 'F':
			db_mode |= DB_OPEN_NO_MMAP;
			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':
			if (*optarg == '\0'
			    || strlen(optarg) > sizeof(DCC_BRAND)
			    || strpbrk(optarg, ":"DCC_WHITESPACE)) {
				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 'a':
			optarg += strspn(optarg, DCC_WHITESPACE);
			if (!addr_str)
				addr_str = optarg;
			rest = dcc_parse_nm_port(dcc_emsg, optarg, 0,
						 hostname, sizeof(hostname),
						 &port, 0, 0, 0, 0);
			if (!rest)
				dcc_logbad(dcc_ex_code, "%s", dcc_emsg);
			rest += strspn(rest, DCC_WHITESPACE);
			if (*rest != '\0')
				dcc_logbad(EX_USAGE,
					   "unrecognized port number in"
					   "\"-a %s\"", optarg);
			if (hostname[0] == '\0') {
				get_ifs(1, port);
			} else if (!strcmp(hostname, "@")) {
				if (addr_str == optarg)
					++addr_str; /* "" but not a const */
				add_srvr_soc(SRVR_SOC_ADDR, 0, 0, port);
			} else {
				dcc_host_lock();
				if (!dcc_get_host(hostname, use_ipv6, &error))
					dcc_logbad(EX_NOHOST,
						   "dcc_get_host(%s): %s",
						   optarg,
						   DCC_HSTRERROR(error));
				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();
			}
			break;

		case 'I':
			strncpy((char *)host_id_rcd.cks[0].sum, optarg,
				sizeof(host_id_rcd.cks[0].sum));
			break;

		case 'q':
			l = strtoul(optarg, &p, 0);
			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" */
			while (*optarg) {
				if (dcc_ck_word_comma(&optarg, "weak-body")
				    || dcc_ck_word_comma(&optarg, "weak_body")
				    || dcc_ck_word_comma(&optarg, "weak")) {
					grey_weak_body = 1;
					continue;
				}
				if (dcc_ck_word_comma(&optarg, "weak-IP")
				    || dcc_ck_word_comma(&optarg, "weak_IP")) {
					grey_weak_ip = 1;
					continue;
				}
				if (dcc_ck_word_comma(&optarg, "on"))
					continue;
				break;
			}
			new_embargo = dcc_get_secs(optarg, &p,
						   0, MAX_GREY_EMBARGO,
						   grey_embargo);
			if (new_embargo < 0) {
				dcc_error_msg("invalid greylist embargo \"%s\"",
					      optarg);
				break;
			}
			new_window = dcc_get_secs(p, &p,
						  new_embargo, 10*24*60*60,
						  max(new_embargo,grey_window));
			if (new_window < 0) {
				dcc_error_msg("invalid greylist wait time"
					      " \"%s\"",
					      optarg);
				break;
			}
			new_white = dcc_get_secs(p, 0,
						 new_window, DB_EXPIRE_SECS_MAX,
						 max(new_window, grey_white));
			if (new_white < 0) {
				dcc_error_msg("invalid greylist whitelist time"
					      " \"%s\"",
					      optarg);
				break;
			}
			grey_embargo = new_embargo;
			grey_window = new_window;
			grey_white = new_white;
			break;

		case 't':
			parse_thold(optarg);
			break;

		case 'K':
			if (!strcasecmp(optarg, "all")) {
				new_nokeep_cks = 0;
				break;
			}
			if (!CSTRCMP(optarg, "no-")) {
				optarg += STRZ("no-");
				i = 0;
			} else {
				i = 1;
			}
			type = dcc_str2type(optarg);
			if (type == DCC_CK_INVALID) {
				dcc_error_msg("bad checksum type in"
					      " \"-K %s\"", optarg);
				break;
			}
			if (i)
				DB_RESET_NOKEEP(new_nokeep_cks, type);
			else
				DB_SET_NOKEEP(new_nokeep_cks, type);
			break;

		case 'T':
			if (!strcasecmp(optarg, "ALL")) {
				dccd_tracemask |= DCC_TRACE_ON_BITS;
			} else 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 {
				dcc_error_msg("invalid trace mode \"%s\"",
					      optarg);
			}
			break;

		case 'u':
			if (!strcasecmp(optarg, "forever")) {
				anon_off = 1;
				break;
			}
			if (!parse_dccd_delay(dcc_emsg, &anon_delay_us,
					      &anon_delay_inflate, optarg,
					      0, 0)) {
				dcc_error_msg("%s", dcc_emsg);
				break;
			}
			anon_off = 0;
			break;

		case 'C':
			dbclean = optarg;
			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, "RL_SUB", optarg);
			rest = parse_rl_rate(&rl_anon_rate, "RL_ANON", rest);
			rest = parse_rl_rate(&rl_all_anon_rate, "RL_ALL_ANON",
					     rest);
			rest = parse_rl_rate(&rl_bugs_rate, "RL_BUGS", rest);
			break;

		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;
	if (argc != 0)
		usage();

	if (my_srvr_id == DCC_ID_INVALID) {
		if (print_version)
			exit(EX_OK);
		dcc_logbad(EX_USAGE, "server-ID not set with -i");
	}

	if (db_mode & DB_OPEN_NO_MMAP)
		add_dbclean_arg("-F");

	if (grey_on) {
		anon_off = 1;
		dccd_tracemask |= DCC_TRACE_IDS_BIT;
		flods_ck_secs = GREY_FLODS_CK_SECS;
	}

	if (addr_str) {
		add_dbclean_arg("-a");
		add_dbclean_arg(addr_str);
	}

	dcc_clnt_unthread_init();

	if (!dcc_cdhome(dcc_emsg, homedir))
		dcc_logbad(dcc_ex_code, "%s", dcc_emsg);

	i = load_ids(dcc_emsg, 0, my_srvr_id);
	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)
		get_ifs(1, def_port);
	/*  fall back to INADDR_ANY if getifaddrs() found nothing */
	if (!srvr_socs)
		add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_LISTEN, 0, 0, def_port);

	/* make initial attempt to open the server UDP sockets */
	srvr_rcvbuf = 1024*1024;
	bind_retries = 45;
	open_srvr_socs(&bind_retries);

	add_dbclean_arg(use_ipv6 == 0 ? "-4" : "-6");

	if (background && daemon(1, 0) < 0)
		dcc_logbad(EX_OSERR, "daemon(): %s", ERROR_STR());

	if (!background)
		signal(SIGHUP, sigterm);    /* SIGHUP fatal during debugging */
	else
		signal(SIGHUP, sighup);	/* speed check of configuration */
	signal(SIGTERM, sigterm);
	signal(SIGINT, sigterm);
	signal(SIGPIPE, SIG_IGN);

	atexit(stop_children);

	gettimeofday(&db_time, 0);
	wake_time = db_time;

	/* 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(1, "database broken");
		ck_dbclean(0);		/* stall until it ends */
		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);
		}
	}

	boot_ok_time = db_time.tv_sec + LAST_ERROR_ACT_SECS;
	host_id_next = db_time.tv_sec + LAST_ERROR_ACT_SECS;
	flods_init();
	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 with %s and %s",
		      ntohs(srvr_socs->arg_port), dcc_homedir, db_window_size);

	recv_job();
}



static SRVR_SOC *			/* 0 or new entry */
add_srvr_soc(u_char flags,
	     int family,		/* 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 (!memcmp(&su, &sp->su, sizeof(su))) {
			if (!(sp->flags & SRVR_SOC_ADDR))
				sp->flags = flags;
			return 0;
		}
	}

	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 void
open_srvr_socs(int *retry_secs)
{
	static u_char buf_set = 0;
	int num_socs = 0;
	SRVR_SOC *sp;

	for (sp = srvr_socs; sp; sp = sp->fwd) {
		if (sp->udp >= 0) {
			++num_socs;
			continue;
		}

		/* resolve default if we finally know it */
		if (!sp->arg_port)
			sp->arg_port = def_port;

		if (!sp->arg_family && use_ipv6 == 2) {
			/* using INADDR_ANY and do not know if IPv6 works */
			if (!dcc_udp_bind(dcc_emsg, &sp->udp,
					  dcc_mk_su(&sp->su, AF_INET6,
						    0, sp->arg_port),
					  retry_secs)) {
				dcc_error_msg("%s", dcc_emsg);
				/* still don't know about IPv6 */
				continue;
			}
			if (sp->udp >= 0) {
				use_ipv6 = 1;
				sp->arg_family = AF_INET6;
				continue;
			} else {
				use_ipv6 = 0;
			}
		}

		/* create the UDP sockets */
		if (!sp->arg_family)
			sp->arg_family = use_ipv6 ? AF_INET6 : AF_INET;
		else if (use_ipv6 == 2 && sp->arg_family == AF_INET6)
			use_ipv6 = 1;
		dcc_mk_su(&sp->su, sp->arg_family, &sp->arg_addr, sp->arg_port);
		if (!dcc_udp_bind(dcc_emsg, &sp->udp, &sp->su, retry_secs)
		    || sp->udp < 0) {
			dcc_error_msg("%s", dcc_emsg);
			continue;
		}

		for (;;) {
			if (!setsockopt(sp->udp, SOL_SOCKET, SO_RCVBUF,
					&srvr_rcvbuf, sizeof(srvr_rcvbuf)))
				break;
			if (buf_set || srvr_rcvbuf <= 4096) {
				dcc_error_msg("setsockopt(%s,SO_RCVBUF=%d): %s",
					      dcc_su2str(&sp->su),
					      srvr_rcvbuf,
					      ERROR_STR());
				break;
			}
			srvr_rcvbuf -= 4096;
		}
		buf_set = 1;

		++num_socs;
	}

	/* Finally decide the IPv6 issue if we found no sign of IPv6 */
	if (use_ipv6 == 2)
		use_ipv6 = 0;

	if (!num_socs)
		dcc_logbad(EX_OSERR, "failed to open any server sockets");
}



/* get ready to bind to all local IP addreses */
#ifndef HAVE_GETIFADDRS
static u_char				/* 1=added an interface */
get_ifs(u_char quiet UATTRIB,
	u_int16_t port)			/* network byte order */
{
	return 0 != add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_LISTEN, 0, 0, port);
}
#else /* defined(HAVE_GETIFADDRS) */
static u_char				/* 1=added an interface */
get_ifs(u_char quiet,
	u_int16_t port)			/* network byte order */
{
	struct ifaddrs *ifap0, *ifap;
	const SRVR_SOC *sp = 0;
	u_char changed, flags;

	changed = 0;
	flags = SRVR_SOC_IF | SRVR_SOC_LISTEN;

	if (0 > getifaddrs(&ifap0)) {
		dcc_error_msg("getifaddrs(): %s", ERROR_STR());
		return 0 != add_srvr_soc(flags, 0, 0, port);
	}

	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:
			sp = add_srvr_soc(flags, ifap->ifa_addr->sa_family,
					  &((struct sockaddr_in *)ifap->ifa_addr
					    )->sin_addr,
					  port);
			flags = SRVR_SOC_IF;
			break;
		case AF_INET6:
			if (use_ipv6 == 0)
				continue;
			if (use_ipv6 == 2)
				use_ipv6 = 1;
			sp = add_srvr_soc(flags, ifap->ifa_addr->sa_family,
					  &((struct sockaddr_in6*)ifap->ifa_addr
					    )->sin6_addr,
					  port);
			flags = SRVR_SOC_IF;
			break;
		}
		if (sp) {
			changed = 1;
			if (!quiet)
				dcc_trace_msg("start listening on %s",
					      dcc_su2str(&sp->su));
			sp = 0;
		}
	}

#ifdef HAVE_FREEIFADDRS
	/* since this is done only a few times, don't worry if we cannot release
	 * the list of interfaces when HAVE_FREEIFADDRS is not defined */
	freeifaddrs(ifap0);
#endif

	return changed;
}



/* deal with changes to network interfaces */
static void
get_if_changes(void)
{
#ifdef HAVE_FREEIFADDRS
	/* only if we know how to release the result of getifaddrs() */
	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;
	}

	/* reconnect to all network interfaces using the same port
	 * numbers as the first time */
	changed = 0;
	for (sp = srvr_socs; sp; sp = sp->fwd) {
		if (!(sp->flags & SRVR_SOC_IF))
			continue;
		if (!(sp->flags & SRVR_SOC_LISTEN))
			continue;
		if (get_ifs(0, sp->arg_port))
			changed = 1;
	}

	spp = &srvr_socs;
	while ((sp = *spp) != 0) {
		if (!(sp->flags & SRVR_SOC_MARK)) {
			spp = &sp->fwd;
			continue;
		}

		dcc_trace_msg("stop listening on %s", dcc_su2str(&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);
		memset(sp, 0x55, sizeof(*sp));
		dcc_free(sp);
	}

	if (changed) {
		/*  fall back to INADDR_ANY if getifaddrs() found nothing */
		if (!srvr_socs) {
			add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_LISTEN,
				     0, 0, def_port);
			dcc_trace_msg("start listening on %s",
				      dcc_su2str(&srvr_socs->su));
		}
		flods_restart("network interfaces changed");
		open_srvr_socs(0);
	}
#endif /* HAVE_FREEIFADDRS */
}
#endif /* HAVE_GETIFADDRS */



static void
parse_thold(const char *arg)
{
	DCC_TGTS tgts;
	char *duparg, *cnt;
	DCC_CK_TYPES type;

	duparg = dcc_strdup(arg);
	cnt = strchr(duparg, ',');
	if (!cnt) {
		dcc_error_msg("missing comma in \"-t %s\"",
			      arg);
		dcc_free(duparg);
		return;
	}
	*cnt++ = '\0';
	type = dcc_str2type(duparg);
	tgts = dcc_str2cnt(cnt);
	dcc_free(duparg);
	if (tgts == 0 || tgts > DCC_TGTS_TOO_MANY) {
		dcc_error_msg("unrecognized count in \"-t %s\"",
			      arg);
		return;
	}
	/* silently limit the flooding threshold */
	if (tgts < BULK_THRESHOLD/2)
		tgts = BULK_THRESHOLD/2;

	if (type != DCC_CK_INVALID) {
		oflod_thold[type] = tgts;
		return;
	}
	if (arg[0] == ',') {
		for (type = 0;
		     type < DIM(oflod_thold);
		     type++) {
			if (DCC_CK_IS_BODY(type))
				oflod_thold[type] = tgts;
		}
		return;
	}
	dcc_error_msg("bad checksum type in \"-t %s\"", arg);
}



static const char *
parse_rl_rate(RL_RATE *rate, const char *label, const char *arg)
{
	char *p;
	int sec, hi;

	if (*arg == '\0')
		return arg;

	if (*arg == ',')
		return ++arg;

	sec = strtod(arg, &p) * RL_SCALE;
	hi = sec*RL_AVG_SECS;
	if ((*p != '\0' && *p != ',')
	    || hi < RL_SCALE || sec > RL_MAX_CREDITS) {
		dcc_error_msg("invalid %s value in \"%s\"",
			      label, arg);
		return "";
	}

	rate->sec = sec;
	rate->hi = hi;
	rate->lo = -hi;

	return (*p == ',') ? p+1 : p;
}



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_arg_str+dbclean_arg_len,
		     sizeof(dbclean_arg_str)-dbclean_arg_len,
		     " %s", arg);
	dbclean_arg_len += i;
	if (dbclean_arg_len >= ISZ(dbclean_arg_str)-2)
		dcc_logbad(EX_SOFTWARE, "too many args for dbclean");
}



/* check effort to repair database */
static void
ck_dbclean(int options)
{
	int status;
	pid_t pid;

	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 */
#ifdef WEXITSTATUS
	status = WEXITSTATUS(status);
#else
	status &= 0xff;
#endif
	if (status == 0) {
		dbclean_limit_secs = DBCLEAN_LIMIT_SECS;
	} else {
		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(u_char S_mode, const char *reason)
{
	if (*dbclean == '\0')
		return;

	ck_dbclean(0);			/* wait until previous ends */

	dbclean_pid = fork();
	if (dbclean_pid < 0) {
		dcc_error_msg("dbclean fork(): %s", ERROR_STR());
	} else if (dbclean_pid == 0) {
		strcpy(dbclean_mode, "-DPq");
		if (db_failed)
			strcat(dbclean_mode, "R");
		if (S_mode)
			strcat(dbclean_mode, "S");
		dcc_trace_msg("%s; starting `%s %s%s`",
			      reason,
			      dbclean_argv[0], dbclean_argv[1],
			      dbclean_arg_str);
		execv(dbclean, (char **)dbclean_argv);
		dcc_error_msg("execv(%s %s): %s",
			      dbclean, dbclean_arg_str, ERROR_STR());
		exit(-1);
	}

	need_del_dbclean = 0;
	dbclean_limit = db_time.tv_sec + dbclean_limit_secs;
}



/* check for changes or other interesting events when the flood timer expires */
static time_t				/* seconds to wait */
ck_changes(void)
{
	static time_t misc_timer;
	time_t secs;
	DB_HADDR hash_free;
	const char *reason;
	char reason_buf[100];

	/* check nothing if it is not yet time */
	secs = next_flods_ck - db_time.tv_sec;
	if (secs > 0 && secs <= flods_ck_secs)
		return secs;
	next_flods_ck = db_time.tv_sec + flods_ck_secs;

	/* do not make some checks too often */
	if (DB_IS_TIME(misc_timer, MISC_CK_SECS)) {
		if (!db_sync_some(dcc_emsg))
			db_broken(dcc_emsg);

		switch (check_load_ids(dcc_emsg, my_srvr_id)) {
		case 0:
			dcc_error_msg("check/reload: %s", dcc_emsg);
			break;
		case 1:
			dcc_trace_msg("reloaded %s", DCC_NM2PATH(ids_nm));
			flods_restart("restart flooding with new IDs");
			break;
		}

#ifdef HAVE_GETIFADDRS
		get_if_changes();
#endif

		cycle_q_delay();

		check_blacklist_file(0);

		/* sound a claim to our server-ID if the database is locked */
		if (DB_IS_TIME(host_id_next, DCC_SRVR_ID_SEC)
		    && DB_IS_LOCKED()) {
			host_id_last = db_time.tv_sec;
			host_id_next = host_id_last + DCC_SRVR_ID_SEC;
			dcc_timeval2ts(host_id_rcd.ts, &db_time, 0);

			host_id_rcd.srvr_id_auth = my_srvr_id;
			DB_TGTS_RCD_SET(&host_id_rcd, 1);
			host_id_rcd.fgs_num_cks = 1;
			host_id_rcd.cks[0].type_fgs = DCC_CK_SRVR_ID;
			if (!db_add_rcd(dcc_emsg, &host_id_rcd))
				db_broken("adding server-ID claim: %s",
					  dcc_emsg);
		}

		gettimeofday(&db_time, 0);
		misc_timer = db_time.tv_sec + MISC_CK_SECS;
	}

	/* note when previous hash expansion finishes and collect a zombie */
	ck_dbclean(WNOHANG);

	if (db_failed) {
		reason = "database broken";
	} else {
		if (need_del_dbclean != 0
		    && DB_IS_TIME(del_dbclean_next, DEL_DBCLEAN_SECS)) {
			reason = need_del_dbclean;
		} else if ((hash_free = db_hash_len - db_hash_used)
			   < MIN_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;

		} else if (DB_IS_TIME(dbclean_cron_sub, 3*24*60*60)) {
			/* clean the database again after being forced so a
			 * missing cron job does not let it bloat */
			reason = "work around broken cron job";
		} else {
			reason = 0;
		}
	}

	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(0, reason);
			next_flods_ck = db_time.tv_sec + 1;
		} else {
			next_flods_ck = db_time.tv_sec + 1;
		}
	} else {
		flods_ck(0);
	}

	secs = next_flods_ck - db_time.tv_sec;
	return secs >= 0 ? secs : 0;
}



static void NRATTRIB
recv_job(void)
{
	fd_set rfds, *prfds, wfds, *pwfds;
#	define PFD_SET(_fd,_fds) {p##_fds = &_fds; FD_SET(_fd,p##_fds);}
	int max_fd, nfds;
	IFLOD_INFO *ifp;
	OFLOD_INFO *ofp;
	struct timeval delay;
	time_t secs;
	u_char was_too_busy, db_has_failed;
	SRVR_SOC *sp;
	QUEUE *q;
	int fd, i;

	was_too_busy = 1;
	db_has_failed = 0;
	for (;;) {
		if (stopint != 0) {
			if (flods_ck_secs > SHUTDOWN_DELAY)
				flods_ck_secs = SHUTDOWN_DELAY;
			if (flods_off < 100000) {
				flods_off = 100000;
				iflods_stop("server stopping", 0);
				oflods_stop(0);
				db_unlock(0);
				db_unload(0);
			}
			if (iflods.active == 0 && oflods.active == 0) {
				if (stopint < 0)
					db_quit(0, "gracefully stopping");
				db_quit(stopint | 128,
					"exiting with signal %d", stopint);
			}
		}
		if (db_has_failed != db_failed) {
			db_has_failed = db_failed;
			if (db_failed) {
				db_unlock(0);
				db_unload(0);
				++flod_db_sick;
				++flods_off;
				oflods_stop(1);
				iflods_stop("database corrupt", 1);
			}
		}

		FD_ZERO(&rfds);
		prfds = 0;
		FD_ZERO(&wfds);
		pwfds = 0;
		max_fd = 0;
		if (was_too_busy) {
			delay.tv_sec = 1;
			delay.tv_usec = 0;
		} else {
			for (sp = srvr_socs; sp; sp = sp->fwd) {
				fd = sp->udp;
				if (fd < 0)
					continue;
				PFD_SET(fd, rfds);
				if (max_fd <= fd)
					max_fd = fd;

				/* accept new incoming flood connections
				 * if flooding is on
				 * and we don't already have too many */
				fd = sp->listen;
				if (fd >= 0
				    && iflods.active < DCCD_MAX_FLOODS) {
					PFD_SET(fd, rfds);
					if (max_fd < fd)
					    max_fd = fd;
				}
			}
			/* pay attention to currently flooding reports */
			for (ifp = iflods.infos, i = 0;
			     i < iflods.active;
			     ++ifp) {
				if (ifp->s < 0)
					continue;
				++i;
				if (ifp->flags & IFLOD_FG_CONNECTED) {
					PFD_SET(ifp->s, rfds);
				} else {
					PFD_SET(ifp->s, wfds);
				}
				if (max_fd < ifp->s)
					max_fd = ifp->s;
			}
			for (ofp = oflods.infos, i = 0;
			     i < oflods.active;
			     ++ofp) {
				if (ofp->s < 0)
					continue;
				++i;
				if (ofp->flags & OFLOD_FG_CONNECTED) {
					PFD_SET(ofp->s, rfds);
					if (ofp->obuf_len != 0
					    || (ofp->flags
						& OFLOD_FG_TOO_BUSY))
					    PFD_SET(ofp->s, wfds);
				} else {
					PFD_SET(ofp->s, wfds);
				}
				if (max_fd < ofp->s)
					max_fd = ofp->s;
			}
		}
		/* delay only until it is time to answer the oldest
		 * anonymous request */
		nfds = select(max_fd+1, prfds, pwfds, 0, &delay);
		if (nfds < 0) {
			if (errno != EINTR)
				dcc_logbad(EX_OSERR, "select():%s",
					   ERROR_STR());
			/* ignore EINTR but recompute timers */
			FD_ZERO(&rfds);
			FD_ZERO(&wfds);
		}
		gettimeofday(&db_time, 0);
		wake_time = db_time;

		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;
				while (new_job(sp))
					continue;
			}

			/* start a new incoming flood stream */
			fd = sp->listen;
			if (fd >= 0 && FD_ISSET(fd, &rfds)) {
				--nfds;
				iflod_start(sp);
			}
		}

		/* accept new flood data or start new SOCKS floods */
		for (ifp = iflods.infos, i = 0;
		     nfds > 0 && i < iflods.active;
		     ++ifp) {
			if (ifp->s < 0)
				continue;
			++i;
			if (FD_ISSET(ifp->s, &rfds)
			    || FD_ISSET(ifp->s, &wfds)) {
				--nfds;
				iflod_read(ifp);
			}
		}

		/* pump output flood data and receive confirmations */
		for (ofp = oflods.infos, i = 0;
		     nfds > 0 && i < oflods.active;
		     ++ofp) {
			if (ofp->s < 0)
				continue;
			++i;
			if (FD_ISSET(ofp->s, &rfds)) {
				--nfds;
				oflod_read(ofp);
				if (ofp->s < 0)
					continue;
			}
			if (FD_ISSET(ofp->s, &wfds)) {
				--nfds;
				oflod_write(ofp);
			}
		}

		/* process anonymous jobs when their times arrive */
		for (;;) {
			q = queue_anon_head;
			if (!q) {
				delay.tv_sec = flods_ck_secs;
				delay.tv_usec = 0;
				break;
			}

			/* decide whether this job's time has come
			 * while defending against time jumps */
			delay.tv_sec = q->answer.tv_sec - db_time.tv_sec;
			delay.tv_usec = q->answer.tv_usec - db_time.tv_usec;
			if (delay.tv_usec < 0) {
				--delay.tv_sec;
				delay.tv_usec += DCC_USECS;
			}
			if ((delay.tv_sec > 0
			     && delay.tv_sec < DCC_MAX_RTT_SEC)
			    || (delay.tv_sec == 0
				&& delay.tv_usec >= 1000))
				break;  /* not yet time for next job */

			queue_anon_head = q->later;
			if (queue_anon_head)
				queue_anon_head->earlier = 0;
			--queue_anon_cur;
			do_work(q);
			free_q(q);
		}

		if (!was_too_busy) {
			/* check configuration changes etc. */
			secs = ck_changes();
			if (delay.tv_sec >= secs) {
				delay.tv_sec = secs;
				delay.tv_usec = 0;
			}
		}

		if (too_busy) {
			too_busy = 0;
			was_too_busy = 1;
		} else {
			was_too_busy = 0;
		}
	}
}



static void
add_queue(QUEUE *q)
{
	QUEUE *qnext, **qp;

	TMSG3(QUERY, "received %s from %d at %s",
	      dcc_req_op2str(&q->pkt.a),
	      (DCC_CLNT_ID)ntohl(q->pkt.hdr.sender), Q_CIP(q));
	if (!ck_clnt_id(q)) {
		free_q(q);
		return;
	}

	/* immediately process requests from authenticated clients */
	if (q->delay_us == 0) {
		do_work(q);
		free_q(q);
		return;
	}

	/* don't let the queue of anonymous clients get too large */
	if (queue_anon_cur >= queue_max) {
		anon_msg("drop excess queued %s request from %s",
			 dcc_req_op2str(&q->pkt.a), Q_CIP(q));
		free_q(q);
		return;
	}
	if (anon_off && q->clnt_id == DCC_ID_ANON) {
		anon_msg("drop anomymous %s request from %s",
			 dcc_req_op2str(&q->pkt.a), Q_CIP(q));
		free_q(q);
		return;
	}

	if (q->delay_us >= DCC_MAX_RTT) {
		TMSG1(BL, "drop excessively delayed request from %s", Q_CIP(q));
		free_q(q);
		return;
	}
	q->answer.tv_usec += q->delay_us;
	q->answer.tv_sec += q->answer.tv_usec / DCC_USECS;
	q->answer.tv_usec %= DCC_USECS;


	/* add the new job to the queue */
	++queue_anon_cur;
	qp = &queue_anon_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 (DCC_CONNECT_ERRORS()) {
			TMSG2(QUERY, "recvmsg(%s): %s",
			      dcc_su2str(&sp->su), ERROR_STR());
		} else {
			dcc_error_msg("recvmsg(%s): %s",
				      dcc_su2str(&sp->su), ERROR_STR());
		}
		free_q(q);
		return 0;
	}
	if (q->clnt_su.sa.sa_family != sp->su.sa.sa_family) {
		dcc_logbad(EX_OSERR, "unexpected recvmsg address family %d",
			   q->clnt_su.sa.sa_family);
		free_q(q);
		return 1;
	}

	q->pkt_len = i;
	if (i < ISZ(DCC_HDR)) {
		if (ck_id(q, DCC_ID_ANON)) {
			anon_msg("short packet of %d bytes from %s",
				 i, Q_CIP(q));
			discard_error(q, "short packet of %d bytes", i);
		}
		free_q(q);
		return 1;
	}
	j = ntohs(q->pkt.hdr.len);
	if (j != i) {
		if (ck_id(q, DCC_ID_ANON)) {
			anon_msg("header length %d instead of %d from %s",
				 j, i, Q_CIP(q));
			discard_error(q, "header length %d instead of %d",
				      j, i);
		}
		free_q(q);
		return 1;
	}

	if ((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) {
		if (ck_id(q, DCC_ID_ANON)) {
			anon_msg("unrecognized protocol version #%d"
				 " for %s from %s",
				 q->pkt.hdr.pkt_vers,
				 dcc_req_op2str(&q->pkt.a),
				 Q_CIP(q));
			discard_error(q, "unrecognized protocol version #%d"
				      " for %s",
				      q->pkt.hdr.pkt_vers,
				      dcc_req_op2str(&q->pkt.a));
		}
		free_q(q);
		return 1;
	}

	q->answer = wake_time;

	switch ((DCC_OPS)q->pkt.hdr.op) {
	case DCC_OP_NOP:
		do_nop(q);
		return 1;

	case DCC_OP_REPORT:
		if (db_flags & DB_MAGIC_ST_GREY)
			break;
		++dccd_stats.reports;
		add_queue(q);
		return 1;

	case DCC_OP_QUERY:
		++dccd_stats.queries;
		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_flags & DB_MAGIC_ST_GREY))
			break;
		do_grey(q);
		free_q(q);
		return 1;

	case DCC_OP_GREY_SPAM:
		if (!(db_flags & DB_MAGIC_ST_GREY))
			break;
		do_grey_spam(q);
		free_q(q);
		return 1;

	case DCC_OP_INVALID:
	case DCC_OP_QUERY_RESP:
	case DCC_OP_OK:
	case DCC_OP_ERROR:
		break;
	}

	anon_msg("invalid op %d from %s", q->pkt.hdr.op, Q_CIP(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;
	struct tm tm;
	struct timeval sn;
	int cur_hour, i, j;

	if (!db_open(dcc_emsg, 0, 0, lock_mode | db_mode))
		return 0;

	if (grey_on) {
		for (type = DCC_CK_TYPE_FIRST;
		     type <= DCC_CK_TYPE_LAST;
		     ++type) {
			/* use dbclean values for server-ID declarations */
			if (type == DCC_CK_SRVR_ID)
				continue;
			db_ex_secs[type].clean_thold = 1;
			oflod_thold[type] = 1;
			if (type == DCC_CK_GREY_TRIPLE
			    || type == DCC_CK_IP) {
				db_ex_secs[type].all = grey_window;
				db_ex_secs[type].spam = grey_white;
			} else if (type == DCC_CK_BODY
				   || type == DCC_CK_GREY_MSG) {
				db_ex_secs[type].all = grey_window;
				db_ex_secs[type].spam = grey_window;
			} else {
				db_ex_secs[type].all = 1;
				db_ex_secs[type].spam = 1;
			}
		}

		/* silently impose our notion of which checksums to keep */
		new_nokeep_cks = def_nokeep_cks();
		if (grey_weak_ip)
			DB_RESET_NOKEEP(new_nokeep_cks, DCC_CK_IP);
		db_nokeep_cks = new_nokeep_cks;

		summarize_delay_secs = grey_embargo - flods_ck_secs*2;
		if (summarize_delay_secs < 1)
			summarize_delay_secs = 1;

	} else {
		/* impose our notion of which checksums to keep */
		db_nokeep_cks = new_nokeep_cks;

		summarize_delay_secs = MAX_SUMMARIZE_DELAY_SECS;
		for (i = 0; i < DIM(db_ex_secs); ++i) {
			j = db_ex_secs[i].all;
			if (j != 0
			    && j < summarize_delay_secs)
				j = summarize_delay_secs;
		}
	}

	memcpy(db_flod_tholds, oflod_thold, sizeof(db_flod_tholds));

	/* Compute the quarter hour when the database was last cleaned
	 * or 3 minutes past a random quarter hour beteen midnight and 06:00 */
	dcc_ts2timeval(&sn, db_sn);
	dcc_localtime(sn.tv_sec, &tm);
	if (!(db_flags & DB_MAGIC_ST_SELF_CLEAN)) {
		i = tm.tm_hour*60 + tm.tm_min + DBCLEAN_LIMIT_SECS;
	} else if (tm.tm_hour < 6) {
		i = tm.tm_hour*60 + tm.tm_min;
	} else {
		j = (u_int)((sn.tv_sec + db_time.tv_sec) * my_srvr_id) % (6*4);
		i = (j/4)*60 + (j%4)*15 + 3;
	}
	dcc_localtime(db_time.tv_sec, &tm);
	cur_hour = tm.tm_hour;
	tm.tm_hour = i / 60;
	tm.tm_min = i % 60;
	tm.tm_sec = 0;
	dbclean_cron_sub = mktime(&tm);

	/* 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 gradually bloat when
	 * the cron job is broken. */
	if (!(db_flags & DB_MAGIC_ST_SELF_CLEAN)) {
		dbclean_cron_sub += 2*24*60*60;
	} else if (!(db_flags & DB_MAGIC_ST_SELF_CLEAN2)) {
		dbclean_cron_sub += 24*60*60;
	} else if (cur_hour >= 6) {	/* wait until tomorrow if past 06:00 */
		dbclean_cron_sub += 24*60*60;
	}
	if (dbclean_cron_sub < sn.tv_sec + 24*60*60)
		dbclean_cron_sub += 24*60*60;

	return db_flush_magic(dcc_emsg);
}



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;
		}
		if (sp->listen >= 0) {
			close(sp->listen);
			sp->listen = -1;
		}
	}
}



/* clean shut down */
static void NRATTRIB
db_quit(int exitcode, const char *p, ...)
{
	va_list args;

	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.
	 * 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 some UNIX systems that lack
	 * inter-process coherent mmap(),
	 * except on BSD/OS where close() takes forever. */
#ifdef __bsdi
	close_srvr_socs();
#endif
	stop_children();
	db_close(0, 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;
	too_busy = 0;
	next_flods_ck = db_time.tv_sec;
	(void)signal(sig, SIG_DFL);	/* catch it only once */
}



/* SIGHUP hurries checking the configuration files */
static void
sighup(int sig UATTRIB)
{
	next_flods_ck = db_time.tv_sec;
}


static void
stop_children(void)
{
	if (resolve_hosts_pid > 0) {
		kill(resolve_hosts_pid, SIGKILL);
		resolve_hosts_pid = -1;
	}

	if (dbclean_pid > 0) {
		kill(dbclean_pid, SIGKILL);
		dbclean_pid = -1;
	}
}
