view dccd/iflod.c @ 3:b689077d4918

Ignore old patches
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 14:31:24 +0100
parents c7f6b056b673
children
line wrap: on
line source

/* Distributed Checksum Clearinghouse
 *
 * deal with incoming floods of checksums
 *
 * Copyright (c) 2008 by Rhyolite Software, LLC
 *
 * This agreement is not applicable to any entity which sells anti-spam
 * solutions to others or provides an anti-spam solution as part of a
 * security solution sold to other entities, or to a private network
 * which employs the DCC or uses data provided by operation of the DCC
 * but does not provide corresponding data to other users.
 *
 * Permission to use, copy, modify, and distribute this software without
 * changes for any purpose with or without fee is hereby granted, provided
 * that the above copyright notice and this permission notice appear in all
 * copies and any distributed versions or copies are either unchanged
 * or not called anything similar to "DCC" or "Distributed Checksum
 * Clearinghouse".
 *
 * Parties not eligible to receive a license under this agreement can
 * obtain a commercial license to use DCC by contacting Rhyolite Software
 * at sales@rhyolite.com.
 *
 * A commercial license would be for Distributed Checksum and Reputation
 * Clearinghouse software.  That software includes additional features.  This
 * free license for Distributed ChecksumClearinghouse Software does not in any
 * way grant permision to use Distributed Checksum and Reputation Clearinghouse
 * software
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.3.103-1.249 $Revision$
 */

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


IFLODS iflods;

u_int complained_many_iflods;

time_t got_hosts;
pid_t resolve_hosts_pid = -1;

time_t iflods_ok_timer;			/* incoming flooding ok since then */

int flod_trace_gen;			/* unsuppress tracing */

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


static DCC_TS future;			/* timestamp sanity */



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 */
	for (i = 0; i < opts->num_maps; ++i) {
		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 *
rem_str(const char *hostname, const DCC_SOCKU *su)
{
	static int bufno;
	static struct {
	    char    str[90];
	} bufs[4];
	char *s;
	char sustr[DCC_SU2STR_SIZE];
	int i;

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

	STRLCPY(s, hostname, sizeof(bufs[0].str));

	if (su->sa.sa_family == AF_UNSPEC
	    && *DCC_SU_PORTP(su) == 0)
		return s;

	dcc_su2str2(sustr, sizeof(sustr), su);
	if (strcmp(s, sustr)) {
		STRLCAT(s, " ", sizeof(bufs[0].str));
		STRLCAT(s, sustr, sizeof(bufs[0].str));
	}
	if (*DCC_SU_PORTP(su) != DCC_GREY2PORT(grey_on)) {
		i = strlen(s);
		snprintf(s+i, sizeof(bufs[0].str)-i,
			 ",%d", ntohs(*DCC_SU_PORTP(su)));
	}
	return s;
}



const char *
ifp_rem_str(const IFLOD_INFO *ifp)
{
	if (!ifp)
		return "(null ifp)";
	return rem_str(ifp->rem_hostname, &ifp->rem_su);
}



const char *
ofp_rem_str(const OFLOD_INFO *ofp)
{
	if (!ofp)
		return "(null ofp)";
	return rem_str(ofp->rem_hostname, &ofp->rem_su);
}



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

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

	snprintf(s, sizeof(bufs[0].str), "%s%s %s ID=%s %s%s",
		 type ? "flooded " : "",
		 type ? type : "",
		 ts2str_err(&rcd->ts),
		 id2str(id_buf, sizeof(id_buf), rcd->srvr_id_auth),
		 ifp ? "from " : "",
		 ifp ? ifp_rem_str(ifp) : "");
	return s;
}



void PATTRIB(2,3)
flod_cnterr(const FLOD_LIMCNT *lc, const char *p, ...)
{
	char buf[200];
	va_list args;

	va_start(args, p);
	if (lc->cur < lc->lim + FLOD_LIM_COMPLAINTS) {
		dcc_verror_msg(p, args);
	} else {
		vsnprintf(buf, sizeof(FLOD_EMSG), p, args);
		dcc_error_msg("%s; stop complaints", buf);
	}
	va_end(args);
}



static void
date_err_msg(FLOD_EMSG out, int len, const char *in)
{
	memcpy(out, in, len);
	if (len >= ISZ(FLOD_EMSG)-LITZ(" at hh:mm:ss")-1) {
		out[len] = '\0';
	} else {
		dcc_time2str(&out[len], ISZ(FLOD_EMSG)-len, " at %T",
			     db_time.tv_sec);
	}
}



/* remove extra quotes and strings from a message to or from a peer */
static void
trim_err_msg(FLOD_EMSG out, const FLOD_EMSG in)
{
	char *q1, *q2;
	int len;

	q1 = strchr(in, '\'');
	if (q1) {
		q2 = strrchr(++q1, '\'');
		if (q2) {
			len = q2-q1;
			if (len > LITZ(DCC_FLOD_OK_STR)
			    && !LITCMP(q1, DCC_FLOD_OK_STR)) {
				len -= LITZ(DCC_FLOD_OK_STR);
				q1 += LITZ(DCC_FLOD_OK_STR);
			}
			date_err_msg(out, len, q1);
			return;
		}
	}
	date_err_msg(out, strlen(in), in);
}



/* report a flooding error */
void PATTRIB(4,5)
rpt_err(OFLOD_INFO *ofp,
	u_char trace,			/* 0=error, 1=trace, 2=no dup trace */
	u_char in,			/* 0=output 1=input */
	const char *p, ...)
{
	FLOD_MMAP *mp;
	LAST_ERROR *ep;
	FLOD_EMSG tmp, trimmed;
	va_list args;

	va_start(args, p);
	vsnprintf(tmp, sizeof(FLOD_EMSG), p, args);
	va_end(args);

	mp = ofp ? ofp->mp : 0;

	if (trace != 0) {
		if (!mp) {
			TMSG_FLOD(ofp, tmp);
			return;
		}

		trim_err_msg(trimmed, tmp);
		ep = in ? &mp->iflod_err : &mp->oflod_err;
		if (TMSG_FB(ofp)
		    && (trace < 2
			|| strcmp(trimmed, ep->trace_msg)
			|| ep->trace_gen != flod_trace_gen)) {
			/* suppress some duplicate flooding messages */
			dcc_trace_msg(tmp);
			ep->trace_gen = flod_trace_gen;
		}
		strncpy(ep->trace_msg, trimmed, sizeof(ep->trace_msg));
		ep->complained = 0;

	} else {
		if (!mp) {
			dcc_error_msg(tmp);
			return;
		}

		dcc_error_msg(tmp);

		ep = in ? &mp->iflod_err : &mp->oflod_err;
		trim_err_msg(ep->msg, tmp);
		ep->trace_msg[0] = '\0';
		ep->complained = 0;
	}
}



