/* Distributed Checksum Clearinghouse * * control dcc server * * Copyright (c) 2006 by Rhyolite Software, LLC * * This agreement is not applicable to any entity which sells anti-spam * solutions to others or provides an anti-spam solution as part of a * security solution sold to other entities, or to a private network * which employs the DCC or uses data provided by operation of the DCC * but does not provide corresponding data to other users. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * Parties not eligible to receive a license under this agreement can * obtain a commercial license to use DCC and permission to use * U.S. Patent 6,330,590 by contacting Commtouch at http://www.commtouch.com/ * or by email to nospam@commtouch.com. * * A commercial license would be for Distributed Checksum and Reputation * Clearinghouse software. That software includes additional features. This * free license for Distributed ChecksumClearinghouse Software does not in any * way grant permision to use Distributed Checksum and Reputation Clearinghouse * software * * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. * * Rhyolite Software DCC 1.3.50-1.176 $Revision$ */ #include "dcc_ck.h" #include "dcc_xhdr.h" #include "dcc_heap_debug.h" #include "dcc_ids.h" #ifndef DCC_WIN32 #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 const char *ids_nm; 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 info_flags = 0; static u_char grey_set; static u_char quiet; static u_char do_cmds(char *); static u_char init_map(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 uid_0; /* 1=require set-UID privileges */ 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 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 trace_def; static const CMD_TBL_ENTRY cmd_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]"}, {"new map", new_map_cmd, 0, 0, 0, "new map [map]"}, {"delete", delete_cmd, 1, 0, 1, "delete host[,port]"}, {"add", add_cmd, 1, 0, 1, "add host,[port|-] [RTT+/-#] [ID [passwd]]"}, {"load", load_cmd, 1, 0, 1, "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 #"}, {"passwd", passwd_cmd, 0, 0, 0, "passwd secret"}, {"password", 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]"}, {"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 [-nsiaV] [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"}, {"flood FFWD in",ffwd_in, 1, 1, 0, "flood FFWD in ID"}, {"flood FFWD out",ffwd_out, 1, 1, 0, "flood FFWD out ID"}, {"flood stats", flod_stats, 1, 1, 0, "flood stats [clear] {ID|all}"}, {"stats", stats_cmd, 0, 0, 0, "stats [clear|all]"}, {"status", stats_cmd, 0, 0, 0, 0}, {"trace default",trace_def, 2, 1, 0, "trace default"}, }; #define PRV_MSG ";\n" \ " use the \"id server-ID\" command\n" \ " and either \"passwd secret\" or `su` to read passwords from %s" static AOP_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; } aop_tbl[] = { #define TMAC(s,b) \ {"trace "#s" on", "trace "#s" {on|off}", \ DCC_AOP_TRACE_ON, 1, DCC_TRACE_##b},\ {"trace "#s" off", "", 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), #undef TMAC {"stop", 0, DCC_AOP_STOP, 1, 0}, {"system stop", 0, DCC_AOP_STOP, 1, 1}, {"flood check", 0, DCC_AOP_FLOD, 1, DCC_AOP_FLOD_CHECK}, {"flood shutdown", 0, DCC_AOP_FLOD, 1, DCC_AOP_FLOD_SHUTDOWN}, {"flood halt", 0, DCC_AOP_FLOD, 1, DCC_AOP_FLOD_HALT}, {"flood resume", 0, DCC_AOP_FLOD, 1, DCC_AOP_FLOD_RESUME}, {"flood list", 0, DCC_AOP_FLOD, 0, DCC_AOP_FLOD_LIST}, {"DB unlock", 0, DCC_AOP_DB_UNLOCK, 1, 0}, {"DB new", 0, DCC_AOP_DB_NEW, 1, 0}, }; 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:")) != EOF) { 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; if (!dcc_cdhome(dcc_emsg, homedir)) dcc_error_msg("%s", dcc_emsg); set_ids_path(0, ids_nm); dcc_clnt_unthread_init(); dcc_wf_init(&cmn_wf, 0); dcc_all_srvrs = 1; if (!init_map(!quiet)) which_map = MAP_TMP; else dcc_ctxts_unlock(); /* 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); } if (!dcc_info_unlock(dcc_emsg)) dcc_error_msg("%s", dcc_emsg); ++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 (;;) { if (!dcc_info_unlock(dcc_emsg)) dcc_error_msg("%s", dcc_emsg); 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); } } static u_char /* 0=failed, 1=ok */ get_passwd(u_char privileged) { ID_TBL *srvr_clnt_tbl; srvr.clnt_id = srvr_clnt_id; if (passwd_set) { memcpy(srvr.passwd, passwd, sizeof(srvr.passwd)); return (!privileged || srvr.clnt_id != DCC_ID_ANON); } memset(srvr.passwd, 0, sizeof(srvr.passwd)); 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) return !privileged; if (load_ids(dcc_emsg, &srvr_clnt_tbl, srvr_clnt_id) < 0) { if (srvr_clnt_id != DCC_ID_ANON) { if (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 clear_conn(void) { if (ctxt) { dcc_rel_ctxt(ctxt); ctxt = 0; } } 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 (!dcc_unmap_info(dcc_emsg)) dcc_error_msg("%s", dcc_emsg); clear_conn(); which_map = new; if (new == MAP_INFO) { passwd_set = 0; src.family = AF_UNSPEC; } } /* start talking to the local map file * on success the contexts and mapped file are locked */ static u_char /* 0=failed 1=mapped and locked */ init_map(u_char complain) { u_char result; dcc_ctxts_lock(); if (which_map == MAP_TMP) { result = dcc_map_lock_tmp_info(dcc_emsg, &srvr, &src, info_flags); } else { result = dcc_map_lock_info(dcc_emsg, info_map_nm, -1); } if (result) { info_flags = dcc_clnt_info->flags; return 1; } dcc_ctxts_unlock(); if (complain) dcc_error_msg("%s", dcc_emsg); return 0; } /* start talking to a DCC server * If we already had a private map file, forget it. * on success the contexts are locked but the mapped file is not locked */ static u_char /* 0=failed, 1=ok */ init_conn(u_char mode) /* 1=no srvr ok, 2=ignore fail backoff */ { u_int fgs; fgs = ((grey_on ? DCC_CLNT_FG_GREY : 0) | (mode >= 1 ? DCC_CLNT_FG_NO_SRVR_OK : 0) | (mode > 1 ? 0 : DCC_CLNT_FG_NO_FAIL)); if (ctxt) { if (!dcc_clnt_rdy(dcc_emsg, ctxt, fgs)) { dcc_error_msg("%s", dcc_emsg); return 0; } /* check the other (greylist or not) server */ if (which_map != MAP_TMP && !dcc_clnt_rdy(dcc_emsg, ctxt, fgs ^ DCC_CLNT_FG_GREY) && dcc_clnt_debug) dcc_error_msg("%s", dcc_emsg); info_flags = dcc_clnt_info->flags; return 1; } if (which_map == MAP_TMP) { /* create a brand new temporary map */ ctxt = dcc_tmp_clnt_init(dcc_emsg, ctxt, &srvr, &src, fgs, info_flags); if (!ctxt) { dcc_error_msg("%s", dcc_emsg); return 0; } info_flags = dcc_clnt_info->flags; return 1; } if (!init_map(1)) /* lock things */ return 0; if (!grey_set) { grey_on = (dcc_clnt_info->dcc.nms[0].hostname[0] == '\0' && dcc_clnt_info->grey.nms[0].hostname[0] != '\0'); } ctxt = dcc_clnt_init(dcc_emsg, ctxt, info_map_nm, fgs); if (!ctxt) { dcc_error_msg("%s", dcc_emsg); return 0; } /* check the other (greylist or not) server */ if (!dcc_clnt_rdy(dcc_emsg, ctxt, fgs ^ DCC_CLNT_FG_GREY) && dcc_clnt_debug) dcc_error_msg("%s", dcc_emsg); info_flags = dcc_clnt_info->flags; return 1; } static void dcc_map_changed(void) { dcc_force_measure_rtt(&dcc_clnt_info->dcc, 1); dcc_force_measure_rtt(&dcc_clnt_info->grey, 1); if (ctxt) ctxt->bind_su.sa.sa_family = AF_UNSPEC; } /* 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 anum) /* -1 or server index */ { const DCC_SRVR_CLASS *class; char date_buf[40]; char sustr[DCC_SU2STR_SIZE]; const char *srvr_nm; struct tm tm; dcc_su2str2(sustr, sizeof(sustr), &op_result_su); class = DCC_GREY2CLASS(grey_on); /* Display the preferred server if anum is DCC_NO_SRVR */ if (anum == DCC_NO_SRVR) anum = class->act_inx; if (anum < class->num_addrs) { srvr_nm = class->nms[class->addrs[anum].nm_inx].hostname; if (srvr_nm && strcmp(srvr_nm, sustr)) { fputs(srvr_nm, stdout); putchar(' '); } printf("%s\n server-ID %d", dcc_su2str_err(&op_result_su), class->addrs[anum].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_info_nm); strftime(date_buf, sizeof(date_buf), " %X", dcc_localtime(op_start.tv_sec, &tm)); 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 anum) { DCC_OPS result; if (!init_conn(0)) return 0; gettimeofday(&op_start, 0); result = dcc_aop(dcc_emsg, ctxt, grey_on, anum, 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 anum, /* index of server */ u_char psrvr) /* 1=print server name */ { if (psrvr) print_aop(anum); /* 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 anum, u_char psrvr) { if (!start_aop(aop, val1, anum)) return 0; fin_aop(anum, psrvr); return 1; } static u_char /* 0=bad command, 1=did it */ do_op(const char *op) { int op_num; op_num = 0; for (;;) { if (op_num >= DIM(aop_tbl)) { dcc_error_msg("unrecognized command \"%s\"", op); return 0; } /* do a command */ if (!cmd_cmp(op, aop_tbl[op_num].op)) break; op_num++; } /* send an administrative request to the server */ if (!get_passwd(aop_tbl[op_num].privileged)) { dcc_error_msg("\"%s\" is a privileged operation"PRV_MSG, aop_tbl[op_num].op, ids_path); return 0; } /* try to send it */ return do_aop(aop_tbl[op_num].aop, aop_tbl[op_num].val, DCC_NO_SRVR, 1); } static u_char ck_priv_cmd(const CMD_TBL_ENTRY *ce, u_char uid_0, u_char privileged) { /* always call get_passwd() so we have always fetched a password */ if (!get_passwd(privileged) || (which_map != MAP_TMP && uid_0 && 0 > access(info_map_nm, R_OK) && errno != ENOENT && errno != ENOTDIR)) { dcc_error_msg("\"%s\" is a privileged command"PRV_MSG, ce->cmd, ids_path); return 0; } return 1; } static u_char /* 1=ok 0=bad command */ cmd(const char *p) { const char *arg; int cmd_num, j; const CMD_TBL_ENTRY *ce; /* look for the string as a command and execute it if we find */ for (cmd_num = 0; cmd_num < DIM(cmd_tbl); ++cmd_num) { ce = &cmd_tbl[cmd_num]; arg = cmd_cmp(p, ce->cmd); /* if the command table entry and the command completely * matched, then infer a null argument */ if (!arg) { if (!ck_priv_cmd(ce, ce->uid_0, ce->privileged)) return 0; if (ce->args != 1) { j = ce->fnc("", ce); if (j >= 0) return j; } help_cmd(p, 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. Trim and use it as the argument */ j = strspn(arg, DCC_WHITESPACE); if (j) { if (ce->args == 2) { help_cmd(p, 0); /* arg not allowed */ return 0; } if (!ck_priv_cmd(ce, ce->uid_0, ce->privileged)) return 0; j = ce->fnc(arg+j, ce); if (j >= 0) return j; help_cmd(p, 0); return 0; } } /* otherwise try to interpret it as a DCC administrative packet */ return do_op(p); } 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 *p; /* say something about one command */ if (arg) { for (i = 0; i < DIM(cmd_tbl); ++i) { p = cmd_cmp(arg, cmd_tbl[i].cmd); if (!p || *p == ' ' || *p == '\t') { p = cmd_tbl[i].help_str; if (!p) break; if (*p == '\n') ++p; printf("usage: %s\n", p); return 1; } } for (i = 0; i < DIM(aop_tbl); ++i) { p = cmd_cmp(arg, aop_tbl[i].op); if (!p || *p == ' ' || *p == '\t') { p = aop_tbl[i].help_str; if (!p || !*p) p = aop_tbl[i].op; printf("usage: %s\n", p); return 1; } } } /* talk about all of the commands */ printf(" version "DCC_VERSION"\n"); pos = 0; for (i = 0; i < DIM(cmd_tbl); ++i) { p = cmd_tbl[i].help_str; if (!p) continue; pos = help_cmd_print(pos, p); } for (i = 0; i < DIM(aop_tbl); ++i) { p = aop_tbl[i].help_str; if (!p) { p = aop_tbl[i].op; } else if (!*p) { continue; } pos = help_cmd_print(pos, p); } 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; clear_conn(); } else if (!strcmp(arg, "on")) { grey_on = 1; grey_set = 1; clear_conn(); } 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(dcc_emsg, arg)) { dcc_error_msg("%s", dcc_emsg); 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) { DCC_PATH path; if (arg[0] == '\0') { if (which_map == MAP_INFO) printf(" using map file %s\n", fnm2path_err(path, dcc_info_nm)); else printf(" map file %s but using temporary file\n", fnm2path_err(path, 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) { DCC_PATH path; 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", fnm2path_err(path, dcc_info_nm)); return info_cmd("", ce); } return 1; } /* server hostname */ static int host_cmd(const char *arg, const CMD_TBL_ENTRY *ce) { DCC_SRVR_NM nm; int error; if (arg[0] == '\0') { if (which_map == MAP_INFO) return info_cmd("", ce); 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)) { set_which_map(MAP_TMP); return 0; } dcc_ctxts_unlock(); 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) { u_int port; if (arg[0] == '\0') { if (which_map == MAP_INFO) return info_cmd("", ce); 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 (!init_map(1)) return 0; if (arg[0] == '\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_priv_cmd(ce, 1, 0)) return 0; dcc_ctxts_lock(); if (!dcc_clnt_resolve_lock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); return 0; } if (dcc_clnt_info && (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; dcc_map_changed(); } if (!dcc_clnt_resolve_unlock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); return 0; } dcc_ctxts_unlock(); if (init_conn(2) && (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 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 (!init_map(1)) return 0; if (arg[0] == '\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_priv_cmd(ce, 1, 0)) return 0; dcc_ctxts_lock(); if (!dcc_clnt_resolve_lock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); return 0; } src = new_ip; dcc_clnt_info->src = src; if (!dcc_clnt_resolve_unlock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); return 0; } dcc_ctxts_unlock(); return 1; } static int socks_cmd(const char *arg, const CMD_TBL_ENTRY *ce) { u_char new_use_socks; if (!init_map(1)) return 0; if (arg[0] == '\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_priv_cmd(ce, 1, 0)) return 0; dcc_ctxts_lock(); if (!dcc_clnt_resolve_lock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); return 0; } if (dcc_clnt_info && (dcc_clnt_info->flags & DCC_INFO_FG_SOCKS) != new_use_socks) { dcc_clnt_info->flags ^= DCC_INFO_FG_SOCKS; info_flags = dcc_clnt_info->flags; dcc_map_changed(); } if (!dcc_clnt_resolve_unlock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); return 0; } dcc_ctxts_unlock(); return 1; } static int passwd_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) { DCC_PASSWD new_passwd; DCC_PATH path; if (arg[0] == '\0') { if (which_map == MAP_INFO) { printf(" using password in %s\n", fnm2path_err(path, dcc_info_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 = srvr_clnt_id = id; set_which_map(MAP_TMP); get_passwd(1); 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 (!CSTRCMP(arg, "off")) { new_debug = 0; arg += STRZ("off"); } else if (!CSTRCMP(arg, "on")) { ++new_debug; arg += STRZ("on"); } else if (!CSTRCMP(arg, "ttl=")) { new_ttl = strtoul(arg+STRZ("ttl="), &p, 0); #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 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)) return 0; if (!dcc_clnt_resolve_lock(dcc_emsg)) { dcc_ctxts_unlock(); dcc_error_msg("%s", dcc_emsg); return 0; } class = DCC_GREY2CLASS(del_grey); for (nmp = class->nms; nmp <= LAST(class->nms); ++nmp) { if (strcasecmp(nmp->hostname, nm.hostname)) continue; if (nmp->port == nm.port) { /* Found it. Delete everything we've learned about * its IP addresses so their values won't be saved * during the re-measuring of RTT's */ for (addr = class->addrs; addr <= LAST(class->addrs); ++addr) { if (addr->nm_inx == nmp - class->nms) memset(addr, 0, sizeof(*addr)); } if (nmp != LAST(class->nms)) memmove(nmp, nmp+1, (LAST(class->nms) - nmp)*sizeof(*nmp)); memset(LAST(class->nms), 0, sizeof(*nmp)); dcc_force_measure_rtt(class, 1); if (!dcc_clnt_resolve_unlock(dcc_emsg)) dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); return 1; } } if (!dcc_clnt_resolve_unlock(dcc_emsg)) dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); dcc_error_msg("entry not found"); 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)) return 0; /* lock the right to resolve hostnames */ if (!dcc_clnt_resolve_lock(dcc_emsg)) { dcc_ctxts_unlock(); dcc_error_msg("%s", dcc_emsg); 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)); dcc_force_measure_rtt(class, 1); if (!dcc_clnt_resolve_unlock(dcc_emsg)) dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); return 1; } if (!dcc_clnt_resolve_unlock(dcc_emsg)) dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); 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) { DCC_PATH path; u_char new_info_flags, load_grey; int flags_set; u_char created; 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" : fnm2path_err(path, 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 (!CSTRCMP(cp, DCC_INFO_USE_IPV4)) { cp += STRZ(DCC_INFO_USE_IPV4); new_info_flags &= ~DCC_INFO_FG_IPV6; } else if (!CSTRCMP(cp, DCC_INFO_USE_IPV6)) { cp += STRZ(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 (!CSTRCMP(cp, DCC_INFO_USE_SOCKS)) { new_info_flags |= DCC_INFO_FG_SOCKS; cp += STRZ(DCC_INFO_USE_SOCKS); cp += strspn(cp, DCC_WHITESPACE); } if (!CSTRCMP(cp, DCC_INFO_USE_SRC)) { cp += STRZ(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 (!CSTRCMP(cp, DCC_INFO_USE_SRCBAD)) { cp += STRZ(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 and lock, install, and unlock the information */ dcc_rel_priv(); dcc_ctxts_lock(); created = 0; if (0 < dcc_map_info(dcc_emsg, info_map_nm, -1)) { /* copy into an existing map */ if (!dcc_info_lock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); return 0; } } else { /* create a new map */ dcc_ctxts_unlock(); if (!dcc_create_map(dcc_emsg, info_map_nm, 0, 0, 0, 0, 0, &new_src, new_info_flags)) { dcc_error_msg("%s", dcc_emsg); return 0; } created = 1; printf(" created %s\n", fnm2path_err(path, dcc_info_nm)); set_which_map(MAP_INFO); if (!init_map(1)) return 0; } if (!dcc_clnt_resolve_lock(dcc_emsg)) { if (created) unlink(dcc_info_nm); dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); 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; dcc_map_changed(); if (!dcc_clnt_resolve_unlock(dcc_emsg)) dcc_error_msg("%s", dcc_emsg); dcc_ctxts_unlock(); if (!lfile) printf("##################\n\n"); set_which_map(MAP_INFO); if (!quiet) return info_cmd("", ce); return 1; } static int info_work(const char *arg) { DCC_CLNT_INFO info; u_char dcc, srcbad, names; if (*arg == '\0') { names = 0; } else if (!strcmp(arg, "-N")) { names = 1; } else { return -1; } /* snapshot the data and then release it while we print it */ if (!dcc_info_lock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); return 0; } memcpy(&info, dcc_clnt_info, sizeof(info)); srcbad = ctxt && (ctxt->flags & DCC_CTXT_SRCBAD); if (!dcc_info_unlock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); return 0; } dcc_rel_priv(); if (which_map == MAP_INFO) { if (info.dcc.nms[0].hostname[0] != '\0' || !grey_on) { dcc_print_info(dcc_info_nm, &info, 0, srcbad, names, 0 <= access(dcc_info_nm, R_OK)); dcc = 1; } else { dcc = 0; } if (info.grey.nms[0].hostname[0] != '\0' || grey_on) { if (dcc) fputs("\n################\n", stdout); dcc_print_info(dcc_info_nm, &info, 1, srcbad, names, 0 <= access(dcc_info_nm, R_OK)); } } else { dcc_print_info(0, &info, grey_on, srcbad, names, 1); } putchar('\n'); 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 */ if (init_conn(2)) dcc_ctxts_unlock(); if (!dcc_clnt_info) return 0; return info_work(arg); } static int rtt_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB) { if (!init_map(1)) return 0; if (!dcc_clnt_resolve_lock(dcc_emsg)) { dcc_error_msg("%s", dcc_emsg); } else { dcc_force_measure_rtt(&dcc_clnt_info->dcc, 0); dcc_force_measure_rtt(&dcc_clnt_info->grey, 0); if (!dcc_clnt_resolve_unlock(dcc_emsg)) dcc_error_msg("%s", dcc_emsg); } dcc_ctxts_unlock(); /* map, copy, and unlock the information * wait to talk to the server, but don't insist */ if (init_conn(1)) dcc_ctxts_unlock(); if (!dcc_clnt_info) return 0; return info_work(arg); } /* 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; union { DCC_HDR hdr; DCC_ERROR error; } resp; char opbuf[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)); 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.hdr, 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(opbuf, sizeof(opbuf), &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(dcc_emsg, type_str, sizeof(type_str), arg, 0, 0, 0); if (!arg) { dcc_error_msg("%s", dcc_emsg); return -1; } if (!init_conn(0)) return 0; if (0 >= dcc_parse_hex_ck(dcc_emsg, &cmn_wf, type_str, dcc_str2type(type_str), arg, 0, delck_sub)) { dcc_error_msg("%s", dcc_emsg); return 0; } return 1; } 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_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) { #define DEFAULT_MAX_CLIENTS 11000 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; int total, subtotal; 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 tm; char *p; const char *ac; u_char need_head; int i; passed_flags = 0; thold = 0; passed_thold = 0; max_clients = DEFAULT_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; 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(dcc_emsg, &addr6, 0, arg, 0, 0); if (bits <= 0) { dcc_error_msg("%s", dcc_emsg); 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 && i < (1<<16) && (*p == ' ' || *p == '\t' || *p == '\0')) { 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_priv_cmd(ce, 0, 1)) return 0; if (!init_conn(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; memset(versions, 0, sizeof(versions)); num_clients = 0; clist = 0; for (;;) { DCC_OPS result; int len, result_len; u_char vers, result_flags; u_int clnt_id, last_used, requests, nops; gettimeofday(&op_start, 0); result = dcc_aop(dcc_emsg, ctxt, grey_on, DCC_NO_SRVR, ids ? DCC_AOP_CLIENTS_ID : DCC_AOP_CLIENTS, (num_clients << 16) + thold, ISZ(aop_resp.resp.val.string ) >> DCC_ADMIN_RESP_CLIENTS_SHIFT, req_flags, 0, 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 (!num_clients) 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 { if (num_clients > DEFAULT_MAX_CLIENTS) { dcc_error_msg("too many clients"); goto stop; } 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) { num_clients += last_used; continue; } if (vers != 0) { if (vers < DIM(versions)) versions[vers] += requests; else versions[0] += requests; } /* add the new entry to the possibly sorted list */ ++num_clients; ctnew = dcc_malloc(sizeof(*ctnew)); memset(ctnew, 0, sizeof(*ctnew)); ctnew->flags = result_flags & DCC_ADMN_RESP_CLIENTS_BL; ctnew->vers = vers; ctnew->clnt_id = clnt_id; ctnew->last_used = last_used; ctnew->requests = requests; total += requests; ctnew->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 & DCC_ADMN_RESP_CLIENTS_BL)); i -= (0 !=(ctnew->flags & DCC_ADMN_RESP_CLIENTS_BL)); } 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. * If we are sorting, we must get everything. */ if (!sort && num_clients >= max_clients) break; } stop: if (!total) total = 1; /* 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) { fputs(" ops nops ", stdout); if (ids) fputs(" ID ", stdout); fputs(" last ", stdout); if (req_flags & DCC_AOP_CLIENTS_VERS) fputs(" v", stdout); } else { fputs(" ops nops last ID ", stdout); 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("%7d %6d ", ct->requests, ct->nops); if (sort && ids) printf("%5d ", ct->clnt_id); strftime(date_buf, sizeof(date_buf), "%m/%d %X", dcc_localtime(ct->last_used, &tm)); printf("%s", date_buf); 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); if (!ids) { char name[MAXHOSTNAMELEN]; char sustr[DCC_SU2STR_SIZE]; printf(" %-16s %s", dcc_su2str2(sustr, sizeof(sustr), &ct->su), nonames ? "" : 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 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_priv_cmd(ce, 0, 1)) return 0; } if (!init_conn(0)) return 0; gettimeofday(&op_start, 0); result = dcc_aop(dcc_emsg, ctxt, grey_on, DCC_NO_SRVR, 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, DCC_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, DCC_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, DCC_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 (!CSTRCMP(arg, "clear")) { arg += STRZ("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, DCC_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(DCC_NO_SRVR, 1); } return 1; } fin_aop(DCC_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, DCC_NO_SRVR, heading); } /* 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 anum; int srvrs_gen; DCC_AOPS aop; /* look for "clear" or "all" */ anum = DCC_NO_SRVR; aop = DCC_AOP_STATS; while (*arg != 0) { arg += strspn(arg, " \t"); if (anum == DCC_NO_SRVR && !CSTRCMP(arg, "clear")) { arg += STRZ("clear"); aop = DCC_AOP_STATS_CLEAR; if (!get_passwd(aop_tbl[aop].privileged)) { dcc_error_msg("\"stats clear\"" " is a privileged operation" PRV_MSG, ids_path); return 0; } } else if (aop == DCC_AOP_STATS && !CSTRCMP(arg, "all")) { arg += STRZ("all"); anum = 0; } if (*arg != '\0' && *arg != ' ' && *arg != '\t') return -1; } if (!init_conn(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 (anum != DCC_NO_SRVR && class->addrs[anum].srvr_id == DCC_ID_INVALID ) continue; do_aop(aop, sizeof(aop_resp.resp.val.string), anum, 1); fflush(stderr); fflush(stdout); } while (anum != DCC_NO_SRVR && ++anum < class->num_addrs); return 1; } /* restore tracing to default */ static int trace_def(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB) { if (!init_conn(0)) return 0; return (do_aop(DCC_AOP_TRACE_ON, DCC_TRACE_ON_DEF_BITS, DCC_NO_SRVR, 1) && do_aop(DCC_AOP_TRACE_OFF, DCC_TRACE_OFF_DEF_BITS, DCC_NO_SRVR, 1)); }