/* Distributed Checksum Clearinghouse
 *
 * deal with incoming floods of checksums
 *
 * 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.138 $Revision$
 */

#include "dccd_defs.h"
#include <signal.h>
#include <sys/wait.h>


IFLODS iflods;

u_int complained_many_iflods;

time_t got_hosts;
pid_t resolve_hosts_pid = -1;

time_t boot_ok_time;			/* suppress flooding errors at first */

static u_char iflod_write(IFLOD_INFO *, void *, int, const char *, u_char);

static DCC_TS future_ts;
#define DCC_FUTURE_SECS	    (4*60*60)	/* refuse reports this far in advance */



ID_MAP_RESULT
id_map(DCC_SRVR_ID srvr, const OFLOD_OPTS *opts)
{
	int i;
	ID_MAP_RESULT result;

	/* apply the first server-ID map that matches, if any */
	i = opts->num_maps;
	while (--i >= 0) {
		if (opts->srvr_map[i].from_lo <= srvr
		    && opts->srvr_map[i].from_hi >= srvr) {
			result = opts->srvr_map[i].result;
			if (result == ID_MAP_SELF
			    && srvr == my_srvr_id)
				return ID_MAP_NO;
			return result;
		}
	}
	return ID_MAP_NO;
}



static const char *
rpt_id(const char *type, const DB_RCD* rcd,
       const IFLOD_INFO *ifp)
{
	static int bufno;
	static struct {
	    char    str[90];
	} bufs[4];
	char *s;

	s = bufs[bufno].str;
	bufno = (bufno+1) % DIM(bufs);

	snprintf(s, sizeof(bufs[0].str),
		 "%s%s %s ID=%d %s%s",
		 type ? "flooded " : "",
		 type ? type : "",
		 dcc_ts2str_err(rcd->ts),
		 DB_RCD_ID(rcd),
		 ifp ? "from " : "",
		 ifp ? ifp->hostname : "");
	return s;
}



void PATTRIB(5,6)
rpt_err(LAST_ERROR *ep, u_char bad, enum FLOD_ERR_OP new_op, int new_errno,
	const char *p, ...)
{
	va_list args;

	if (new_op == FLOD_ERR_SAME) {
	    new_op = ep->op;
	    new_errno = ep->old_errno;
	}

	if (!bad && (!DB_IS_TIME(ep->ok, LAST_ERROR_OK_SECS)
		     || !DB_IS_TIME(boot_ok_time, LAST_ERROR_ACT_SECS)
		     || (ep->op == new_op
			 && ep->old_errno == new_errno
			 && !DB_IS_TIME(ep->rep_report, LAST_ERROR_SECS))
		     || new_op == FLOD_ERR_LOCAL_OFF)) {
		if ((DCC_TRACE_FLOD_BIT & dccd_tracemask)
		    && (ep->op != new_op
			|| ep->old_errno != new_errno)) {
			va_start(args, p);
			dcc_vtrace_msg(p, args);
			va_end(args);
		}
		ep->op = new_op;
		ep->old_errno = new_errno;
		return;
	}

	va_start(args, p);
	dcc_verror_msg(p, args);
	va_end(args);

	ep->rep_report = db_time.tv_sec + LAST_ERROR_SECS;
	ep->op = new_op;
	ep->old_errno = new_errno;
}



u_char
set_flod_socket(int s, const char *hostname, const DCC_SOCKU *sup)
{
#if IP_TOS
	static u_char tos_ok = 1;
#endif
	int on;

	if (0 > fcntl(s, F_SETFD, FD_CLOEXEC))
		dcc_error_msg("fcntl(flod %s, F_SETFD, FD_CLOEXEC): %s",
			      hostname, ERROR_STR());

	if (-1 == fcntl(s, F_SETFL,
			fcntl(s, F_GETFL, 0) | O_NONBLOCK)) {
		dcc_error_msg("fcntl(flod %s, O_NONBLOCK): %s",
			      hostname, ERROR_STR());
		return 0;
	}

	on = 1;
	if (0 > setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
			   &on, sizeof(on)))
		dcc_error_msg("setsockopt(flod %s, SO_KEEPALIVE): %s",
			      hostname, ERROR_STR());

#ifdef IP_TOS
	/* It would be nice and clean to use netinet/ip.h for the definition
	 * of IPTOS_THROUGHPUT.  However, it is hard to use netinet/ip.h
	 * portably because in_sysm.h is required for n_long on some
	 * systems and not others.  A bunch of messy ./configure fiddling
	 * might patch that hassle, but the bit really ought to be the same
	 * as the old 0x08 in the IPv4 header. */
	if (sup->sa.sa_family == AF_INET
	    && tos_ok) {
		on = 0x08;		/* IPTOS_THROUGHPUT */
		if (0 > setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(on))) {
			dcc_error_msg("setsockopt(IP_TOS,"
				      " IPTOS_THROUGHPUT, %s): %s",
				      hostname, ERROR_STR());
			tos_ok = 0;
	      }
	}
#endif

	return 1;
}



/* see if the host name resolution process is still running */
u_char					/* 1=not running 0=please wait */
flod_names_resolve_ck(void)
{
	pid_t pid;
	int status;

	if (resolve_hosts_pid < 0)
		return 1;

	pid = waitpid(resolve_hosts_pid, &status, WNOHANG);
	if (pid == resolve_hosts_pid) {
		resolve_hosts_pid = -1;
		return 1;
	}

	/* check again soon */
	if (next_flods_ck > db_time.tv_sec + 1)
		next_flods_ck = db_time.tv_sec + 1;
	return 0;
}



static void
flod_names_resolve(void)
{
	FLOD_MMAP *mp;
	u_char ipv6, ok;

	for (mp = flod_mmaps->mmaps; mp <= LAST(flod_mmaps->mmaps); ++mp) {
		if (mp->hostname[0] == '\0'
		    || (mp->flags & OFLOD_MMAP_FG_PASSIVE))
			continue;
		ipv6 = ((mp->flags & OFLOD_MMAP_FG_IPv4) ? 0
			: (mp->flags & OFLOD_MMAP_FG_IPv6) ? 1
			: use_ipv6);
		dcc_host_lock();
		if (mp->flags & OFLOD_MMAP_FG_SOCKS)
			ok = dcc_get_host_SOCKS(mp->hostname, ipv6,
						&mp->host_error);
		else
			ok = dcc_get_host(mp->hostname, ipv6,
					  &mp->host_error);
		if (!ok) {
			TMSG2(FLOD, "failed to resolve %s: %s",
			      mp->hostname, DCC_HSTRERROR(mp->host_error));
		} else {
			mp->su = dcc_hostaddrs[0];
			*DCC_SU_PORT(&mp->su) = mp->port;
		}
		dcc_host_unlock();
	}
}



/* start a process to wait for the domain name system or other
 * hostname system to get the IP addresses of our flooding peers */
u_char					/* 1=finished 0=please wait */
flod_names_resolve_start(void)
{
	IFLOD_INFO *ifp;
	OFLOD_INFO *ofp;
	FLOD_MMAP *mp;

	if (!flod_mmaps
	    || !flod_names_resolve_ck())
		return 0;

	/* we're finished if we have recent address for all of the names */
	if (!DB_IS_TIME(got_hosts, FLOD_NAMES_RESOLVE_SECS))
		return 1;
	got_hosts = db_time.tv_sec + FLOD_NAMES_RESOLVE_SECS;

	if (!background) {
		TMSG(FLOD, "resolving hostnames in the foreground");
		flod_names_resolve();
		return 1;
	}

	for (mp = flod_mmaps->mmaps; mp <= LAST(flod_mmaps->mmaps); ++mp) {
		mp->su.sa.sa_family = AF_UNSPEC;
		mp->host_error = 0;
	}
	flod_sync(0);

	resolve_hosts_pid = fork();
	if (resolve_hosts_pid > 0) {
		/* check again soon */
		if (next_flods_ck > db_time.tv_sec + 1)
			next_flods_ck = db_time.tv_sec + 1;
		return 0;
	}

	if (resolve_hosts_pid == -1) {
		dcc_error_msg("fork(flod_names_resolve_start): %s",
			      ERROR_STR());
	} else {
		TMSG(FLOD, "resolving hostnames started");

		/* close files and sockets to avoid interfering with parent */
		db_close(0, -1);
		close_srvr_socs();
		for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) {
			if (ifp->s >= 0)
				close(ifp->s);
		}
		for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) {
			if (ofp->s >= 0)
				close(ofp->s);
		}

		flod_names_resolve();

		TMSG(FLOD, "resolving hostnames finished");

		exit(0);
	}
	return 1;
}



static void
iflod_clear(IFLOD_INFO *ifp, u_char fail)
{
	OFLOD_INFO *ofp;

	if (fail && (ofp = ifp->ofp) != 0
	    && (ofp->o_opts.flags & FLOD_OPT_SOCKS)) {
		if (ofp->in_try_secs == 0)
			ofp->in_try_secs = FLOD_RETRY_SECS;
		ofp->in_try_again = db_time.tv_sec + ofp->in_try_secs;
		TMSG2(FLOD, "postpone restarting flood from %s for %d seconds",
		      ofp->hostname, ofp->in_try_secs);
	}

	if (ifp->s >= 0)
		--iflods.active;
	memset(ifp, 0, sizeof(*ifp));
	ifp->s = -1;

	if (iflods.active == 0
	    && oflods.active == 0
	    && flods_st != FLODS_ST_ON)
		oflods_unmap();
}



