Mercurial > notdcc
diff dccd/iflod.c @ 0:c7f6b056b673
First import of vendor version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 13:49:58 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dccd/iflod.c Tue Mar 10 13:49:58 2009 +0100 @@ -0,0 +1,2627 @@ +/* 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 +}