u_char
set_flod_socket(OFLOD_INFO *ofp, u_char in, 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))
		rpt_err(ofp, 0, in, "fcntl(%s, F_SETFD, FD_CLOEXEC): %s",
			rem_str(hostname, sup), ERROR_STR());

	if (-1 == fcntl(s, F_SETFL,
			fcntl(s, F_GETFL, 0) | O_NONBLOCK)) {
		rpt_err(ofp, 0, in, "fcntl(%s, O_NONBLOCK): %s",
			rem_str(hostname, sup), ERROR_STR());
		return 0;
	}

	on = 1;
	if (0 > setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
			   &on, sizeof(on)))
		rpt_err(ofp, 0, in, "setsockopt(flod %s, SO_KEEPALIVE): %s",
			rem_str(hostname, sup), ERROR_STR());

	if (in) {
		/* Ensure that we have enough socket buffer space to send
		 * complaints about the input flood.  Normally little or
		 * nothing is sent upstream, but bad clocks or other
		 * problems can cause many complaints. */
		if (0 > setsockopt(s, SOL_SOCKET, SO_SNDBUF,
				   &srvr_rcvbuf, sizeof(srvr_rcvbuf)))
			rpt_err(ofp, 0, in, "setsockopt(%s, SO_SNDBUF): %s",
				rem_str(hostname, sup), 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))) {
			rpt_err(ofp, 0, in,
				"setsockopt(IP_TOS, IPTOS_THROUGHPUT, %s): %s",
				rem_str(hostname, sup), 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)
{
	int status;

	if (resolve_hosts_pid < 0)
		return 1;

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

	RUSH_NEXT_FLODS_CK();
	return 0;
}



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

	for (mp = flod_mmaps->mmaps; mp <= LAST(flod_mmaps->mmaps); ++mp) {
		if (mp->rem_hostname[0] == '\0'
		    || (mp->flags & FLODMAP_FG_PASSIVE))
			continue;
		ipv6 = ((mp->flags & FLODMAP_FG_IPv4) ? 0
			: (mp->flags & FLODMAP_FG_IPv6) ? 1
			: use_ipv6 ? 2 : 0);
		dcc_host_lock();
		if (mp->flags & FLODMAP_FG_SOCKS)
			ok = dcc_get_host_SOCKS(mp->rem_hostname, ipv6,
						&mp->host_error);
		else
			ok = dcc_get_host(mp->rem_hostname, ipv6,
					  &mp->host_error);
		if (!ok) {
			TMSG2(FLOD, "failed to resolve %s: %s",
			      mp->rem_hostname, DCC_HSTRERROR(mp->host_error));
		} else {
			for (sup = dcc_hostaddrs;
			     sup < dcc_hostaddrs_end;
			     ++sup) {
				if ((ipv6 == 0
				     && sup->sa.sa_family != AF_INET)
				    || (ipv6 == 1
					&& sup->sa.sa_family != AF_INET6))
					continue;
				mp->rem_su = *sup;
				*DCC_SU_PORTP(&mp->rem_su) = mp->rem_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)
{
	FLOD_MMAP *mp;

	if (!flod_mmaps)
		return 0;

	/* wait for background job to finish */
	if (!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;
	for (mp = flod_mmaps->mmaps; mp <= LAST(flod_mmaps->mmaps); ++mp) {
		mp->rem_su.sa.sa_family = AF_UNSPEC;
		mp->host_error = 0;
	}
	flod_mmap_sync(0, 1);

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

	resolve_hosts_pid = fork();
	if (resolve_hosts_pid > 0) {
		/* check again soon */
		RUSH_NEXT_FLODS_CK();
		return 0;
	}

	if (resolve_hosts_pid == -1) {
		dcc_error_msg("fork(flood names resolve start): %s;"
			      " fall back to foreground resolving",
			      ERROR_STR());
		flod_names_resolve();
		return 1;
	}

	TMSG(FLOD, "resolving hostnames started");
	/* close files and sockets to avoid interfering with parent */
	db_close(-1);
	after_fork();

	flod_names_resolve();

	TMSG(FLOD, "resolving hostnames finished");

	exit(0);
}



static void
iflod_clear(IFLOD_INFO *ifp,
	    u_char fail)		/* 1=problems so delay restart */
{
	OFLOD_INFO *ofp;
	FLOD_MMAP *mp;

	ofp = ifp->ofp;
	if (fail && ofp != 0) {
		mp = ofp->mp;
		if (mp->itimers.retry_secs < FLOD_RETRY_SECS)
			mp->itimers.retry_secs = FLOD_RETRY_SECS;
		mp->itimers.retry = db_time.tv_sec + mp->itimers.retry_secs;
		if ((mp->flags & FLODMAP_FG_ACT) != 0)
			TMSG3_FLOD(ofp,
				   "postpone restarting %s flood from"
				   " %s for %d seconds",
				   (mp->flags & FLODMAP_FG_SOCKS)
				   ? "SOCKS"
				   : (mp->flags & FLODMAP_FG_NAT)
				   ? "NAT"
				   : "auto-NAT",
				   ofp_rem_str(ofp), mp->itimers.retry_secs);
	}

	/* do not close the socket here, because it may be a passive outgoing
	 * stream that is being converted  */
	if (ifp->soc >= 0)
		--iflods.open;

	memset(ifp, 0, sizeof(*ifp));
	ifp->soc = -1;

	if (iflods.open == 0
	    && oflods.open == 0
	    && flods_st != FLODS_ST_ON)
		oflods_clear();
}



void PATTRIB(5,6)
iflod_close(IFLOD_INFO *ifp,
	    u_char fail,		/* 1=already sick; more is no problem */
	    u_char complain,		/* 1=error not just trace message */
	    u_char send_reason,
	    const char *pat, ...)
{
	struct {
	    DCC_FLOD_POS    last_pos;
	    DCC_FLOD_RESP   e;
	    u_char	    null;	/* to eat '\0' for e.msg */
	} resp;
	void *wp;
	va_list args;
	OFLOD_INFO *ofp;
	struct linger nowait;
	int wlen;

	ofp = ifp->ofp;

	db_ptr2flod_pos(resp.e.end.pos, DCC_FLOD_POS_END);
	va_start(args, pat);
	/* Throw away the last byte of resp.e.end.msg because the too smart
	 * by half gcc Fortify nonsense won't allow ISZ(resp.e.end.msg)+1.
	 * That put the would the unwanted '\0' from vsnprintf() into
	 * resp.null*/
	wlen = vsnprintf(resp.e.end.msg, ISZ(resp.e.end.msg), pat, args);
	va_end(args);
	if (wlen > ISZ(resp.e.end.msg))
		wlen = ISZ(resp.e.end.msg);
	wlen += FLOD_END_OVHD;

	/* If useful, prefix our final message with our final position
	 * The peer will see our position and final operation as two
	 * separate responses. */
	if (memcmp(ifp->pos, ifp->pos_sent, ISZ(ifp->pos))) {
		memcpy(resp.last_pos, ifp->pos, ISZ(resp.last_pos));
		wlen += ISZ(resp.last_pos);
		wp = &resp.last_pos;
	} else {
		wp = &resp.e;
	}

	if (send_reason) {
		rpt_err(ofp, !complain, 1,
			"stop incoming flood; %ssend '%s' to %s",
			fail ? "error, " : "",
			resp.e.end.msg,
			ifp_rem_str(ifp));

		/* send the final status report to the sending flooder */
		iflod_write(ifp, wp, wlen, "stop incoming flood", fail ? 2 : 1);
	} else {
		rpt_err(ofp, !complain, 1,
			"stop incoming flood; %s'%s'",
			fail ? "error, " : "", resp.e.end.msg);
	}

	if (ifp->soc >= 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->soc, SOL_SOCKET, SO_LINGER,
					   &nowait, sizeof(nowait))
			    && !fail)
				dcc_error_msg("setsockopt(SO_LINGER %s): %s",
					      ifp_rem_str(ifp), ERROR_STR());
		}

		if (0 > close(ifp->soc)
		    && !fail) {
			if (errno == ECONNRESET)
				TMSG2_FLOD(ofp, "close(flood from %s): %s",
					   ifp_rem_str(ifp), ERROR_STR());
			else
				dcc_error_msg("close(flood from %s): %s",
					      ifp_rem_str(ifp), ERROR_STR());
		}
	}

	/* if this was not a new duplicate connection being discarded,
	 * break the association with the outgoing stream */
	if (ofp != 0 && ofp->ifp == ifp) {
		save_flod_cnts(ofp);
		ofp->ifp = 0;
	}

	iflod_clear(ifp, fail);
}



/* can close the incoming flood and so clear things */
static u_char				/* 0=failed & should be or is closed */
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 (ifp->ofp
	    && (ifp->ofp->o_opts.flags & FLOD_OPT_SOCKS)) {
		i = Rsend(ifp->soc, buf, buf_len, 0);
	} else {
		/* 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. */
		i = send(ifp->soc, buf, buf_len, 0);
	}
	if (i == buf_len) {
		ifp->iflod_alive = db_time.tv_sec;
		return 1;
	}

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



/* send our current position to the peer
 *	the peer must be well known so that ifp->ofp->mp!=0, usually
 *	    because (ifp->flags & IFLOD_FG_VERS_CK)
 *	can close the incoming flood and so clear things */
int					/* -1=fail 0=nothing to send, 1=sent */
iflod_send_pos(IFLOD_INFO *ifp,
	       u_char force)		/* say just anything */
{
	DCC_FLOD_POS req;
	OFLOD_INFO *ofp;
	FLOD_MMAP *mp;

	ofp = ifp->ofp;
	mp = ofp->mp;

	/* ask peer to start over if our database has been cleared */
	if (mp->flags & FLODMAP_FG_FFWD_IN) {
		mp->flags &= ~(FLODMAP_FG_FFWD_IN
			       | FLODMAP_FG_NEED_REWIND);
		memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent));
		db_ptr2flod_pos(req, DCC_FLOD_POS_FFWD_IN);
		dcc_trace_msg("ask %s to FFWD flood to us",
			      ifp_rem_str(ifp));
		if (!iflod_write(ifp, req, sizeof(req), "ffwd request", 0))
			return -1;
		return 1;
	}
	if (mp->flags & FLODMAP_FG_NEED_REWIND) {
		mp->flags &= ~FLODMAP_FG_NEED_REWIND;
		memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent));
		db_ptr2flod_pos(req, DCC_FLOD_POS_REWIND);
		dcc_trace_msg("ask %s to rewind flood to us",
			      ifp_rem_str(ifp));
		if (!iflod_write(ifp, req, sizeof(req), "rewind request", 0))
			return -1;
		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));
		if (!iflod_write(ifp, ifp->pos_sent, sizeof(ifp->pos_sent),
				 "confirmed pos", 0))
			return -1;

		/* reset the no-connection-from-peer complaint delay */
		mp->itimers.msg_secs = FLOD_IN_COMPLAIN1;
		mp->itimers.msg = db_time.tv_sec + FLOD_IN_COMPLAIN1;

		/* things are going well, so forget old input complaints */
		if (!(mp->flags & FLODMAP_FG_IN_SRVR)) {
			mp->iflod_err.msg[0] = '\0';
			mp->iflod_err.trace_msg[0] = '\0';
		}

		/* limit the backoff for outgoing connection attempts
		 * while the incoming connection is working */
		DB_ADJ_TIMER(&mp->otimers.retry, &mp->otimers.retry_secs,
			     FLOD_RETRY_SECS);

		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) {
		DCC_FLOD_RESP buf;

		db_ptr2flod_pos(buf.note.op, DCC_FLOD_POS_NOTE);
		strcpy(buf.note.str, "are you there?");
		buf.note.len = sizeof("are you there?") + FLOD_NOTE_OVHD;
		TMSG1_FLOD(ofp, "flood note to %s: \"are you there?\"",
			   ifp_rem_str(ifp));
		if (!iflod_write(ifp, &buf.note, buf.note.len, buf.note.str, 0))
			return -1;
		return 1;
	}

	return 0;
}



void
iflod_listen_close(SRVR_SOC *sp)
{
	if (sp->listen < 0)
		return;

	TMSG1(FLOD, "stop flood listening on %s", dcc_su2str_err(&sp->su));
	if (0 > close(sp->listen))
		TMSG2(FLOD, "close(flood listen on %s): %s",
		      dcc_su2str_err(&sp->su), ERROR_STR());
	sp->listen = -1;
}



/* send stop requests to DCC servers flooding to us
 *	can close the incoming flood and so clear things */