/* put the flood counters in stable storage */
void
save_flod_cnt(OFLOD_INFO *ofp, u_char in_is_connected)
{
	FLOD_MMAP *mp;

	dccd_stats.iflod_total += ofp->cnts.total;
	dccd_stats.iflod_accepted += ofp->cnts.accepted;
	dccd_stats.iflod_stale += ofp->cnts.stale.val;
	dccd_stats.iflod_dup += ofp->cnts.dup.val;
	dccd_stats.iflod_ok2 += ofp->cnts.ok2.val;
	dccd_stats.iflod_not_deleted += ofp->cnts.not_deleted.val;

	mp = ofp->mp;
	if (mp) {
		mp->cnts.total += ofp->cnts.total;
		mp->cnts.accepted += ofp->cnts.accepted;
		mp->cnts.stale += ofp->cnts.stale.val;
		mp->cnts.dup += ofp->cnts.dup.val;
		mp->cnts.ok2 += ofp->cnts.ok2.val;
		mp->cnts.not_deleted += ofp->cnts.not_deleted.val;
		if (ofp->cnts.in_start != 0)
			mp->cnts.in_connected += (db_time.tv_sec
						  - ofp->cnts.in_start);
		if (ofp->cnts.in_start != 0 || in_is_connected)
			mp->cnts.in_last_connected = db_time.tv_sec;

		mp->cnts.out_reports += ofp->cnts.out_reports;
		if (ofp->cnts.out_start != 0)
			mp->cnts.out_connected += (db_time.tv_sec
						   - ofp->cnts.out_start);
		if (ofp->cnts.out_start != 0
		    || (ofp->flags & OFLOD_FG_CONNECTED))
			mp->cnts.out_last_connected = db_time.tv_sec;
	}

	memset(&ofp->cnts, 0, sizeof(ofp->cnts));
	if (in_is_connected)
		ofp->cnts.in_start = db_time.tv_sec;
	if (ofp->flags & OFLOD_FG_CONNECTED)
		ofp->cnts.out_start = db_time.tv_sec;
}



void PATTRIB(5,6)
iflod_close(IFLOD_INFO *ifp, u_char fail,
	    enum FLOD_ERR_OP err_op, int new_errno,
	    const char *pat, ...)
{
	struct {
	    DCC_FLOD_POS    last_pos;
	    DCC_FLOD_POS    end;
	    char	    str[DCC_FLOD_MAX_RESP];
	    char	    null;
	} resp;
	va_list args;
	OFLOD_INFO *ofp;
	struct linger nowait;
	void *wp;
	int wlen;
	const char *opstr;
	int print_errno;
	u_char is_ok;

	ofp = ifp->ofp;

	memset(&resp, 0, sizeof(resp));
	db_ptr2flod_pos(resp.end, DCC_FLOD_POS_END);
	if (!pat)
		pat = "";
	va_start(args, pat);
	wlen = vsnprintf(resp.str, sizeof(resp.str)+1, pat, args);
	va_end(args);

	/* notice if we think it went ok */
	is_ok = !memcmp(resp.str, DCC_FLOD_OK_STR, sizeof(DCC_FLOD_OK_STR)-1);

	/* add our counts to the string we will send the peer
	 * if there is room in our buffer
	 * and if it is a recognized peer for which we have counts
	 * and if some progress was made */
	if (ISZ(resp.str) - wlen > 30
	    && ofp != 0
	    && (is_ok || ofp->cnts.total != 0)) {
		while (wlen > 0
		       && (resp.str[wlen-1] == ':'
			   || resp.str[wlen-1] == ' '))
			--wlen;
		wlen += snprintf(&resp.str[wlen], ISZ(resp.str)-wlen+1,
				 ": %d received  %d accepted"
				 "  %d dup  %d stale"
				 "  %d bad whitelist  %d not deleted",
				 ofp->cnts.total, ofp->cnts.accepted,
				 ofp->cnts.dup.val, ofp->cnts.stale.val,
				 ofp->cnts.ok2.val, ofp->cnts.not_deleted.val);
	}
	if (wlen > ISZ(resp.str))
		wlen = ISZ(resp.str);

	/* if useful, prefix our final message with our final position */
	if (memcmp(ifp->pos, ifp->pos_sent, ISZ(ifp->pos))) {
		memcpy(resp.last_pos, ifp->pos, ISZ(resp.last_pos));
		wp = &resp.last_pos;
		wlen +=  ISZ(resp.end) + ISZ(resp.last_pos);
	} else {
		wp = &resp.end;
		wlen += ISZ(resp.end);
	}

	if (!is_ok && err_op == FLOD_ERR_NO_LINK)
		err_op = FLOD_ERR_PEER_FAIL;
	opstr = flod_stats_str(&print_errno, new_errno, err_op);
	if (ofp && ofp->mp) {
		rpt_err(&ofp->mp->i_err, 0, err_op, new_errno,
			"iflod close status fail=%d,ok=%d to %s: %s",
			fail, is_ok, ifp->hostname, resp.str);
	} else if (fail || !is_ok) {
		dcc_error_msg("iflod close status fail=%d,ok=%d to %s: %s %s",
			      fail, is_ok, ifp->hostname, opstr, resp.str);
	} else {
		TMSG3(FLOD, "iflod close status to %s: %s %s",
		      ifp->hostname, opstr, resp.str);
	}

	/* send the final status report to the sending flooder */
	if (!iflod_write(ifp, wp, wlen, "iflod_close()",
			 fail ? 2 : 1))
		fail = 1;

	if (ifp->s >= 0) {
		if (stopint
		    && !(ifp->flags & IFLOD_FG_FAST_LINGER)) {
			ifp->flags |= IFLOD_FG_FAST_LINGER;
			nowait.l_onoff = 1;
			nowait.l_linger = SHUTDOWN_DELAY;
			if (0 > setsockopt(ifp->s, SOL_SOCKET, SO_LINGER,
					   &nowait, sizeof(nowait))
			    && !fail)
				dcc_error_msg("setsockopt(SO_LINGER %s): %s",
					      ofp->hostname, ERROR_STR());
		}

		if (0 > close(ifp->s)
		    && !fail)
			dcc_error_msg("close(iflod %s): %s",
				      ifp->hostname, ERROR_STR());
	}

	if (ofp != 0)
		save_flod_cnt(ofp, 0);

	iflod_clear(ifp, fail);
}



static u_char
iflod_write(IFLOD_INFO *ifp,
	    void *buf, int buf_len,
	    const char *type,		/* string describing operation */
	    u_char close_it)		/* 0=iflod_close() on error, */
{					/* 1=complain, 2=ignore error */
	int i;

	if (!(ifp->flags & IFLOD_FG_CONNECTED))
		return 1;

	/* if we don't know the corresponding output stream because we have
	 * not yet seen any authentication, we at least know the connection
	 * did not involve SOCKS because we did not originate it. */
	if (ifp->ofp
	    && (ifp->ofp->o_opts.flags & FLOD_OPT_SOCKS))
		i = Rsend(ifp->s, buf, buf_len, 0);
	else
		i = send(ifp->s, buf, buf_len, 0);
	if (i == buf_len)
		return 1;

	if (i < 0) {
		if (close_it == 0) {
			iflod_close(ifp, 1, FLOD_ERR_IO, errno,
				    "send(%s %s): %s",
				    type, ifp->hostname, ERROR_STR());
		} else if (close_it == 1) {
			dcc_error_msg("send(%s %s): %s",
				      type, ifp->hostname, ERROR_STR());
		}
	} else {
		if (close_it == 0) {
			iflod_close(ifp, 1, FLOD_ERR_IO, 0,
				    "send(%s %s)=%d not %d",
				    type, ifp->hostname, i, buf_len);
		} else if (close_it == 1) {
			dcc_error_msg("send(%s %s)=%d not %d",
				      type, ifp->hostname, i, buf_len);
		}
	}
	return 0;
}



