view cdcc/cdcc.c @ 4:d329bb5c36d0

Changes making it compile the new upstream release
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 14:57:12 +0100
parents c7f6b056b673
children
line wrap: on
line source

/* 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));

}