void
iflods_stop(const char *reason,
	    u_char force)		/* 1=now */
{
	SRVR_SOC *sp;
	IFLOD_INFO *ifp;
	DCC_FLOD_POS end_req;

	/* stop listening for new connections */
	for (sp = srvr_socs; sp; sp = sp->fwd) {
		iflod_listen_close(sp);
	}

	for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) {
		if (ifp->soc < 0)
			continue;

		/* start shutting down each real, still alive connection */
		if (!(ifp->flags & IFLOD_FG_END_REQ)
		    && (ifp->flags & IFLOD_FG_VERS_CK)) {
			if (!reason || !*reason)
				rpt_err(ifp->ofp, 1, 1,
					"flood from %s stopping",
					ifp_rem_str(ifp));
			else
				rpt_err(ifp->ofp, 1, 1,
					"flood from %s stopping: '%s'",
					ifp_rem_str(ifp), reason);

			/* send any delay position and then a stop request */
			if (0 <= iflod_send_pos(ifp, 0)) {
				db_ptr2flod_pos(end_req, DCC_FLOD_POS_END_REQ);
				iflod_write(ifp, end_req, sizeof(end_req),
					    "flood stop req", 0);
				ifp->flags |= IFLOD_FG_END_REQ;
			}

			/* done if the socket died */
			if (ifp->soc < 0)
				continue;
		}

		/* break the connection if forced or never authenticated */
		if (force || !(ifp->flags & IFLOD_FG_VERS_CK)) {
			if (!reason || !*reason)
				iflod_close(ifp, 1, 0, 1,
					    "flooding off at %s",
					    our_hostname);
			else
				iflod_close(ifp, 1, 0, 1,
					    "flooding off at %s; %s",
					    our_hostname, reason);
			continue;
		}

		/* break the conneciton if the peer is too slow */
		if ((ifp->flags & IFLOD_FG_END_REQ)
		    && IFP_DEAD(ifp, stopint
				? SHUTDOWN_DELAY : KEEPALIVE_IN_STOP)) {
			if (!reason || !*reason)
				iflod_close(ifp, 1, 0, 1,
					    DCC_FLOD_OK_STR" force close from"
					    " %s",
					    ifp_rem_str(ifp));
			else
				iflod_close(ifp, 1, 0, 1,
					    DCC_FLOD_OK_STR" force close from"
					    " %s; %s",
					    ifp_rem_str(ifp), reason);
			continue;
		}
	}
}



/* start receiving checksums from another DCC server */
void
iflod_start(SRVR_SOC *sp)
{
	IFLOD_INFO *ifp;
	DCC_SOCKLEN_T l;
	struct in6_addr peer_addr;
	int count;
	const struct in6_addr *cap;
	RL *rl;

	/* accept all waiting connections to avoid starving any	*/
	for (count = 0; count <= DCCD_MAX_FLOODS; ++count) {
		/* find a free input flooding slot */
		for (ifp = iflods.infos; ifp->soc >= 0; ++ifp) {
			if (ifp > LAST(iflods.infos)) {
				if (!(complained_many_iflods++))
					dcc_error_msg("too many floods");
				goto again;
			}
		}

		l = sizeof(ifp->rem_su);
		ifp->soc = accept(sp->listen, &ifp->rem_su.sa, &l);
		if (ifp->soc < 0) {
			if (!DCC_BLOCK_ERROR() || count == 0)
				dcc_error_msg("accept(flood): %s", ERROR_STR());
			return;
		}

		/* use the IP address as the host name until we know which
		 * peer it is */
		dcc_su2str2(ifp->rem_hostname, sizeof(ifp->rem_hostname),
			    &ifp->rem_su);
		if (!set_flod_socket(0, 1, ifp->soc, ifp->rem_hostname,
				     &ifp->rem_su)) {
			close(ifp->soc);
			ifp->soc = -1;
			continue;
		}

		/* quietly forget this peer if it is blacklisted */
		if (ifp->rem_su.sa.sa_family == AF_INET6) {
			cap = &ifp->rem_su.ipv6.sin6_addr;
		} else {
			dcc_ipv4toipv6(&peer_addr, ifp->rem_su.ipv4.sin_addr);
			cap = &peer_addr;
		}
		rl = 0;
		if (ck_ip_bl(&rl, DCC_ID_SRVR_ROGUE, cap)) {
			char buf[120];
			int len;

			rl_inc(rl, &rl_anon_rate);
			len = snprintf(buf, sizeof(buf)-1,
				       "rejected blacklisted flood from %s",
				       ifp_rem_str(ifp));
			if (len > ISZ(buf))
				len = sizeof(buf);
			if ((rl->d.flags & RL_FG_TRACE)
			    || (dccd_tracemask & DCC_TRACE_FLOD_BIT))
				dcc_trace_msg(buf);
			buf[len++] = '\n';
			send(ifp->soc, buf, len, 0);
			close(ifp->soc);
			ifp->soc = -1;
			continue;
		}

		/* reset timer that delays responses to clients while flooding
		 * is stopped */
		if (++iflods.open == 1)
			iflods_ok_timer = db_time.tv_sec + IFLODS_OK_SECS;

		ifp->flags |= IFLOD_FG_CONNECTED;
		ifp->iflod_alive = db_time.tv_sec;

		TMSG1(FLOD, "start flood from %s", ifp_rem_str(ifp));
again:;
	}
}



static void
iflod_socks_backoff(OFLOD_INFO *ofp)
{
	FLOD_MMAP *mp;

	mp = ofp->mp;
	mp->itimers.retry_secs *= 2;
	if (mp->itimers.retry_secs > FLOD_MAX_RETRY_SECS)
		mp->itimers.retry_secs = FLOD_MAX_RETRY_SECS;
	else if (mp->itimers.retry_secs < FLOD_RETRY_SECS)
		mp->itimers.retry_secs = FLOD_RETRY_SECS;
}



/* Start an incoming SOCKS flood by connecting to the other system.
 *	We will eventually turn the connection around and pretend that
 *	the other system initiated the TCP connection.
 *	This can close the flood and so clear things */
static int				/* -1=failure, 0=not yet, 1=done */
iflod_socks_connect(IFLOD_INFO *ifp)
{
	OFLOD_INFO *ofp;
	DCC_SRVR_ID id;
	DCC_FLOD_VERSION_HDR buf;
	DCC_FNM_LNO_BUF fnm_buf;
	const char *emsg;
	int i;

	ofp = ifp->ofp;

	memset(&buf, 0, sizeof(buf));
	strcpy(buf.body.str, version_str(ofp));
	id = htons(my_srvr_id);
	memcpy(buf.body.sender_srvr_id, &id, sizeof(buf.body.sender_srvr_id));
	buf.body.turn = 1;
	emsg = flod_sign(ofp, 1, &buf, sizeof(buf));
	if (emsg) {
		iflod_socks_backoff(ofp);
		iflod_close(ifp, 1, 1, 1, "%s %d%s",
			    emsg, ofp->out_passwd_id,
			    fnm_lno(&fnm_buf, flod_path, ofp->lno));
		return -1;
	}

	if (ofp->o_opts.flags & FLOD_OPT_SOCKS) {
		i = Rconnect(ifp->soc, &ifp->rem_su.sa,
			     DCC_SU_LEN(&ifp->rem_su));
	} else {
		/* must be NAT or assumed NAT */
		i = connect(ifp->soc, &ifp->rem_su.sa,
			    DCC_SU_LEN(&ifp->rem_su));
	}
	if (0 > i && errno != EISCONN) {
		if (errno == EAGAIN
		    || errno == EINPROGRESS
		    || errno == EALREADY) {
			rpt_err(ofp, 1, 0, "starting flood from %s",
				ifp_rem_str(ifp));
			return 0;
		}

		/* it is lame to only trace instead of reporting EINVAL as
		 * an error, but several UNIX-like systems return EINVAL for
		 * the second connect() after a Unreachable ICMP message
		 * or after a timeout */
		rpt_err(ofp,
			(errno == EINVAL || errno == ECONNABORTED
			 || errno == ECONNRESET || errno == ETIMEDOUT
			 || errno == ECONNREFUSED),
			1, "connect(SOCKS FLOD %s): %s",
			ifp_rem_str(ifp),
			errno == EINVAL
			? "likely connection refused or local firewall"
			: ERROR_STR());
		close(ifp->soc);
		iflod_socks_backoff(ofp);
		iflod_clear(ifp, 1);

		return -1;
	}

	ifp->flags |= (IFLOD_FG_CONNECTED | IFLOD_FG_CLIENT);

	rpt_err(ofp, 1, 1, "starting SOCKS from %s", ifp_rem_str(ifp));

	/* After the SOCKS incoming flood socket is connected,
	 *	send our authentication to convince the peer to send its
	 *	authentication and then its checksums. */
	if (!iflod_write(ifp, &buf, sizeof(buf),
			 "flood SOCKS authentication", 0))
		return -1;
	return 1;
}



/* Request the start of an input flood via SOCKS if it is not already flowing,
 *	by connecting to the remote system.
 *	This can close the flood and so clear things */