u_char					/* 0=nothing to send, 1=sent */
iflod_send_pos(IFLOD_INFO *ifp, u_char force)
{
	DCC_FLOD_POS req;
	OFLOD_INFO *ofp;

	if (!(ifp->flags & IFLOD_FG_CONNECTED))
		return 0;

	/* ask peer to start over if our database has been cleared */
	ofp = ifp->ofp;
	if ((ifp->flags & IFLOD_FG_CONNECTED)
	     && ofp && ofp->mp) {
		if (ofp->mp->flags & OFLOD_MMAP_FG_FFWD_IN) {
			ofp->mp->flags &= ~(OFLOD_MMAP_FG_FFWD_IN
					    | OFLOD_MMAP_FG_NEED_REWIND);
			memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent));
			db_ptr2flod_pos(req, DCC_FLOD_POS_FFWD_IN);
			iflod_write(ifp, req, sizeof(req),
				    "ffwd request", 0);
			dcc_trace_msg("ask %s %s to FFWD flood to us",
				      ifp->hostname, dcc_su2str(&ifp->su));
			return 1;
		}
		if (ofp->mp->flags & OFLOD_MMAP_FG_NEED_REWIND) {
			ofp->mp->flags &= ~OFLOD_MMAP_FG_NEED_REWIND;
			memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent));
			db_ptr2flod_pos(req, DCC_FLOD_POS_REWIND);
			iflod_write(ifp, req, sizeof(req),
				    "rewind request", 0);
			dcc_trace_msg("ask %s %s to rewind flood to us",
				      ifp->hostname, dcc_su2str(&ifp->su));
			return 1;
		}
	}

	if ((force && flod_pos2db_ptr(ifp->pos) >= DCC_FLOD_POS_MIN)
	    || memcmp(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent))) {
		memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent));
		iflod_write(ifp, ifp->pos_sent, sizeof(ifp->pos_sent),
			    "confirmed pos", 0);
		if (ofp)
			ofp->keep_in_time = db_time.tv_sec + KEEPALIVE_IN;
		return 1;
	}

	/* Say just anything if we are doing a keepalive probe before
	 * any checksums have been sent by the peer and so before we
	 * have a position to confirm. */
	if (force) {
		FLOD_NOTE buf;

		db_ptr2flod_pos(buf.op, DCC_FLOD_POS_NOTE);
		strcpy(buf.str, "are you still there?");
		buf.len = sizeof("are you still there?") + FLOD_NOTE_OVHD;
		iflod_write(ifp, &buf, buf.len, buf.str, 0);
		if (ofp)
			ofp->keep_in_time = db_time.tv_sec + KEEPALIVE_IN;
		return 1;
	}

	return 0;
}



void
iflod_stop(IFLOD_INFO *ifp, const char *reason)
{
	DCC_FLOD_POS end_req;

	if (reason)
		TMSG2(FLOD, "iflod stop %s: %s", ifp->hostname, reason);
	else
		TMSG1(FLOD, "iflod stop %s", ifp->hostname);
	iflod_send_pos(ifp, 0);
	db_ptr2flod_pos(end_req, DCC_FLOD_POS_END_REQ);
	iflod_write(ifp, end_req, sizeof(end_req), "iflod stop req", 0);
	ifp->flags |= IFLOD_FG_END_REQ;
	if (ifp->ofp)
		ifp->ofp->keep_in_time = db_time.tv_sec + KEEPALIVE_IN_STOP;
}



/* send stop requests to DCC servers flooding to us */
void
iflods_stop(const char *reason,
	    u_char force)		/* now! */
{
	SRVR_SOC *sp;
	IFLOD_INFO *ifp;

	/* stop listening for new connections */
	for (sp = srvr_socs; sp; sp = sp->fwd) {
		if (sp->listen >= 0) {
			close(sp->listen);
			sp->listen = -1;
		}
	}

	for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) {
		if (ifp->s < 0)
			continue;
		if (!(ifp->flags & IFLOD_FG_END_REQ)
		    && (ifp->flags & IFLOD_FG_CONNECTED))
			iflod_stop(ifp, reason);

		if (force || !(ifp->flags & IFLOD_FG_CONNECTED)) {
			iflod_close(ifp, 1, FLOD_ERR_NO_LINK, 0, reason);
			continue;
		}
	}
}



/* start receiving checksums from another DCC server */
void
iflod_start(SRVR_SOC *sp)
{
	IFLOD_INFO *ifp;
	int l;

	/* find a free input flooding slot */
	for (ifp = iflods.infos; ifp->s >= 0; ++ifp) {
		if (ifp > LAST(iflods.infos)) {
			if (!complained_many_iflods++)
				dcc_error_msg("too many incoming floods");
			return;
		}
	}

	l = sizeof(ifp->su);
	ifp->s = accept(sp->listen, &ifp->su.sa, &l);
	if (ifp->s < 0) {
		dcc_error_msg("accept(flod): %s", ERROR_STR());
		return;
	}
	/* use the IP address until we know which peer it is */
	strcpy(ifp->hostname, dcc_su2str(&ifp->su));
	if (!set_flod_socket(ifp->s, ifp->hostname, &ifp->su)) {
		close(ifp->s);
		ifp->s = -1;
		return;
	}

	++iflods.active;
	ifp->flags |= IFLOD_FG_CONNECTED;
	ifp->quit_connect = db_time.tv_sec + IFLOD_CONNECT_SECS;

	TMSG1(FLOD, "start flood from %s", ifp->hostname);
}



/* Start an incoming SOCKS flood stream by connecting to the other system.
 *	We will eventually turn the connection around and pretend
 *	the other system initiated the TCP connection. */
static int				/* -1=failure, 0=not yet, 1=done */
iflod_socks_connect(IFLOD_INFO *ifp)
{
	ID_TBL *tp;
	DCC_FLOD_VERSION_HDR buf;
	int i;

	tp = find_id_tbl(ifp->ofp->out_passwd_id);
	if (!tp) {
		iflod_close(ifp, 1, FLOD_ERR_SIGN, 0,
			    "unknown passwd-ID %d%s",
			    ifp->ofp->out_passwd_id,
			    fnm_lineno(FLOD_NM(grey_on), ifp->ofp->lineno));
		return -1;
	}
	if (tp->cur_passwd[0] == '\0') {
		iflod_close(ifp, 1, FLOD_ERR_SIGN, 0,
			    "no password for passwd-ID %d%s",
			    ifp->ofp->out_passwd_id,
			    fnm_lineno(FLOD_NM(grey_on), ifp->ofp->lineno));
		return -1;
	}

	i = Rconnect(ifp->s, &ifp->su.sa, DCC_SU_LEN(&ifp->su));
	if (0 > i && errno != EISCONN) {
		if (errno == EAGAIN
		    || errno == EINPROGRESS
		    || errno == EALREADY)
			return 0;

		/* it is lame to ignore EINVAL, but several UNIX-like
		 * systems return EINVAL for the second connect() after
		 * a Unreachable ICMP message or timeout */
		if (errno != EINVAL)
			rpt_err(&ifp->ofp->mp->i_err, 0,
				FLOD_ERR_CONNECT, errno,
				"connect(iflod SOCKS %s %s): %s",
				ifp->ofp->hostname,
				dcc_su2str(&ifp->su),
				ERROR_STR());
		close(ifp->s);
		iflod_clear(ifp, 1);

		return -1;
	}

	ifp->flags |= IFLOD_FG_CONNECTED;
	ifp->ofp->mp->i_err.ok = db_time.tv_sec + LAST_ERROR_OK_SECS;

	/* after the SOCKS incoming flood socket is connected,
	 * send authentication to convince the peer to send its
	 * authentication and then flood its checksums. */
	memset(&buf, 0, sizeof(buf));
#ifdef DCC_FLOD_VERSION6
	if (ifp->ofp->iversion == DCC_FLOD_VERSION6)
		strcpy(buf.body.str, DCC_FLOD_VERSION6_STR);
	else
#endif
		strcpy(buf.body.str, DCC_FLOD_VERSION_CUR_STR);
	buf.body.sender_srvr_id = htons(my_srvr_id);
	buf.body.turn = 1;
	dcc_sign(tp->cur_passwd, sizeof(tp->cur_passwd), &buf, sizeof(buf));
	if (!iflod_write(ifp, &buf, sizeof(buf),
			 "iflod SOCKS authentication", 0))
		return -1;

	TMSG2(FLOD, "start SOCKS flood from %s %s",
	      ifp->hostname, dcc_su2str(&ifp->su));
	return 1;
}



/* request the start of an input flood via SOCKS if it is not already flowing */
void
iflod_socks_start(OFLOD_INFO *ofp)
{
	IFLOD_INFO *ifp, *ifp1;

	if (!(ofp->o_opts.flags & FLOD_OPT_SOCKS)
	    || (ofp->i_opts.flags & FLOD_OPT_OFF))
		return;
	if (!DB_IS_TIME(ofp->in_try_again, ofp->in_try_secs))
		return;
	ofp->in_try_secs = 0;

	/* look for a free slot or an existing slot for the incoming flood */
	ifp = 0;
	for (ifp1 = iflods.infos; ifp1 <= LAST(iflods.infos); ++ifp1) {
		if (ifp1->s < 0) {
			if (!ifp)
				ifp = ifp1;
		}
		/* there is nothing to do if it already exists */
		if (ifp1->ofp == ofp)
			return;
	}
	if (!ifp) {
		if (!complained_many_iflods++)
			dcc_error_msg("too many incoming floods"
				      " to start SOCKS flood from %s",
				      ofp->hostname);
		return;
	}

	if (!flod_names_resolve_start())
		return;			/* wait for name resolution */
	if (ofp->mp->su.sa.sa_family == AF_UNSPEC) {
		rpt_err(&ofp->mp->i_err, 0,
			FLOD_ERR_GET_HOST, ofp->mp->host_error,
			"flood SOCKS peer name %s: %s%s",
			ofp->hostname, DCC_HSTRERROR(ofp->mp->host_error),
			fnm_lineno(FLOD_NM(grey_on), ofp->lineno));
		return;
	}
	ifp->ofp = ofp;
	ifp->su = ofp->mp->su;
	strcpy(ifp->hostname, ofp->hostname);

	ifp->s = socket(ifp->su.sa.sa_family, SOCK_STREAM, 0);
	if (ifp->s < 0) {
		dcc_error_msg("socket(SOCKS iflod): %s", ERROR_STR());
		return;
	}

	if (!set_flod_socket(ifp->s, ifp->hostname, &ifp->su)) {
		close(ifp->s);
		ifp->s = -1;
		return;
	}

	++iflods.active;
	iflod_socks_connect(ifp);
}



