/* Distributed Checksum Clearinghouse
 *
 * server
 *
 * --C-LICENSE--
 * $Revision: 1.352 $
 */

#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.c;
static pid_t dbclean_pid = -1;
static char dbclean_str[] = "dbclean";
#define MAX_DBCLEAN_FLAGS 50
static char dbclean_flags[MAX_DBCLEAN_FLAGS+1+1] = "-Pq";
static int dbclean_flags_len = LITZ("-Pq");
#define MAX_DBCLEAN_ARGS 20
static const char *dbclean_argv[MAX_DBCLEAN_ARGS+2+1] = {
	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 */
static time_t dbclean_wfix_range;
#define WFIX_POST_CLEAN_SECS (2*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 */

const char *need_del_dbclean;
time_t del_dbclean_next;
time_t dbclean_limit;
static time_t next_failsafe_clean_secs;	/* timer for missing cron job */
static time_t next_failsafe_clean_delay;
static time_t last_cron_clean;

int iflod_dup_chain;			/* hitting gross duplicate chains */

DCC_TRACEMASK tracemask;

u_char background = 1;
int stopint;				/* <0=stopping  >0=stop signaled */

static time_t flods_ck_secs = FLODS_CK_SECS;

const char *brand = "";

static char *my_srvr_id_str;
DCC_SRVR_ID my_srvr_id;

DCC_GETH use_ipv46 = DCC_GETH_DEF;
u_char have_ipv6_peer;
static u_char ipv4_works;
static u_char ipv6_works;

in_port_t def_port;			/* default port #, network byte order */
typedef struct port_list {
    struct port_list *fwd;
    in_port_t	port;			/* network byte order */
} PORT_LIST;
static PORT_LIST *ports;
SRVR_SOC *srvr_socs;
static SRVR_SOC **srvr_socs_end = &srvr_socs;
int socbuf;
static u_char socbuf_set = 0;

#ifdef DCC_USE_DBCLEAN_F
static DB_OPEN_MODES db_mode = DB_OPEN_WRITE | DB_OPEN_DCCD_DEFAULT;
#else
static DB_OPEN_MODES db_mode = DB_OPEN_MSYNC | DB_OPEN_DCCD_DEFAULT;
#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_range;	/* when to advertise */

u_char anon_off;			/* turn off anonymous access */
DCC_PTIME 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=DB sync'ed */

static int total_ops;
static QUEUE *queue_free;
static QUEUE *queue_head;

/* assume or hope we can handle 500 requests/second */
u_int queue_max = 500*DCC_MAX_RTT_SECS;	/* ultimate bound on queue size */
static u_char queue_max_set = 0;
static u_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, const DCC_SOCKU *, in_port_t);
static void add_srvr_soc_any(SRVR_SOC **, SRVR_SOC **, u_char, in_port_t);
static int open_srvr_socs(int);
static void wfix_later(time_t);
static void recv_job(void) DCC_NORET;
static u_char new_job(SRVR_SOC *);
static void dccd_quit(void);

static void
usage(const char* barg)
{
	static const char usage_str[] = {
	    "usage: [-dVbfFQ] -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 [no-]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\"\n%s\n..."
				      " continuing",
				      barg, usage_str);
		else
			dcc_error_msg("%s\n... continuing", usage_str);
		complained = 1;
	}
}


