/* Distributed Checksum Clearinghouse * * control dcc server * * --C-LICENSE-- * $Revision: 1.286 $ */ #include "dcc_ck.h" #include "dcc_xhdr.h" #include "dcc_heap_debug.h" #include "dcc_ids.h" #ifndef DCC_WIN32 #include #endif #ifdef HAVE_EDITLINE #include #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 u_char dup_emsg; 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 src4, src6; static DCC_SRVR_NM srvr = DCC_SRVR_NM_DEF(DCC_SRVR_PORT_HO); static u_char port_set; static DCC_CLNT_ID srvr_clnt_id = DCC_ID_ANON; static u_char anon_ids_complained; static enum WHICH_MAP {MAP_TMP, MAP_INFO} which_map = MAP_INFO; static u_char map_changed = 1; static DCC_INFO_FGS info_fgs; static u_char grey_set; static DCC_CLNT_FGS grey_clnt_fgs; static u_char quiet; #ifdef HAVE_EDITLINE /* Use editline() instead of readline() to avoid problems for those who * distribute DCC binaries such as FreeBSD DCC packages. */ static History *el_history; static HistEvent el_event; #else #define EditLine void #endif static EditLine *el_e = 0; 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=anon, 2=not anon ID, 3=server-ID */ 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; #ifndef DCC_WIN32 static CMD libexecdir_cmd; #endif 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 CMD clock_ck_cmd; static CMD trace_def; static const CMD_TBL_ENTRY cmds_tbl[] = { {"help", help_cmd, 0, 0, 0, "help [cmd]"}, {"?", 0, 0, 0, 0, 0}, {"exit", exit_cmd, 2, 0, 0, "exit"}, {"quit", 0, 2, 0, 0, 0}, {"grey", grey_cmd, 0, 0, 0, "grey [on|off]"}, {"homedir", homedir_cmd,0, 0, 0, "homedir [path]"}, #ifndef DCC_WIN32 {"libexecdir", libexecdir_cmd,2, 0, 0, "libexecdir"}, #endif {"file", file_cmd, 0, 0, 0, "file [mapfile]"}, {"map", 0, 0, 0, 0, 0}, {"new map", new_map_cmd,0, 0, 0, "new map [mapfile]"}, {"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 {map.txtfile | -}"}, {"host", host_cmd, 0, 0, 0, "host [name]"}, {"server", 0, 0, 0, 0, 0}, {"port", port_cmd, 0, 0, 0, "port #"}, {"password", passwd_cmd, 0, 0, 0, "password secret"}, {"passwd", 0, 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|only]"}, {"src", src_cmd, 0, 0, 0, "src [-|IPaddress[,IPv6address]"}, {"SOCKS", socks_cmd, 0, 0, 0, "SOCKS [on|off]"}, {"info", info_cmd, 0, 1, 0, "info [-N]"}, {"RTT", rtt_cmd, 0, 1, 0, "RTT [-N]"}, {"delck", delck_cmd, 1, 3, 0, "delck type hex1..4"}, {"sleep", sleep_cmd, 1, 0, 0, "sleep sec.onds"}, {"clients", clients_cmd,0, 2, 0, "clients [-nsiaVAK] [-I id] [max [thold [addr/prefix]]]"}, {"anon delay", anon_cmd, 0, 1, 0, "\nanon delay [delay[,inflate]]"}, {"flood rewind", flod_rewind,1, 3, 0, "flood rewind ID"}, {"flod rewind", 0, 1, 3, 0, 0}, {"flood FFWD in", ffwd_in, 1, 3, 0, "flood FFWD in ID"}, {"flod FFWD in", 0, 1, 3, 0, 0}, {"flood FFWD out", ffwd_out, 1, 3, 0, "flood FFWD out ID"}, {"flod FFWD out", 0, 1, 3, 0, 0}, {"flood stats", flod_stats, 0, 3, 0, "flood stats [clear] {ID|all}"}, {"flood status", 0, 0, 3, 0, 0}, {"stats", stats_cmd, 0, 0, 0, "stats [clear|all]"}, {"status", 0, 0, 1, 0, 0}, {"clock check", clock_ck_cmd,2,1, 0, "clock check"}, {"trace default", trace_def, 2, 3, 0, "trace default"}, }; 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; /* 1=anon, 2=not anon ID, 3=server-ID */ u_int32_t val; } aops_tbl[] = { #define TMAC1(s,h,b) \ {"trace "#s" on", h, DCC_AOP_TRACE_ON, 1, DCC_TRACE_##b},\ {"trace "#s" off", 0, DCC_AOP_TRACE_OFF, 1, DCC_TRACE_##b} #define TMAC(s,b) TMAC1(s,"trace "#s" {on|off}",b) #define TMACQ(s,b) TMAC1(s,0,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(flood1,FLOD1_BIT), TMACQ(flood,FLOD1_BIT), TMACQ(flod1,FLOD1_BIT), TMAC(flood2,FLOD2_BIT), TMACQ(flod2,FLOD2_BIT), /* TMAC(xx,_BIT), */ TMAC(bl,BL_BIT), TMAC(db,DB_BIT), TMAC(wlist,WLIST_BIT), #undef TMAC {"stop", "", DCC_AOP_STOP, 3, 0}, {"system stop", "", DCC_AOP_STOP, 3, 1}, {"clean stop", "", DCC_AOP_STOP, 3, 2}, {"flood check", "", DCC_AOP_FLOD, 3, DCC_AOP_FLOD_CHECK}, {"flod check", 0, DCC_AOP_FLOD, 3, DCC_AOP_FLOD_CHECK}, {"flood shutdown", "", DCC_AOP_FLOD, 3, DCC_AOP_FLOD_SHUTDOWN}, {"flod shutdown", 0, DCC_AOP_FLOD, 3, DCC_AOP_FLOD_SHUTDOWN}, {"flood halt", "", DCC_AOP_FLOD, 3, DCC_AOP_FLOD_HALT}, {"flod halt", 0, DCC_AOP_FLOD, 3, DCC_AOP_FLOD_HALT}, {"flood resume", "", DCC_AOP_FLOD, 3, DCC_AOP_FLOD_RESUME}, {"flod resume", 0, DCC_AOP_FLOD, 3, DCC_AOP_FLOD_RESUME}, {"flood list", "", DCC_AOP_FLOD, 2, DCC_AOP_FLOD_LIST}, {"flod list", 0, DCC_AOP_FLOD, 2, DCC_AOP_FLOD_LIST}, {"DB clean", "", DCC_AOP_DB_CLEAN, 3, 0}, {"DB new", "", DCC_AOP_DB_NEW, 3, 0}, }; static void DCC_NORET usage(void) { dcc_logbad(EX_USAGE, "usage: [-VBdq] [-h homedir] [-c ids] [op1 [op2] ... ]\n"); } static void DCC_PF(1,2) cdcc_error_msg(const char *p, ...) { va_list args; if (dup_emsg) { va_start(args, p); vprintf(p, args); va_end(args); } /* On some systems you cannot use args twice after 1 va_start() */ va_start(args, p); dcc_verror_msg(p, args); va_end(args); } static char * prompt(EditLine *unused DCC_UNUSED) { static char buf[sizeof(DCC_PATH)+sizeof("cdcc >")]; snprintf(buf, sizeof(buf), "cdcc %s> ", which_map == MAP_INFO ? info_map_nm.c : "-"); return buf; } #ifdef HAVE_EDITLINE static void get_editrc(void) { const char *home; DCC_PATH editrc; struct stat sb; if (el_source(el_e, 0) >= 0) return; home = getenv("HOME"); if (!home) return; if (STRLCPY(editrc.c, home, sizeof(editrc)) >= sizeof(editrc)) return; if (STRLCAT(editrc.c, "/.editrc", sizeof(editrc)) >= sizeof(editrc)) return; if (stat(editrc.c, &sb) < 0) return; if (sb.st_uid != dcc_real_uid) return; el_source(el_e, editrc.c); } #endif int main(int argc, char **argv) { char cmd_buf[500]; u_char print_version = 0; int i; dcc_init_priv(); dcc_syslog_init(0, argv[0], 0); #ifdef HAVE_EDITLINE if (isatty(STDIN_FILENO)) { const char *self; self = strrchr(argv[0], '/'); if (self) ++self; else self = argv[0]; el_e = el_init(self, stdin, stdout, stderr); } if (el_e) { int mode; if (0 > el_get(el_e, EL_EDITMODE, &mode) || !mode) { el_end(el_e); el_e = 0; } } if (el_e) { /* prefer emacs mode but let the user choose in .editrc */ el_set(el_e, EL_EDITOR, "emacs"); /* bind emacs search to ^R */ el_set(el_e, EL_BIND, "\022", "em-inc-search-prev", NULL); get_editrc(); el_history = history_init(); history(el_history, &el_event, H_SETSIZE, 800); el_set(el_e, EL_HIST, history, el_history); el_set(el_e, EL_PROMPT, prompt); el_set(el_e, EL_SIGNAL, 1); } #endif while ((i = getopt(argc, argv, "VBdqh:c:")) != -1) { switch (i) { case 'V': dcc_version_print(); print_version = 1; break; case 'B': dup_emsg = 1; 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; if (print_version && argc == 0) exit_cmd(0, 0); dcc_clnt_unthread_init(); if (!dcc_cdhome(0, homedir, 1) && homedir) dcc_cdhome(0, homedir = 0, 1); set_ids_path(0, ids_nm); 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; #ifdef HAVE_EDITLINE if (el_e != NULL) history(el_history, &el_event, H_ENTER, *argv); #endif if (!do_cmds(*argv)) { fputs(" ?\n", stderr); exit(EX_UNAVAILABLE); } assert_ctxts_unlocked(); assert_info_unlocked(); ++argv; if (!--argc) exit_cmd(0, 0); } } /* Without an arg list of commands or after "-", look for commands * from STDIN. Commands end with a semicolon or newline. */ for (;;) { assert_ctxts_unlocked(); assert_info_unlocked(); fflush(stderr); fflush(stdout); if (el_e) { #ifdef HAVE_EDITLINE const char *cmd; int cmd_len; cmd = el_gets(el_e, &cmd_len); if (!cmd) { fputc('\n', stdout); fflush(stderr); exit_cmd(0, 0); } if (*(cmd+strspn(cmd, DCC_WHITESPACE)) != '\0') history(el_history, &el_event, H_ENTER, cmd); STRLCPY(cmd_buf, cmd, sizeof(cmd_buf)); #endif } else { fputs(prompt(el_e), stdout); fflush(stdout); if (!fgets(cmd_buf, sizeof(cmd_buf), stdin)) { fputc('\n', stdout); fflush(stderr); exit_cmd(0, 0); } } if (!do_cmds(cmd_buf)) { fputs(" ?\n", stderr); fflush(stderr); if (which_map == MAP_TMP) no_fail_cmd(0, 0); } } } static void get_passwd_fail(u_char privileged, const char *op, const char *type) { const char *id_type; if (privileged < 3) id_type = ""; else id_type = "server-"; dcc_error_msg("\"%s\" is a privileged %s." " Use the \"id %sID\" command\n" " and \"password secret\" command or `su`" " to read passwords from %s", op, type, id_type, ids_path.c); } static u_char load_ids_failed(u_char privileged, const char *op, const char *type) { if (privileged < 2) { if (!anon_ids_complained) { anon_ids_complained = 1; dcc_error_msg("%s", dcc_emsg.c); dcc_error_msg("using the anonymous client-ID"); } return 1; } dcc_error_msg("%s", dcc_emsg.c); get_passwd_fail(privileged, op, type); return 0; } /* see if we 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, /* 1=anon, 2=not anon ID, 3=server-ID */ const char *op, const char *type) { const ID_TBL *srvr_clnt_tbl; DCC_PATH tmp; srvr.clnt_id = DCC_ID_ANON; memset(srvr.passwd, 0, sizeof(srvr.passwd)); if (privileged == 0) return 1; if (passwd_set) { /* Using a manual password. */ if (srvr_clnt_id == DCC_ID_ANON) { if (privileged > 1) { get_passwd_fail(privileged, op, type); return 0; } if (!anon_ids_complained) { dcc_error_msg("ID not specified," " so not using the password"); anon_ids_complained = 1; } return 1; } if (srvr_clnt_id > DCC_SRVR_ID_MAX && privileged > 2) { get_passwd_fail(privileged, op, type); return 0; } srvr.clnt_id = srvr_clnt_id; memcpy(srvr.passwd, passwd, sizeof(srvr.passwd)); anon_ids_complained = 0; return 1; } if (srvr_clnt_id == DCC_ID_ANON) { if (privileged <= 1) return 1; get_passwd_fail(privileged, op, type); return 0; } /* 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 privilege * users who could use an editor to read the cleartext passwords. */ dcc_rel_priv(); if (0 > access(ids_path.c, R_OK) && errno == EACCES) { dcc_pemsg(EX_NOINPUT, &dcc_emsg, "access(%s): %s", dcc_fnm2abs_msg(&tmp, ids_path.c), ERROR_STR()); return load_ids_failed(privileged, op, type); } if (0 >= load_ids(&dcc_emsg, srvr_clnt_id, &srvr_clnt_tbl, 1, dcc_clnt_debug > 1)) { return load_ids_failed(privileged, op, type); } if (srvr_clnt_id > DCC_SRVR_ID_MAX && privileged > 2) { get_passwd_fail(privileged, op, type); return 0; } srvr.clnt_id = srvr_clnt_id; memcpy(srvr.passwd, srvr_clnt_tbl->cur_passwd, sizeof(srvr.passwd)); anon_ids_complained = 0; 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 and open a new map file */ 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; src4.family = AF_UNSPEC; src6.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.c); 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; dcc_emsg.c[0] = '\0'; dcc_ctxts_lock(); if (which_map == MAP_TMP) { result = (dcc_map_tmp_info(&dcc_emsg, &srvr, &src4, &src6, info_fgs) && dcc_info_lock(&dcc_emsg)); } else { result = dcc_map_lock_info(&dcc_emsg, info_map_nm.c, -1); } if (result) { info_fgs = dcc_clnt_info->fgs; if (!lock) result = cdcc_unlock(complain); } else { dcc_ctxts_unlock(); if (complain) dcc_error_msg("%s", dcc_emsg.c); } return result; } /* get ready to start talking to a DCC server * on entry only the contexts are locked and only if we have a * context and the file is not mapped * both locks are released on exit */ static u_char /* 1=ok, 0=failed */ rdy_ctxt(DCC_CLNT_FGS fgs) { u_char rdy_done; /* 1=readied things enough */ u_char need_unlock; u_char result; rdy_done = 0; need_unlock = 0; result = 0; if (grey_on) fgs |= DCC_CLNT_FG_GREY; else fgs &= ~DCC_CLNT_FG_GREY; fgs |= DCC_CLNT_FG_NO_FAIL; if (!ctxt) { if (which_map == MAP_TMP) { /* create a new temporary map */ ctxt = dcc_tmp_clnt_init(&dcc_emsg, ctxt, &srvr, &src4, &src6, fgs, info_fgs); } else { /* open official map file */ ctxt = dcc_clnt_init(&dcc_emsg, ctxt, info_map_nm.c, fgs); } if (!ctxt) { dcc_error_msg("%s", dcc_emsg.c); return 0; } rdy_done = 1; result = 1; } /* if there is only a greylist server name, care only about it */ 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; result = 0; } dcc_ctxts_lock(); if (!rdy_done) { dcc_emsg.c[0] = '\0'; if (dcc_clnt_rdy(&dcc_emsg, ctxt, fgs)) { need_unlock = 1; result = 1; } else { dcc_error_msg("%s", dcc_emsg.c); result = 0; } /* we must give up now if we do not have the file */ if (!dcc_clnt_info) { dcc_rel_ctxt(ctxt); ctxt = 0; dcc_ctxts_unlock(); dcc_error_msg("%s", dcc_emsg.c); return 0; } } info_fgs = dcc_clnt_info->fgs; if (!(fgs & DCC_CLNT_FG_NO_MEASURE_RTTS)) 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.c); return 0; } /* check the other (greylist or not) server */ if (which_map == MAP_INFO) { dcc_emsg.c[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.c); } else { dcc_info_unlock(0); } } dcc_ctxts_unlock(); return result; } /* should hold both locks on entry; both are released on exit */ static void fix_info(u_char new_src) { map_changed = 1; assert_ctxts_locked(); assert_info_locked(); dcc_force_measure_rtt(new_src); cdcc_unlock(1); } /* compare ignoring case */ static const char * /* 0 or mismatch in str */ cmd_cmp(const char *user, /* from the user */ const char *op, /* from command table entry */ u_char *partial) /* exact initial substring match */ { char op_c, user_c; int len; if (partial) *partial = 0; len = 0; for (;;) { op_c = *op; op_c = DCC_TO_LOWER(op_c); user_c = *user; if (user_c == '\t') user_c = ' '; else user_c = DCC_TO_LOWER(user_c); if (op_c != user_c) { /* compress bursts of blanks */ if (user_c == ' ' && len != 0 && *(op-1) == ' ') { ++user; continue; } /* Treat substring match without an arg * as a complete match. */ if (user_c == '\0') { if (partial) *partial = 1; return 0; } return user; } /* stop at an exact match */ if (op_c == '\0') return 0; ++op; ++user; ++len; } } /* Display our name for the server and its address, * while suppressing some duplicates */ static void print_aop(SRVR_INX srvr_inx) /* NO_SRVR or server index */ { const DCC_SRVR_CLASS *class; char date_buf[40]; char sustr[DCC_SU2STR_SIZE]; DCC_PATH tmp; const char *srvr_nm; NAM_INX nam_inx; dcc_su2str2(sustr, sizeof(sustr), &op_result_su); class = DCC_GREY2CLASS(grey_on); /* Display the currently chosen server * if the specified srvr_inx is NO_SRVR */ if (!VALID_SRVR(class, srvr_inx)) srvr_inx = class->srvr_inx; if (VALID_SRVR(class, srvr_inx) && (VALID_NAM(nam_inx = class->addrs[srvr_inx].nam_inx))) { srvr_nm = class->nms[nam_inx].hostname; if (strcmp(srvr_nm, sustr) && strcmp(srvr_nm, DCC_LOCALHOST)) { 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", dcc_fnm2abs_msg(&tmp, info_map_nm.c)); 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_clnt_fgs, 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) { cdcc_error_msg("%s", dcc_emsg.c); 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, /* 0=anon ok 1=server-ID 2=client-ID */ u_char write_map) /* 1=write map, 2=write /var/dcc/map */ { DCC_PATH tmp; /* 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, ce->cmd, "server command")) 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.c, R_OK) && errno != ENOENT && errno != ENOTDIR) { dcc_error_msg("\"%s\" is a privileged command changing %s", ce->cmd, dcc_fnm2abs_msg(&tmp, info_map_nm.c)); return 0; } return 1; } static u_char run_cmd(const char *op, const char *arg, const CMD_TBL_ENTRY *ce) { int i; if (!ck_cmd_priv(ce, ce->privileged, ce->write_map)) return 0; i = ce->fnc(arg, ce); if (i > 0) return 1; if (i == 0) return 0; help_cmd(op, 0); return 0; } static u_char /* 1=ok 0=bad command */ cmd(const char *op) { const char *arg, *help_str; int op_num, op_num1, j; const CMD_TBL_ENTRY *ce, *ce1; u_char iss; /* look for the string as a command and execute it if we find it */ ce1 = 0; ce = &cmds_tbl[0]; for (op_num = 0; op_num < DIM(cmds_tbl); ++op_num) { if (cmds_tbl[op_num].fnc) ce = &cmds_tbl[op_num]; arg = cmd_cmp(op, cmds_tbl[op_num].cmd, &iss); if (arg == op) continue; /* if the command table entry and the command completely * matched, then infer a null argument */ if (!arg) { if (iss) { /* continue searching after the 1st or a * duplicate initial substring match */ if (!ce1 || ce1 == ce) { ce1 = ce; continue; } help_cmd(op, 0); return 0; } if (ce->args != 1) return run_cmd(op, "", ce); help_cmd(op, 0); return 0; } /* If the command table entry is an initial substring 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 blanks 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; } return run_cmd(op, arg+j, ce); } } /* run an unambigious partial command */ if (ce1) return run_cmd(op, "", ce1); /* otherwise try to interpret it as a DCC administrative packet */ op_num1 = -1; op_num = 0; help_str = ""; for (;;) { if (op_num >= DIM(aops_tbl)) { /* allow previously seen unambiguous partial match */ if (op_num1 != -1) { op_num = op_num1; break; } 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, &iss)) { if (!iss) break; if (op_num1 == -1) { /* continue searching after 1st partial match */ op_num1 = op_num; } else { /* quit on 2nd partial match */ help_cmd(op, 0); return 0; } break; } ++op_num; } /* send an administrative request to the server */ if (!get_passwd(aops_tbl[op_num].privileged, help_str, "operation")) 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 DCC_UNUSED) { u_char iss; int found; /* 0=partial ISS, 1=iss, 2=exact */ const char *help_str; const char *p; int pos; int i; /* See if the request specifies one or more commands. */ found = -1; if (arg && *arg != '\0') { for (i = 0; i < DIM(cmds_tbl); ++i) { p = cmd_cmp(arg, cmds_tbl[i].cmd, &iss); if (!p) { if (!iss) found = 2; else if (found < 1) found = 1; } else if (p != arg && found < 0) { found = 0; } } for (i = 0; i < DIM(aops_tbl); ++i) { p = cmd_cmp(arg, aops_tbl[i].op, &iss); if (!p) { if (!iss) { /* complete match */ found = 2; } else if (found < 1) { /* target is initial substring of * command; note if it is best so far */ found = 1; } } else if (p != arg && found < 0) { /* target has an initial substring that * matches initial substring of command */ found = 0; } } } /* If we found something, show it */ if (found >= 0) { help_str = 0; for (i = 0; i < DIM(cmds_tbl); ++i) { /* show the help string for a matching synonym */ if (cmds_tbl[i].help_str) help_str = cmds_tbl[i].help_str; if (!help_str) continue; p = cmd_cmp(arg, cmds_tbl[i].cmd, &iss); /* don't show a command that does not match at all */ if (p == arg) continue; /* don't show commands that share initial substring * with target if we have better */ if (p != NULL && found > 0) continue; /* show commands for which the target is an initial * substring only if we do not have a exact match */ if (found > 1 && iss) continue; /* skip a leading '\n' that signals a * new line after this command */ if (*help_str == '\n') ++help_str; printf("usage: %s\n", help_str); help_str = 0; } help_str = 0; for (i = 0; i < DIM(aops_tbl); ++i) { if (aops_tbl[i].help_str) { help_str = aops_tbl[i].help_str; /* use the command itself if the string empty */ if (*help_str == '\0') help_str = aops_tbl[i].op; } if (!help_str) continue; p = cmd_cmp(arg, aops_tbl[i].op, &iss); /* don't show a command that does not match at all */ if (p == arg) continue; /* don't show commands that share initial substring * with target if we have better */ if (p != NULL && found > 0) continue; /* show commands for which the target is an initial * substring only if we do not have a exact match */ if (found > 1 && iss) continue; /* skip a leading '\n' that signals a * new line after this command */ if (*help_str == '\n') ++help_str; printf("usage: %s\n", help_str); help_str = 0; } 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) 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 DCC_NORET exit_cmd(const char *arg DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { #ifdef HAVE_EDITLINE if (el_e) { if (el_history) history_end(el_history); el_end(el_e); } #endif exit(EX_OK); #ifndef HAVE_GCC_ATTRIBUTES return -1; #endif } static int grey_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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; grey_clnt_fgs &= ~DCC_CLNT_FG_GREY; set_which_map(which_map); } else if (!strcmp(arg, "on")) { grey_on = 1; grey_set = 1; grey_clnt_fgs |= DCC_CLNT_FG_GREY; 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 DCC_UNUSED) { 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.c); set_which_map(MAP_INFO); } printf(" homedir=%s\n", dcc_homedir.c); return 1; } #ifndef DCC_WIN32 static int libexecdir_cmd(const char *arg DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { printf(" libexecdir=%s\n", DCC_LIBEXECDIR); return 1; } #endif /* set name of map file */ static int file_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { DCC_PATH tmp; if (arg[0] == '\0') { if (which_map == MAP_INFO) printf(" using map file %s\n", dcc_fnm2abs_msg(&tmp, info_map_nm.c)); else printf(" map file %s but using temporary file\n", dcc_fnm2abs_msg(&tmp, info_map_nm.c)); return 1; } BUFCPY(info_map_nm.c, 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 DCC_UNUSED) { DCC_PATH tmp; 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, 0, info_fgs)) { dcc_error_msg("%s", dcc_emsg.c); return 0; } BUFCPY(info_map_nm.c, arg); set_which_map(MAP_INFO); if (!quiet) printf(" created %s\n", dcc_fnm2abs_msg(&tmp, info_map_nm.c)); return 1; } static int info_work(const char *arg, int fgs) { DCC_CLNT_INFO info; u_char dcc, names; if (*arg == '\0') { names = 0; } else if (!strcmp(arg, "-N")) { names = 1; } else { return -1; } if (!rdy_ctxt(fgs | DCC_CLNT_FG_BAD_SRVR_OK)) 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)); cdcc_unlock(1); dcc_rel_priv(); if (which_map == MAP_INFO) { if (info.dcc.nms[0].hostname[0] != '\0' || !grey_on) { print_info(info_map_nm.c, &info, quiet, 0, names, 0 <= access(info_map_nm.c, R_OK)); dcc = 1; } else { dcc = 0; } if (info.grey.nms[0].hostname[0] != '\0' || grey_on) { if (dcc && !quiet) fputs("\n################\n", stdout); print_info(info_map_nm.c, &info, quiet, 1, names, 0 <= access(info_map_nm.c, R_OK)); } } else { print_info(0, &info, quiet, grey_on ? 2 : 0, names, 1); } if (!quiet) putchar('\n'); return 1; } /* server host name */ static int host_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { DCC_SRVR_NM nm; if (arg[0] == '\0') { if (which_map == MAP_INFO) return info_work(arg, 0) ; printf(" %s server name \"%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; } return 1; } /* server port # */ static int port_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { u_int port; if (arg[0] == '\0') { if (which_map == MAP_INFO) return info_work(arg, 0) ; 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) { DCC_INFO_FGS new_info_fgs; char sustr[DCC_SU2STR_SIZE]; sa_family_t check_family; if (!init_map(1, 0)) return 0; if (arg[0] == '\0') { printf(" %s\n", (info_fgs & DCC_INFO_FG_IPV6_OFF) ? DCC_INFO_TXT_IPV6_OFF : (info_fgs & DCC_INFO_FG_IPV4_OFF) ? DCC_INFO_TXT_IPV6_ONLY : DCC_INFO_TXT_IPV6_ON); return 1; } if (!strcasecmp(arg, "on")) { /* "ON" means both IPv4 and IPv6 */ new_info_fgs = 0; check_family = AF_INET6; } else if (!strcasecmp(arg, "off")) { new_info_fgs = DCC_INFO_FG_IPV6_OFF; check_family = AF_INET; } else if (!strcasecmp(arg, "only")) { new_info_fgs = DCC_INFO_FG_IPV4_OFF; check_family = AF_INET6; } else { return -1; } if (!ipv_check(&dcc_emsg, check_family, 1)) { if (new_info_fgs == DCC_INFO_FG_IPV4_OFF) { dcc_error_msg("%s", dcc_emsg.c); return 0; } dcc_error_msg("warning %s", dcc_emsg.c); } if (!ck_cmd_priv(ce, 0, 1)) return 0; if (!init_map(1, 1)) return 0; if ((new_info_fgs & DCC_INFO_FG_IPV4_OFF) && dcc_clnt_info->src4.ip.family == AF_INET) { dcc_error_msg("IPv4 %s incompatible with " DCC_INFO_TXT_USE_SRC"%s\n" " Remove the source addresses with 'src -'", arg, dcc_ip2str(sustr, sizeof(sustr), &dcc_clnt_info->src4.ip)); cdcc_unlock(1); return 0; } if ((new_info_fgs & DCC_INFO_FG_IPV6_OFF) && dcc_clnt_info->src6.ip.family == AF_INET6) { dcc_error_msg("IPv6 %s incompatible with " DCC_INFO_TXT_USE_SRC"%s" "\n Remove the source addresses with 'src -'", arg, dcc_ip2str(sustr, sizeof(sustr), &dcc_clnt_info->src6.ip)); cdcc_unlock(1); return 0; } new_info_fgs |= (info_fgs & ~(DCC_INFO_FG_IPV6_OFF | DCC_INFO_FG_IPV4_OFF)); if (info_fgs != new_info_fgs) { dcc_clnt_info->fgs = info_fgs = new_info_fgs; fix_info(0); } else if (!cdcc_unlock(1)) { return 0; } if (rdy_ctxt(0) && 0 != ((new_info_fgs ^ dcc_clnt_info->fgs) & (DCC_INFO_FG_IPV6_OFF | DCC_INFO_FG_IPV4_OFF))) { #ifdef DCC_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_EMSG *emsg, DCC_IP *new_src4, DCC_IP *new_src6, const char *addr, DCC_INFO_FGS fgs) { DCC_SOCKU su; DCC_GETH geth; DCC_SOCKU *hp; int error; memset(&su, 0, sizeof(su)); /* Allow host name. * Disallow IPv4 or IPv6 if not allowed. * Prefer IPv6 if we already have IPv4 (or the reverse). */ if (!str2su(&su, addr)) { if (fgs & DCC_INFO_FG_IPV6_OFF) geth = DCC_GETH_V4; else if (fgs & DCC_INFO_FG_IPV4_OFF) geth = DCC_GETH_V6; else geth = DCC_GETH_DEF; dcc_host_lock(); if (!dcc_get_host(addr, geth, &error)) { dcc_host_unlock(); dcc_pemsg(EX_NOHOST, emsg, "%s: %s", addr, H_ERROR_STR1(error)); return 0; } if (new_src4->family == AF_UNSPEC && !(fgs & DCC_INFO_FG_IPV4_OFF)) { for (hp = dcc_hostaddrs; hp < dcc_hostaddrs_end; ++hp) { if (hp->sa.sa_family == AF_INET) { dcc_su2ip(new_src4, hp); break; } } } if (new_src6->family == AF_UNSPEC && !(fgs & DCC_INFO_FG_IPV6_OFF)) { for (hp = dcc_hostaddrs; hp < dcc_hostaddrs_end; ++hp) { if (hp->sa.sa_family == AF_INET6) { dcc_su2ip(new_src6, hp); break; } } } dcc_host_unlock(); return 1; } if (su.sa.sa_family == AF_INET) { if (fgs & DCC_INFO_FG_IPV4_OFF) { dcc_pemsg(EX_NOHOST, emsg, "cannot use IPv4 source address" " when IPv4 disabled"); return 0; } if (new_src4->family == AF_UNSPEC) dcc_su2ip(new_src4, &su); return 1; } if (su.sa.sa_family == AF_INET6) { if (fgs & DCC_INFO_FG_IPV6_OFF) { dcc_pemsg(EX_NOHOST, emsg, "cannot use IPv6 source address" " when IPv6 disabled"); return 0; } if (new_src6->family == AF_UNSPEC) dcc_su2ip(new_src6, &su); return 1; } dcc_pemsg(EX_NOHOST, emsg, "\"%s\" has unkown address family", addr); return 0; } static void get_src_addr(char *addr, int addr_len, const char **argp) { const char *arg, *p; arg = *argp + strspn(*argp, DCC_WHITESPACE); p = strpbrk(arg, ","DCC_WHITESPACE); if (!p) { *argp = arg+strlen(arg); STRLCPY(addr, arg, addr_len); } else { STRLCPY(addr, arg, min(p-arg+1, addr_len)); p += strspn(p, DCC_WHITESPACE); if (!CLITCMP(p, DCC_INFO_TXT_USE_SRCBAD)) p += LITZ(DCC_INFO_TXT_USE_SRCBAD); p += strspn(p, ","DCC_WHITESPACE); *argp = p; } } static u_char ck_new_srcs(DCC_EMSG *emsg, DCC_IP *new_src4, DCC_IP *new_src6, const char **argp, DCC_INFO_FGS fgs) { DCC_SOCKU su; DCC_SOCKET soc; char addr1[MAXHOSTNAMELEN+1]; char addr2[MAXHOSTNAMELEN+1]; /* separate IPv4 and IPv6 addresses */ get_src_addr(addr1, sizeof(addr1), argp); get_src_addr(addr2, sizeof(addr2), argp); memset(new_src4, 0, sizeof(*new_src4)); memset(new_src6, 0, sizeof(*new_src6)); if ((!strcmp(addr1, "-") || !strcmp(addr1, "*")) && addr2[0] == '\0') return 1; if (!ck_new_src(emsg, new_src4, new_src6, addr1, fgs)) return 0; if (addr2[0] != '\0' && !ck_new_src(emsg, new_src4, new_src6, addr2, fgs)) return 0; if (new_src4->family != AF_UNSPEC) { soc = INVALID_SOCKET; if (!udp_create(&dcc_emsg, &soc, dcc_ip2su(&su, new_src4), 1, 1, 0)) return 0; closesocket(soc); } if (new_src6->family != AF_UNSPEC) { soc = INVALID_SOCKET; if (!udp_create(&dcc_emsg, &soc, dcc_ip2su(&su, new_src6), 1, 1, 0)) return 0; closesocket(soc); } return 1; } static int src_cmd(const char *arg, const CMD_TBL_ENTRY *ce) { DCC_IP new_src4, new_src6; if (arg[0] == '\0') { if (!init_map(1, 0)) return 0; if (dcc_clnt_info->src4.ip.family == AF_UNSPEC && dcc_clnt_info->src6.ip.family == AF_UNSPEC) { printf(" no source address specified"); } else { print_info_src(dcc_clnt_info, "src="); } putchar('\n'); return 1; } if (!ck_cmd_priv(ce, 0, 1)) return 0; if (!init_map(1, 1)) return 0; /* check the host name after we have locked the IPv4 vs. IPv6 choice */ if (!ck_new_srcs(&dcc_emsg, &new_src4, &new_src6, &arg, dcc_clnt_info->fgs)) { dcc_error_msg("%s", dcc_emsg.c); cdcc_unlock(1); return 0; } if (*arg != '\0') { dcc_error_msg("unrecognized \"%s\"", arg); cdcc_unlock(1); return 0; } src4 = new_src4; dcc_clnt_info->src4.ip = new_src4; src6 = new_src6; dcc_clnt_info->src6.ip = new_src6; fix_info(1); return 1; } static int socks_cmd(const char *arg, const CMD_TBL_ENTRY *ce) { DCC_INFO_FGS new_use_socks; if (arg[0] == '\0') { if (!init_map(1, 0)) return 0; printf(" SOCKS %s\n", (info_fgs & 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->fgs & DCC_INFO_FG_SOCKS) == new_use_socks) return cdcc_unlock(1); /* nothing to do */ dcc_clnt_info->fgs ^= DCC_INFO_FG_SOCKS; info_fgs = dcc_clnt_info->fgs; fix_info(1); return 1; } static int passwd_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { DCC_PASSWD new_passwd; DCC_PATH tmp; if (arg[0] == '\0') { if (which_map == MAP_INFO) { printf(" using password in %s\n", dcc_fnm2abs_msg(&tmp, info_map_nm.c)); 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 if (srvr.passwd[0] != '\0') { printf(" password not set;" " using "DCC_PASSWD_PAT" from %s\n", srvr.passwd, dcc_fnm2abs_msg(&tmp, ids_path.c)); } 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; anon_ids_complained = 0; set_which_map(MAP_TMP); return 1; } static int id_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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; anon_ids_complained = 0; set_which_map(MAP_TMP); return 1; } static int debug_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { char debug_str[24]; char ttl_str[24]; u_long new_ttl, l, 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 } else if ((l = strtoul(arg, &p, 10)) > 0 && l < 10) { new_debug = l; arg = p; } 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 DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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 DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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 DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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; } /* parse a line of the following form * name[,port-#] [RTT+adj] [Greylist] [client-ID [password]] * The port-# can be "-" to specifiy the default DCC server port. * If both the client-ID and the password are absent, then the anonymous * client-ID is used. * A null string is assumed if the password is missing. */ static int /* 0=minor, -1=can't add, -2=can't del */ parse_srvr_nm(DCC_EMSG *emsg, DCC_SRVR_NM *nmp, /* build this entry */ u_char *has_port, /* 1=has non-null port # */ u_char *pgrey, /* 1=for greylisting */ const char *line0, /* from this string */ const char *fnm, int lno) /* that came from here */ { const char *line; char id_buf[12]; char port_buf[3]; int result; char *p; long l; result = 1; memset(nmp, 0, sizeof(DCC_SRVR_NM)); line = dcc_parse_nm_port(emsg, line0, DCC_GREY2PORT(pgrey && *pgrey), nmp->hostname, sizeof(nmp->hostname), &nmp->port, port_buf, sizeof(port_buf), fnm, lno); if (!line) return -2; if (has_port) { p = strchr(line0, ','); *has_port = (p && p < line); } /* look for RTT and greylist flag in either order */ for (;;) { /* look for greylist flag */ if (!CLITCMP(line, "greylist") && (line[LITZ("greylist")] == '\0' || line[LITZ("greylist")] == ' ' || line[LITZ("greylist")] == '\t')) { line += LITZ("greylist")+strspn(line+LITZ("greylist"), DCC_WHITESPACE); if (pgrey) *pgrey = 1; if (port_buf[0] == '\0' || !strcmp(port_buf, "-")) nmp->port = htons(DCC_GREY_PORT); continue; } /* look for optional RTT adjustment */ if (CLITCMP(line, "rtt")) break; line += LITZ("rtt"); line += strspn(line, DCC_WHITESPACE); l = strtol(line, &p, 10); if (p != line) { int wsp = strspn(p, DCC_WHITESPACE); if (!CLITCMP(p+wsp, "ms")) p += wsp+LITZ("ms"); } if (p == line || (*p != '\0' && *p != ' ' && *p != '\t')) { dcc_pemsg(EX_DATAERR, emsg, "invalid RTT adjustment%s", dcc_fnm_lno(&fnm_buf, fnm, lno)); result = 0; } else { if (l < -DCC_RTT_ADJ_MAX/1000) { l = -DCC_RTT_ADJ_MAX/1000; dcc_pemsg(EX_DATAERR, emsg, "invalid RTT adjustment%s", dcc_fnm_lno(&fnm_buf, fnm, lno)); result = 0; } else if (l > DCC_RTT_ADJ_MAX/1000) { l = DCC_RTT_ADJ_MAX/1000; dcc_pemsg(EX_DATAERR, emsg, "invalid RTT adjustment%s", dcc_fnm_lno(&fnm_buf, fnm, lno)); result = 0; } nmp->rtt_adj = l*1000; line = p+strspn(p, DCC_WHITESPACE); } } /* get the client-ID */ line = dcc_parse_word(emsg, id_buf, sizeof(id_buf), line, "client-ID", fnm, lno); if (!line) return -2; if (id_buf[0] == '\0') { nmp->clnt_id = DCC_ID_ANON; } else { nmp->clnt_id = dcc_get_id(emsg, id_buf, fnm, lno); if (nmp->clnt_id == DCC_ID_INVALID) return -1; if (nmp->clnt_id < DCC_CLNT_ID_MIN && nmp->clnt_id != DCC_ID_ANON) { dcc_pemsg(EX_DATAERR, emsg, "server-ID %d is not a client-ID", nmp->clnt_id); return -1; } } /* allow null password only for anonymous clients */ if (*line == '\0') { if (nmp->clnt_id != DCC_ID_ANON) { dcc_pemsg(EX_DATAERR, emsg, "invalid null password for client-ID %d" " for %s%s", nmp->clnt_id, nmp->hostname, dcc_fnm_lno(&fnm_buf, fnm, lno)); return -1; } return result; } line = parse_passwd(emsg, nmp->passwd, line, "passwd", fnm, lno); if (nmp->passwd[0] == '\0' || *line != '\0') { dcc_pemsg(EX_DATAERR, emsg, "invalid password server %s, client-ID %d%s", nmp->hostname, nmp->clnt_id, dcc_fnm_lno(&fnm_buf, fnm, lno)); if (nmp->clnt_id != DCC_ID_ANON) return -1; result = 0; } if (nmp->clnt_id == DCC_ID_ANON) { dcc_pemsg(EX_DATAERR, emsg, "password invalid for %s with anonymous client-ID%s", nmp->hostname, dcc_fnm_lno(&fnm_buf, fnm, lno)); result = 0; } return result; } static void delete_error(const DCC_SRVR_NM *nmp, char has_port, const char *msg) { if (has_port) { dcc_error_msg("server \"%s,%d\" %s", nmp->hostname, ntohs(nmp->port), msg); } else { dcc_error_msg("server \"%s\" %s", nmp->hostname, msg); } cdcc_unlock(1); } static int delete_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { DCC_SRVR_CLASS *class; DCC_SRVR_NM nm, *nmp, *nmp2; DCC_SRVR_ADDR *addr; const char *zi; u_char del_grey, has_port; int i; del_grey = grey_on; if (-1 > parse_srvr_nm(&dcc_emsg, &nm, &has_port, &del_grey, arg, 0, 0)) { dcc_error_msg("%s", dcc_emsg.c); return 0; } /* map and lock */ set_which_map(MAP_INFO); if (!init_map(1, 1)) return 0; class = DCC_GREY2CLASS(del_grey); nmp2 = 0; for (nmp = class->nms; nmp <= LAST(class->nms); ++nmp) { if (!strcasecmp(nmp->hostname, nm.hostname)) { /* found exact match */ if (nmp->port == nm.port) break; /* matching all except port # is ok if * there is only one possible match */ if (!has_port) { if (!nmp2) { nmp2 = nmp; } else { nmp2 = LAST(class->nms)+1; } } continue; } zi = strchr(nmp->hostname, '%'); if (!zi) continue; i = zi - nmp->hostname; if (!strncasecmp(nmp->hostname, nm.hostname, i) && (int)strlen(nm.hostname) == i) { /* all of name except zone index matches */ if (nmp->port == nm.port || !has_port) { if (!nmp2) { nmp2 = nmp; } else { nmp2 = LAST(class->nms)+1; } break; } } } if (nmp > LAST(class->nms)) { nmp = nmp2; if (!nmp) { delete_error(&nm, has_port, "not found"); return 0; } if (nmp > LAST(class->nms)) { delete_error(&nm, has_port, "ambiguous"); return 0; } } /* 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(0); return 1; } static int add_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { DCC_SRVR_CLASS *class; DCC_SRVR_NM nm, *nmp, *tgt_nmp; u_char add_grey; int i; add_grey = grey_set && grey_on; i = parse_srvr_nm(&dcc_emsg, &nm, 0, &add_grey, arg, 0, 0); if (i < 0) { dcc_error_msg("%s", dcc_emsg.c); 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; } if (i < 1) dcc_error_msg(" %s", dcc_emsg.c); /* 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(0); 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; } } } } /* open a file of server names, ports, and so forth */ static FILE * open_map_txt(DCC_EMSG *emsg, const char *nm) { FILE *f; DCC_PATH path; f = fopen(nm, "r"); if (!f) { dcc_pemsg(EX_NOINPUT, emsg, "fopen(%s): %s", dcc_fnm2abs_msg(&path, nm), ERROR_STR()); return 0; } /* the file contains passwords, * so refuse to use it if everyone can read it */ if (!dcc_ck_private(emsg, 0, nm, fileno(f))) { fclose(f); return 0; } return f; } static int /* -1=bad line 0=not option line 1=ok */ get_options(DCC_EMSG *emsg, const char *cp, DCC_INFO_FGS *new_info_fgs, DCC_IP *new_src4, DCC_IP *new_src6) { u_char option_line; sa_family_t check_family; char *p; int version; option_line = 0; version = 0; for (;;) { cp += strspn(cp, DCC_WHITESPACE); if (*cp == '\0') break; if (!CLITCMP(cp, DCC_INFO_TXT_IPV6)) { if (!CLITCMP(cp, DCC_INFO_TXT_IPV6_OFF)) { cp += LITZ(DCC_INFO_TXT_IPV6_OFF); *new_info_fgs &= ~DCC_INFO_FG_IPV4_OFF; *new_info_fgs |= DCC_INFO_FG_IPV6_OFF; check_family = AF_INET; } else if (!CLITCMP(cp, DCC_INFO_TXT_IPV6_ON)) { cp += LITZ(DCC_INFO_TXT_IPV6_ON); *new_info_fgs &= ~(DCC_INFO_FG_IPV4_OFF | DCC_INFO_FG_IPV6_OFF); check_family = AF_INET6; } else if (!CLITCMP(cp, DCC_INFO_TXT_IPV6_ONLY)) { cp += LITZ(DCC_INFO_TXT_IPV6_ONLY); *new_info_fgs &= ~DCC_INFO_FG_IPV6_OFF; *new_info_fgs |= DCC_INFO_FG_IPV4_OFF; check_family = AF_INET6; } else { break; } if (dcc_clnt_debug && !ipv_check(&dcc_emsg, check_family, 1)) dcc_error_msg("warning %s", dcc_emsg.c); option_line = 1; continue; } if (!CLITCMP(cp, DCC_INFO_TXT_USE_SOCKS)) { cp += LITZ(DCC_INFO_TXT_USE_SOCKS); cp += strspn(cp, DCC_WHITESPACE); *new_info_fgs |= DCC_INFO_FG_SOCKS; option_line = 1; continue; } if (!CLITCMP(cp, DCC_INFO_TXT_USE_SRC)) { cp += LITZ(DCC_INFO_TXT_USE_SRC); if (!ck_new_srcs(&dcc_emsg, new_src4, new_src6, &cp, *new_info_fgs)) return -1; option_line = 1; continue; } if (!CLITCMP(cp, DCC_INFO_TXT_VERSION)) { cp += LITZ(DCC_INFO_TXT_VERSION); version = strtol(cp, &p, 10); if (version != DCC_INFO_TXT_VERSION_CUR || (*p != '\0' && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r')){ dcc_pemsg(EX_DATAERR, emsg, "unrecognized text version number"); return -1; } cp = p; option_line = 1; continue; } if (option_line) break; return 0; } if (option_line && *cp != '\0') { dcc_pemsg(EX_DATAERR, emsg, "unrecognized option \"%s\"", cp); return -1; } /* ignore IPv6 settings in old files */ if (version < 3) *new_info_fgs &= ~(DCC_INFO_FG_IPV4_OFF | DCC_INFO_FG_IPV6_OFF); return 1; } static int load_cmd(const char *lfile, const CMD_TBL_ENTRY *ce DCC_UNUSED) { DCC_INFO_FGS new_info_fgs; u_char load_grey; 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; int got_options; DCC_IP new_src4, new_src6; char buf[sizeof(DCC_SRVR_NM)*3]; const char *bufp; DCC_PATH tmp; FILE *f; int fd, lno; int i; 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 = open_map_txt(&dcc_emsg, lfile); if (!f) { dcc_error_msg("%s", dcc_emsg.c); return 0; } } /* parse the text file to create a pair of lists of server names */ new_info_fgs = info_fgs; got_options = -1; num_dcc_nms = 0; memset(dcc_nms, 0, sizeof(dcc_nms)); num_grey_nms = 0; memset(grey_nms, 0, sizeof(grey_nms)); memset(&new_src4, 0, sizeof(new_src4)); memset(&new_src6, 0, sizeof(new_src6)); lno = 0; for (;;) { bufp = fgets(buf, sizeof(buf), f); if (!bufp) { if (ferror(f)) { dcc_error_msg("fgets(%s): %s", !lfile ? "STDIN" : dcc_fnm2abs_msg(&tmp, lfile), ERROR_STR()); fclose(f); return 0; } break; } i = strlen(bufp); if (buf[i-1] == '\n') buf[i-1] = '\0'; ++lno; /* skip blank lines and comments */ bufp += strspn(bufp, DCC_WHITESPACE); if (*bufp == '\0' || *bufp == '#') continue; /* look for version # and flags in the first non-comment line */ if (got_options < 0) { got_options = get_options(&dcc_emsg, bufp, &new_info_fgs, &new_src4, &new_src6); if (got_options < 0) { dcc_error_msg("%s%s", dcc_emsg.c, dcc_fnm_lno(&fnm_buf, lfile, lno)); fclose(f); return 0; } if (got_options > 0) continue; } load_grey = 0; i = parse_srvr_nm(&dcc_emsg, &new_nm, 0, &load_grey, bufp, lfile, lno); if (i < 0) { dcc_error_msg("%s", dcc_emsg.c); fclose(f); return 0; } if (i < 1) dcc_error_msg(" %s", dcc_emsg.c); 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, dcc_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", dcc_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", dcc_fnm_lno(&fnm_buf, lfile, lno)); fclose(f); return 0; } dcc_nms[num_dcc_nms++] = new_nm; } } fclose(f); if (!got_options && num_grey_nms == 0 && num_dcc_nms == 0) { dcc_error_msg("no DCC server names or options in %s", dcc_fnm2abs_msg(&tmp, lfile)); 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.c, 0, 0, 0, 0, 0, &new_src4, &new_src6, new_info_fgs)) return 0; printf(" created %s\n", dcc_fnm2abs_msg(&tmp, info_map_nm.c)); 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->fgs = info_fgs = new_info_fgs; if (new_src4.family == AF_INET) dcc_clnt_info->src4.ip = new_src4; if (new_src6.family == AF_INET6) dcc_clnt_info->src6.ip = new_src6; fix_info(1); if (!quiet) { if (!lfile) printf("##################\n\n"); return info_work("", 0) ; } return 1; } static int info_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { /* map, copy, and unlock the information * prefer to talk to the server, but don't wait * unless we have changed the file and are not being quiet */ return info_work(arg, (!map_changed || quiet) ? DCC_CLNT_FG_NO_MEASURE_RTTS : 0); } static int rtt_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { if (!init_map(1, 1)) return 0; dcc_force_measure_rtt(1); cdcc_unlock(1); /* wait to talk to the server, but don't insist */ return info_work(arg, 0); } /* delete a checksum */ static int /* 1=ok, 0=bad checksum, -1=fatal */ delck_sub(DCC_EMSG *emsg, WF *wf DCC_UNUSED, DCC_CK_TYPES type, DCC_SUM *sum, DCC_TGTS tgts DCC_UNUSED) { struct timeval cmd_start, cmd_end; char type_buf[DCC_XHDR_MAX_TYPE_LEN_BUF]; 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", type2str(type_buf, sizeof(type_buf), type, 0, 1, grey_on), 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, grey_clnt_fgs | 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.c); } else { switch (resp.hdr.op) { case DCC_OP_OK: break; case DCC_OP_ERROR: cdcc_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 DCC_UNUSED) { char type_str[DCC_XHDR_MAX_TYPE_LEN_BUF]; 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 DCC_UNUSED) { 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_unpack64(const u_char *cp, DCC_SCNTR *vp) { u_char c; DCC_SCNTR v; int shift; v = 0; shift = 0; do { c = *cp++; v |= ((DCC_SCNTR)(c & 0x7f)) << shift; shift += 7; } while (c & 0x80); *vp = v; return cp; } static int client_unpack(const u_char *cp0, DCC_ADMN_RESP_CLIENTS_FLAGS *flagsp, DCC_CLNT_ID *clnt_idp, u_int *offsetp, DCC_PTIME *last_usedp, DCC_PTIME *prev_last_usedp, DCC_SCNTR *reqsp, DCC_SCNTR *nopsp, u_char *versp, DCC_SOCKU *su) { const u_char *cp; DCC_ADMN_RESP_CLIENTS_FLAGS flags; DCC_SCNTR v; struct in6_addr in6_addr; struct in_addr in_addr; #ifdef DCC_PKT_VERS6 if (aop_resp.hdr.pkt_vers <= DCC_PKT_VERS6) { #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_BLACK | DCC_ADMN_RESP_CLIENTS_SKIP); *clnt_idp = CPY4(cl->clnt_id); if (flags & DCC_ADMN_RESP_CLIENTS_SKIP) { /* skip place keepers */ *offsetp += CPY3(cl->reqs); *last_usedp = 0; *reqsp = 0; } else { *last_usedp = CPY4(cl->last_used); *reqsp = CPY3(cl->reqs); } *prev_last_usedp = 0; *nopsp = CPY2(cl->nops); if (flags & DCC_ADMN_RESP_CLIENTS_IPV6) { memcpy(&in6_addr, &cl->addr, sizeof(in6_addr)); dcc_mk_inet6_su(su, &in6_addr, 0, 0); return (sizeof(*cl) - sizeof(cl->addr) + sizeof(cl->addr.ipv6)); } memcpy(&in_addr, &cl->addr, sizeof(in_addr)); dcc_mk_inet_su(su, &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_BLACK | DCC_ADMN_RESP_CLIENTS_DROPPED | DCC_ADMN_RESP_CLIENTS_SKIP | DCC_ADMN_RESP_CLIENTS_LAST | DCC_ADMN_RESP_CLIENTS_BLOCK); /* if the version is absent, * then it must be the same as the previous value */ if (flags & DCC_ADMN_RESP_CLIENTS_VERS) *versp = *cp++; #ifdef DCC_PKT_VERS9 if (aop_resp.hdr.pkt_vers <= DCC_PKT_VERS9) { v = *cp++ << 24; v |= *cp++ << 16; v |= *cp++ << 8; v |= *cp++; *prev_last_usedp = 0; } else #endif cp = client_unpack64(cp, &v); if (flags & DCC_ADMN_RESP_CLIENTS_SKIP) { *offsetp += v; *last_usedp = 0; } else { if (v <= DCC_ADMIN_CLIENTS_MAX_DELTA) v += *prev_last_usedp; *prev_last_usedp = v; *last_usedp = v; } if (flags & DCC_ADMN_RESP_CLIENTS_ANON) v = DCC_ID_ANON; else cp = client_unpack64(cp, &v); *clnt_idp = v; cp = client_unpack64(cp, reqsp); cp = client_unpack64(cp, nopsp); if (flags & DCC_ADMN_RESP_CLIENTS_IPV6) { memcpy(&in6_addr, cp, sizeof(in6_addr)); dcc_mk_inet6_su(su, &in6_addr, 0, 0); cp += 16; } else { memcpy(&in_addr, cp, sizeof(in_addr)); dcc_mk_inet_su(su, &in_addr, 0); cp += 4; } return cp - cp0; } typedef struct ct { struct ct *lt, *gt; /* sorted order */ DCC_PTIME last_used; DCC_SCNTR reqs; DCC_SCNTR nops; u_char fgs; # define CT_FG_EARLY 0x01 # define CT_FG_BLACK 0x02 # define CT_FG_DROPPED 0x04 # define CT_FG_BLOCK 0x08 u_char vers; DCC_CLNT_ID clnt_id; DCC_SOCKU su; } CT; static void print_ct_id(const CT *ct) { char buf[22+1]; DCC_CLNT_ID id; id = ct->clnt_id & ~DCC_ADMN_RESP_CLIENTS_NO_AUTH; if (id == DCC_ID_CLIENTS_MULT) { strcpy(buf, "multi"); } else if (id == DCC_ID_SRVR_ROGUE) { strcpy(buf, "server"); } else { snprintf(buf, sizeof(buf), "%d", id); } if (ct->clnt_id & DCC_ADMN_RESP_CLIENTS_NO_AUTH) STRLCAT(buf, "*", sizeof(buf)); printf(" %6s", buf); } /* get the server's list of recent clients */ static int clients_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { u_char nonames, sort, ids, req_flags; DCC_CLNT_ID tgt_id; u_char passed_flags, passed_max_clients, passed_thold, passed_cidr; struct in6_addr addr6; DCC_AOP_CLIENTS_VAL5 val5; u_int val5_len; u_int max_clients, thold; u_int total, subtotal; DCC_SCNTR max_ops, max_nops; int ops_width, nops_width; u_int offset; /* next client wanted from server */ u_int num_clients, list_len; DCC_SOCKU su; CT *cttop, *ctbot, *ctnew, *ct; struct { u_int clients; DCC_SCNTR reqs; DCC_SCNTR nops; DCC_SCNTR black_reqs; DCC_SCNTR black_nops; } totals[DCC_PKT_VERS_NOP_MAX+1]; char date_buf[40]; struct tm last, now; char *p; const char *ac; u_char need_head; u_int n; 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(&val5, 0, sizeof(val5)); val5_len = 0; ac = strpbrk(arg, "/.:"); /* look for "-n", "-ns", "-n -s", etc. */ nonames = 0; sort = 0; ids = 0; tgt_id = DCC_ID_INVALID; 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 'I': arg += 1+strspn(arg+1, " \t"); tgt_id = strtoul(arg, &p, 10); if (tgt_id < DCC_SRVR_ID_MIN || tgt_id > DCC_CLNT_ID_MAX || (*p != '\0' && *p != ' ' && *p != '\t')) { help_cmd("clients", 0); return -1; } arg = p-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 = str2cidr(0, &addr6, 0, arg); if (bits <= 0) return -1; memcpy(val5.tgt_addr.addr6, &addr6, sizeof(addr6)); val5.tgt_addr.bits = bits; if (val5_len < sizeof(val5.tgt_addr)) val5_len = sizeof(val5.tgt_addr); 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_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; /* decide which server to use and its protocol */ if (!rdy_ctxt(0)) return 0; if (tgt_id != DCC_ID_INVALID) { const DCC_SRVR_CLASS *class; SRVR_INX srvr_inx; DCC_CLNT_ID tgt_id_net; dcc_ctxts_lock(); if (!dcc_info_lock(0)) { dcc_ctxts_lock(); return 0; } class = DCC_GREY2CLASS(grey_on); srvr_inx = class->srvr_inx; if (VALID_SRVR(class, srvr_inx) && class->addrs[srvr_inx].srvr_pkt_vers >= DCC_PKT_VERS12) { tgt_id_net = htonl(tgt_id); memcpy(&val5.tgt_id, &tgt_id_net, sizeof(val5.tgt_id)); val5_len = sizeof(DCC_AOP_CLIENTS_VAL5); } if (!cdcc_unlock(1)) 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(totals, 0, sizeof(totals)); offset = 0; num_clients = 0; list_len = 0; cttop = 0; ctbot = 0; for (;;) { DCC_OPS result; int done, result_len; u_char vers; DCC_ADMN_RESP_CLIENTS_FLAGS resp_flags; DCC_CLNT_ID clnt_id; DCC_PTIME last_used; DCC_PTIME prev_last_used; DCC_SCNTR reqs, 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_clnt_fgs, NO_SRVR, clock_kludge, ids ? DCC_AOP_CLIENTS_ID : DCC_AOP_CLIENTS, (offset << 16) + thold, ISZ(aop_resp.resp.val.string ) >> DCC_ADMIN_CLIENTS_SHIFT, req_flags, offset >> 16, &val5, val5_len, &aop_resp, &op_result_su); if (result == DCC_OP_INVALID || result == DCC_OP_ERROR) { cdcc_error_msg("%s", dcc_emsg.c); break; } /* print heading before the first chunk */ if (!offset) print_aop(NO_SRVR); result_len = (ntohs(aop_resp.hdr.len) - (sizeof(aop_resp.resp) - sizeof(aop_resp.resp.val.string))); /* stop when the server had nothing to add */ if (result_len <= 1) break; done = 0; vers = 0; prev_last_used = 0; do { done += client_unpack(&aop_resp.resp.val.clients[done], &resp_flags, &clnt_id, &offset, &last_used, &prev_last_used, &reqs, &nops, &vers, &su); if (resp_flags & DCC_ADMN_RESP_CLIENTS_SKIP) continue; /* quit if we are in some kind of loop */ if (++num_clients > DCC_ADMIN_RESP_MAX_CLIENTS) { dcc_error_msg("assume looping after %d" " responses", num_clients); goto stop; } ++offset; /* forget this entry if it is for the wrong ID */ if (tgt_id != DCC_ID_INVALID && tgt_id != (clnt_id & ~DCC_ADMN_RESP_CLIENTS_NO_AUTH)) continue; /* add the new entry to the possibly sorted list */ if (list_len <= max_clients) { ctnew = dcc_malloc(sizeof(*ctnew)); } else { ctnew = ctbot; ctbot = ctbot->gt; if (ctbot) ctbot->lt = 0; --list_len; } memset(ctnew, 0, sizeof(*ctnew)); ctnew->vers = vers; ctnew->clnt_id = clnt_id; ctnew->last_used = last_used; ctnew->reqs = reqs; if (resp_flags & DCC_ADMN_RESP_CLIENTS_DROPPED) ctnew->fgs |= (CT_FG_EARLY | CT_FG_DROPPED); if (resp_flags & DCC_ADMN_RESP_CLIENTS_BLOCK) ctnew->fgs |= CT_FG_BLOCK; if (resp_flags & DCC_ADMN_RESP_CLIENTS_BLACK) { ctnew->fgs |= (CT_FG_EARLY | CT_FG_BLACK); /* clients in blacklisted address blocks * are not reported separately */ if (vers < DIM(totals)) { ++totals[vers].clients; totals[vers].black_reqs += reqs; totals[vers].black_nops += nops; } else { ++totals[0].clients; totals[0].reqs += reqs; totals[0].nops += nops; } } if (vers != DCC_PKT_VERS_INVALID && vers != DCC_PKT_VERS_MULTI && !(resp_flags & DCC_ADMN_RESP_CLIENTS_BLOCK) && !(resp_flags & DCC_ADMN_RESP_CLIENTS_BLACK)) { if (vers < DIM(totals)) { ++totals[vers].clients; totals[vers].reqs += reqs; totals[vers].nops += nops; } else { ++totals[0].clients; totals[0].reqs += reqs; totals[0].nops += nops; } } total += reqs; if (max_ops < reqs) max_ops = reqs; ctnew->nops = nops; if (max_nops < nops) max_nops = nops; ctnew->su = su; ct = ctbot; n = list_len; while (ct) { if (!sort || ((ct->fgs & CT_FG_EARLY) && !(ctnew->fgs & CT_FG_EARLY)) || (!((ct->fgs ^ ctnew->fgs) & CT_FG_EARLY) && ct->reqs >= ctnew->reqs)) break; /* We will insert the new entry above this one * in the sorted list. * Increase the theshold for next batch from * the server if we have enough entries */ if (--n >= max_clients && thold < ct->reqs && thold > 1) thold = min(ct->reqs, DCC_ADMIN_CLIENTS_MAX_THOLD); ct = ct->gt; } if (!ct) { ctnew->lt = cttop; if (cttop) { cttop->gt = ctnew; } else { ctbot = ctnew; } cttop = ctnew; } else { ctnew->gt = ct; ctnew->lt = ct->lt; if (ct->lt) ct->lt->gt = ctnew; else ctbot = ctnew; ct->lt = ctnew; } ++list_len; } while (done < result_len); if (done != result_len) { dcc_error_msg("wrong sized clients response; %d != %d", result_len, done); break; } /* quit if the server ran out of things to say */ if (resp_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 knows. */ if (!sort && offset >= max_clients) break; } 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 = cttop; ct; ct = ctnew) { ctnew = ct->lt; 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->reqs; printf("%3d%% %3d%% ", (int)(ct->reqs*100.0/total), (int)(subtotal*100.0/total)); } printf(L_DWPAT(*)" "L_DWPAT(*), ops_width, ct->reqs, nops_width, ct->nops); if (sort && ids) print_ct_id(ct); /* print year and no time if it was long ago */ dcc_localtime(ct->last_used, &last); if (last.tm_year != now.tm_year && (last.tm_mon < 6 || now.tm_mon > 2)) dcc_time2str(date_buf, sizeof(date_buf), " %Y/%m/%d ", ct->last_used); else dcc_time2str(date_buf, sizeof(date_buf), " %m/%d %X", ct->last_used); fputs(date_buf, stdout); if (!sort) print_ct_id(ct); if (req_flags & DCC_AOP_CLIENTS_VERS) { if ((ct->fgs & CT_FG_BLOCK) && ct->vers == DCC_PKT_VERS_MULTI) printf(" %2s", "m"); else if (ct->vers == DCC_PKT_VERS_INVALID) printf(" %2s", ""); else printf(" %2d", ct->vers); } if (ct->fgs & CT_FG_BLACK) { fputs(" BLACKLIST", stdout); n = 16-LITZ(" BLACKLIST"); } else if (ct->fgs & CT_FG_DROPPED) { fputs(" BAD", stdout); n = 16-LITZ(" BAD"); } else { n = 16; } if (!ids) { char name[DCC_MAXDOMAINLEN]; char sustr[DCC_SU2STR_SIZE]; dcc_su2str2(sustr, sizeof(sustr), &ct->su); if (ct->fgs & CT_FG_BLOCK) { if (ct->su.sa.sa_family == AF_INET) i = DCC_ADMN_RESP_CLIENTS_IPV4_BITS; else i = DCC_ADMN_RESP_CLIENTS_IPV6_BITS; printf(" %s/%d", sustr, i); } else if (nonames) { printf(" %s", sustr); } else { printf(" %-*s %s", n, sustr, dcc_su2name(name, sizeof(name), &ct->su)); } } putchar('\n'); } memset(ct, 0, sizeof(*ct)); dcc_free(ct); } putchar('\n'); need_head = 1; for (i = 0; i < DIM(totals); ++i) { if (totals[i].clients == 0) continue; if (need_head) { need_head = 0; fputs(" blacklisted\n", stdout); fputs("version requests nops requests nops\n", stdout); /* dddddd dddddddd dddddddd dddddddd dddddddd */ } /* do not show numbers of clients because our number is * wrong for blacklisted address blocks */ printf("%6d " L_DWPAT(8)" "L_DWPAT(8)" "L_DWPAT(8)" "L_DWPAT(8)"\n", i, totals[i].reqs, totals[i].nops, totals[i].black_reqs, totals[i].black_nops); } if (!need_head) putchar('\n'); return 1; } /* get and set the server's default anonymous client delay */ static int anon_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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_clnt_fgs, 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) { cdcc_error_msg("%s", dcc_emsg.c); 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 DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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 DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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 DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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, const CMD_TBL_ENTRY *ce DCC_UNUSED) { DCC_AOP_FLODS op; char id_buf[12]; u_char all; u_int32_t id, next_id; u_char heading; int sresult; if (!arg) return -1; op = DCC_AOP_FLOD_STATS; id = DCC_ID_INVALID; all = 0; while (*arg != '\0') { arg = dcc_parse_word(&dcc_emsg, id_buf, sizeof(id_buf), arg, "server-ID", 0, 0); if (!arg) { dcc_error_msg("%s", dcc_emsg.c); return 0; } if (!all && id == DCC_ID_INVALID && id_buf[0] >= '1' && id_buf[0] <= '9') { id = dcc_get_id(&dcc_emsg, id_buf, 0, 0); if (id != DCC_ID_INVALID) continue; } if (id == DCC_ID_INVALID && !strcasecmp(id_buf, "all")) { all = 1; continue; } if (!strcasecmp(id_buf, "clear")) { op = DCC_AOP_FLOD_STATS_CLEAR; continue; } return -1; } if (!all && id == DCC_ID_INVALID) all = 1; heading = 1; if (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; } } return do_aop(DCC_AOP_FLOD, id*256 + op, NO_SRVR, heading); } /* get the statistics from all known servers */ static int stats_cmd(const char *arg, const CMD_TBL_ENTRY *ce DCC_UNUSED) { DCC_SRVR_CLASS *class; SRVR_INX srvr_inx; int srvrs_gen; DCC_AOPS aop; /* look for "clear" or "clear 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(3, "stats clear", "operation")) 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 (!get_passwd(1, "stats clear", "operation")) return 0; 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 servers that have never answered a NOP */ 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 DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { 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 DCC_UNUSED, const CMD_TBL_ENTRY *ce DCC_UNUSED) { return (do_aop(DCC_AOP_TRACE_ON, grey_on ? DCC_TRACE_GREY_DEF : DCC_TRACE_DEF, NO_SRVR, 1) && do_aop(DCC_AOP_TRACE_OFF, DCC_TRACE_BITS & ~(grey_on ? DCC_TRACE_GREY_DEF : DCC_TRACE_DEF), NO_SRVR, 1)); }