static u_char				/* 1=duplicate or deleted */
iflod_ck_dup(IFLOD_INFO *ifp,
	     const DB_RCD *new, DCC_TGTS new_tgts_raw,
	     const DB_RCD *old)
{
	int i;

	/* detect duplicate reports */
	if (DB_RCD_ID(new) == DB_RCD_ID(old)
	    && !memcmp(new->ts, old->ts, sizeof(new->ts))) {
		if (DB_CK_TYPE(&new->cks[DB_NUM_CKS(new)-1])!=DCC_CK_SRVR_ID) {
			i = FLOD_CNTERR(&ifp->ofp->cnts.dup);
			if (i <= 0)
				TMSG2(FLOD2, "duplicate %s%s",
				      rpt_id("report", new, ifp),
				      (i < 0 ? "" : "; stop complaints"));
		}
		return 1;
	}

	/* ignore reports for deleted checksums
	 * unless they are new reports or delete requests */
	if (DB_TGTS_RCD_RAW(old) == DCC_TGTS_DEL
	    && new_tgts_raw != DCC_TGTS_DEL
	    && !DCC_TS_NEWER_TS(new->ts, old->ts)) {
		i = FLOD_CNTERR(&ifp->ofp->cnts.stale);
		if (i <= 0)
			TMSG3(FLOD, "ignore deleted %s after %s%s",
			      rpt_id("report", new, ifp),
			      rpt_id(0, old, 0),
			      (i < 0 ? "" : "; stop complaints"));
		return 1;
	}
	return 0;
}



/* complain about a received flooded report */
static void PATTRIB(6,7)
iflod_rpt_complain(IFLOD_INFO *ifp,
		   const DB_RCD *new,	/* complain about this report */
		   u_char serious,	/* 0=send mere note, 1=send complaint */
		   FLOD_LIMCNT *lc,	/* limit complaints with this */
		   const char *str,	/* type of report */
		   const char *pat,...)	/* the complaint */
{
	FLOD_NOTE buf;
	const char *sc;
	va_list args;
	int i, len;

	va_start(args, pat);
	len = vsnprintf(buf.str, sizeof(buf.str), pat, args);
	if (len >= ISZ(buf.str))
		len = sizeof(buf.str)-1;
	va_end(args);

	if (!lc) {
		sc = "";
	} else {
		i = FLOD_CNTERR(lc);
		if (i > 0)
			return;
		sc = i < 0 ? "" : "; stop complaints";
	}

	if (serious) {
		dcc_error_msg("%s %s%s", buf.str, rpt_id(str, new, ifp), sc);
		db_ptr2flod_pos(buf.op, DCC_FLOD_POS_COMPLAINT);
	} else {
		TMSG3(FLOD2, "%s %s%s", buf.str, rpt_id(str, new, ifp), sc);
		db_ptr2flod_pos(buf.op, DCC_FLOD_POS_NOTE);
	}

	len += snprintf(&buf.str[len], sizeof(buf.str)-len, " %s%s",
			rpt_id(str, new, 0), sc);
	if (len >= ISZ(buf.str))
		len = ISZ(buf.str)-1;

	buf.len = len+1 + FLOD_NOTE_OVHD;
	iflod_write(ifp, &buf, buf.len, buf.str, 0);
}