int
main(int argc, char **argv)
{
	char *p;
	const char *rest;
	u_char print_version = 0;
	DCC_CK_TYPES type;
	DCC_SOCKU *sup;
	in_port_t port;
	char *addr_str;
	int new_embargo, new_window, new_white;
	DCC_TRACEMASK bit, trace_off = 0;
	int error, i;
	const char *cp;
	DCC_PATH msg_path;
	double d1, d2;
	u_long l;

	dcc_syslog_init(1, argv[0], 0);

	/* Ensure that arrays of DCC_CKs contain an even number
	 * so that structures containing them will have no structure packing */
	if (DCC_DIM_CKS != (DCC_DIM_CKS/2)*2)
		dcc_logbad(EX_SOFTWARE, "DCC_DIM_CKS is not even");

	/* 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 host name from gethostname()");
	strncpy((char *)host_id_sum.b, our_hostname, sizeof(host_id_sum));

	/* fix dccd.man if these change */
	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", "2000");
	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':
		case '4':
			/* obsolete with *.2.104 */
			break;

		case 'd':
			add_dbclean_flag('d');
			++db_debug;
			break;

		case 'V':
			dcc_version_print();
			print_version = 1;
			break;

		case 'b':
			background = 0;
			break;

		case 'f':
			db_mode &= ~(DB_OPEN_MSYNC | DB_OPEN_WRITE
				     | DB_OPEN_DCCD_DEFAULT);
			db_mode |= DB_OPEN_MSYNC;
			add_dbclean_flag('f');
			break;

		case 'F':
			db_mode &= ~(DB_OPEN_MSYNC | DB_OPEN_WRITE
				     | DB_OPEN_DCCD_DEFAULT);
			db_mode |= DB_OPEN_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(emsg_ex_code(&dcc_emsg),
					   "%s", dcc_emsg.c);
			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.b, 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;
				queue_max_set = 1;
			}
			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 (ck_word_comma(&rest, "weak-body")
				    || ck_word_comma(&rest, "weak_body")
				    || ck_word_comma(&rest, "weak")) {
					grey_weak_body = 1;
					continue;
				}
				if (ck_word_comma(&rest, "weak-IP")
				    || ck_word_comma(&rest, "weak_IP")) {
					grey_weak_ip = 1;
					continue;
				}
				if (!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 > 1.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-")
			    || !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 (!CLITCMP(optarg, "no-")) {
				optarg += LITZ("no-");
				i = 0;
			} else {
				i = 1;
			}
			bit = 0;
			if (!strcasecmp(optarg, "ADMN")) {
				bit = DCC_TRACE_ADMN_BIT;
			} else if (!strcasecmp(optarg, "ANON")) {
				bit = DCC_TRACE_ANON_BIT;
			} else if (!strcasecmp(optarg, "CLNT")) {
				bit = DCC_TRACE_CLNT_BIT;
			} else if (!strcasecmp(optarg, "RLIM")) {
				bit = DCC_TRACE_RLIM_BIT;
			} else if (!strcasecmp(optarg, "QUERY")) {
				bit = DCC_TRACE_QUERY_BIT;
			} else if (!strcasecmp(optarg, "RIDC")) {
				bit = DCC_TRACE_RIDC_BIT;
			} else if (!strcasecmp(optarg, "FLOOD1")
				   || !strcasecmp(optarg, "FLOOD")) {
				bit = DCC_TRACE_FLOD1_BIT;
			} else if (!strcasecmp(optarg, "FLOOD2")) {
				bit = DCC_TRACE_FLOD2_BIT;
			} else if (!strcasecmp(optarg, "IDS")) {
				;	/* obsolete */;
			} else if (!strcasecmp(optarg, "BL")) {
				bit = DCC_TRACE_BL_BIT;
			} else if (!strcasecmp(optarg, "DB")) {
				bit = DCC_TRACE_DB_BIT;
			} else if (!strcasecmp(optarg, "WLIST")) {
				bit = DCC_TRACE_WLIST_BIT;
			} else {
				dcc_error_msg("invalid trace mode \"%s\"",
					      optarg);
			}
			if (i) {
				tracemask |= bit;
				trace_off &= ~bit;
			} else {
				tracemask &= ~bit;
				trace_off |= bit;
			}
			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.c);
			} 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;
		tracemask |= (DCC_TRACE_GREY_DEF & ~trace_off);
	} else {
		tracemask |= (DCC_TRACE_DEF & ~trace_off);
	}

	/* delay checking IPv4 & IPv6 until debugging is known
	 * for the messages */
	if (ipv_check(&dcc_emsg, AF_INET, 0)) {
		ipv4_works = 1;
	} else {
		/* always complain about no IPv4 */
		dcc_error_msg("%s", dcc_emsg.c);
		ipv4_works = 0;
	}
	if (ipv_check(&dcc_emsg, AF_INET6, 0)) {
		ipv6_works = 1;
	} else {
		if (db_debug)
			dcc_error_msg("%s", dcc_emsg.c);
		ipv6_works = 0;
	}

	if (!ipv4_works && ipv6_works) {
		use_ipv46 = DCC_GETH_V6;
	} else if (ipv4_works && !ipv6_works) {
		use_ipv46 = DCC_GETH_V4;
	}

	/* parse addresses after we know whether IPv4 and IPv6 are disabled */
	addr_str = 0;
	for (aarg = &aargs[0]; aarg <= LAST(aargs) && *aarg; ++aarg) {
		char hostname[DCC_MAXDOMAINLEN];

		if (!addr_str)
			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.c);
			continue;
		}
		rest += strspn(rest, DCC_WHITESPACE);
		if (*rest != '\0')
			dcc_error_msg("unrecognized port number in"
				      "\"-a %s\"", *aarg);
		/* null hostname = "separate sockets on all local interfaces" */
		if (hostname[0] == '\0') {
			PORT_LIST *pport = dcc_malloc(sizeof(*pport));
			pport->port = port;
			pport->fwd = ports;
			ports = pport;
			continue;
		}
		/* allow "@" for upward compatibility */
		if (!strcmp(hostname, "@"))
			hostname[0] = '*';
		dcc_host_lock();
		if (!dcc_get_host(hostname, use_ipv46, &error)) {
			dcc_host_unlock();
			dcc_error_msg("%s: %s", *aarg, H_ERROR_STR1(error));
			continue;
		}
		for (sup = dcc_hostaddrs; sup < dcc_hostaddrs_end; ++sup) {
			SRVR_SOC *sp;

			sp = add_srvr_soc(SRVR_SOC_ADDR | SRVR_SOC_NEW,
					  sup, port);
			if ((sp->flags & SRVR_SOC_NEW)
			    && db_debug != 0) {
				dcc_trace_msg("start request listening on %s",
					      dcc_su2str_err(&sp->udp_su));
				sp->flags &= ~SRVR_SOC_NEW;
			}
		}
		dcc_host_unlock();
	}
	if (addr_str) {
		/* tell dbclean about one "-a addr" */
		add_dbclean_arg("-a");
		add_dbclean_arg(addr_str);
	}

	if (!queue_max_set) {
		queue_max = rl_sub_rate.per_sec*2;
		if (!anon_off)
			queue_max += rl_all_anon_rate.per_sec;
		queue_max /= RL_SCALE;
		queue_max *= DCC_MAX_RTT_SECS;
	}

	dcc_clnt_unthread_init();
	if (!dcc_cdhome(&dcc_emsg, homedir, 0))
		dcc_logbad(emsg_ex_code(&dcc_emsg), "%s", dcc_emsg.c);

	i = check_load_ids(1);
	if (i < 0)
		dcc_logbad(emsg_ex_code(&dcc_emsg), "%s", dcc_emsg.c);
	else if (!i)
		dcc_error_msg("%s", dcc_emsg.c);

	if (!def_port)
		def_port = DCC_GREY2PORT(grey_on);
	if (!srvr_socs && !ports) {
		/* listen to at least one port */
		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 */
	if (open_srvr_socs(45) <= 0)
		dcc_logbad(EX_OSERR, "failed to open any server sockets");

	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.c);
		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.c);
			dcc_logbad(EX_NOINPUT,
				   "could not start database %s",
				   dcc_fnm2abs_msg(&msg_path, db_nm.c));
		}
	}
	/* do not start if dbclean is running */
	if (!lock_dbclean(&dcc_emsg, db_nm.c))
		dcc_logbad(emsg_ex_code(&dcc_emsg),
			   "%s: dbclean already running?", dcc_emsg.c);
	unlock_dbclean();

	host_id_next = db_time.tv_sec + (host_id_range = 5);
	check_blacklist_file();
	flods_init();
	rl_init();

	stats_clear();
	/* get counters from the file unless they have never been set */
	if (flod_mmaps != 0
	    && flod_mmaps->dccd_stats.reset != 0) {
		memcpy(&dccd_stats, &flod_mmaps->dccd_stats,
		       sizeof(dccd_stats));
	}
	dcc_trace_msg(DCC_VERSION" listening to port %d  %s",
		      ntohs(DCC_SU_PORT(&srvr_socs->udp_su)), dcc_homedir.c);

	recv_job();
}



