diff cdcc/cdcc.c @ 0:c7f6b056b673

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