/* consider an incoming flooded report */
static int				/* -1=failed, 0=not yet, else length */
iflod_rpt(IFLOD_INFO *ifp, int total_len)
{
	DCC_FLOD *fp;
	OFLOD_INFO *ofp;
	DB_PTR pos;
	DCC_TGTS rpt_tgts, found_tgts, min_tgts;
	DB_RCD new;
	DCC_SRVR_ID old_srvr, psrvr;
	const DCC_CK *ck_lim, *ck;
	DB_RCD_CK *new_ck, *found_ck;
	DB_PTR rcd_pos, min_rcd_pos;
	DCC_CK_TYPES type, prev_type, min_type;
	DCC_FLOD_PATH_ID *new_path_id, *old_path_id;
	int num_path_blocks;
	char tgts_buf[DCC_XHDR_MAX_TGTS_LEN];
	int ok2;
	int rpt_len;
	u_char stale;
	ID_MAP_RESULT srvr_mapped;
	int i;

	ofp = ifp->ofp;			/* must not be null here */

	fp = (DCC_FLOD *)&ifp->ibuf.c[ifp->ibuf_off];
	if (fp->num_cks == 0 || fp->num_cks > DCC_QUERY_MAX) {
		iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
			    "impossible %d checksums in report #%d from %s",
			    fp->num_cks, ofp->cnts.total, ifp->hostname);
		return -1;
	}
	rpt_len = (sizeof(*fp) - sizeof(fp->cks)
		   + fp->num_cks * sizeof(fp->cks[0]));
	if (rpt_len > total_len)
		return 0;		/* wait for more */

	pos = flod_pos2db_ptr(fp->pos);
	if (pos < DCC_FLOD_POS_MIN) {
		iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
			    "bogus position "L_HPAT" in flooded report #%d"
			    " from %s",
			    pos, ofp->cnts.total, ifp->hostname);
		return -1;
	}

	/* save the position to return to the sender */
	memcpy(ifp->pos, fp->pos, sizeof(ifp->pos));

	memset(&new, 0, sizeof(new));
	memcpy(new.ts, fp->ts, sizeof(new.ts));
	memcpy(&new.srvr_id_auth, fp->srvr_id_auth, sizeof(new.srvr_id_auth));
	new.srvr_id_auth = ntohs(new.srvr_id_auth);
	old_srvr = DB_RCD_ID(&new);

	memcpy(&rpt_tgts, fp->tgts, sizeof(rpt_tgts));
	rpt_tgts = ntohl(rpt_tgts);
	if (rpt_tgts == DCC_TGTS_DEL) {
		if (!(ofp->i_opts.flags & FLOD_OPT_DEL_OK)) {
			iflod_rpt_complain(ifp, &new, 1, &ofp->cnts.not_deleted,
					   "delete request", "refuse");
			return rpt_len;
		}
		if (!(ofp->i_opts.flags & FLOD_OPT_NO_LOG_DEL))
			dcc_error_msg("accept %s",
				      rpt_id("delete request", &new, ifp));
	} else if (rpt_tgts == 0
		   || rpt_tgts > DCC_TGTS_TOO_MANY) {
		iflod_close(ifp, 1,  FLOD_ERR_BAD_DATA, 0,
			    "bogus target count %s in %s",
			    dcc_cnt2str(tgts_buf, sizeof(tgts_buf),
					rpt_tgts, grey_on),
			    rpt_id("report", &new, 0));
		return -1;
	} else if (ofp->i_opts.flags & FLOD_OPT_TRAPS) {
		/* comply if the source watches only spam traps */
		rpt_tgts = DCC_TGTS_TOO_MANY;
	}

	/* notice reports from the distant future */
	if (DCC_TS_NEWER_TS(new.ts, future_ts)) {
		iflod_rpt_complain(ifp, &new, 1, &ofp->cnts.stale,
				   "report", "future");
		return rpt_len;
	}

	DB_TGTS_RCD_SET(&new, rpt_tgts);
	new.fgs_num_cks = 0;
	stale = 1;
	ck_lim = &fp->cks[fp->num_cks];
	new_ck = new.cks;
	num_path_blocks = 0;

	srvr_mapped = id_map(old_srvr, &ofp->i_opts);
	switch (srvr_mapped) {
	case ID_MAP_NO:
		if (!find_id_tbl(old_srvr)
		    && (DCC_TRACE_IDS_BIT & dccd_tracemask)
		    && (i = FLOD_CNTERR(&ofp->cnts.bad_id)) <= 0) {
			dcc_error_msg("%s%s",
				      rpt_id("unknown server-ID in", &new, ifp),
				      (i < 0 ? "" : "; stop complaints"));
		}
		break;
	case ID_MAP_REJ:
		iflod_rpt_complain(ifp, &new, 0, 0,
				   "rejected server-ID in", "refuse");
		return rpt_len;
	case ID_MAP_SELF:
		new.srvr_id_auth = my_srvr_id;
		/* create path pointing to ourself if we translate the ID */
		memset(new_ck, 0, sizeof(*new_ck));
		new_ck->type_fgs = DCC_CK_FLOD_PATH;
		new_path_id = (DCC_FLOD_PATH_ID *)new_ck->sum;
		/* start the path with the ID of the previous hop because
		 * we know it is defined */
		new_path_id->hi = ofp->rem_id>>8;
		new_path_id->lo = ofp->rem_id;
		new.fgs_num_cks = 1;
		++new_ck;
		break;
	}

	for (prev_type = DCC_CK_INVALID, ck = fp->cks;
	     ck < ck_lim;
	     prev_type = type, ++ck) {
		type = ck->type;
		if (!DCC_CK_OK_FLOD(type, grey_on)) {
			iflod_rpt_complain(ifp, &new, 1, 0,
					   "report",
					   "unknown checksum type %s in",
					   dcc_type2str_err(type, 0, 1));
			continue;
		}
		if (ck->len != sizeof(*ck)) {
			/* relax this someday if necessary */
			iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
				    "unknown checksum length %d in %s",
				    ck->len, rpt_id("report", &new, ifp));
			return -1;
		}
		if (type <= prev_type && prev_type != DCC_CK_FLOD_PATH) {
			iflod_rpt_complain(ifp, &new, 1, 0,
					   "report",
					   "out of order %s checksum in",
					   dcc_type2str_err(type, 0, 1));
			return rpt_len;
		}

		new_ck->type_fgs = type;
		new_ck->prev = DB_PTR_CP(DB_PTR_NULL);
		memcpy(new_ck->sum, ck->sum, sizeof(new_ck->sum));
		if (type == DCC_CK_FLOD_PATH) {
			/* discard report if path is too long */
			if (++num_path_blocks > DCC_MAX_FLOD_PATH_CKSUMS) {
				TMSG2(FLOD, "%d path blocks in %s",
				      num_path_blocks,
				      rpt_id("report", &new, ifp));
				return rpt_len;
			}
			/* don't add this path if we translated the origin */
			if (srvr_mapped == ID_MAP_SELF)
				continue;
			old_path_id = (DCC_FLOD_PATH_ID *)ck->sum;
			new_path_id = old_path_id;
			for (i = 0; i < DCC_NUM_FLOD_PATH; ++i, ++old_path_id) {
				psrvr = (old_path_id->hi<<8) | old_path_id->lo;
				if (psrvr == DCC_ID_INVALID)
					break;	/* end of path */
				switch (id_map(psrvr, &ofp->i_opts)) {
				case ID_MAP_NO:
				case ID_MAP_REJ:
					break;
				case ID_MAP_SELF:
					psrvr = my_srvr_id;
					break;
				}
				new_path_id->hi = psrvr>>8;
				new_path_id->lo = psrvr;
				++new_path_id;
			}

		} else {
			/* discard translated server-ID declarations */
			if (type == DCC_CK_SRVR_ID
			    && srvr_mapped == ID_MAP_SELF) {
				TMSG2(FLOD,
				      "translated server-ID from %d in %s",
				      old_srvr, rpt_id("report", &new, ifp));
				return rpt_len;
			}

			/* discard this checksum if we would not have kept
			 * it if we had received the original report
			 * and either its server-ID is translated
			 * or it is not kept by default */
			if (DB_TEST_NOKEEP(db_nokeep_cks, type)
			    && (srvr_mapped == ID_MAP_SELF
				|| DB_DEF_NOKEEP(type)))
				continue;

			/* notice if this checksum makes the report timely */
			if (stale
			    && !DCC_TS_OLDER_TS(new.ts,
						db_ex_ts[DB_CK_TYPE(new_ck)
							].all))
				stale = 0;
		}

		++new_ck;
		++new.fgs_num_cks;
	}
	if (stale) {
		i = FLOD_CNTERR(&ofp->cnts.stale);
		if (i <= 0)
			TMSG2(FLOD2, "stale %s%s",
			      rpt_id("report", &new, ifp),
			      (i < 0 ? "" : "; stop complaints"));
		return rpt_len;
	}

	if (!DB_NUM_CKS(&new)) {	/* no known checksums */
		iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
			    "no known checksum types in %s",
			    rpt_id("report", &new, ifp));
		return -1;
	}

	/* See if the report is a duplicate.
	 * Check all of the checksums to find one that is absent or
	 * the one with the smallest total to minimize the number
	 * of reports we must check to see if this is a duplicate */
	min_tgts = DCC_TGTS_TOO_MANY;
	min_type = DCC_CK_INVALID;
	min_rcd_pos = DB_PTR_NULL;
	ok2 = 0;
	while (new_ck-- > new.cks) {
		type = DB_CK_TYPE(new_ck);
		if (DB_TEST_NOKEEP(db_nokeep_cks, type))
			continue;

		switch (db_lookup(dcc_emsg, type, new_ck->sum,
				  0, MAX_HASH_ENTRIES,
				  &db_sts.hash, &db_sts.rcd, &found_ck)) {
		case DB_FOUND_LATER:
		case DB_FOUND_SYSERR:
			iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
				    "%s", dcc_emsg);
			db_broken(dcc_emsg);
			return -1;

		case DB_FOUND_IT:
			/* notice claims by other servers to our ID */
			if (DB_RCD_ID(&new) == my_srvr_id
			    && type == DCC_CK_SRVR_ID
			    && memcmp(host_id_rcd.cks[0].sum, new_ck->sum,
				      sizeof(host_id_rcd.cks[0].sum))) {
				dcc_error_msg("host %s used our server-ID "
					      "%d at %s",
					      dcc_ck2str_err(type, new_ck->sum),
					      my_srvr_id,
					      dcc_ts2str_err(new.ts));
			}

			if (iflod_ck_dup(ifp, &new, rpt_tgts, db_sts.rcd.d.r))
				return rpt_len;

			/* notice the local server's white list */
			found_tgts = DB_TGTS_CK(found_ck);
			if (found_tgts == DCC_TGTS_OK
			    || (found_tgts == DCC_TGTS_OK2
				&& ++ok2 >= 2)) {
				iflod_rpt_complain(ifp, &new, 0, &ofp->cnts.ok2,
						   "report", "whitelisted");
				return rpt_len;
			}

			/* At least one checksum is already known to us.
			 * If the report is a delete request,
			 * then we need to run dbclean to fix all of the
			 * totals affected by the deleted reports. */
			if (rpt_tgts == DCC_TGTS_DEL
			    && !grey_on)
				need_del_dbclean = "flood checksum deletion";

			rcd_pos = DB_PTR_EX(found_ck->prev);
			if (rcd_pos == DB_PTR_NULL) {
				/* not a duplicate */
				min_type = DCC_CK_INVALID;
				goto not_dup;
			}

			/* notice the checksum with smallest count */
			if (min_type == DCC_CK_INVALID
			    || min_tgts > found_tgts) {
				min_tgts = found_tgts;
				min_type = type;
				min_rcd_pos = rcd_pos;
			}
			break;

		case DB_FOUND_EMPTY:
		case DB_FOUND_CHAIN:
		case DB_FOUND_INTRUDER:
			/* We will fail to find this checksum in our database
			 * only if the report is not a duplicate or if it is
			 * a duplicate report but we have expired this
			 * particular checksum.
			 * Unless we've been using dbclean with
			 * inconsistent expiration durations, the second
			 * case won't happen, so assume it is not a duplicate */
			min_type = DCC_CK_INVALID;
			goto not_dup;
			break;
		}
	}
	/* At least one checksum in the report is known to us, and
	 * we've chosen the checksum with the smallest total.
	 * Chase the chosen chain to check all occurances of
	 * the checksum to see if the flooded report is a duplicate */
	if (min_type != DCC_CK_INVALID) {
		for (;;) {
			found_ck = db_map_rcd_ck(dcc_emsg, &db_sts.rcd,
						 min_rcd_pos, min_type);
			if (!found_ck) {
				iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
					    "%s", dcc_emsg);
				db_broken(dcc_emsg);
				return -1;
			}
			if (iflod_ck_dup(ifp, &new, rpt_tgts, db_sts.rcd.d.r))
				return rpt_len;

			rcd_pos = DB_PTR_EX(found_ck->prev);
			if (rcd_pos == DB_PTR_NULL)
				break;
			if (rcd_pos >= min_rcd_pos) {
				db_broken("bad %s link of "L_HPAT" at "L_HPAT,
					  dcc_type2str_err(min_type, 0, 1),
					  rcd_pos, min_rcd_pos);
				return -1;
			}
			min_rcd_pos = rcd_pos;
		}
	}
not_dup:;

	/* the report is not a duplicate, so add it to our database */
	if (!add_dly_rcd(dcc_emsg, &new)) {
		iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
			    "%s", dcc_emsg);
		return -1;
	}

	++ofp->cnts.accepted;
	return rpt_len;
}