static SRVR_SOC *			/* 0 or new entry */
add_srvr_soc(u_char flags,
	     const DCC_SOCKU *sup,
	     in_port_t port)
{
	SRVR_SOC *sp;

	/* notice duplicates or previously known interfaces */
	for (sp = srvr_socs; sp; sp = sp->fwd) {
		if (DCC_SUnP_EQ(&sp->udp_su, sup)
		    && DCC_SU_PORT(&sp->udp_su) == port)
			return sp;
	}

	sp = dcc_malloc(sizeof(*sp));
	memset(sp, 0, sizeof(*sp));
	sp->flags = flags;
	sp->udp = -1;
	sp->listen = -1;
	sp->udp_su = *sup;
	DCC_SU_PORT(&sp->udp_su) = port;

	*srvr_socs_end = sp;
	srvr_socs_end = &sp->fwd;

	return sp;
}



/* add one or two socket addresses for INADDR_ANY */
static void
add_srvr_soc_any(SRVR_SOC **sp1p, SRVR_SOC **sp2p, u_char flags, in_port_t port)
{
	DCC_SOCKU su;
	SRVR_SOC *sp;

	/* first create the IPv4 socket */
	if (use_ipv46 == DCC_GETH_V6) {
		sp = 0;
	} else {
		dcc_mk_su(&su, AF_INET, 0, 0, port);
		sp = add_srvr_soc(flags, &su, port);
	}
	if (sp1p)
		*sp1p = sp;

	/* then create the IPv6 socket */
	if (use_ipv46 == DCC_GETH_V4) {
		sp = 0;
	} else {
		dcc_mk_su(&su, AF_INET6, 0, 0, port);
		sp = add_srvr_soc(flags, &su, port);
	}
	if (sp2p)
		*sp2p = sp;
}



static int				/* # of sockets opened */
open_srvr_socs(int retry_secs)
{
	int *retry_secsp;
	SRVR_SOC *sp;
	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 (!DCC_SU_PORT(&sp->udp_su))
			DCC_SU_PORT(&sp->udp_su) = def_port;

		if (!udp_create(&dcc_emsg, &sp->udp,
				    &sp->udp_su, 1, 1, retry_secsp)) {
			dcc_error_msg("%s", dcc_emsg.c);
			continue;
		}

		if (!set_rcvbuf(&dcc_emsg, sp->udp, &sp->udp_su,
				&socbuf, &socbuf_set))
				dcc_error_msg("%s", dcc_emsg.c);

		++num_socs;
	}

	return num_socs;
}



