Mercurial > notdcc
diff cdcc/cdcc.c @ 0:c7f6b056b673
First import of vendor version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 13:49:58 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cdcc/cdcc.c Tue Mar 10 13:49:58 2009 +0100 @@ -0,0 +1,2752 @@ +/* Distributed Checksum Clearinghouse + * + * control dcc 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.227 $Revision$ + */ + +#include "dcc_ck.h" +#include "dcc_xhdr.h" +#include "dcc_heap_debug.h" +#include "dcc_ids.h" +#ifndef DCC_WIN32 +#include <arpa/inet.h> +#endif + +static DCC_EMSG dcc_emsg; +static DCC_FNM_LNO_BUF fnm_buf; + +static DCC_CLNT_CTXT *ctxt; + +static DCC_PATH info_map_nm = DCC_MAP_NM_DEF; +static const char *ids_nm; +static time_t clock_kludge; +static const char *homedir; +static DCC_PASSWD passwd; +static u_char passwd_set; +static DCC_IP src; +static DCC_SRVR_NM srvr = DCC_SRVR_NM_DEF; +static u_char port_set; +static DCC_CLNT_ID srvr_clnt_id = DCC_ID_ANON; +static enum WHICH_MAP {MAP_TMP, MAP_INFO} which_map = MAP_INFO; +static u_char map_changed = 1; + +static u_char info_flags; + +static u_char grey_set; + +static u_char quiet; + + +static u_char do_cmds(char *); +static void set_which_map(enum WHICH_MAP); +static u_char init_map(u_char, u_char); + +struct cmd_tbl_entry; +/* -1=display help message, 0=command failed, 1=success */ +typedef int CMD (const char *, const struct cmd_tbl_entry *); +typedef struct cmd_tbl_entry { + const char *cmd; + CMD (*fnc); + u_char args; /* 0=optional, 1=required, 2=none */ + u_char privileged; /* 1=must have server's password */ + u_char write_map; /* 1=write map, 2=write /var/dcc/map */ + const char *help_str; +} CMD_TBL_ENTRY; + +static CMD help_cmd; +static CMD exit_cmd; +static CMD grey_cmd; +static CMD file_cmd; +static CMD new_map_cmd; +static CMD delete_cmd; +static CMD add_cmd; +static CMD load_cmd; +static CMD host_cmd; +static CMD port_cmd; +static CMD passwd_cmd; +static CMD id_cmd; +static CMD homedir_cmd; +static CMD debug_cmd; +static CMD quiet_cmd; +static CMD no_fail_cmd; +static CMD ckludge_cmd; +static CMD ipv6_cmd; +static CMD src_cmd; +static CMD socks_cmd; +static CMD info_cmd; +static CMD rtt_cmd; +static CMD delck_cmd; +static CMD sleep_cmd; +static CMD clients_cmd; +static CMD anon_cmd; +static CMD flod_rewind; +static CMD ffwd_in; +static CMD ffwd_out; +static CMD flod_stats; +static CMD stats_cmd; +static const char *stats_help; +static CMD clock_ck_cmd; +static CMD trace_def; + +static const CMD_TBL_ENTRY cmds_tbl[] = { + {"help", help_cmd, 0, 0, 0, "help [cmd]"}, + {"?", help_cmd, 0, 0, 0, 0}, + {"exit", exit_cmd, 2, 0, 0, "exit"}, + {"quit", exit_cmd, 2, 0, 0, 0}, + {"grey", grey_cmd, 0, 0, 0, "grey [on|off]"}, + {"homedir", homedir_cmd, 0, 0, 0, "homedir [path]"}, + {"file", file_cmd, 0, 0, 0, "file [map]"}, + {"map", file_cmd, 0, 0, 0, 0}, + {"new map", new_map_cmd, 0, 0, 0, "new map [map]"}, + {"delete", delete_cmd, 1, 0, 2, "delete host[,port]"}, + {"add", add_cmd, 1, 0, 2, + "add host,[port|-] [RTT+/-#] [ID [passwd]]"}, + {"load", load_cmd, 1, 0, 2, "load {info-file | -}"}, + {"host", host_cmd, 0, 0, 0, "host [hostname]"}, + {"server", host_cmd, 0, 0, 0, 0}, + {"port", port_cmd, 0, 0, 0, "port #"}, + {"password", passwd_cmd, 0, 0, 0, "password secret"}, + {"passwd", passwd_cmd, 0, 0, 0, 0}, + {"id", id_cmd, 0, 0, 0, "id [ID]"}, + {"debug", debug_cmd, 0, 0, 0, "debug [on|off|TTL=x]"}, + {"quiet", quiet_cmd, 0, 0, 0, "-quiet [on|off]"}, + {"no fail", no_fail_cmd, 2, 0, 2, "-no fail"}, + {"clock kludge",ckludge_cmd, 0, 0, 0, "clock kludge +/-secs"}, + {"IPv6", ipv6_cmd, 0, 0, 0, "IPv6 [on|off]"}, + {"src", src_cmd, 0, 0, 0, "src [-|IPaddress]"}, + {"SOCKS", socks_cmd, 0, 0, 0, "SOCKS [on|off]"}, + {"info", info_cmd, 0, 0, 0, "info [-N]"}, + {"RTT", rtt_cmd, 0, 0, 0, "RTT [-N]"}, + {"delck", delck_cmd, 1, 1, 0, "delck type hex1..4"}, + {"sleep", sleep_cmd, 1, 0, 0, "sleep sec.onds"}, + {"clients", clients_cmd, 0, 0, 0, + "clients [-nsiaVAK] [max [thold [addr/prefix]]]"}, + {"anon delay", anon_cmd, 0, 0, 0, "\nanon delay [delay[,inflate]]"}, + {"flood rewind",flod_rewind, 1, 1, 0, "flood rewind ID"}, + {"flod rewind", flod_rewind, 1, 1, 0, 0}, + {"flood FFWD in",ffwd_in, 1, 1, 0, "flood FFWD in ID"}, + {"flod FFWD in",ffwd_in, 1, 1, 0, 0}, + {"flood FFWD out",ffwd_out, 1, 1, 0, "flood FFWD out ID"}, + {"flod FFWD out",ffwd_out, 1, 1, 0, 0}, + {"flood stats", flod_stats, 1, 1, 0, "flood stats [clear] {ID|all}"}, + {"flod stats", flod_stats, 1, 1, 0, 0}, + {"stats", stats_cmd, 0, 0, 0, "stats [clear|all]"}, + {"status", stats_cmd, 0, 0, 0, 0}, + {"clock check", clock_ck_cmd, 0, 0, 0, "clock check"}, + {"trace default",trace_def, 2, 1, 0, "trace default"}, +}; + + +#define PRV_MSG ";\n" \ +" use the \"id server-ID\" command\n" \ +" and either \"password secret\" command or `su` to read passwords from %s" + + +static DCC_OP_RESP aop_resp; +static struct timeval op_start, op_end; +static DCC_SOCKU op_result_su; + +static struct { + const char *op; + const char *help_str; + DCC_AOPS aop; + u_char privileged; + u_int32_t val; +} aops_tbl[] = { +#define TMAC(s,b) \ + {"trace "#s" on", "trace "#s" {on|off}", \ + DCC_AOP_TRACE_ON, 1, DCC_TRACE_##b},\ + {"trace "#s" off", 0, DCC_AOP_TRACE_OFF, 1, DCC_TRACE_##b} + TMAC(admn,ADMN_BIT), + TMAC(anon,ANON_BIT), + TMAC(clnt,CLNT_BIT), + TMAC(rlim,RLIM_BIT), + TMAC(query,QUERY_BIT), + TMAC(ridc,RIDC_BIT), + TMAC(flood,FLOD_BIT), + TMAC(flood2,FLOD2_BIT), + TMAC(ids,IDS_BIT), + TMAC(bl,BL_BIT), + TMAC(db,DB_BIT), + TMAC(wlist,WLIST_BIT), +#undef TMAC + + {"stop", "", DCC_AOP_STOP, 1, 0}, + {"system stop", "", DCC_AOP_STOP, 1, 1}, + {"clean stop", "", DCC_AOP_STOP, 1, 2}, + {"flood check", "", DCC_AOP_FLOD, 1, DCC_AOP_FLOD_CHECK}, + {"flod check", 0, DCC_AOP_FLOD, 1, DCC_AOP_FLOD_CHECK}, + {"flood shutdown", "", DCC_AOP_FLOD, 1, DCC_AOP_FLOD_SHUTDOWN}, + {"flood shutdown", 0, DCC_AOP_FLOD, 1, DCC_AOP_FLOD_SHUTDOWN}, + {"flood halt", "", DCC_AOP_FLOD, 1, DCC_AOP_FLOD_HALT}, + {"flood halt", 0, DCC_AOP_FLOD, 1, DCC_AOP_FLOD_HALT}, + {"flood resume", "", DCC_AOP_FLOD, 1, DCC_AOP_FLOD_RESUME}, + {"flood resume", 0, DCC_AOP_FLOD, 1, DCC_AOP_FLOD_RESUME}, + {"flood list", "", DCC_AOP_FLOD, 0, DCC_AOP_FLOD_LIST}, + {"flood list", 0, DCC_AOP_FLOD, 0, DCC_AOP_FLOD_LIST}, + {"DB clean", "", DCC_AOP_DB_CLEAN, 1, 0}, + {"DB new", "", DCC_AOP_DB_NEW, 1, 0}, + {"DB flush cache", "", DCC_AOP_DB_UNLOAD, 1, 0}, + {"DB cache ok", "", DCC_AOP_DB_UNLOAD, 1, 1}, +}; + + +static void NRATTRIB +usage(void) +{ + dcc_logbad(EX_USAGE, + "usage: [-Vdq] [-h homedir] [-c ids] [op1 [op2] ... ]\n"); +} + + + +int NRATTRIB +main(int argc, char **argv) +{ + char cmd_buf[500]; + int i; + + srvr.port = htons(DCC_SRVR_PORT); + + dcc_init_priv(); + dcc_syslog_init(0, argv[0], 0); + + while ((i = getopt(argc, argv, "Vdqh:c:")) != -1) { + switch (i) { + case 'V': + fprintf(stderr, DCC_VERSION"\n"); + break; + + case 'd': + ++dcc_clnt_debug; + break; + + case 'q': + ++quiet; + break; + + case 'h': + homedir = optarg; + break; + + case 'c': + ids_nm = optarg; + break; + + default: + usage(); + } + } + argc -= optind; + argv += optind; + + dcc_clnt_unthread_init(); + dcc_cdhome(0, homedir, 1); + set_ids_path(0, ids_nm); + dcc_wf_init(&cmn_wf, 0); + + dcc_all_srvrs = 1; + + if (!init_map(!quiet, 0)) + set_which_map(MAP_TMP); + + /* with a list of commands, act as a batch utility */ + if (argc != 0) { + for (;;) { + /* a final arg of "-" says switch to interactive mode */ + if (argc == 1 && !strcmp(*argv, "-")) + break; + + if (!do_cmds(*argv)) { + fputs(" ?\n", stderr); + exit(EX_UNAVAILABLE); + } + assert_ctxts_unlocked(); + assert_info_unlocked(); + + ++argv; + if (!--argc) { + exit(EX_OK); + } + } + } + + /* Without an arg list of commands, look for commands from STDIN. + * Commands end with a semicolon or newline. */ + for (;;) { + assert_ctxts_unlocked(); + assert_info_unlocked(); + printf("cdcc %s> ", + which_map == MAP_INFO ? info_map_nm : "-"); + fflush(stderr); + fflush(stdout); + if (!fgets(cmd_buf, sizeof(cmd_buf), stdin)) { + fputc('\n', stdout); + exit(EX_OK); + } + if (!do_cmds(cmd_buf)) + fputs(" ?\n", stderr); + } +} + + + +/* see if don't need a server-ID password, have one or if we can get it */ +static u_char /* 0=failed, 1=ok */ +get_passwd(u_char privileged) /* 0=no privileges need, 1=need power */ +{ + const ID_TBL *srvr_clnt_tbl; + + srvr.clnt_id = srvr_clnt_id; + if (passwd_set) { + /* set to use the manual password */ + memcpy(srvr.passwd, passwd, sizeof(srvr.passwd)); + /* succeed if this is not a priviledge command + * or if we won't be trying to get the server to do + * something priviledge for the anonymous client-ID */ + return (!privileged || srvr.clnt_id != DCC_ID_ANON); + } + memset(srvr.passwd, 0, sizeof(srvr.passwd)); + + /* fail if we would be trying to get the server to do something + * powerful for the anonymous client-ID */ + if (srvr.clnt_id == DCC_ID_ANON) + return !privileged; + + /* Fetch the common server passwords only if we can read them without + * set-UID. This keeps random local users from attacking local + * or remote servers with privileged commands, but does not slow + * down privilege users who could use an editor to read and use + * the cleartext passwords manually. */ + dcc_rel_priv(); + if (0 > access(ids_path, R_OK) && errno == EACCES) { + srvr.clnt_id = DCC_ID_ANON; + if (!privileged) + return 1; + dcc_error_msg("access(%s): %s", + fnm2abs_err(0, ids_path), ERROR_STR()); + return 0; + } + + if (0 >= load_ids(dcc_emsg, srvr_clnt_id, &srvr_clnt_tbl, 1)) { + if (srvr_clnt_id != DCC_ID_ANON && privileged) + dcc_error_msg("%s", dcc_emsg); + srvr.clnt_id = DCC_ID_ANON; + return !privileged; + } + if (srvr_clnt_tbl) + memcpy(srvr.passwd, srvr_clnt_tbl->cur_passwd, + sizeof(srvr.passwd)); + return 1; +} + + + +static void +set_which_map(enum WHICH_MAP new) +{ + /* release things even if nothing seems to be changing + * to ensure that we bind a new socket */ + if (ctxt) { + dcc_ctxts_lock(); + if (dcc_clnt_info) + dcc_unmap_close_info(0); + if (ctxt) { + dcc_rel_ctxt(ctxt); + ctxt = 0; + } + dcc_ctxts_unlock(); + } + + map_changed = 1; + which_map = new; + if (new == MAP_INFO) { + passwd_set = 0; + src.family = AF_UNSPEC; + } +} + + + +static u_char +cdcc_unlock(u_char complain) +{ + u_char result; + + result = dcc_info_unlock(dcc_emsg); + dcc_ctxts_unlock(); + if (!result && complain) + dcc_error_msg("%s", dcc_emsg); + return result; +} + + + +/* start talking to the local map file */ +static u_char /* 0=failed 1=mapped and locked */ +init_map(u_char complain, + u_char lock) /* 1=keep both locks on success */ +{ + u_char result; + + info_flags = 0; + + dcc_emsg[0] = '\0'; + dcc_ctxts_lock(); + if (which_map == MAP_TMP) { + result = (dcc_map_tmp_info(dcc_emsg, &srvr, &src, info_flags) + && dcc_info_lock(dcc_emsg)); + } else { + result = dcc_map_lock_info(dcc_emsg, info_map_nm, -1); + } + if (result) { + info_flags = dcc_clnt_info->flags; + if (!lock) + result = cdcc_unlock(complain); + } else { + dcc_ctxts_unlock(); + if (complain) + dcc_error_msg("%s", dcc_emsg); + } + return result; +} + + + +/* get ready start talking to a DCC server */ +static u_char /* 0=failed, 1=ok */ +rdy_ctxt(DCC_CLNT_FGS fgs) +{ + u_char rdy_done, need_unlock; + + info_flags = 0; + + if (grey_on) + fgs |= DCC_CLNT_FG_GREY; + else + fgs &= ~DCC_CLNT_FG_GREY; + fgs |= DCC_CLNT_FG_NO_FAIL; + + if (!dcc_clnt_info && ctxt) { + dcc_rel_ctxt(ctxt); + ctxt = 0; + } + + if (ctxt) { + rdy_done = 0; + } else { + if (which_map == MAP_TMP) { + /* create a new temporary map */ + ctxt = dcc_tmp_clnt_init(dcc_emsg, ctxt, &srvr, &src, + fgs, 0); + } else { + /* open official map file */ + ctxt = dcc_clnt_init(dcc_emsg, ctxt, info_map_nm, fgs); + } + if (!ctxt) { + dcc_error_msg("%s", dcc_emsg); + return 0; + } + rdy_done = 1; + } + + if (!grey_set && dcc_clnt_info + && !grey_on + && dcc_clnt_info->dcc.nms[0].hostname[0] == '\0' + && dcc_clnt_info->grey.nms[0].hostname[0] != '\0') { + grey_on = 1; + fgs |= DCC_CLNT_FG_GREY; + rdy_done = 0; + } + + dcc_ctxts_lock(); + if (rdy_done) { + need_unlock = 0; + } else { + dcc_emsg[0] = '\0'; + need_unlock = dcc_clnt_rdy(dcc_emsg, ctxt, fgs); + if (!dcc_clnt_info) { + dcc_rel_ctxt(ctxt); + ctxt = 0; + dcc_ctxts_unlock(); + dcc_error_msg("%s", dcc_emsg); + return 0; + } + } + info_flags = dcc_clnt_info->flags; + if (!(fgs & DCC_CLNT_FG_NO_PICK_SRVR)) + map_changed = 0; + + if (need_unlock && !dcc_info_unlock(0)) { + dcc_rel_ctxt(ctxt); + ctxt = 0; + dcc_ctxts_unlock(); + dcc_error_msg("%s", dcc_emsg); + return 0; + } + + /* check the other (greylist or not) server */ + if (which_map != MAP_TMP) { + dcc_emsg[0] = '\0'; + if (!dcc_clnt_rdy(dcc_emsg, ctxt, fgs ^ DCC_CLNT_FG_GREY)) { + if (dcc_clnt_debug > 1) + dcc_error_msg("%s", dcc_emsg); + } else { + dcc_info_unlock(0); + } + } + + dcc_ctxts_unlock(); + return 1; +} + + + +static void +fix_info(DCC_SRVR_CLASS *class) +{ + map_changed = 1; + + if (class) { + dcc_force_measure_rtt(class); + } else { + dcc_force_measure_rtt(&dcc_clnt_info->dcc); + dcc_force_measure_rtt(&dcc_clnt_info->grey); + } + cdcc_unlock(1); + + /* repair addresses in the real map file now */ + if (!quiet + && which_map == MAP_INFO) + rdy_ctxt(DCC_CLNT_FG_BAD_SRVR_OK); +} + + + +/* compare ignoring case */ +static const char * /* 0 or mismatch in str */ +cmd_cmp(const char *str, const char *op) +{ + char op_c, str_c; + int len; + + len = 0; + for (;;) { + op_c = *op; + /* avoid tolower() to avoid build hassles on odd systems */ + if (op_c >= 'A' && op_c <= 'Z') + op_c += 'a'-'A'; + str_c = *str; + if (str_c == '\t') + str_c = ' '; + else if (str_c >= 'A' && str_c <= 'Z') + str_c += 'a'-'A'; + if (op_c != str_c) { + /* compress bursts of blanks */ + if (str_c == ' ' && len != 0 && *(op-1) == ' ') { + ++str; + continue; + } + return str; + } + if (op_c == '\0') + return 0; + ++op; + ++str; + ++len; + } +} + + + +/* Display our name for the server and its address, + * while suppressing some duplicates */ +static void +print_aop(SRVR_INX srvr_inx) /* -1 or server index */ +{ + const DCC_SRVR_CLASS *class; + char date_buf[40]; + char sustr[DCC_SU2STR_SIZE]; + const char *srvr_nm; + NAM_INX nam_inx; + + dcc_su2str2(sustr, sizeof(sustr), &op_result_su); + class = DCC_GREY2CLASS(grey_on); + /* Display the preferred server if srvr_inx is NO_SRVR */ + if (!GOOD_SRVR(class, srvr_inx)) + srvr_inx = class->srvr_inx; + if (GOOD_SRVR(class, srvr_inx) + && (GOOD_NAM(nam_inx = class->addrs[srvr_inx].nam_inx))) { + srvr_nm = class->nms[nam_inx].hostname; + if (strcmp(srvr_nm, sustr)) { + fputs(srvr_nm, stdout); + putchar(' '); + } + printf("%s\n server-ID %d", + dcc_su2str_err(&op_result_su), + class->addrs[srvr_inx].srvr_id); + } else { + printf("%s\n ", + dcc_su2str_err(&op_result_su)); + } + if (srvr.clnt_id != DCC_ID_ANON) + printf(" client-ID %d", srvr.clnt_id); + if (which_map == MAP_INFO) + printf(" %s", info_map_nm); + dcc_time2str(date_buf, sizeof(date_buf), " %X", + op_start.tv_sec); + fputs(date_buf, stdout); + putchar('\n'); +} + + + +static u_char /* 0=some kind of problem, 1=done */ +start_aop(DCC_AOPS aop, u_int32_t val1, SRVR_INX srvr_inx) +{ + DCC_OPS result; + + if (!rdy_ctxt(0)) + return 0; + + gettimeofday(&op_start, 0); + result = dcc_aop(dcc_emsg, ctxt, grey_on ? DCC_CLNT_FG_GREY : 0, + srvr_inx, clock_kludge, + aop, val1, 0, 0, 0, 0, 0, &aop_resp, &op_result_su); + gettimeofday(&op_end, 0); + + if (result == DCC_OP_INVALID + || result == DCC_OP_ERROR) { + dcc_error_msg("%s", dcc_emsg); + return 0; + } + + return 1; +} + + + +static void +fin_aop(SRVR_INX srvr_inx, /* index of server */ + u_char psrvr) /* 1=print server name */ +{ + if (quiet && !dcc_clnt_debug) + return; + + if (psrvr) + print_aop(srvr_inx); + + /* say what the server had to say */ + if (aop_resp.resp.val.string[0] >= ' ' + && aop_resp.resp.val.string[0] < 0x7f) { + fputs(aop_resp.resp.val.string, stdout); + putchar('\n'); + } + + if (dcc_clnt_debug) { + printf("%.2f ms\n", + ((op_end.tv_sec-op_start.tv_sec)*1000.0 + + (op_end.tv_usec-op_start.tv_usec)/1000.0)); + } + putchar('\n'); +} + + + +static u_char /* 0=some kind of problem, 1=done */ +do_aop(DCC_AOPS aop, u_int32_t val1, SRVR_INX srvr_inx, u_char psrvr) +{ + if (!start_aop(aop, val1, srvr_inx)) + return 0; + fin_aop(srvr_inx, psrvr); + return 1; +} + + + +static u_char /* 0=not enough power */ +ck_cmd_priv(const CMD_TBL_ENTRY *ce, + u_char privileged, /* 1=need good server-ID & password */ + u_char write_map) /* 1=write map, 2=write /var/dcc/map */ +{ + /* always call get_passwd() so we have always fetched a password + * fail if this command needs a good server-ID and password */ + if (!get_passwd(privileged)) { + dcc_error_msg("\"%s\" is a privileged server command"PRV_MSG, + ce->cmd, ids_path); + return 0; + } + + if (!write_map) + return 1; + + /* we can always write to our own throw-away map file */ + if (write_map == 1 && which_map == MAP_TMP) + return 1; + + if (0 > access(info_map_nm, R_OK) + && errno != ENOENT && errno != ENOTDIR) { + dcc_error_msg("\"%s\" is a privileged command changing %s", + ce->cmd, fnm2abs_err(0, info_map_nm)); + return 0; + } + return 1; +} + + + +static u_char /* 1=ok 0=bad command */ +cmd(const char *op) +{ + const char *arg, *help_str; + int op_num, j; + const CMD_TBL_ENTRY *ce; + + /* look for the string as a command and execute it if we find */ + ce = &cmds_tbl[0]; + for (op_num = 0; op_num < DIM(cmds_tbl); ++op_num) { + if (cmds_tbl[op_num].help_str) + ce = &cmds_tbl[op_num]; + arg = cmd_cmp(op, cmds_tbl[op_num].cmd); + /* if the command table entry and the command completely + * matched, then infer a null argument */ + if (!arg) { + if (!ck_cmd_priv(ce, ce->privileged, ce->write_map)) + return 0; + if (ce->args != 1) { + j = ce->fnc("", ce); + if (j >= 0) + return j; + } + help_cmd(op, 0); + return 0; + } + /* If the command table entry is an initial sustring of + * the user's command, then the rest of the command must + * start with white space or '='. (Allow '=' to let + * homedir/fix-map not need use `eval` to quote blanks + * `eval` in bash loses exit status. + * Trim and use the rest of the string as the argument */ + j = strspn(arg, DCC_WHITESPACE"="); + if (j) { + if (ce->args == 2) { + help_cmd(op, 0); /* arg not allowed */ + return 0; + } + if (!ck_cmd_priv(ce, ce->privileged, ce->write_map)) + return 0; + j = ce->fnc(arg+j, ce); + if (j >= 0) + return j; + help_cmd(op, 0); + return 0; + } + } + + + /* otherwise try to interpret it as a DCC administrative packet */ + op_num = 0; + help_str = ""; + for (;;) { + if (op_num >= DIM(aops_tbl)) { + dcc_error_msg("unrecognized command \"%s\"", op); + return 0; + } + /* do a command */ + if (aops_tbl[op_num].help_str) { + help_str = aops_tbl[op_num].help_str; + if (*help_str == '\0') + help_str = aops_tbl[op_num].op; + } + if (!cmd_cmp(op, aops_tbl[op_num].op)) + break; + ++op_num; + } + + /* send an administrative request to the server */ + if (!get_passwd(aops_tbl[op_num].privileged)) { + dcc_error_msg("\"%s\" is a privileged operation"PRV_MSG, + help_str, ids_path); + return 0; + } + + /* try to send it */ + return do_aop(aops_tbl[op_num].aop, aops_tbl[op_num].val, + NO_SRVR, 1); +} + + + +static u_char /* 0=bad command, 1=ok */ +do_cmds(char *cmd_buf) +{ + char *next_cmd, *cur_cmd, *cmd_end; + char c; + + next_cmd = cmd_buf; + for (;;) { + cur_cmd = next_cmd + strspn(next_cmd, DCC_WHITESPACE";"); + + if (*cur_cmd == '#' || *cur_cmd == '\0') + return 1; + + next_cmd = cur_cmd + strcspn(cur_cmd, ";\n\r"); + cmd_end = next_cmd; + next_cmd += strspn(next_cmd, ";\n\r"); + + /* null terminate and trim trailing white space from + * command or arg */ + do { + *cmd_end-- = '\0'; + c = *cmd_end; + } while (cmd_end >= cur_cmd + && strchr(DCC_WHITESPACE";", c)); + + if (*cur_cmd == '\0') /* ignore blank commands */ + continue; + + if (!cmd(cur_cmd)) + return 0; + } +} + + + +static int +help_cmd_print(int pos, const char *str) +{ +#define HELP_COL 24 + int col, nl; + + if (str[0] == '\n') { + nl = 100; + ++str; + } else { + nl = 0; + } + col = strlen(str)+1; + col += HELP_COL - (col % HELP_COL); + pos += col; + if (pos > 78) { + putchar('\n'); + pos = col; + } + printf("%-*s", col, str); + pos += nl; + + return pos; +#undef HELP_COL +} + + + +static int +help_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + int i, pos; + const char *help_str; + const char *p; + + /* say something about one command */ + if (arg) { + help_str = ""; + for (i = 0; i < DIM(cmds_tbl); ++i) { + if (cmds_tbl[i].help_str) + help_str = cmds_tbl[i].help_str; + p = cmd_cmp(arg, cmds_tbl[i].cmd); + if (!p || *p == ' ' || *p == '\t') { + while (*help_str == '\n' || *help_str == '-') + ++help_str; + printf("usage: %s\n", help_str); + if (cmds_tbl[i].fnc == stats_cmd) + printf(stats_help); + return 1; + } + } + for (i = 0; i < DIM(aops_tbl); ++i) { + if (aops_tbl[i].help_str) { + help_str = aops_tbl[i].help_str; + if (*help_str == '\0') + help_str = aops_tbl[i].op; + } + p = cmd_cmp(arg, aops_tbl[i].op); + if (!p || *p == ' ' || *p == '\t') { + while (*help_str == '\n' || *help_str == '-') + ++help_str; + printf("usage: %s\n", help_str); + return 1; + } + } + } + + /* talk about all of the commands */ + printf(" version "DCC_VERSION"\n"); + pos = 0; + for (i = 0; i < DIM(cmds_tbl); ++i) { + if (cmds_tbl[i].help_str + && cmds_tbl[i].help_str[0] != '-') + pos = help_cmd_print(pos, cmds_tbl[i].help_str); + } + for (i = 0; i < DIM(aops_tbl); ++i) { + help_str = aops_tbl[i].help_str; + if (!help_str) + continue; + if (*help_str == '\0') + help_str = aops_tbl[i].op; + pos = help_cmd_print(pos, help_str); + } + putchar('\n'); + + return 1; +} + + + +static int NRATTRIB +exit_cmd(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + exit(EX_OK); +#ifndef HAVE_GCC_ATTRIBUTES + return -1; +#endif +} + + +static int +grey_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + if (arg[0] == '\0') { + printf(" Greylist mode %s%s\n", + grey_on ? "on" : "off", + grey_set ? "" : " by default"); + return 1; + } + if (!strcmp(arg, "off")) { + grey_on = 0; + grey_set = 1; + set_which_map(which_map); + } else if (!strcmp(arg, "on")) { + grey_on = 1; + grey_set = 1; + set_which_map(which_map); + } else { + return -1; + } + if (!port_set) + srvr.port = DCC_GREY2PORT(grey_on); + return 1; +} + + + +static int +homedir_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + if (arg[0] != '\0') { + if (!dcc_cdhome(0, arg, 1)) + return 0; + if (ids_nm && !set_ids_path(dcc_emsg, ids_nm)) + dcc_error_msg("%s", dcc_emsg); + set_which_map(MAP_INFO); + } + printf(" homedir=%s\n", dcc_homedir); + return 1; +} + + + +/* set name of map file */ +static int +file_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + if (arg[0] == '\0') { + if (which_map == MAP_INFO) + printf(" using map file %s\n", + fnm2abs_err(0, info_map_nm)); + else + printf(" map file %s but using temporary file\n", + fnm2abs_err(0, info_map_nm)); + return 1; + } + + BUFCPY(info_map_nm, arg); + set_which_map(MAP_INFO); + return 1; +} + + + +/* create a new client map or parameter file */ +static int +new_map_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + if (arg[0] == '\0') + arg = DCC_MAP_NM_DEF; + + dcc_rel_priv(); + if (!dcc_create_map(dcc_emsg, arg, 0, 0, 0, 0, 0, 0, info_flags)) { + dcc_error_msg("%s", dcc_emsg); + return 0; + } + BUFCPY(info_map_nm, arg); + set_which_map(MAP_INFO); + if (!quiet) + printf(" created %s\n", fnm2abs_err(0, info_map_nm)); + return 1; +} + + + +static int +info_work(const char *arg, int fgs) +{ + DCC_CLNT_INFO info; + u_char dcc, srcbad, names; + + if (*arg == '\0') { + names = 0; + } else if (!strcmp(arg, "-N")) { + names = 1; + } else { + return -1; + } + + if (!rdy_ctxt(fgs)) + return 0; + + /* Snapshot the data and then release it while we print it. */ + dcc_ctxts_lock(); + if (!dcc_info_lock(0)) { + dcc_ctxts_lock(); + return 0; + } + memcpy(&info, dcc_clnt_info, sizeof(info)); + srcbad = ctxt && (ctxt->flags & DCC_CTXT_SRCBAD); + cdcc_unlock(1); + + dcc_rel_priv(); + if (which_map == MAP_INFO) { + if (info.dcc.nms[0].hostname[0] != '\0' + || !grey_on) { + dcc_print_info(info_map_nm, &info, + quiet, 0, srcbad, names, + 0 <= access(info_map_nm, R_OK)); + dcc = 1; + } else { + dcc = 0; + } + if (info.grey.nms[0].hostname[0] != '\0' + || grey_on) { + if (dcc && !quiet) + fputs("\n################\n", stdout); + dcc_print_info(info_map_nm, &info, + quiet, 1, srcbad, names, + 0 <= access(info_map_nm, R_OK)); + } + } else { + dcc_print_info(0, &info, quiet, grey_on, srcbad, names, 1); + } + if (!quiet) + putchar('\n'); + return 1; +} + + + +/* server hostname */ +static int +host_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + DCC_SRVR_NM nm; + int error; + + if (arg[0] == '\0') { + if (which_map == MAP_INFO) + return info_work(arg, DCC_CLNT_FG_BAD_SRVR_OK) ; + printf(" %s server hostname \"%s\"\n", + grey_on ? "greylist" : "DCC", srvr.hostname); + return 1; + } + if (!strcmp(arg, "-")) { + set_which_map(MAP_INFO); + if (!init_map(1, 0)) { + set_which_map(MAP_TMP); + return 0; + } + return 1; + } + + arg = dcc_parse_nm_port(0, arg, 0, + nm.hostname, sizeof(nm.hostname), + &nm.port, 0, 0, + 0, 0); + if (!arg) + return 0; + arg += strspn(arg, DCC_WHITESPACE); + if (*arg != '\0') + return 0; + + set_which_map(MAP_TMP); + memcpy(srvr.hostname, nm.hostname, sizeof(srvr.hostname)); + if (nm.port != 0) { + srvr.port = nm.port; + port_set = 1; + } + + /* go with the flow for IPv6 */ + dcc_host_lock(); + if (!dcc_get_host(nm.hostname, + (info_flags & DCC_INFO_FG_IPV6) ? 2 : 3, + &error)) { + dcc_host_unlock(); + dcc_error_msg("%s: %s", nm.hostname, DCC_HSTRERROR(error)); + } else { + if (dcc_hostaddrs[0].sa.sa_family == AF_INET) + info_flags &= ~DCC_INFO_FG_IPV6; + else + info_flags |= DCC_INFO_FG_IPV6; + dcc_host_unlock(); + } + + return 1; +} + + + +/* server port # */ +static int +port_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + u_int port; + + if (arg[0] == '\0') { + if (which_map == MAP_INFO) + return info_work(arg, DCC_CLNT_FG_BAD_SRVR_OK) ; + printf(" port=%d\n", ntohs(srvr.port)); + return 1; + } + + port = dcc_get_port(0, arg, DCC_GREY2PORT(grey_on), 0, 0); + if (port == DCC_GET_PORT_INVALID) + return 0; + + srvr.port = port; + port_set = 1; + set_which_map(MAP_TMP); + return 1; +} + + + +static int +ipv6_cmd(const char *arg, const CMD_TBL_ENTRY *ce) +{ + u_char new_use_ipv6; + + if (arg[0] == '\0') { + if (!init_map(1, 0)) + return 0; + printf(" IPv6 %s\n", + (info_flags & DCC_INFO_FG_IPV6) ? "on" : "off"); + return 1; + } + + if (!strcasecmp(arg, "off")) { + new_use_ipv6 = 0; + } else if (!strcasecmp(arg, "on")) { + new_use_ipv6 = DCC_INFO_FG_IPV6; + } else { + return -1; + } + + if (!ck_cmd_priv(ce, 0, 1)) + return 0; + + if (!init_map(1, 1)) + return 0; + if ((dcc_clnt_info->flags & DCC_INFO_FG_IPV6) != new_use_ipv6) { + dcc_clnt_info->flags ^= DCC_INFO_FG_IPV6; + info_flags = dcc_clnt_info->flags; + fix_info(0); + } else if (!cdcc_unlock(1)) { + return 0; + } + + if (rdy_ctxt(0) + && (dcc_clnt_info->flags & DCC_INFO_FG_IPV6) != new_use_ipv6) { +#ifdef NO_IPV6 + dcc_error_msg("IPv6 switch not changed;" + " No IPv6 support in this system?"); +#else + dcc_error_msg("IPv6 switch not changed."); +#endif + return 0; + } + + return 1; +} + + + +static u_char +ck_new_src(DCC_IP *new_ip, const char *arg, u_char use_ipv6) +{ + SOCKET soc; + DCC_SOCKU su; + int error; + + memset(new_ip, 0, sizeof(*new_ip)); + if (!strcmp(arg, "-")) + return 1; + + dcc_host_lock(); + if (!dcc_get_host(arg, use_ipv6 ? 1 : 0, &error)) { + dcc_host_unlock(); + dcc_error_msg("%s: %s", arg, DCC_HSTRERROR(error)); + return 0; + } + if (use_ipv6) + dcc_ipv4sutoipv6(&su, &dcc_hostaddrs[0]); + else + dcc_ipv6sutoipv4(&su, &dcc_hostaddrs[0]); + dcc_su2ip(new_ip, &su); + dcc_host_unlock(); + + soc = INVALID_SOCKET; + if (0 >= dcc_udp_bind(dcc_emsg, &soc, &su, 0)) { + dcc_error_msg("%s", dcc_emsg); + return 0; + } + closesocket(soc); + + return 1; +} + + + +static int +src_cmd(const char *arg, const CMD_TBL_ENTRY *ce) +{ + DCC_IP new_ip; + char sustr[DCC_SU2STR_SIZE]; + + if (arg[0] == '\0') { + if (!init_map(1, 0)) + return 0; + + if (dcc_clnt_info->src.family == AF_UNSPEC) { + printf(" no source address specified\n"); + } else { + /* display what the system actually uses */ + printf(" source address=%s%s\n", + dcc_su2str2(sustr, sizeof(sustr), + &ctxt->bind_su), + (ctxt->flags & DCC_CTXT_SRCBAD) + ? " "DCC_INFO_USE_SRCBAD : ""); + } + return 1; + } + + if (!ck_new_src(&new_ip, arg, DCC_INFO_IPV6())) + return 0; + + if (!ck_cmd_priv(ce, 0, 1)) + return 0; + + if (!init_map(1, 1)) + return 0; + src = new_ip; + dcc_clnt_info->src = src; + + fix_info(0); + return 1; +} + + + +static int +socks_cmd(const char *arg, const CMD_TBL_ENTRY *ce) +{ + u_char new_use_socks; + + if (arg[0] == '\0') { + if (!init_map(1, 0)) + return 0; + printf(" SOCKS %s\n", + (info_flags & DCC_INFO_FG_SOCKS) ? "on" : "off"); + return 1; + } + + if (!strcmp(arg, "off")) { + new_use_socks = 0; + } else if (!strcmp(arg, "on")) { + new_use_socks = DCC_INFO_FG_SOCKS; + } else { + return -1; + } + + if (!ck_cmd_priv(ce, 0, 1)) + return 0; + + if (!init_map(1, 1)) + return 0; + if ((dcc_clnt_info->flags & DCC_INFO_FG_SOCKS) == new_use_socks) + return cdcc_unlock(1); /* nothing to do */ + + dcc_clnt_info->flags ^= DCC_INFO_FG_SOCKS; + info_flags = dcc_clnt_info->flags; + + fix_info(0); + return 1; +} + + + +static int +passwd_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + DCC_PASSWD new_passwd; + + if (arg[0] == '\0') { + if (which_map == MAP_INFO) { + printf(" using password in %s\n", + fnm2abs_err(0, info_map_nm)); + if (passwd_set) + printf(" but the password for explicitly" + " named servers is "DCC_PASSWD_PAT"\n", + passwd); + } else { + if (passwd_set) + printf(" password "DCC_PASSWD_PAT"\n", + passwd); + else + printf(" password not set\n"); + } + return 1; + } + + arg = parse_passwd(0, new_passwd, arg, "password", 0, 0); + if (!arg || *arg != '\0') + return -1; + memcpy(passwd, new_passwd, sizeof(passwd)); + passwd_set = 1; + set_which_map(MAP_TMP); + return 1; +} + + + +static int +id_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + DCC_CLNT_ID id; + + if (arg[0] == '\0') { + printf(" ID=%d\n", srvr_clnt_id); + return 1; + } + + id = dcc_get_id(0, arg, 0, 0); + if (id == DCC_ID_INVALID) + return -1; + + srvr_clnt_id = id; + set_which_map(MAP_TMP); + return 1; +} + + + +static int +debug_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + char debug_str[24]; + char ttl_str[24]; + int new_ttl, new_debug; + char *p; + + if (arg[0] == '\0') { + if (!dcc_clnt_debug) + snprintf(debug_str, sizeof(debug_str), + "debug off"); + else if (dcc_clnt_debug == 1) + snprintf(debug_str, sizeof(debug_str), + "debug on"); + else + snprintf(debug_str, sizeof(debug_str), + "debug on+%d\n", dcc_clnt_debug-1); + if (dcc_debug_ttl != 0) + snprintf(ttl_str, sizeof(ttl_str), + " TTL=%d", dcc_debug_ttl); + else + ttl_str[0] = '\0'; + printf(" %s%s\n", debug_str, ttl_str); + return 1; + } + + new_ttl = dcc_debug_ttl; + new_debug = dcc_clnt_debug; + for (;;) { + if (!CLITCMP(arg, "off")) { + new_debug = 0; + arg += LITZ("off"); + } else if (!CLITCMP(arg, "on")) { + ++new_debug; + arg += LITZ("on"); + } else if (!CLITCMP(arg, "ttl=")) { + new_ttl = strtoul(arg+LITZ("ttl="), &p, 10); +#if defined(IPPROTO_IP) && defined(IP_TTL) + if (new_ttl < 256) + arg = p; +#else + printf(" TTL setting not supported\n"); +#endif + } + + if (*arg == '\0') + break; + if (*arg == ' ' || *arg == '\t') { + arg += strspn(arg, DCC_WHITESPACE); + } else { + return -1; + } + } + dcc_debug_ttl = new_ttl; + if (dcc_debug_ttl != 0) + set_which_map(MAP_TMP); + dcc_clnt_debug = new_debug; + if (dcc_clnt_debug > 1) + printf(" debug on+%d\n", dcc_clnt_debug-1); + return 1; +} + + + +static int +no_fail_cmd(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + if (!init_map(1, 1)) + return 0; + DCC_GREY2CLASS(grey_on)->fail_time= 0; + cdcc_unlock(1); + return 1; +} + + + +static int +quiet_cmd(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + if (arg[0] == '\0') { + printf(" %s\n", quiet ? "on" : "off"); + return 1; + } else if (!CLITCMP(arg, "on")) { + quiet = 1; + return 1; + } else if (!CLITCMP(arg, "off")) { + quiet = 0; + return 1; + } + return -1; +} + + + +static int +ckludge_cmd(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + char *p; + long l; + + if (arg[0] == '\0') { + printf(" clock kludge=%d\n", (int)clock_kludge); + return 1; + } + + l = strtol(arg, &p, 10); + if (*p != '\0') { + dcc_error_msg("invalid clock kludge \"%s\"", arg); + return -1; + } + clock_kludge = l; + return 1; +} + + + +static int +delete_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + DCC_SRVR_CLASS *class; + DCC_SRVR_NM nm, *nmp; + DCC_SRVR_ADDR *addr; + u_char del_grey; + + del_grey = grey_on; + if (!dcc_parse_srvr_nm(dcc_emsg, &nm, &del_grey, arg, 0, 0)) { + dcc_error_msg("%s", dcc_emsg); + return 0; + } + + /* map and lock */ + set_which_map(MAP_INFO); + if (!init_map(1, 1)) + return 0; + + class = DCC_GREY2CLASS(del_grey); + for (nmp = class->nms; nmp <= LAST(class->nms); ++nmp) { + if (strcasecmp(nmp->hostname, nm.hostname) + || nmp->port != nm.port) + continue; + + /* Found it. */ + + /* zap its IP addresses so they won't be used + * if resolving the remaining names fails */ + for (addr = class->addrs; addr <= LAST(class->addrs); ++addr) { + if (addr->nam_inx == nmp - class->nms) { + addr->rtt = DCC_RTT_BAD; + addr->nam_inx = NO_NAM; + } + } + if (nmp != LAST(class->nms)) + memmove(nmp, nmp+1, + (LAST(class->nms) - nmp)*sizeof(*nmp)); + memset(LAST(class->nms), 0, sizeof(*nmp)); + ++class->gen; + fix_info(class); + return 1; + } + + dcc_error_msg("server \"%s,%d\" not found", + nm.hostname, ntohs(nm.port)); + cdcc_unlock(1); + return 0; +} + + + +static int +add_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + DCC_SRVR_CLASS *class; + DCC_SRVR_NM nm, *nmp, *tgt_nmp; + u_char add_grey; + + add_grey = grey_set && grey_on; + + if (0 >= dcc_parse_srvr_nm(dcc_emsg, &nm, &add_grey, arg, 0, 0)) { + dcc_error_msg("%s", dcc_emsg); + return 0; + } + if (nm.clnt_id == DCC_ID_ANON && add_grey) { + dcc_error_msg("anonymous client-ID invalid" + " for Greylist server %s", + nm.hostname); + return 0; + } + + /* map and lock the information */ + set_which_map(MAP_INFO); + if (!init_map(1, 1)) + return 0; + + /* look for the old entry or a new, free entry */ + class = DCC_GREY2CLASS(add_grey); + tgt_nmp = 0; + for (nmp = class->nms; nmp <= LAST(class->nms); ++nmp) { + if (nmp->hostname[0] == '\0') { + if (!tgt_nmp) + tgt_nmp = nmp; + continue; + } + if (!strcmp(nmp->hostname, nm.hostname) + && nmp->port == nm.port) { + printf(" overwriting existing entry\n"); + tgt_nmp = nmp; + break; + } + } + + if (tgt_nmp) { + memcpy(tgt_nmp, &nm, sizeof(*tgt_nmp)); + fix_info(class); + return 1; + } + + cdcc_unlock(1); + if (add_grey) + dcc_error_msg("too many Greylist server names"); + else + dcc_error_msg("too many DCC server names"); + return 0; +} + + + +static void +add_new_nms(const DCC_SRVR_NM new_nms[DCC_MAX_SRVR_NMS], + DCC_SRVR_NM old_nms[DCC_MAX_SRVR_NMS]) +{ + const DCC_SRVR_NM *new_nmp; + DCC_SRVR_NM *old_nmp; + + for (new_nmp = new_nms; + new_nmp < &new_nms[DCC_MAX_SRVR_NMS] + && new_nmp->hostname[0] != '\0'; + ++new_nmp) { + for (old_nmp = old_nms; + old_nmp <= &old_nms[DCC_MAX_SRVR_NMS]; + ++old_nmp) { + if (old_nmp->hostname[0] == '\0' + || (!strcmp(old_nmp->hostname, new_nmp->hostname) + && old_nmp->port == new_nmp->port)) { + memcpy(old_nmp, new_nmp, sizeof(*old_nmp)); + break; + } + } + } +} + + + +static int +load_cmd(const char *lfile, const CMD_TBL_ENTRY *ce UATTRIB) +{ + u_char new_info_flags, load_grey; + int flags_set; + DCC_SRVR_NM new_nm; + DCC_SRVR_NM dcc_nms[DCC_MAX_SRVR_NMS]; + int num_dcc_nms; + DCC_SRVR_NM grey_nms[DCC_MAX_SRVR_NMS]; + int num_grey_nms; + char src_addr[INET6_ADDRSTRLEN+1]; + char buf[sizeof(DCC_SRVR_NM)*3]; + DCC_IP new_src; + const char *bufp, *cp; + FILE *f; + int fd, lno; + + if (*lfile == '\0') + return -1; + + dcc_rel_priv(); + if (!strcmp(lfile,"-")) { + lfile = 0; + fd = dup(fileno(stdin)); + if (fd < 0) { + dcc_error_msg("dup(stdin): %s", ERROR_STR()); + return 0; + } + f = fdopen(fd, "r"); + if (!f) { + dcc_error_msg("fdopen(): %s", ERROR_STR()); + return 0; + } + } else { + f = dcc_open_srvr_nm(dcc_emsg, lfile); + if (!f) { + dcc_error_msg("%s", dcc_emsg); + return 0; + } + } + + /* parse the text file to create a pair of lists of server names */ + flags_set = 0; + new_info_flags = info_flags; + num_dcc_nms = 0; + memset(dcc_nms, 0, sizeof(dcc_nms)); + num_grey_nms = 0; + memset(grey_nms, 0, sizeof(grey_nms)); + memset(&new_src, 0, sizeof(new_src)); + lno = 0; + for (;;) { + bufp = fgets(buf, sizeof(buf), f); + if (!bufp) { + if (ferror(f)) { + dcc_error_msg("fgets(%s): %s", + !lfile + ? "STDIN" + : fnm2abs_err(0, lfile), + ERROR_STR()); + fclose(f); + return 0; + } + break; + } + + ++lno; + + /* skip blank lines and comments */ + bufp += strspn(bufp, DCC_WHITESPACE); + if (*bufp == '\0' || *bufp == '#') + continue; + + /* look for flags in the first non-comment line */ + if (!flags_set++) { + cp = bufp; + if (!CLITCMP(cp, DCC_INFO_USE_IPV4)) { + cp += LITZ(DCC_INFO_USE_IPV4); + new_info_flags &= ~DCC_INFO_FG_IPV6; + } else if (!CLITCMP(cp, DCC_INFO_USE_IPV6)) { + cp += LITZ(DCC_INFO_USE_IPV6); + new_info_flags |= DCC_INFO_FG_IPV6; + } else { + ++flags_set; + } + if (flags_set == 1) { + /* We found "IPv6 on" or "off". + * Look for "use SOCKS" and "src=x.y.z.w" */ + cp += strspn(cp, DCC_WHITESPACE); + if (!CLITCMP(cp, DCC_INFO_USE_SOCKS)) { + new_info_flags |= DCC_INFO_FG_SOCKS; + cp += LITZ(DCC_INFO_USE_SOCKS); + cp += strspn(cp, DCC_WHITESPACE); + } + if (!CLITCMP(cp, DCC_INFO_USE_SRC)) { + cp += LITZ(DCC_INFO_USE_SRC); + cp = dcc_parse_word(dcc_emsg, + src_addr, + sizeof(src_addr), + cp, 0, 0, 0); + if (!cp) { + dcc_error_msg("%s", dcc_emsg); + continue; + } + if (!CLITCMP(cp, DCC_INFO_USE_SRCBAD)) { + cp += LITZ(DCC_INFO_USE_SRCBAD); + cp += strspn(cp, DCC_WHITESPACE); + } + ck_new_src(&new_src, src_addr, + (new_info_flags + & DCC_INFO_FG_SOCKS)); + } + } + if (*cp == '\0') + continue; + /* the first non-comment line must be a server name */ + } + + load_grey = 0; + if (0 >= dcc_parse_srvr_nm(dcc_emsg, &new_nm, &load_grey, + bufp, lfile, lno)) { + dcc_error_msg("%s", dcc_emsg); + fclose(f); + return 0; + } + if (load_grey) { + if (new_nm.clnt_id == DCC_ID_ANON) { + dcc_error_msg("anonymous client-ID invalid" + " for Greylist server %s%s", + new_nm.hostname, + fnm_lno(&fnm_buf, lfile, lno)); + fclose(f); + return 0; + } + if (num_grey_nms >= DIM(grey_nms)) { + dcc_error_msg("too many Greylist server names" + "%s", + fnm_lno(&fnm_buf, lfile, lno)); + fclose(f); + return 0; + } + grey_nms[num_grey_nms++] = new_nm; + } else { + if (num_dcc_nms >= DIM(dcc_nms)) { + dcc_error_msg("too many DCC server names%s", + fnm_lno(&fnm_buf, lfile, lno)); + fclose(f); + return 0; + } + dcc_nms[num_dcc_nms++] = new_nm; + } + } + fclose(f); + if (num_grey_nms == 0 && num_dcc_nms == 0) { + dcc_error_msg("no DCC server names%s", + fnm_lno(&fnm_buf, lfile, lno)); + return 0; + } + + /* create the map, without set-UID powers to prevent games, + * and then lock, install, and unlock the information */ + dcc_rel_priv(); + + if (which_map != MAP_INFO) + set_which_map(MAP_INFO); + if (!init_map(0, 1)) { + /* create a new map */ + if (!dcc_create_map(0, info_map_nm, 0, + 0, 0, 0, 0, &new_src, new_info_flags)) + return 0; + printf(" created %s\n", fnm2abs_err(0, info_map_nm)); + if (!init_map(1, 1)) + return 0; + } + + /* merge the old and new entries */ + add_new_nms(grey_nms, dcc_clnt_info->grey.nms); + add_new_nms(dcc_nms, dcc_clnt_info->dcc.nms); + dcc_clnt_info->flags = info_flags = new_info_flags; + if (new_src.family != AF_UNSPEC) + dcc_clnt_info->src = new_src; + + fix_info(0); + + if (!quiet) { + if (!lfile) + printf("##################\n\n"); + return info_work("", DCC_CLNT_FG_BAD_SRVR_OK) ; + } + return 1; +} + + + +static int +info_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + /* map, copy, and unlock the information + * prefer to talk to the server, but don't wait + * unless we have changed the file */ + return info_work(arg, + map_changed + ? DCC_CLNT_FG_BAD_SRVR_OK + : (DCC_CLNT_FG_NO_PICK_SRVR + | DCC_CLNT_FG_BAD_SRVR_OK)); +} + + + +static int +rtt_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + if (!init_map(1, 1)) + return 0; + dcc_force_measure_rtt(&dcc_clnt_info->dcc); + dcc_force_measure_rtt(&dcc_clnt_info->grey); + cdcc_unlock(1); + + /* wait to talk to the server, but don't insist */ + return info_work(arg, quiet ? DCC_CLNT_FG_BAD_SRVR_OK : 0); +} + + +/* delete a checksum */ +static int /* 1=ok, 0=bad checksum, -1=fatal */ +delck_sub(DCC_EMSG emsg, DCC_WF *wf UATTRIB, + DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts UATTRIB) +{ + struct timeval cmd_start, cmd_end; + char type_buf[DCC_XHDR_MAX_TYPE_LEN]; + char ck_buf[sizeof(DCC_SUM)*3+2]; + DCC_DELETE del; + DCC_OP_RESP resp; + char ob[DCC_OPBUF]; + u_char result; + + printf(" deleting %s %s\n", + dcc_type2str(type_buf, sizeof(type_buf), type, 0, 1, grey_on), + dcc_ck2str(ck_buf, sizeof(ck_buf), type, sum, 0)); + + memset(&del, 0, sizeof(del)); + gettimeofday(&cmd_start, 0); + del.date = htonl(cmd_start.tv_sec); + del.ck.type = type; + del.ck.len = sizeof(del.ck); + memcpy(&del.ck.sum, sum, sizeof(DCC_SUM)); + result = dcc_clnt_op(emsg, ctxt, DCC_CLNT_FG_NO_FAIL, + 0, 0, 0, &del.hdr, sizeof(del), + DCC_OP_DELETE, &resp, sizeof(resp)); + gettimeofday(&cmd_end, 0); + if (!result) { + dcc_error_msg("%s", dcc_emsg); + } else { + switch (resp.hdr.op) { + case DCC_OP_OK: + break; + + case DCC_OP_ERROR: + dcc_error_msg(" %.*s", + (ntohs(resp.hdr.len) + -(int)(sizeof(resp.error) + - sizeof(resp.error.msg))), + resp.error.msg); + result = 0; + break; + + default: + dcc_error_msg("unexpected response: %s", + dcc_hdr_op2str(ob,sizeof(ob), &resp.hdr)); + result = 0; + break; + } + } + + if (dcc_clnt_debug) { + printf("%.2f ms\n", + ((cmd_end.tv_sec-cmd_start.tv_sec)*1000.0 + + (cmd_end.tv_usec-cmd_start.tv_usec)/1000.0)); + } + return result; +} + + + +/* delete a simple checksum */ +static int +delck_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + char type_str[DCC_XHDR_MAX_TYPE_LEN+1]; + + if (*arg == '\0') + return -1; + arg = dcc_parse_word(0, type_str, sizeof(type_str), + arg, 0, 0, 0); + if (!arg) + return -1; + + if (!rdy_ctxt(0)) + return 0; + return 0 < dcc_parse_hex_ck(0, &cmn_wf, + type_str, dcc_str2type_del(type_str, -1), + arg, 0, delck_sub); +} + + + +static int +sleep_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + double s; + char *p; + + s = strtod(arg, &p); + if (*p != '\0' || s < 0.001 || s > 1000) + return -1; + usleep((u_int)(s*1000000.0)); + return 1; +} + + + + +static const u_char * +client_unpack4(const u_char *cp, + u_int *vp) +{ + u_char c; + u_int v; + int shift; + + v = 0; + shift = 0; + do { + c = *cp++; + v |= (c & 0x7f) << shift; + shift += 7; + } while (c & 0x80); + + *vp = v; + return cp; +} + + + +static int +client_unpack(const u_char *cp0, + u_char *flagsp, + u_int *clnt_idp, + u_int *last_usedp, + u_int *requestsp, + u_int *nopsp, + u_char *versp, + DCC_SOCKU *su) +{ + const u_char *cp; + u_char flags; + u_int v; + struct in6_addr in6_addr; + struct in_addr in_addr; + +#ifdef DCC_PKT_VERSION6 + if (aop_resp.hdr.pkt_vers <= DCC_PKT_VERSION6) { +#define CPY2(s) ((s[0]<<8) | s[1]) +#define CPY3(s) ((s[0]<<16) | (s[1]<<8) | s[2]) +#define CPY4(s) ((s[0]<<24) | (s[1]<<16) | (s[2]<<8) | s[3]) + const DCC_ADMN_RESP_CLIENTSv6 *cl; + + cl = (DCC_ADMN_RESP_CLIENTSv6 *)cp0; + flags = cl->flags; + *flagsp = flags & (DCC_ADMN_RESP_CLIENTS_BL + | DCC_ADMN_RESP_CLIENTS_SKIP); + *clnt_idp = CPY4(cl->clnt_id); + *last_usedp = CPY4(cl->last_used); + if (flags & DCC_ADMN_RESP_CLIENTS_SKIP) { + /* skip place keepers */ + *last_usedp = CPY3(cl->requests); + *requestsp = 0; + } else { + *requestsp = CPY3(cl->requests); + } + *nopsp = CPY2(cl->nops); + if (flags & DCC_ADMN_RESP_CLIENTS_IPV6) { + memcpy(&in6_addr, &cl->addr, sizeof(in6_addr)); + dcc_mk_su(su, AF_INET6, &in6_addr, 0); + return (sizeof(*cl) - sizeof(cl->addr) + + sizeof(cl->addr.ipv6)); + } + memcpy(&in_addr, &cl->addr, sizeof(in_addr)); + dcc_mk_su(su, AF_INET, &in_addr, 0); + return (sizeof(*cl) - sizeof(cl->addr) + + sizeof(cl->addr.ipv4)); + } +#undef CPY2 +#undef CPY3 +#undef CPY4 +#endif + + cp = cp0; + flags = *cp++; + *flagsp = flags & (DCC_ADMN_RESP_CLIENTS_BL + | DCC_ADMN_RESP_CLIENTS_BAD + | DCC_ADMN_RESP_CLIENTS_SKIP + | DCC_ADMN_RESP_CLIENTS_LAST); + /* if the version is absent, + * then it must be the same as the previous value */ + if (flags & DCC_ADMN_RESP_CLIENTS_VERS) + *versp = *cp++; + v = *cp++ << 24; + v |= *cp++ << 16; + v |= *cp++ << 8; + v |= *cp++; + *last_usedp = v; + if ((flags & DCC_ADMN_RESP_CLIENTS_ID1) != 0) + *clnt_idp = DCC_ID_ANON; + else + cp = client_unpack4(cp, clnt_idp); + cp = client_unpack4(cp, requestsp); + cp = client_unpack4(cp, nopsp); + if (flags & DCC_ADMN_RESP_CLIENTS_IPV6) { + memcpy(&in6_addr, cp, sizeof(in6_addr)); + dcc_mk_su(su, AF_INET6, &in6_addr, 0); + cp += 16; + } else { + memcpy(&in_addr, cp, sizeof(in_addr)); + dcc_mk_su(su, AF_INET, &in_addr, 0); + cp += 4; + } + return cp - cp0; +} + + + +/* get the server's list of recent clients */ +static int +clients_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + u_char nonames, sort, ids, req_flags; + u_char passed_flags, passed_max_clients, passed_thold, passed_cidr; + struct in6_addr addr6; + DCC_AOP_CLIENTS_CIDR addr_bits; + u_int max_clients, thold; + u_int total, subtotal; + u_int max_ops, max_nops; + int ops_width, nops_width; + u_int offset; /* next client wanted from server */ + u_int num_clients; + DCC_SOCKU su; + struct ct { + struct ct *lt, *gt, *up; + time_t last_used; + u_int requests; + u_int nops; + u_int rank; + u_char flags; + u_char vers; + DCC_CLNT_ID clnt_id; + DCC_SOCKU su; + } *clist, **ctptr, *ctup, *ct, *ctnew; + u_int versions[DCC_PKT_VERSION_MAX+1]; + + char date_buf[40]; + struct tm last, now; + char *p; + const char *ac; + u_char need_head; + int i; + + passed_flags = 0; + thold = 0; + passed_thold = 0; + max_clients = DCC_ADMIN_RESP_MAX_CLIENTS; + passed_max_clients = 0; + passed_cidr = 0; + memset(addr_bits, 0, sizeof(addr_bits)); + + ac = strpbrk(arg, "/.:"); + + /* look for "-n", "-ns", "-n -s", etc. */ + nonames = 0; + sort = 0; + ids = 0; + req_flags = 0; + while (*arg != 0) { + arg += strspn(arg, " \t"); + if (*arg == '-' && !passed_flags) { + ++arg; + do { + switch (*arg) { + case 'n': + nonames = 1; + break; + case 's': + sort = 1; + break; + case 'i': + ids = 1; + break; + case 'a': + req_flags |= DCC_AOP_CLIENTS_AVG; + break; + case 'V': + req_flags |= DCC_AOP_CLIENTS_VERS; + break; + case 'A': + req_flags |= DCC_AOP_CLIENTS_ANON; + req_flags &= ~DCC_AOP_CLIENTS_NON_ANON; + break; + case 'K': + req_flags |= DCC_AOP_CLIENTS_NON_ANON; + req_flags &= ~DCC_AOP_CLIENTS_ANON; + break; + default: + help_cmd("clients", 0); + return -1; + } + } while (*++arg != ' ' && *arg != '\t' && *arg != '\0'); + continue; + } + if (!passed_cidr && ac && !strpbrk(arg, DCC_WHITESPACE)) { + int bits; + + bits = dcc_str2cidr(0, &addr6, 0, 0, arg, 0, 0); + if (bits <= 0) + return -1; + memcpy(addr_bits, &addr6, sizeof(addr6)); + addr_bits[sizeof(addr6)] = bits; + arg = ""; + passed_cidr = 1; + passed_flags = 1; + passed_max_clients = 1; + passed_thold = 1; + continue; + } + if (!passed_max_clients + && (i = strtoul(arg, &p, 10)) != 0 + && (*p == ' ' || *p == '\t' || *p == '\0')) { + max_clients = i; + arg = p; + passed_max_clients = 1; + passed_flags = 1; + continue; + } + if (!passed_thold + && (i = strtoul(arg, &p, 10)) > 0 + && (*p == ' ' || *p == '\t' || *p == '\0') + && i <= DCC_ADMIN_RESP_CLIENTS_MAX_THOLD) { + thold = i; + arg = p; + passed_thold = 1; + passed_max_clients = 1; + passed_flags = 1; + continue; + } + help_cmd("clients", 0); + return -1; + } + + if (ids) + req_flags &= ~DCC_AOP_CLIENTS_VERS; + + /* Require a server password for client IP addresses + * The server demands only client ID for "clients -i" */ + if (!ids + && !ck_cmd_priv(ce, 1, 0)) + return 0; + + if (!rdy_ctxt(0)) + return 0; + + /* Collect all of the information before printing it to minimize + * the changes in the position of hosts and so deleted or missing + * entries. */ + total = 0; + subtotal = 0; + max_ops = 0; + max_nops = 0; + memset(versions, 0, sizeof(versions)); + offset = 0; + num_clients = 0; + clist = 0; + for (;;) { + DCC_OPS result; + int len, result_len; + u_char vers, result_flags; +# define BL_FLAGS (DCC_ADMN_RESP_CLIENTS_BL | DCC_ADMN_RESP_CLIENTS_BAD) + u_int clnt_id, last_used, requests, nops; + + if (offset > DCC_AOP_CLIENTS_MAX_OFFSET) { + dcc_error_msg("%d are too many clients", offset); + break; + } + + gettimeofday(&op_start, 0); + result = dcc_aop(dcc_emsg, ctxt, grey_on ? DCC_CLNT_FG_GREY : 0, + NO_SRVR, clock_kludge, + ids ? DCC_AOP_CLIENTS_ID : DCC_AOP_CLIENTS, + (offset << 16) + + min(thold, + DCC_ADMIN_RESP_CLIENTS_MAX_THOLD), + ISZ(aop_resp.resp.val.string + ) >> DCC_ADMIN_RESP_CLIENTS_SHIFT, + req_flags, + offset >> 16, + addr_bits, passed_cidr ? sizeof(addr_bits) : 0, + &aop_resp, &op_result_su); + if (result == DCC_OP_INVALID + || result == DCC_OP_ERROR) { + dcc_error_msg("%s", dcc_emsg); + break; + } + + /* print heading before the first chunk */ + if (!offset) + print_aop(-1); + + result_len = (ntohs(aop_resp.hdr.len) + - (sizeof(aop_resp.resp) + - sizeof(aop_resp.resp.val.string))); + /* stop when the server has nothing to add */ + if (result_len <= 1) + break; + + len = 0; + vers = 0; + do { + len += client_unpack(&aop_resp.resp.val.clients[len], + &result_flags, &clnt_id, + &last_used, + &requests, &nops, &vers, + &su); + if (result_flags & DCC_ADMN_RESP_CLIENTS_SKIP) { + offset += last_used; + continue; + } + + if (vers != 0) { + if (vers < DIM(versions)) + versions[vers] += requests; + else + versions[0] += requests; + } + + + /* quit if we are in some kind of loop */ + if (++num_clients > DCC_ADMIN_RESP_MAX_CLIENTS) + goto stop; + ++offset; + + /* add the new entry to the possibly sorted list */ + + ctnew = dcc_malloc(sizeof(*ctnew)); + memset(ctnew, 0, sizeof(*ctnew)); + ctnew->flags = (result_flags & BL_FLAGS ); + ctnew->vers = vers; + ctnew->clnt_id = clnt_id; + ctnew->last_used = last_used; + ctnew->requests = requests; + if (max_ops < requests) + max_ops = requests; + total += requests; + ctnew->nops = nops; + if (max_nops < nops) + max_nops = nops; + ctnew->su = su; + + ctptr = &clist; + ctup = 0; + for (;;) { + ct = *ctptr; + if (!ct) { + ctnew->up = ctup; + *ctptr = ctnew; + break; + } + i = !sort; + if (!i) { + i = (0!= (ct->flags & BL_FLAGS)); + i -= (0 != (ctnew->flags & BL_FLAGS)); + } + if (!i) { + i = ct->requests; + i -= ctnew->requests; + } + ctup = ct; + if (i >= 0) { + ctptr = &ct->lt; + } else { + /* update the threshold if sorting */ + if (++ct->rank >= max_clients + && thold < ct->requests) { + thold = ct->requests; + } + ctptr = &ct->gt; + } + } + } while (len < result_len); + if (len != result_len) { + dcc_error_msg("wrong sized clients response; %d != %d", + result_len, len); + break; + } + + /* quit if the server ran out of things to say */ + if (result_flags & DCC_ADMN_RESP_CLIENTS_LAST) + break; + + /* Quit if we want only part of the list and we have it. + * We must get everything the server sends if we are sorting. + * The server uses our threshold to avoid sending everything + * it know. */ + if (!sort && offset >= max_clients) + break; +#undef BL_FLAGS + } +stop: + if (!total) + total = 1; + + dcc_localtime(time(0), &now); + + if (max_ops > 99*1000*1000) + ops_width = 9; + else if (max_ops > 9*1000*1000) + ops_width = 8; + else + ops_width = 7; + if (max_nops > 99*1000) + nops_width = 6; + else if (max_nops > 9*1000) + nops_width = 5; + else + nops_width = 4; + + /* print the list */ + num_clients = 0; + for (ct = clist; ct; ct = ctnew) { + ctnew = ct->gt; + if (ctnew) { + ct->gt = 0; + continue; + } + + if (num_clients == 0) { + if (sort) { + printf(" %*s %*s ", + ops_width, "ops", + nops_width, "nops"); + if (ids) + fputs(" ID ", stdout); + fputs(" last ", stdout); + if (req_flags & DCC_AOP_CLIENTS_VERS) + fputs(" v", stdout); + } else { + printf("%*s %*s last ID ", + ops_width, "ops", + nops_width, "nops"); + if (req_flags & DCC_AOP_CLIENTS_VERS) + fputs(" v", stdout); + } + putchar('\n'); + } + if (++num_clients <= max_clients) { + if (sort) { + subtotal += ct->requests; + printf("%3d%% %3d%% ", + (int)(ct->requests*100.0/total), + (int)(subtotal*100.0/total)); + } + printf("%*d %*d", + ops_width, ct->requests, + nops_width, ct->nops); + if (sort && ids) + printf(" %6d", ct->clnt_id); + /* print year and no time if it was long ago */ + dcc_localtime(ct->last_used, &last); + printf(" %s", dcc_time2str(date_buf, sizeof(date_buf), + (last.tm_year != now.tm_year + && (last.tm_mon < 6 + || now.tm_mon > 2)) + ? "%Y/%m/%d" : "%m/%d %X", + ct->last_used)); + if (!sort) + printf(" %6d", ct->clnt_id); + if (req_flags & DCC_AOP_CLIENTS_VERS) { + if (ct->vers != 0) + printf(" %d", ct->vers); + else + fputs(" ?", stdout); + } + if (ct->flags & DCC_ADMN_RESP_CLIENTS_BL) + fputs(" BLACKLIST", stdout); + else if (ct->flags & DCC_ADMN_RESP_CLIENTS_BAD) + fputs(" BAD", stdout); + if (!ids) { + char name[DCC_MAXDOMAINLEN]; + char sustr[DCC_SU2STR_SIZE]; + + if (nonames) { + printf(" %s", + dcc_su2str2(sustr, sizeof(sustr), + &ct->su)); + } else { + printf(" %-16s %s", + dcc_su2str2(sustr, sizeof(sustr), + &ct->su), + dcc_su2name(name, sizeof(name), + &ct->su)); + } + } + putchar('\n'); + + ctnew = ct->lt; + if (!ctnew) { + ctnew = ct->up; + } else { + ctnew->up = ct->up; + } + } + + memset(ct, 0, sizeof(*ct)); + dcc_free(ct); + } + putchar('\n'); + + + need_head = 1; + for (i = 0; i < DIM(versions); ++i) { + if (versions[i] == 0) + continue; + if (need_head) { + need_head = 0; + fputs("version total\n", stdout); + } + printf("%6d %8d\n", i, versions[i]); + } + + return 1; +} + + + +/* get and set the server's default anonymous client delay */ +static int +anon_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + int new_delay, old_delay, inflate; + DCC_OPS result; + char *inflate_str, *p; + + inflate = 0; + if (*arg == '\0') { + new_delay = DCC_NO_ANON_DELAY; + } else { + if (!strcasecmp(arg, "forever")) { + new_delay = DCC_ANON_DELAY_FOREVER; + } else { + new_delay = strtoul(arg, &inflate_str, 10); + if (new_delay > DCC_ANON_DELAY_MAX + || (*inflate_str != '\0' && *inflate_str != ',' + && *inflate_str != '*')) { + dcc_error_msg("invalid delay: \"%s\"", arg); + return 0; + } + if (*inflate_str != '\0') { + ++inflate_str; + inflate_str += strspn(inflate_str, + DCC_WHITESPACE); + } + if (*inflate_str != '\0' + && strcasecmp(inflate_str, "none")) { + inflate = strtoul(inflate_str, &p, 10); + if (*p != '\0') { + dcc_error_msg("invalid delay inflation:" + " \"%s\"", inflate_str); + return 0; + } + } + } + if (!ck_cmd_priv(ce, 1, 0)) + return 0; + } + + if (!rdy_ctxt(0)) + return 0; + + gettimeofday(&op_start, 0); + result = dcc_aop(dcc_emsg, ctxt, grey_on ? DCC_CLNT_FG_GREY : 0, + NO_SRVR, clock_kludge, DCC_AOP_ANON_DELAY, + inflate, new_delay>>8, new_delay, 0, 0, 0, + &aop_resp, &op_result_su); + if (result == DCC_OP_INVALID + || result == DCC_OP_ERROR) { + dcc_error_msg("%s", dcc_emsg); + return 0; + } + + old_delay = ((aop_resp.resp.val.anon_delay.delay[0]<<8) + + aop_resp.resp.val.anon_delay.delay[1]); + if (old_delay == DCC_ANON_DELAY_FOREVER) { + printf(" anon delay %s FOREVER\n", + new_delay != DCC_NO_ANON_DELAY ? "was" : "is"); + } else { + printf(" anon delay %s %d", + new_delay != DCC_NO_ANON_DELAY ? "was" : "is", + old_delay); + inflate = ((aop_resp.resp.val.anon_delay.inflate[0]<<24) + +(aop_resp.resp.val.anon_delay.inflate[1]<<16) + +(aop_resp.resp.val.anon_delay.inflate[2]<<8) + +aop_resp.resp.val.anon_delay.inflate[3]); + if (inflate != 0) + printf(",%d", inflate); + putchar('\n'); + } + return 1; +} + + + +/* rewind the flood from a single server */ +static int +flod_rewind(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + DCC_CLNT_ID id; + + if (!arg) + return -1; + id = dcc_get_id(0, arg, 0, 0); + if (id == DCC_ID_INVALID) + return -1; + + return do_aop(DCC_AOP_FLOD, id*256 + DCC_AOP_FLOD_REWIND, NO_SRVR, 1); +} + + + +/* fast forward the flood to a single server */ +static int +ffwd_out(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + DCC_CLNT_ID id; + + if (!arg) + return -1; + id = dcc_get_id(0, arg, 0, 0); + if (id == DCC_ID_INVALID) + return -1; + + return do_aop(DCC_AOP_FLOD, id*256 + DCC_AOP_FLOD_FFWD_OUT, NO_SRVR, 1); +} + + + +/* fast forward the flood to a single server */ +static int +ffwd_in(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + DCC_CLNT_ID id; + + if (!arg) + return -1; + id = dcc_get_id(0, arg, 0, 0); + if (id == DCC_ID_INVALID) + return -1; + + return do_aop(DCC_AOP_FLOD, id*256 + DCC_AOP_FLOD_FFWD_IN, NO_SRVR, 1); +} + + + +/* get the flood counts for a server */ +static int +flod_stats(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + u_int32_t id, next_id; + DCC_AOP_FLODS op; + u_char heading; + int sresult; + + if (!arg) + return -1; + if (!CLITCMP(arg, "clear")) { + arg += LITZ("clear"); + arg += strspn(arg, DCC_WHITESPACE); + op = DCC_AOP_FLOD_STATS_CLEAR; + } else { + op = DCC_AOP_FLOD_STATS; + } + + heading = 1; + if (!strcasecmp(arg, "all")) { + id = DCC_SRVR_ID_MAX+1; + for (;;) { + if (!start_aop(DCC_AOP_FLOD, id*256 + op, NO_SRVR)) + return 0; + sresult = sscanf(aop_resp.resp.val.string, + DCC_AOP_FLOD_STATS_ID, &next_id); + if (1 == sresult + && id == next_id) { + if (id == DCC_SRVR_ID_MAX+1) { + BUFCPY(aop_resp.resp.val.string, + " (no flooding peers)"); + fin_aop(NO_SRVR, 1); + } + return 1; + } + fin_aop(NO_SRVR, heading); + heading = 0; + if (1 != sresult) + return 0; + id = next_id+DCC_SRVR_ID_MAX+1; + } + } + + id = dcc_get_id(0, arg, 0, 0); + if (id == DCC_ID_INVALID) + return -1; + return do_aop(DCC_AOP_FLOD, id*256 + op, NO_SRVR, heading); +} + + + +static const char *stats_help = ""; + +/* get the statistics from all known servers */ +static int +stats_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) +{ + DCC_SRVR_CLASS *class; + SRVR_INX srvr_inx; + int srvrs_gen; + DCC_AOPS aop; + + /* look for "clear" or "all" */ + srvr_inx = NO_SRVR; + aop = DCC_AOP_STATS; + while (*arg != 0) { + arg += strspn(arg, " \t"); + if (srvr_inx == NO_SRVR + && !CLITCMP(arg, "clear")) { + arg += LITZ("clear"); + aop = DCC_AOP_STATS_CLEAR; + if (!get_passwd(aops_tbl[aop].privileged)) { + dcc_error_msg("\"stats clear\"" + " is a privileged operation" + PRV_MSG, ids_path); + return 0; + } + } else if (aop == DCC_AOP_STATS + && !CLITCMP(arg, "all")) { + arg += LITZ("all"); + srvr_inx = 0; + } + if (*arg != '\0' && *arg != ' ' && *arg != '\t') + return -1; + } + + if (!rdy_ctxt(0)) + return 0; + class = DCC_GREY2CLASS(grey_on); + srvrs_gen = class->gen; + do { + if (srvrs_gen != class->gen) { + dcc_error_msg("list of servers changed"); + return 0; + } + /* skip dead servers */ + if (srvr_inx != NO_SRVR + && class->addrs[srvr_inx].srvr_id == DCC_ID_INVALID ) + continue; + + do_aop(aop, sizeof(aop_resp.resp.val.string), srvr_inx, 1); + fflush(stderr); + fflush(stdout); + } while (srvr_inx != NO_SRVR && ++srvr_inx < class->num_srvrs); + + return 1; +} + + + +static int +clock_ck_cmd(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + if (!rdy_ctxt(0)) + return 0; + do_aop(DCC_AOP_CLOCK_CHECK, sizeof(aop_resp.resp.val.string), + NO_SRVR, 1); + + return 1; +} + + + +/* restore tracing to default */ +static int +trace_def(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) +{ + if (!rdy_ctxt(0)) + return 0; + + return (do_aop(DCC_AOP_TRACE_ON, DCC_TRACE_ON_DEF_BITS, + NO_SRVR, 1) + && do_aop(DCC_AOP_TRACE_OFF, DCC_TRACE_OFF_DEF_BITS, + NO_SRVR, 1)); + +}