/* Distributed Checksum Clearinghouse * * deal with incoming floods of checksums * * Copyright (c) 2006 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 for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * Parties not eligible to receive a license under this agreement can * obtain a commercial license to use DCC and permission to use * U.S. Patent 6,330,590 by contacting Commtouch at http://www.commtouch.com/ * or by email to nospam@commtouch.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.50-1.191 $Revision$ */ #include "dccd_defs.h" #include IFLODS iflods; DB_SPAM_EX_TS stale_ts; u_int complained_many_iflods; time_t got_hosts; pid_t resolve_hosts_pid = -1; time_t boot_ok_time; /* suppress flooding errors at first */ static u_char iflod_write(IFLOD_INFO *, void *, int, const char *, u_char); static DCC_TS future_ts; #define DCC_FUTURE_SECS (4*60*60) /* refuse reports this far in advance */ ID_MAP_RESULT id_map(DCC_SRVR_ID srvr, const OFLOD_OPTS *opts) { int i; ID_MAP_RESULT result; /* apply the ***first*** server-ID map that matches, if any */ 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 * rpt_id(const char *type, const DB_RCD* rcd, const IFLOD_INFO *ifp) { static int bufno; static struct { char str[90]; } bufs[4]; char *s; s = bufs[bufno].str; bufno = (bufno+1) % DIM(bufs); snprintf(s, sizeof(bufs[0].str), "%s%s %s ID=%d %s%s", type ? "flooded " : "", type ? type : "", ts2str_err(rcd->ts), DB_RCD_ID(rcd), ifp ? "from " : "", ifp ? ifp->hostname : ""); return s; } void PATTRIB(5,6) rpt_err(LAST_ERROR *ep, u_char bad, enum FLOD_ERR_OP new_op, int new_errno, const char *p, ...) { va_list args; if (new_op == FLOD_ERR_SAME) { new_op = ep->op; new_errno = ep->old_errno; } if (!bad && (!DB_IS_TIME(ep->ok, LAST_ERROR_OK_SECS) || !DB_IS_TIME(boot_ok_time, LAST_ERROR_ACT_SECS) || (ep->op == new_op && ep->old_errno == new_errno && !DB_IS_TIME(ep->rep_report, LAST_ERROR_SECS)) || new_op == FLOD_ERR_LOCAL_OFF)) { if ((DCC_TRACE_FLOD_BIT & dccd_tracemask) && (ep->op != new_op || ep->old_errno != new_errno)) { va_start(args, p); dcc_vtrace_msg(p, args); va_end(args); } ep->op = new_op; ep->old_errno = new_errno; return; } va_start(args, p); dcc_verror_msg(p, args); va_end(args); ep->rep_report = db_time.tv_sec + LAST_ERROR_SECS; ep->op = new_op; ep->old_errno = new_errno; } u_char set_flod_socket(int s, const char *hostname, const DCC_SOCKU *sup, u_char in) { #if IP_TOS static u_char tos_ok = 1; #endif int on; if (0 > fcntl(s, F_SETFD, FD_CLOEXEC)) dcc_error_msg("fcntl(flod %s, F_SETFD, FD_CLOEXEC): %s", hostname, ERROR_STR()); if (-1 == fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK)) { dcc_error_msg("fcntl(flod %s, O_NONBLOCK): %s", hostname, ERROR_STR()); return 0; } on = 1; if (0 > setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))) dcc_error_msg("setsockopt(flod %s, SO_KEEPALIVE): %s", hostname, ERROR_STR()); 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))) dcc_error_msg("setsockopt(%s, SO_SNDBUF): %s", hostname, ERROR_STR()); /* Use a small window for flooding to reduce stalling of * flood shutdowns waiting for the database. * A 6 KByte window with 1500 byte MSS is enough to move * a GByte/day over an RTT of 0.5 seconds. * Application layer metering could be done, but * because flooded reports are all about the same size, * it would act about the same and require much of the * machinery that is already in TCP. */ on = FLOD_BUF_SIZE*3; if (0 > setsockopt(s, SOL_SOCKET, SO_RCVBUF, &on, sizeof(on))) dcc_error_msg("setsockopt(%s, SO_RCVBUF): %s", hostname, ERROR_STR()); } else { on = FLOD_BUF_SIZE*3; if (0 > setsockopt(s, SOL_SOCKET, SO_SNDBUF, &on, sizeof(on))) dcc_error_msg("setsockopt(%s, SO_SNDBUF): %s", hostname, ERROR_STR()); } #ifdef IP_TOS /* It would be nice and clean to use netinet/ip.h for the definition * of IPTOS_THROUGHPUT. However, it is hard to use netinet/ip.h * portably because in_sysm.h is required for n_long on some * systems and not others. A bunch of messy ./configure fiddling * might patch that hassle, but the bit really ought to be the same * as the old 0x08 in the IPv4 header. */ if (sup->sa.sa_family == AF_INET && tos_ok) { on = 0x08; /* IPTOS_THROUGHPUT */ if (0 > setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(on))) { dcc_error_msg("setsockopt(IP_TOS," " IPTOS_THROUGHPUT, %s): %s", hostname, ERROR_STR()); tos_ok = 0; } } #endif return 1; } /* see if the host name resolution process is still running */ u_char /* 1=not running 0=please wait */ flod_names_resolve_ck(void) { 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 & FLOD_MMAP_FG_PASSIVE)) continue; ipv6 = ((mp->flags & FLOD_MMAP_FG_IPv4) ? 0 : (mp->flags & FLOD_MMAP_FG_IPv6) ? 1 : use_ipv6 ? 2 : 0); dcc_host_lock(); if (mp->flags & FLOD_MMAP_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; /* 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; if (!flod_names_resolve_ck()) return 0; got_hosts = db_time.tv_sec + FLOD_NAMES_RESOLVE_SECS; if (!background) { TMSG(FLOD, "resolving hostnames in the foreground"); flod_names_resolve(); return 1; } for (mp = flod_mmaps->mmaps; mp <= LAST(flod_mmaps->mmaps); ++mp) { mp->rem_su.sa.sa_family = AF_UNSPEC; mp->host_error = 0; } flod_mmap_sync(0, 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(flod_names_resolve_start): %s", ERROR_STR()); } else { 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); } return 1; } static void iflod_clear(IFLOD_INFO *ifp, u_char fail) /* 1=problems so delay restart */ { OFLOD_INFO *ofp; if (fail && (ofp = ifp->ofp) != 0 && (ofp->o_opts.flags & FLOD_OPT_SOCKS)) { if (ofp->in_try_secs == 0) ofp->in_try_secs = FLOD_RETRY_SECS; ofp->in_try_again = db_time.tv_sec + ofp->in_try_secs; TMSG2(FLOD, "postpone restarting flood from %s for %d seconds", ofp->rem_hostname, ofp->in_try_secs); } if (ifp->s >= 0) --iflods.active; memset(ifp, 0, sizeof(*ifp)); ifp->s = -1; if (iflods.active == 0 && oflods.active == 0 && flods_st != FLODS_ST_ON) oflods_unmap(); } void PATTRIB(6,7) iflod_close(IFLOD_INFO *ifp, u_char fail, u_char complain, enum FLOD_ERR_OP err_op, int new_errno, const char *pat, ...) { struct { DCC_FLOD_POS last_pos; DCC_FLOD_POS end; char str[DCC_FLOD_MAX_RESP]; char null; } resp; char opstr_buf[80]; char sustr[DCC_SU2STR_SIZE+1]; va_list args; OFLOD_INFO *ofp; struct linger nowait; void *wp; int wlen; const char *opstr; u_char is_ok; ofp = ifp->ofp; memset(&resp, 0, sizeof(resp)); db_ptr2flod_pos(resp.end, DCC_FLOD_POS_END); if (!pat) pat = ""; va_start(args, pat); wlen = vsnprintf(resp.str, sizeof(resp.str), pat, args); va_end(args); /* notice if we think it went ok */ is_ok = !memcmp(resp.str, DCC_FLOD_OK_STR, STRZ(DCC_FLOD_OK_STR)); /* add our counts to the string we will send the peer * if there is room in our buffer * and if it is a recognized peer for which we have counts * and if some progress was made */ if (ISZ(resp.str) - wlen > 30 && ofp != 0 && (is_ok || ofp->cnts.total != 0)) { while (wlen > 0 && (resp.str[wlen-1] == ':' || resp.str[wlen-1] == ' ')) --wlen; wlen += snprintf(&resp.str[wlen], ISZ(resp.str)-wlen+1, ": %u received %u accepted" " %u dup %u stale" " %u bad whitelist %u not deleted", ofp->cnts.total, ofp->cnts.accepted, ofp->cnts.dup.val, ofp->cnts.stale.val, ofp->cnts.ok2.val, ofp->cnts.not_deleted.val); } if (wlen > ISZ(resp.str)) wlen = ISZ(resp.str); /* if useful, prefix our final message with our final position */ if (memcmp(ifp->pos, ifp->pos_sent, ISZ(ifp->pos))) { memcpy(resp.last_pos, ifp->pos, ISZ(resp.last_pos)); wp = &resp.last_pos; wlen += ISZ(resp.end) + ISZ(resp.last_pos); } else { wp = &resp.end; wlen += ISZ(resp.end); } if (!is_ok && err_op == FLOD_ERR_NO_LINK) err_op = FLOD_ERR_PEER_FAIL; opstr = flod_stats_str(0, new_errno, err_op); dcc_su2str(sustr+1, sizeof(sustr)-1, &ifp->su); if (strcmp(ifp->hostname, sustr+1)) sustr[0] = ' '; else sustr[0] = '\0'; snprintf(opstr_buf, sizeof(opstr_buf), "iflod close status%s%s%s", fail ? " failed" : "", (fail && *opstr) ? ", " : *opstr ? " " : "", opstr); if (ofp && ofp->mp) { rpt_err(&ofp->mp->i_err, complain, err_op, new_errno, "%s to %s%s: %s", opstr_buf, ifp->hostname, sustr, resp.str); } else if (complain) { dcc_error_msg("%s to %s%s: %s", opstr_buf, ifp->hostname, sustr, resp.str); } else { TMSG4(FLOD, "%s to %s%s: %s", opstr_buf, ifp->hostname, sustr, resp.str); } /* send the final status report to the sending flooder */ if (!iflod_write(ifp, wp, wlen, "iflod_close()", fail ? 2 : 1)) fail = complain = 1; if (ifp->s >= 0) { if (stopint && !(ifp->flags & IFLOD_FG_FAST_LINGER)) { ifp->flags |= IFLOD_FG_FAST_LINGER; nowait.l_onoff = 1; nowait.l_linger = SHUTDOWN_DELAY; if (0 > setsockopt(ifp->s, SOL_SOCKET, SO_LINGER, &nowait, sizeof(nowait)) && !fail) dcc_error_msg("setsockopt(SO_LINGER %s): %s", ofp->rem_hostname, ERROR_STR()); } if (0 > close(ifp->s) && !fail) dcc_error_msg("close(iflod %s): %s", ifp->hostname, ERROR_STR()); } if (ofp != 0 && ofp->ifp == ifp) { save_flod_cnts(ofp); ofp->ifp = 0; } iflod_clear(ifp, fail); } static u_char iflod_write(IFLOD_INFO *ifp, void *buf, int buf_len, const char *type, /* string describing operation */ u_char close_it) /* 0=iflod_close() on error, */ { /* 1=complain, 2=ignore error */ int i; if (!(ifp->flags & IFLOD_FG_CONNECTED)) return 1; /* if we don't know the corresponding output stream because we have * not yet seen any authentication, we at least know the connection * did not involve SOCKS because we did not originate it. */ if (ifp->ofp && (ifp->ofp->o_opts.flags & FLOD_OPT_SOCKS)) i = Rsend(ifp->s, buf, buf_len, 0); else i = send(ifp->s, buf, buf_len, 0); if (i == buf_len) return 1; if (i < 0) { if (close_it == 0) { iflod_close(ifp, 1, 0, FLOD_ERR_IO, errno, "send(%s %s): %s", type, ifp->hostname, ERROR_STR()); } else if (close_it == 1) { dcc_error_msg("send(%s %s): %s", type, ifp->hostname, ERROR_STR()); } } else { if (close_it == 0) { iflod_close(ifp, 1, 0, FLOD_ERR_IO, 0, "send(%s %s)=%d not %d", type, ifp->hostname, i, buf_len); } else if (close_it == 1) { dcc_error_msg("send(%s %s)=%d not %d", type, ifp->hostname, i, buf_len); } } return 0; } u_char /* 0=nothing to send, 1=sent */ iflod_send_pos(IFLOD_INFO *ifp, u_char force) { DCC_FLOD_POS req; OFLOD_INFO *ofp; if (!(ifp->flags & IFLOD_FG_CONNECTED)) return 0; /* ask peer to start over if our database has been cleared */ ofp = ifp->ofp; if ((ifp->flags & IFLOD_FG_CONNECTED) && ofp && ofp->mp) { if (ofp->mp->flags & FLOD_MMAP_FG_FFWD_IN) { ofp->mp->flags &= ~(FLOD_MMAP_FG_FFWD_IN | FLOD_MMAP_FG_NEED_REWIND); memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent)); db_ptr2flod_pos(req, DCC_FLOD_POS_FFWD_IN); iflod_write(ifp, req, sizeof(req), "ffwd request", 0); dcc_trace_msg("ask %s %s to FFWD flood to us", ifp->hostname, dcc_su2str_err(&ifp->su)); return 1; } if (ofp->mp->flags & FLOD_MMAP_FG_NEED_REWIND) { ofp->mp->flags &= ~FLOD_MMAP_FG_NEED_REWIND; memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent)); db_ptr2flod_pos(req, DCC_FLOD_POS_REWIND); iflod_write(ifp, req, sizeof(req), "rewind request", 0); dcc_trace_msg("ask %s %s to rewind flood to us", ifp->hostname, dcc_su2str_err(&ifp->su)); return 1; } } if ((force && flod_pos2db_ptr(ifp->pos) >= DCC_FLOD_POS_MIN) || memcmp(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent))) { memcpy(ifp->pos_sent, ifp->pos, sizeof(ifp->pos_sent)); iflod_write(ifp, ifp->pos_sent, sizeof(ifp->pos_sent), "confirmed pos", 0); if (ofp) ofp->keep_in_time = db_time.tv_sec + KEEPALIVE_IN; return 1; } /* Say just anything if we are doing a keepalive probe before * any checksums have been sent by the peer and so before we * have a position to confirm. */ if (force) { FLOD_NOTE buf; db_ptr2flod_pos(buf.op, DCC_FLOD_POS_NOTE); strcpy(buf.str, "are you still there?"); buf.len = sizeof("are you still there?") + FLOD_NOTE_OVHD; iflod_write(ifp, &buf, buf.len, buf.str, 0); if (ofp) ofp->keep_in_time = db_time.tv_sec + KEEPALIVE_IN; return 1; } return 0; } void iflod_stop(IFLOD_INFO *ifp, const char *reason) { DCC_FLOD_POS end_req; if (reason) TMSG2(FLOD, "iflod stop %s: %s", ifp->hostname, reason); else TMSG1(FLOD, "iflod stop %s", ifp->hostname); iflod_send_pos(ifp, 0); db_ptr2flod_pos(end_req, DCC_FLOD_POS_END_REQ); iflod_write(ifp, end_req, sizeof(end_req), "iflod stop req", 0); ifp->flags |= IFLOD_FG_END_REQ; if (ifp->ofp) ifp->ofp->keep_in_time = db_time.tv_sec + KEEPALIVE_IN_STOP; } /* send stop requests to DCC servers flooding to us */ void iflods_stop(const char *reason, u_char force) /* now! */ { SRVR_SOC *sp; IFLOD_INFO *ifp; /* stop listening for new connections */ for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->listen >= 0) { close(sp->listen); sp->listen = -1; } } for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) { if (ifp->s < 0) continue; if (!(ifp->flags & IFLOD_FG_END_REQ) && (ifp->flags & IFLOD_FG_CONNECTED)) iflod_stop(ifp, reason); if (force || !(ifp->flags & IFLOD_FG_CONNECTED)) { iflod_close(ifp, 1, 0, (flods_st == FLODS_ST_OFF || (ifp->ofp && (ifp->ofp->i_opts.flags & FLOD_OPT_OFF))) ? FLOD_ERR_LOCAL_OFF : FLOD_ERR_NO_LINK, 0, "%s", 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; u_char flags; /* find a free input flooding slot */ for (ifp = iflods.infos; ifp->s >= 0; ++ifp) { if (ifp > LAST(iflods.infos)) { if (!complained_many_iflods++) dcc_error_msg("too many incoming floods"); return; } } l = sizeof(ifp->su); ifp->s = accept(sp->listen, &ifp->su.sa, &l); if (ifp->s < 0) { dcc_error_msg("accept(flod): %s", ERROR_STR()); return; } /* use the IP address until we know which peer it is */ strcpy(ifp->hostname, dcc_su2str_err(&ifp->su)); if (!set_flod_socket(ifp->s, ifp->hostname, &ifp->su, 1)) { close(ifp->s); ifp->s = -1; return; } if (ifp->su.sa.sa_family == AF_INET6) { flags = ck_ip_bl(&ifp->su.ipv6.sin6_addr); } else { dcc_ipv4toipv6(&peer_addr, ifp->su.ipv4.sin_addr); flags = ck_ip_bl(&peer_addr); } if (flags) { if (flags & RL_FG_TRACE) dcc_trace_msg("rejected flood from %s", ifp->hostname); close(ifp->s); ifp->s = -1; return; } ++iflods.active; ifp->flags |= IFLOD_FG_CONNECTED; ifp->quit_connect = db_time.tv_sec + IFLOD_CONNECT_SECS; TMSG1(FLOD, "start flood from %s", ifp->hostname); } /* Start an incoming SOCKS flood by connecting to the other system. * We will eventually turn the connection around and pretend * the other system initiated the TCP connection. */ static int /* -1=failure, 0=not yet, 1=done */ iflod_socks_connect(IFLOD_INFO *ifp) { OFLOD_INFO *ofp; ID_TBL *tp; DCC_FLOD_VERSION_HDR buf; DCC_FNM_LNO_BUF fnm_buf; int i; ofp = ifp->ofp; tp = find_id_tbl(ofp->out_passwd_id); if (!tp) { iflod_close(ifp, 1, 1, FLOD_ERR_SIGN, 0, DCC_FLOD_PASSWD_ID_MSG" %d%s", ofp->out_passwd_id, fnm_lno(fnm_buf, flod_path, ofp->lno)); return -1; } if (tp->cur_passwd[0] == '\0') { iflod_close(ifp, 1, 1, FLOD_ERR_SIGN, 0, DCC_FLOD_NO_PASSWD_MSG" %d%s", ofp->out_passwd_id, fnm_lno(fnm_buf, flod_path, ofp->lno)); return -1; } i = Rconnect(ifp->s, &ifp->su.sa, DCC_SU_LEN(&ifp->su)); if (0 > i && errno != EISCONN) { if (errno == EAGAIN || errno == EINPROGRESS || errno == EALREADY) return 0; /* it is lame to ignore EINVAL, but several UNIX-like * systems return EINVAL for the second connect() after * a Unreachable ICMP message or timeout */ if (errno != EINVAL) rpt_err(&ofp->mp->i_err, 0, FLOD_ERR_CONNECT, errno, "connect(iflod SOCKS %s %s): %s", ofp->rem_hostname, dcc_su2str_err(&ifp->su), ERROR_STR()); close(ifp->s); iflod_clear(ifp, 1); return -1; } ifp->flags |= IFLOD_FG_CONNECTED; ofp->mp->i_err.ok = db_time.tv_sec + LAST_ERROR_OK_SECS; /* after the SOCKS incoming flood socket is connected, * send authentication to convince the peer to send its * authentication and then its checksums. */ memset(&buf, 0, sizeof(buf)); strcpy(buf.body.str, version_str(ofp)); buf.body.sender_srvr_id = htons(my_srvr_id); buf.body.turn = 1; dcc_sign(tp->cur_passwd, sizeof(tp->cur_passwd), &buf, sizeof(buf)); if (!iflod_write(ifp, &buf, sizeof(buf), "iflod SOCKS authentication", 0)) return -1; TMSG2(FLOD, "start SOCKS flood from %s %s", ifp->hostname, dcc_su2str_err(&ifp->su)); return 1; } /* request the start of an input flood via SOCKS if it is not already flowing */ void iflod_socks_start(OFLOD_INFO *ofp) { DCC_FNM_LNO_BUF fnm_buf; IFLOD_INFO *ifp, *ifp1; if (!(ofp->o_opts.flags & FLOD_OPT_SOCKS) || (ofp->i_opts.flags & FLOD_OPT_OFF)) return; if (!DB_IS_TIME(ofp->in_try_again, ofp->in_try_secs)) return; ofp->in_try_secs = 0; /* look for a free slot or an existing slot for the incoming flood */ ifp = 0; for (ifp1 = iflods.infos; ifp1 <= LAST(iflods.infos); ++ifp1) { if (ifp1->s < 0) { if (!ifp) ifp = ifp1; } /* there is nothing to do if it already exists */ if (ifp1->ofp == ofp) return; } if (!ifp) { if (!complained_many_iflods++) dcc_error_msg("too many incoming floods" " to start SOCKS flood from %s", ofp->rem_hostname); return; } if (!flod_names_resolve_start()) return; /* wait for name resolution */ if (ofp->mp->rem_su.sa.sa_family == AF_UNSPEC) { rpt_err(&ofp->mp->i_err, 0, FLOD_ERR_GET_HOST, ofp->mp->host_error, "flood SOCKS peer name %s: %s%s", ofp->rem_hostname, DCC_HSTRERROR(ofp->mp->host_error), fnm_lno(fnm_buf, flod_path, ofp->lno)); return; } ifp->ofp = ofp; ifp->su = ofp->mp->rem_su; strcpy(ifp->hostname, ofp->rem_hostname); ifp->s = socket(ifp->su.sa.sa_family, SOCK_STREAM, 0); if (ifp->s < 0) { dcc_error_msg("socket(SOCKS iflod): %s", ERROR_STR()); return; } if (!set_flod_socket(ifp->s, ifp->hostname, &ifp->su, 1)) { close(ifp->s); ifp->s = -1; return; } ++iflods.active; iflod_socks_connect(ifp); } /* db_sts.rcd.d points to the old record */ static u_char /* 1=duplicate or deleted */ iflod_ck_dup(IFLOD_INFO *ifp, DB_RCD *new, DCC_TGTS new_tgts_raw) { #define OLD db_sts.rcd.d /* original is there */ DB_RCD_CK *new_ck, *old_ck; int new_num_cks, old_num_cks; int i; /* ignore reports for deleted checksums * unless they are new reports or delete requests */ if (DB_TGTS_RCD_RAW(OLD.r) == DCC_TGTS_DEL && new_tgts_raw != DCC_TGTS_DEL && !DCC_TS_NEWER_TS(new->ts, OLD.r->ts)) { i = FLOD_CNTERR(&ifp->ofp->cnts.stale); if (i <= 0) TMSG3(FLOD, "ignore deleted %s after %s%s", rpt_id("report", new, ifp), rpt_id(0, OLD.r, 0), (i < 0 ? "" : "; stop complaints")); return 1; } /* not duplicate if the server-IDs or timestamps differ */ if (DB_RCD_ID(new) != DB_RCD_ID(OLD.r) || memcmp(new->ts, OLD.r->ts, sizeof(new->ts))) 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; /* count the duplication */ i = FLOD_CNTERR(&ifp->ofp->cnts.dup); if (i <= 0) TMSG2(FLOD2, "duplicate %s%s", rpt_id("report", new, ifp), (i < 0 ? "" : "; stop complaints")); /* count the checksums other than paths in the old checksum * we have simple duplicate if there are as many or more checksums * in the old record */ for (old_num_cks = DB_NUM_CKS(OLD.r), old_ck = OLD.r->cks; old_num_cks != 0; --old_num_cks, ++old_ck) { if (DB_CK_TYPE(old_ck) != DCC_CK_FLOD_PATH) break; } if (old_num_cks >= new_num_cks) return 1; /* The new record has more checksums. Assume or hope * that we are dealing with a simple case like reputations * and that intermediate servers always filter the same checksums. * * Mark totals for checksums in the new that were present in * the original to not be increased when the new record is linked * in the database. */ while (old_num_cks < new_num_cks && old_num_cks != 0 && new_num_cks != 0) { if (DB_CK_TYPE(old_ck) == DB_CK_TYPE(new_ck)) { ++old_ck; --old_num_cks; } ++new_ck; --new_num_cks; } /* if the old record has checksums not in the new record, * give up and call the new record a duplicate */ if (old_num_cks != 0) return 1; /* delete the original report */ DB_TGTS_RCD_SET(OLD.r, 0); db_set_flush(&db_sts.rcd, 1, DB_RCD_HDR_LEN); return 0; } /* complain about a received flooded report */ static void PATTRIB(6,7) iflod_rpt_complain(IFLOD_INFO *ifp, const DB_RCD *new, /* complain about this report */ u_char serious, /* 0=send mere note, 1=send complaint */ FLOD_LIMCNT *lc, /* limit complaints with this */ const char *str, /* type of report */ const char *pat,...) /* the complaint */ { FLOD_NOTE buf; const char *sc; va_list args; int i, len; va_start(args, pat); len = vsnprintf(buf.str, sizeof(buf.str), pat, args); if (len >= ISZ(buf.str)) len = sizeof(buf.str)-1; va_end(args); if (!lc && ifp->ofp) lc = &ifp->ofp->cnts.iflod_bad; if (!lc) { sc = ""; } else { i = FLOD_CNTERR(lc); if (i > 0) return; sc = i < 0 ? "" : "; stop complaints"; } if (serious) { dcc_error_msg("%s %s%s", buf.str, rpt_id(str, new, ifp), sc); db_ptr2flod_pos(buf.op, DCC_FLOD_POS_COMPLAINT); } else { TMSG3(FLOD2, "%s %s%s", buf.str, rpt_id(str, new, ifp), sc); db_ptr2flod_pos(buf.op, DCC_FLOD_POS_NOTE); } len += snprintf(&buf.str[len], sizeof(buf.str)-len, " %s%s", rpt_id(str, new, 0), sc); if (len >= ISZ(buf.str)) len = ISZ(buf.str)-1; buf.len = len+1 + FLOD_NOTE_OVHD; iflod_write(ifp, &buf, buf.len, buf.str, 0); } /* consider an incoming flooded report */ static int /* -1=failed, 0=not yet, else length */ iflod_rpt(IFLOD_INFO *ifp, int total_len) { DCC_FLOD *fp; OFLOD_INFO *ofp; DB_PTR pos; DCC_TGTS rpt_tgts, found_tgts, min_tgts; DB_RCD new; DCC_SRVR_ID old_srvr, psrvr; const DCC_CK *ck_lim, *ck; DB_RCD_CK *found_ck, *new_ck; DB_PTR rcd_pos, min_rcd_pos; DCC_CK_TYPES type, prev_type, min_type; DCC_FLOD_PATH_ID *new_path_id, *old_path_id; int num_path_blocks; char tgts_buf[DCC_XHDR_MAX_TGTS_LEN]; int ok2; int rpt_len; u_char stale; ID_MAP_RESULT srvr_mapped; int i; ofp = ifp->ofp; /* must not be null here */ fp = (DCC_FLOD *)&ifp->ibuf.c[ifp->ibuf_off]; if (fp->num_cks == 0 || fp->num_cks > DCC_QUERY_MAX) { iflod_close(ifp, 1, 1, FLOD_ERR_BAD_DATA, 0, "impossible %d checksums in report #%d from %s", fp->num_cks, ofp->cnts.total, ifp->hostname); return -1; } rpt_len = (sizeof(*fp) - sizeof(fp->cks) + fp->num_cks * sizeof(fp->cks[0])); if (rpt_len > total_len) return 0; /* wait for more */ pos = flod_pos2db_ptr(fp->pos); if (pos < DCC_FLOD_POS_MIN) { iflod_close(ifp, 1, 1, FLOD_ERR_BAD_DATA, 0, "bogus position "L_HPAT" in flooded report #%d" " from %s", pos, ofp->cnts.total, ifp->hostname); return -1; } /* save the position to return to the sender */ memcpy(ifp->pos, fp->pos, sizeof(ifp->pos)); memcpy(new.ts, fp->ts, sizeof(new.ts)); memcpy(&new.srvr_id_auth, fp->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(&rpt_tgts, fp->tgts, sizeof(rpt_tgts)); rpt_tgts = ntohl(rpt_tgts); if (rpt_tgts == DCC_TGTS_DEL) { if (!(ofp->i_opts.flags & FLOD_OPT_DEL_OK)) { iflod_rpt_complain(ifp, &new, 1, &ofp->cnts.not_deleted, "delete request", "refuse"); return rpt_len; } if (!(ofp->i_opts.flags & FLOD_OPT_NO_LOG_DEL)) dcc_error_msg("accept %s", rpt_id("delete request", &new, ifp)); } else if (rpt_tgts == 0 || (rpt_tgts > DCC_TGTS_FLOD_RPT_MAX && rpt_tgts != DCC_TGTS_TOO_MANY)) { iflod_close(ifp, 1, 1, FLOD_ERR_BAD_DATA, 0, "bogus target count %s in %s", dcc_tgts2str(tgts_buf, sizeof(tgts_buf), rpt_tgts, grey_on), rpt_id("report", &new, ifp)); return -1; } else if (ofp->i_opts.flags & FLOD_OPT_TRAPS) { /* comply if the source watches only spam traps */ rpt_tgts = DCC_TGTS_TOO_MANY; } /* notice reports from the distant future */ if (DCC_TS_NEWER_TS(new.ts, future_ts)) { iflod_rpt_complain(ifp, &new, 1, &ofp->cnts.stale, "report", "future"); return rpt_len; } DB_TGTS_RCD_SET(&new, rpt_tgts); new.fgs_num_cks = 0; stale = 1; ck_lim = &fp->cks[fp->num_cks]; new_ck = new.cks; num_path_blocks = 0; srvr_mapped = id_map(old_srvr, &ofp->i_opts); switch (srvr_mapped) { case ID_MAP_NO: if (!find_id_tbl(old_srvr) && (DCC_TRACE_IDS_BIT & dccd_tracemask) && (i = FLOD_CNTERR(&ofp->cnts.bad_id)) <= 0) { dcc_error_msg("%s%s", rpt_id("unknown server-ID in", &new, ifp), (i < 0 ? "" : "; stop complaints")); } break; case ID_MAP_REJ: iflod_rpt_complain(ifp, &new, 0, 0, "rejected server-ID in", "refuse"); return rpt_len; case ID_MAP_SELF: new.srvr_id_auth = my_srvr_id; /* create path pointing to ourself if we translate the ID */ memset(new_ck, 0, sizeof(*new_ck)); new_ck->type_fgs = DCC_CK_FLOD_PATH; new_path_id = (DCC_FLOD_PATH_ID *)new_ck->sum; /* start the path with the ID of the previous hop because * we know it is defined */ new_path_id->hi = ofp->rem_id>>8; new_path_id->lo = ofp->rem_id; new.fgs_num_cks = 1; ++new_ck; break; } for (prev_type = DCC_CK_INVALID, ck = fp->cks; ck < ck_lim; prev_type = type, ++ck) { type = ck->type; if (!DCC_CK_OK_FLOD(grey_on, type)) { iflod_rpt_complain(ifp, &new, 1, 0, "report", "unknown checksum type %s in", DB_TYPE2STR(type)); continue; } if (ck->len != sizeof(*ck)) { /* relax this someday if necessary */ iflod_close(ifp, 1, 1, FLOD_ERR_BAD_DATA, 0, "unknown checksum length %d in %s", ck->len, rpt_id("report", &new, ifp)); return -1; } if (type <= prev_type && prev_type != DCC_CK_FLOD_PATH) { iflod_rpt_complain(ifp, &new, 1, 0, "report", "out of order %s checksum in", DB_TYPE2STR(type)); return rpt_len; } new_ck->type_fgs = type; new_ck->prev = DB_PTR_CP(DB_PTR_NULL); memcpy(new_ck->sum, ck->sum, sizeof(new_ck->sum)); if (type == DCC_CK_FLOD_PATH) { /* discard report if path is too long */ if (++num_path_blocks > DCC_MAX_FLOD_PATH_CKSUMS) { TMSG2(FLOD, "%d path blocks in %s", num_path_blocks, rpt_id("report", &new, ifp)); return rpt_len; } /* don't add this path if we translated the origin */ if (srvr_mapped == ID_MAP_SELF) continue; old_path_id = (DCC_FLOD_PATH_ID *)ck->sum; new_path_id = old_path_id; for (i = 0; i < DCC_NUM_FLOD_PATH; ++i, ++old_path_id) { psrvr = (old_path_id->hi<<8) | old_path_id->lo; if (psrvr == DCC_ID_INVALID) break; /* end of path */ switch (id_map(psrvr, &ofp->i_opts)) { case ID_MAP_NO: case ID_MAP_REJ: break; case ID_MAP_SELF: psrvr = my_srvr_id; break; } new_path_id->hi = psrvr>>8; new_path_id->lo = psrvr; ++new_path_id; } } else { if (type == DCC_CK_SRVR_ID && srvr_mapped == ID_MAP_SELF) { /* discard translated server-ID declarations */ TMSG2(FLOD, "translated server-ID from %d in %s", old_srvr, rpt_id("report", &new, ifp)); return rpt_len; } /* discard this checksum if we would not have kept * it if we had received the original report * and either its server-ID is translated * or it is not kept by default */ if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type) && (srvr_mapped == ID_MAP_SELF || DB_GLOBAL_NOKEEP(grey_on, type))) continue; /* notice if this checksum makes the report timely */ if (stale && !DCC_TS_OLDER_TS(new.ts, stale_ts[DB_CK_TYPE(new_ck)])) stale = 0; } ++new_ck; ++new.fgs_num_cks; } if (stale) { i = FLOD_CNTERR(&ofp->cnts.stale); if (i <= 0) TMSG2(FLOD2, "stale %s%s", rpt_id("report", &new, ifp), (i < 0 ? "" : "; stop complaints")); return rpt_len; } if (!DB_NUM_CKS(&new)) { /* no known checksums */ iflod_close(ifp, 1, 1, FLOD_ERR_BAD_DATA, 0, "no known checksum types in %s", rpt_id("report", &new, ifp)); return -1; } /* See if the report is a duplicate. * Check all of the checksums to find one that is absent or * the one with the smallest total to minimize the number * of reports we must check to see if this is a duplicate */ min_tgts = DCC_TGTS_TOO_MANY; min_type = DCC_CK_INVALID; min_rcd_pos = DB_PTR_NULL; ok2 = 0; while (new_ck-- > new.cks) { type = DB_CK_TYPE(new_ck); if (DB_TEST_NOKEEP(db_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, FLOD_ERR_BAD_DATA, 0, "%s", dcc_emsg); db_broken(__LINE__,__FILE__, "%s", dcc_emsg); return -1; case DB_FOUND_IT: /* notice claims by other servers to our ID */ if (DB_RCD_ID(&new) == my_srvr_id && type == DCC_CK_SRVR_ID && memcmp(host_id_rcd.cks[0].sum, new_ck->sum, sizeof(host_id_rcd.cks[0].sum))) { dcc_error_msg("host %s used our server-ID " "%d at %s", dcc_ck2str_err(type, new_ck->sum), my_srvr_id, ts2str_err(new.ts)); } /* see if this report is the duplicate--we * might get lucky */ if (iflod_ck_dup(ifp, &new, rpt_tgts)) return rpt_len; /* notice the local server's white list * greylisting uses DCC_TGTS_GREY_WHITE=DCC_TGTS_OK2 */ found_tgts = DB_TGTS_CK(found_ck); if (found_tgts == DCC_TGTS_OK || (found_tgts == DCC_TGTS_GREY_WHITE && (++ok2 >= 2 || grey_on))) { iflod_rpt_complain(ifp, &new, 0, &ofp->cnts.ok2, "report", "whitelisted"); return rpt_len; } /* At least one checksum is already known to us. * If the report is a delete request, * then we need to run dbclean to fix all of the * totals affected by the deleted reports. */ if (rpt_tgts == DCC_TGTS_DEL && !grey_on && !DCC_CK_IS_REP_CMN(grey_on, type)) need_del_dbclean = "flood checksum deletion"; /* We can use this checksum to see if this report is * a duplicate if it would not have been discarded * by an upstream peer */ if (!DB_GLOBAL_NOKEEP(grey_on, type)) { rcd_pos = DB_PTR_EX(found_ck->prev); if (rcd_pos == DB_PTR_NULL) goto not_dup; /* not a duplicate */ /* notice the checksum with smallest count */ if (min_type == DCC_CK_INVALID || min_tgts > found_tgts) { min_tgts = found_tgts; min_type = type; min_rcd_pos = rcd_pos; } } break; case DB_FOUND_EMPTY: case DB_FOUND_CHAIN: case DB_FOUND_INTRUDER: /* We will fail to find this checksum in our database * only if the report is not a duplicate or if it is * a duplicate report but either an upstream peer * did not pass it along or we have expired this * checksum. * Unless we've been using dbclean with * inconsistent expiration durations, the second * case won't happen, so assume it is not a duplicate */ if (!DB_GLOBAL_NOKEEP(grey_on, type)) goto not_dup; break; } } /* At least one checksum in the report is known to us, and * we've chosen the checksum with the smallest total. * Chase the chosen chain to check all occurances of * the checksum to see if the flooded report is a duplicate */ if (min_type != DCC_CK_INVALID) { for (;;) { found_ck = db_map_rcd_ck(dcc_emsg, &db_sts.rcd, min_rcd_pos, min_type); if (!found_ck) { iflod_close(ifp, 1, 1, FLOD_ERR_BAD_DATA, 0, "%s", dcc_emsg); db_broken(__LINE__,__FILE__, "%s", dcc_emsg); return -1; } if (iflod_ck_dup(ifp, &new, rpt_tgts)) return rpt_len; rcd_pos = DB_PTR_EX(found_ck->prev); if (rcd_pos == DB_PTR_NULL) break; if (rcd_pos >= min_rcd_pos) { db_broken(__LINE__,__FILE__, "bad %s link of "L_HPAT" at "L_HPAT, DB_TYPE2STR(min_type), rcd_pos, min_rcd_pos); return -1; } min_rcd_pos = rcd_pos; } } not_dup:; /* the report is not a duplicate, so add it to our database */ if (!add_dly_rcd(&new, 1)) { iflod_close(ifp, 1, 1, FLOD_ERR_BAD_DATA, 0, "%s", dcc_emsg); return -1; } ++ofp->cnts.accepted; return rpt_len; } static void bad_vers(IFLOD_INFO *ifp, u_char fail) { iflod_close(ifp, fail, fail, FLOD_ERR_BAD_VERS, 0, DCC_FLOD_BAD_VER_MSG DCC_FLOD_VERSION_CUR_STR "\" not \"%.*s\"", STRZ(DCC_FLOD_VERSION_STR_BASE)+10, ifp->ibuf.vers.body.str); } static u_char check_iflod_vers(IFLOD_INFO *ifp) { DCC_FNM_LNO_BUF fnm_buf; DCC_FLOD_VERSION_HDR *vp; OFLOD_INFO *ofp; IFLOD_INFO *ifp1; ID_TBL *tp; DCC_SRVR_ID rem_id; int iversion; vp = &ifp->ibuf.vers; if (!strcmp(vp->body.str, DCC_FLOD_VERSION_CUR_STR)) { iversion = DCC_FLOD_VERSION_CUR; ifp->flags |= IFLOD_FG_VERS_CK; ifp->flags |= IFLOD_FG_PEER_REP_OFF; #ifdef DCC_FLOD_VERSION7 } else if (!strcmp(vp->body.str, DCC_FLOD_VERSION7_STR)) { iversion = DCC_FLOD_VERSION7; ifp->flags |= IFLOD_FG_VERS_CK; ifp->flags |= IFLOD_FG_PEER_REP_OFF; #endif /* DCC_FLOD_VERSION7 */ } else if (!strncmp(vp->body.str, DCC_FLOD_REP_VERSION_STR_BASE, STRZ(DCC_FLOD_REP_VERSION_STR_BASE))) { /* It seems to be a peer selling reputations. * Complain quietly after identifying it and hope it will * try again without reputations */ iversion = 0; } else if (!strncmp(vp->body.str, DCC_FLOD_VERSION_STR_BASE, STRZ(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; } rem_id = ntohs(vp->body.sender_srvr_id); if (rem_id < DCC_SRVR_ID_MIN || rem_id > DCC_SRVR_ID_MAX) { iflod_close(ifp, 1, 1, FLOD_ERR_ID, 0, 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, FLOD_ERR_ID, 0, DCC_FLOD_BAD_ID_MSG" %d", rem_id); return 0; } if (ofp->rem_id == rem_id) { ifp->ofp = ofp; strcpy(ifp->hostname, ofp->rem_hostname); if (ofp->i_opts.flags & FLOD_OPT_OFF) { iflod_close(ifp, 1, 1, FLOD_ERR_LOCAL_OFF, 0, "incoming flood turnd off%s", fnm_lno(fnm_buf, flod_path, ofp->lno)); return 0; } break; } } /* we now know which peer it is */ if (!ck_sign(&tp, 0, ofp->in_passwd_id, vp, sizeof(*vp))) { if (!tp) iflod_close(ifp, 1, 1, FLOD_ERR_ID, 0, 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, FLOD_ERR_SIGN, 0, DCC_FLOD_BAD_AUTH_MSG" %d", ofp->in_passwd_id); return 0; } if (!(ifp->flags & IFLOD_FG_VERS_CK)) { bad_vers(ifp, iversion != 0); /* hope that the peer will try with a better version someday */ return 0; } ofp->iversion = iversion; ifp->ibuf_off += sizeof(*vp); if (iversion != DCC_FLOD_VERSION_CUR) TMSG2(FLOD, "iflod version %d from %s", iversion, ifp->hostname); /* switch a passive output flood */ if (vp->body.turn) { if ((ofp->o_opts.flags & FLOD_OPT_OFF) || flods_st != FLODS_ST_ON) { iflod_close(ifp, 1, 1, FLOD_ERR_LOCAL_OFF, 0, "passive output flooding off" " for ID %d%s", rem_id, fnm_lno(fnm_buf, flod_path, ofp->lno)); return 0; } if (ofp->s >= 0) { /* We have a duplicate passive output flood. * Keep the new one if the existing stream * has been quiet for a long time. */ if (!ofp->mp || DB_IS_TIME(ofp->mp->o_err.ok, LAST_ERROR_OK_SECS)) { TMSG2(FLOD,"new duplicate passive output flood" " for ID %d%s", rem_id, fnm_lno(fnm_buf, flod_path, ofp->lno)); oflod_close(ofp, 0, FLOD_ERR_DUP, 0); } else { iflod_close(ifp, 1, 0, FLOD_ERR_DUP, 0, "duplicate passive output flood" " for ID %d%s", rem_id, fnm_lno(fnm_buf, flod_path, ofp->lno)); return 0; } } ofp->s = ifp->s; ofp->rem_su = ifp->su; ++oflods.active; iflod_clear(ifp, 0); if (!oflod_connect_fin(ofp)) oflod_close(ofp, 0, FLOD_ERR_SAME, 0); return 0; } /* detect duplicate incoming floods */ for (ifp1 = iflods.infos; ifp1 <= LAST(iflods.infos); ++ifp1) { if (ifp1 == ifp || ifp1->ofp != ofp) continue; /* We have a duplicate. Keep the new flood and close the * old flood if the old flood has no position to confirm, * perhaps because it was never really used. */ if (!ofp->mp || !iflod_send_pos(ifp1, 1)) { iflod_close(ifp1, 0, 0, FLOD_ERR_DUP, 0, "new duplicate incoming flood for ID %d%s", rem_id, fnm_lno(fnm_buf, flod_path, ofp->lno)); break; } /* Otherwise, kill the new flood */ iflod_close(ifp, 1, 1, FLOD_ERR_DUP, 0, "duplicate incoming flood for ID %d%s", rem_id, fnm_lno(fnm_buf, flod_path, ofp->lno)); return 0; } ofp->ifp = ifp; save_flod_cnts(ofp); ofp->keep_in_time = db_time.tv_sec + KEEPALIVE_IN; /* send DCC_FLOD_POS_REWIND immediately if needed */ iflod_send_pos(ifp, 0); /* try to restart corresponding output flood */ if (ofp->s < 0) { ofp->out_try_again = 0; ofp->ids_mtime = 0; /* try both passwords */ oflod_open(ofp); } return 1; } /* see what a distant flooder is telling us */ u_char /* 1=probably still data in kernel */ iflod_read(IFLOD_INFO *ifp) { DCC_FLOD_VERSION_HDR *vp; DCC_FLOD *fp; OFLOD_INFO *ofp; const char *reason; int len, i; if (!(ifp->flags & IFLOD_FG_CONNECTED) && iflod_socks_connect(ifp) <= 0) return 0; /* read only once before returning * to ensure we pay attention to other work */ len = (((ifp->flags & IFLOD_FG_VERS_CK) ? sizeof(ifp->ibuf) : sizeof(*vp)) - ifp->ibuf_len); ofp = ifp->ofp; if (ofp && ofp->o_opts.flags & FLOD_OPT_SOCKS) i = Rrecv(ifp->s, &ifp->ibuf.c[ifp->ibuf_len], len, 0); else i = recv(ifp->s, &ifp->ibuf.c[ifp->ibuf_len], len, 0); if (i < 0) { /* If kernel ran out of data, stop for now. * Give up on an I/O error */ if (!DCC_BLOCK_ERROR()) { iflod_close(ifp, 1, 0, FLOD_ERR_IO, errno, "iflod recv(%s): %s", ifp->hostname, ERROR_STR()); } return 0; } if (i == 0) { /* EOF or remote shutdown() on the socket */ if (ifp->ibuf_len != 0) { /* we have some final data. */ if (ifp->ibuf_len < sizeof(DCC_FLOD_POS)) { /* complain if it is too little or noise */ iflod_close(ifp, 1, 0, FLOD_ERR_BAD_DATA, 0, "report %d from %s truncated", ofp ? ofp->cnts.total : 0, ifp->hostname); } else if (flod_pos2db_ptr((u_char *)&ifp->ibuf.c[ ifp->ibuf_off]) < DCC_FLOD_POS_MIN) { /* Take it as an error message if enough. * Recognize bad password error message and * slow down retries */ reason = &ifp->ibuf.c[ifp->ibuf_off +sizeof(DCC_FLOD_POS)]; if (ofp && ofp->cnts.total == 0 && (ofp->o_opts.flags & FLOD_OPT_SOCKS) && (!strncmp(reason, DCC_FLOD_BAD_ID_MSG, STRZ(DCC_FLOD_BAD_ID_MSG)) || !strncmp(reason, DCC_FLOD_BAD_AUTH_MSG, STRZ(DCC_FLOD_BAD_AUTH_MSG)))) ofp->in_try_secs = FLOD_SLOW_RETRY_SECS; iflod_close(ifp, 1, 0, FLOD_ERR_BAD_DATA, 0, "report %d from %s truncated" " with \"%s\"", ofp ? ofp->cnts.total : 0, ifp->hostname, reason); } else { /* complain if it is noise */ iflod_close(ifp, 1, 1, FLOD_ERR_BAD_DATA, 0, "garbage report %d from %s", ofp ? ofp->cnts.total : 0, ifp->hostname); } } else { iflod_close(ifp, 0, 0, (flods_st == FLODS_ST_OFF || (ofp && (ofp->i_opts.flags & FLOD_OPT_OFF))) ? FLOD_ERR_LOCAL_OFF : FLOD_ERR_REMOTE_OFF, 0, DCC_FLOD_OK_STR); } return 0; } ifp->ibuf_len += i; if (ofp && ofp->mp) ofp->mp->i_err.ok = db_time.tv_sec + LAST_ERROR_OK_SECS; if (db_lock() < 0) { dcc_error_msg("flod %s", dcc_emsg); return 0; } dcc_timeval2ts(future_ts, &db_time, DCC_FUTURE_SECS); for (;;) { len = ifp->ibuf_len - ifp->ibuf_off; if (!(ifp->flags & IFLOD_FG_VERS_CK)) { if (len < ISZ(*vp)) break; if (!check_iflod_vers(ifp)) break; ofp = ifp->ofp; } else { /* when we have enough of the report, see if we have * the whole thing by trying swallow it */ if (len < (int)(sizeof(*fp) - sizeof(fp->cks) + sizeof(fp->cks[0]))) break; i = iflod_rpt(ifp, len); if (i < 0) break; /* stream closed */ if (i == 0) break; /* wait for more of it */ if (ofp != 0) ++ofp->cnts.total; ifp->ibuf_off += i; } if (ifp->ibuf_off >= ifp->ibuf_len) { ifp->ibuf_len = 0; ifp->ibuf_off = 0; break; } } if (ifp->ibuf_off != 0) { ifp->ibuf_len -= ifp->ibuf_off; if (ifp->ibuf_len != 0) memmove(&ifp->ibuf.c[0], &ifp->ibuf.c[ifp->ibuf_off], ifp->ibuf_len); ifp->ibuf_off = 0; } return 1; } void iflods_start(void) { SRVR_SOC *sp; DCC_SOCKU su; const DCC_SOCKU *sup = 0; int i, on; /* (re)start only when things are quiet */ if (iflods.active != 0) return; for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->flags & SRVR_SOC_ADDR) { /* open a TCP listen socket for incoming floods * for each explicitly configured IP address */ sup = &sp->su; } else if (sp->flags & SRVR_SOC_LISTEN) { /* open one TCP INADDR_ANY listen socket for * the first implicitly configured interface */ sup = dcc_mk_su(&su, sp->su.sa.sa_family, 0, sp->su.ipv6.sin6_port); } else { if (sp->listen >= 0) { close(sp->listen); sp->listen = -1; } continue; } if (sp->listen >= 0) continue; /* don't need to listen if there is no flooding */ if (!oflods.total) continue; sp->listen = socket(sup->sa.sa_family, SOCK_STREAM, 0); if (sp->listen < 0) { dcc_error_msg("socket(listen %s): %s", dcc_su2str_err(sup), ERROR_STR()); continue; } on = 1; if (0 > setsockopt(sp->listen, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) dcc_error_msg("setsockopt(listen %s, SO_REUSADDR): %s", dcc_su2str_err(sup), ERROR_STR()); if (0 > fcntl(sp->listen, F_SETFD, FD_CLOEXEC)) dcc_error_msg("fcntl(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(listen %s): %s", dcc_su2str_err(sup), ERROR_STR()); close(sp->listen); sp->listen = -1; continue; } if (0 > listen(sp->listen, 1)) { dcc_error_msg("listen(%s): %s", dcc_su2str_err(sup), ERROR_STR()); close(sp->listen); sp->listen = -1; } } } /* list the current flooders */ int flods_list(char *buf, int buf_len, u_char anon) { #define FLODS_LIST_TOO_SHORT "buffer too short\n" #define FLODS_LIST_ALLOC(i) { \ p += (i); \ if ((buf_len -= (i)) <= 0) { \ strcpy(p, FLODS_LIST_TOO_SHORT); \ return (p-buf)+ISZ(FLODS_LIST_TOO_SHORT); \ }} IFLOD_INFO *ifp; OFLOD_INFO *ofp; char instr[DCC_SU2STR_SIZE], outstr[DCC_SU2STR_SIZE]; char hostname[60], fg_buf[30]; u_char have_in, sep_in; int i; char *p; if (buf_len < ISZ(FLODS_LIST_TOO_SHORT) +INET6_ADDRSTRLEN+1) return 0; buf_len -= ISZ(FLODS_LIST_TOO_SHORT); p = buf; for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) { if (ofp->rem_hostname[0] == '\0') break; have_in = 0; sep_in = 0; for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) { if (ifp->ofp == ofp) { if (ifp->s >= 0) { DCC_SOCKU in, out; have_in = 1; dcc_ipv6sutoipv4(&in, &ifp->su); dcc_ipv6sutoipv4(&out, &ofp->rem_su); if (ofp->s < 0 || !DCC_SU_EQ(&in, &out)) sep_in = 1; } break; } } if (anon) { i = snprintf(p, buf_len, "%5d %15s\t%s\n", ofp->rem_id, ofp->s >= 0 ? (!(ofp->flags & OFLOD_FG_CONNECTED) ? " (connecting)" : "") : (ofp->o_opts.flags & FLOD_OPT_OFF) ? " (output off)" : flods_st != FLODS_ST_ON ? " (flood off)" : " (no output)", have_in ? (!(ifp->flags & IFLOD_FG_VERS_CK) ? " (connecting)" : "") : (ofp->i_opts.flags & FLOD_OPT_OFF) ? " (input off)" : flods_st != FLODS_ST_ON ? " (flood off)" : " (no input)"); } else { dcc_host_portname(hostname, sizeof(hostname), ofp->rem_hostname, ofp->rem_port == def_port ? 0 : ofp->rem_portname), i = strlen(hostname); flod_mmap_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, ofp->s >= 0 ? ((ofp->flags & OFLOD_FG_CONNECTED) ? dcc_su2str2(outstr, sizeof(outstr), &ofp->rem_su) : " (connecting)") : (ofp->o_opts.flags & FLOD_OPT_OFF) ? " (output off)" : flods_st != FLODS_ST_ON ? " (flood off)" : " (no output)", have_in ? (!(ifp->flags & IFLOD_FG_VERS_CK) ? " (connecting)" : sep_in ? dcc_su2str2(instr, sizeof(instr), &ifp->su) : "\t") : (ofp->i_opts.flags & FLOD_OPT_OFF) ? " (input off)" : flods_st != FLODS_ST_ON ? " (flood off)" : " (no input)", hostname, fg_buf); } FLODS_LIST_ALLOC(i); } for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) { if (ifp->s < 0 || ifp->ofp != 0) continue; /* already handled this one */ /* say something about an incomplete connection */ i = snprintf(p, buf_len, " ? %s\n", dcc_su2str2(instr, sizeof(instr), &ifp->su)); FLODS_LIST_ALLOC(i); } if (p > buf) --p; /* trim trailing '\n' */ return p-buf; #undef FLODS_LIST_TOO_SHORT #undef FLODS_LIST_ALLOC } static u_char /* 0=no room */ flod_stats_time(char **buf, int *buf_len, const char *pat, time_t when) { struct tm tm; int i; if (*buf_len <= 0) return 0; i = strftime(*buf, *buf_len, pat, dcc_localtime(when, &tm)); if (i <= 0) { *buf_len = 0; return 0; } *buf += i; *buf_len -= i; return 1; } static void flod_stats_conn_total(char **buf, int *buf_len, const char *label, int connected) { 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; } const char * flod_stats_str(int *pe, int e, enum FLOD_ERR_OP op) { int ignore; if (!pe) pe = &ignore; switch (op) { case FLOD_ERR_OK: *pe = 0; return ""; case FLOD_ERR_SAME: *pe = 0; return ""; case FLOD_ERR_NO_LINK: *pe = 0; return "no link"; case FLOD_ERR_GET_HOST: *pe = 0; return "bad host name"; case FLOD_ERR_CONNECT: *pe = e; return "initial connect"; case FLOD_ERR_CONNECT2: *pe = e; return "second connect"; case FLOD_ERR_IO: *pe = e; return "I/O error"; case FLOD_ERR_DUP: *pe = 0; return "duplicate connection"; case FLOD_ERR_LOCAL_OFF: *pe = 0; return "local flood off"; case FLOD_ERR_REMOTE_OFF: *pe = 0; return "remote flood off"; case FLOD_ERR_ID: *pe = 0; return "bad ID"; case FLOD_ERR_SIGN: *pe = 0; return "bad password"; case FLOD_ERR_BAD_VERS: *pe = 0; return "bad version"; case FLOD_ERR_REP: *pe = 0; return DCC_FLOD_REPS_OFF_MSG; case FLOD_ERR_BAD_DATA: *pe = 0; return "bad data"; case FLOD_ERR_PEER_FAIL: *pe = 0; return "peer disconnect"; case FLOD_ERR_KEEPALIVE: *pe = 0; return "keepalive"; } *pe = 0; return "??"; } static void flod_stats_conn_cur(char **buf, int *buf_len, u_char connected, time_t conn_changed, time_t cleared, LAST_ERROR *ep, time_t keep_time, time_t try_again) { const char *opstr; int old_errno; time_t rep_report; int i; if (*buf_len <= 0) return; if (connected) { if (conn_changed >= cleared && !flod_stats_time(buf, buf_len, " connected since %b %d %X", conn_changed)) return; flod_stats_time(buf, buf_len, (db_time.tv_sec >= keep_time ? " keepalive expired %X" : " keepalive expires %X"), keep_time); return; } opstr = flod_stats_str(&old_errno, ep->old_errno, ep->op); i = snprintf(*buf, *buf_len, " not connected"); if ((*buf_len -= i) <= 0) { *buf_len = 0; return; } *buf += i; if (conn_changed >= cleared) { if (!flod_stats_time(buf, buf_len, " since %b %d %X", conn_changed)) return; } if (old_errno == 0) { i = snprintf(*buf, *buf_len, "; %s", opstr); } else { i = snprintf(*buf, *buf_len, "; %s: %s", opstr, ERROR_STR1(old_errno)); } if ((*buf_len -= i) <= 0) { *buf_len = 0; return; } *buf += i; if (try_again > db_time.tv_sec && !flod_stats_time(buf, buf_len, "%n do not check until %b %d %X", try_again)) return; rep_report = ep->rep_report; if (rep_report > ep->ok && rep_report > db_time.tv_sec && !flod_stats_time(buf, buf_len, "%n no repeated complaints until %b %d %X", rep_report)) return; } void new_peer(OFLOD_INFO *ofp) { ofp->in_try_again = 0; ofp->in_try_secs = 0; ofp->out_try_again = 0; ofp->out_try_secs = 0; if (ofp->mp) { ofp->mp->i_err.op = ((flods_st == FLODS_ST_OFF || (ofp->i_opts.flags & FLOD_OPT_OFF)) ? FLOD_ERR_LOCAL_OFF : FLOD_ERR_NO_LINK); ofp->mp->i_err.ok = 0; ofp->mp->i_err.rep_report = db_time.tv_sec + FLOD_RETRY_SECS*2; ofp->mp->o_err.op = ((flods_st == FLODS_ST_OFF || (ofp->o_opts.flags & FLOD_OPT_OFF)) ? FLOD_ERR_LOCAL_OFF : FLOD_ERR_NO_LINK); ofp->mp->o_err.ok = 0; ofp->mp->o_err.rep_report = db_time.tv_sec + FLOD_RETRY_SECS*2; } } /* list the counts for a flood */ int flod_stats(char *buf, int buf_len, u_int32_t tgt, u_char clear) { #define FLOD_STATS_TOO_SHORT "buffer too short\n" #define FLOD_STATS_ALLOC(i) (p += (i), len -= (i)) OFLOD_INFO *ofp, *ofp1; FLOD_MMAP *mp; char now_buf[26], time_buf[26], fg_buf[60]; DCC_SRVR_ID min_srvr, max_srvr; u_char mapped; struct tm tm; int len, i; char *p; if (buf_len < ISZ(FLOD_STATS_TOO_SHORT)) return 0; len = buf_len - ISZ(FLOD_STATS_TOO_SHORT); p = buf; strftime(now_buf, sizeof(now_buf), "%b %d %X %Z", dcc_localtime(db_time.tv_sec, &tm)); if (flod_mmaps) { mapped = 0; } else { oflods_load(); mapped = 1; } if (tgt <= DCC_SRVR_ID_MAX) { /* an explicit target server-ID was specified */ min_srvr = max_srvr = tgt; } else { /* look for next server-ID after the target value */ min_srvr = tgt - DCC_SRVR_ID_MAX; max_srvr = DCC_SRVR_ID_MAX; } ofp = 0; for (ofp1 = oflods.infos; ofp1 <= LAST(oflods.infos); ++ofp1) { if (ofp1->rem_id != DCC_ID_INVALID && ofp1->rem_id >= min_srvr && ofp1->rem_id <= max_srvr && ofp1->mp != 0) { /* This peer fits and is the best so far. */ ofp = ofp1; max_srvr = ofp->rem_id-1; } } if (!ofp) { i = snprintf(p, len, DCC_AOP_FLOD_STATS_ID"unknown remote server-ID", tgt); FLOD_STATS_ALLOC(i); if (mapped) oflods_unmap(); return p-buf; } mp = ofp->mp; save_flod_cnts(ofp); strftime(time_buf, sizeof(time_buf), "%b %d %X %Z", dcc_localtime(mp->cnts.cnts_cleared, &tm)); i = snprintf(p, len, DCC_AOP_FLOD_STATS_ID" %s%s %s\n status start %s", ofp->rem_id, mp->rem_hostname, flod_mmap_fg(fg_buf, sizeof(fg_buf), " ", mp), now_buf, time_buf); FLOD_STATS_ALLOC(i); flod_stats_conn_total(&p, &len, "output", mp->cnts.out_total_conn); i = snprintf(p, len, " %d reports sent%s\n", mp->cnts.out_reports+ofp->cnts.out_reports, mp->flags & FLOD_MMAP_FG_PASSIVE ? " PASSIVE" : ""); FLOD_STATS_ALLOC(i); flod_stats_conn_cur(&p, &len, ofp->flags & OFLOD_FG_CONNECTED, ofp->mp->cnts.out_conn_changed, mp->cnts.cnts_cleared, &ofp->mp->o_err, ofp->keep_out_time, ofp->out_try_again); flod_stats_conn_total(&p, &len, "input", mp->cnts.in_total_conn); i = snprintf(p, len, " %d reports received %d accepted" " %d duplicate %d stale\n" " %d bad whitelist %d not deleted%s\n", mp->cnts.total+ofp->cnts.total, mp->cnts.accepted+ofp->cnts.accepted, mp->cnts.dup+ofp->cnts.dup.val, mp->cnts.stale+ofp->cnts.stale.val, mp->cnts.ok2+ofp->cnts.ok2.val, mp->cnts.not_deleted+ofp->cnts.not_deleted.val, mp->flags & FLOD_MMAP_FG_SOCKS ? " SOCKS" : ""); FLOD_STATS_ALLOC(i); flod_stats_conn_cur(&p, &len, ofp->ifp != 0, ofp->mp->cnts.in_conn_changed, mp->cnts.cnts_cleared, &ofp->mp->i_err, ofp->keep_in_time, ofp->in_try_again); if (len <= 0) { strcpy(buf, FLOD_STATS_TOO_SHORT); if (mapped) oflods_unmap(); return ISZ(FLOD_STATS_TOO_SHORT); } if (clear) { ofp->limit_reset = db_time.tv_sec + FLOD_LIM_CLEAR_SECS; memset(&mp->cnts, 0, sizeof(mp->cnts)); mp->cnts.cnts_cleared = db_time.tv_sec; new_peer(ofp); save_flod_cnts(ofp); } if (mapped) oflods_unmap(); return p-buf; #undef FLOD_STATS_TOO_SHORT #undef FLOD_STATS_ALLOC }