/* get ready to bind to local IP addreses */
static u_char				/* 1=added an interface */
add_ifs(u_char not_quiet)
{
	u_char added;
	PORT_LIST *pport;
	static u_char complained;
#ifdef HAVE_GETIFADDRS
	struct ifaddrs *ifap0, *ifap;
	int num_ifs;
#endif

	/* look at the interfaces only if there is at least one port number
	 * without an associated IP addresses */
	if (!ports)
		return 0;

	added = 0;

#ifdef HAVE_GETIFADDRS
	if (0 > getifaddrs(&ifap0)) {
		dcc_error_msg("getifaddrs(): %s", ERROR_STR());
		ifap0 = 0;
	}

	/* Assume we have only site- or link-local IPv6 interfaces.
	 * Prefer IPv4 until we find a non-trivial IPv6 interface. */
	if (use_ipv46 == DCC_GETH_V64)
		use_ipv46 = DCC_GETH_V46;

	num_ifs = 0;
	for (pport = ports; pport; pport = pport->fwd) {
		const DCC_SOCKU *sup;
		const SRVR_SOC *ipv4_any_listener = 0;
		const SRVR_SOC *ipv6_any_listener = 0;
		const SRVR_SOC **any_listenerp = 0;

		for (ifap = ifap0; ifap; ifap = ifap->ifa_next) {
			SRVR_SOC *sp;

			if (!(ifap->ifa_flags & IFF_UP))
				continue;
			sup = (DCC_SOCKU *)ifap->ifa_addr;
			if (!sup)
				continue;
			switch (sup->sa.sa_family) {
			case AF_INET:
				if (use_ipv46 == DCC_GETH_V6)
					continue;
				++num_ifs;
				any_listenerp = &ipv4_any_listener;
				break;
			case AF_INET6:
				if (use_ipv46 == DCC_GETH_V4)
					continue;
				++num_ifs;
				any_listenerp = &ipv6_any_listener;
				break;
			default:
				continue;
			}

			sp = add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_NEW,
					  sup, pport->port);
			if (sp->flags & SRVR_SOC_NEW) {
				added = 1;
				if (not_quiet)
					dcc_trace_msg("start request listening"
						      " on %s",
						      dcc_su2str_err(&sp
							  ->udp_su));
			}
			sp->flags &= ~(SRVR_SOC_MARK | SRVR_SOC_NEW);

			/* The first IPv4 and IPv6 UDP sockets have associated
			 * TCP INADDR_ANY sockets listening for incoming
			 * flood connections */
			if (*any_listenerp == sp) {
				/* Interfaces can have duplicate addresses */
				continue;
			}
			if (!*any_listenerp) {
				*any_listenerp = sp;
				if (!(sp->flags & SRVR_SOC_ANY_LISTEN)) {
					sp->flags |= SRVR_SOC_ANY_LISTEN;
					added = 1;
				}
				continue;
			}

			if (sp->flags & SRVR_SOC_ANY_LISTEN) {
				sp->flags &= ~SRVR_SOC_ANY_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 */
	if (ifap0)
		freeifaddrs(ifap0);
#endif

	if (num_ifs > 0) {
		complained = 0;
		return added;
	}
#endif /* HAVE_GETIFADDRS */

	if (!complained) {
		dcc_error_msg("failed to find any network interfaces"
			      " with getifaddrs()");
		complained = 1;
	}

	/* we got no joy from getifaddrs(), so add INADDR_ANY sockets */
	for (pport = ports; pport; pport = pport->fwd) {
		SRVR_SOC *sp1, *sp2;

		add_srvr_soc_any(&sp1, &sp2,
				 SRVR_SOC_NEW
				 | SRVR_SOC_IF | SRVR_SOC_ANY_LISTEN,
				 pport->port);
		if (sp1) {
			if (sp1->flags & SRVR_SOC_NEW) {
				added = 1;
				if (not_quiet)
					dcc_trace_msg("request listening on %s",
						      dcc_su2str_err(&sp1
							  ->udp_su));
			}
			sp1->flags &= ~(SRVR_SOC_MARK | SRVR_SOC_NEW);
		}

		if (sp2) {
			if (sp2->flags & SRVR_SOC_NEW) {
				added = 1;
				if (not_quiet)
					dcc_trace_msg("request listening on %s",
						      dcc_su2str_err(&sp2
							  ->udp_su));
			}
			sp2->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);

	have_ipv6_peer = 0;

	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)) {
			/* switch to prefering IPv6 if this is a
			 * non-trivial IPv6 address */
			if (sp->udp_su.sa.sa_family == AF_INET6
			    && !dcc_su_is_loopback(&sp->udp_su)
			    && !DCC_SU_IS_LINKLOCAL(&sp->udp_su)
			    && !DCC_SU_IS_SITELOCAL(&sp->udp_su)) {
				have_ipv6_peer = 1;
				if (use_ipv46 == DCC_GETH_V46)
					use_ipv46 = DCC_GETH_V64;
			}

			spp = &sp->fwd;

		} else {
			/* Forget interfaces that have disappeared. */
			dcc_trace_msg("stop listening on %s",
				      dcc_su2str_err(&sp->udp_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 >= MAX_DBCLEAN_ARGS)
		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
	if (status == EX_OK) {
		ok = 1;
	} else {
		dcc_error_msg("dbclean exited with status %d", status);
		ok = 0;
	}
#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%s%s",
		     ssd_mode ? "u" : "",
		     (use_ipv46 == DCC_GETH_V4) ? "4"
		     : (use_ipv46 == DCC_GETH_V6) ? "6"
		     : "",
		     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);
		after_fork();
		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;

	db_close(DB_CLOSE_FORK);

	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;
	dbclean_wfix_range = delay;
}



/* measure load to see if the clients flee */
static double				/* <0.0 if not enough time to say */
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;
		dbclean_wfix_range = WFIX_MEASURE_SECS;
		wfix_ops = total_ops;
		return -1.0;
	}
	return (total_ops - wfix_ops) / secs;
}