static u_char
check_iflod_vers(IFLOD_INFO *ifp)
{
	DCC_FLOD_VERSION_HDR *vp;
	OFLOD_INFO *ofp;
	IFLOD_INFO *ifp1;
	ID_TBL *tp;
	DCC_SRVR_ID rem_id;
	u_char iversion;

	vp = &ifp->ibuf.vers;
	if (!strcmp(vp->body.str, DCC_FLOD_VERSION_CUR_STR)) {
		iversion = DCC_FLOD_VERSION_CUR;
		ifp->flags |= IFLOD_FG_VERS_CK;
#ifdef DCC_FLOD_VERSION6
	} else if (!strcmp(vp->body.str, DCC_FLOD_VERSION6_STR)) {
		iversion = DCC_FLOD_VERSION6;
		ifp->flags |= IFLOD_FG_VERS_CK;
#endif
	} else if (!strncmp(vp->body.str, DCC_FLOD_VERSION_STR_BASE,
			    ISZ(DCC_FLOD_VERSION_STR_BASE)-1)) {
		/* postpone giving up until later if the version string is
		 * not entirely wrong */
		iversion = DCC_FLOD_VERSION_DEF;
	} else {
		iflod_close(ifp, 1, FLOD_ERR_BAD_VERS, 0,
			    BAD_VER_MSG"\"%.*s\"",
			    ISZ(DCC_FLOD_VERSION_STR_BASE)-1,
			    vp->body.str);
		return 0;
	}

	rem_id = ntohs(vp->body.sender_srvr_id);
	if (rem_id < DCC_SRVR_ID_MIN
	    || rem_id > DCC_SRVR_ID_MAX) {
		iflod_close(ifp, 1, FLOD_ERR_ID, 0,
			    BAD_ID_MSG"%d", rem_id);
		return 0;
	}
	for (ofp = oflods.infos; ; ++ofp) {
		if (ofp > LAST(oflods.infos)) {
			iflod_close(ifp, 1, FLOD_ERR_ID, 0,
				    BAD_ID_MSG"%d", rem_id);
			return 0;
		}
		if (ofp->rem_id == rem_id) {
			ifp->ofp = ofp;
			strcpy(ifp->hostname, ofp->hostname);
			if (ofp->i_opts.flags & FLOD_OPT_OFF) {
				iflod_close(ifp, 1, FLOD_ERR_LOCAL_OFF, 0,
					    "incoming flood turnd off%s",
					    fnm_lineno(FLOD_NM(grey_on),
						       ofp->lineno));
				return 0;
			}
			break;
		}
	}

	/* we now know which peer it is */
	if (!ck_sign(&tp, 0, ofp->in_passwd_id, vp, sizeof(*vp))) {
		if (!tp)
			iflod_close(ifp, 1, FLOD_ERR_ID, 0,
				    "unknown passwd-ID %d%s",
				    ofp->in_passwd_id,
				    fnm_lineno(FLOD_NM(grey_on), ofp->lineno));
		else
			iflod_close(ifp, 1, FLOD_ERR_SIGN, 0,
				    BAD_AUTH_MSG"%d", ofp->in_passwd_id);
		return 0;
	}
	if (!(ifp->flags & IFLOD_FG_VERS_CK)) {
		iflod_close(ifp, 1, FLOD_ERR_BAD_VERS, 0,
			    "unrecognized flod version \"%.*s\"",
			    ISZ(DCC_FLOD_VERSION_CUR_STR),
			    vp->body.str);
		/* try to restart corresponding output flood so that the
		 * other DCC server will try again with the right version */
		if (ofp->s < 0) {
			ofp->out_try_again = 0;
			oflod_open(ofp);
		}
		return 0;
	}
	ofp->iversion = iversion;
	ifp->ibuf_off += sizeof(*vp);
	if (iversion != DCC_FLOD_VERSION_CUR)
		TMSG2(FLOD, "iflod version %d from %s",
		      iversion, ifp->hostname);

	/* switch a passive output flood */
	if (vp->body.turn) {
		if ((ofp->o_opts.flags & FLOD_OPT_OFF)
		    || flods_st != FLODS_ST_ON) {
			iflod_close(ifp, 1, FLOD_ERR_LOCAL_OFF, 0,
				    "passive output flooding off"
				    " for ID %d%s", rem_id,
				    fnm_lineno(FLOD_NM(grey_on), ofp->lineno));
			return 0;
		}
		if (ofp->s >= 0) {
			/* We have a duplicate passive output flood.
			 * Keep the new one if the existing stream
			 * has been quiet for a long time. */
			if (!ofp->mp
			    || DB_IS_TIME(ofp->mp->o_err.ok,
					   LAST_ERROR_OK_SECS)) {
				TMSG2(FLOD,"new duplicate passive output flood"
				      " for ID %d%s", rem_id,
				      fnm_lineno(FLOD_NM(grey_on),ofp->lineno));
				oflod_close(ofp, 0, FLOD_ERR_DUP, 0);
			} else {
				iflod_close(ifp, 1, FLOD_ERR_DUP, 0,
					    "duplicate passive output flood"
					    " for ID %d%s", rem_id,
					    fnm_lineno(FLOD_NM(grey_on),
						       ofp->lineno));
				return 0;
			}
		}

		ofp->s = ifp->s;
		ofp->su = ifp->su;
		++oflods.active;
		iflod_clear(ifp, 0);

		if (!oflod_connect_fin(ofp))
			oflod_close(ofp, 0, FLOD_ERR_SAME, 0);
		return 0;
	}

	/* detect duplicate incoming floods */
	for (ifp1 = iflods.infos; ifp1 <= LAST(iflods.infos); ++ifp1) {
		if (ifp1 == ifp || ifp1->ofp != ofp)
			continue;

		/* We have a duplicate.  Keep the new flood and close the
		 * old flood if the old flood has no position to confirm
		 * perhaps because it was never really used. */
		if (!ofp->mp
		    || !iflod_send_pos(ifp1, 1)) {
			iflod_close(ifp1, 0, FLOD_ERR_DUP, 0,
				    "new duplicate incoming flood for ID %d%s",
				    rem_id, fnm_lineno(FLOD_NM(grey_on),
						       ofp->lineno));
			break;
		}

		/* Otherwise, kill the new flood */
		iflod_close(ifp, 1,  FLOD_ERR_DUP, 0,
			    "duplicate incoming flood for ID %d%s",
			    rem_id, fnm_lineno(FLOD_NM(grey_on), ofp->lineno));
		return 0;
	}

	/* ensure that we have enough socket buffer space to send
	 * complaints about the input flood */
	if (0 > setsockopt(ifp->s, SOL_SOCKET, SO_SNDBUF,
			   &srvr_rcvbuf, sizeof(srvr_rcvbuf)))
		dcc_error_msg("setsockopt(%s, SO_SNDBUF): %s",
			      ifp->hostname, ERROR_STR());

	/* send DCC_FLOD_POS_REWIND immediately if needed */
	iflod_send_pos(ifp, 0);

	/* try to restart corresponding output flood */
	if (ofp->s < 0) {
		ofp->out_try_again = 0;
		ofp->ids_mtime = 0;	/* try both passwords */
		oflod_open(ofp);
	}

	ofp->cnts.in_start = db_time.tv_sec;
	ofp->keep_in_time = db_time.tv_sec + KEEPALIVE_IN;

	return 1;
}



/* see what a distant flooder is telling us */
void
iflod_read(IFLOD_INFO *ifp)
{
	int was_locked;
	DCC_FLOD_VERSION_HDR *vp;
	DCC_FLOD *fp;
	OFLOD_INFO *ofp;
	const char *reason;
	int len, i;

	if (!(ifp->flags & IFLOD_FG_CONNECTED)
	    && iflod_socks_connect(ifp) <= 0)
		return;

	/* read only once before returning
	 * to ensure we pay attention to other work */
	len = (((ifp->flags & IFLOD_FG_VERS_CK)
		? sizeof(ifp->ibuf) : sizeof(*vp))
	       - ifp->ibuf_len);
	ofp = ifp->ofp;
	if (ofp && ofp->o_opts.flags & FLOD_OPT_SOCKS)
		i = Rrecv(ifp->s, &ifp->ibuf.c[ifp->ibuf_len], len, 0);
	else
		i = recv(ifp->s, &ifp->ibuf.c[ifp->ibuf_len], len, 0);
	if (i < 0) {
		/* If kernel ran out of data, stop for now.
		 * Give up on an I/O error */
		if (!DCC_BLOCK_ERROR()) {
			iflod_close(ifp, 1, FLOD_ERR_IO, errno,
				    "iflod recv(%s): %s",
				    ifp->hostname, ERROR_STR());
		}
		return;
	}
	if (i == 0) {
		/* EOF or remote shutdown() on the socket */
		if (ifp->ibuf_len != 0) {
			/* we have some final data. */
			if (ifp->ibuf_len < sizeof(DCC_FLOD_POS)) {
				/* complain if it is too little or noise */
				iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
					    "report %d from %s truncated",
					    ofp ? ofp->cnts.total : 0,
					    ifp->hostname);

			} else if (flod_pos2db_ptr((u_char *)&ifp->ibuf.c[
							ifp->ibuf_off])
				   < DCC_FLOD_POS_MIN) {
				/* Take it as an error message if enough.
				 * Recognize bad password error message and
				 * slow down retries */
				reason = &ifp->ibuf.c[ifp->ibuf_off
						      +sizeof(DCC_FLOD_POS)];
				if (ofp
				    && ofp->cnts.total == 0
				    && (ofp->o_opts.flags & FLOD_OPT_SOCKS)
				    && (!strncmp(reason, BAD_ID_MSG,
						 ISZ(BAD_ID_MSG)-1)
					|| !strncmp(reason, BAD_AUTH_MSG,
						    ISZ(BAD_AUTH_MSG)-1)))
					ofp->in_try_secs = FLOD_SLOW_RETRY_SECS;
				iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
					    "report %d from %s truncated"
					    " with \"%s\"",
					    ofp ? ofp->cnts.total : 0,
					    ifp->hostname, reason);

			} else {
				/* complain if it is noise */
				iflod_close(ifp, 1, FLOD_ERR_BAD_DATA, 0,
					    "garbage report %d from %s",
					    ofp ? ofp->cnts.total : 0,
					    ifp->hostname);
			}
		} else {
			iflod_close(ifp, 0, FLOD_ERR_REMOTE_OFF, 0,
				    DCC_FLOD_OK_STR);
		}
		return;
	}
	ifp->ibuf_len += i;
	if (ofp && ofp->mp)
		ofp->mp->i_err.ok = db_time.tv_sec + LAST_ERROR_OK_SECS;

	was_locked = db_lock(dcc_emsg);
	if (was_locked < 0) {
		dcc_error_msg("flod %s", dcc_emsg);
		return;
	}

	dcc_timeval2ts(future_ts, &db_time, DCC_FUTURE_SECS);
	for (;;) {
		len = ifp->ibuf_len - ifp->ibuf_off;
		if (!(ifp->flags & IFLOD_FG_VERS_CK)) {
			if (len < ISZ(*vp))
				break;
			if (!check_iflod_vers(ifp))
				break;
			ofp = ifp->ofp;

		} else {
			/* when we have enough of the report, see if we have
			 * the whole thing by trying swallow it */
			if (len < (int)(sizeof(*fp) - sizeof(fp->cks)
					+ sizeof(fp->cks[0])))
				break;
			i = iflod_rpt(ifp, len);
			if (i < 0)
				break;	/* stream closed */
			if (i == 0)
				break;	/* wait for more of it */
			if (ofp != 0)
				++ofp->cnts.total;
			ifp->ibuf_off += i;
		}

		if (ifp->ibuf_off >= ifp->ibuf_len) {
			ifp->ibuf_len = 0;
			ifp->ibuf_off = 0;
		} else if (ifp->ibuf_off >= (sizeof(ifp->ibuf.c)
					     - sizeof(DCC_FLOD))) {
			ifp->ibuf_len -= ifp->ibuf_off;
			memmove(&ifp->ibuf.c[0], &ifp->ibuf.c[ifp->ibuf_off],
				ifp->ibuf_len);
			ifp->ibuf_off = 0;
		}
	}

	if (!was_locked
	    && !db_unlock(dcc_emsg))
		dcc_error_msg("flod %s", dcc_emsg);
}



