Mercurial > notdcc
view dccd/dccd.c @ 0:c7f6b056b673
First import of vendor version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 13:49:58 +0100 |
parents | |
children |
line wrap: on
line source
/* Distributed Checksum Clearinghouse * * server * * 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.297 $Revision$ */ #include "dccd_defs.h" #include <signal.h> /* for Linux and SunOS */ #include <sys/uio.h> #include <sys/wait.h> #include "dcc_ifaddrs.h" DCC_EMSG dcc_emsg; static const char *homedir; static char *aargs[10]; static char **aarg = &aargs[0]; static DCC_PATH dbclean_def = DCC_LIBEXECDIR"/dbclean"; static char *dbclean = dbclean_def; static pid_t dbclean_pid = -1; static char *addr_str; /* an IP address for dbclean */ static char dbclean_str[] = "dbclean"; #define MAX_DBCLEAN_FLAGS 50 static char dbclean_flags[MAX_DBCLEAN_FLAGS+1] = "-Pq"; static int dbclean_flags_len = LITZ("-Pq"); static const char *dbclean_argv[20] = {dbclean_str, dbclean_flags}; static int dbclean_argc = 2; static char dbclean_args_str[200]; static int dbclean_args_str_len; /* do not try to run dbclean too often */ time_t dbclean_limit_secs = DBCLEAN_LIMIT_SECS; static time_t dbclean_failed; static double wfix_quiet_rate = 1.0; /* max load that allows window fixing */ static double wfix_busy_rate; /* measured active rate */ static double wfix_rate_change = 0.4; /* rate reduction to this allows fix */ static DB_PTR wfix_size; static u_char wfix_size_set = 0; DBCLEAN_WFIX_STATE dbclean_wfix_state = WFIX_DELAY; static double wfix_ops; static time_t wfix_check_start; static time_t dbclean_wfix_secs; /* window fixing timer */ #define WFIX_POST_CLEAN_SECS (6*60*60) /* do not fix it soon after cleaning */ #define WFIX_PRE_CLEAN_SECS (2*60*60) /* or just before cleaning */ #define WFIX_RECHECK_SECS (15*60) /* between checks for window overflow */ #define WFIX_QUIET_SECS (5*60) /* waiting for clients to flee */ #define WFIX_MEASURE_SECS (5*60) /* counting clients */ #define WFIX_MAX_SECS (WFIX_PRE_CLEAN_SECS+WFIX_POST_CLEAN_SECS) const char *need_del_dbclean; time_t del_dbclean_next; time_t dbclean_limit; static time_t clean_fake_secs; /* timer for missing cron job */ static time_t clean_last_secs; u_long dccd_tracemask = DCC_TRACE_ON_DEF_BITS; u_char background = 1; int stopint; /* !=0 if stopping or received signal */ static time_t flods_ck_secs = FLODS_CK_SECS; const char *brand = ""; static char *my_srvr_id_str; DCC_SRVR_ID my_srvr_id; u_char use_ipv6 = 0; u_int16_t def_port; /* default port #, network byte order */ typedef struct port_list { struct port_list *fwd; u_int16_t port; /* network byte order */ } PORT_LIST; static PORT_LIST *ports; SRVR_SOC *srvr_socs; static SRVR_SOC **srvr_socs_end = &srvr_socs; int srvr_rcvbuf = 1024*1024; #ifdef USE_DBCLEAN_F static u_char db_mode = DB_OPEN_MMAP_WRITE; #else static u_char db_mode = 0; #endif int grey_embargo = DEF_GREY_EMBARGO; /* seconds to delay new traffic */ int grey_window = DEF_GREY_WINDOW; /* wait as long as this */ int grey_white = DEF_GREY_WHITE; /* remember this long */ char our_hostname[MAXHOSTNAMELEN]; DCC_SUM host_id_sum; /* advertised with our server-ID */ time_t host_id_next, host_id_last; /* when to advertise */ u_char anon_off; /* turn off anonymous access */ time_t anon_delay_us = DCC_ANON_DELAY_US_DEF; /* delay anonymous clients */ u_int anon_delay_inflate = DCC_ANON_INFLATE_OFF; u_char stop_mode; /* 0=normal 1=reboot 2=with/DB clean */ static int total_ops; static QUEUE *queue_free; static QUEUE *queue_head; /* assume or hope we can handle 500 requests/second */ int queue_max = 500*DCC_MAX_RTT_SECS; /* ultimate bound on queue size */ static int queue_max_cur; /* current upper bound */ static int queue_cur; /* current queue size */ struct timeval wake_time; /* when we awoke from select() */ struct timeval req_recv_time; /* when request arrived */ DCC_TS future; /* timestamp sanity */ static DB_NOKEEP_CKS set_new_nokeep_cks, reset_new_nokeep_cks; DCC_TGTS flod_tholds[DCC_DIM_CKS]; /* flood at or after these thresholds */ static const char *parse_rl_rate(RL_RATE *, float, const char *, const char *); static void add_dbclean_flag(char); static void add_dbclean_arg(const char *); static void check_dbclean(int); static void run_dbclean(const char *, const char *); static void sigterm(int); static void sighup(int); static void stop_children(void); static u_char get_if_changes(u_char); static SRVR_SOC *add_srvr_soc(u_char, int, const void *, u_int16_t); static int open_srvr_socs(int); static void wfix_later(time_t); static void recv_job(void) NRATTRIB; static u_char new_job(SRVR_SOC *); static void NRATTRIB dccd_quit(int, const char *, ...) PATTRIB(2,3); static void usage(const char* barg) { static const char str[] = { "usage: [-64dVbfFQ] -i server-ID [-n brand] [-h homedir]\n" " [-I [host-ID][,user]] [-a [server-addr][,server-port]]" " [-q qsize]\n" " [-G [on,][weak-body,][weak-IP,]embargo],[window],[white]]\n" " [-W [rate][,chg][,dbsize]] [-K [no-]type] [-T tracemode]\n" " [-u anon-delay[,inflate] [-C dbclean]" " [-L ltype,facility.level]\n" " [-R [RL_SUB],[RL_FREE],[RL_ALL_FREE],[RL_BUGS]]" }; static u_char complained; if (!complained) { if (barg) dcc_error_msg("unrecognized \"%s\"\nusage: %s\n..." " continuing", barg, str); else dcc_error_msg("%s\n... continuing", str); complained = 1; } } int NRATTRIB main(int argc, char **argv) { char *p; const char *rest; u_char print_version = 0; DCC_CK_TYPES type; DCC_SOCKU *sup; u_int16_t port; int new_embargo, new_window, new_white; int error, i; const char *cp; double d1, d2; u_long l; dcc_syslog_init(1, argv[0], 0); if (DCC_DIM_CKS != DCC_COMP_DIM_CKS) dcc_logbad(EX_SOFTWARE, "DCC_DIM_CKS != DCC_COMP_DIM_CKS;" " check uses of both"); /* get first bytes of our hostname to name our server-ID */ memset(our_hostname, 0, sizeof(our_hostname)); if (0 > gethostname(our_hostname, sizeof(our_hostname)-1)) dcc_logbad(EX_NOHOST, "gethostname(): %s", ERROR_STR()); our_hostname[sizeof(our_hostname)-1] = '\0'; if (our_hostname[0] == '\0') dcc_logbad(EX_NOHOST, "null hostname from gethostname()"); strncpy((char *)host_id_sum, our_hostname, sizeof(host_id_sum)); parse_rl_rate(&rl_sub_rate, 0.5, "RL_SUB", "400"); parse_rl_rate(&rl_anon_rate, RL_AVG_SECS, "RL_ANON", "50"); parse_rl_rate(&rl_all_anon_rate, 0.5, "RL_ALL_ANON", "600"); parse_rl_rate(&rl_bugs_rate, RL_AVG_SECS, "RL_BUGS", "0.1"); /* this must match DCCD_GETOPTS in cron-dccd.in */ while ((i = getopt(argc, argv, "64dVbfFQi:n:h:a:I:q:G:t:W:K:T:u:C:L:R:")) != -1) { switch (i) { case '6': #ifndef NO_IPV6 use_ipv6 = 2; #endif break; case '4': use_ipv6 = 0; break; case 'd': add_dbclean_flag('d'); ++db_debug; break; case 'V': fprintf(stderr, DCC_VERSION"\n"); print_version = 1; break; case 'b': background = 0; break; case 'f': db_mode &= ~DB_OPEN_MMAP_WRITE; add_dbclean_flag('f'); break; case 'F': db_mode |= DB_OPEN_MMAP_WRITE; add_dbclean_flag('F'); break; case 'Q': query_only = 1; break; case 'i': my_srvr_id_str = optarg; if (!dcc_get_srvr_id(dcc_emsg, &my_srvr_id, my_srvr_id_str, 0, 0, 0)) dcc_logbad(dcc_ex_code, "%s", dcc_emsg); add_dbclean_arg("-i"); add_dbclean_arg(my_srvr_id_str); break; case 'n': /* RFC 2822 says "values between 33 and 126" */ cp = optarg; while (*cp >= 33 && *cp <= 126 && *cp != ':') ++cp; if (cp == optarg || (cp - optarg) > ISZ(DCC_BRAND) || *cp != '\0') { dcc_error_msg("invalid brand name \"-n %s\"", optarg); } else { brand = optarg; } break; case 'h': homedir = optarg; /* tell dbclean "-h ." because we will already * be there and that allows our -h to be relative */ add_dbclean_arg("-h."); break; case 'I': p = strchr(optarg, ','); if (p) { *p++ = '\0'; dcc_daemon_su(p); if (*optarg == '\0') break; } if (*optarg != '\0') strncpy((char *)host_id_sum, optarg, sizeof(host_id_sum)); break; case 'a': /* postpone checking host names until we know -6 */ if (aarg > LAST(aargs)) { dcc_error_msg("too many -a args"); break; } optarg += strspn(optarg, DCC_WHITESPACE); *aarg++ = optarg; break; case 'q': l = strtoul(optarg, &p, 10); if (*p != '\0' || l < 2 || l > 1000) { dcc_error_msg("invalid queue length \"%s\"", optarg); } else { queue_max = l; } break; case 'G': grey_on = 1; dcc_syslog_init(1, argv[0], " grey"); add_dbclean_arg("-Gon"); /* handle leading "on" "weak-body", and "weak-IP" */ rest = optarg; while (*rest) { if (dcc_ck_word_comma(&rest, "weak-body") || dcc_ck_word_comma(&rest, "weak_body") || dcc_ck_word_comma(&rest, "weak")) { grey_weak_body = 1; continue; } if (dcc_ck_word_comma(&rest, "weak-IP") || dcc_ck_word_comma(&rest, "weak_IP")) { grey_weak_ip = 1; continue; } if (!dcc_ck_word_comma(&rest, "on")) break; } new_embargo = dcc_get_secs(rest, &rest, 0, MAX_GREY_EMBARGO, grey_embargo); if (new_embargo < 0) { dcc_error_msg("invalid greylist embargo" " \"-G %s\"", optarg); break; } new_window = dcc_get_secs(rest, &rest, new_embargo, MAX_GREY_WINDOW, max(new_embargo,grey_window)); if (new_window < 0) { dcc_error_msg("invalid greylist wait time" " \"-G %s\"", optarg); break; } new_white = dcc_get_secs(rest, &rest, new_window, MAX_GREY_WHITE, max(new_window, grey_white)); if (new_white < 0 || *rest != '\0') { dcc_error_msg("invalid greylist whitelist time" " \"-G %s\"", optarg); break; } grey_embargo = new_embargo; grey_window = new_window; grey_white = new_white; break; case 't': /* obsolete */ break; case 'W': p = optarg; if (*p == '\0') { dcc_error_msg("unrecognized" " \"-W %s\"", optarg); break; } d1 = wfix_quiet_rate; if (*p != '\0' && *p != ',') { d1 = strtod(p, &p); if ((*p != '\0' && *p != ',') || d1 < 0.0 || d1 > 1000*1000.0) { dcc_error_msg("bad quiet rate in" " \"-W %s\"", optarg); break; } } if (*p == ',') ++p; d2 = wfix_rate_change; if (*p != '\0' && *p != ',') { d1 = strtod(p, &p); if ((*p != '\0' && *p != ',') || d1 < 0.0 || d1 > 1000*1000.0) { dcc_error_msg("bad rate change in" " \"-W %s\"", optarg); break; } } if (*p == ',') ++p; l = wfix_size/(1024*1024); if (*p != '\0') { l = strtoul(p, &p, 10); if ((*p != '\0' || l < DB_MIN_MIN_MBYTE || l > MAX_MAX_DB_MBYTE) && l != wfix_size) { dcc_error_msg("bad database size in" " \"-W %s\"", optarg); break; } } wfix_quiet_rate = d1; wfix_rate_change = d2; if (wfix_size/(1024*1024) != l) { wfix_size = ((DB_PTR)l)*(1024*1024); wfix_size_set = 1; } break; case 'K': if (!strcasecmp(optarg, "all")) { reset_new_nokeep_cks = -1; break; } if (!CLITCMP(optarg, "no-")) { optarg += LITZ("no-"); i = 0; } else { i = 1; } type = dcc_str2type_db(optarg, -1); if (type == DCC_CK_INVALID) { dcc_error_msg("bad checksum type in" " \"-K %s\"", optarg); break; } if (i) DB_SET_NOKEEP(reset_new_nokeep_cks, type); else DB_SET_NOKEEP(set_new_nokeep_cks, type); break; case 'T': if (!strcasecmp(optarg, "ADMN")) { dccd_tracemask |= DCC_TRACE_ADMN_BIT; } else if (!strcasecmp(optarg, "ANON")) { dccd_tracemask |= DCC_TRACE_ANON_BIT; } else if (!strcasecmp(optarg, "CLNT")) { dccd_tracemask |= DCC_TRACE_CLNT_BIT; } else if (!strcasecmp(optarg, "RLIM")) { dccd_tracemask |= DCC_TRACE_RLIM_BIT; } else if (!strcasecmp(optarg, "QUERY")) { dccd_tracemask |= DCC_TRACE_QUERY_BIT; } else if (!strcasecmp(optarg, "RIDC")) { dccd_tracemask |= DCC_TRACE_RIDC_BIT; } else if (!strcasecmp(optarg, "FLOOD")) { dccd_tracemask |= DCC_TRACE_FLOD_BIT; } else if (!strcasecmp(optarg, "FLOOD2")) { dccd_tracemask |= DCC_TRACE_FLOD2_BIT; } else if (!strcasecmp(optarg, "IDS")) { dccd_tracemask |= DCC_TRACE_IDS_BIT; } else if (!strcasecmp(optarg, "BL")) { dccd_tracemask |= DCC_TRACE_BL_BIT; } else if (!strcasecmp(optarg, "DB")) { dccd_tracemask |= DCC_TRACE_DB_BIT; } else if (!strcasecmp(optarg, "WLIST")) { dccd_tracemask |= DCC_TRACE_WLIST_BIT; } else { dcc_error_msg("invalid trace mode \"%s\"", optarg); } break; case 'u': i = parse_dccd_delay(dcc_emsg, &anon_delay_us, &anon_delay_inflate, optarg, 0, 0); if (!i) { dcc_error_msg("%s", dcc_emsg); } else if (i == 2) { anon_off = 1; } else { anon_off = 0; } break; case 'C': if (*optarg == '\0') { dcc_error_msg("no path to dbclean \"-C %s\"", optarg); break; } /* capture the path to the dbclean program */ dbclean = optarg; /* capture any args following the program */ for (p = strpbrk(optarg, DCC_WHITESPACE); p != 0; p = strpbrk(p, DCC_WHITESPACE)) { *p++ = '\0'; p += strspn(p, DCC_WHITESPACE); if (*p == '\0') break; add_dbclean_arg(p); } break; case 'L': if (dcc_parse_log_opt(optarg)) { add_dbclean_arg("-L"); add_dbclean_arg(optarg); } break; case 'R': rest = parse_rl_rate(&rl_sub_rate, -1.0, "RL_SUB", optarg); rest = parse_rl_rate(&rl_anon_rate, -1.0, "RL_ANON", rest); rest = parse_rl_rate(&rl_all_anon_rate, -1.0, "RL_ALL_ANON", rest); rest = parse_rl_rate(&rl_bugs_rate, -1.0, "RL_BUGS", rest); break; default: usage(optopt2str(optopt)); } } argc -= optind; argv += optind; if (argc != 0) usage(argv[0]); if (my_srvr_id == DCC_ID_INVALID) { if (print_version) exit(EX_OK); dcc_logbad(EX_USAGE, "server-ID not set with -i"); } if (grey_on) { anon_off = 1; dccd_tracemask |= DCC_TRACE_IDS_BIT; } /* parse addresses after we know whether -6 was among the args */ for (aarg = &aargs[0]; aarg <= LAST(aargs) && *aarg; ++aarg) { char hostname[DCC_MAXDOMAINLEN]; addr_str = *aarg; rest = dcc_parse_nm_port(dcc_emsg, *aarg, 0, hostname, sizeof(hostname), &port, 0, 0, 0, 0); if (!rest) { dcc_error_msg("%s", dcc_emsg); continue; } rest += strspn(rest, DCC_WHITESPACE); if (*rest != '\0') dcc_error_msg("unrecognized port number in" "\"-a %s\"", *aarg); if (hostname[0] == '\0') { PORT_LIST *pport = dcc_malloc(sizeof(*pport)); pport->port = port; pport->fwd = ports; ports = pport; continue; } if (!strcmp(hostname, "@")) { ++addr_str; /* "" but not a const */ add_srvr_soc(SRVR_SOC_ADDR, AF_UNSPEC, 0, port); continue; } dcc_host_lock(); if (!dcc_get_host(hostname, use_ipv6 ? 2 : 0, &error)) { dcc_host_unlock(); dcc_error_msg("%s: %s", *aarg, DCC_HSTRERROR(error)); continue; } for (sup = dcc_hostaddrs; sup < dcc_hostaddrs_end; ++sup) { if (sup->sa.sa_family == AF_INET) add_srvr_soc(SRVR_SOC_ADDR, AF_INET, &sup->ipv4.sin_addr, port); else add_srvr_soc(SRVR_SOC_ADDR, AF_INET6, &sup->ipv6.sin6_addr, port); } dcc_host_unlock(); } if (addr_str) { /* tell dbclean about one "-a addr" */ add_dbclean_arg("-a"); add_dbclean_arg(addr_str); } dcc_clnt_unthread_init(); if (!dcc_cdhome(dcc_emsg, homedir, 0)) dcc_logbad(dcc_ex_code, "%s", dcc_emsg); i = check_load_ids(1); if (i < 0) dcc_logbad(dcc_ex_code, "%s", dcc_emsg); else if (!i) dcc_error_msg("%s", dcc_emsg); if (!def_port) def_port = DCC_GREY2PORT(grey_on); if (!srvr_socs && !ports) { ports = dcc_malloc(sizeof(*ports)); ports->fwd = 0; ports->port = def_port; } get_if_changes(db_debug != 0); /* make initial attempt to open the server UDP sockets * This also sets use_ipv6 to 0 or 1 if it is still 2 */ if (open_srvr_socs(45) <= 0) dcc_logbad(EX_OSERR, "failed to open any server sockets"); add_dbclean_flag(use_ipv6 == 0 ? '4' : '6'); if (background) { if (0 > daemon(1, 0)) dcc_logbad(EX_OSERR, "daemon(): %s", ERROR_STR()); } if (!background) signal(SIGHUP, sigterm); /* SIGHUP fatal during debugging */ else signal(SIGHUP, sighup); /* speed configuration check */ signal(SIGTERM, sigterm); signal(SIGINT, sigterm); signal(SIGPIPE, SIG_IGN); #ifdef SIGXFSZ signal(SIGXFSZ, SIG_IGN); #endif atexit(stop_children); gettimeofday(&db_time, 0); wake_time = db_time; flod_mmap_path_set(); /* open the database, and try once to fix it */ if (!dccd_db_open(DB_OPEN_LOCK_NOWAIT)) { dcc_error_msg("%s", dcc_emsg); run_dbclean("SRbad", "database initially broken"); check_dbclean(0); /* stall until dbclean finishes */ if (!dccd_db_open(DB_OPEN_LOCK_NOWAIT)) { dcc_error_msg("%s", dcc_emsg); dcc_logbad(EX_NOINPUT, "could not start database %s", db_nm); } } /* do not start if dbclean is running */ if (!lock_dbclean(dcc_emsg, db_nm)) dcc_logbad(dcc_ex_code, "%s: dbclean already running?", dcc_emsg); unlock_dbclean(); flod_trace_gen = db_time.tv_sec; host_id_next = db_time.tv_sec + DCC_SRVR_ID_SECS_ST; check_blacklist_file(); flods_init(); clients_load(); stats_clear(); if (flod_mmaps != 0 && flod_mmaps->dccd_stats.reset.tv_sec != 0) { memcpy(&dccd_stats, &flod_mmaps->dccd_stats, sizeof(dccd_stats)); } dcc_trace_msg(DCC_VERSION" listening to port %d %s %s", ntohs(srvr_socs->arg_port), dcc_homedir, db_window_size_str); recv_job(); } static SRVR_SOC * /* 0 or new entry */ add_srvr_soc(u_char flags, int family, /* AF_UNSPEC or 0 if addrp==0 */ const void *addrp, /* 0, *in_addr, or *in6_addr */ u_int16_t port) { DCC_SOCKU su; SRVR_SOC *sp; dcc_mk_su(&su, family, addrp, port); for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->arg_family == family && sp->arg_port == port && !memcmp(&sp->arg_addr, addrp, ((family == AF_INET) ? sizeof(sp->arg_addr.in4) : sizeof(sp->arg_addr.in6)))) return sp; } sp = dcc_malloc(sizeof(*sp)); memset(sp, 0, sizeof(*sp)); sp->flags = flags; sp->udp = -1; sp->listen = -1; sp->su = su; sp->arg_family = family; if (addrp) memcpy(&sp->arg_addr, addrp, ((family == AF_INET) ? sizeof(sp->arg_addr.in4) : sizeof(sp->arg_addr.in6))); sp->arg_port = port; *srvr_socs_end = sp; srvr_socs_end = &sp->fwd; return sp; } static int /* # of sockets opened */ open_srvr_socs(int retry_secs) { static u_char srvr_rcvbuf_set = 0; int *retry_secsp; u_char family; SRVR_SOC *sp; int i; int num_socs = 0; if (stopint) return -1; retry_secsp = retry_secs ? &retry_secs : 0; for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->udp >= 0) { ++num_socs; continue; } /* resolve default port number if we finally know it */ if (!sp->arg_port) sp->arg_port = def_port; family = sp->arg_family; /* create the UDP socket * If we are using INADDR_ANY * and do not yet know if IPv6 works, just try it */ if (family == AF_UNSPEC && use_ipv6 == 2) { dcc_mk_su(&sp->su, AF_INET6, &sp->arg_addr, sp->arg_port); i = dcc_udp_bind(dcc_emsg, &sp->udp, &sp->su, retry_secsp); if (i == 0) { dcc_error_msg("%s", dcc_emsg); /* still don't know about IPv6 */ continue; } if (i > 0) { /* we finished an INADDR_ANY socket * and learned that IPv6 works */ use_ipv6 = 1; continue; } /* we know or guess that IPv6 does not work */ use_ipv6 = 0; } if (family == AF_UNSPEC) { /* using INADDR_ANY but now know whether IPv6 works */ family = use_ipv6 ? AF_INET6 : AF_INET; } else if (family == AF_INET6 && use_ipv6 == 2) { /* don't know if IPv6 works but have an IPv6 address */ use_ipv6 = 1; } dcc_mk_su(&sp->su, family, &sp->arg_addr, sp->arg_port); if (0 >= dcc_udp_bind(dcc_emsg, &sp->udp, &sp->su, retry_secsp)) { dcc_error_msg("%s", dcc_emsg); continue; } /* set socket receive buffer size as large as possible */ for (;;) { if (!setsockopt(sp->udp, SOL_SOCKET, SO_RCVBUF, &srvr_rcvbuf, sizeof(srvr_rcvbuf))) break; if (srvr_rcvbuf_set || srvr_rcvbuf <= 4096) { dcc_error_msg("setsockopt(%s,SO_RCVBUF=%d): %s", dcc_su2str_err(&sp->su), srvr_rcvbuf, ERROR_STR()); break; } srvr_rcvbuf -= 4096; } srvr_rcvbuf_set = 1; ++num_socs; } /* Finally decide the IPv6 issue if we found no sign of IPv6 */ if (use_ipv6 == 2) use_ipv6 = 0; return num_socs; } /* get ready to bind to all local IP addreses */ static u_char /* 1=added an interface */ add_ifs(u_char not_quiet) { u_char added; SRVR_SOC *sp; PORT_LIST *pport; #ifdef HAVE_GETIFADDRS struct ifaddrs *ifap0, *ifap; int num_ifs; #endif if (!ports) return 0; added = 0; #ifdef HAVE_GETIFADDRS if (0 > getifaddrs(&ifap0)) { dcc_error_msg("getifaddrs(): %s", ERROR_STR()); ifap0 = 0; } num_ifs = 0; for (pport = ports; pport; pport = pport->fwd) { const SRVR_SOC *listener = 0; for (ifap = ifap0; ifap; ifap = ifap->ifa_next) { if (!(ifap->ifa_flags & IFF_UP)) continue; if (!ifap->ifa_addr) continue; switch (ifap->ifa_addr->sa_family) { case AF_INET: ++num_ifs; sp = add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_NEW, ifap->ifa_addr->sa_family, &((struct sockaddr_in *)ifap ->ifa_addr)->sin_addr, pport->port); break; case AF_INET6: if (use_ipv6 == 0) continue; if (use_ipv6 == 2) use_ipv6 = 1; ++num_ifs; sp = add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_NEW, ifap->ifa_addr->sa_family, &((struct sockaddr_in6*)ifap ->ifa_addr)->sin6_addr, pport->port); break; default: continue; } if (sp->flags & SRVR_SOC_NEW) { added = 1; if (not_quiet) dcc_trace_msg("start listening on %s", dcc_su2str_err(&sp->su)); } sp->flags &= ~(SRVR_SOC_MARK | SRVR_SOC_NEW); /* interfaces can have duplicate addresses */ if (listener == sp) continue; if (!listener) { listener = sp; if (!(sp->flags & SRVR_SOC_LISTEN)) { sp->flags |= SRVR_SOC_LISTEN; added = 1; } } else { if (sp->flags & SRVR_SOC_LISTEN) { sp->flags &= ~SRVR_SOC_LISTEN; added = 1; } } } } #ifdef HAVE_FREEIFADDRS /* since this is done only a few times when HAVE_FREEIFADDRS is not * defined, don't worry if we cannot release the list of interfaces */ freeifaddrs(ifap0); #endif if (num_ifs > 0) return added; #endif /* HAVE_GETIFADDRS */ /* if we got no joy from getifaddrs(), use INADDR_ANY */ for (pport = ports; pport; pport = pport->fwd) { sp = add_srvr_soc(SRVR_SOC_IF | SRVR_SOC_LISTEN | SRVR_SOC_NEW, AF_UNSPEC, 0, pport->port); if (sp->flags & SRVR_SOC_NEW) { added = 1; if (not_quiet) dcc_trace_msg("fallback listen on %s", dcc_su2str_err(&sp->su)); } sp->flags &= ~(SRVR_SOC_MARK | SRVR_SOC_NEW); } return added; } /* deal with changes to network interfaces */ static u_char /* 1=something changed */ get_if_changes(u_char not_quiet) { SRVR_SOC *sp, **spp; u_char changed; for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->flags & SRVR_SOC_IF) sp->flags |= SRVR_SOC_MARK; } changed = add_ifs(not_quiet); spp = &srvr_socs; while ((sp = *spp) != 0) { /* an interface recognized by add_srvr_soc() will have * its SRVR_SOC_MARK cleared */ if (!(sp->flags & SRVR_SOC_MARK)) { spp = &sp->fwd; continue; } /* forget interfaces that have disappeared */ dcc_trace_msg("stop listening on %s", dcc_su2str_err(&sp->su)); changed = 1; if (srvr_socs_end == &sp->fwd) srvr_socs_end = spp; *spp = sp->fwd; if (sp->udp >= 0) close(sp->udp); if (sp->listen >= 0) close(sp->listen); dcc_free(sp); } return changed; } static const char * parse_rl_rate(RL_RATE *rate, float penalty_secs, const char *label, const char *arg) { char *p; int per_sec, hi; if (penalty_secs >= 0.0) rate->penalty_secs = penalty_secs; if (*arg == '\0') return arg; if (*arg == ',') return ++arg; per_sec = strtod(arg, &p) * RL_SCALE; hi = per_sec*RL_AVG_SECS; if ((*p != '\0' && *p != ',') || hi < RL_SCALE || per_sec > RL_MAX_CREDITS) { dcc_error_msg("invalid %s value in \"%s\"", label, arg); return ""; } /* maximum events/second * RL_SCALE */ rate->per_sec = per_sec; /* maximum allowed accumulated credits */ rate->hi = hi; /* minimum credit account balance */ rate->lo = -per_sec * rate->penalty_secs; return (*p == ',') ? p+1 : p; } static void add_dbclean_flag(char flag) { if (dbclean_flags_len >= MAX_DBCLEAN_FLAGS) dcc_logbad(EX_SOFTWARE, "too many flags for dbclean"); dbclean_flags[dbclean_flags_len++] = flag; } static void add_dbclean_arg(const char *arg) { int i; if (dbclean_argc >= DIM(dbclean_argv)-2) dcc_logbad(EX_SOFTWARE, "too many args for dbclean"); dbclean_argv[dbclean_argc++] = arg; i = snprintf(dbclean_args_str+dbclean_args_str_len, sizeof(dbclean_args_str)-dbclean_args_str_len, " %s", arg); dbclean_args_str_len += i; if (dbclean_args_str_len >= ISZ(dbclean_args_str)-2) dcc_logbad(EX_SOFTWARE, "too many args for dbclean"); } /* check effort to repair database */ static void check_dbclean(int options) { int status; pid_t pid; u_char ok; if (dbclean_pid < 0) return; pid = waitpid(dbclean_pid, &status, options); if (pid != dbclean_pid) return; dbclean_pid = -1; /* do not try failing dbclean too often */ #if defined(WIFEXITED) && defined(WEXITSTATUS) && defined(WTERMSIG) && defined(WIFSIGNALED) ok = 1; if (WIFSIGNALED(status)) { dcc_error_msg("dbclean exited with signal %d", WTERMSIG(status)); ok = 0; } else if (WIFEXITED(status)) { status = WEXITSTATUS(status); if (status != EX_OK) { if (status > 100 && status < 130) dcc_error_msg("dbclean stopped after signal %d", status-100); else dcc_error_msg("dbclean exited with status %d", status); ok = 0; } } #else ok = (status == EX_OK); #endif if (ok) { dbclean_failed = 0; dbclean_limit_secs = DBCLEAN_LIMIT_SECS; } else { dbclean_failed = db_time.tv_sec; dbclean_limit_secs *= 2; if (dbclean_limit_secs > DEL_DBCLEAN_SECS) dbclean_limit_secs = DEL_DBCLEAN_SECS; } /* don't restart dbclean until after it has stopped running * and cooled for a while */ dbclean_limit = db_time.tv_sec + dbclean_limit_secs; } /* try to repair the database */ static void run_dbclean(const char *mode, /* combination of '', R, S, and W */ const char *reason) { int i; check_dbclean(0); /* wait until previous ends */ wfix_later(WFIX_RECHECK_SECS); i = snprintf(&dbclean_flags[dbclean_flags_len], ISZ(dbclean_flags)-dbclean_flags_len, "%s", mode); if (i+dbclean_flags_len >= ISZ(dbclean_flags)) dcc_logbad(EX_SOFTWARE, "too many flags for dbclean"); dbclean_pid = fork(); if (dbclean_pid < 0) { dcc_error_msg("dbclean fork(): %s", ERROR_STR()); } else if (dbclean_pid == 0) { dcc_trace_msg("%s; starting `%s %s%s`", reason, dbclean, dbclean_flags, dbclean_args_str); execv(dbclean, (char **)dbclean_argv); dcc_error_msg("execv(%s %s%s): %s", dbclean, dbclean_flags, dbclean_args_str, ERROR_STR()); exit(-1); } need_del_dbclean = 0; dbclean_limit = db_time.tv_sec + dbclean_limit_secs; } static void close_srvr_socs(void) { SRVR_SOC *sp; for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->udp >= 0) { close(sp->udp); sp->udp = -1; } iflod_listen_close(sp); } } /* close files and otherwise clean up after being forked as a helper */ void after_fork(void) { IFLOD_INFO *ifp; OFLOD_INFO *ofp; resolve_hosts_pid = -1; dbclean_pid = -1; close_srvr_socs(); for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) { if (ifp->soc >= 0) close(ifp->soc); } for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) { if (ofp->soc >= 0) close(ofp->soc); } signal(SIGHUP, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGPIPE, SIG_DFL); #ifdef SIGXFSZ signal(SIGXFSZ, SIG_DFL); #endif } /* do not worry about cleaning to fix a window overflow for a while */ static void wfix_later(time_t delay) { dbclean_wfix_state = WFIX_DELAY; dbclean_wfix_secs = db_time.tv_sec + delay; } static double wfix_measure(u_char force) { double secs; secs = db_time.tv_sec - wfix_check_start; if (force || secs <= 0.0 || secs > WFIX_MEASURE_SECS*2 || total_ops < wfix_ops) { wfix_check_start = db_time.tv_sec; dbclean_wfix_secs = db_time.tv_sec + WFIX_MEASURE_SECS; wfix_ops = total_ops; return -1.0; } return (total_ops - wfix_ops) / secs; } static u_char /* 1=need dbclean now */ wfix(char *reason, u_int reason_len) { double rate; struct timeval sn; time_t next_clean; /* stop everything if dbclean is running */ if (db_minimum_map || wfix_quiet_rate <= 0.0) { wfix_later(WFIX_RECHECK_SECS); return 0; } switch (dbclean_wfix_state) { case WFIX_DELAY: /* just checking */ if (!DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS)) return 0; /* no quick cleaning soon after the database was created, * cleaned or repaired */ dcc_ts2timeval(&sn, &db_parms.sn); if (sn.tv_sec > db_time.tv_sec) sn.tv_sec = 0; if (sn.tv_sec < dbclean_failed && dbclean_failed <= db_time.tv_sec) sn.tv_sec = dbclean_failed; if (sn.tv_sec <= db_time.tv_sec) { dbclean_wfix_secs = sn.tv_sec + WFIX_POST_CLEAN_SECS; if (!DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS)) return 0; } /* check later if dbclean might run soon */ next_clean = clean_fake_secs; if (db_time.tv_sec >= next_clean - WFIX_PRE_CLEAN_SECS) { dbclean_wfix_secs = next_clean + WFIX_POST_CLEAN_SECS; if (!DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS)) return 0; } next_clean = clean_last_secs + 24*60*60; if (db_time.tv_sec >= next_clean - WFIX_PRE_CLEAN_SECS) { dbclean_wfix_secs = next_clean + WFIX_POST_CLEAN_SECS; if (!DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS)) return 0; } /* check later if the database is not too large now */ if (db_fsize + db_hash_fsize < (wfix_size_set ? wfix_size : db_max_rss)) { wfix_later(WFIX_RECHECK_SECS); break; } /* the database is too big, so try to chase the clients * to the other DCC server (if any) */ dbclean_wfix_state = WFIX_BUSY; wfix_measure(1); break; case WFIX_BUSY: rate = wfix_measure(0); if (rate >= 0.0) { wfix_busy_rate = rate; dbclean_wfix_state = WFIX_QUIET; dbclean_wfix_secs = db_time.tv_sec + WFIX_QUIET_SECS; } break; case WFIX_QUIET: /* waiting for clients to flee */ dbclean_wfix_state = WFIX_CHECK; wfix_measure(1); break; case WFIX_CHECK: /* counting clients */ rate = wfix_measure(0); if (rate < 0.0) break; if (rate <= wfix_quiet_rate || rate <= wfix_busy_rate * wfix_rate_change) { snprintf(reason, reason_len, "database size "L_DPAT">"L_DPAT "; load changed from %0.1f to %0.1f", db_fsize + db_hash_fsize, wfix_size_set ? wfix_size : db_max_rss, wfix_busy_rate, rate); return 1; } /* Other DCC servers did not take over the load. Maybe later */ if (db_debug) dcc_trace_msg("database size "L_DPAT" > "L_DPAT ", but load changed from %0.1f to %0.1f", db_fsize + db_hash_fsize, wfix_size_set ? wfix_size : db_max_rss, wfix_busy_rate, rate); wfix_later(WFIX_RECHECK_SECS); break; } return 0; } /* check for changes or other interesting events when the flood timer expires */ static time_t /* microseconds to wait */ check_changes(void) { static time_t misc_timer, files_timer; time_t secs; DB_HADDR hash_free; const char *mode; const char *reason; char reason_buf[100]; reason = 0; mode = 0; if (db_failed_line) { snprintf(reason_buf, sizeof(reason_buf), "database broken at line %d in %s "DCC_VERSION, db_failed_line, db_failed_file); reason = reason_buf; mode = "Rbad"; } else if ((hash_free = db_hash_len - db_hash_used) < MIN_CLEAN_HASH_ENTRIES || hash_free < db_hash_len/20) { /* try to expand the hash table when there are only * a few free slots or the load factor rises above .95 */ if (hash_free < MIN_HASH_ENTRIES) snprintf(reason_buf, sizeof(reason_buf), "%d free hash entries", hash_free); else snprintf(reason_buf, sizeof(reason_buf), "%d free hash entries among %d total", hash_free, db_hash_len); reason = reason_buf; mode = "Rhash"; } else { /* check nothing else if it is not yet time */ secs = next_flods_ck - db_time.tv_sec; if (secs > 0 && secs <= flods_ck_secs) return secs * DCC_US; } next_flods_ck = db_time.tv_sec + flods_ck_secs; /* do not make some checks too often even when flood checking is * being rushed */ if (DB_IS_TIME(misc_timer, MISC_CK_SECS)) { misc_timer = db_time.tv_sec + MISC_CK_SECS; /* shrink our memory footprint * if we are so idle that we don't have anything to flush */ if (db_need_flush_secs == 0) { if (db_unload(0, 1) == 1) { rel_db_states(); db_unload(0, 1); } } if (DB_IS_TIME(files_timer, 30)) { files_timer = db_time.tv_sec + 30; if (0 >= check_load_ids(0)) dcc_error_msg("check/reload: %s", dcc_emsg); check_blacklist_file(); #ifdef HAVE_GETIFADDRS /* check for network interface changes, but only * if we can release the result of getifaddrs() */ if (get_if_changes(1)) { int socs = open_srvr_socs(0); if (!socs) bad_stop("no server sockets"); else if (socs > 0) flods_restart("new network interfaces", 1); } #endif } if (DB_IS_TIME(need_clients_save, CLIENTS_SAVE_SECS)) clients_save(); /* sound a claim to our server-ID if the database is locked */ if (DB_IS_TIME(host_id_next, DCC_SRVR_ID_SECS) && DB_IS_LOCKED() &&!db_failed_line ) { refresh_srvr_rcd(host_id_sum, my_srvr_id, "adding server-ID claim"); if (!db_failed_line ) { host_id_last = db_time.tv_sec; host_id_next = host_id_last + DCC_SRVR_ID_SECS; } } } /* note when hash expansion finishes and collect a zombie */ check_dbclean(WNOHANG); if (reason != 0) { ; } else if (DB_IS_TIME(clean_fake_secs, 3*24*60*60) && (!db_minimum_map || DB_IS_TIME(clean_last_secs+2*24*60*60, 4*24*60*60))) { reason = "work around broken cron job"; mode = "Rcron"; } else if (need_del_dbclean != 0 && DB_IS_TIME(del_dbclean_next, DEL_DBCLEAN_SECS)) { /* the deletion of a report needs to be cleaned up */ reason = need_del_dbclean; mode = "Rdel"; } else if (DB_IS_TIME(dbclean_wfix_secs, WFIX_MAX_SECS) && wfix(reason_buf, sizeof(reason_buf))) { reason = reason_buf; mode = "Rquick"; } if (reason) { if (!DB_IS_TIME(dbclean_limit, dbclean_limit_secs)) { if (next_flods_ck > dbclean_limit) next_flods_ck = dbclean_limit; } else if (dbclean_pid < 0) { run_dbclean(mode, reason); } else { RUSH_NEXT_FLODS_CK(); } } else { flods_ck(0); } /* we probably delayed */ gettimeofday(&db_time, 0); secs = next_flods_ck - db_time.tv_sec; return secs >= 0 ? secs*DCC_US : 0; } static void NRATTRIB recv_job(void) { fd_set rfds, *prfds, wfds, *pwfds; # define PFD_SET(_fd0,_fds) {int _fd = _fd0; \ p##_fds = &_fds; FD_SET(_fd,p##_fds); \ if (max_fd < _fd) max_fd = _fd;} int max_fd, nfds; IFLOD_INFO *ifp; OFLOD_INFO *ofp; struct timeval delay; time_t delay_us, slept_us, was_too_busy, us; struct timeval iflods_read, busy_time, extra_time; u_char db_has_failed; # define QUANTUM (DCC_US/10) /* try to use only ~50% of the system */ SRVR_SOC *sp; QUEUE *q; int bad_select; u_char worked; int fd, i; bad_select = 3; was_too_busy = 0; gettimeofday(&db_time, 0); iflods_read = db_time; busy_time = db_time; extra_time = db_time; delay_us = flods_ck_secs*DCC_US; db_has_failed = 0; for (;;) { dcc_emsg[0] = '\0'; if (stopint) { flods_ck_secs = SHUTDOWN_DELAY; if (flods_off < 100000) { flods_off = 100000; flods_stop("server stopping", 0); check_dbclean(WNOHANG); if (dbclean_pid > 0) { kill(dbclean_pid, SIGINT); usleep(100*1000); } /* get started flushing the database while * we wait for flooding to stop */ rel_db_states(); db_minimum_map = 1; db_unload(0, 2); } /* get serious when the floods have stopped */ if (oflods.total == 0) { if (stopint < 0) dccd_quit(0, "gracefully stopping%s", stop_mode == 1 ? " for reboot" : stop_mode == 2 ? " cleanly" : ""); dccd_quit(stopint | 128, "exiting with signal %d", stopint); } } if (db_has_failed != db_failed_line) { db_has_failed = db_failed_line; if (db_failed_line) { ++flods_off; flods_stop("database corrupt", 1); rel_db_states(); db_unload(0, 2); db_unlock(); } } FD_ZERO(&rfds); prfds = 0; FD_ZERO(&wfds); pwfds = 0; max_fd = -1; /* look for client requests */ for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->udp >= 0) PFD_SET(sp->udp, rfds); } if (was_too_busy > 0) was_too_busy = QUANTUM - tv_diff2us(&db_time, &busy_time); if (was_too_busy > 0 && (tv_diff2us(&db_time, &extra_time) < min(30, min(KEEPALIVE_IN, KEEPALIVE_OUT)/2)) && FLODS_OK()) { /* if we have been too busy, * then do nothing extra for a while */ if (delay_us > was_too_busy) delay_us = was_too_busy; } else { extra_time = db_time; /* Accept new incoming flood connections * if flooding is on * and we don't already have too many floods. */ if (iflods.open < DCCD_MAX_FLOODS) { for (sp = srvr_socs; sp; sp = sp->fwd) { if (sp->listen >= 0) PFD_SET(sp->listen, rfds); } } /* pump floods out */ for (ofp = oflods.infos, i = 0; i < oflods.open; ++ofp) { if (ofp->soc < 0) continue; ++i; if (ofp->flags & OFLOD_FG_CONNECTED) { PFD_SET(ofp->soc, rfds); if (!(ofp->flags & OFLOD_FG_EAGAIN) && ofp->obuf_len != 0) PFD_SET(ofp->soc, wfds); } else { PFD_SET(ofp->soc, wfds); } } /* pump floods in */ for (ifp = iflods.infos, i = 0; i < iflods.open; ++ifp) { if (ifp->soc < 0) continue; ++i; if (ifp->flags & IFLOD_FG_CONNECTED) { PFD_SET(ifp->soc, rfds); } else { PFD_SET(ifp->soc, wfds); } } } /* push data to the disk */ if (db_need_flush_secs != 0) { if (DB_IS_TIME(db_need_flush_secs, max(DB_URGENT_NEED_FLUSH_SECS, DB_NEED_FLUSH_SECS))) { db_flush_needed(); gettimeofday(&db_time, 0); } us = db_need_flush_secs - db_time.tv_sec; if (us >= 0) { us *= DCC_US; if (delay_us > us) delay_us = us; } } /* let dbclean run if we have run out of work * or if we have been holding the lock for 0.1 seconds*/ if (db_minimum_map && DB_IS_LOCKED() && (delay_us != 0 || tv_diff2us(&db_time, &db_locked) >= DCC_US/10)) { db_unlock(); } /* delay until it is time to answer the oldest anonymous * request or something else that needs doing */ delay.tv_sec = delay_us/DCC_US; delay.tv_usec = delay_us%DCC_US; nfds = select(max_fd+1, prfds, pwfds, 0, &delay); if (nfds < 0) { if (errno != EINTR) { if (--bad_select < 0) bad_stop("give up after select(): %s", ERROR_STR()); else dcc_error_msg("select(): %s", ERROR_STR()); } /* ignore EINTR but recompute timers */ FD_ZERO(&rfds); FD_ZERO(&wfds); } else { bad_select = 3; } gettimeofday(&wake_time, 0); slept_us = tv_diff2us(&wake_time, &db_time); if (slept_us >= 500) { /* If select() paused for at least 0.5 millisecond, * then the waiting request has just now arrived. */ req_recv_time = wake_time; } else { /* If select() did not pause, then assume the waiting * requests arrived when we were half finished working * on flooding and other work besides ordinary requests * before calling select(). */ tv_add_us(&req_recv_time, tv_diff2us(&wake_time, &req_recv_time) / 2); } db_time = wake_time; worked = 0; for (sp = srvr_socs; sp; sp = sp->fwd) { /* queue a new anonymous request * or answer a new authenticated request */ fd = sp->udp; if (fd >= 0 && FD_ISSET(fd, &rfds)) { --nfds; worked = 1; while (new_job(sp)) continue; } /* start a new incoming flood */ fd = sp->listen; if (fd >= 0 && FD_ISSET(fd, &rfds)) { --nfds; worked = 1; iflod_start(sp); } } if (worked) gettimeofday(&db_time, 0); /* reset request receipt clock for next time */ req_recv_time = db_time; /* Accept new flood data or start new SOCKS floods. * Listen to all peers to prevent starvation */ worked = 0; for (ifp = iflods.infos, i = 0; nfds > 0 && i < iflods.open; ++ifp) { if (ifp->soc < 0) continue; ++i; if (FD_ISSET(ifp->soc, &rfds) || FD_ISSET(ifp->soc, &wfds)) { --nfds; iflod_read(ifp); worked = 1; } } if (worked) { gettimeofday(&db_time, 0); iflods_read = db_time; } else if (was_too_busy <= 0) { /* if incoming floods have been quiet for * awhile, then assume flooding has caught up * after having been turned off */ if (tv_diff2us(&wake_time, &iflods_read) > 2*DCC_US) iflods_ok_timer = db_time.tv_sec; } /* pump output flood data and receive confirmations * talk to all peers to prevent starvation */ worked = 0; for (ofp = oflods.infos, i = 0; nfds > 0 && i < oflods.open; ++ofp) { if (ofp->soc < 0) continue; ++i; if (FD_ISSET(ofp->soc, &rfds)) { --nfds; oflod_read(ofp); if (ofp->soc < 0) continue; } if (FD_ISSET(ofp->soc, &wfds)) { --nfds; oflod_write(ofp); worked = 1; } } if (worked) gettimeofday(&db_time, 0); /* process delayed jobs when their times arrive */ worked = 0; for (;;) { q = queue_head; if (!q) { delay_us = flods_ck_secs*DCC_US; break; } /* decide whether this job's time has come * while defending against time jumps */ delay_us = tv_diff2us(&q->answer, &db_time); if (delay_us >= 1000 && delay_us <= DCC_MAX_RTT && !stopint) break; /* not yet time for next job */ queue_head = q->later; if (queue_head) queue_head->earlier = 0; --queue_cur; do_work(q); worked = 1; free_q(q); } if (worked) gettimeofday(&db_time, 0); /* check configuration changes etc. */ us = check_changes(); if (delay_us >= us) delay_us = us; us = tv_diff2us(&db_time, &wake_time); if (us >= QUANTUM && !stopint) { gettimeofday(&db_time, 0); busy_time = db_time; was_too_busy = QUANTUM; } } } static void add_queue(QUEUE *q) { QUEUE *qnext, **qp; TMSG1(QUERY, "received %s", op_id_ip(q)); if (!ck_clnt_id(q)) { free_q(q); return; } ++total_ops; /* immediately process requests from authenticated clients * if flooding is working */ if (q->delay_us == 0) { do_work(q); free_q(q); return; } /* don't let the queue of delayed requests get too large */ if (queue_cur >= queue_max) { clnt_msg(q, "drop excess queued %s", op_id_ip(q)); free_q(q); return; } tv_add_us(&q->answer, q->delay_us); /* add the new job to the queue */ ++queue_cur; qp = &queue_head; for (;;) { qnext = *qp; if (!qnext) { *qp = q; break; } if (qnext->answer.tv_sec > q->answer.tv_sec || (qnext->answer.tv_sec == q->answer.tv_sec && qnext->answer.tv_usec > q->answer.tv_usec)) { q->later = qnext; qnext->earlier = q; *qp = q; break; } q->earlier = qnext; qp = &qnext->later; } } /* get a new job in a datagram */ static u_char /* 1=call again */ new_job(SRVR_SOC *sp) { QUEUE *q; static struct iovec iov = {0, sizeof(q->pkt)}; static struct msghdr msg; int i, j; /* Find a free queue entry for the job. * Because we don't check for incoming jobs unless we think the * queue is not full, there must always be a free entry or * permission to make more entries. */ q = queue_free; if (q) { queue_free = q->later; } else { i = 16; q = dcc_malloc(i * sizeof(*q)); if (!q) dcc_logbad(EX_UNAVAILABLE, "malloc(%d queue entries) failed", i); queue_max_cur += i; /* put all but the last new queue entry on the free list */ while (--i > 0) { q->later = queue_free; queue_free = q; ++q; } } memset(q, 0, sizeof(*q)); q->sp = sp; iov.iov_base = (char *)&q->pkt; msg.msg_name = (void *)&q->clnt_su; msg.msg_namelen = sizeof(q->clnt_su); msg.msg_iov = &iov; msg.msg_iovlen = 1; i = recvmsg(sp->udp, &msg, 0); if (i < 0) { /* ignore some results of ICMP unreachables for UDP * retransmissions seen on some platforms */ if (DCC_BLOCK_ERROR()) { ; } else if (UNREACHABLE_ERRORS()) { TMSG2(QUERY, "recvmsg(%s): %s", dcc_su2str_err(&sp->su), ERROR_STR()); } else { dcc_error_msg("recvmsg(%s): %s", dcc_su2str_err(&sp->su), ERROR_STR()); } free_q(q); return 0; } if (q->clnt_su.sa.sa_family != sp->su.sa.sa_family && !dcc_ipv4sutoipv6(&q->clnt_su, &q->clnt_su)) { dcc_error_msg("recvmsg address family %d instead of %d", q->clnt_su.sa.sa_family, sp->su.sa.sa_family); free_q(q); return 1; } if (DCC_SU_PORT(&q->clnt_su) == 0) { drop_msg(q, "source port 0"); free_q(q); return 1; } q->pkt_len = i; if (i < ISZ(DCC_HDR)) { drop_msg(q, "short request of %d bytes", i); free_q(q); return 1; } j = ntohs(q->pkt.hdr.len); if (j != i) { drop_msg(q, "request with header length %d instead of %d", j, i); free_q(q); return 1; } if (q->pkt.hdr.pkt_vers > DCC_PKT_VERSION_MAX_VALID || q->pkt.hdr.pkt_vers < DCC_PKT_VERSION_MIN_VALID || ((q->pkt.hdr.pkt_vers > DCC_PKT_VERSION_MAX || q->pkt.hdr.pkt_vers < DCC_PKT_VERSION_MIN) && q->pkt.hdr.op != DCC_OP_NOP)) { drop_msg(q, "%s in unrecognized protocol version #%d", qop2str(q), q->pkt.hdr.pkt_vers); free_q(q); return 1; } q->answer = req_recv_time; switch ((DCC_OPS)q->pkt.hdr.op) { case DCC_OP_NOP: do_nop(q); free_q(q); return 1; case DCC_OP_REPORT: if (db_parms.flags & DB_PARM_FG_GREY) break; /* not valid for greylist servers */ add_queue(q); return 1; case DCC_OP_QUERY: add_queue(q); return 1; case DCC_OP_ADMN: do_admn(q); free_q(q); return 1; case DCC_OP_DELETE: do_delete(q); free_q(q); return 1; case DCC_OP_GREY_REPORT: case DCC_OP_GREY_QUERY: case DCC_OP_GREY_WHITE: if (!(db_parms.flags & DB_PARM_FG_GREY)) break; /* valid only for greylist servers */ do_grey(q); free_q(q); return 1; case DCC_OP_GREY_SPAM: if (!(db_parms.flags & DB_PARM_FG_GREY)) break; /* valid only for greylist servers */ do_grey_spam(q); free_q(q); return 1; case DCC_OP_INVALID: case DCC_OP_ANSWER: case DCC_OP_OK: case DCC_OP_ERROR: break; } drop_msg(q, "invalid %s", op_id_ip(q)); free_q(q); return 1; } void free_q(QUEUE *q) { if (q->rl) --q->rl->ref_cnt; q->later = queue_free; queue_free = q; } u_char dccd_db_open(u_char lock_mode) { DCC_CK_TYPES type; time_t clean_secs; DCC_TGTS tgts; int i; if (!db_open(dcc_emsg, -1, 0, 0, lock_mode | db_mode)) return 0; if (grey_on) { /* for greylisting, ignore the args an silently impose our * notion of which checksums to keep and flooding thresholds */ db_parms.nokeep_cks = def_nokeep_cks(); if (grey_weak_ip) DB_RESET_NOKEEP(db_parms.nokeep_cks, DCC_CK_IP); for (type = DCC_CK_TYPE_FIRST; type <= DCC_CK_TYPE_LAST; ++type) { if (type == DCC_CK_SRVR_ID) { flod_tholds[type] = 1; continue; } if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type)) flod_tholds[type] = DCC_TGTS_INVALID; else flod_tholds[type] = 1; if (DCC_CK_IS_GREY_TRIPLE(grey_on,type) || type == DCC_CK_IP) { db_parms.ex_secs[type].all = grey_window; db_parms.ex_secs[type].spam = grey_white; } else if (type == DCC_CK_BODY || DCC_CK_IS_GREY_MSG(grey_on,type)) { db_parms.ex_secs[type].all = grey_window; db_parms.ex_secs[type].spam = grey_window; } else { db_parms.ex_secs[type].all = 1; db_parms.ex_secs[type].spam = 1; } } summarize_delay_secs = grey_embargo - FLODS_CK_SECS*2; } else { /* impose our notion of which normal checksums to keep */ DB_SET_NOKEEP(set_new_nokeep_cks, DCC_CK_FLOD_PATH); DB_SET_NOKEEP(set_new_nokeep_cks, DCC_CK_INVALID); DB_SET_NOKEEP(set_new_nokeep_cks, DCC_CK_REP_TOTAL); DB_SET_NOKEEP(set_new_nokeep_cks, DCC_CK_REP_BULK); db_parms.nokeep_cks = ((def_nokeep_cks() & ~reset_new_nokeep_cks) | set_new_nokeep_cks); for (type = DCC_CK_TYPE_FIRST; type <= DCC_CK_TYPE_LAST; ++type) { if (type == DCC_CK_SRVR_ID) { flod_tholds[type] = 1; continue; } if (type == DCC_CK_REP_TOTAL) tgts = DCC_TGTS_INVALID; if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type)) tgts = DCC_TGTS_INVALID; else if (DCC_CK_IS_REP_CMN(0, type)) tgts = DCC_TGTS_INVALID; else tgts = BULK_THRESHOLD; flod_tholds[type] = tgts; } /* We should not delay reports or summaries so much that * dbclean might expire them before we can summarize them. */ summarize_delay_secs = DCC_OLD_SPAM_SECS; for (type = DCC_CK_TYPE_FIRST; type <= DCC_CK_TYPE_LAST; ++type) { if (DB_TEST_NOKEEP(db_parms.nokeep_cks, type)) continue; i = db_parms.ex_secs[type].spam; if (i != 0 && summarize_delay_secs > i) summarize_delay_secs = i; } } if (summarize_delay_secs < 1) summarize_delay_secs = 1; /* adjust the thresholds after possible changes to kept checksums */ set_db_tholds(db_parms.nokeep_cks); /* If we instead of cron asked for the last cleaning, make a note * to clean the database during the graveyard shift. * Otherwise the database will bloat while the cron job is broken. * * Compute 1 day + 45 minutes after cron should have last cleaned the * database, if it has been cleaned by cron within the last 3 days * or the quarter hour when it was last cleaned by this mechanism * provided that was between local midnight and 05:00 * or 3 minutes past a random quarter hour beteen midnight and 05:00 */ if (db_parms.cleaned_cron >= db_time.tv_sec - 3*24*60*60 && db_parms.cleaned_cron <= db_time.tv_sec) { clean_last_secs = db_parms.cleaned_cron; /* failsafe cleaning for the greylist database starts 15 minutes * before the main database */ clean_secs = clean_last_secs + 45*60 - grey_on*15*60; } else { struct tm tm; clean_last_secs = db_parms.cleaned; if (clean_last_secs >= db_time.tv_sec) clean_last_secs = 0; clean_secs = clean_last_secs; /* if the previous time for this mechanism is not good, * pick a new random time */ dcc_localtime(clean_secs, &tm); if (clean_secs == 0 || tm.tm_hour < 1 || tm.tm_hour >= 5) { int rnum = (u_int)(db_time.tv_sec + db_time.tv_usec + my_srvr_id - grey_on) % 23; tm.tm_hour = ((rnum / 4) % 4) + 1; tm.tm_min = (rnum % 4) * 15; clean_secs = mktime(&tm); } } /* round down to a quarter hour to prevent creep due to inevitiable * delays in cron or this mechanism starting dbclean */ clean_secs -= clean_secs % (15*60); clean_secs %= (24*60*60); /* failsafe cleaning target minute */ /* compute the next scheduled failsafe cleaning. */ clean_fake_secs = db_time.tv_sec - (db_time.tv_sec % (24*60*60)); clean_fake_secs += clean_secs; if (clean_fake_secs <= db_time.tv_sec) clean_fake_secs += 24*60*60; /* The next failsafe cleaning should happen a day after the * most recent cron or failsafe cleaning, modulo the delay before * dbclean starts */ while (clean_fake_secs <= clean_last_secs + 24*60*60 - 60*60) clean_fake_secs += 24*60*60; /* Do not failsafe clean during the first 48 hours after the * database was created to give the cron job a chance. * We do not want failsafe cleaning to ever be running when the * cron job tries to start. */ while (clean_fake_secs <= db_parms.cleared + 2*24*60*60 && db_parms.cleared <= db_time.tv_sec) clean_fake_secs += 24*60*60; total_ops = 0; /* push our thresholds and flags to the file */ return db_flush_parms(0); } /* clean shut down */ static void NRATTRIB dccd_quit(int exitcode, const char *p, ...) { va_list args; if (stop_mode == 1) db_stop(); else if (stop_mode == 2) make_clean(2); db_unlock(); va_start(args, p); if (exitcode) dcc_verror_msg(p, args); else dcc_vtrace_msg(p, args); va_end(args); /* db_close() can take a long time, so close some things early. */ stop_children(); check_dbclean(WNOHANG); clients_save(); #ifdef HAVE_COHERENT_MMAP /* If mmap() is not coherent, do not call close_srvr_socs() but * keep the UDP sockets open to prevent another server from starting * until we have flushed our buffers to prevent problems on systems * that lack inter-process coherent mmap() */ if (!(db_mode & DB_OPEN_MMAP_WRITE)) close_srvr_socs(); #endif db_close(1); if (exitcode) dcc_error_msg("stopped"); else dcc_trace_msg("stopped"); exit(exitcode); } /* watch for fatal signals */ static void sigterm(int sig) { stopint = sig; stop_mode = 1; next_flods_ck = 0; (void)signal(sig, SIG_DFL); /* catch it only once */ } /* SIGHUP hurries checking the configuration files */ static void sighup(int sig UATTRIB) { next_flods_ck = 0; } /* emergency shutdown but close the database cleanly */ void bad_stop(const char *pat, ...) { va_list args; if (stopint) return; va_start(args, pat); dcc_verror_msg(pat, args); va_end(args); stopint = -1; next_flods_ck = 0; } static void stop_children(void) { if (resolve_hosts_pid > 0) kill(resolve_hosts_pid, SIGKILL); if (dbclean_pid > 0) kill(dbclean_pid, SIGKILL); }