static u_char
wfix_ck_timer(time_t secs)
{
	if (secs == 0)
		return 1;

	if (db_time.tv_sec >= secs - WFIX_PRE_CLEAN_SECS) {
		dbclean_wfix_secs = secs + WFIX_POST_CLEAN_SECS;
		dbclean_wfix_range = (WFIX_POST_CLEAN_SECS
				      + secs - db_time.tv_sec);
		if (!DB_IS_TIME(dbclean_wfix_secs, dbclean_wfix_range))
			return 0;
	}

	return 1;
}



static u_char				/* 1=need dbclean now */
wfix(char *reason, u_int reason_len)
{
	double rate;
	time_t sn_secs;
	DB_PTR cur_size, tgt_size;
	char cause[100];
	int cause_len;

	/* stop everything if dbclean is running */
	if (dbclean_running || 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, dbclean_wfix_range))
			return 0;

		/* no quick cleaning soon after the database was created,
		 * cleaned or repaired */
		sn_secs = ts2secs(&db_parms.sn);
		if (sn_secs > db_time.tv_sec)
			sn_secs = 0;
		if (sn_secs < dbclean_failed
		    && dbclean_failed <= db_time.tv_sec)
			sn_secs = dbclean_failed;
		if (!wfix_ck_timer(sn_secs))
			return 0;

		/* Check later if dbclean might run soon.
		 * Dbclean should run 1 day after cron last ran it or when
		 * the failsafe timer expires. */
		if (!wfix_ck_timer(next_failsafe_clean_secs))
			return 0;
		if (!wfix_ck_timer(last_cron_clean + 24*60*60))
			return 0;

		/* check later if the database does not have bad duplicate
		 * chains and is not too large */
		if (iflod_dup_chain == 0
		    && (db_fsize + db_hash_fsize
			< (wfix_size_set ? wfix_size : db_max_byte))) {
			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;
			dbclean_wfix_range = 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;
		cur_size = db_fsize + db_hash_fsize;
		tgt_size = wfix_size_set ? wfix_size : db_max_byte;
		cause_len = 0;
		cause[0] = '\0';
		if (iflod_dup_chain != 0)
			cause_len = snprintf(cause, sizeof(cause),
					     "flooding duplicate chain of %d",
					     iflod_dup_chain);
		if (cause_len == 0
		    || (cause_len < ISZ(cause)-1
			&& cur_size > tgt_size))
			cause_len += snprintf(&cause[cause_len],
					      sizeof(cause) - cause_len,
					      "%sdatabase size "L_DPAT">"L_DPAT,
					      cause_len ? " and " : "",
					      cur_size, tgt_size);
		if (rate <= wfix_quiet_rate
		    || rate <= wfix_busy_rate * wfix_rate_change) {
			snprintf(reason, reason_len,
				 "%s; load changed from %0.1f to %0.1f",
				 cause, wfix_busy_rate, rate);
			return 1;
		}
		/* Other DCC servers did not take over the load. Maybe later */
		if (db_debug)
			dcc_trace_msg("%s but load changed from %0.1f to %0.1f",
				      cause, 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;
	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";
	}

	if (!reason) {
		DB_HADDR hash_free;

		hash_free = db_hash_len - db_hash_used;
		if (hash_free < MIN_FREE_HASH_ENTRIES
		    || hash_free < db_hash_len/20) {
			/* try to expand the hash table when there are 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";
		}
	}

	/* check nothing else if it is not yet time */
	if (!reason) {
		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 and send changes to the disk
		 * if we are so idle that we have nothing to flush,
		 * unless dbclean is running
		 * we are avoiding writing to an SSD disk.  */
		if (DB_IS_LOCKED() && db_need_flush_secs == 0
		    && !dbclean_running && !ssd_mode)
			db_unload(0, DB_UNLOAD_ONE);

		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.c);

			check_blacklist_file();

#if defined(HAVE_GETIFADDRS) && defined(HAVE_FREEIFADDRS)
			/* check again 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();

		/* add server-ID and kludge records */
		if (DB_IS_TIME(host_id_next, host_id_range)
		    && DB_IS_LOCKED()
		    && !db_failed_line) {
			DCC_SUM ck;
			time_t datestamp;

			host_id_next = 0;   /* need a new timer */

			/* broadcast a claim to our server-ID */
			datestamp = db_time.tv_sec;
			datestamp -= datestamp % SRVR_ID_REP_SECS;
			refresh_srvr_rcd(&host_id_sum, my_srvr_id,
					 datestamp,
					 datestamp + SRVR_ID_REP_SECS,
					 "adding server-ID claim");

			/* Install keepalive or date records.
			 * They are sent to ensure that there is always
			 * a position to acknowledge even with a server-ID
			 * mapping that stops all reports.
			 * They are also used to limit checks for duplicate
			 * incoming flooded records.  For that purpose, it
			 * is more important that they be present than
			 * that they be accurate.  They must be present
			 * before any locally added record with a timestamp
			 * after their key (checksum) value. */
			datestamp = db_time.tv_sec + DATE_RCD_REP_SECS;
			datestamp -= datestamp % DATE_RCD_REP_SECS;
			date_rcd_ck(&ck, datestamp);
			refresh_srvr_rcd(&ck, DCC_ID_SRVR_DATE,
					 datestamp - DATE_RCD_REP_SECS-1,
					 datestamp,
					 "adding timestamp");
		}
	}

	/* collect zombies */
	flod_names_resolve_ck();
	check_dbclean(WNOHANG);

	if (!reason
	    && DB_IS_TIME(next_failsafe_clean_secs,
			  next_failsafe_clean_delay)) {
		reason = "work around broken cron job";
		mode = "Rfailsafe";
	}

	if (!reason
	    && DB_IS_TIME(next_failsafe_clean_secs,
			  next_failsafe_clean_delay)
	    && (!dbclean_running
		|| DB_IS_TIME(dbclean_limit+4*60*60,
			      4*60*60+dbclean_limit_secs))) {
		/* try to failsafe clean even if dbclean seems to be running
		 * if it seems to have running more than 4 hours */
		reason = "work around broken cron job and stuck dbclean";
		mode = "Rfailsafe";
	}

	if (!reason
	    && 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";
	}

	if (!reason
	    && DB_IS_TIME(dbclean_wfix_secs, dbclean_wfix_range)
	    && 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 && !stopint) {
			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 DCC_NORET
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.c[0] = '\0';

		if (stopint)
			dccd_quit();

		if (db_has_failed != db_failed_line) {
			db_has_failed = db_failed_line;
			if (db_failed_line) {
				++flods_off;
				flods_stop("database corrupt", 1);
			}
		}

		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, DB_NEED_FLUSH_SECS))
				dccd_flush(TMSG_BIT(DB));
			if (db_need_flush_secs != 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 (dbclean_running
		    && DB_IS_LOCKED()
		    && (delay_us != 0
			|| tv_diff2us(&db_time, &db_locked) >= DCC_US/10)) {
			db_unload(0, DB_UNLOAD_ENOUGH);
			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, DCC_CLNT_ID id)
{
	QUEUE *qnext, **qp;

	if (!ck_clnt_id(q, id, anon_off)) {
		TMSG_OP(QUERY, q);
		free_q(q);
		return;
	}
	TMSG_OP(QUERY, q);

	++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, "after %d, drop queued %s", queue_cur, 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;
	char ob[DCC_OPBUF];
	DCC_CLNT_ID id;
	int recvlen, 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);
		/* 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)-sizeof(q->pkt)+sizeof(q->pkt.hdr));
	q->pkt.hdr.sender = DCC_ID_ANON;    /* for truncated packets */
	q->sp = sp;
	iov.iov_base = &q->pkt;
	msg.msg_name = &q->clnt_su;
	msg.msg_namelen = sizeof(q->clnt_su);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	recvlen = recvmsg(sp->udp, &msg, 0);
	if (recvlen < 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->udp_su), ERROR_STR());
		} else {
			dcc_error_msg("recvmsg(%s): %s",
				      dcc_su2str_err(&sp->udp_su), ERROR_STR());
		}
		free_q(q);
		return 0;
	}
	if (q->clnt_su.sa.sa_family != sp->udp_su.sa.sa_family
	    && !dcc_ipv4su2ipv6su(&q->clnt_su, &q->clnt_su)) {
		dcc_error_msg("recvmsg address family %d instead of %d",
			      q->clnt_su.sa.sa_family,
			      sp->udp_su.sa.sa_family);
		free_q(q);
		return 1;
	}

	q->pkt_len = recvlen;
	if (DCC_SU_PORT(&q->clnt_su) == 0) {
		junk_msg(q, 1, "source port 0");
		free_q(q);
		return 1;
	}

	if (recvlen < ISZ(DCC_HDR)) {
		junk_msg(q, 0, "short request of %d bytes", recvlen);
		free_q(q);
		return 1;
	}
	j = ntohs(q->pkt.hdr.len);
	if (j != recvlen) {
		junk_msg(q, 0, "request with header length %d instead of %d",
			 j, recvlen);
		free_q(q);
		return 1;
	}

	if (q->pkt.hdr.pkt_vers > DCC_PKT_VERS_NOP_MAX
	    || q->pkt.hdr.pkt_vers < DCC_PKT_VERS_NOP_MIN
	    || (q->pkt.hdr.op != DCC_OP_NOP
		&& (q->pkt.hdr.pkt_vers > DCC_PKT_VERS_MAX
		    || q->pkt.hdr.pkt_vers < DCC_PKT_VERS_MIN))) {
		junk_msg(q, 1, "%s in unrecognized protocol version #%d",
			 qop2str(q), q->pkt.hdr.pkt_vers);
		free_q(q);
		return 1;
	}

	id = ntohl(q->pkt.hdr.sender);
	if (id != DCC_ID_ANON
	    && (id < DCC_SRVR_ID_MIN || id > DCC_CLNT_ID_MAX)) {
		junk_msg(q, 1, "invalid ID %d for %s", id, qop2str(q));
		free_q(q);
		return 1;
	}

	q->answer = req_recv_time;

	switch ((DCC_OPS)q->pkt.hdr.op) {
	case DCC_OP_NOP:
		do_nop(q, id);
		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, id);
		return 1;

	case DCC_OP_QUERY:
		add_queue(q, id);
		return 1;

	case DCC_OP_ADMN:
		do_admn(q, id);
		free_q(q);
		return 1;

	case DCC_OP_DELETE:
		do_delete(q, id);
		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, id);
		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, id);
		free_q(q);
		return 1;

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

	junk_msg(q, 1, "invalid %s, ID %d",
		 dcc_hdr_op2str(ob, sizeof(ob), &q->pkt.hdr),
		 (DCC_CLNT_ID)ntohl(q->pkt.hdr.sender));
	free_q(q);
	return 1;
}