void
iflods_start(void)
{
	SRVR_SOC *sp;
	DCC_SOCKU su;
	const DCC_SOCKU *sup = 0;
	int i, on;

	/* (re)start only when things are quiet */
	if (iflods.active != 0)
		return;

	for (sp = srvr_socs; sp; sp = sp->fwd) {
		if (sp->flags & SRVR_SOC_ADDR) {
			/* open a TCP listen socket for incoming floods
			 * for each explicitly configured IP address */
			sup = &sp->su;
		} else if (sp->flags & SRVR_SOC_LISTEN) {
			/* open one TCP INADDR_ANY listen socket for
			 * the first implicitly configured interface */
			sup = dcc_mk_su(&su, sp->su.sa.sa_family, 0,
					sp->su.ipv6.sin6_port);
		} else {
			if (sp->listen >= 0) {
				close(sp->listen);
				sp->listen = -1;
			}
			continue;
		}

		if (sp->listen >= 0)
			continue;

		/* don't need to listen if there is no flooding */
		if (!oflods.total)
			continue;

		sp->listen = socket(sup->sa.sa_family, SOCK_STREAM, 0);
		if (sp->listen < 0) {
			dcc_error_msg("socket(listen %s): %s",
				      dcc_su2str(sup), ERROR_STR());
			continue;
		}
		on = 1;
		if (0 > setsockopt(sp->listen, SOL_SOCKET, SO_REUSEADDR,
				   &on, sizeof(on)))
			dcc_error_msg("setsockopt(listen %s, SO_REUSADDR): %s",
				      dcc_su2str(sup), ERROR_STR());
		if (0 > fcntl(sp->listen, F_SETFD, FD_CLOEXEC))
			dcc_error_msg("fcntl(listen %s FD_CLOEXEC): %s",
				      dcc_su2str(sup), ERROR_STR());

		i = bind(sp->listen, &sup->sa, DCC_SU_LEN(sup));
		if (0 > i) {
			dcc_error_msg("bind(listen %s): %s",
				      dcc_su2str(sup), ERROR_STR());
			close(sp->listen);
			sp->listen = -1;
			continue;
		}

		if (0 > listen(sp->listen, 1)) {
			dcc_error_msg("listen(%s): %s",
				      dcc_su2str(sup), ERROR_STR());
			close(sp->listen);
			sp->listen = -1;
		}
	}
}



/* list the current flooders */
int
flods_list(char *buf, int buf_len, u_char anon)
{
#define FLODS_LIST_TOO_SHORT "buffer too short\n"
#define FLODS_LIST_ALLOC(i) {					\
	p += (i);						\
	if ((buf_len -= (i)) <= 0) {				\
		strcpy(p, FLODS_LIST_TOO_SHORT);		\
		return (p-buf)+ISZ(FLODS_LIST_TOO_SHORT);	\
	}}
	IFLOD_INFO *ifp;
	OFLOD_INFO *ofp;
	u_char have_in, sep_in;
	int i;
	char *p;

	if (buf_len < ISZ(FLODS_LIST_TOO_SHORT) +INET6_ADDRSTRLEN+1)
		return 0;

	buf_len -= ISZ(FLODS_LIST_TOO_SHORT);
	p = buf;
	for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) {
		if (ofp->hostname[0] == '\0')
			break;
		have_in = 0;
		sep_in = 0;
		for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) {
			if (ifp->ofp == ofp) {
				if (ifp->s >= 0) {
					have_in = 1;
					if (ofp->s < 0
					    || !DCC_SU_SA_EQ(&ifp->su,
							&ofp->su))
					    sep_in = 1;
				}
				break;
			}
		}
		if (anon) {
			i = snprintf(p, buf_len, "%5d %15s\t%s\n",
				     ofp->rem_id,
				     ofp->s >= 0
				     ? (!(ofp->flags & OFLOD_FG_CONNECTED)
					? "  (connecting)"
					: "")
				     : (ofp->o_opts.flags & FLOD_OPT_OFF)
				     ? "  (output off)"
				     : flods_st != FLODS_ST_ON
				     ? "  (flood off)"
				     : "  (no output)",
				     have_in
				     ? (!(ifp->flags & IFLOD_FG_VERS_CK)
					? "  (connecting)"
					: "")
				     : (ofp->i_opts.flags & FLOD_OPT_OFF)
				     ? "  (input off)"
				     : flods_st != FLODS_ST_ON
				     ? "  (flood off)"
				     : "  (no input)");
		} else {
			i = snprintf(p, buf_len, "%5d %15s\t%s\t%s%s%s\n",
				     ofp->rem_id,
				     ofp->s >= 0
				     ? ((ofp->flags & OFLOD_FG_CONNECTED)
					? dcc_su2str_opt(&ofp->su, 0, '\0')
					: "  (connecting)")
				     : (ofp->o_opts.flags & FLOD_OPT_OFF)
				     ? "  (output off)"
				     : flods_st != FLODS_ST_ON
				     ? "  (flood off)"
				     : "  (no output)",
				     have_in
				     ? (!(ifp->flags & IFLOD_FG_VERS_CK)
					? "  (connecting)"
					: sep_in
					? dcc_su2str_opt(&ifp->su, 0, '\0')
					: "\t")
				     : (ofp->i_opts.flags & FLOD_OPT_OFF)
				     ? "  (input off)"
				     : flods_st != FLODS_ST_ON
				     ? "  (flood off)"
				     : "  (no input)",
				     ofp->hostname,
				     ofp->port!=def_port ? "," : "",
				     ofp->port!=def_port ? ofp->portname : "");
		}
		FLODS_LIST_ALLOC(i);
	}

	for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) {
		if (ifp->s < 0 || ifp->ofp != 0)
			continue;	/* already handled this one */

		/* say something about an incomplete connection */
		i = snprintf(p, buf_len, " ?    %s\n",
			     dcc_su2str_opt(&ifp->su, 0, '\0'));
		FLODS_LIST_ALLOC(i);
	}
	if (p > buf)
		--p;			/* trim trailing '\n' */
	return p-buf;
#undef FLODS_LIST_TOO_SHORT
#undef FLODS_LIST_ALLOC
}



static u_char
flod_stats_time(char **buf, int *buf_len,
		const char *pat, time_t when)
{
	struct tm tm;
	int i;

	if (*buf_len <= 0)
		return 0;
	i = strftime(*buf, *buf_len, pat, dcc_localtime(when, &tm));
	if (i <= 0) {
		*buf_len = 0;
		return 0;
	}
	*buf += i;
	*buf_len -= i;
	return 1;
}



static void
flod_stats_conn_total(char **buf, int *buf_len,
		      const char *label, int connected, time_t start)
{
	int i;

	if (*buf_len <= 0)
		return;

	if (start)
		connected += db_time.tv_sec - start;
	i = snprintf(*buf, *buf_len,
		     "\n  %s connected a total of %d days %d:%02d:%02d\n",
		     label,
		     connected/(24*60*60),
		     (connected/(60*60)) % 24,
		     (connected/60) % 60,
		     connected % 60);
	*buf += i;
	*buf_len -= i;
}



