/* Distributed Checksum Clearinghouse * * deal with incoming floods of checksums * * --C-LICENSE-- * $Revision: 1.307 $ */ #include "dccd_defs.h" #include #ifdef HAVE__RES #include #endif 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 */ u_char flod_msg_gen; /* unsuppress tracing */ static u_char iflod_write(IFLOD_INFO *, void *, int, const char *, u_char); static DCC_TS future; /* timestamp sanity */ #ifndef HAVE_GCC_INLINE void db_ptr2flod_pos(DCC_FLOD_POS bp, DB_PTR pos) _db_ptr2flod_pos(bp, pos) DB_PTR flod_pos2db_ptr(const DCC_FLOD_POS pos) { return _flod_pos2db_ptr(pos); } #endif ID_MAP_RESULT id_map(DCC_SRVR_ID srvr, const SRVR_ID_MAPS *maps) { ID_MAP_RESULT result; int i; /* pass special IDs unchanged */ if (!DCC_ID_SRVR_NORMAL(srvr)) return ID_MAP_NO; /* apply the first server-ID map that matches, if any */ for (i = 0; i < maps->len; ++i) { if (maps->entry[i].lo <= srvr && maps->entry[i].hi >= srvr) { result = maps->entry[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_PORT(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_PORT(su) != DCC_GREY2PORT(grey_on)) { i = strlen(s); snprintf(s+i, sizeof(bufs[0].str)-i, ",%d", ntohs(DCC_SU_PORT(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); } const char * ofp_rem_str(const OFLOD_INFO *ofp) { if (!ofp) return "(null ofp)"; return rem_str(ofp->rem_hostname, &ofp->rem); } 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), ifp ? "from " : "", ifp ? ifp_rem_str(ifp) : ""); return s; } /* print an error message, * followed by a note saying this is the last if it is. * must always be preceded by CK_FLOD_CNTERR() */ void DCC_PF(2,3) flod_cnterr(const FLOD_LIMCNT *lc, const char *p, ...) { char buf[DCC_FLOD_MAX_RESP]; va_list args; va_start(args, p); if (db_debug >= 2 || lc->cur < lc->lim + FLOD_LIM_COMPLAINTS) { dcc_verror_msg(p, args); } else { vsnprintf(buf, sizeof(buf), p, args); dcc_error_msg("%s; stop complaints", buf); } va_end(args); } static void date_ferr_msg(FERR_MSG out, int len, const char *in) { memcpy(out, in, len); if (len >= ISZ(FERR_MSG)-LITZ(" at hh:mm:ss")-1) { out[len] = '\0'; } else { dcc_time2str(&out[len], ISZ(FERR_MSG)-len, " %b %d %X", db_time.tv_sec); } } /* remove extra quotes and strings from a message to or from a peer */ static void trim_ferr_msg(FERR_MSG out, const FERR_MSG 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_ferr_msg(out, len, q1); return; } } date_ferr_msg(out, strlen(in), in); } /* report a flooding error */ void DCC_PF(5,6) ferr(OFLOD_INFO *ofp, u_char in, /* 0=output 1=input */ FERR_TYPE etype, u_char trace, /* 0=error, 1=trace, 2=no dup trace */ const char *p, ...) { LAST_FERR *ep; FERR_MSG tmp, trimmed; va_list args; va_start(args, p); vsnprintf(tmp, sizeof(tmp), p, args); va_end(args); if (ofp) { ep = in ? &ofp->mp->iflod_err : &ofp->mp->oflod_err; } else { ep = 0; } if (!trace) { dcc_error_msg("%s", tmp); if (ep) { trim_ferr_msg(ep->error_msg, tmp); ep->error_type = etype; ep->error_gen = flod_msg_gen-1; ep->trace_type = FERR_NONE; } return; } if (!ep) { TMSG_FLOD1(ofp, tmp); return; } trim_ferr_msg(trimmed, tmp); if (TMSG_FB1(ofp) && (trace < 2 || ep->error_type != etype || strcmp(trimmed, ep->trace_msg) || ep->trace_gen != flod_msg_gen)) { /* suppress some duplicate flooding messages */ dcc_trace_msg("%s", tmp); ep->trace_gen = flod_msg_gen; } else { ep->trace_gen = flod_msg_gen-1; } STRLCPY(ep->trace_msg, trimmed, sizeof(ep->trace_msg)); ep->trace_type = etype; } /* forget error message when the problem is fixed */ void ferr_clear(OFLOD_INFO *ofp, u_char in, FERR_TYPE etype) { LAST_FERR *ep; ep = in ? &ofp->mp->iflod_err : &ofp->mp->oflod_err; if (ep->error_type != FERR_NONE && (ep->error_type == etype || etype == FERR_NONE)) { ep->error_type = FERR_NONE; ep->trace_type = FERR_NONE; } else if (ep->trace_type == etype || etype == FERR_NONE) { ep->trace_type = FERR_NONE; } } void ferr_clear_all(OFLOD_INFO *ofp, u_char in) { ferr_clear(ofp, in, FERR_NONE); } u_char set_flod_socket(OFLOD_INFO *ofp, u_char in, int s, const char *rem_hostname, const DCC_SOCKU *sup) { int on; if (0 > fcntl(s, F_SETFD, FD_CLOEXEC)) ferr(ofp, in, FERR_SOCK, 0, "fcntl(%s, F_SETFD, FD_CLOEXEC): %s", rem_str(rem_hostname, sup), ERROR_STR()); if (-1 == fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK)) { ferr(ofp, in, FERR_SOCK, 0, "fcntl(%s, O_NONBLOCK): %s", rem_str(rem_hostname, sup), ERROR_STR()); return 0; } on = 1; if (0 > setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))) { ferr(ofp, in, FERR_SOCK, 0, "setsockopt(flod %s, SO_KEEPALIVE): %s", rem_str(rem_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, &socbuf, sizeof(socbuf))) ferr(ofp, in, FERR_SOCK, 0, "setsockopt(%s, SO_SNDBUF): %s", rem_str(rem_hostname, sup), ERROR_STR()); } return 1; } void bind_flod_socket(OFLOD_INFO *ofp, int s, const DCC_SOCKU *rem, const DNS *dns) { DCC_SOCKU loc; const SRVR_SOC *sp; DCC_FNM_LNO_BUF fnm_buf; /* match local IPv6 or IPv4 to remote name */ if (rem->sa.sa_family == dns->loc_v4.sa.sa_family) { ofp->flags &= ~OFLOD_FG_LOC_MSG; loc = dns->loc_v4; } else if (rem->sa.sa_family == dns->loc_v6.sa.sa_family) { ofp->flags &= ~OFLOD_FG_LOC_MSG; loc = dns->loc_v6; } else if (ofp->loc_hostname[0] == '\0') { ofp->flags &= ~OFLOD_FG_LOC_MSG; memset(&loc, 0, sizeof(loc)); } else { if (!(ofp->flags & OFLOD_FG_LOC_MSG)) { /* after complaining, continue as if no * local name had been specified */ if (dns->loc_v4.sa.sa_family == AF_UNSPEC && dns->loc_v6.sa.sa_family == AF_UNSPEC) { dcc_error_msg("flood local name %s: %s%s", ofp->loc_hostname, H_ERROR_STR1(dns->loc_h_errno), dcc_fnm_lno(&fnm_buf, flod_path.c, ofp->lno)); } else { dcc_error_msg("no IPv%c address for" " flood local name %s %s %s", rem->sa.sa_family == AF_INET ? '4' : '6', ofp->loc_hostname, dcc_su2str_err(rem), dcc_fnm_lno(&fnm_buf, flod_path.c, ofp->lno)); } ofp->flags |= OFLOD_FG_LOC_MSG; } memset(&loc, 0, sizeof(loc)); } /* If we did not find a local hostname for the connection * and if there is a single "-a address" other than localhost * and of the right family, then default to it. */ if (loc.sa.sa_family == AF_UNSPEC) { for (sp = srvr_socs; sp; sp = sp->fwd) { if (dcc_su_is_loopback(&sp->udp_su)) continue; if (sp->udp_su.sa.sa_family != rem->sa.sa_family) continue; if (loc.sa.sa_family != AF_UNSPEC) { /* more than one, so give up */ memset(&loc, 0, sizeof(loc)); break; } loc = sp->udp_su; } } /* if we have a local IP address for connection, * try to bind() to it */ if (loc.sa.sa_family != AF_UNSPEC || ofp->loc_port != 0) { if (loc.sa.sa_family == AF_UNSPEC) dcc_mk_su(&loc, rem->sa.sa_family, 0, 0, ofp->loc_port); else DCC_SU_PORT(&loc) = ofp->loc_port; if (0 > bind(s, &loc.sa, DCC_SU_LEN(&loc))) dcc_error_msg("bind(flood %s%s): %s", dcc_su2str_err(&loc), dcc_fnm_lno(&fnm_buf, flod_path.c, ofp->lno), ERROR_STR()); } } /* 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; pid_t pid; if (resolve_hosts_pid < 0) return 1; pid = waitpid(resolve_hosts_pid, &status, WNOHANG); if (pid <= 0) { if (pid < 0 && errno != EINTR) dcc_error_msg("resolve waitpid(): %s", ERROR_STR()); RUSH_NEXT_FLODS_CK(); return 0; } resolve_hosts_pid = -1; #if defined(WIFEXITED) && defined(WEXITSTATUS) && defined(WTERMSIG) && defined(WIFSIGNALED) if (WIFEXITED(status)) { status = WEXITSTATUS(status); if (status != 0) dcc_error_msg("resolver child exited with %d", status); } else if (WIFSIGNALED(status)) { status = WTERMSIG(status); dcc_error_msg("resolver child exited with signal %d", status); } else { dcc_error_msg("unknown resolve exit status %#x", status); } #else if (status != EX_OK) dcc_error_msg("resolver child exited with status %d", status); #endif return 1; } /* Resolve all needed host names, perhaps in a separate process. * Leave the results in the file */ static void flod_names_resolve(void) { FLOD_MMAP *mp; DCC_GETH ipv6; u_char ok; DCC_SOCKU *sup, *sup2; #ifdef HAVE_RES_INIT /* get new contents of /etc/resolv.conf */ res_init(); #endif for (mp = flod_mmaps->mmaps; mp <= LAST(flod_mmaps->mmaps); ++mp) { if (mp->rem_hostname[0] == '\0' || (mp->flags & FLODMAP_FG_PASSIVE)) continue; if (mp->flags & FLODMAP_FG_IPv4) ipv6 = DCC_GETH_V4; else if (mp->flags & FLODMAP_FG_IPv6) ipv6 = DCC_GETH_V6; else if (use_ipv46 == DCC_GETH_V46 && !have_ipv6_peer) ipv6 = DCC_GETH_V4; else ipv6 = use_ipv46; memset(&mp->dns, 0, sizeof(mp->dns)); dcc_host_lock(); /* use first addresses for the local host name */ if (mp->loc_hostname[0] != '\0') { if (mp->flags & FLODMAP_FG_SOCKS) ok = dcc_get_host_SOCKS(mp->loc_hostname, ipv6, &mp->dns.loc_h_errno); else ok = dcc_get_host(mp->loc_hostname, ipv6, &mp->dns.loc_h_errno); if (!ok) { TMSG2(FLOD1, "failed to resolve %s: %s", mp->loc_hostname, H_ERROR_STR1(mp->dns.loc_h_errno)); } else { /* save first local IP address of each family */ for (sup = dcc_hostaddrs; sup < dcc_hostaddrs_end; ++sup) { sup2 = (sup->sa.sa_family == AF_INET ? &mp->dns.loc_v4 : &mp->dns.loc_v6); if (sup2->sa.sa_family != sup->sa.sa_family) *sup2 = *sup; } /* If we have only 1 local address, then prefer * that family for the remote addresses. * If we don't have an IPv6 address and we are * not constrained to IPv6 for the remote * address, look for an IPv4 remote address. */ if (mp->dns.loc_v6.sa.sa_family == AF_UNSPEC && ipv6 != DCC_GETH_V6) ipv6 = DCC_GETH_V4; else if (mp->dns.loc_v4.sa.sa_family==AF_UNSPEC && ipv6 != DCC_GETH_V4) ipv6 = DCC_GETH_V6; } } /* resolve the remote host name into a bunch of addresses */ if (mp->flags & FLODMAP_FG_SOCKS) ok = dcc_get_host_SOCKS(mp->rem_hostname, ipv6, &mp->dns.rem_h_errno); else ok = dcc_get_host(mp->rem_hostname, ipv6, &mp->dns.rem_h_errno); if (!ok) { TMSG2(FLOD1, "failed to resolve %s: %s", mp->rem_hostname, H_ERROR_STR1(mp->dns.rem_h_errno)); } else { sup2 = mp->dns.su; sup = dcc_hostaddrs; do { *sup2++ = *sup++; } while (++mp->dns.num < DIM(mp->dns.su) && sup < dcc_hostaddrs_end); } dcc_host_unlock(); flod_mmap_sync(0, 1); } } /* 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(const OFLOD_INFO *ofp) { 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; flod_mmap_sync(0, 0); if (!background) { TMSG_FLOD1(ofp, "resolving host names 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_FLOD1(ofp, "resolving host names started"); /* close files and sockets to avoid interfering with parent */ after_fork(); flod_names_resolve(); TMSG_FLOD1(ofp, "resolving host names finished"); exit(0); } static void iflod_delay(OFLOD_INFO *ofp) { FLOD_MMAP *mp; ofp->i_dns.num = 0; /* start from 1st IP address next time */ 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_FLOD1(ofp, "postpone restarting %s flood from" " %s for %d seconds", socks_type_str(mp), ofp_rem_str(ofp), mp->itimers.retry_secs); } static void iflod_socks_backoff(OFLOD_INFO *ofp) { FLOD_MMAP *mp; ofp->i_dns.num = 0; /* start from 1st IP address next time */ 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; } static void iflod_clear(IFLOD_INFO *ifp, u_char fail) /* 1=problems so delay restart */ { OFLOD_INFO *ofp; ofp = ifp->ofp; if (fail && ofp != 0) iflod_delay(ifp->ofp); /* 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 DCC_PF(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) { ferr(ofp, 1, FERR_CLOSE, !complain, "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 { ferr(ofp, 1, FERR_CLOSE, !complain, "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_FLOD1(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 bad connection being discarded, * break the association with the outgoing stream. */ if (ofp != 0 && ofp->ifp == ifp) { ofp->ifp = 0; save_flod_cnts(ofp); } 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; if (mp->flags & FLODMAP_FG_FFWD_IN) { mp->flags &= ~(FLODMAP_FG_FFWD_IN | FLODMAP_FG_NEED_RWD); 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_RWD) { mp->flags &= ~FLODMAP_FG_NEED_RWD; 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)) ferr_clear_all(ofp, 1); /* limit the backoff for outgoing connection attempts * while the incoming connection is working */ DB_ADJ_TIMER(&mp->otimers.retry, &mp->otimers.retry_secs, FLOD_SUBMAX_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, DCC_FLOD_AYT); buf.note.len = sizeof(DCC_FLOD_AYT) + FLOD_NOTE_OVHD; TMSG1_FLOD1(ofp, "send note to %s: \""DCC_FLOD_AYT"\"", 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(FLOD1, "stop flood listening on %s", dcc_su2str_err(&sp->listen_su)); if (0 > close(sp->listen)) TMSG2(FLOD1, "close(flood listen on %s): %s", dcc_su2str_err(&sp->listen_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) ferr(ifp->ofp, 1, FERR_STOP, 1, "flood from %s stopping", ifp_rem_str(ifp)); else ferr(ifp->ofp, 1, FERR_STOP, 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; socklen_t l; struct in6_addr peer_addr; int count; RL *ip_rl, *block_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); ifp->soc = accept(sp->listen, &ifp->rem.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); if (!set_flod_socket(0, 1, ifp->soc, ifp->rem_hostname, &ifp->rem)) { 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; /* quietly forget this peer if it is blacklisted */ ip_rl = rl_get(&block_rl, DCC_PKT_VERS_INVALID, DCC_ID_SRVR_ROGUE, dcc_su2ipv6(&peer_addr, 0, &ifp->rem), 0); rl_inc2(ip_rl, &rl_sub_rate, block_rl); if ((ip_rl->d.fgs & RL_FG_BLACK_ADDR) && !(ip_rl->d.fgs & RL_FG_FLOD_OK)) { u_char complain = 0; if (!(ip_rl->d.fgs & RL_FG_DROPPED)) { ip_rl->d.fgs |= RL_FG_DROPPED; complain = 1; } if (block_rl && !(block_rl->d.fgs & RL_FG_DROPPED)) { block_rl->d.fgs |= RL_FG_DROPPED; complain = 1; } iflod_close(ifp, 1, (complain || (ip_rl->d.fgs & RL_FG_TRACE) != 0 || TMSG_BIT(FLOD1)), 1, "blacklisted"); continue; } TMSG1(FLOD1, "start flood from %s", ifp_rem_str(ifp)); again:; } } /* 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_VERS_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, dcc_fnm_lno(&fnm_buf, flod_path.c, ofp->lno)); return -1; } if (ofp->o_opts.flags & FLOD_OPT_SOCKS) { i = Rconnect(ifp->soc, &ifp->rem.sa, DCC_SU_LEN(&ifp->rem)); } else { /* must be NAT or assumed NAT */ i = connect(ifp->soc, &ifp->rem.sa, DCC_SU_LEN(&ifp->rem)); } if (0 > i && errno != EISCONN) { if (CONN_WAIT_ERRORS()) { ferr(ofp, 1, FERR_CONNECT, 1, "waiting to connect %s flood from %s", socks_type_str(ofp->mp), ifp_rem_str(ifp)); return 0; } /* failure */ ferr(ofp, 1, FERR_CONNECT, 1, "connect(%s %s): %s", socks_type_str(ofp->mp), ifp_rem_str(ifp), CONN_EMSG()); close(ifp->soc); if (++ofp->i_dns.cur < ofp->i_dns.num) { /* Immediately try the next address */ iflod_clear(ifp, 0); } else { /* Start over later if last IP address failed */ iflod_socks_backoff(ofp); iflod_clear(ifp, 1); } return -1; } ifp->flags |= (IFLOD_FG_CONNECTED | IFLOD_FG_CLIENT); ofp->i_dns.num = 0; /* start from 1st IP address next time */ ferr(ofp, 1, FERR_CONNECT, 1, "starting %s flood from %s", socks_type_str(ofp->mp), 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 or NAT 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; /* try all host names */ for (;;) { /* do nothing if it is already running or is not using SOCKS */ if (ofp->ifp || 0 == (ofp->mp->flags & FLODMAP_FG_ACT) || IFLOD_OPT_OFF_ROGUE(ofp)) return; /* wait if it is not time to try again */ if (!DB_IS_TIME(ofp->mp->itimers.retry, ofp->mp->itimers.retry_secs)) return; /* wait if output has not been running for a while to * give peer a chance to connect to us before assuming NAT */ if (ofp->mp->flags & FLODMAP_FG_NAT_AUTO) { if (!(ofp->flags & OFLOD_FG_CONNECTED) || (ofp->flags & OFLOD_FG_NEW)) return; ofp->mp->itimers.retry = (ofp->mp->cnts.out_conn_changed + FLOD_AUTO_NAT_DELAY); ofp->mp->itimers.retry_secs = FLOD_AUTO_NAT_DELAY; if (!DB_IS_TIME(ofp->mp->itimers.retry, ofp->mp->itimers.retry_secs)) return; } /* find free slot or an existing slot for the incoming flood */ ifp = 0; for (ifp1 = iflods.infos; ifp1 <= LAST(iflods.infos); ++ifp1) { /* there is nothing to do if it already exists */ if (ifp1->ofp == ofp) return; if (ifp1->soc < 0 && !ifp) ifp = ifp1; /* note first free slot */ } if (!ifp) { ferr(ofp, 1, FERR_START, ++complained_many_iflods == 1 ? 0 : 2, "too many incoming floods to start" " %s flood from %s", socks_type_str(ofp->mp), ofp->rem_hostname); iflod_socks_backoff(ofp); iflod_delay(ofp); return; } if (ofp->i_dns.cur >= ofp->i_dns.num) { if (!flod_names_resolve_start(ofp)) return; /* wait for name resolution */ /* snapshot new names */ ofp->i_dns = ofp->mp->dns; if (ofp->i_dns.num == 0) { ferr(ofp, 1, FERR_DNS, 0, "flood peer name %s: '%s'%s", ofp->rem_hostname, H_ERROR_STR1(ofp->i_dns.rem_h_errno), dcc_fnm_lno(&fnm_buf, flod_path.c, ofp->lno)); iflod_socks_backoff(ofp); iflod_delay(ofp); return; } /* since we have addresses, * forget old complaints about bad host names */ ferr_clear(ofp, 1, FERR_DNS); } ifp->ofp = ofp; STRLCPY(ifp->rem_hostname, ofp->rem_hostname, sizeof(ifp->rem_hostname)); ifp->rem = ofp->i_dns.su[ofp->i_dns.cur]; DCC_SU_PORT(&ifp->rem) = ofp->rem_port; ifp->soc = socket(ifp->rem.sa.sa_family, SOCK_STREAM, 0); /* stop if we cannot open the socket */ if (ifp->soc < 0) { ferr(ofp, 1, FERR_START, 0, "socket(%s flood %s): %s", socks_type_str(ofp->mp), ifp_rem_str(ifp), ERROR_STR()); iflod_socks_backoff(ofp); iflod_clear(ifp, 1); return; } if (!set_flod_socket(ofp, 1, ifp->soc, ifp->rem_hostname, &ifp->rem)) { close(ifp->soc); ifp->soc = -1; iflod_socks_backoff(ofp); iflod_clear(ifp, 1); return; } bind_flod_socket(ofp, ifp->soc, &ifp->rem, &ofp->i_dns); /* 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; if (0 <= iflod_socks_connect(ifp)) return; } } /* See if the new duplicate report contains exactly the same checksums as * the record that is already in the database. * Reputation checksums are trimmed from reports flooded to non-reputation * peers. Otherwise all reports are the same. */ typedef enum { CK_DUP_BROKEN, /* broken database & flood closed */ CK_DUP_NOT, /* not a duplicate report */ CK_DUP_NEXT_CK, /* not duplicate in this chain */ CK_DUP_DISCARD, /* discard duplicate report */ CK_DUP_REPLACE /* replace old record with new report */ } CK_DUP; static CK_DUP ck_dup_rcd(IFLOD_INFO *ifp, DB_RCD *new, DB_ST *old_st, DB_RCD *old) { DB_RCD_CK *new_ck, *old_ck; int new_num_cks, old_num_cks; int new_unique, old_unique; /* See if one record is a subset of the other * * Because of varying db_parms.nokeep_cks or -K bits, * the records might not have the same checksums. * * We do not need to compare the checksums, because the fact that * the records have the same server-ID and timestamp implies that * any checksums of the same type are the same. */ old_unique = 0; old_num_cks = DB_NUM_CKS(old); old_ck = old->cks; new_unique = 0; new_num_cks = DB_NUM_CKS(new); new_ck = new->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(new_ck) == DCC_CK_FLOD_PATH) { /* skip paths in the new record */ ++new_ck; --new_num_cks; } else if (DB_CK_TYPE(old_ck) == DB_CK_TYPE(new_ck)) { /* skip identical checksums, * but do not even count duplicate server-ID * declarations */ if (DB_CK_TYPE(new_ck) == DCC_CK_SRVR_ID) return CK_DUP_DISCARD; ++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 (TMSG_FB2(ifp->ofp) && CK_FLOD_CNTERR(&ifp->ofp->lc.dup)) flod_cnterr(&ifp->ofp->lc.dup, "%sduplicate %s", old_unique+old_num_cks == 0 ? "" : "subset ", rpt_id("report", new, ifp)); return CK_DUP_DISCARD; } /* 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 */ if (old_unique+old_num_cks == 0) { if (TMSG_FB2(ifp->ofp) && CK_FLOD_CNTERR(&ifp->ofp->lc.dup)) flod_cnterr(&ifp->ofp->lc.dup, "superset duplicate %s", rpt_id("report", new, ifp)); DB_TGTS_RCD_SET(old, 0); DIRTY_RCD(old_st, 1); return CK_DUP_REPLACE; } /* Keep the new record and delete the overlapping checksums in * the old record. * This doubles counts until the next time dbclean is run. */ if (TMSG_FB2(ifp->ofp) && CK_FLOD_CNTERR(&ifp->ofp->lc.dup)) flod_cnterr(&ifp->ofp->lc.dup, "partial duplicate %s", rpt_id("report", new, ifp)); old_num_cks = DB_NUM_CKS(old); old_ck = old->cks; new_num_cks = DB_NUM_CKS(new); new_ck = new->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(new_ck) == DCC_CK_FLOD_PATH) { /* skip paths in the new record */ ++new_ck; --new_num_cks; } else if (DB_CK_TYPE(old_ck) == DB_CK_TYPE(new_ck)) { /* zap an identical checksum in the old record, * unless it is already dead, in which case * the new checksum is also junk */ if (DB_CK_JUNK(old_ck)) { new_ck->type_fgs |= DB_CK_FG_JUNK; } else { old_ck->type_fgs |= DB_CK_FG_JUNK; DIRTY_RCD(old_st, 1); } ++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_ck; --old_num_cks; } else { /* skip unique checksum in the new record */ ++new_ck; --new_num_cks; } } return CK_DUP_REPLACE; } /* See if two reports came via the same path */ static u_char eq_paths(const DB_RCD *new, const DB_RCD *old) { const DB_RCD_CK *new_ck, *old_ck; int new_num_cks, old_num_cks; new_num_cks = DB_NUM_CKS(new); new_ck = new->cks; while (new_num_cks != 0 && DB_CK_TYPE(new_ck) != DCC_CK_FLOD_PATH) { ++new_ck; --new_num_cks; } old_num_cks = DB_NUM_CKS(old); old_ck = old->cks; while (old_num_cks != 0 && DB_CK_TYPE(old_ck) != DCC_CK_FLOD_PATH) { ++old_ck; --old_num_cks; } /* Assume the path is in a contiguous set of checksums. */ while (old_num_cks != 0 && new_num_cks != 0) { if (DB_CK_TYPE(new_ck) != DCC_CK_FLOD_PATH) { if (DB_CK_TYPE(old_ck) != DCC_CK_FLOD_PATH) return 1; return 0; } if (DB_CK_TYPE(old_ck) != DCC_CK_FLOD_PATH) return 0; if (memcmp(&old_ck->sum, &new_ck->sum, sizeof(old_ck->sum))) return 0; ++new_ck; --new_num_cks; ++old_ck; --old_num_cks; } return 0; } /* See if the new record is a duplicate of any existing records containing * the specified checksum */ static CK_DUP ck_dup_ck_chain(IFLOD_INFO *ifp, DB_ST *old_st, DB_RCD *new, DCC_TGTS new_tgts_raw, DCC_CK_TYPES type, const DB_RCD_CK *found_ck) /* checksum in initial record */ { #define MAX_CHAIN 100 DB_PTR limit, prev; time_t limit_secs; const char *limit_str; DB_RCD *old; DCC_TGTS prev_total; DCC_TGTS new_tgts; int cnt, deleted; const char *reason; CK_DUP result; int i; /* Compute a time before which a copy of the record could not be * in the database. * A duplicate of the record could not have been received here * before TS+SKEW, where TS=timestamp in the record and * SKEW=(true time - originating server's clock). * * Assume all of the servers in the path to the originating server * have reasonable clocks, but the originating server's clock * might be wrong by MAX_FLOD_CLOCK_SKEW seconds. * So a duplicate of the record could not have been received here * before TS-MAX_FLOD_CLOCK_SKEW. */ limit_secs = ts2secs(&new->ts) - MAX_FLOD_CLOCK_SKEW; if (limit_secs >= TIME_T(db_parms.cleaned) && db_parms.cleaned != 0) { limit = db_parms.old_db_csize; limit_str = "expire limit "; } else { limit = DB_PTR_BASE; limit_str = "base "; } new_tgts = DB_COOK_TGTS(new_tgts_raw); /* Look at all reports for this checksum */ reason = ""; for (cnt = 1, deleted = 0; ; ++cnt) { old = old_st->d.r; if (DB_TGTS_RCD_RAW(old) == DCC_TGTS_DEL) { /* trigger a quick cleaning if we are hitting giant * chains of old, compressed records */ if (++deleted >= MAX_DUP_CHAIN && iflod_dup_chain < cnt) iflod_dup_chain = cnt; /* Recent deletions of one of the target checksums * make the flooded report stale. * The timestamps in the new and existing deleted * reports could be wrong. Regardless of those errors, * if the new record had been in the database when * dbclean compressed it or when a delete request * arrived, we would have deleted it. */ if (new_tgts_raw != DCC_TGTS_DEL && !ts_newer_ts(&new->ts, &old->ts)) { reason = "deleted "; result = CK_DUP_DISCARD; break; } } if (DB_RCD_ID(old) == DB_RCD_ID(new)) { /* This old report came from the same server. * * If it has the same timestamp so is the same report, * then save the copy that has the most checksums. */ int t = tscmp(&new->ts, &old->ts); if (t == 0) { result = ck_dup_rcd(ifp, new, old_st, old); break; } /* If the reports came by the same path, then we know * whether the new report is a duplicate by comparing * timestamps, because both the new report and a * possible duplicate would have been filtered by the * same db_parms.nokeep_cks or -K bits. * There is no re-ordering of reports within a path. * If the new report is older than the existing report, * then it is a duplicate or uninteresting. * If the new report is newer, then it is not a * duplicate. */ if (eq_paths(new, old)) { reason = "newer report "; result = t < 0 ? CK_DUP_DISCARD : CK_DUP_NOT; break; } } /* If count in the new report is as large as the existing * total, then the new report cannot be a duplicate * for this checksum. */ prev_total = DB_TGTS_CK(found_ck); if (prev_total < new_tgts || (prev_total == new_tgts && new_tgts != DCC_TGTS_TOO_MANY)) { reason = "big "; result = CK_DUP_NEXT_CK; break; } /* Look for a position in the database that precedes * any existing (and so duplicate) copy of the record. * Because hitting the hash table is expensive, * and most duplicates are quickly found, * do this only for chains that might be long. */ if (cnt == 5) { DB_ST *rcd_st = GET_DB_ST(); i = find_date_rcd(limit_secs, rcd_st); if (i < 0) { iflod_close(ifp, 1, 1, 1, "%s", dcc_emsg.c); DB_ERROR_MSG(dcc_emsg.c); free_db_st(rcd_st); return CK_DUP_BROKEN; } if (i > 0 && limit < rcd_st->s.rptr) { limit = rcd_st->s.rptr; limit_str = "date limit "; } free_db_st(rcd_st); } if (old_st->s.rptr < limit) { reason = limit_str; result = CK_DUP_NEXT_CK; break; } /* Do only a cursory duplicate check on known spam. * Rely on the database checks to mark it as junk * if it is a duplicate. */ if (cnt >= MAX_CHAIN && prev_total == DCC_TGTS_TOO_MANY) { reason = "spam "; result = CK_DUP_NEXT_CK; break; } /* try the preceding record */ prev = DB_PTR_EX(found_ck->prev); if (prev == DB_PTR_NULL) { /* we know it is not a duplicate when we finally * reach the end of the chain */ reason = "end "; result = CK_DUP_NEXT_CK; break; } if (prev >= old_st->s.rptr) { dcc_pemsg(EX_DATAERR, &dcc_emsg, "bad %s link of "L_HxPAT" at "L_HxPAT, DB_TYPE2STR(type), prev, old_st->s.rptr); iflod_close(ifp, 1, 1, 1, "%s", dcc_emsg.c); DB_ERROR_MSG(dcc_emsg.c); return CK_DUP_BROKEN; } found_ck = db_map_rcd_ck(&dcc_emsg, old_st, prev, type); if (!found_ck) { iflod_close(ifp, 1, 1, 1, "%s", dcc_emsg.c); DB_ERROR_MSG(dcc_emsg.c); return CK_DUP_BROKEN; } } if (((cnt >= MAX_CHAIN && TMSG_BIT(DB)) || TMSG_FB2(ifp->ofp)) && CK_FLOD_CNTERR(&ifp->ofp->lc.dup)) { const char *rstr = 0; switch (result) { case CK_DUP_BROKEN: rstr = "broken DB"; break; case CK_DUP_NOT: rstr = "not duplicate"; break; case CK_DUP_NEXT_CK: rstr = "next-ck"; break; case CK_DUP_DISCARD: rstr = "discard"; break; case CK_DUP_REPLACE: rstr = "replace"; break; } flod_cnterr(&ifp->ofp->lc.dup, "stop looking with \"%s\" for %s in %s chain" " after %d records at %s"L_HPAT, rstr, rpt_id("report", new, ifp), DB_TYPE2STR(type), cnt, reason, old_st->s.rptr); } return result; } /* complain about a received flooded report, * and possibly close and so clear things */ static u_char DCC_PF(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 || db_debug >= 2) { 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); } /* consider a server-ID in an incoming report */ static u_char /* 0=discard it */ iflod_rpt_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]; const OFLOD_INFO *ofp1; ID_TBL *tp; /* notice claims by other servers to our ID */ if (srvr_id == my_srvr_id) { if (memcmp(&host_id_sum, &ck->sum, sizeof(host_id_sum))) dcc_error_msg("host %s claimed our server-ID %d at %s", ck2str_err(DCC_CK_SRVR_ID, &ck->sum, 0), my_srvr_id, ts2str_err(&new->ts)); return 0; } /* accept it if it is not a server-ID type announcement */ if (!DCC_ID_SRVR_TYPE(srvr_id)) return 1; /* discard type announcements for mapped server-IDs */ tgt_id = DCC_ID_SRVR_TYPE_ID(&ck->sum); switch (id_map(tgt_id, &ifp->ofp->i_opts.maps)) { case ID_MAP_NO: case ID_MAP_REJ: break; case ID_MAP_SELF: return 0; } type_id = srvr_id; switch (type_id) { case DCC_ID_SRVR_SIMPLE: case DCC_ID_SRVR_ROGUE: case DCC_ID_SRVR_IGNORE: break; default: return 0; } if (ck->sum.b[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 ((type_id == DCC_ID_SRVR_ROGUE) != ((ofp1->o_opts.flags & FLOD_OPT_ROGUE) != 0)) { if (TMSG_FB1(ifp->ofp) || TMSG_FB1(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_ST *rcd_st) { DB_PTR pos; DCC_TGTS new_tgts, found_tgts; DB_RCD new; DCC_SRVR_ID old_srvr; const DCC_CK *ck_lim, *ck; const DB_RCD_CK *srvr_id_ck; DB_RCD_CK *found_ck, *new_ck; DCC_CK_TYPES type, prev_type; DCC_FLOD_PATH_ID *new_path_id; int num_path_blocks; char tgts_buf[DCC_XHDR_MAX_TGTS_LEN]; int ok2; int rpt_len; u_char timely; ID_MAP_RESULT srvr_mapped; ID_TBL *tp; CK_DUP r; u_char *not_dup_class, not_dup_global, not_dup_nokeep; u_char not_dup_rep; pos = flod_pos2db_ptr(stream->r.pos); if (pos < DCC_FLOD_POS_MIN) { iflod_close(ifp, 1, 1, 1, "bogus position "L_HxPAT" in flooded report #%d", pos, ofp->cnts.total); return -1; } 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, stream->r.srvr_id, sizeof(new.srvr_id)); /* DCC_SRVR_ID_AUTH is obsolete but can be seen from ancient peers */ old_srvr = ntohs(new.srvr_id) & DCC_SRVR_ID_MASK; /* ignore keepalives * They are sent only to ensure that there is always a position * to acknowledge even with a server-ID mapping that stops all * reports */ if (old_srvr == DCC_ID_SRVR_DATE) return rpt_len; new.srvr_id = 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) { iflod_close(ifp, 1, 1, 1, "bogus target count %s in %s", tgts2str(tgts_buf, sizeof(tgts_buf), new_tgts, grey_on), rpt_id("report", &new, 0)); return -1; } /* notice reports from the distant future */ if (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; timely = 0; 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.maps); 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 = 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 = new_ck->sum.p; /* 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 = type = DCC_CK_INVALID, ck = stream->r.cks; ck < ck_lim; prev_type = type, ++ck) { type = ck->type; if (!DCC_CK_TYPE_FLOD_OK(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); new_ck->sum = ck->sum; if (type == DCC_CK_FLOD_PATH) { /* discard report if path is too long */ if (++num_path_blocks > DCC_MAX_FLOD_PATHS) { TMSG2_FLOD1(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; } 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; if (DCC_CK_IS_REP(grey_on, type) && new_tgts > DCC_REP_TGTS_MAX && new_tgts != DCC_TGTS_DEL) { iflod_close(ifp, 1, 1, 1, "bogus target count" " %s for %s in %s", tgts2str(tgts_buf, sizeof(tgts_buf), new_tgts, grey_on), DB_TYPE2STR(type), rpt_id("report", &new, 0)); return -1; } /* server-ID declarations are never stale */ if (type == DCC_CK_SRVR_ID) { timely = 1; 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 (!timely) { if ((tp != 0 && (tp->srvr_type == DCC_ID_SRVR_ROGUE || tp->srvr_type == DCC_ID_SRVR_IGNORE)) || ts2secs(&new.ts) < (db_time.tv_sec - FLOD_STALE_SECS)) { if (TMSG_FB2(ofp) && CK_FLOD_CNTERR(&ofp->lc.stale)) flod_cnterr(&ofp->lc.stale, "stale %s", rpt_id("report", &new, ifp)); return rpt_len; } if (ts_newer_ts(&new.ts, new_tgts >= db_tholds[type] ? &db_parms.ex_spam[type] : &db_parms.ex_all[type])) timely = 1; } } ++new_ck; ++new.fgs_num_cks; } if (!timely) { if (TMSG_FB2(ofp) && CK_FLOD_CNTERR(&ofp->lc.stale)) 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. * Divide the checksums into classes, and check one chain in each. * Each class is of checksums that can be separated from the * original report as the report is flooded. */ ok2 = 0; not_dup_global = 0; not_dup_nokeep = 0; not_dup_rep = 0; /* check body checksum first because its chain is usually shortest */ for (new_ck = new.cks; new_ck < &new.cks[DB_NUM_CKS(&new)]; ++new_ck) { type = DB_CK_TYPE(new_ck); if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type)) continue; /* chase each class of chain only once */ if (DB_GLOBAL_NOKEEP(grey_on, type)) not_dup_class = ¬_dup_nokeep; else if (DCC_CK_IS_REP(grey_on, type)) not_dup_class = ¬_dup_rep; else not_dup_class = ¬_dup_global; if (*not_dup_class) continue; switch (db_lookup(&dcc_emsg, type, &new_ck->sum, 0, rcd_st, &found_ck)) { case DB_FOUND_SYSERR: iflod_close(ifp, 1, 1, 1, "%s", dcc_emsg.c); DB_ERROR_MSG(dcc_emsg.c); return -1; case DB_FOUND_IT: found_tgts = DB_TGTS_CK(found_ck); /* This checksum is already in the database. * The report might or might not be a duplicate. */ r = ck_dup_ck_chain(ifp, rcd_st, &new, new_tgts, type, found_ck); switch (r) { case CK_DUP_BROKEN: return -1; case CK_DUP_NOT: goto keep; case CK_DUP_DISCARD: return rpt_len; case CK_DUP_REPLACE: goto keep; case CK_DUP_NEXT_CK: /* The report is not a duplicate in this * checksum chain. It might be a superset * of a report on some other chain because * of varying db_parms.nokeep_cks or -K bits. * * 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 */ if (found_tgts == DCC_TGTS_OK || (found_tgts == DCC_TGTS_GREY_WHITE && (++ok2 >= 2 || grey_on))) { iflod_rpt_complain(ifp, &new, 0, &ofp->lc.wlist, "report","whitelisted"); return rpt_len; } *not_dup_class = 1; break; } 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 */ *not_dup_class = 1; break; } } keep:; /* 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(grey_on, type) && !grey_on) need_del_dbclean = "flood checksum deletion"; } if (srvr_id_ck) { /* discard mapped server-ID assertions */ if (srvr_mapped == ID_MAP_SELF) { TMSG2_FLOD1(ofp, "mapped server-ID assertion from %d in %s", old_srvr, rpt_id("report", &new, ifp)); return rpt_len; } if (!iflod_rpt_id(srvr_id_ck, ifp, &new, old_srvr)) { return rpt_len; } } if (srvr_mapped == ID_MAP_NO && !tp && DCC_ID_SRVR_NORMAL(old_srvr) && grey_on /* no anonymous greylist clients */ && CK_FLOD_CNTERR(&ifp->ofp->lc.bad_id)) flod_cnterr(&ifp->ofp->lc.bad_id, "unknown server-ID in %s", rpt_id("report", &new, ifp)); /* the report is ok and not a duplicate, so add it to our database */ if (!add_dly_rcd(&new, 1, rcd_st)) { iflod_close(ifp, 1, 1, 1, "%s", dcc_emsg.c); 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 "; expected \"" DCC_FLOD_VERS_CUR_STR "\" not \"%.*s\"", LITZ(DCC_FLOD_VERS_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_VERS_HDR *vp; OFLOD_INFO *ofp; IFLOD_INFO *ifp1; FLOD_MMAP *mp; const ID_TBL *tp; DCC_SRVR_ID rem_id; u_char peer_rep_on = 0; int iversion; u_int n; int i; vp = &ifp->ibuf.s.v; if (!strcmp(vp->body.str, DCC_FLOD_VERS_CUR_STR)) { iversion = DCC_FLOD_VERS_CUR; ifp->flags |= IFLOD_FG_VERS_CK; #ifdef DCC_FLOD_VERS7 } else if (!strcmp(vp->body.str, DCC_FLOD_VERS7_STR)) { iversion = DCC_FLOD_VERS7; ifp->flags |= IFLOD_FG_VERS_CK; #endif /* DCC_FLOD_VERS7 */ } else if (!strcmp(vp->body.str, DCC_FLOD_REP_VERS_CUR_STR)) { iversion = DCC_FLOD_VERS_CUR; ifp->flags |= IFLOD_FG_VERS_CK; peer_rep_on = 1; #ifdef DCC_FLOD_VERS7 } else if (!strcmp(vp->body.str, DCC_FLOD_REP_VERS7_STR)) { iversion = DCC_FLOD_VERS_CUR; ifp->flags |= IFLOD_FG_VERS_CK; #endif /* DCC_FLOD_VERS7 */ } else if (!strncmp(vp->body.str, DCC_FLOD_VERS_BASE, LITZ(DCC_FLOD_VERS_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 (!DCC_ID_SRVR_NORMAL(rem_id)) { 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 */ mp = ofp->mp; /* check that the peer 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, dcc_fnm_lno(&fnm_buf, flod_path.c, ofp->lno)); else iflod_close(ifp, 1, 1, 1, DCC_FLOD_BAD_AUTH_MSG" %d", ofp->in_passwd_id); return 0; } if (i == 1) mp->flags &= ~FLODMAP_FG_USE_2PASSWD; else mp->flags |= FLODMAP_FG_USE_2PASSWD; /* no more auto-NAT if it contacted us */ if (!(ifp->flags & IFLOD_FG_CLIENT)) 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. */ 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, 0); return 0; } if (peer_rep_on) { /* the peer wants to use DCC Reputations */ if (iversion != DCC_FLOD_REP_VERS_CUR) TMSG2_FLOD1(ofp, "version %d from %s", iversion, ifp_rem_str(ifp)); mp->flags |= FLODMAP_FG_REP_PEER_ON; if (!grey_on) { /* try reputations next time we try to connect */ ofp->flags |= OFLOD_FG_REP_TRY; /* Reputations implies accepting deletes by default so * that we can fix reputations */ if (!(ofp->i_opts.flags & FLOD_OPT_DEL_SET)) ofp->i_opts.flags |= FLOD_OPT_DEL_OK; } } else { /* the peer said nothing about wanting DCC Reputations */ if (iversion != DCC_FLOD_VERS_CUR) TMSG2_FLOD1(ofp, "version %d from %s", iversion, ifp_rem_str(ifp)); mp->flags &= ~FLODMAP_FG_REP_PEER_ON; /* no reputations implies no deletes by default */ if (!(ofp->i_opts.flags & FLOD_OPT_DEL_SET)) ofp->i_opts.flags &= ~FLOD_OPT_DEL_OK; } /* 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), dcc_fnm_lno(&fnm_buf, flod_path.c, 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)) { ferr(ofp, 0, FERR_DUP, 1, " assume response to shutdown lost from %s", ofp_rem_str(ofp)); oflod_close(ofp, 0); } if (ofp->soc >= 0) { /* We still have a duplicate. * Assume we missed the shutdown and the connection is * not from a fraud if the IP address of the prospective * peer is the address of the old peer. */ if (!DCC_SU_EQ(&ofp->rem, &ifp->rem)) { iflod_close(ifp, 1, 0, 1, "reject duplicate passive output" " flood from %s", ifp_rem_str(ifp)); return 0; } ferr(ofp, 0, FERR_DUP, 1, "accept duplicate passive output flood from %s", ifp_rem_str(ifp)); oflod_close(ofp, 0); } ofp->soc = ifp->soc; ofp->rem = ifp->rem; ++oflods.open; if (!(mp->flags & FLODMAP_FG_PASSIVE)) { dcc_trace_msg("convert incoming flood to forced passive" " outgoing to %s", ofp_rem_str(ofp)); } else { TMSG1_FLOD1(ofp, "convert incoming flood to passive" " outgoing to %s", ofp_rem_str(ofp)); } iflod_clear(ifp, 0); 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), dcc_fnm_lno(&fnm_buf, flod_path.c, 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) goto start_flood; /* 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)); goto start_flood; } /* Assume we missed the shutdown and the connection is not * from a fraud if the IP address of the prospective peer * is an address of the old peer. */ if (DCC_SUnP_EQ(&ifp1->rem, &ifp->rem)) { iflod_close(ifp1, 0, 0, 1, "assumed dead link and replacement" " for flood from %s", ifp_rem_str(ifp1)); goto start_flood; } for (n = 0; n < ofp->i_dns.num; ++n) { if (DCC_SUnP_EQ(&ifp1->rem, &ofp->i_dns.su[n])) { iflod_close(ifp1, 0, 0, 1, "assumed dead link and replacement" " for flood from %s", ifp_rem_str(ifp1)); goto start_flood; } } /* 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)); goto start_flood; } /* 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; } start_flood: ofp->ifp = ifp; if (ifp->flags & IFLOD_FG_CLIENT) mp->flags &= ~FLODMAP_FG_IN_SRVR; else mp->flags |= FLODMAP_FG_IN_SRVR; save_flod_cnts(ofp); ifp->iflod_alive = db_time.tv_sec; TMSG1_FLOD1(ofp, "flood from %s authenticated", ifp_rem_str(ifp)); /* Try to restart the corresponding output flood because a new * incoming flood might indicate that peer has awakened. */ if (ofp->soc < 0 && !(ofp->o_opts.flags & FLOD_OPT_PASSIVE)) { mp->otimers.retry_secs = FLOD_RETRY_SECS; 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(OFLOD_INFO *ofp, 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, "%s rejected with \"%.*s\"", socks_type_str(ofp->mp), 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, "%s rejected with \"%.*s\"", socks_type_str(ofp->mp), 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, "%s rejected by %s with \"%.*s\"", socks_type_str(ofp->mp), 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; DB_ST *rcd_st; 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) && 0 >= iflod_socks_connect(ifp)) 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_VERS_HDR)) { if (!check_iflod_vers(ifp)) return 1; /* stream closed or converted */ ofp = ifp->ofp; off = ISZ(DCC_FLOD_VERS_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(ofp, ifp); return 1; } else { iflod_close(ifp, 1, 1, 0, "garbage connection from %s", ifp_rem_str(ifp)); return 1; } } /* we know ofp != 0 because check_iflod_vers() has found the peer * ofp->mp != because oflods.infos is either clear or the file * is mapped */ /* deal with the data */ timeval2ts(&future, &db_time, MAX_FLOD_CLOCK_SKEW); rcd_st = GET_DB_ST(); while ((len = ifp->ibuf_len - off) >= DCC_FLOD_RPT_LEN(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, rcd_st); if (i < 0) { free_db_st(rcd_st); return 1; /* stream closed */ } if (i == 0) break; /* wait for rest of report */ off += i; ++ofp->cnts.total; } free_db_st(rcd_st); /* 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 after %d bytes at EOF", ofp->cnts.total, ifp->ibuf_len); } else if (flods_st != FLODS_ST_ON || IFLOD_OPT_OFF_ROGUE(ofp)) { iflod_close(ifp, 0, 0, 1, DCC_FLOD_OK_STR"%s EOF", our_hostname); } else { iflod_close(ifp, 0, 0, 1, DCC_FLOD_OK_STR"%s EOF", 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_IRETRY; ofp->mp->itimers.msg_secs = FLOD_IN_COMPLAIN1; ofp->mp->itimers.msg = db_time.tv_sec + FLOD_IN_COMPLAIN1; /* Limit the backoff for outgoing connection attempts while * receiving data. */ if (!(ofp->o_opts.flags & FLOD_OPT_PASSIVE)) DB_ADJ_TIMER(&ofp->mp->otimers.retry, &ofp->mp->otimers.retry_secs, FLOD_RETRY_SECS); return (req_len > recv_len); } void iflods_listen(void) { SRVR_SOC *sp; int i, on; for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->listen >= 0) continue; /* don't need to listen if there is no flooding */ if (!oflods.total) { iflod_listen_close(sp); continue; } if (sp->flags & SRVR_SOC_ADDR) { /* need to open a TCP listen socket for incoming floods * for each explicitly configured IP address */ sp->listen_su = sp->udp_su; } else if (sp->flags & SRVR_SOC_ANY_LISTEN) { /* need to open one TCP INADDR_ANY listen socket for * the first implicitly configured interface */ dcc_mk_su(&sp->listen_su, sp->udp_su.sa.sa_family, 0, 0, DCC_SU_PORT(&sp->udp_su)); } else { /* otherwise close unneeded listen socket */ iflod_listen_close(sp); continue; } TMSG1(FLOD1, "start flood listening on %s", dcc_su2str_err(&sp->listen_su)); sp->listen = socket(sp->listen_su.sa.sa_family, SOCK_STREAM, 0); if (sp->listen < 0) { dcc_error_msg("socket(flood listen %s): %s", dcc_su2str_err(&sp->listen_su), ERROR_STR()); continue; } if (!soc_v6only(&dcc_emsg, sp->listen, &sp->listen_su)) dcc_trace_msg("UDP %s", dcc_emsg.c); if (0 > fcntl(sp->listen, F_SETFD, FD_CLOEXEC)) dcc_error_msg("fcntl(flood listen %s FD_CLOEXEC): %s", dcc_su2str_err(&sp->listen_su), ERROR_STR()); 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(&sp->listen_su), 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(&sp->listen_su), ERROR_STR()); i = bind(sp->listen, &sp->listen_su.sa, DCC_SU_LEN(&sp->listen_su)); if (0 > i) { dcc_error_msg("bind(flood listen %s): %s", dcc_su2str_err(&sp->listen_su), 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(&sp->listen_su), 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 *distinct_inp) { if (ofp->soc >= 0) { if (ofp->flags & (OFLOD_FG_SHUTDOWN_REQ | OFLOD_FG_SHUTDOWN)) return " (shutting)"; if (!(ofp->flags & OFLOD_FG_CONNECTED)) { *distinct_inp = 1; return " (connecting)"; } return dcc_su2str2(outstr, DCC_SU2STR_SIZE, &ofp->rem); } 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 have_in, u_char distinct_in) { if (have_in) { if (!(ifp->flags & IFLOD_FG_VERS_CK)) return " (connecting)"; if (distinct_in) return dcc_su2str2(instr, DCC_SU2STR_SIZE, &ifp->rem); return ""; } 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 inbuf[DCC_SU2STR_SIZE], outbuf[DCC_SU2STR_SIZE]; const char *inp, *outp; char hostname[60], fg_buf[120]; int l1, l2, s0, s1, s2; 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_ipv6su2ipv4su(&in, &ifp->rem); dcc_ipv6su2ipv4su(&out, &ofp->rem); if (ofp->soc < 0 || !DCC_SUnP_EQ(&in, &out)) distinct_in = 1; } break; } } if (anon) { /* do not tell anonymous users IP addresses, names * or flags */ inp = ""; outp = ""; hostname[0] = '\0'; fg_buf[0] = '\0'; } else { outp = oflod_state_str(outbuf, ofp, &distinct_in); inp = iflod_state_str(inbuf, ofp, ifp, have_in, distinct_in); dcc_host_portname(hostname, sizeof(hostname), ofp->rem_hostname, ofp->rem_port == def_port ? 0 : ofp->rem_portname), flodmap_fg(fg_buf, sizeof(fg_buf), ofp->mp); } l1 = strlen(outp); s0 = 15 - l1; if (s0 < 0) s0 = 0; s1 = 15+2 - (s0+l1); if (s1 < 0) s1 = 0; l2 = strlen(inp); s2 = 13+2 - l2; if (s2 < 0) s2 = 0; if (s0+l1+s1+l2+s2 > (15+2+13+2)) { s2 -= (s0+l1+s1+l2+s2 - (15+2+13+2)); if (s2 < 0) s2 = 0; } if (s0+l1+s1+l2+s2 > (15+2+13+2)) { s1 -= (s0+l1+s1+l2+s2 - (15+2+13+2)); if (s1 < 0) s1 = 0; } if (s0+l1+s1+l2+s2 > (15+2+13+2)) { s0 -= (s0+l1+s1+l2+s2 - (15+2+13+2)); if (s0 < 0) s0 = 0; } i = snprintf(p, buf_len, "%5d %*s%s%*s %s%*s %-22s%s\n", ofp->rem_id, s0,"", outp, s1,"", inp, s2,"", 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 DCC_PF(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 u_char flod_stats_conn_emsg(char **buf, int *buf_len, const LAST_FERR *ep) { const char *msg; if (ep->error_type != FERR_NONE) msg = ep->error_msg; else if (ep->trace_type != FERR_NONE) msg = ep->trace_msg; else return 1; return flod_stats_str(buf, buf_len, "\n\t%s", msg); } 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; DCC_FNM_LNO_BUF fnm_buf; time_t deadline; u_char passive; 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 >= TIME_T(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); /* show the last active error messages * if we are forced passive */ if (!in && (mp->flags & FLODMAP_FG_OUT_SRVR) && !(mp->flags & FLODMAP_FG_PASSIVE)) flod_stats_conn_emsg(buf, buf_len, &mp->oflod_err); return; } if ((in && (mp->flags & FLODMAP_FG_IN_OFF)) || (!in && (mp->flags & FLODMAP_FG_OUT_OFF))) { flod_stats_str(buf, buf_len, " off%s", dcc_fnm_lno(&fnm_buf, flod_path.c, ofp->lno)); return; } if (!flod_stats_str(buf, buf_len, " not connected")) return; if (conn_changed >= TIME_T(mp->cnts.cnts_cleared)) { if (!flod_stats_time(buf, buf_len, " since", "%b %d %X", conn_changed)) return; } if (!flod_stats_conn_emsg(buf, buf_len, in ? &mp->iflod_err : &mp->oflod_err)) 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) { char socks_buf[40]; const char *pat; if (passive) { pat = "\n complain after"; } else if (in) { snprintf(socks_buf, sizeof(socks_buf), "\n try %s again after", socks_type_str(mp)); pat = socks_buf; } else { pat = "\n try again after"; } flod_stats_time(buf, buf_len, pat, "%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[120]; 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(1, 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_HxPAT, mp->confirm_pos); FLOD_STATS_ALLOC(i); if (ofp->mp->reps_rejected != 0) flod_stats_time(&p, &len, "\n reputations rejected at", "%b %d %X", ofp->mp->reps_rejected); 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, 1); 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 }