void
free_q(QUEUE *q)
{
	if (q->ip_rl)
		--q->ip_rl->ref_cnt;
	if (q->block_rl)
		--q->block_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 tgt_sec;
	DCC_TGTS tgts;
	int i;

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

	gettimeofday(&db_time, 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;
		if (summarize_delay_secs < 1)
			summarize_delay_secs = 1;

	} 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_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(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 = min(DCC_OLD_SPAM_SECS, 4*60*60);
		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 < DB_EXPIRE_SECS_DEF_MIN)
			summarize_delay_secs = DB_EXPIRE_SECS_DEF_MIN;
	}
	summarize_limit = DB_PTR_NULL;
	summarize_limit_secs = 0;

	/* adjust the thresholds after possible changes to kept checksums */
	set_db_tholds(db_parms.nokeep_cks);

	last_cron_clean = db_parms.cleaned_cron;
	if (last_cron_clean > db_time.tv_sec)
		last_cron_clean = 0;

	/* 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 + 15 minutes after cron should have last cleaned the
	 *	database, if it has been cleaned by cron within the last 3 days
	 * or when it was last cleaned by this mechanism
	 * or 3 minutes past a random quarter hour between 01:00 and 04:45 */
#   define FS_CLEAN_HOUR_START  1
#   define FS_CLEAN_HOURS	4
#   define FS_CLEAN_HOUR_END	(FS_CLEAN_HOUR_START+FS_CLEAN_HOURS-1)
#   define FS_CLEAN_MIN_START	3
#   define FS_CLEAN_MIN_INTERVAL 15
#   define FS_CLEAN_INTS	((FS_CLEAN_HOURS*60)/FS_CLEAN_MIN_INTERVAL)

	/* Schedule failsafe cleaning 15 minutes after the cron job in case
	 * it runs on some days of the week,
	 * except once every sixth fail safe cleaning. */
	tgt_sec = last_cron_clean;
	if (tgt_sec != 0
	    && db_parms.failsafe_cleanings % 6 != 5) {
		tgt_sec += 15*60;
		/* coordinate greylist and DCC cleaning */
		if (!grey_on)
			tgt_sec += 15*60;

	} else {
		struct tm tm;
		u_int rnum;

		/* pick an arbitrary quarter hour that depends
		 * unpredictably on the host and usually differs
		 * by 1 for the greylist server */
		rnum = db_time.tv_sec / (24*60*60);
		if (ports)		/* this includes greylist difference */
			rnum += ntohs(ports->port);
		for (i = 0; i < ISZ(our_hostname); ++i) {
			if (!our_hostname[i])
				break;
			rnum += our_hostname[i]*i;
		}
		rnum %= FS_CLEAN_INTS;

		dcc_localtime(db_time.tv_sec, &tm);
		tm.tm_hour = (rnum%FS_CLEAN_HOURS
			      + FS_CLEAN_HOUR_START);
		rnum /= FS_CLEAN_HOURS;
		tm.tm_min = (rnum*FS_CLEAN_MIN_INTERVAL
			     + FS_CLEAN_MIN_START);
		tgt_sec = mktime(&tm);
	}

	/* Round down to a minute to prevent creep due to
	 * delays starting dbclean. */
	tgt_sec -= tgt_sec % 60;

	tgt_sec %= (24*60*60);

	/* Start to schedule the failsafe cleaning at the target second
	 * in the next 24 hours */
	next_failsafe_clean_secs = db_time.tv_sec;
	next_failsafe_clean_secs -= (next_failsafe_clean_secs % (24*60*60));
	next_failsafe_clean_secs += tgt_sec;
	if (next_failsafe_clean_secs <= db_time.tv_sec)
		next_failsafe_clean_secs += 24*60*60;
	next_failsafe_clean_delay = next_failsafe_clean_secs - db_time.tv_sec;

	/* The next failsafe cleaning should happen a day after the
	 * most recent failsafe cleaning, so skip today if a new failsafe
	 * time is within 12 hours of the last failsafe cleaning. */
	if (TIME_T(next_failsafe_clean_secs) < (TIME_T(db_parms.cleaned)
						+ 12*60*60)
	    && TIME_T(db_parms.cleaned) <= db_time.tv_sec) {
		next_failsafe_clean_secs += 24*60*60;
		next_failsafe_clean_delay += 24*60*60;
	}

	/* Delay failsafe cleaning at least 36 hours after the last cron job
	 * in case the cron job runs at different times on different days.
	 * Failsafe cleaning should never be running when the cron job starts.
	 * Do not failsafe clean during the first 2 days after the
	 * database was created to give the cron job a chance. */
	while (TIME_T(next_failsafe_clean_secs) < last_cron_clean + 36*60*60
	       || (TIME_T(next_failsafe_clean_secs) <= (TIME_T(db_parms.cleared)
							+ 2*24*60*60)
		   && TIME_T(db_parms.cleared) <= db_time.tv_sec)) {
		next_failsafe_clean_secs += 24*60*60;
		next_failsafe_clean_delay += 24*60*60;
	}

	total_ops = 0;

	iflod_dup_chain = 0;
	complained_missing_date = 1;

	/* push our thresholds and flags to the file */
	return db_flush_parms(0);
}