void
iflod_socks_start(OFLOD_INFO *ofp)
{
	DCC_FNM_LNO_BUF fnm_buf;
	IFLOD_INFO *ifp, *ifp1;

	/* do nothing if it is already running or is not using SOCKS */
	if (ofp->ifp
	    || (ofp->mp->flags & (FLODMAP_FG_SOCKS | FLODMAP_FG_NAT)) == 0
	    || IFLOD_OPT_OFF_ROGUE(ofp))
		return;
	if (!DB_IS_TIME(ofp->mp->itimers.retry, ofp->mp->itimers.retry_secs))
		return;

	/* 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->soc < 0) {
			if (!ifp)
				ifp = ifp1;
		}
		/* there is nothing to do if it already exists */
		if (ifp1->ofp == ofp)
			return;
	}
	if (!ifp) {
		rpt_err(ofp, ++complained_many_iflods == 1 ? 0 : 2, 1,
			"too many incoming floods to start SOCKS from %s",
			ofp->rem_hostname);
		iflod_socks_backoff(ofp);
		return;
	}

	if (!flod_names_resolve_start())
		return;			/* wait for name resolution */
	if (ofp->mp->rem_su.sa.sa_family == AF_UNSPEC) {
		rpt_err(ofp, 0, 1, "SOCKS flood peer name %s: %s%s",
			ofp->rem_hostname, DCC_HSTRERROR(ofp->mp->host_error),
			fnm_lno(&fnm_buf, flod_path, ofp->lno));
		iflod_socks_backoff(ofp);
		return;
	}
	if (!LITCMP(ofp->mp->oflod_err.msg, "SOCKS flood peer name "))
		ofp->mp->oflod_err.msg[0] = '\0';

	ifp->ofp = ofp;
	ifp->rem_su = ofp->rem_su = ofp->mp->rem_su;
	STRLCPY(ifp->rem_hostname, ofp->rem_hostname,
		sizeof(ifp->rem_hostname));

	ifp->soc = socket(ifp->rem_su.sa.sa_family, SOCK_STREAM, 0);
	if (ifp->soc < 0) {
		rpt_err(ofp, 0, 1, "socket(SOCKS flood %s): %s",
			ifp_rem_str(ifp), ERROR_STR());
		iflod_socks_backoff(ofp);
		return;
	}

	if (!set_flod_socket(ofp, 1, ifp->soc,
			     ifp->rem_hostname, &ifp->rem_su)) {
		close(ifp->soc);
		ifp->soc = -1;
		iflod_socks_backoff(ofp);
		return;
	}

	/* reset timer that delays responses to clients while flooding is
	 * not working */
	if (++iflods.open == 1)
		iflods_ok_timer = db_time.tv_sec + IFLODS_OK_SECS;

	iflod_socks_connect(ifp);
}



/* See if the new report is a duplicate of an old record in the database
 *	db_sts.rcd.d points to the old record */
static int				/* -1=continue, 0=not dup, 1=dup */
ck_dup_rcd(IFLOD_INFO *ifp,
	   const DB_RCD *new, DCC_TGTS new_tgts_raw)
{
	DCC_TGTS old_tgts;
	const DB_RCD_CK *new_ck, *old_ck;
	int new_num_cks, old_num_cks;
	int new_unique, old_unique;

	/* ignore reports for deleted checksums
	 * unless they are new reports or delete requests */
	old_tgts = DB_TGTS_RCD_RAW(db_sts.rcd.d.r);
	if (old_tgts == DCC_TGTS_DEL
	    && new_tgts_raw != DCC_TGTS_DEL
	    && !dcc_ts_newer_ts(&new->ts, &db_sts.rcd.d.r->ts)) {
		if (CK_FLOD_CNTERR(&ifp->ofp->lc.stale)
		    && TMSG_FB2(ifp->ofp))
			flod_cnterr(&ifp->ofp->lc.stale,
				    "ignore deleted %s after %s",
				    rpt_id("report", new, ifp),
				    rpt_id(0, db_sts.rcd.d.r, 0));
		return 1;
	}

	/* not duplicate if the server-IDs or timestamps differ */
	if (DB_RCD_ID(new) != DB_RCD_ID(db_sts.rcd.d.r)
	    || memcmp(&new->ts, &db_sts.rcd.d.r->ts, sizeof(new->ts)))
		return -1;

	/* We know we have a duplicate
	 * Stop looking if the old record has been deleted */
	if (old_tgts == 0)
		return 0;

	/* look for the first real checksum in the new record */
	for (new_num_cks = DB_NUM_CKS(new), new_ck = new->cks;
	     new_num_cks != 0;
	     --new_num_cks, ++new_ck) {
		if (DB_CK_TYPE(new_ck) != DCC_CK_FLOD_PATH)
			break;
	}

	/* do not even count duplicate server-ID declarations */
	if (DB_CK_TYPE(new_ck) == DCC_CK_SRVR_ID)
		return 1;

	/* See if one record is a subset of the other */
	new_unique = 0;
	old_unique = 0;
	old_num_cks = DB_NUM_CKS(db_sts.rcd.d.r);
	old_ck = db_sts.rcd.d.r->cks;
	while (old_num_cks != 0 && new_num_cks != 0) {
		if (DB_CK_TYPE(old_ck) == DCC_CK_FLOD_PATH) {
			/* skip paths in the old record */
			++old_ck;
			--old_num_cks;
		} else if (DB_CK_TYPE(old_ck) == DB_CK_TYPE(new_ck)) {
			/* skip identical checksums */
			++old_ck;
			--old_num_cks;
			++new_ck;
			--new_num_cks;
		} else if (DB_CK_TYPE(old_ck) < DB_CK_TYPE(new_ck)) {
			/* skip unique checksum in the old record,
			 * using the ordering of checksums in records */
			++old_unique;
			++old_ck;
			--old_num_cks;
		} else {
			/* skip unique checksum in the new record */
			++new_unique;
			++new_ck;
			--new_num_cks;
		}
	}

	/* forget the new record if it has nothing unique */
	if (new_unique+new_num_cks == 0) {
		if (CK_FLOD_CNTERR(&ifp->ofp->lc.dup)
		    && TMSG_FB2(ifp->ofp))
			flod_cnterr(&ifp->ofp->lc.dup, "%sduplicate %s",
				    old_unique+old_num_cks == 0
				    ? "" : "subset ",
				    rpt_id("report", new, ifp));

		return 1;
	}

	/* Keep both records if each has unique checksums
	 * This inflates the count for common checksums, so hope it
	 * is rare. */
	if (old_unique+old_num_cks != 0) {
		if (CK_FLOD_CNTERR(&ifp->ofp->lc.dup)
		    && TMSG_FB2(ifp->ofp))
			flod_cnterr(&ifp->ofp->lc.dup, "partial duplicate %s",
				    rpt_id("report", new, ifp));

		return 0;
	}

	/* Delete the original report if it has nothing unique
	 *	This results in doubling the contribution to the total for
	 *	each checksum from this report in our database until the
	 *	next time dbclean is run.  At worst this could push a
	 *	DCC client over its bulk threshold */
	DB_TGTS_RCD_SET(db_sts.rcd.d.r, 0);
	SET_FLUSH_RCD_HDR(&db_sts.rcd, 1);

	if (CK_FLOD_CNTERR(&ifp->ofp->lc.dup)
	    && TMSG_FB2(ifp->ofp))
		flod_cnterr(&ifp->ofp->lc.dup, "superset duplicate %s",
			    rpt_id("report", new, ifp));
	return 0;
}



/* see if the new record is a duplicate of any existing records containing
 * the specified checksum
 *	use db_sts.rcd for the initial record and the chain */
static int				/* -1=fail,  0=not dup,  1=duplicate */
ck_dup_ck_chain(IFLOD_INFO *ifp, const DB_RCD *new, DCC_TGTS new_tgts_raw,
		DCC_CK_TYPES type, const DB_RCD_CK *found_ck)
{
	DB_PTR next_rcd_pos;
	int cnt, i;

	for (cnt = 0; ; ++cnt) {
		i = ck_dup_rcd(ifp, new, new_tgts_raw);
		if (i >= 0) {
			if (cnt >= 50 && (dccd_tracemask & DCC_TRACE_DB_BIT))
				dcc_trace_msg("long duplicate chain of %d"
					      " from %s at %s",
					      cnt, ifp->rem_hostname,
					      rpt_id("report",new,ifp));
			return i;
		}

		next_rcd_pos = DB_PTR_EX(found_ck->prev);
		if (next_rcd_pos == DB_PTR_NULL)
			return 0;
		if (next_rcd_pos >= db_sts.rcd.s.rptr) {    /* prevent loops */
			db_error_msg(__LINE__,__FILE__,
				     "bad %s link of "L_HPAT" at "L_HPAT,
				     DB_TYPE2STR(type),
				     db_sts.rcd.s.rptr, next_rcd_pos);
			return -1;
		}

		found_ck = db_map_rcd_ck(dcc_emsg, &db_sts.rcd,
					 next_rcd_pos, type);
		if (!found_ck) {
			iflod_close(ifp, 1, 1, 1, "%s", dcc_emsg);
			DB_ERROR_MSG(dcc_emsg);
			return -1;
		}
	}
}



/* complain about a received flooded report,
 *	and possibly close and so clear things */
