Mercurial > notdcc
view dccd/iflod.c @ 6:c7785b85f2d2 default tip
Init scripts try to conform LSB header
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 15:15:36 +0100 |
parents | c7f6b056b673 |
children |
line wrap: on
line source
/* Distributed Checksum Clearinghouse * * deal with incoming floods of checksums * * Copyright (c) 2008 by Rhyolite Software, LLC * * This agreement is not applicable to any entity which sells anti-spam * solutions to others or provides an anti-spam solution as part of a * security solution sold to other entities, or to a private network * which employs the DCC or uses data provided by operation of the DCC * but does not provide corresponding data to other users. * * Permission to use, copy, modify, and distribute this software without * changes for any purpose with or without fee is hereby granted, provided * that the above copyright notice and this permission notice appear in all * copies and any distributed versions or copies are either unchanged * or not called anything similar to "DCC" or "Distributed Checksum * Clearinghouse". * * Parties not eligible to receive a license under this agreement can * obtain a commercial license to use DCC by contacting Rhyolite Software * at sales@rhyolite.com. * * A commercial license would be for Distributed Checksum and Reputation * Clearinghouse software. That software includes additional features. This * free license for Distributed ChecksumClearinghouse Software does not in any * way grant permision to use Distributed Checksum and Reputation Clearinghouse * software * * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. * * Rhyolite Software DCC 1.3.103-1.249 $Revision$ */ #include "dccd_defs.h" #include <sys/wait.h> IFLODS iflods; u_int complained_many_iflods; time_t got_hosts; pid_t resolve_hosts_pid = -1; time_t iflods_ok_timer; /* incoming flooding ok since then */ int flod_trace_gen; /* unsuppress tracing */ static u_char iflod_write(IFLOD_INFO *, void *, int, const char *, u_char); static DCC_TS future; /* timestamp sanity */ ID_MAP_RESULT id_map(DCC_SRVR_ID srvr, const OFLOD_OPTS *opts) { int i; ID_MAP_RESULT result; /* apply the ***first*** server-ID map that matches, if any */ for (i = 0; i < opts->num_maps; ++i) { if (opts->srvr_map[i].from_lo <= srvr && opts->srvr_map[i].from_hi >= srvr) { result = opts->srvr_map[i].result; if (result == ID_MAP_SELF && srvr == my_srvr_id) return ID_MAP_NO; return result; } } return ID_MAP_NO; } static const char * rem_str(const char *hostname, const DCC_SOCKU *su) { static int bufno; static struct { char str[90]; } bufs[4]; char *s; char sustr[DCC_SU2STR_SIZE]; int i; s = bufs[bufno].str; bufno = (bufno+1) % DIM(bufs); STRLCPY(s, hostname, sizeof(bufs[0].str)); if (su->sa.sa_family == AF_UNSPEC && *DCC_SU_PORTP(su) == 0) return s; dcc_su2str2(sustr, sizeof(sustr), su); if (strcmp(s, sustr)) { STRLCAT(s, " ", sizeof(bufs[0].str)); STRLCAT(s, sustr, sizeof(bufs[0].str)); } if (*DCC_SU_PORTP(su) != DCC_GREY2PORT(grey_on)) { i = strlen(s); snprintf(s+i, sizeof(bufs[0].str)-i, ",%d", ntohs(*DCC_SU_PORTP(su))); } return s; } const char * ifp_rem_str(const IFLOD_INFO *ifp) { if (!ifp) return "(null ifp)"; return rem_str(ifp->rem_hostname, &ifp->rem_su); } const char * ofp_rem_str(const OFLOD_INFO *ofp) { if (!ofp) return "(null ofp)"; return rem_str(ofp->rem_hostname, &ofp->rem_su); } static const char * rpt_id(const char *type, const DB_RCD* rcd, const IFLOD_INFO *ifp) { static int bufno; static struct { char str[120]; } bufs[4]; char *s; char id_buf[30]; s = bufs[bufno].str; bufno = (bufno+1) % DIM(bufs); snprintf(s, sizeof(bufs[0].str), "%s%s %s ID=%s %s%s", type ? "flooded " : "", type ? type : "", ts2str_err(&rcd->ts), id2str(id_buf, sizeof(id_buf), rcd->srvr_id_auth), ifp ? "from " : "", ifp ? ifp_rem_str(ifp) : ""); return s; } void PATTRIB(2,3) flod_cnterr(const FLOD_LIMCNT *lc, const char *p, ...) { char buf[200]; va_list args; va_start(args, p); if (lc->cur < lc->lim + FLOD_LIM_COMPLAINTS) { dcc_verror_msg(p, args); } else { vsnprintf(buf, sizeof(FLOD_EMSG), p, args); dcc_error_msg("%s; stop complaints", buf); } va_end(args); } static void date_err_msg(FLOD_EMSG out, int len, const char *in) { memcpy(out, in, len); if (len >= ISZ(FLOD_EMSG)-LITZ(" at hh:mm:ss")-1) { out[len] = '\0'; } else { dcc_time2str(&out[len], ISZ(FLOD_EMSG)-len, " at %T", db_time.tv_sec); } } /* remove extra quotes and strings from a message to or from a peer */ static void trim_err_msg(FLOD_EMSG out, const FLOD_EMSG in) { char *q1, *q2; int len; q1 = strchr(in, '\''); if (q1) { q2 = strrchr(++q1, '\''); if (q2) { len = q2-q1; if (len > LITZ(DCC_FLOD_OK_STR) && !LITCMP(q1, DCC_FLOD_OK_STR)) { len -= LITZ(DCC_FLOD_OK_STR); q1 += LITZ(DCC_FLOD_OK_STR); } date_err_msg(out, len, q1); return; } } date_err_msg(out, strlen(in), in); } /* report a flooding error */ void PATTRIB(4,5) rpt_err(OFLOD_INFO *ofp, u_char trace, /* 0=error, 1=trace, 2=no dup trace */ u_char in, /* 0=output 1=input */ const char *p, ...) { FLOD_MMAP *mp; LAST_ERROR *ep; FLOD_EMSG tmp, trimmed; va_list args; va_start(args, p); vsnprintf(tmp, sizeof(FLOD_EMSG), p, args); va_end(args); mp = ofp ? ofp->mp : 0; if (trace != 0) { if (!mp) { TMSG_FLOD(ofp, tmp); return; } trim_err_msg(trimmed, tmp); ep = in ? &mp->iflod_err : &mp->oflod_err; if (TMSG_FB(ofp) && (trace < 2 || strcmp(trimmed, ep->trace_msg) || ep->trace_gen != flod_trace_gen)) { /* suppress some duplicate flooding messages */ dcc_trace_msg(tmp); ep->trace_gen = flod_trace_gen; } strncpy(ep->trace_msg, trimmed, sizeof(ep->trace_msg)); ep->complained = 0; } else { if (!mp) { dcc_error_msg(tmp); return; } dcc_error_msg(tmp); ep = in ? &mp->iflod_err : &mp->oflod_err; trim_err_msg(ep->msg, tmp); ep->trace_msg[0] = '\0'; ep->complained = 0; } } u_char set_flod_socket(OFLOD_INFO *ofp, u_char in, int s, const char *hostname, const DCC_SOCKU *sup) { #if IP_TOS static u_char tos_ok = 1; #endif int on; if (0 > fcntl(s, F_SETFD, FD_CLOEXEC)) rpt_err(ofp, 0, in, "fcntl(%s, F_SETFD, FD_CLOEXEC): %s", rem_str(hostname, sup), ERROR_STR()); if (-1 == fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK)) { rpt_err(ofp, 0, in, "fcntl(%s, O_NONBLOCK): %s", rem_str(hostname, sup), ERROR_STR()); return 0; } on = 1; if (0 > setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))) rpt_err(ofp, 0, in, "setsockopt(flod %s, SO_KEEPALIVE): %s", rem_str(hostname, sup), ERROR_STR()); if (in) { /* Ensure that we have enough socket buffer space to send * complaints about the input flood. Normally little or * nothing is sent upstream, but bad clocks or other * problems can cause many complaints. */ if (0 > setsockopt(s, SOL_SOCKET, SO_SNDBUF, &srvr_rcvbuf, sizeof(srvr_rcvbuf))) rpt_err(ofp, 0, in, "setsockopt(%s, SO_SNDBUF): %s", rem_str(hostname, sup), ERROR_STR()); } #ifdef IP_TOS /* It would be nice and clean to use netinet/ip.h for the definition * of IPTOS_THROUGHPUT. However, it is hard to use netinet/ip.h * portably because in_sysm.h is required for n_long on some * systems and not others. A bunch of messy ./configure fiddling * might patch that hassle, but the bit really ought to be the same * as the old 0x08 in the IPv4 header. */ if (sup->sa.sa_family == AF_INET && tos_ok) { on = 0x08; /* IPTOS_THROUGHPUT */ if (0 > setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(on))) { rpt_err(ofp, 0, in, "setsockopt(IP_TOS, IPTOS_THROUGHPUT, %s): %s", rem_str(hostname, sup), ERROR_STR()); tos_ok = 0; } } #endif return 1; } /* see if the host name resolution process is still running */ u_char /* 1=not running 0=please wait */ flod_names_resolve_ck(void) { int status; if (resolve_hosts_pid < 0) return 1; if (resolve_hosts_pid == waitpid(resolve_hosts_pid, &status, WNOHANG)) { resolve_hosts_pid = -1; return 1; } RUSH_NEXT_FLODS_CK(); return 0; } static void flod_names_resolve(void) { FLOD_MMAP *mp; u_char ipv6, ok; const DCC_SOCKU *sup; for (mp = flod_mmaps->mmaps; mp <= LAST(flod_mmaps->mmaps); ++mp) { if (mp->rem_hostname[0] == '\0' || (mp->flags & FLODMAP_FG_PASSIVE)) continue; ipv6 = ((mp->flags & FLODMAP_FG_IPv4) ? 0 : (mp->flags & FLODMAP_FG_IPv6) ? 1 : use_ipv6 ? 2 : 0); dcc_host_lock(); if (mp->flags & FLODMAP_FG_SOCKS) ok = dcc_get_host_SOCKS(mp->rem_hostname, ipv6, &mp->host_error); else ok = dcc_get_host(mp->rem_hostname, ipv6, &mp->host_error); if (!ok) { TMSG2(FLOD, "failed to resolve %s: %s", mp->rem_hostname, DCC_HSTRERROR(mp->host_error)); } else { for (sup = dcc_hostaddrs; sup < dcc_hostaddrs_end; ++sup) { if ((ipv6 == 0 && sup->sa.sa_family != AF_INET) || (ipv6 == 1 && sup->sa.sa_family != AF_INET6)) continue; mp->rem_su = *sup; *DCC_SU_PORTP(&mp->rem_su) = mp->rem_port; } } dcc_host_unlock(); } } /* start a process to wait for the domain name system or other * hostname system to get the IP addresses of our flooding peers */ u_char /* 1=finished 0=please wait */ flod_names_resolve_start(void) { FLOD_MMAP *mp; if (!flod_mmaps) return 0; /* wait for background job to finish */ if (!flod_names_resolve_ck()) return 0; /* we're finished if we have recent address for all of the names */ if (!DB_IS_TIME(got_hosts, FLOD_NAMES_RESOLVE_SECS)) return 1; got_hosts = db_time.tv_sec + FLOD_NAMES_RESOLVE_SECS; for (mp = flod_mmaps->mmaps; mp <= LAST(flod_mmaps->mmaps); ++mp) { mp->rem_su.sa.sa_family = AF_UNSPEC; mp->host_error = 0; } flod_mmap_sync(0, 1); if (!background) { TMSG(FLOD, "resolving hostnames in the foreground"); flod_names_resolve(); return 1; } resolve_hosts_pid = fork(); if (resolve_hosts_pid > 0) { /* check again soon */ RUSH_NEXT_FLODS_CK(); return 0; } if (resolve_hosts_pid == -1) { dcc_error_msg("fork(flood names resolve start): %s;" " fall back to foreground resolving", ERROR_STR()); flod_names_resolve(); return 1; } TMSG(FLOD, "resolving hostnames started"); /* close files and sockets to avoid interfering with parent */ db_close(-1); after_fork(); flod_names_resolve(); TMSG(FLOD, "resolving hostnames finished"); exit(0); } static void iflod_clear(IFLOD_INFO *ifp, u_char fail) /* 1=problems so delay restart */ { OFLOD_INFO *ofp; FLOD_MMAP *mp; ofp = ifp->ofp; if (fail && ofp != 0) { mp = ofp->mp; if (mp->itimers.retry_secs < FLOD_RETRY_SECS) mp->itimers.retry_secs = FLOD_RETRY_SECS; mp->itimers.retry = db_time.tv_sec + mp->itimers.retry_secs; if ((mp->flags & FLODMAP_FG_ACT) != 0) TMSG3_FLOD(ofp, "postpone restarting %s flood from" " %s for %d seconds", (mp->flags & FLODMAP_FG_SOCKS) ? "SOCKS" : (mp->flags & FLODMAP_FG_NAT) ? "NAT" : "auto-NAT", ofp_rem_str(ofp), mp->itimers.retry_secs); } /* do not close the socket here, because it may be a passive outgoing * stream that is being converted */ if (ifp->soc >= 0) --iflods.open; memset(ifp, 0, sizeof(*ifp)); ifp->soc = -1; if (iflods.open == 0 && oflods.open == 0 && flods_st != FLODS_ST_ON) oflods_clear(); } void PATTRIB(5,6) iflod_close(IFLOD_INFO *ifp, u_char fail, /* 1=already sick; more is no problem */ u_char complain, /* 1=error not just trace message */ u_char send_reason, const char *pat, ...) { struct { DCC_FLOD_POS last_pos; DCC_FLOD_RESP e; u_char null; /* to eat '\0' for e.msg */ } resp; void *wp; va_list args; OFLOD_INFO *ofp; struct linger nowait; int wlen; ofp = ifp->ofp; db_ptr2flod_pos(resp.e.end.pos, DCC_FLOD_POS_END); va_start(args, pat); /* Throw away the last byte of resp.e.end.msg because the too smart * by half gcc Fortify nonsense won't allow ISZ(resp.e.end.msg)+1. * That put the would the unwanted '\0' from vsnprintf() into * resp.null*/ wlen = vsnprintf(resp.e.end.msg, ISZ(resp.e.end.msg), pat, args); va_end(args); if (wlen > ISZ(resp.e.end.msg)) wlen = ISZ(resp.e.end.msg); wlen += FLOD_END_OVHD; /* If useful, prefix our final message with our final position * The peer will see our position and final operation as two * separate responses. */ if (memcmp(ifp->pos, ifp->pos_sent, ISZ(ifp->pos))) { memcpy(resp.last_pos, ifp->pos, ISZ(resp.last_pos)); wlen += ISZ(resp.last_pos); wp = &resp.last_pos; } else { wp = &resp.e; } if (send_reason) { rpt_err(ofp, !complain, 1, "stop incoming flood; %ssend '%s' to %s", fail ? "error, " : "", resp.e.end.msg, ifp_rem_str(ifp)); /* send the final status report to the sending flooder */ iflod_write(ifp, wp, wlen, "stop incoming flood", fail ? 2 : 1); } else { rpt_err(ofp, !complain, 1, "stop incoming flood; %s'%s'", fail ? "error, " : "", resp.e.end.msg); } if (ifp->soc >= 0) { if (stopint && !(ifp->flags & IFLOD_FG_FAST_LINGER)) { ifp->flags |= IFLOD_FG_FAST_LINGER; nowait.l_onoff = 1; nowait.l_linger = SHUTDOWN_DELAY; if (0 > setsockopt(ifp->soc, SOL_SOCKET, SO_LINGER, &nowait, sizeof(nowait)) && !fail) dcc_error_msg("setsockopt(SO_LINGER %s): %s", ifp_rem_str(ifp), ERROR_STR()); } if (0 > close(ifp->soc) && !fail) { if (errno == ECONNRESET) TMSG2_FLOD(ofp, "close(flood from %s): %s", ifp_rem_str(ifp), ERROR_STR()); else dcc_error_msg("close(flood from %s): %s", ifp_rem_str(ifp), ERROR_STR()); } } /* if this was not a new duplicate connection being discarded, * break the association with the outgoing stream */ if (ofp != 0 && ofp->ifp == ifp) { save_flod_cnts(ofp); ofp->ifp = 0; } iflod_clear(ifp, fail); } /* can close the incoming flood and so clear things */ static u_char /* 0=failed & should be or is closed */ iflod_write(IFLOD_INFO *ifp, void *buf, int buf_len, const char *type, /* string describing operation */ u_char close_it) /* 0=iflod_close() on error, */ { /* 1=complain, 2=ignore error */ int i; if (!(ifp->flags & IFLOD_FG_CONNECTED)) return 1; if (ifp->ofp && (ifp->ofp->o_opts.flags & FLOD_OPT_SOCKS)) { i = Rsend(ifp->soc, buf, buf_len, 0); } else { /* If we don't know the corresponding output stream because we * have not yet seen any authentication, we at least know the * connection did not involve SOCKS because we did not * originate it. */ i = send(ifp->soc, buf, buf_len, 0); } if (i == buf_len) { ifp->iflod_alive = db_time.tv_sec; return 1; } if (i < 0) { if (close_it == 0) { iflod_close(ifp, 1, 0, 0, "send(%s %s): %s", type, ifp_rem_str(ifp), ERROR_STR()); } else if (close_it == 1) { dcc_error_msg("send(%s %s): %s", type, ifp_rem_str(ifp), ERROR_STR()); } } else { if (close_it == 0) { iflod_close(ifp, 1, 0, 0, "send(%s %s)=%d not %d", type, ifp_rem_str(ifp), i, buf_len); } else if (close_it == 1) { dcc_error_msg("send(%s %s)=%d not %d", type, ifp_rem_str(ifp), i, buf_len); } } return 0; } /* send our current position to the peer * the peer must be well known so that ifp->ofp->mp!=0, usually * because (ifp->flags & IFLOD_FG_VERS_CK) * can close the incoming flood and so clear things */ int /* -1=fail 0=nothing to send, 1=sent */ iflod_send_pos(IFLOD_INFO *ifp, u_char force) /* say just anything */ { DCC_FLOD_POS req; OFLOD_INFO *ofp; FLOD_MMAP *mp; ofp = ifp->ofp; mp = ofp->mp; /* ask peer to start over if our database has been cleared */ if (mp->flags & FLODMAP_FG_FFWD_IN) { mp->flags &= ~(FLODMAP_FG_FFWD_IN | FLODMAP_FG_NEED_REWIND); memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent)); db_ptr2flod_pos(req, DCC_FLOD_POS_FFWD_IN); dcc_trace_msg("ask %s to FFWD flood to us", ifp_rem_str(ifp)); if (!iflod_write(ifp, req, sizeof(req), "ffwd request", 0)) return -1; return 1; } if (mp->flags & FLODMAP_FG_NEED_REWIND) { mp->flags &= ~FLODMAP_FG_NEED_REWIND; memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent)); db_ptr2flod_pos(req, DCC_FLOD_POS_REWIND); dcc_trace_msg("ask %s to rewind flood to us", ifp_rem_str(ifp)); if (!iflod_write(ifp, req, sizeof(req), "rewind request", 0)) return -1; return 1; } if ((force && flod_pos2db_ptr(ifp->pos) >= DCC_FLOD_POS_MIN) || memcmp(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent))) { memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent)); if (!iflod_write(ifp, ifp->pos_sent, sizeof(ifp->pos_sent), "confirmed pos", 0)) return -1; /* reset the no-connection-from-peer complaint delay */ mp->itimers.msg_secs = FLOD_IN_COMPLAIN1; mp->itimers.msg = db_time.tv_sec + FLOD_IN_COMPLAIN1; /* things are going well, so forget old input complaints */ if (!(mp->flags & FLODMAP_FG_IN_SRVR)) { mp->iflod_err.msg[0] = '\0'; mp->iflod_err.trace_msg[0] = '\0'; } /* limit the backoff for outgoing connection attempts * while the incoming connection is working */ DB_ADJ_TIMER(&mp->otimers.retry, &mp->otimers.retry_secs, FLOD_RETRY_SECS); return 1; } /* Say just anything if we are doing a keepalive probe before * any checksums have been sent by the peer and so before we * have a position to confirm. */ if (force) { DCC_FLOD_RESP buf; db_ptr2flod_pos(buf.note.op, DCC_FLOD_POS_NOTE); strcpy(buf.note.str, "are you there?"); buf.note.len = sizeof("are you there?") + FLOD_NOTE_OVHD; TMSG1_FLOD(ofp, "flood note to %s: \"are you there?\"", ifp_rem_str(ifp)); if (!iflod_write(ifp, &buf.note, buf.note.len, buf.note.str, 0)) return -1; return 1; } return 0; } void iflod_listen_close(SRVR_SOC *sp) { if (sp->listen < 0) return; TMSG1(FLOD, "stop flood listening on %s", dcc_su2str_err(&sp->su)); if (0 > close(sp->listen)) TMSG2(FLOD, "close(flood listen on %s): %s", dcc_su2str_err(&sp->su), ERROR_STR()); sp->listen = -1; } /* send stop requests to DCC servers flooding to us * can close the incoming flood and so clear things */ void iflods_stop(const char *reason, u_char force) /* 1=now */ { SRVR_SOC *sp; IFLOD_INFO *ifp; DCC_FLOD_POS end_req; /* stop listening for new connections */ for (sp = srvr_socs; sp; sp = sp->fwd) { iflod_listen_close(sp); } for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) { if (ifp->soc < 0) continue; /* start shutting down each real, still alive connection */ if (!(ifp->flags & IFLOD_FG_END_REQ) && (ifp->flags & IFLOD_FG_VERS_CK)) { if (!reason || !*reason) rpt_err(ifp->ofp, 1, 1, "flood from %s stopping", ifp_rem_str(ifp)); else rpt_err(ifp->ofp, 1, 1, "flood from %s stopping: '%s'", ifp_rem_str(ifp), reason); /* send any delay position and then a stop request */ if (0 <= iflod_send_pos(ifp, 0)) { db_ptr2flod_pos(end_req, DCC_FLOD_POS_END_REQ); iflod_write(ifp, end_req, sizeof(end_req), "flood stop req", 0); ifp->flags |= IFLOD_FG_END_REQ; } /* done if the socket died */ if (ifp->soc < 0) continue; } /* break the connection if forced or never authenticated */ if (force || !(ifp->flags & IFLOD_FG_VERS_CK)) { if (!reason || !*reason) iflod_close(ifp, 1, 0, 1, "flooding off at %s", our_hostname); else iflod_close(ifp, 1, 0, 1, "flooding off at %s; %s", our_hostname, reason); continue; } /* break the conneciton if the peer is too slow */ if ((ifp->flags & IFLOD_FG_END_REQ) && IFP_DEAD(ifp, stopint ? SHUTDOWN_DELAY : KEEPALIVE_IN_STOP)) { if (!reason || !*reason) iflod_close(ifp, 1, 0, 1, DCC_FLOD_OK_STR" force close from" " %s", ifp_rem_str(ifp)); else iflod_close(ifp, 1, 0, 1, DCC_FLOD_OK_STR" force close from" " %s; %s", ifp_rem_str(ifp), reason); continue; } } } /* start receiving checksums from another DCC server */ void iflod_start(SRVR_SOC *sp) { IFLOD_INFO *ifp; DCC_SOCKLEN_T l; struct in6_addr peer_addr; int count; const struct in6_addr *cap; RL *rl; /* accept all waiting connections to avoid starving any */ for (count = 0; count <= DCCD_MAX_FLOODS; ++count) { /* find a free input flooding slot */ for (ifp = iflods.infos; ifp->soc >= 0; ++ifp) { if (ifp > LAST(iflods.infos)) { if (!(complained_many_iflods++)) dcc_error_msg("too many floods"); goto again; } } l = sizeof(ifp->rem_su); ifp->soc = accept(sp->listen, &ifp->rem_su.sa, &l); if (ifp->soc < 0) { if (!DCC_BLOCK_ERROR() || count == 0) dcc_error_msg("accept(flood): %s", ERROR_STR()); return; } /* use the IP address as the host name until we know which * peer it is */ dcc_su2str2(ifp->rem_hostname, sizeof(ifp->rem_hostname), &ifp->rem_su); if (!set_flod_socket(0, 1, ifp->soc, ifp->rem_hostname, &ifp->rem_su)) { close(ifp->soc); ifp->soc = -1; continue; } /* quietly forget this peer if it is blacklisted */ if (ifp->rem_su.sa.sa_family == AF_INET6) { cap = &ifp->rem_su.ipv6.sin6_addr; } else { dcc_ipv4toipv6(&peer_addr, ifp->rem_su.ipv4.sin_addr); cap = &peer_addr; } rl = 0; if (ck_ip_bl(&rl, DCC_ID_SRVR_ROGUE, cap)) { char buf[120]; int len; rl_inc(rl, &rl_anon_rate); len = snprintf(buf, sizeof(buf)-1, "rejected blacklisted flood from %s", ifp_rem_str(ifp)); if (len > ISZ(buf)) len = sizeof(buf); if ((rl->d.flags & RL_FG_TRACE) || (dccd_tracemask & DCC_TRACE_FLOD_BIT)) dcc_trace_msg(buf); buf[len++] = '\n'; send(ifp->soc, buf, len, 0); close(ifp->soc); ifp->soc = -1; continue; } /* reset timer that delays responses to clients while flooding * is stopped */ if (++iflods.open == 1) iflods_ok_timer = db_time.tv_sec + IFLODS_OK_SECS; ifp->flags |= IFLOD_FG_CONNECTED; ifp->iflod_alive = db_time.tv_sec; TMSG1(FLOD, "start flood from %s", ifp_rem_str(ifp)); again:; } } static void iflod_socks_backoff(OFLOD_INFO *ofp) { FLOD_MMAP *mp; mp = ofp->mp; mp->itimers.retry_secs *= 2; if (mp->itimers.retry_secs > FLOD_MAX_RETRY_SECS) mp->itimers.retry_secs = FLOD_MAX_RETRY_SECS; else if (mp->itimers.retry_secs < FLOD_RETRY_SECS) mp->itimers.retry_secs = FLOD_RETRY_SECS; } /* Start an incoming SOCKS flood by connecting to the other system. * We will eventually turn the connection around and pretend that * the other system initiated the TCP connection. * This can close the flood and so clear things */ static int /* -1=failure, 0=not yet, 1=done */ iflod_socks_connect(IFLOD_INFO *ifp) { OFLOD_INFO *ofp; DCC_SRVR_ID id; DCC_FLOD_VERSION_HDR buf; DCC_FNM_LNO_BUF fnm_buf; const char *emsg; int i; ofp = ifp->ofp; memset(&buf, 0, sizeof(buf)); strcpy(buf.body.str, version_str(ofp)); id = htons(my_srvr_id); memcpy(buf.body.sender_srvr_id, &id, sizeof(buf.body.sender_srvr_id)); buf.body.turn = 1; emsg = flod_sign(ofp, 1, &buf, sizeof(buf)); if (emsg) { iflod_socks_backoff(ofp); iflod_close(ifp, 1, 1, 1, "%s %d%s", emsg, ofp->out_passwd_id, fnm_lno(&fnm_buf, flod_path, ofp->lno)); return -1; } if (ofp->o_opts.flags & FLOD_OPT_SOCKS) { i = Rconnect(ifp->soc, &ifp->rem_su.sa, DCC_SU_LEN(&ifp->rem_su)); } else { /* must be NAT or assumed NAT */ i = connect(ifp->soc, &ifp->rem_su.sa, DCC_SU_LEN(&ifp->rem_su)); } if (0 > i && errno != EISCONN) { if (errno == EAGAIN || errno == EINPROGRESS || errno == EALREADY) { rpt_err(ofp, 1, 0, "starting flood from %s", ifp_rem_str(ifp)); return 0; } /* it is lame to only trace instead of reporting EINVAL as * an error, but several UNIX-like systems return EINVAL for * the second connect() after a Unreachable ICMP message * or after a timeout */ rpt_err(ofp, (errno == EINVAL || errno == ECONNABORTED || errno == ECONNRESET || errno == ETIMEDOUT || errno == ECONNREFUSED), 1, "connect(SOCKS FLOD %s): %s", ifp_rem_str(ifp), errno == EINVAL ? "likely connection refused or local firewall" : ERROR_STR()); close(ifp->soc); iflod_socks_backoff(ofp); iflod_clear(ifp, 1); return -1; } ifp->flags |= (IFLOD_FG_CONNECTED | IFLOD_FG_CLIENT); rpt_err(ofp, 1, 1, "starting SOCKS from %s", ifp_rem_str(ifp)); /* After the SOCKS incoming flood socket is connected, * send our authentication to convince the peer to send its * authentication and then its checksums. */ if (!iflod_write(ifp, &buf, sizeof(buf), "flood SOCKS authentication", 0)) return -1; return 1; } /* Request the start of an input flood via SOCKS if it is not already flowing, * by connecting to the remote system. * This can close the flood and so clear things */ void iflod_socks_start(OFLOD_INFO *ofp) { DCC_FNM_LNO_BUF fnm_buf; IFLOD_INFO *ifp, *ifp1; /* do nothing if it is already running or is not using SOCKS */ if (ofp->ifp || (ofp->mp->flags & (FLODMAP_FG_SOCKS | FLODMAP_FG_NAT)) == 0 || IFLOD_OPT_OFF_ROGUE(ofp)) return; if (!DB_IS_TIME(ofp->mp->itimers.retry, ofp->mp->itimers.retry_secs)) return; /* look for a free slot or an existing slot for the incoming flood */ ifp = 0; for (ifp1 = iflods.infos; ifp1 <= LAST(iflods.infos); ++ifp1) { if (ifp1->soc < 0) { if (!ifp) ifp = ifp1; } /* there is nothing to do if it already exists */ if (ifp1->ofp == ofp) return; } if (!ifp) { rpt_err(ofp, ++complained_many_iflods == 1 ? 0 : 2, 1, "too many incoming floods to start SOCKS from %s", ofp->rem_hostname); iflod_socks_backoff(ofp); return; } if (!flod_names_resolve_start()) return; /* wait for name resolution */ if (ofp->mp->rem_su.sa.sa_family == AF_UNSPEC) { rpt_err(ofp, 0, 1, "SOCKS flood peer name %s: %s%s", ofp->rem_hostname, DCC_HSTRERROR(ofp->mp->host_error), fnm_lno(&fnm_buf, flod_path, ofp->lno)); iflod_socks_backoff(ofp); return; } if (!LITCMP(ofp->mp->oflod_err.msg, "SOCKS flood peer name ")) ofp->mp->oflod_err.msg[0] = '\0'; ifp->ofp = ofp; ifp->rem_su = ofp->rem_su = ofp->mp->rem_su; STRLCPY(ifp->rem_hostname, ofp->rem_hostname, sizeof(ifp->rem_hostname)); ifp->soc = socket(ifp->rem_su.sa.sa_family, SOCK_STREAM, 0); if (ifp->soc < 0) { rpt_err(ofp, 0, 1, "socket(SOCKS flood %s): %s", ifp_rem_str(ifp), ERROR_STR()); iflod_socks_backoff(ofp); return; } if (!set_flod_socket(ofp, 1, ifp->soc, ifp->rem_hostname, &ifp->rem_su)) { close(ifp->soc); ifp->soc = -1; iflod_socks_backoff(ofp); return; } /* reset timer that delays responses to clients while flooding is * not working */ if (++iflods.open == 1) iflods_ok_timer = db_time.tv_sec + IFLODS_OK_SECS; iflod_socks_connect(ifp); } /* See if the new report is a duplicate of an old record in the database * db_sts.rcd.d points to the old record */ static int /* -1=continue, 0=not dup, 1=dup */ ck_dup_rcd(IFLOD_INFO *ifp, const DB_RCD *new, DCC_TGTS new_tgts_raw) { DCC_TGTS old_tgts; const DB_RCD_CK *new_ck, *old_ck; int new_num_cks, old_num_cks; int new_unique, old_unique; /* ignore reports for deleted checksums * unless they are new reports or delete requests */ old_tgts = DB_TGTS_RCD_RAW(db_sts.rcd.d.r); if (old_tgts == DCC_TGTS_DEL && new_tgts_raw != DCC_TGTS_DEL && !dcc_ts_newer_ts(&new->ts, &db_sts.rcd.d.r->ts)) { if (CK_FLOD_CNTERR(&ifp->ofp->lc.stale) && TMSG_FB2(ifp->ofp)) flod_cnterr(&ifp->ofp->lc.stale, "ignore deleted %s after %s", rpt_id("report", new, ifp), rpt_id(0, db_sts.rcd.d.r, 0)); return 1; } /* not duplicate if the server-IDs or timestamps differ */ if (DB_RCD_ID(new) != DB_RCD_ID(db_sts.rcd.d.r) || memcmp(&new->ts, &db_sts.rcd.d.r->ts, sizeof(new->ts))) return -1; /* We know we have a duplicate * Stop looking if the old record has been deleted */ if (old_tgts == 0) return 0; /* look for the first real checksum in the new record */ for (new_num_cks = DB_NUM_CKS(new), new_ck = new->cks; new_num_cks != 0; --new_num_cks, ++new_ck) { if (DB_CK_TYPE(new_ck) != DCC_CK_FLOD_PATH) break; } /* do not even count duplicate server-ID declarations */ if (DB_CK_TYPE(new_ck) == DCC_CK_SRVR_ID) return 1; /* See if one record is a subset of the other */ new_unique = 0; old_unique = 0; old_num_cks = DB_NUM_CKS(db_sts.rcd.d.r); old_ck = db_sts.rcd.d.r->cks; while (old_num_cks != 0 && new_num_cks != 0) { if (DB_CK_TYPE(old_ck) == DCC_CK_FLOD_PATH) { /* skip paths in the old record */ ++old_ck; --old_num_cks; } else if (DB_CK_TYPE(old_ck) == DB_CK_TYPE(new_ck)) { /* skip identical checksums */ ++old_ck; --old_num_cks; ++new_ck; --new_num_cks; } else if (DB_CK_TYPE(old_ck) < DB_CK_TYPE(new_ck)) { /* skip unique checksum in the old record, * using the ordering of checksums in records */ ++old_unique; ++old_ck; --old_num_cks; } else { /* skip unique checksum in the new record */ ++new_unique; ++new_ck; --new_num_cks; } } /* forget the new record if it has nothing unique */ if (new_unique+new_num_cks == 0) { if (CK_FLOD_CNTERR(&ifp->ofp->lc.dup) && TMSG_FB2(ifp->ofp)) flod_cnterr(&ifp->ofp->lc.dup, "%sduplicate %s", old_unique+old_num_cks == 0 ? "" : "subset ", rpt_id("report", new, ifp)); return 1; } /* Keep both records if each has unique checksums * This inflates the count for common checksums, so hope it * is rare. */ if (old_unique+old_num_cks != 0) { if (CK_FLOD_CNTERR(&ifp->ofp->lc.dup) && TMSG_FB2(ifp->ofp)) flod_cnterr(&ifp->ofp->lc.dup, "partial duplicate %s", rpt_id("report", new, ifp)); return 0; } /* Delete the original report if it has nothing unique * This results in doubling the contribution to the total for * each checksum from this report in our database until the * next time dbclean is run. At worst this could push a * DCC client over its bulk threshold */ DB_TGTS_RCD_SET(db_sts.rcd.d.r, 0); SET_FLUSH_RCD_HDR(&db_sts.rcd, 1); if (CK_FLOD_CNTERR(&ifp->ofp->lc.dup) && TMSG_FB2(ifp->ofp)) flod_cnterr(&ifp->ofp->lc.dup, "superset duplicate %s", rpt_id("report", new, ifp)); return 0; } /* see if the new record is a duplicate of any existing records containing * the specified checksum * use db_sts.rcd for the initial record and the chain */ static int /* -1=fail, 0=not dup, 1=duplicate */ ck_dup_ck_chain(IFLOD_INFO *ifp, const DB_RCD *new, DCC_TGTS new_tgts_raw, DCC_CK_TYPES type, const DB_RCD_CK *found_ck) { DB_PTR next_rcd_pos; int cnt, i; for (cnt = 0; ; ++cnt) { i = ck_dup_rcd(ifp, new, new_tgts_raw); if (i >= 0) { if (cnt >= 50 && (dccd_tracemask & DCC_TRACE_DB_BIT)) dcc_trace_msg("long duplicate chain of %d" " from %s at %s", cnt, ifp->rem_hostname, rpt_id("report",new,ifp)); return i; } next_rcd_pos = DB_PTR_EX(found_ck->prev); if (next_rcd_pos == DB_PTR_NULL) return 0; if (next_rcd_pos >= db_sts.rcd.s.rptr) { /* prevent loops */ db_error_msg(__LINE__,__FILE__, "bad %s link of "L_HPAT" at "L_HPAT, DB_TYPE2STR(type), db_sts.rcd.s.rptr, next_rcd_pos); return -1; } found_ck = db_map_rcd_ck(dcc_emsg, &db_sts.rcd, next_rcd_pos, type); if (!found_ck) { iflod_close(ifp, 1, 1, 1, "%s", dcc_emsg); DB_ERROR_MSG(dcc_emsg); return -1; } } } /* complain about a received flooded report, * and possibly close and so clear things */ static u_char PATTRIB(6,7) /* 0=flood closed */ iflod_rpt_complain(IFLOD_INFO *ifp, const DB_RCD *new, /* complain about this report */ u_char serious, /* 0=send mere note, 1=send complaint */ FLOD_LIMCNT *lc, /* limit complaints with this */ const char *str, /* type of report */ const char *pat,...) /* the complaint */ { DCC_FLOD_RESP buf; const char *sc; va_list args; int i, len; if (!lc && ifp->ofp) lc = &ifp->ofp->lc.iflod_bad; if (!lc) { sc = ""; } else { i = ++lc->cur - (lc->lim + FLOD_LIM_COMPLAINTS); if (i > 0) return 1; sc = i < 0 ? "" : "; stop complaints"; } va_start(args, pat); len = vsnprintf(buf.note.str, sizeof(buf.note.str), pat, args); if (len >= ISZ(buf.note.str)) len = sizeof(buf.note.str)-1; va_end(args); if (serious) { dcc_error_msg("%s %s%s", buf.note.str, rpt_id(str, new, ifp), sc); db_ptr2flod_pos(buf.note.op, DCC_FLOD_POS_COMPLAINT); } else { TMSG3_FLOD2(ifp->ofp, "%s %s%s", buf.note.str, rpt_id(str, new, ifp), sc); db_ptr2flod_pos(buf.note.op, DCC_FLOD_POS_NOTE); } len += snprintf(&buf.note.str[len], sizeof(buf.note.str)-len, " %s%s", rpt_id(str, new, 0), sc); if (len >= ISZ(buf.note.str)) len = ISZ(buf.note.str)-1; buf.note.len = len+1 + FLOD_NOTE_OVHD; return iflod_write(ifp, &buf, buf.note.len, buf.note.str, 0); } static u_char parse_srvr_id(const DB_RCD_CK *ck, const IFLOD_INFO *ifp, const DB_RCD *new, DCC_SRVR_ID srvr_id) { DCC_SRVR_ID tgt_id, type_id; char buf1[24]; OPT_FLAGS opt_flags; const OFLOD_INFO *ofp1; ID_TBL *tp; /* notice server-ID type announcements */ tgt_id = (ck->sum[1] << 8) + ck->sum[2]; type_id = srvr_id; switch (type_id) { case DCC_ID_SRVR_REP_OK: type_id = DCC_ID_SRVR_SIMPLE; opt_flags = 0; break; case DCC_ID_SRVR_SIMPLE: opt_flags = FLOD_OPT_SIMPLE; break; case DCC_ID_SRVR_IGNORE: opt_flags = 0; break; case DCC_ID_SRVR_ROGUE: opt_flags = FLOD_OPT_ROGUE; break; default: return 0; } if (ck->sum[0] != DCC_CK_SRVR_ID) { return 0; } tp = find_srvr_type(tgt_id); /* Restart flooding if the announced type of a peer changes. */ for (ofp1 = oflods.infos; ofp1 <= LAST(oflods.infos); ++ofp1) { /* Wait a bit for more announcements before restarting * flooding. When we restart flooding, we will * check the database. */ if (ofp1->rem_id == tgt_id) { if (opt_flags != (ofp1->o_opts.flags & (FLOD_OPT_ROGUE | FLOD_OPT_SIMPLE))) { if (TMSG_FB(ifp->ofp) || TMSG_FB(ofp1)) dcc_trace_msg("server-ID %d for %s" " changed to \"%s\"" " in %s", tgt_id, ofp1->rem_hostname, id2str(buf1, sizeof(buf1), type_id), rpt_id("report",new,ifp)); if (flod_mtime > 1) flod_mtime = 1; } break; } } /* accept changes to our records */ tp->srvr_type = type_id; return 1; } /* consider an incoming flooded report */ static int /* -1=failed, 0=not yet, else length */ iflod_rpt(IFLOD_INFO *ifp, OFLOD_INFO *ofp, const DCC_FLOD_STREAM *stream, int max_len) { DB_PTR pos; DCC_TGTS new_tgts, found_tgts; DB_RCD new; DCC_SRVR_ID old_srvr, psrvr; const DCC_CK *ck_lim, *ck; const DB_RCD_CK *new_ck_lim, *srvr_id_ck; DB_RCD_CK *found_ck, *new_ck; DCC_CK_TYPES type, prev_type; DCC_FLOD_PATH_ID *new_path_id, *old_path_id; int num_path_blocks; char tgts_buf[DCC_XHDR_MAX_TGTS_LEN]; int ok2; int rpt_len; u_char stale; ID_MAP_RESULT srvr_mapped; ID_TBL *tp; int i; pos = flod_pos2db_ptr(stream->r.pos); if (pos < DCC_FLOD_POS_MIN) { iflod_close(ifp, 1, 1, 1, "bogus position "L_HPAT" in flooded report #%d", pos, ofp->cnts.total); return -1; } /* wait for the header of the report */ if (max_len < DCC_FLOD_RPT_LEN(0)) { return 0; } if (stream->r.num_cks == 0 || stream->r.num_cks > DCC_QUERY_MAX) { iflod_close(ifp, 1, 1, 1, "impossible %d checksums in report #%d", stream->r.num_cks, ofp->cnts.total); return -1; } rpt_len = DCC_FLOD_RPT_LEN(stream->r.num_cks); if (rpt_len > max_len) return 0; /* wait for more */ if (db_failed_line) return rpt_len; /* can do nothing if database broken */ /* save the position to return to the sender */ memcpy(ifp->pos, stream->r.pos, sizeof(ifp->pos)); new.ts = stream->r.ts; memcpy(&new.srvr_id_auth, stream->r.srvr_id_auth, sizeof(new.srvr_id_auth)); old_srvr = ntohs(new.srvr_id_auth) & ~DCC_SRVR_ID_AUTH; new.srvr_id_auth = old_srvr; new.fgs_num_cks = 0; memcpy(&new_tgts, stream->r.tgts, sizeof(new_tgts)); new_tgts = ntohl(new_tgts); if (new_tgts == DCC_TGTS_DEL) { if (!(ofp->i_opts.flags & FLOD_OPT_DEL_OK)) { if (!iflod_rpt_complain(ifp, &new, 1, &ofp->lc.not_deleted, "delete request", "refuse")) return -1; return rpt_len; } } else if (new_tgts == 0 || (new_tgts > DCC_TGTS_FLOD_RPT_MAX && new_tgts != DCC_TGTS_TOO_MANY)) { iflod_close(ifp, 1, 1, 1, "bogus target count %s in %s", dcc_tgts2str(tgts_buf, sizeof(tgts_buf), new_tgts, grey_on), rpt_id("report", &new, 0)); return -1; } else if (ofp->i_opts.flags & FLOD_OPT_TRAPS) { /* comply if the source watches only spam traps */ new_tgts = DCC_TGTS_TOO_MANY; } /* notice reports from the distant future */ if (dcc_ts_newer_ts(&new.ts, &future)) { if (!iflod_rpt_complain(ifp, &new, 1, &ofp->lc.stale, "report", "future")) return -1; return rpt_len; } DB_TGTS_RCD_SET(&new, new_tgts); new.fgs_num_cks = 0; srvr_id_ck = 0; stale = 1; ck_lim = &stream->r.cks[stream->r.num_cks]; new_ck = new.cks; num_path_blocks = 0; tp = 0; srvr_mapped = id_map(old_srvr, &ofp->i_opts); switch (srvr_mapped) { case ID_MAP_NO: tp = find_srvr_type(old_srvr); break; case ID_MAP_REJ: if (!iflod_rpt_complain(ifp, &new, 0, 0, "rejected server-ID in", "refuse")) return -1; return rpt_len; case ID_MAP_SELF: new.srvr_id_auth = my_srvr_id; /* create path pointing to ourself if we translate the ID */ memset(new_ck, 0, sizeof(*new_ck)); new_ck->type_fgs = DCC_CK_FLOD_PATH; new_path_id = (DCC_FLOD_PATH_ID *)new_ck->sum; /* start the path with the ID of the previous hop because * we know it is defined */ new_path_id->hi = ofp->rem_id>>8; new_path_id->lo = ofp->rem_id; new.fgs_num_cks = 1; ++new_ck; break; } for (prev_type = DCC_CK_INVALID, ck = stream->r.cks; ck < ck_lim; prev_type = type, ++ck) { type = ck->type; if (!DCC_CK_OK_FLOD(grey_on, type)) { if (!iflod_rpt_complain(ifp, &new, 1, 0, "report", "unknown checksum type %s in", DB_TYPE2STR(type))) return -1; continue; } if (ck->len != sizeof(*ck)) { iflod_close(ifp, 1, 1, 1, "unknown checksum length %d in %s", ck->len, rpt_id("report", &new, 0)); return -1; } if (type <= prev_type && prev_type != DCC_CK_FLOD_PATH) { if (!iflod_rpt_complain(ifp, &new, 1, 0, "report", "out of order %s checksum in", DB_TYPE2STR(type))) return -1; return rpt_len; } new_ck->type_fgs = type; new_ck->prev = DB_PTR_CP(DB_PTR_NULL); memcpy(new_ck->sum, ck->sum, sizeof(new_ck->sum)); if (type == DCC_CK_FLOD_PATH) { /* discard report if path is too long */ if (++num_path_blocks > DCC_MAX_FLOD_PATH_CKSUMS) { TMSG2_FLOD(ofp, "%d path blocks in %s", num_path_blocks, rpt_id("report", &new, ifp)); return rpt_len; } /* don't add this path if we translated the origin */ if (srvr_mapped == ID_MAP_SELF) continue; old_path_id = (DCC_FLOD_PATH_ID *)ck->sum; new_path_id = old_path_id; for (i = 0; i < DCC_NUM_FLOD_PATH; ++i, ++old_path_id) { psrvr = (old_path_id->hi<<8) | old_path_id->lo; if (psrvr == DCC_ID_INVALID) break; /* end of path */ switch (id_map(psrvr, &ofp->i_opts)) { case ID_MAP_NO: case ID_MAP_REJ: break; case ID_MAP_SELF: psrvr = my_srvr_id; break; } new_path_id->hi = psrvr>>8; new_path_id->lo = psrvr; ++new_path_id; } } else { /* discard this checksum if we would not have kept * it if we had received the original report * and either its server-ID is translated * or it is not kept by default */ if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type) && (srvr_mapped == ID_MAP_SELF || DB_GLOBAL_NOKEEP(grey_on, type))) continue; /* server-ID declarations are never stale */ if (type == DCC_CK_SRVR_ID) { stale = 0; srvr_id_ck = new_ck; } /* Notice if this checksum makes the report timely * We cannot detect duplicates of reports that * have expired, so consider stale anything older * than our expiration. * Ignore reports of checksums from crazy servers */ if (stale && dcc_ts_newer_ts(&new.ts, new_tgts >= db_tholds[type] ? &db_parms.ex_spam[type] : &db_parms.ex_all[type]) && (tp == 0 || (tp->srvr_type != DCC_ID_SRVR_IGNORE && tp->srvr_type != DCC_ID_SRVR_ROGUE))) stale = 0; } ++new_ck; ++new.fgs_num_cks; } if (stale) { if (CK_FLOD_CNTERR(&ofp->lc.stale) && TMSG_FB2(ofp)) flod_cnterr(&ofp->lc.stale, "stale %s", rpt_id("report", &new, ifp)); return rpt_len; } if (!DB_NUM_CKS(&new)) { iflod_close(ifp, 1, 1, 1, "no known checksum types in %s", rpt_id("report", &new, 0)); return -1; } /* only now might we look at the database */ if (db_lock() < 0) { iflod_close(ifp, 1, 1, 1, "iflod lock failure"); return -1; } /* See if the report is a duplicate. * Check all of the checksums to find one that is absent or * the one with the smallest total to minimize the number * of reports we must check to see if this is a duplicate */ ok2 = 0; new_ck_lim = &new.cks[DB_NUM_CKS(&new)]; for (new_ck = new.cks; new_ck < new_ck_lim; ++new_ck) { type = DB_CK_TYPE(new_ck); if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type)) continue; switch (db_lookup(dcc_emsg, type, new_ck->sum, 0, MAX_HASH_ENTRIES, &db_sts.hash, &db_sts.rcd, &found_ck)) { case DB_FOUND_LATER: case DB_FOUND_SYSERR: iflod_close(ifp, 1, 1, 1, "%s", dcc_emsg); DB_ERROR_MSG(dcc_emsg); return -1; case DB_FOUND_IT: /* At least this checksum is already in the database */ i = ck_dup_ck_chain(ifp, &new, new_tgts, type, found_ck); if (i < 0) return -1; /* broken database */ if (i > 0) return rpt_len; /* duplicate */ /* Maybe not a duplicate. * Notice reports of checksums on the local server's * whitelist. * An ordinary checksum is whitelisted by DCC_TGTS_OK * or two reports with DCC_TGTS_OK2. * Greylisting uses DCC_TGTS_GREY_WHITE=DCC_TGTS_OK2 * and so one report of DCC_TGTS_GREY_WHITE is enough */ found_tgts = DB_TGTS_CK(found_ck); if (found_tgts == DCC_TGTS_OK || (found_tgts == DCC_TGTS_GREY_WHITE && (++ok2 >= 2 || grey_on))) { if (!iflod_rpt_complain(ifp, &new, 0, &ofp->lc.wlist, "report","whitelisted")) return -1; return rpt_len; } break; case DB_FOUND_EMPTY: case DB_FOUND_CHAIN: case DB_FOUND_INTRUDER: /* We will fail to find this checksum in our database * if the new report is not a duplicate * or if it is a duplicate superset report */ break; } } /* If the new report is a delete request, * then we need to run dbclean to fix all of the * totals affected by the deleted reports. */ if (new_tgts == DCC_TGTS_DEL) { if (!(ofp->i_opts.flags & FLOD_OPT_NO_LOG_DEL)) dcc_trace_msg("accept %s", rpt_id("delete request", &new, ifp)); if (!DCC_CK_IS_REP_OP(grey_on, type) && !grey_on) need_del_dbclean = "flood checksum deletion"; } if (srvr_id_ck) { /* discard translated server-ID declarations */ if (srvr_mapped == ID_MAP_SELF) { TMSG2_FLOD(ofp, "translated server-ID from %d in %s", old_srvr, rpt_id("report", &new, ifp)); return rpt_len; } /* notice claims by other servers to our ID */ if (old_srvr == my_srvr_id) { if (memcmp(host_id_sum, srvr_id_ck->sum, sizeof(host_id_sum))) dcc_error_msg("host %s used our server-ID" " %d at %s", dcc_ck2str_err(DCC_CK_SRVR_ID, srvr_id_ck->sum, 0), my_srvr_id, ts2str_err(&new.ts)); return rpt_len; } if (old_srvr < DCC_SRVR_ID_MIN && !parse_srvr_id(srvr_id_ck, ifp, &new, old_srvr)) return rpt_len; } /* the report is ok and not a duplicate, so add it to our database */ if (!add_dly_rcd(&new, 1)) { iflod_close(ifp, 1, 1, 1, "%s", dcc_emsg); return -1; } ++ofp->cnts.accepted; return rpt_len; } static void bad_vers(IFLOD_INFO *ifp, u_char fail) /* 1=complain */ { iflod_close(ifp, fail, fail, 1, DCC_FLOD_BAD_VER_MSG" need \"" DCC_FLOD_VERSION_CUR_STR "\" not \"%.*s\"", LITZ(DCC_FLOD_VERSION_STR_BASE)+10, ifp->ibuf.s.v.body.str); } /* authenticate and otherwise check a new incoming flood */ static u_char /* 0=closed or switched to output */ check_iflod_vers(IFLOD_INFO *ifp) { DCC_FNM_LNO_BUF fnm_buf; const DCC_FLOD_VERSION_HDR *vp; OFLOD_INFO *ofp; IFLOD_INFO *ifp1; const ID_TBL *tp; DCC_SRVR_ID rem_id; int iversion; int i; vp = &ifp->ibuf.s.v; if (!strcmp(vp->body.str, DCC_FLOD_VERSION_CUR_STR)) { iversion = DCC_FLOD_VERSION_CUR; ifp->flags |= IFLOD_FG_VERS_CK; #ifdef DCC_FLOD_VERSION7 } else if (!strcmp(vp->body.str, DCC_FLOD_VERSION7_STR)) { iversion = DCC_FLOD_VERSION7; ifp->flags |= IFLOD_FG_VERS_CK; #endif /* DCC_FLOD_VERSION7 */ } else if (!strncmp(vp->body.str, DCC_FLOD_VERSION_STR_BASE, LITZ(DCC_FLOD_VERSION_STR_BASE))) { /* it seems to be a DCC server, * so complain after identifying the peer */ iversion = 1; } else { /* junk, so complain and give up */ bad_vers(ifp, 1); return 0; } /* require a sane and familiar server-ID from the prospective peer */ memcpy(&rem_id, vp->body.sender_srvr_id, sizeof(rem_id)); rem_id = ntohs(rem_id); if (rem_id < DCC_SRVR_ID_MIN || rem_id > DCC_SRVR_ID_MAX) { iflod_close(ifp, 1, 1, 1, DCC_FLOD_BAD_ID_MSG" %d", rem_id); return 0; } for (ofp = oflods.infos; ; ++ofp) { if (ofp > LAST(oflods.infos)) { iflod_close(ifp, 1, 1, 1, DCC_FLOD_BAD_ID_MSG" %d", rem_id); return 0; } if (ofp->rem_id == rem_id) { ifp->ofp = ofp; STRLCPY(ifp->rem_hostname, ofp->rem_hostname, sizeof(ifp->rem_hostname)); break; } } /* ofp and ofp->mp are not null, because we now know which peer * it claims to be * * check that it knows the password */ i = ck_sign(&tp, 0, ofp->in_passwd_id, vp, sizeof(*vp)); if (!i) { if (!tp) iflod_close(ifp, 1, 1, 1, DCC_FLOD_PASSWD_ID_MSG" %d%s", ofp->in_passwd_id, fnm_lno(&fnm_buf, flod_path, ofp->lno)); else iflod_close(ifp, 1, 1, 1, DCC_FLOD_BAD_AUTH_MSG" %d", ofp->in_passwd_id); return 0; } if (i == 1) ofp->mp->flags &= ~FLODMAP_FG_USE_2PASSWD; else ofp->mp->flags |= FLODMAP_FG_USE_2PASSWD; /* no more assumed NAT games because it has contacted us */ ofp->mp->flags &= ~FLODMAP_FG_NAT_AUTO; /* Note the version of the protocol it is using so that we can use that * version when connecting to it. */ ofp->mp->iversion = iversion; /* if we do not like its version, reject the connection and hope * that it will retry with a version we like */ if (!(ifp->flags & IFLOD_FG_VERS_CK)) { bad_vers(ifp, iversion != 0); return 0; } if (iversion != DCC_FLOD_VERSION_CUR) TMSG2_FLOD(ofp, "version %d from %s", iversion, ifp_rem_str(ifp)); /* convert to a passive output flood as requested by the peer * This works even if the peer is configured to use SOCKS or NAT * but we are not using PASSIVE */ if (vp->body.turn) { if (OFLOD_OPT_OFF_ROGUE(ofp)) { iflod_close(ifp, 1, 0, 1, "passive output flooding off from %s%s", ifp_rem_str(ifp), fnm_lno(&fnm_buf, flod_path, ofp->lno)); return 0; } /* We have a duplicate passive outgoing flood. * See whether the old stream has broken. */ if (ofp->soc >= 0) oflod_read(ofp); /* If we still have a duplicate and we sent a shutdown request, * assume the response got lost */ if (ofp->soc >= 0 && (ofp->flags & OFLOD_FG_SHUTDOWN_REQ)) { rpt_err(ofp, 1, 0, " assume response to shutdown lost from %s", ofp_rem_str(ofp)); oflod_close(ofp, 0); } if (ofp->soc >= 0) { /* We still have duplicates. * Reject the new one if the IP addresses differ */ if (!DCC_SU_EQ(&ofp->rem_su, &ifp->rem_su)) { iflod_close(ifp, 1, 0, 1, "reject duplicate passive output" " flood from %s", ifp_rem_str(ifp)); return 0; } rpt_err(ofp, 1, 0, "accept duplicate passive output flood from %s", ifp_rem_str(ifp)); oflod_close(ofp, 0); } ofp->soc = ifp->soc; ofp->rem_su = ifp->rem_su; ++oflods.open; TMSG1_FLOD(ofp, "convert incoming flood to passive outgoing to %s", ofp_rem_str(ofp)); iflod_clear(ifp, 0); ofp->mp->flags |= FLODMAP_FG_OUT_SRVR; if (!oflod_connect_fin(ofp)) oflod_close(ofp, 0); return 0; } if (IFLOD_OPT_OFF_ROGUE(ofp)) { iflod_close(ifp, 1, 0, 1, "flood from %s turned off%s", ifp_rem_str(ifp), fnm_lno(&fnm_buf, flod_path, ofp->lno)); return 0; } /* detect duplicate incoming floods */ for (ifp1 = iflods.infos; ifp1 <= LAST(iflods.infos); ++ifp1) { if (ifp1->ofp != ofp || ifp1 == ifp) continue; /* We have a duplicate. Either two servers are using the * same server-ID or the peer has restarted flooding without * our seeing a clean shutdown. * If socket is in CLOSE_WAIT, then sending something will fail * immediately. If the peer was rebooted, then sending will * not fail for at least a round trip time and possibly longer * if the peer has moved. * * Before trying to send anything, check for a FIN waiting * on the other socket */ for (i = 65536/FLOD_BUF_SIZE; i >= 0; --i) { if (iflod_read(ifp1)) break; } /* forget it if reading closed the other stream */ if (ifp1->ofp != ofp) break; /* assume the it is ok if we have sent an end request */ if (ifp1->flags & IFLOD_FG_END_REQ) { iflod_close(ifp1, 0, 0, 1, "missing end response;" " have replacement for flood from %s", ifp_rem_str(ifp1)); break; } /* assume we missed the shutdown * if the IP addresses are the same */ if (DCC_SU_EQ(&ifp->rem_su, &ifp1->rem_su)) { iflod_close(ifp1, 0, 0, 1, "assumed dead link;" " have replacement for flood from %s", ifp_rem_str(ifp1)); break; } /* assume we do not have a duplicate server-ID and switch to * the new connection if sending a position or note * fails immediately, */ if (0 >= iflod_send_pos(ifp1, 1)) { iflod_close(ifp1, 0, 0, 1, "have replacement for flood from %s", ifp_rem_str(ifp1)); break; } /* Otherwise, kill the new flood. If it was legitimate, * sending the note will eventually kill the old stream * and the peer will get through with it tries later */ iflod_close(ifp, 1, 1, 1, "duplicate flood from %s", ifp_rem_str(ifp)); return 0; } ofp->ifp = ifp; if (ifp->flags & IFLOD_FG_CLIENT) ofp->mp->flags &= ~FLODMAP_FG_IN_SRVR; else ofp->mp->flags |= FLODMAP_FG_IN_SRVR; save_flod_cnts(ofp); ifp->iflod_alive = db_time.tv_sec; /* Try to restart the corresponding output flood because a new * incoming flood might indicate that peer has awakened. * Kludge the backoff so that it does not increase. */ if (ofp->soc < 0 && !(ofp->o_opts.flags & FLOD_OPT_PASSIVE)) { ofp->mp->otimers.retry_secs /= 2; ofp->mp->otimers.retry = 0; oflod_open(ofp); } /* Send a rewind or fast forward request immediately if needed. * If not, send a keepalive message so that peer knows we have * accepted the connection and it can stop worrying about an * immediate rejection. */ if (0 > iflod_send_pos(ifp, 1)) return 0; return 1; } /* A new SOCKS incoming stream that we originated has been closed by the * peer without authenticating itself. It could have responded to our * authentication with an error message. */ static void parse_socks_error(IFLOD_INFO *ifp) { const DCC_FLOD_STREAM *stream; int i, msg_len; int fail; stream = (DCC_FLOD_STREAM *)&ifp->ibuf.b[0]; msg_len = ifp->ibuf_len - FLOD_END_OVHD; /* it must look like an end request with an entirely ASCII message */ if (flod_pos2db_ptr(stream->r.pos) != DCC_FLOD_POS_END || msg_len < 1 || msg_len > ISZ(stream->e.msg)) { iflod_close(ifp, 1, 1, 1, "SOCKS rejected with \"%.*s\"", msg_len, stream->e.msg); return; } for (i = 0; i < msg_len; ++i) { if (stream->e.msg[i] < ' ' || stream->e.msg[i] > '~') { iflod_close(ifp, 1, 1, 1, "SOCKS rejected with \"%.*s\"", msg_len, stream->e.msg); return; } } fail = oflod_parse_eof(ifp->ofp, 1, &stream->e, msg_len); if (fail <= 0) { iflod_socks_backoff(ifp->ofp); } else { /* try again immediately * with another protocol version or the 2nd password */ ifp->ofp->mp->itimers.retry = 0; } iflod_close(ifp, fail<=0, fail<=0, 0, "SOCKS rejected by %s with \"%.*s\"", ifp_rem_str(ifp), msg_len, stream->e.msg); } /* see what a distant flooder is telling us * can close the flood and so clear things */ u_char /* 1=kernel buffers empty */ iflod_read(IFLOD_INFO *ifp) { OFLOD_INFO *ofp; int off, req_len, recv_len; const DCC_FLOD_STREAM *stream; int len, i; /* if this is an incoming SOCKS or NAT stream that we originated, * and if Rconnect() said "not yet" when we first tried to connect, * then we must be here because select() says it is time to * try Rconnect() again to finish the connection */ if (!(ifp->flags & IFLOD_FG_CONNECTED) && iflod_socks_connect(ifp) <= 0) return 1; /* read only once before returning * to ensure we pay attention to other work */ req_len = sizeof(ifp->ibuf) - ifp->ibuf_len; ofp = ifp->ofp; if (ofp && (ofp->o_opts.flags & FLOD_OPT_SOCKS)) recv_len = Rrecv(ifp->soc, &ifp->ibuf.b[ifp->ibuf_len], req_len, 0); else recv_len = recv(ifp->soc, &ifp->ibuf.b[ifp->ibuf_len], req_len, 0); if (recv_len < 0) { /* If kernel ran out of data, stop for now. * Give up on an I/O error */ if (!DCC_BLOCK_ERROR()) { iflod_close(ifp, 1, 0, 0, "incoming flood recv(%s): %s", ifp_rem_str(ifp), ERROR_STR()); } return 1; } ifp->ibuf_len += recv_len; off = 0; /* deal with a new connection */ if (!(ifp->flags & IFLOD_FG_VERS_CK)) { if (ifp->ibuf_len >= ISZ(DCC_FLOD_VERSION_HDR)) { if (!check_iflod_vers(ifp)) return 1; /* stream closed or converted */ ofp = ifp->ofp; off = ISZ(DCC_FLOD_VERSION_HDR); } else if (recv_len != 0) { return 1; /* wait for rest of authentication */ } else if (ofp && ofp->mp && (ofp->mp->flags & FLODMAP_FG_ACT) != 0) { parse_socks_error(ifp); return 1; } else { iflod_close(ifp, 1, 1, 0, "garbage connection from %s", ifp_rem_str(ifp)); return 1; } } /* ofp != 0 because check_iflod_vers() has found the peer */ /* deal with the data */ dcc_timeval2ts(&future, &db_time, MAX_FLOD_CLOCK_SKEW); while ((len = ifp->ibuf_len - off) > 0) { stream = (DCC_FLOD_STREAM *)&ifp->ibuf.b[off]; if (len < ISZ(stream->r.pos)) break; /* need at least the position */ i = iflod_rpt(ifp, ofp, stream, len); if (i < 0) return 1; /* stream closed */ if (i == 0) break; /* wait for rest of report */ off += i; ++ofp->cnts.total; } /* save unprocessed bytes for next time */ if (off != 0) { ifp->ibuf_len -= off; if (ifp->ibuf_len < 0) dcc_logbad(EX_SOFTWARE, "ifp->ibuf_len=%d", ifp->ibuf_len); if (ifp->ibuf_len > 0) memmove(&ifp->ibuf.b[0], &ifp->ibuf.b[off], ifp->ibuf_len); } if (recv_len == 0) { /* We are at EOF and have processed all input that we can. */ if (ifp->ibuf_len != 0) { /* Something is wrong if any input remains, */ iflod_close(ifp, 1, 0, 1, "report %d truncated", ofp ? ofp->cnts.total : 0); } else if (flods_st != FLODS_ST_ON || (ofp && IFLOD_OPT_OFF_ROGUE(ofp))) { iflod_close(ifp, 0, 0, 1, DCC_FLOD_OK_STR"%s off", our_hostname); } else { iflod_close(ifp, 0, 0, 1, DCC_FLOD_OK_STR"%s off", ifp_rem_str(ifp)); } return 1; } /* things are going ok, so reset the SOCKS restart backoff * and the no-connection complaint */ ofp->mp->itimers.retry_secs = FLOD_SOCKS_SOCKS_IRETRY; ofp->mp->itimers.msg_secs = FLOD_IN_COMPLAIN1; ofp->mp->itimers.msg = db_time.tv_sec + FLOD_IN_COMPLAIN1; return (req_len > recv_len); } void iflods_listen(void) { SRVR_SOC *sp; DCC_SOCKU su; const DCC_SOCKU *sup = 0; int i, on; for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->flags & SRVR_SOC_ADDR) { /* need to open a TCP listen socket for incoming floods * for each explicitly configured IP address */ sup = &sp->su; } else if (sp->flags & SRVR_SOC_LISTEN) { /* need to open one TCP INADDR_ANY listen socket for * the first implicitly configured interface */ sup = dcc_mk_su(&su, sp->su.sa.sa_family, 0, sp->su.ipv6.sin6_port); } else { /* otherwise close unneeded socket */ iflod_listen_close(sp); continue; } if (sp->listen >= 0) continue; /* don't need to listen if there is no flooding */ if (!oflods.total) continue; TMSG1(FLOD, "start flood listening on %s", dcc_su2str_err(sup)); sp->listen = socket(sup->sa.sa_family, SOCK_STREAM, 0); if (sp->listen < 0) { dcc_error_msg("socket(flood listen %s): %s", dcc_su2str_err(sup), ERROR_STR()); continue; } if (-1 == fcntl(sp->listen, F_SETFL, fcntl(sp->listen, F_GETFL, 0) | O_NONBLOCK)) { dcc_error_msg("fcntl(flood listen %s, O_NONBLOCK): %s", dcc_su2str_err(sup), ERROR_STR()); } on = 1; if (0 > setsockopt(sp->listen, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) dcc_error_msg("setsockopt(flood listen %s," " SO_REUSADDR): %s", dcc_su2str_err(sup), ERROR_STR()); if (0 > fcntl(sp->listen, F_SETFD, FD_CLOEXEC)) dcc_error_msg("fcntl(flood listen %s FD_CLOEXEC): %s", dcc_su2str_err(sup), ERROR_STR()); i = bind(sp->listen, &sup->sa, DCC_SU_LEN(sup)); if (0 > i) { dcc_error_msg("bind(flood listen %s): %s", dcc_su2str_err(sup), ERROR_STR()); close(sp->listen); sp->listen = -1; continue; } if (0 > listen(sp->listen, DCCD_MAX_FLOODS+1)) { dcc_error_msg("flood listen(%s): %s", dcc_su2str_err(sup), ERROR_STR()); close(sp->listen); sp->listen = -1; } } } static const char * oflod_state_str(char outstr[DCC_SU2STR_SIZE], const OFLOD_INFO *ofp, u_char anon) { if (ofp->soc >= 0) { if (ofp->flags & (OFLOD_FG_SHUTDOWN_REQ | OFLOD_FG_SHUTDOWN)) return " (shutting)"; if (!(ofp->flags & OFLOD_FG_CONNECTED)) return " (connecting)"; if (anon) return ""; return dcc_su2str2(outstr, DCC_SU2STR_SIZE, &ofp->rem_su); } if (OFLOD_OPT_OFF_ROGUE(ofp)) return " (output off)"; if (flods_st != FLODS_ST_ON) return " (flood off)"; return " (no output)"; } static const char * iflod_state_str(char instr[DCC_SU2STR_SIZE], const OFLOD_INFO *ofp, const IFLOD_INFO *ifp, u_char anon, u_char have_in, u_char distinct_in) { if (have_in) { if (!(ifp->flags & IFLOD_FG_VERS_CK)) return " (connecting)"; if (anon) return ""; if (distinct_in) return dcc_su2str2(instr, DCC_SU2STR_SIZE, &ifp->rem_su); return "\t"; } if (IFLOD_OPT_OFF_ROGUE(ofp)) return " (input off)"; if (flods_st != FLODS_ST_ON) return " (flood off)"; return " (no input)"; } /* list the current flooders */ int flods_list(char *buf, int buf_len, u_char anon) { #define FLODS_LIST_TOO_SHORT "buffer too short\n" #define FLODS_LIST_ALLOC(i) { \ p += (i); \ if ((buf_len -= (i)) <= 0) { \ strcpy(p, FLODS_LIST_TOO_SHORT); \ return (p-buf)+ISZ(FLODS_LIST_TOO_SHORT); \ }} IFLOD_INFO *ifp; OFLOD_INFO *ofp; char instr[DCC_SU2STR_SIZE], outstr[DCC_SU2STR_SIZE]; char hostname[60], fg_buf[60]; DCC_SOCKU in, out; u_char have_in, distinct_in; int i; char *p; if (buf_len < ISZ(FLODS_LIST_TOO_SHORT) +INET6_ADDRSTRLEN+1) return 0; buf_len -= ISZ(FLODS_LIST_TOO_SHORT); p = buf; for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) { if (ofp->rem_hostname[0] == '\0') break; have_in = 0; distinct_in = 0; for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) { if (ifp->ofp == ofp) { if (ifp->soc >= 0) { have_in = 1; dcc_ipv6sutoipv4(&in, &ifp->rem_su); dcc_ipv6sutoipv4(&out, &ofp->rem_su); if (ofp->soc < 0 || !DCC_SU_EQ(&in, &out)) distinct_in = 1; } break; } } if (anon) { i = snprintf(p, buf_len, "%5d %15s\t%s\n", ofp->rem_id, oflod_state_str(outstr, ofp, 1), iflod_state_str(instr, ofp, ifp, 1, have_in, 0)); } else { dcc_host_portname(hostname, sizeof(hostname), ofp->rem_hostname, ofp->rem_port == def_port ? 0 : ofp->rem_portname), i = strlen(hostname); flodmap_fg(fg_buf, sizeof(fg_buf), i < 16 ? "\t\t" : i > 24 ? " " : "\t", ofp->mp); i = snprintf(p, buf_len, "%5d %15s\t%s\t%s%s\n", ofp->rem_id, oflod_state_str(outstr, ofp, 0), iflod_state_str(instr, ofp, ifp, 0, have_in, distinct_in), hostname, fg_buf); } FLODS_LIST_ALLOC(i); } for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) { if (ifp->soc < 0 || ifp->ofp != 0) continue; /* already handled this one */ /* say something about an incomplete connection */ i = snprintf(p, buf_len, " ? %s\n", ifp->rem_hostname); FLODS_LIST_ALLOC(i); } if (p > buf) --p; /* trim trailing '\n' */ return p-buf; #undef FLODS_LIST_TOO_SHORT #undef FLODS_LIST_ALLOC } static PATTRIB(3,4) u_char /* 0=no room */ flod_stats_str(char **buf, int *buf_len, const char *pat, ...) { int i; va_list args; if (*buf_len <= 0) return 0; va_start(args, pat); i = vsnprintf(*buf, *buf_len, pat, args); va_end(args); if ((*buf_len -= i) <= 0) { *buf_len = 0; return 0; } *buf += i; return 1; } static u_char /* 0=no room */ flod_stats_time(char **buf, int *buf_len, const char *str, const char *timepat, time_t when) { char timebuf[40]; return flod_stats_str(buf, buf_len, "%s %s", str, dcc_time2str(timebuf, sizeof(timebuf), timepat, when)); } static void flod_stats_conn_total(char **buf, int *buf_len, const char *label, int connected) { int i; if (*buf_len <= 0) return; i = snprintf(*buf, *buf_len, "\n %s connected a total of %d days %d:%02d:%02d\n", label, connected/(24*60*60), (connected/(60*60)) % 24, (connected/60) % 60, connected % 60); *buf += i; *buf_len -= i; } static void flod_stats_conn_cur(char **buf, int *buf_len, const OFLOD_INFO *ofp, u_char in) { u_char connected; time_t conn_changed; time_t flod_alive; const FLOD_MMAP *mp; const LAST_ERROR *ep; DCC_FNM_LNO_BUF fnm_buf; time_t deadline; u_char passive; const char *msg; if (*buf_len <= 0) return; mp = ofp->mp; if (in) { connected = ofp->ifp != 0; conn_changed = ofp->mp->cnts.in_conn_changed; flod_alive = ofp->ifp ? ofp->ifp->iflod_alive : 0; } else { connected = (ofp->flags & OFLOD_FG_CONNECTED) != 0; conn_changed = ofp->mp->cnts.out_conn_changed; flod_alive = ofp->oflod_alive; } if (connected) { if (conn_changed >= mp->cnts.cnts_cleared && !flod_stats_time(buf, buf_len, " connected since", "%b %d %X", conn_changed)) return; flod_stats_time(buf, buf_len, " last active", "%X", flod_alive); return; } if ((in && (mp->flags & FLODMAP_FG_IN_OFF)) || (!in && (mp->flags & FLODMAP_FG_OUT_OFF))) { flod_stats_str(buf, buf_len, " off%s", fnm_lno(&fnm_buf, flod_path, ofp->lno)); return; } if (!flod_stats_str(buf, buf_len, " not connected")) return; if (conn_changed >= mp->cnts.cnts_cleared) { if (!flod_stats_time(buf, buf_len, " since", "%b %d %X", conn_changed)) return; } ep = in ? &mp->iflod_err : &mp->oflod_err; msg = ep->msg[0] != '\0' ? ep->msg : ep->trace_msg; if (msg[0] != '\0') { if (!flod_stats_str(buf, buf_len, "\n\t%s", msg)) return; } if (!FLODS_OK_ON()) { flod_stats_str(buf, buf_len, "\n flooding off"); return; } if (in) { if ((ofp->mp->flags & FLODMAP_FG_ACT) != 0) { passive = 0; deadline = ofp->mp->itimers.retry; if (DB_IS_TIME(deadline, ofp->mp->itimers.retry_secs)) deadline = 0; } else { passive = 1; deadline = ofp->mp->itimers.msg; if (DB_IS_TIME(deadline, ofp->mp->itimers.msg_secs)) deadline = 0; } } else { if (ofp->mp->flags & FLODMAP_FG_PASSIVE) { passive = 1; deadline = ofp->mp->otimers.msg; if (DB_IS_TIME(deadline, ofp->mp->otimers.msg_secs)) deadline = 0; } else { passive = 0; deadline = ofp->mp->otimers.retry; if (DB_IS_TIME(deadline, ofp->mp->otimers.retry_secs)) deadline = 0; } } if (deadline == 0) { flod_stats_str(buf, buf_len, passive ? "\n complain soon" : "\n try again soon"); } else { flod_stats_time(buf, buf_len, passive ? "\n complain after" : "\n try again after", "%b %d %X", deadline); } } /* list the counts for a flood */ int /* -1 or buffer length */ flod_stats(char *buf, int buf_len, u_int32_t tgt, u_char clear) { #define FLOD_STATS_TOO_SHORT "buffer too short\n" #define FLOD_STATS_ALLOC(i) (p += (i), len -= (i)) OFLOD_INFO *ofp, *ofp1; FLOD_MMAP *mp; char now_buf[26], time_buf[26], fg_buf[60]; DCC_SRVR_ID min_srvr, max_srvr; u_char loaded; int len, i; char *p; if (buf_len < ISZ(FLOD_STATS_TOO_SHORT)) return 0; len = buf_len - ISZ(FLOD_STATS_TOO_SHORT); p = buf; if (flod_mmaps) { loaded = 0; } else if (!load_flod(0)) { return -1; } else { loaded = 1; } if (tgt <= DCC_SRVR_ID_MAX) { /* an explicit target server-ID was specified */ min_srvr = max_srvr = tgt; } else { /* look for next server-ID after the target value */ min_srvr = tgt - DCC_SRVR_ID_MAX; max_srvr = DCC_SRVR_ID_MAX; } ofp = 0; for (ofp1 = oflods.infos; ofp1 <= LAST(oflods.infos); ++ofp1) { if (ofp1->rem_hostname[0] != '\0' && ofp1->rem_id >= min_srvr && ofp1->rem_id <= max_srvr) { /* This peer fits and is the best so far. */ ofp = ofp1; max_srvr = ofp->rem_id-1; } } if (!ofp) { i = snprintf(p, len, DCC_AOP_FLOD_STATS_ID"unknown remote server-ID", tgt); FLOD_STATS_ALLOC(i); if (loaded) oflods_clear(); return p-buf; } mp = ofp->mp; save_flod_cnts(ofp); i = snprintf(p, len, DCC_AOP_FLOD_STATS_ID" %s%s %s\n status start %s", ofp->rem_id, mp->rem_hostname, flodmap_fg(fg_buf, sizeof(fg_buf), " ", mp), dcc_time2str(now_buf, sizeof(now_buf), "%b %d %X %Z", db_time.tv_sec), dcc_time2str(time_buf, sizeof(time_buf), "%b %d %X %Z", mp->cnts.cnts_cleared)); FLOD_STATS_ALLOC(i); flod_stats_conn_total(&p, &len, "output", mp->cnts.out_total_conn); i = snprintf(p, len, " "L_DPAT" reports sent\n", mp->cnts.out_reports+ofp->cnts.out_reports); FLOD_STATS_ALLOC(i); flod_stats_conn_cur(&p, &len, ofp, 0); i = snprintf(p, len, "\n position "L_HPAT, mp->confirm_pos); FLOD_STATS_ALLOC(i); flod_stats_conn_total(&p, &len, "input", mp->cnts.in_total_conn); i = snprintf(p, len, " "L_DPAT" reports received "L_DPAT" accepted" " "L_DPAT" duplicate "L_DPAT" stale\n" " "L_DPAT" bad whitelist "L_DPAT" not deleted\n", mp->cnts.total+ofp->cnts.total, mp->cnts.accepted+ofp->cnts.accepted, mp->cnts.dup+ofp->lc.dup.cur, mp->cnts.stale+ofp->lc.stale.cur, mp->cnts.wlist+ofp->lc.wlist.cur, mp->cnts.not_deleted+ofp->lc.not_deleted.cur); FLOD_STATS_ALLOC(i); flod_stats_conn_cur(&p, &len, ofp, 1); if (len <= 0) { strcpy(buf, FLOD_STATS_TOO_SHORT); if (loaded) oflods_clear(); return ISZ(FLOD_STATS_TOO_SHORT); } if (clear) { flod_try_again(ofp); save_flod_cnts(ofp); ofp->limit_reset = 0; memset(&mp->cnts, 0, sizeof(mp->cnts)); mp->cnts.cnts_cleared = db_time.tv_sec; } if (loaded) oflods_clear(); return p-buf; #undef FLOD_STATS_TOO_SHORT #undef FLOD_STATS_ALLOC }