/* clean shut down */
static void
dccd_quit(void)
{
	static u_char second_time;
	DB_UNLOAD_MODE unload_mode;
	DB_CLOSE_MODE close_mode;
	int exitcode;

	if (stopint < 0) {
		flods_ck_secs = SHUTDOWN_DELAY;
		if (!second_time) {
			second_time = 1;
			flods_off = 100000;
			flods_stop("server stopping", 0);
			check_dbclean(WNOHANG);
			if (dbclean_pid > 0) {
				kill(dbclean_pid, SIGINT);
				dbclean_pid = -1;
				usleep(100*1000);
			}
		}

		/* Start flushing the database
		 * while we wait for flooding to stop. */
		dbclean_running = 1;
		if (stop_mode == 2) {
			unload_mode = DB_UNLOAD_REBOOT_CLEAN;
			if (!second_time)
				dcc_trace_msg("gracefully stopping cleanly");
		} else if (stop_mode == 1) {
			unload_mode = DB_UNLOAD_REBOOT;
			if (!second_time)
				dcc_trace_msg("gracefully stopping for reboot");
		} else {
			unload_mode = DB_UNLOAD_RELEASE;
			if (!second_time)
				dcc_trace_msg("gracefully stopping");
		}
		db_unload(0, unload_mode);

		/* Return to stop flooding. */
		if (oflods.total != 0)
			return;

		/* get serious when the floods have stopped */
		exitcode = 0;
		if (stop_mode == 1) {
			dcc_trace_msg("gracefully stopping for reboot");
			close_mode = DB_CLOSE_REBOOT;
		} else if (stop_mode == 2) {
			dcc_trace_msg("gracefully stopping cleanly");
			close_mode = DB_CLOSE_STABLE;
		} else {
			dcc_trace_msg("gracefully stopping");
			close_mode = DB_CLOSE;
		}

	} else {
		exitcode = stopint | 128;
		if (stopint == SIGTERM) {
			dcc_error_msg("exiting for reboot with signal %d",
				      stopint);
			close_mode = DB_CLOSE_REBOOT;
		} else {
			dcc_error_msg("exiting with signal %d",
				      stopint);
			close_mode = DB_CLOSE;
		}
	}

	/* db_close() can take a long time, so close some things early. */
	stop_children();
	clients_save();

	db_close(close_mode);

	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 DCC_UNUSED)
{
	next_flods_ck = 0;
	got_hosts = 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);
	flod_names_resolve_ck();

	if (dbclean_pid > 0)
		kill(dbclean_pid, SIGKILL);
	check_dbclean(WNOHANG);
}