static u_char PATTRIB(6,7)		/* 0=flood closed */
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 */
{
	DCC_FLOD_RESP buf;
	const char *sc;
	va_list args;
	int i, len;

	if (!lc && ifp->ofp)
		lc = &ifp->ofp->lc.iflod_bad;
	if (!lc) {
		sc = "";
	} else {
		i = ++lc->cur - (lc->lim + FLOD_LIM_COMPLAINTS);
		if (i > 0)
			return 1;
		sc = i < 0 ? "" : "; stop complaints";
	}

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

	if (serious) {
		dcc_error_msg("%s %s%s",
			      buf.note.str, rpt_id(str, new, ifp), sc);
		db_ptr2flod_pos(buf.note.op, DCC_FLOD_POS_COMPLAINT);
	} else {
		TMSG3_FLOD2(ifp->ofp, "%s %s%s",
			    buf.note.str, rpt_id(str, new, ifp), sc);
		db_ptr2flod_pos(buf.note.op, DCC_FLOD_POS_NOTE);
	}

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

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



static u_char
parse_srvr_id(const DB_RCD_CK *ck, const IFLOD_INFO *ifp,
	      const DB_RCD *new, DCC_SRVR_ID srvr_id)
{
	DCC_SRVR_ID tgt_id, type_id;
	char buf1[24];
	OPT_FLAGS opt_flags;
	const OFLOD_INFO *ofp1;
	ID_TBL *tp;

	/* notice server-ID type announcements */
	tgt_id = (ck->sum[1] << 8) + ck->sum[2];
	type_id = srvr_id;
	switch (type_id) {
	case DCC_ID_SRVR_REP_OK:
		type_id = DCC_ID_SRVR_SIMPLE;
		opt_flags = 0;
		break;
	case DCC_ID_SRVR_SIMPLE:
		opt_flags = FLOD_OPT_SIMPLE;
		break;
	case DCC_ID_SRVR_IGNORE:
		opt_flags = 0;
		break;
	case DCC_ID_SRVR_ROGUE:
		opt_flags = FLOD_OPT_ROGUE;
		break;
	default:
		return 0;
	}
	if (ck->sum[0] != DCC_CK_SRVR_ID) {
		return 0;
	}

	tp = find_srvr_type(tgt_id);
	/* Restart flooding if the announced type of a peer changes. */
	for (ofp1 = oflods.infos; ofp1 <= LAST(oflods.infos); ++ofp1) {
		/* Wait a bit for more announcements before restarting
		 * flooding.  When we restart flooding, we will
		 * check the database. */
		if (ofp1->rem_id == tgt_id) {
			if (opt_flags != (ofp1->o_opts.flags
					  & (FLOD_OPT_ROGUE
					     | FLOD_OPT_SIMPLE))) {
				if (TMSG_FB(ifp->ofp) || TMSG_FB(ofp1))
					dcc_trace_msg("server-ID %d for %s"
						      " changed to \"%s\""
						      " in %s",
						      tgt_id,
						      ofp1->rem_hostname,
						      id2str(buf1, sizeof(buf1),
							  type_id),
						      rpt_id("report",new,ifp));
				if (flod_mtime > 1)
					flod_mtime = 1;
			}
			break;
		}
	}

	/* accept changes to our records */
	tp->srvr_type = type_id;

	return 1;
}



/* consider an incoming flooded report */
static int				/* -1=failed, 0=not yet, else length */
iflod_rpt(IFLOD_INFO *ifp, OFLOD_INFO *ofp,
	  const DCC_FLOD_STREAM *stream, int max_len)
{
	DB_PTR pos;
	DCC_TGTS new_tgts, found_tgts;
	DB_RCD new;
	DCC_SRVR_ID old_srvr, psrvr;
	const DCC_CK *ck_lim, *ck;
	const DB_RCD_CK *new_ck_lim, *srvr_id_ck;
	DB_RCD_CK  *found_ck, *new_ck;
	DCC_CK_TYPES type, prev_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;
	ID_TBL *tp;
	int i;

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

	/* wait for the header of the report */
	if (max_len < DCC_FLOD_RPT_LEN(0)) {
		return 0;
	}

	if (stream->r.num_cks == 0 || stream->r.num_cks > DCC_QUERY_MAX) {
		iflod_close(ifp, 1, 1, 1,
			    "impossible %d checksums in report #%d",
			    stream->r.num_cks, ofp->cnts.total);
		return -1;
	}
	rpt_len = DCC_FLOD_RPT_LEN(stream->r.num_cks);
	if (rpt_len > max_len)
		return 0;		/* wait for more */

	if (db_failed_line)
		return rpt_len;		/* can do nothing if database broken */

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

	new.ts = stream->r.ts;
	memcpy(&new.srvr_id_auth, stream->r.srvr_id_auth,
	       sizeof(new.srvr_id_auth));
	old_srvr = ntohs(new.srvr_id_auth) & ~DCC_SRVR_ID_AUTH;
	new.srvr_id_auth = old_srvr;
	new.fgs_num_cks = 0;

	memcpy(&new_tgts, stream->r.tgts, sizeof(new_tgts));
	new_tgts = ntohl(new_tgts);
	if (new_tgts == DCC_TGTS_DEL) {
		if (!(ofp->i_opts.flags & FLOD_OPT_DEL_OK)) {
			if (!iflod_rpt_complain(ifp, &new, 1,
						&ofp->lc.not_deleted,
						"delete request", "refuse"))
				return -1;
			return rpt_len;
		}
	} else if (new_tgts == 0
		   || (new_tgts > DCC_TGTS_FLOD_RPT_MAX
		       && new_tgts != DCC_TGTS_TOO_MANY)) {
		iflod_close(ifp, 1, 1, 1, "bogus target count %s in %s",
			    dcc_tgts2str(tgts_buf, sizeof(tgts_buf),
					 new_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 */
		new_tgts = DCC_TGTS_TOO_MANY;
	}

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

	DB_TGTS_RCD_SET(&new, new_tgts);
	new.fgs_num_cks = 0;
	srvr_id_ck = 0;
	stale = 1;
	ck_lim = &stream->r.cks[stream->r.num_cks];
	new_ck = new.cks;
	num_path_blocks = 0;

	tp = 0;
	srvr_mapped = id_map(old_srvr, &ofp->i_opts);
	switch (srvr_mapped) {
	case ID_MAP_NO:
		tp = find_srvr_type(old_srvr);
		break;
	case ID_MAP_REJ:
		if (!iflod_rpt_complain(ifp, &new, 0, 0,
					"rejected server-ID in", "refuse"))
			return -1;
		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 = stream->r.cks;
	     ck < ck_lim;
	     prev_type = type, ++ck) {
		type = ck->type;
		if (!DCC_CK_OK_FLOD(grey_on, type)) {
			if (!iflod_rpt_complain(ifp, &new, 1, 0,
						"report",
						"unknown checksum type %s in",
						DB_TYPE2STR(type)))
				return -1;
			continue;
		}
		if (ck->len != sizeof(*ck)) {
			iflod_close(ifp, 1, 1, 1,
				    "unknown checksum length %d in %s",
				    ck->len, rpt_id("report", &new, 0));
			return -1;
		}
		if (type <= prev_type && prev_type != DCC_CK_FLOD_PATH) {
			if (!iflod_rpt_complain(ifp, &new, 1, 0,
						"report",
						"out of order %s checksum in",
						DB_TYPE2STR(type)))
				return -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(ofp, "%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 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_parms.nokeep_cks, type)
			    && (srvr_mapped == ID_MAP_SELF
				|| DB_GLOBAL_NOKEEP(grey_on, type)))
				continue;

			/* server-ID declarations are never stale */
			if (type == DCC_CK_SRVR_ID) {
				stale = 0;
				srvr_id_ck = new_ck;
			}

			/* Notice if this checksum makes the report timely
			 * We cannot detect duplicates of reports that
			 * have expired, so consider stale anything older
			 * than our expiration.
			 * Ignore reports of checksums from crazy servers */
			if (stale
			    && dcc_ts_newer_ts(&new.ts,
					       new_tgts >= db_tholds[type]
					       ? &db_parms.ex_spam[type]
					       : &db_parms.ex_all[type])
			    && (tp == 0
				|| (tp->srvr_type != DCC_ID_SRVR_IGNORE
				    && tp->srvr_type != DCC_ID_SRVR_ROGUE)))
				stale = 0;
		}

		++new_ck;
		++new.fgs_num_cks;
	}
	if (stale) {
		if (CK_FLOD_CNTERR(&ofp->lc.stale)
		    && TMSG_FB2(ofp))
			flod_cnterr(&ofp->lc.stale, "stale %s",
				    rpt_id("report", &new, ifp));
		return rpt_len;
	}

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

	/* only now might we look at the database */
	if (db_lock() < 0) {
		iflod_close(ifp, 1, 1, 1, "iflod lock failure");
		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 */
	ok2 = 0;
	new_ck_lim = &new.cks[DB_NUM_CKS(&new)];
	for (new_ck = new.cks; new_ck < new_ck_lim; ++new_ck) {
		type = DB_CK_TYPE(new_ck);
		if (DB_TEST_NOKEEP(db_parms.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, 1, 1, "%s", dcc_emsg);
			DB_ERROR_MSG(dcc_emsg);
			return -1;

		case DB_FOUND_IT:
			/* At least this checksum is already in the database */
			i = ck_dup_ck_chain(ifp, &new, new_tgts,
					    type, found_ck);
			if (i < 0)
				return -1;  /* broken database */
			if (i > 0)
				return rpt_len;	/* duplicate */

			/* Maybe not a duplicate.
			 * Notice reports of checksums on the local server's
			 * whitelist.
			 * An ordinary checksum is whitelisted by DCC_TGTS_OK
			 * or two reports with DCC_TGTS_OK2.
			 * Greylisting uses DCC_TGTS_GREY_WHITE=DCC_TGTS_OK2
			 * and so one report of DCC_TGTS_GREY_WHITE is enough */
			found_tgts = DB_TGTS_CK(found_ck);
			if (found_tgts == DCC_TGTS_OK
			    || (found_tgts == DCC_TGTS_GREY_WHITE
				&& (++ok2 >= 2 || grey_on))) {
				if (!iflod_rpt_complain(ifp, &new, 0,
							&ofp->lc.wlist,
							"report","whitelisted"))
					return -1;
				return rpt_len;
			}
			break;

		case DB_FOUND_EMPTY:
		case DB_FOUND_CHAIN:
		case DB_FOUND_INTRUDER:
			/* We will fail to find this checksum in our database
			 * if the new report is not a duplicate
			 * or if it is a duplicate superset report */
			break;
		}
	}

	/* If the new report is a delete request,
	 * then we need to run dbclean to fix all of the
	 * totals affected by the deleted reports. */
	if (new_tgts == DCC_TGTS_DEL) {
		if (!(ofp->i_opts.flags & FLOD_OPT_NO_LOG_DEL))
			dcc_trace_msg("accept %s",
				      rpt_id("delete request", &new, ifp));
		if (!DCC_CK_IS_REP_OP(grey_on, type) && !grey_on)
			need_del_dbclean = "flood checksum deletion";
	}

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

		/* notice claims by other servers to our ID */
		if (old_srvr == my_srvr_id) {
			if (memcmp(host_id_sum, srvr_id_ck->sum,
				   sizeof(host_id_sum)))
				dcc_error_msg("host %s used our server-ID"
					      " %d at %s",
					      dcc_ck2str_err(DCC_CK_SRVR_ID,
							srvr_id_ck->sum, 0),
					      my_srvr_id,
					      ts2str_err(&new.ts));
			return rpt_len;
		}

		if (old_srvr < DCC_SRVR_ID_MIN
		    && !parse_srvr_id(srvr_id_ck, ifp, &new, old_srvr))
			return rpt_len;
	}

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

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



static void
bad_vers(IFLOD_INFO *ifp,
	 u_char fail)			/* 1=complain */
{
	iflod_close(ifp, fail, fail, 1,
		    DCC_FLOD_BAD_VER_MSG" need \""
		    DCC_FLOD_VERSION_CUR_STR
		    "\" not \"%.*s\"",
		    LITZ(DCC_FLOD_VERSION_STR_BASE)+10,
		    ifp->ibuf.s.v.body.str);
}



/* authenticate and otherwise check a new incoming flood */
static u_char				/* 0=closed or switched to output */
check_iflod_vers(IFLOD_INFO *ifp)
{
	DCC_FNM_LNO_BUF fnm_buf;
	const DCC_FLOD_VERSION_HDR *vp;
	OFLOD_INFO *ofp;
	IFLOD_INFO *ifp1;
	const ID_TBL *tp;
	DCC_SRVR_ID rem_id;
	int iversion;
	int i;

	vp = &ifp->ibuf.s.v;
	if (!strcmp(vp->body.str, DCC_FLOD_VERSION_CUR_STR)) {
		iversion = DCC_FLOD_VERSION_CUR;
		ifp->flags |= IFLOD_FG_VERS_CK;

#ifdef DCC_FLOD_VERSION7
	} else if (!strcmp(vp->body.str, DCC_FLOD_VERSION7_STR)) {
		iversion = DCC_FLOD_VERSION7;
		ifp->flags |= IFLOD_FG_VERS_CK;

#endif /* DCC_FLOD_VERSION7 */

	} else if (!strncmp(vp->body.str, DCC_FLOD_VERSION_STR_BASE,
			    LITZ(DCC_FLOD_VERSION_STR_BASE))) {
		/* it seems to be a DCC server,
		 * so complain after identifying the peer */
		iversion = 1;

	} else {
		/* junk, so complain and give up */
		bad_vers(ifp, 1);
		return 0;
	}

	/* require a sane and familiar server-ID from the prospective peer */
	memcpy(&rem_id, vp->body.sender_srvr_id, sizeof(rem_id));
	rem_id = ntohs(rem_id);
	if (rem_id < DCC_SRVR_ID_MIN
	    || rem_id > DCC_SRVR_ID_MAX) {
		iflod_close(ifp, 1, 1, 1, DCC_FLOD_BAD_ID_MSG" %d",
			    rem_id);
		return 0;
	}
	for (ofp = oflods.infos; ; ++ofp) {
		if (ofp > LAST(oflods.infos)) {
			iflod_close(ifp, 1, 1, 1, DCC_FLOD_BAD_ID_MSG" %d",
				    rem_id);
			return 0;
		}
		if (ofp->rem_id == rem_id) {
			ifp->ofp = ofp;
			STRLCPY(ifp->rem_hostname, ofp->rem_hostname,
				sizeof(ifp->rem_hostname));
			break;
		}
	}

	/* ofp and ofp->mp are not null, because we now know which peer
	 * it claims to be
	 *
	 * check that it knows the password */
	i = ck_sign(&tp, 0, ofp->in_passwd_id, vp, sizeof(*vp));
	if (!i) {
		if (!tp)
			iflod_close(ifp, 1, 1, 1, DCC_FLOD_PASSWD_ID_MSG" %d%s",
				    ofp->in_passwd_id,
				    fnm_lno(&fnm_buf, flod_path, ofp->lno));
		else
			iflod_close(ifp, 1, 1, 1, DCC_FLOD_BAD_AUTH_MSG" %d",
				    ofp->in_passwd_id);
		return 0;
	}
	if (i == 1)
		ofp->mp->flags &= ~FLODMAP_FG_USE_2PASSWD;
	else
		ofp->mp->flags |= FLODMAP_FG_USE_2PASSWD;

	/* no more assumed NAT games because it has contacted us */
	ofp->mp->flags &= ~FLODMAP_FG_NAT_AUTO;

	/* Note the version of the protocol it is using so that we can use that
	 * version when connecting to it. */
	ofp->mp->iversion = iversion;
	/* if we do not like its version, reject the connection and hope
	 * that it will retry  with a version we like */
	if (!(ifp->flags & IFLOD_FG_VERS_CK)) {
		bad_vers(ifp, iversion != 0);
		return 0;
	}
	if (iversion != DCC_FLOD_VERSION_CUR)
		TMSG2_FLOD(ofp, "version %d from %s",
			   iversion, ifp_rem_str(ifp));

	/* convert to a passive output flood as requested by the peer
	 *	This works even if the peer is configured to use SOCKS or NAT
	 *	but we are not using PASSIVE */
	if (vp->body.turn) {
		if (OFLOD_OPT_OFF_ROGUE(ofp)) {
			iflod_close(ifp, 1, 0, 1,
				    "passive output flooding off from %s%s",
				    ifp_rem_str(ifp),
				    fnm_lno(&fnm_buf, flod_path, ofp->lno));
			return 0;
		}

		/* We have a duplicate passive outgoing flood.
		 * See whether the old stream has broken. */
		if (ofp->soc >= 0)
			oflod_read(ofp);
		/* If we still have a duplicate and we sent a shutdown request,
		 * assume the response got lost */
		if (ofp->soc >= 0
		    && (ofp->flags & OFLOD_FG_SHUTDOWN_REQ)) {
			rpt_err(ofp, 1, 0,
				" assume response to shutdown lost from %s",
				ofp_rem_str(ofp));
			oflod_close(ofp, 0);
		}
		if (ofp->soc >= 0) {
			/* We still have duplicates.
			 * Reject the new one if the IP addresses differ */
			if (!DCC_SU_EQ(&ofp->rem_su, &ifp->rem_su)) {
				iflod_close(ifp, 1, 0, 1,
					    "reject duplicate passive output"
					    " flood from %s",
					    ifp_rem_str(ifp));
				return 0;
			}
			rpt_err(ofp, 1, 0,
				"accept duplicate passive output flood from %s",
				ifp_rem_str(ifp));
			oflod_close(ofp, 0);
		}

		ofp->soc = ifp->soc;
		ofp->rem_su = ifp->rem_su;
		++oflods.open;
		TMSG1_FLOD(ofp,
			   "convert incoming flood to passive outgoing to %s",
			   ofp_rem_str(ofp));
		iflod_clear(ifp, 0);

		ofp->mp->flags |= FLODMAP_FG_OUT_SRVR;

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

	if (IFLOD_OPT_OFF_ROGUE(ofp)) {
		iflod_close(ifp, 1, 0, 1, "flood from %s turned off%s",
			    ifp_rem_str(ifp),
			    fnm_lno(&fnm_buf, flod_path, ofp->lno));
		return 0;
	}

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

		/* We have a duplicate.  Either two servers are using the
		 * same server-ID or the peer has restarted flooding without
		 * our seeing  a clean shutdown.
		 * If socket is in CLOSE_WAIT, then sending something will fail
		 * immediately.  If the peer was rebooted, then sending will
		 * not fail for at least a round trip time and possibly longer
		 * if the peer has moved.
		 *
		 * Before trying to send anything, check for a FIN waiting
		 * on the other socket */
		for (i = 65536/FLOD_BUF_SIZE; i >= 0; --i) {
			if (iflod_read(ifp1))
				break;
		}
		/* forget it if reading closed the other stream */
		if (ifp1->ofp != ofp)
			break;

		/* assume the it is ok if we have sent an end request */
		if (ifp1->flags & IFLOD_FG_END_REQ) {
			iflod_close(ifp1, 0, 0, 1,
				    "missing end response;"
				    " have replacement for flood from %s",
				    ifp_rem_str(ifp1));
			break;
		}

		/* assume we missed the shutdown
		 * if the IP addresses are the same */
		if (DCC_SU_EQ(&ifp->rem_su, &ifp1->rem_su)) {
			iflod_close(ifp1, 0, 0, 1,
				    "assumed dead link;"
				    " have replacement for flood from %s",
				    ifp_rem_str(ifp1));
			break;
		}

		/* assume we do not have a duplicate server-ID and switch to
		 * the new connection if sending a position or note
		 * fails immediately, */
		if (0 >= iflod_send_pos(ifp1, 1)) {
			iflod_close(ifp1, 0, 0, 1,
				    "have replacement for flood from %s",
				    ifp_rem_str(ifp1));
			break;
		}

		/* Otherwise, kill the new flood.  If it was legitimate,
		 * sending the note will eventually kill the old stream
		 * and the peer will get through with it tries later */
		iflod_close(ifp, 1, 1, 1, "duplicate flood from %s",
			    ifp_rem_str(ifp));
		return 0;
	}

	ofp->ifp = ifp;
	if (ifp->flags & IFLOD_FG_CLIENT)
		ofp->mp->flags &= ~FLODMAP_FG_IN_SRVR;
	else
		ofp->mp->flags |= FLODMAP_FG_IN_SRVR;
	save_flod_cnts(ofp);
	ifp->iflod_alive = db_time.tv_sec;

	/* Try to restart the corresponding output flood because a new
	 * incoming flood might indicate that peer has awakened.
	 * Kludge the backoff so that it does not increase. */
	if (ofp->soc < 0
	    && !(ofp->o_opts.flags & FLOD_OPT_PASSIVE)) {
		ofp->mp->otimers.retry_secs /= 2;
		ofp->mp->otimers.retry = 0;
		oflod_open(ofp);
	}

	/* Send a rewind or fast forward request immediately if needed.
	 * If not, send a keepalive message so that peer knows we have
	 * accepted the connection and it can stop worrying about an
	 * immediate rejection. */
	if (0 > iflod_send_pos(ifp, 1))
		return 0;

	return 1;
}



/* A new SOCKS incoming stream that we originated has been closed by the
 * peer without authenticating itself.  It could have responded to our
 * authentication with an error message. */
static void
parse_socks_error(IFLOD_INFO *ifp)
{
	const DCC_FLOD_STREAM *stream;
	int i, msg_len;
	int fail;

	stream = (DCC_FLOD_STREAM *)&ifp->ibuf.b[0];
	msg_len = ifp->ibuf_len - FLOD_END_OVHD;

	/* it must look like an end request with an entirely ASCII message */
	if (flod_pos2db_ptr(stream->r.pos) != DCC_FLOD_POS_END
	    || msg_len < 1 || msg_len > ISZ(stream->e.msg)) {
		iflod_close(ifp, 1, 1, 1, "SOCKS rejected with \"%.*s\"",
			    msg_len, stream->e.msg);
		return;
	}

	for (i = 0; i < msg_len; ++i) {
		if (stream->e.msg[i] < ' ' || stream->e.msg[i] > '~') {
			iflod_close(ifp, 1, 1, 1,
				    "SOCKS rejected with \"%.*s\"",
				    msg_len, stream->e.msg);
			return;
		}
	}

	fail = oflod_parse_eof(ifp->ofp, 1, &stream->e, msg_len);
	if (fail <= 0) {
		iflod_socks_backoff(ifp->ofp);
	} else {
		/* try again immediately
		 * with another protocol version or the 2nd password */
		ifp->ofp->mp->itimers.retry = 0;
	}
	iflod_close(ifp, fail<=0, fail<=0, 0,
		    "SOCKS rejected by %s with \"%.*s\"",
		    ifp_rem_str(ifp), msg_len, stream->e.msg);
}



/* see what a distant flooder is telling us
 *	can close the flood and so clear things */
u_char					/* 1=kernel buffers empty */
iflod_read(IFLOD_INFO *ifp)
{
	OFLOD_INFO *ofp;
	int off, req_len, recv_len;
	const DCC_FLOD_STREAM *stream;
	int len, i;

	/* if this is an incoming SOCKS or NAT stream that we originated,
	 * and if Rconnect() said "not yet" when we first tried to connect,
	 * then we must be here because select() says it is time to
	 * try Rconnect() again to finish the connection */
	if (!(ifp->flags & IFLOD_FG_CONNECTED)
	    && iflod_socks_connect(ifp) <= 0)
		return 1;

	/* read only once before returning
	 * to ensure we pay attention to other work */

	req_len = sizeof(ifp->ibuf) - ifp->ibuf_len;
	ofp = ifp->ofp;
	if (ofp && (ofp->o_opts.flags & FLOD_OPT_SOCKS))
		recv_len = Rrecv(ifp->soc, &ifp->ibuf.b[ifp->ibuf_len],
				 req_len, 0);
	else
		recv_len = recv(ifp->soc, &ifp->ibuf.b[ifp->ibuf_len],
				req_len, 0);
	if (recv_len < 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, 0, 0, "incoming flood recv(%s): %s",
				    ifp_rem_str(ifp), ERROR_STR());
		}
		return 1;
	}
	ifp->ibuf_len += recv_len;

	off = 0;

	/* deal with a new connection */
	if (!(ifp->flags & IFLOD_FG_VERS_CK)) {
		if (ifp->ibuf_len >= ISZ(DCC_FLOD_VERSION_HDR)) {
			if (!check_iflod_vers(ifp))
				return 1;   /* stream closed or converted */
			ofp = ifp->ofp;
			off = ISZ(DCC_FLOD_VERSION_HDR);

		} else if (recv_len != 0) {
			return 1;	/* wait for rest of authentication */

		} else if (ofp && ofp->mp
			   && (ofp->mp->flags & FLODMAP_FG_ACT) != 0) {
			parse_socks_error(ifp);
			return 1;

		} else {
			iflod_close(ifp, 1, 1, 0, "garbage connection from %s",
				    ifp_rem_str(ifp));
			return 1;
		}
	}
	/* ofp != 0 because check_iflod_vers() has found the peer */

	/* deal with the data */
	dcc_timeval2ts(&future, &db_time, MAX_FLOD_CLOCK_SKEW);
	while ((len = ifp->ibuf_len - off) > 0) {
		stream = (DCC_FLOD_STREAM *)&ifp->ibuf.b[off];
		if (len < ISZ(stream->r.pos))
			break;		/* need at least the position */
		i = iflod_rpt(ifp, ofp, stream, len);
		if (i < 0)
			return 1;	/* stream closed */
		if (i == 0)
			break;		/* wait for rest of report */
		off += i;
		++ofp->cnts.total;
	}

	/* save unprocessed bytes for next time */
	if (off != 0) {
		ifp->ibuf_len -= off;
		if (ifp->ibuf_len < 0)
			dcc_logbad(EX_SOFTWARE, "ifp->ibuf_len=%d",
				   ifp->ibuf_len);
		if (ifp->ibuf_len > 0)
			memmove(&ifp->ibuf.b[0], &ifp->ibuf.b[off],
				ifp->ibuf_len);
	}

	if (recv_len == 0) {
		/* We are at EOF and have processed all input that we can. */
		if (ifp->ibuf_len != 0) {
			/* Something is wrong if any input remains,  */
			iflod_close(ifp, 1, 0, 1, "report %d truncated",
				    ofp ? ofp->cnts.total : 0);
		} else if (flods_st != FLODS_ST_ON
			   || (ofp && IFLOD_OPT_OFF_ROGUE(ofp))) {
			iflod_close(ifp, 0, 0, 1, DCC_FLOD_OK_STR"%s off",
				    our_hostname);
		} else {
			iflod_close(ifp, 0, 0, 1, DCC_FLOD_OK_STR"%s off",
				    ifp_rem_str(ifp));
		}
		return 1;
	}

	/* things are going ok, so reset the SOCKS restart backoff
	 * and the no-connection complaint */
	ofp->mp->itimers.retry_secs = FLOD_SOCKS_SOCKS_IRETRY;
	ofp->mp->itimers.msg_secs = FLOD_IN_COMPLAIN1;
	ofp->mp->itimers.msg = db_time.tv_sec + FLOD_IN_COMPLAIN1;

	return (req_len > recv_len);
}



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

	for (sp = srvr_socs; sp; sp = sp->fwd) {
		if (sp->flags & SRVR_SOC_ADDR) {
			/* need to open a TCP listen socket for incoming floods
			 * for each explicitly configured IP address */
			sup = &sp->su;
		} else if (sp->flags & SRVR_SOC_LISTEN) {
			/* need to 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 {
			/* otherwise close unneeded socket */
			iflod_listen_close(sp);
			continue;
		}

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

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

		TMSG1(FLOD, "start flood listening on %s",
		      dcc_su2str_err(sup));

		sp->listen = socket(sup->sa.sa_family, SOCK_STREAM, 0);
		if (sp->listen < 0) {
			dcc_error_msg("socket(flood listen %s): %s",
				      dcc_su2str_err(sup), ERROR_STR());
			continue;
		}

		if (-1 == fcntl(sp->listen, F_SETFL,
				fcntl(sp->listen, F_GETFL, 0) | O_NONBLOCK)) {
			dcc_error_msg("fcntl(flood listen %s, O_NONBLOCK): %s",
				      dcc_su2str_err(sup), ERROR_STR());
		}
		on = 1;
		if (0 > setsockopt(sp->listen, SOL_SOCKET, SO_REUSEADDR,
				   &on, sizeof(on)))
			dcc_error_msg("setsockopt(flood listen %s,"
				      " SO_REUSADDR): %s",
				      dcc_su2str_err(sup), ERROR_STR());
		if (0 > fcntl(sp->listen, F_SETFD, FD_CLOEXEC))
			dcc_error_msg("fcntl(flood listen %s FD_CLOEXEC): %s",
				      dcc_su2str_err(sup), ERROR_STR());

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

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



static const char *
oflod_state_str(char outstr[DCC_SU2STR_SIZE], const OFLOD_INFO *ofp,
		u_char anon)
{
	if (ofp->soc >= 0) {
		if (ofp->flags & (OFLOD_FG_SHUTDOWN_REQ
				  | OFLOD_FG_SHUTDOWN))
			return "  (shutting)";
		if (!(ofp->flags & OFLOD_FG_CONNECTED))
			return "  (connecting)";
		if (anon)
			return "";
		return dcc_su2str2(outstr, DCC_SU2STR_SIZE, &ofp->rem_su);
	}

	if (OFLOD_OPT_OFF_ROGUE(ofp))
		return "  (output off)";
	if (flods_st != FLODS_ST_ON)
		return "  (flood off)";
	return "  (no output)";
}



static const char *
iflod_state_str(char instr[DCC_SU2STR_SIZE],
		const OFLOD_INFO *ofp, const IFLOD_INFO *ifp,
		u_char anon, u_char have_in, u_char distinct_in)
{

	if (have_in) {
		if (!(ifp->flags & IFLOD_FG_VERS_CK))
			return "  (connecting)";
		if (anon)
			return "";
		if (distinct_in)
			return dcc_su2str2(instr, DCC_SU2STR_SIZE, &ifp->rem_su);
		return "\t";
	}
	if (IFLOD_OPT_OFF_ROGUE(ofp))
	    return "  (input off)";
	if (flods_st != FLODS_ST_ON)
		return "  (flood off)";
	return "  (no input)";
}



/* 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;
	char instr[DCC_SU2STR_SIZE], outstr[DCC_SU2STR_SIZE];
	char hostname[60], fg_buf[60];
	DCC_SOCKU in, out;
	u_char have_in, distinct_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->rem_hostname[0] == '\0')
			break;
		have_in = 0;
		distinct_in = 0;
		for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) {
			if (ifp->ofp == ofp) {
				if (ifp->soc >= 0) {
					have_in = 1;
					dcc_ipv6sutoipv4(&in, &ifp->rem_su);
					dcc_ipv6sutoipv4(&out, &ofp->rem_su);
					if (ofp->soc < 0
					    || !DCC_SU_EQ(&in, &out))
					    distinct_in = 1;
				}
				break;
			}
		}
		if (anon) {
			i = snprintf(p, buf_len, "%5d %15s\t%s\n",
				     ofp->rem_id,
				     oflod_state_str(outstr, ofp, 1),
				     iflod_state_str(instr, ofp, ifp,
						     1, have_in, 0));
		} else {
			dcc_host_portname(hostname, sizeof(hostname),
					  ofp->rem_hostname,
					  ofp->rem_port == def_port
					  ? 0 : ofp->rem_portname),
			i = strlen(hostname);
			flodmap_fg(fg_buf, sizeof(fg_buf),
				   i < 16 ? "\t\t" : i > 24 ? "  " : "\t",
				   ofp->mp);
			i = snprintf(p, buf_len, "%5d %15s\t%s\t%s%s\n",
				     ofp->rem_id,
				     oflod_state_str(outstr, ofp, 0),
				     iflod_state_str(instr, ofp, ifp,
						     0, have_in, distinct_in),
				     hostname,
				     fg_buf);
		}
		FLODS_LIST_ALLOC(i);
	}

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

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



static PATTRIB(3,4) u_char		/* 0=no room */
flod_stats_str(char **buf, int *buf_len,
		const char *pat, ...)
{
	int i;
	va_list args;

	if (*buf_len <= 0)
		return 0;

	va_start(args, pat);
	i = vsnprintf(*buf, *buf_len, pat, args);
	va_end(args);

	if ((*buf_len -= i) <= 0) {
		*buf_len = 0;
		return 0;
	}
	*buf += i;
	return 1;
}




static u_char				/* 0=no room */
flod_stats_time(char **buf, int *buf_len,
		const char *str, const char *timepat, time_t when)
{
	char timebuf[40];

	return flod_stats_str(buf, buf_len, "%s %s", str,
			      dcc_time2str(timebuf, sizeof(timebuf), timepat,
					   when));
}



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

	if (*buf_len <= 0)
		return;

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



static void
flod_stats_conn_cur(char **buf, int *buf_len, const OFLOD_INFO *ofp, u_char in)
{
	u_char connected;
	time_t conn_changed;
	time_t flod_alive;
	const FLOD_MMAP *mp;
	const LAST_ERROR *ep;
	DCC_FNM_LNO_BUF fnm_buf;
	time_t deadline;
	u_char passive;
	const char *msg;

	if (*buf_len <= 0)
		return;

	mp = ofp->mp;

	if (in) {
		connected = ofp->ifp != 0;
		conn_changed = ofp->mp->cnts.in_conn_changed;
		flod_alive = ofp->ifp ? ofp->ifp->iflod_alive : 0;
	} else {
		connected = (ofp->flags & OFLOD_FG_CONNECTED) != 0;
		conn_changed = ofp->mp->cnts.out_conn_changed;
		flod_alive = ofp->oflod_alive;
	}

	if (connected) {
		if (conn_changed >= mp->cnts.cnts_cleared
		    && !flod_stats_time(buf, buf_len, "     connected since",
					"%b %d %X", conn_changed))
			return;
		flod_stats_time(buf, buf_len, "     last active", "%X",
				flod_alive);
		return;
	}

	if ((in && (mp->flags & FLODMAP_FG_IN_OFF))
	    || (!in && (mp->flags & FLODMAP_FG_OUT_OFF))) {
		flod_stats_str(buf, buf_len, "     off%s",
			       fnm_lno(&fnm_buf, flod_path, ofp->lno));
		return;
	}

	if (!flod_stats_str(buf, buf_len, "     not connected"))
		return;
	if (conn_changed >= mp->cnts.cnts_cleared) {
		if (!flod_stats_time(buf, buf_len, " since",
				     "%b %d %X", conn_changed))
			return;
	}
	ep = in ? &mp->iflod_err : &mp->oflod_err;
	msg = ep->msg[0] != '\0' ? ep->msg : ep->trace_msg;
	if (msg[0] != '\0') {
		if (!flod_stats_str(buf, buf_len, "\n\t%s", msg))
			return;
	}

	if (!FLODS_OK_ON()) {
		flod_stats_str(buf, buf_len, "\n     flooding off");
		return;
	}

	if (in) {
		if ((ofp->mp->flags & FLODMAP_FG_ACT) != 0) {
			passive = 0;
			deadline = ofp->mp->itimers.retry;
			if (DB_IS_TIME(deadline, ofp->mp->itimers.retry_secs))
				deadline = 0;

		} else {
			passive = 1;
			deadline = ofp->mp->itimers.msg;
			if (DB_IS_TIME(deadline, ofp->mp->itimers.msg_secs))
				deadline = 0;
		}
	} else {
		if (ofp->mp->flags & FLODMAP_FG_PASSIVE) {
			passive = 1;
			deadline = ofp->mp->otimers.msg;
			if (DB_IS_TIME(deadline, ofp->mp->otimers.msg_secs))
				deadline = 0;
		} else {
			passive = 0;
			deadline = ofp->mp->otimers.retry;
			if (DB_IS_TIME(deadline, ofp->mp->otimers.retry_secs))
				deadline = 0;
		}
	}
	if (deadline == 0) {
		flod_stats_str(buf, buf_len,
			       passive
			       ? "\n     complain soon"
			       : "\n     try again soon");
	} else {
		flod_stats_time(buf, buf_len,
				passive
				? "\n     complain after"
				: "\n     try again after",
				"%b %d %X", deadline);
	}
}



/* list the counts for a flood */
int					/* -1 or buffer length */
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], fg_buf[60];
	DCC_SRVR_ID min_srvr, max_srvr;
	u_char loaded;
	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;

	if (flod_mmaps) {
		loaded = 0;
	} else if (!load_flod(0)) {
		return -1;
	} else {
		loaded = 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_hostname[0] != '\0'
		    && ofp1->rem_id >= min_srvr
		    && ofp1->rem_id <= max_srvr) {
			/* 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 (loaded)
			oflods_clear();
		return p-buf;
	}
	mp = ofp->mp;

	save_flod_cnts(ofp);
	i = snprintf(p, len,
		     DCC_AOP_FLOD_STATS_ID" %s%s  %s\n  status start %s",
		     ofp->rem_id, mp->rem_hostname,
		     flodmap_fg(fg_buf, sizeof(fg_buf), "  ", mp),
		     dcc_time2str(now_buf, sizeof(now_buf), "%b %d %X %Z",
				  db_time.tv_sec),
		     dcc_time2str(time_buf, sizeof(time_buf), "%b %d %X %Z",
				  mp->cnts.cnts_cleared));
	FLOD_STATS_ALLOC(i);

	flod_stats_conn_total(&p, &len, "output", mp->cnts.out_total_conn);
	i = snprintf(p, len, "     "L_DPAT" reports sent\n",
		     mp->cnts.out_reports+ofp->cnts.out_reports);
	FLOD_STATS_ALLOC(i);
	flod_stats_conn_cur(&p, &len, ofp, 0);
	i = snprintf(p, len, "\n     position "L_HPAT, mp->confirm_pos);
	FLOD_STATS_ALLOC(i);

	flod_stats_conn_total(&p, &len, "input", mp->cnts.in_total_conn);
	i = snprintf(p, len,
		     "     "L_DPAT" reports received  "L_DPAT" accepted"
		     "  "L_DPAT" duplicate  "L_DPAT" stale\n"
		     "     "L_DPAT" bad whitelist  "L_DPAT" not deleted\n",
		     mp->cnts.total+ofp->cnts.total,
		     mp->cnts.accepted+ofp->cnts.accepted,
		     mp->cnts.dup+ofp->lc.dup.cur,
		     mp->cnts.stale+ofp->lc.stale.cur,
		     mp->cnts.wlist+ofp->lc.wlist.cur,
		     mp->cnts.not_deleted+ofp->lc.not_deleted.cur);
	FLOD_STATS_ALLOC(i);
	flod_stats_conn_cur(&p, &len, ofp, 1);

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

	if (clear) {
		flod_try_again(ofp);
		save_flod_cnts(ofp);
		ofp->limit_reset = 0;
		memset(&mp->cnts, 0, sizeof(mp->cnts));
		mp->cnts.cnts_cleared = db_time.tv_sec;
	}

	if (loaded)
		oflods_clear();
	return p-buf;
#undef FLOD_STATS_TOO_SHORT
#undef FLOD_STATS_ALLOC
}