const char *
flod_stats_str(int *pe, int e, enum FLOD_ERR_OP op)
{
	switch (op) {
	case FLOD_ERR_OK:	    *pe = 0;	return "";
	case FLOD_ERR_SAME:	    *pe = 0;	return "";
	case FLOD_ERR_NO_LINK:	    *pe = 0;	return "no link";
	case FLOD_ERR_GET_HOST:	    *pe = e;	return "bad host name";
	case FLOD_ERR_CONNECT:	    *pe = e;	return "initial connect";
	case FLOD_ERR_CONNECT2:	    *pe = e;	return "second connect";
	case FLOD_ERR_IO:	    *pe = e;	return "I/O error";
	case FLOD_ERR_DUP:	    *pe = 0;	return "duplicate connection";
	case FLOD_ERR_LOCAL_OFF:    *pe = 0;	return "local flood off";
	case FLOD_ERR_REMOTE_OFF:   *pe = 0;	return "remote flood off";
	case FLOD_ERR_ID:	    *pe = 0;	return "bad ID";
	case FLOD_ERR_SIGN:	    *pe = 0;	return "bad password";
	case FLOD_ERR_BAD_VERS:	    *pe = 0;	return "bad version";
	case FLOD_ERR_BAD_DATA:	    *pe = 0;	return "bad data";
	case FLOD_ERR_PEER_FAIL:    *pe = 0;	return "peer disconnect";
	case FLOD_ERR_KEEPALIVE:    *pe = 0;	return "keepalive";
	}
	*pe = 0; return "??";
}



static void
flod_stats_conn_cur(char **buf, int *buf_len,
		    time_t start, time_t stop, time_t cleared,
		    LAST_ERROR *ep, time_t keep_time)
{
	const char *opstr;
	int old_errno;
	time_t ok, rep_report;
	int i;

	if (*buf_len <= 0)
		return;

	if (start != 0) {
		if (!flod_stats_time(buf, buf_len,
				     "     connected since %b %d %X",
				     start))
			return;
		if (!flod_stats_time(buf, buf_len,
				     (db_time.tv_sec >= keep_time
				      ? "    keepalive expired %X"
				      : "    keepalive expires %X"),
				     keep_time))
			return;
		return;
	}

	opstr = flod_stats_str(&old_errno, ep->old_errno, ep->op);
	i = snprintf(*buf, *buf_len, "     not connected");
	if ((*buf_len -= i) <= 0) {
		*buf_len = 0;
		return;
	}
	*buf += i;
	if (stop >cleared) {
		if (!flod_stats_time(buf, buf_len, " since %b %d %X",
				     stop))
			return;
	}
	if (old_errno == 0) {
		i = snprintf(*buf, *buf_len, "; %s",
			     opstr);
	} else {
		i = snprintf(*buf, *buf_len, "; %s: %s",
			     opstr, ERROR_STR1(old_errno));
	}
	if ((*buf_len -= i) <= 0) {
		*buf_len = 0;
		return;
	}
	*buf += i;

	ok = ep->ok;
	if (ok > db_time.tv_sec
	    && !flod_stats_time(buf, buf_len,
				"%n     do not check until %b %d %X", ok))
		return;

	rep_report = ep->rep_report;
	if (rep_report > ok && rep_report > db_time.tv_sec
	    && !flod_stats_time(buf, buf_len,
				"%n     no repeated complaints until %b %d %X",
				rep_report))
		return;
}



void
new_peer(OFLOD_INFO *ofp)
{
	ofp->in_try_again = 0;
	ofp->out_try_again = 0;
	if (ofp->mp) {
		ofp->mp->i_err.op = FLOD_ERR_NO_LINK;
		ofp->mp->i_err.ok = 0;
		ofp->mp->i_err.rep_report = db_time.tv_sec + FLOD_RETRY_SECS*2;

		ofp->mp->o_err.op = FLOD_ERR_NO_LINK;
		ofp->mp->o_err.ok = 0;
		ofp->mp->o_err.rep_report = db_time.tv_sec + FLOD_RETRY_SECS*2;
	}
}



/* list the counts for a flood */
int
flod_stats(char *buf, int buf_len, u_int32_t tgt, u_char clear)
{
#define FLOD_STATS_TOO_SHORT "buffer too short\n"
#define FLOD_STATS_ALLOC(i) (p += (i), len -= (i))
	OFLOD_INFO *ofp, *ofp1;
	FLOD_MMAP *mp;
	char now_buf[26], time_buf[26];
	DCC_SRVR_ID min_srvr, max_srvr;
	u_char mapped;
	struct tm tm;
	int len, i;
	char *p;

	if (buf_len < ISZ(FLOD_STATS_TOO_SHORT))
		return 0;
	len = buf_len - ISZ(FLOD_STATS_TOO_SHORT);
	p = buf;

	strftime(now_buf, sizeof(now_buf), "%b %d %X %Z",
		 dcc_localtime(db_time.tv_sec, &tm));

	if (flod_mmaps) {
		mapped = 0;
	} else {
		oflods_load();
		mapped = 1;
	}

	if (tgt <= DCC_SRVR_ID_MAX) {
		/* an explicit target server-ID was specified */
		min_srvr = max_srvr = tgt;
	} else {
		/* look for next server-ID after the target value */
		min_srvr = tgt - DCC_SRVR_ID_MAX;
		max_srvr = DCC_SRVR_ID_MAX;
	}
	ofp = 0;
	for (ofp1 = oflods.infos; ofp1 <= LAST(oflods.infos); ++ofp1) {
		if (ofp1->rem_id != DCC_ID_INVALID
		    && ofp1->rem_id >= min_srvr
		    && ofp1->rem_id <= max_srvr
		    && ofp1->mp != 0) {
			/* This peer fits and is the best so far. */
			ofp = ofp1;
			max_srvr = ofp->rem_id-1;
		}
	}
	if (!ofp) {
		i = snprintf(p, len,
			     DCC_AOP_FLOD_STATS_ID"unknown remote server-ID",
			     tgt);
		FLOD_STATS_ALLOC(i);
		if (mapped)
			oflods_unmap();
		return p-buf;
	}
	mp = ofp->mp;

	strftime(time_buf, sizeof(time_buf), "%b %d %X %Z",
		 dcc_localtime(mp->cnts.counts_cleared, &tm));
	i = snprintf(p, len,
		     DCC_AOP_FLOD_STATS_ID" %s%s%s%s  %s\n  status start %s",
		     ofp->rem_id, mp->hostname,
		     (mp->flags & OFLOD_MMAP_FG_REWINDING)
		     ? "  rewinding" : "",
		     ((mp->flags & OFLOD_MMAP_FG_NEED_REWIND)
		      ? "  need rewind"
		      : (mp->flags & OFLOD_MMAP_FG_FFWD_IN)
		      ? "  need FFWD"
		      : ""),
		     (mp->flags & OFLOD_MMAP_FG_PASSWD_NEXT)
		     ? "  alt password" : "",
		     now_buf, time_buf);
	FLOD_STATS_ALLOC(i);
	flod_stats_conn_total(&p, &len, "output",
			      mp->cnts.out_connected, ofp->cnts.out_start);
	i = snprintf(p, len, "     %d reports sent%s\n",
		     mp->cnts.out_reports+ofp->cnts.out_reports,
		     mp->flags & OFLOD_MMAP_FG_PASSIVE ? "   PASSIVE" : "");
	FLOD_STATS_ALLOC(i);
	flod_stats_conn_cur(&p, &len, ofp->cnts.out_start,
			    ofp->mp->cnts.out_last_connected,
			    mp->cnts.counts_cleared,
			    &ofp->mp->o_err,
			    ofp->keep_out_time);

	flod_stats_conn_total(&p, &len, "input",
			      mp->cnts.in_connected, ofp->cnts.in_start);
	i = snprintf(p, len,
		     "     %d reports received  %d accepted"
		     "  %d duplicate  %d stale\n"
		     "     %d bad whitelist  %d not deleted%s\n",
		     mp->cnts.total+ofp->cnts.total,
		     mp->cnts.accepted+ofp->cnts.accepted,
		     mp->cnts.dup+ofp->cnts.dup.val,
		     mp->cnts.stale+ofp->cnts.stale.val,
		     mp->cnts.ok2+ofp->cnts.ok2.val,
		     mp->cnts.not_deleted+ofp->cnts.not_deleted.val,
		     mp->flags & OFLOD_MMAP_FG_SOCKS ? "   SOCKS" : "");
	FLOD_STATS_ALLOC(i);
	flod_stats_conn_cur(&p, &len, ofp->cnts.in_start,
			    ofp->mp->cnts.in_last_connected,
			    mp->cnts.counts_cleared,
			    &ofp->mp->i_err,
			    ofp->keep_in_time);

	if (len <= 0) {
		strcpy(buf, FLOD_STATS_TOO_SHORT);
		if (mapped)
			oflods_unmap();
		return ISZ(FLOD_STATS_TOO_SHORT);
	}

	save_flod_cnt(ofp, ofp->cnts.in_start != 0);
	if (clear) {
		ofp->limit_reset = db_time.tv_sec + FLOD_LIM_CLEAR_SECS;
		memset(&mp->cnts, 0, sizeof(mp->cnts));
		mp->cnts.counts_cleared = db_time.tv_sec;
		ofp->mp->cnts.out_last_connected = db_time.tv_sec;
		ofp->mp->cnts.in_last_connected = db_time.tv_sec;
		new_peer(ofp);
	}

	if (mapped)
		oflods_unmap();
	return p-buf;
#undef FLOD_STATS_TOO_SHORT
#undef FLOD_STATS_ALLOC
}
