/* Distributed Checksum Clearinghouse
 *
 * control dcc server
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * 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.2.74-1.134 $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_CLNT_CTXT *ctxt;

static DCC_PATH info_map_nm = DCC_MAP_NM_DEF;
static const char *homedir;
static DCC_PASSWD passwd;
static u_char passwd_set;
static DCC_SRVR_NM srvr = DCC_SRVR_NM_DEF;
static u_char port_set;
static ID_TBL *srvr_clnt_tbl;
static DCC_CLNT_ID srvr_clnt_id;
static enum WHICH_MAP {MAP_TMP, MAP_INFO} which_map = MAP_INFO;

#if defined(NO_IPV6)
static u_char clnt_flags = 0;
#else
static u_char clnt_flags = DCC_CLNT_INFO_FG_IPV6;
#endif

static u_char grey_set;

static u_char quiet;


static u_char do_cmds(char *);
static u_char init_map(u_char);

struct cmd_tbl_entry;
/* -1=display help message, 0=command failed, 1=success */
typedef int CMD (const char *, const struct cmd_tbl_entry *);
typedef struct cmd_tbl_entry {
    const char	*cmd;
    CMD		(*fnc);
    u_char	args;			/* 0=optional, 1=required, 2=none */
    u_char	privileged;		/* 1=must have server's password */
    u_char	uid_0;			/* 1=require set-UID privileges */
    const char	*help_str;
} CMD_TBL_ENTRY;

static CMD help_cmd;
static CMD exit_cmd;
static CMD grey_cmd;
static CMD file_cmd;
static CMD new_map_cmd;
static CMD delete_cmd;
static CMD add_cmd;
static CMD load_cmd;
static CMD host_cmd;
static CMD port_cmd;
static CMD passwd_cmd;
static CMD id_cmd;
static CMD homedir_cmd;
static CMD debug_cmd;
static CMD ipv6_cmd;
static CMD socks_cmd;
static CMD info_cmd;
static CMD rtt_cmd;
static CMD delck_cmd;
static CMD sleep_cmd;
static CMD clients_cmd;
static CMD anon_cmd;
static CMD flod_rewind;
static CMD ffwd_in;
static CMD ffwd_out;
static CMD flod_stats;
static CMD stats_cmd;
static CMD trace_def;

static const CMD_TBL_ENTRY cmd_tbl[] = {
    {"help",	    help_cmd,     0, 0, 0, "help [cmd]"},
    {"?",	    help_cmd,     0, 0, 0, 0},
    {"exit",	    exit_cmd,     2, 0, 0, "exit"},
    {"quit",	    exit_cmd,     2, 0, 0, 0},
    {"grey",	    grey_cmd,     0, 0, 0, "grey [on|off]"},
    {"homedir",	    homedir_cmd,  0, 0, 0, "homedir [path]"},
    {"file",	    file_cmd,     0, 0, 0, "file [map]"},
    {"new map",	    new_map_cmd,  0, 0, 0, "new map [map]"},
    {"delete",	    delete_cmd,   1, 0, 1, "delete host[,port]"},
    {"add",	    add_cmd,      1, 0, 1,
	    "add host,[port|-] [RTT+/-#] [ID [passwd]]"},
    {"load",	    load_cmd,     1, 0, 1, "load {info-file | -}"},
    {"host",	    host_cmd,     0, 0, 0, "host [hostname]"},
    {"server",	    host_cmd,	  0, 0, 0, 0},
    {"port",	    port_cmd,     0, 0, 0, "port #"},
    {"passwd",	    passwd_cmd,   0, 0, 0, "passwd secret"},
    {"password",    passwd_cmd,   0, 0, 0, 0},
    {"id",	    id_cmd,	  0, 0, 0, "id [ID]"},
    {"debug",	    debug_cmd,    0, 0, 0, "debug [on|off|TTL=x]"},
    {"IPv6",	    ipv6_cmd,	  0, 0, 0, "IPv6 [on|off]"},
    {"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 [-ns] [max [thold]]"},
    {"anon delay",  anon_cmd,	  0, 0, 0, "anon delay [delay[,inflate]]"},
    {"flood rewind",flod_rewind,  1, 1, 0, "flood rewind ID"},
    {"flood FFWD in",ffwd_in,	  1, 1, 0, "flood FFWD in ID"},
    {"flood FFWD out",ffwd_out,	  1, 1, 0, "flood FFWD out ID"},
    {"flood stats", flod_stats,	  1, 1, 0, "flood stats [clear] {ID|all}"},
    {"stats",	    stats_cmd,	  0, 0, 0, "stats [clear|all]"},
    {"status",	    stats_cmd,	  0, 0, 0, 0},
    {"trace default",trace_def,	  2, 1, 0, "trace default"},
};


#define PRV_MSG ";\n"		\
"   use the \"id server-ID\" command\n"	\
"   and either \"passwd secret\" or `su` to read passwords from %s"


static DCC_ADMN_RESP_VAL op_resp;
static struct timeval op_start, op_end;
static DCC_SOCKU op_resp_su;

static struct {
    const char	*op;
    const char	*help_str;
    DCC_AOPS	aop;
    u_char	privileged;
    u_int32_t	val;
} aop_tbl[] = {
    {"stop",		0,  DCC_AOP_STOP,	1, 0},
    {"new IDs",		"", DCC_AOP_NEW_IDS,	1, 0},
    {"reload IDs",	0,  DCC_AOP_NEW_IDS,	1, 0},
    {"flood check",	0,  DCC_AOP_FLOD,	1, DCC_AOP_FLOD_CHECK},
    {"flood shutdown",	0,  DCC_AOP_FLOD,	1, DCC_AOP_FLOD_SHUTDOWN},
    {"flood halt",	0,  DCC_AOP_FLOD,	1, DCC_AOP_FLOD_HALT},
    {"flood resume",	0,  DCC_AOP_FLOD,	1, DCC_AOP_FLOD_RESUME},
    {"flood list",	0,  DCC_AOP_FLOD,	0, DCC_AOP_FLOD_LIST},
    {"DB unlock",	0,  DCC_AOP_DB_UNLOCK,	1, 0},
    {"DB new",		0,  DCC_AOP_DB_NEW,	1, 0},

#define TMAC(s,b) \
    {"trace "#s" on",	"trace "#s" {on|off}",			\
			    DCC_AOP_TRACE_ON, 1, DCC_TRACE_##b},\
    {"trace "#s" off",	"", DCC_AOP_TRACE_OFF,	1, DCC_TRACE_##b}
    TMAC(admn,ADMN_BIT),
    TMAC(anon,ANON_BIT),
    TMAC(clnt,CLNT_BIT),
    TMAC(rlim,RLIM_BIT),
    TMAC(query,QUERY_BIT),
    TMAC(ridc,RIDC_BIT),
    TMAC(flood,FLOD_BIT),
    TMAC(flood2,FLOD2_BIT),
    TMAC(ids,IDS_BIT),
    TMAC(bl,BL_BIT),
    {"trace all on",	"trace all {on|off}",
			    DCC_AOP_TRACE_ON,	1, DCC_TRACE_ON_BITS},
    {"trace all off",	"", DCC_AOP_TRACE_OFF,	1, DCC_TRACE_OFF_BITS},
#undef TMAC
};


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);
	srvr_clnt_id = srvr.clnt_id;

	dcc_init_priv();
	dcc_syslog_init(0, argv[0], 0);

	while ((i = getopt(argc, argv, "Vdqh:c:")) != EOF) {
		switch (i) {
		case 'V':
			fprintf(stderr, DCC_VERSION"\n");
			break;

		case 'd':
			++dcc_clnt_debug;
			break;

		case 'q':
			++quiet;
			break;

		case 'h':
			homedir = optarg;
			break;

		case 'c':
			ids_nm = optarg;
			break;

		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;

	if (!dcc_cdhome(dcc_emsg, homedir))
		dcc_error_msg("%s", dcc_emsg);

	dcc_clnt_unthread_init();
	dcc_wf_init(&cmn_wf, 0, 0);

	dcc_all_srvrs = 1;
	if (!init_map(!quiet))
		which_map = MAP_TMP;
	else
		dcc_ctxts_unlock();

	/* with a list of commands, act as a batch utility */
	if (argc != 0) {
		for (;;) {
			/* a final arg of "-" says switch to interactive mode */
			if (argc == 1 && !strcmp(*argv, "-"))
				break;

			if (!do_cmds(*argv)) {
				fputs(" ?\n", stderr);
				exit(EX_UNAVAILABLE);
			}
			if (!dcc_info_unlock(dcc_emsg))
				dcc_error_msg("%s", dcc_emsg);

			++argv;
			if (!--argc) {
				exit(EX_OK);
			}
		}
	}

	/* Without an arg list of commands, look for commands from STDIN.
	 * Commands end with a semicolon or newline. */
	for (;;) {
		if (!dcc_info_unlock(dcc_emsg))
			dcc_error_msg("%s", dcc_emsg);
		printf("cdcc %s> ",
		       which_map == MAP_INFO ? info_map_nm : "-");
		fflush(stderr);
		fflush(stdout);
		if (!fgets(cmd_buf, sizeof(cmd_buf), stdin)) {
			fputc('\n', stdout);
			exit(EX_OK);
		}
		if (!do_cmds(cmd_buf))
			fputs(" ?\n", stderr);
	}
}



static u_char				/* 0=failed, 1=ok */
get_passwd(u_char privileged)
{
	if (passwd_set) {
		strncpy(srvr.passwd, passwd, sizeof(srvr.passwd));
		return (!privileged || srvr.clnt_id != DCC_ID_ANON);
	}
	memset(srvr.passwd, 0, sizeof(srvr.passwd));

	srvr_clnt_tbl = 0;
	srvr_clnt_id = srvr.clnt_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_nm, R_OK)
	    && errno == EACCES)
		return !privileged;
	if (load_ids(dcc_emsg, &srvr_clnt_tbl, srvr_clnt_id) < 0) {
		if (srvr_clnt_id != DCC_ID_ANON) {
			if (privileged)
				dcc_error_msg("%s", dcc_emsg);
			srvr.clnt_id = DCC_ID_ANON;
		}
		return !privileged;
	}
	if (srvr_clnt_tbl)
		strncpy(srvr.passwd, srvr_clnt_tbl->cur_passwd,
			sizeof(srvr.passwd));
	return 1;
}



static void
clear_conn(void)
{
	if (ctxt) {
		dcc_rel_ctxt(0, ctxt);
		ctxt = 0;
	}
}



static void
set_which_map(enum WHICH_MAP new)
{
	/* release things even if nothing seems to be changing
	 * to ensure that we bind a new socket */
	if (!dcc_unmap_info(dcc_emsg))
		dcc_error_msg("%s", dcc_emsg);
	clear_conn();
	which_map = new;
	if (new == MAP_INFO)
		passwd_set = 0;
}



/* start talking to the local map file
 *	on success the contexts and mapped file are locked */
static u_char				/* 0=failed 1=mapped and locked */
init_map(u_char complain)
{
	u_char result;

	dcc_ctxts_lock();
	if (which_map == MAP_TMP) {
		result = dcc_map_lock_tmp_info(dcc_emsg, &srvr, clnt_flags);
	} else {
		result = dcc_map_lock_info(dcc_emsg, info_map_nm, -1);
	}
	if (result) {
		clnt_flags = dcc_clnt_info->flags;
		return 1;
	}
	dcc_ctxts_unlock();
	if (complain)
		dcc_error_msg("%s", dcc_emsg);
	return 0;
}



/* start talking to a DCC server
 *	If we already had a private map file, forget it.
 *	on success the contexts are locked but the mapped file is not locked */
static u_char				/* 0=failed, 1=ok */
init_conn(u_char no_srvr_ok)
{
	if (ctxt) {
		if (!dcc_clnt_rdy(dcc_emsg, ctxt,
				  (grey_on ? DCC_CLNT_FG_GREY : 0)
				  | (no_srvr_ok ? DCC_CLNT_FG_NO_SRVR_OK : 0)
				  | DCC_CLNT_FG_NO_FAIL)) {
			dcc_error_msg("%s", dcc_emsg);
			return 0;
		}
		/* check the other (greylist or not) server */
		if (!dcc_clnt_rdy(dcc_emsg, ctxt,
				  (!grey_on ? DCC_CLNT_FG_GREY : 0)
				  | DCC_CLNT_FG_NO_SRVR_OK
				  | DCC_CLNT_FG_NO_FAIL)
		    && dcc_clnt_debug)
			dcc_error_msg("%s", dcc_emsg);
		clnt_flags = dcc_clnt_info->flags;
		return 1;
	}

	if (which_map == MAP_TMP) {
		/* create a brand new temporary map */
		ctxt = dcc_tmp_clnt_init(dcc_emsg, ctxt, &srvr,
					 grey_on, clnt_flags);
		if (!ctxt) {
			dcc_error_msg("%s", dcc_emsg);
			return 0;
		}
		clnt_flags = dcc_clnt_info->flags;
		return 1;
	}

	if (!init_map(1))		/* lock things */
		return 0;
	if (!grey_set) {
		grey_on = (dcc_clnt_info->dcc.nms[0].hostname[0] == '\0'
			   && dcc_clnt_info->grey.nms[0].hostname[0] != '\0');
	}

	ctxt = dcc_clnt_init(dcc_emsg, ctxt, info_map_nm,
			     (grey_on ? DCC_CLNT_FG_GREY : 0)
			     | (no_srvr_ok ? DCC_CLNT_FG_NO_SRVR_OK : 0)
			     | DCC_CLNT_FG_NO_FAIL);
	if (!ctxt) {
		dcc_error_msg("%s", dcc_emsg);
		return 0;
	}
	/* check the other (greylist or not) server */
	if (!dcc_clnt_rdy(dcc_emsg, ctxt,
			  (!grey_on ? DCC_CLNT_FG_GREY : 0)
			  | DCC_CLNT_FG_NO_SRVR_OK
			  | DCC_CLNT_FG_NO_FAIL)
	    && dcc_clnt_debug)
		dcc_error_msg("%s", dcc_emsg);

	clnt_flags = dcc_clnt_info->flags;

	return 1;
}



static void
dcc_map_changed(void)
{
	if (which_map == MAP_INFO)
		dcc_mmap_utime(dcc_info_nm, 1);
	dcc_force_measure_rtt(&dcc_clnt_info->dcc, 1);
	dcc_force_measure_rtt(&dcc_clnt_info->grey, 1);
}



/* 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(int anum)			/* -1 or server index */
{
	const DCC_SRVR_CLASS *class;
	const char *resp_addr;
	char date_buf[40];
	const char *srvr_nm;
	struct tm tm;

	resp_addr = dcc_su2str_opt(&op_resp_su, 0, '\0');
	class = DCC_GREY2CLASS(grey_on);
	/* Display the preferred server if anum is -1 */
	if (anum < 0)
		anum = class->act_inx;
	if (anum < class->num_addrs) {
		srvr_nm = class->nms[class->addrs[anum].nm_inx].hostname;
		if (srvr_nm && strcmp(srvr_nm, resp_addr)) {
			fputs(srvr_nm, stdout);
			putchar(' ');
		}
		printf("%s\n        server-ID %d",
		       dcc_su2str(&op_resp_su),
		       class->addrs[anum].srvr_id);
	} else {
		printf("%s\n                    ",
		       dcc_su2str(&op_resp_su));
	}
	if (srvr.clnt_id != DCC_ID_ANON)
		printf("  client-ID %d", srvr.clnt_id);
	if (which_map == MAP_INFO)
		printf("  %s", dcc_info_nm);
	strftime(date_buf, sizeof(date_buf), "  %X",
		 dcc_localtime(op_start.tv_sec, &tm));
	fputs(date_buf, stdout);
	putchar('\n');
}



static u_char				/* 0=some kind of problem, 1=done */
start_aop(DCC_AOPS aop, u_int32_t val1, int anum)
{
	DCC_OPS result;
	int result_len;

	if (!init_conn(0))
		return 0;

	memset(&op_resp, 0, sizeof(op_resp));
	gettimeofday(&op_start, 0);
	result_len = sizeof(op_resp);
	result = dcc_aop(dcc_emsg, ctxt, grey_on, anum, aop, val1, 0, 0, 0,
			 &op_resp, &result_len, &op_resp_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(int anum,			/* -1 or index of server */
	u_char psrvr)			/* 1=print server name */
{
	if (psrvr)
		print_aop(anum);

	/* say what the server had to say */
	fputs(op_resp.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 val, int anum, u_char psrvr)
{
	if (!start_aop(aop, val, anum))
		return 0;
	fin_aop(anum, psrvr);
	return 1;
}



static u_char				/* 0=bad command,  1=did it */
do_op(const char *op)
{
	int op_num;

	op_num = 0;
	for (;;) {
		if (op_num >= DIM(aop_tbl)) {
			dcc_error_msg("unrecognized command \"%s\"", op);
			return 0;
		}
		/* do a command */
		if (!cmd_cmp(op, aop_tbl[op_num].op))
			break;
		op_num++;
	}

	/* send an administrative request to the server */
	if (!get_passwd(aop_tbl[op_num].privileged)) {
		dcc_error_msg("\"%s\" is a privileged operation"PRV_MSG,
			      aop_tbl[op_num].op, DCC_NM2PATH(IDS_NM_DEF));
		return 0;
	}

	/* try to send it */
	return do_aop(aop_tbl[op_num].aop, aop_tbl[op_num].val, -1, 1);
}



static u_char
ck_priv_cmd(const CMD_TBL_ENTRY *ce, u_char uid_0, u_char privileged)
{
	/* always call get_passwd() so we have always fetched a password */
	if (!get_passwd(privileged)
	    || (which_map != MAP_TMP
		&& uid_0
		&& 0 > access(info_map_nm, R_OK)
		&& errno != ENOENT)) {
		dcc_error_msg("\"%s\" is a privileged command"PRV_MSG,
			      ce->cmd, DCC_NM2PATH(IDS_NM_DEF));
		return 0;
	}
	return 1;
}



static u_char				/* 1=ok 0=bad command */
cmd(const char *p)
{
	const char *arg;
	int cmd_num, j;
	const CMD_TBL_ENTRY *ce;

	/* look for the string as a command and execute it if we find */
	for (cmd_num = 0; cmd_num < DIM(cmd_tbl); ++cmd_num) {
		ce = &cmd_tbl[cmd_num];
		arg = cmd_cmp(p, ce->cmd);
		/* if the command table entry and the command completely
		 * matched, then infer a null argument */
		if (!arg) {
			if (!ck_priv_cmd(ce, ce->uid_0, ce->privileged))
				return 0;
			if (ce->args != 1) {
				j = ce->fnc("", ce);
				if (j >= 0)
					return j;
			}
			help_cmd(p, 0);
			return 0;
		}
		/* if the command table entry is an initial sustring of
		 * the user's command, then the rest of the command must
		 * start with white space.  Trim and use it as the argument */
		j = strspn(arg, DCC_WHITESPACE);
		if (j) {
			if (ce->args == 2) {
				help_cmd(p, 0);	/* arg not allowed */
				return 0;
			}
			if (!ck_priv_cmd(ce, ce->uid_0, ce->privileged))
				return 0;
			j = ce->fnc(arg+j, ce);
			if (j >= 0)
				return j;
			help_cmd(p, 0);
			return 0;
		}
	}

	/* otherwise try to interpret it as a DCC administrative packet */
	return do_op(p);
}



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;

	col = strlen(str)+1;
	col += HELP_COL - (col % HELP_COL);
	pos += col;
	if (pos > 78) {
		putchar('\n');
		pos = col;
	}
	printf("%-*s", col, str);

	return pos;
#undef HELP_COL
}



static int
help_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	int i, pos;
	const char *p;

	/* say something about one command */
	if (arg) {
		for (i = 0; i < DIM(cmd_tbl); ++i) {
			p = cmd_cmp(arg, cmd_tbl[i].cmd);
			if (!p || *p == ' ' || *p == '\t') {
				if (!cmd_tbl[i].help_str)
					break;
				printf("usage: %s\n", cmd_tbl[i].help_str);
				return 1;
			}
		}
		for (i = 0; i < DIM(aop_tbl); ++i) {
			p = cmd_cmp(arg, aop_tbl[i].op);
			if (!p || *p == ' ' || *p == '\t') {
				p = aop_tbl[i].help_str;
				if (!p || !*p)
					p = aop_tbl[i].op;
				printf("usage: %s\n", p);
				return 1;
			}
		}
	}

	/* talk about all of the commands */
	printf("   version "DCC_VERSION"\n");
	pos = 0;
	for (i = 0; i < DIM(cmd_tbl); ++i) {
		p = cmd_tbl[i].help_str;
		if (!p)
			continue;
		pos = help_cmd_print(pos, p);
	}
	for (i = 0; i < DIM(aop_tbl); ++i) {
		p = aop_tbl[i].help_str;
		if (!p) {
			p = aop_tbl[i].op;
		} else if (!*p) {
			continue;
		}
		pos = help_cmd_print(pos, p);
	}
	putchar('\n');

	return 1;
}



static int
exit_cmd(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB)
{
	exit(EX_OK);
	return -1;
}


static int
grey_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	if (arg[0] == '\0') {
		printf("    Greylist mode %s%s\n",
		       grey_on ? "on" : "off",
		       grey_set ? "" : " by default");
		return 1;
	}
	if (!strcmp(arg, "off")) {
		grey_on = 0;
		grey_set = 1;
		clear_conn();
	} else if (!strcmp(arg, "on")) {
		grey_on = 1;
		grey_set = 1;
		clear_conn();
	} else {
		return -1;
	}
	if (!port_set)
		srvr.port = DCC_GREY2PORT(grey_on);
	return 1;
}



static int
homedir_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	if (arg[0] != '\0') {
		if (!dcc_cdhome(dcc_emsg, arg)) {
			dcc_error_msg("%s", dcc_emsg);
			return 0;
		}
		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",
			       dcc_info_nm);
		else
			printf("    map file %s but using temporary file\n",
			       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)
{
	if (arg[0] == '\0')
		arg = DCC_MAP_NM_DEF;

	dcc_rel_priv();
	if (!dcc_create_map(dcc_emsg, arg, 0, 0, 0, 0, 0, clnt_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", dcc_info_nm);
		return info_cmd("", ce);
	}
	return 1;
}



/* server hostname */
static int
host_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
	DCC_SRVR_NM nm;

	if (arg[0] == '\0') {
		if (which_map == MAP_INFO)
			return info_cmd("", ce);
		printf("    %s server hostname \"%s\"\n",
		       grey_on ? "Greylist" : "DCC", srvr.hostname);
		return 1;
	}
	if (!strcmp(arg, "-")) {
		set_which_map(MAP_INFO);
		if (!init_map(1)) {
			set_which_map(MAP_TMP);
			return 0;
		}
		dcc_ctxts_unlock();
		return 1;
	}

	arg = dcc_parse_nm_port(0, arg, 0,
				nm.hostname, sizeof(nm.hostname),
				&nm.port, 0, 0,
				0, 0);
	if (!arg)
		return 0;
	arg += strspn(arg, DCC_WHITESPACE);
	if (*arg != '\0')
		return 0;

	set_which_map(MAP_TMP);
	memcpy(srvr.hostname, nm.hostname, sizeof(srvr.hostname));
	if (nm.port != 0) {
		srvr.port = nm.port;
		port_set = 1;
	}
	return 1;
}



/* server port # */
static int
port_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
	u_int port;

	if (arg[0] == '\0') {
		if (which_map == MAP_INFO)
			return info_cmd("", ce);
		printf("    port=%d\n", ntohs(srvr.port));
		return 1;
	}

	port = dcc_get_port(0, arg, DCC_GREY2PORT(grey_on), 0, 0);
	if (port == DCC_GET_PORT_INVALID)
		return 0;

	srvr.port = port;
	port_set = 1;
	set_which_map(MAP_TMP);
	return 1;
}



static int
ipv6_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
	u_char new_use_ipv6;

	if (!init_map(1))
		return 0;

	if (arg[0] == '\0') {
		printf("    IPv6 %s\n",
		       (clnt_flags & DCC_CLNT_INFO_FG_IPV6) ? "on" : "off");
		return 1;
	}

	if (!ck_priv_cmd(ce, 1, 0))
		return 0;

	if (!strcmp(arg, "off")) {
		new_use_ipv6 = 0;
	} else if (!strcmp(arg, "on")) {
		new_use_ipv6 = DCC_CLNT_INFO_FG_IPV6;
	} else {
		return -1;
	}

	dcc_ctxts_lock();
	if (!dcc_resolve_lock(dcc_emsg)) {
		dcc_error_msg("%s", dcc_emsg);
		dcc_ctxts_unlock();
		return 0;
	}
	if (dcc_clnt_info
	    && (dcc_clnt_info->flags & DCC_CLNT_INFO_FG_IPV6) != new_use_ipv6) {
		dcc_clnt_info->flags ^= DCC_CLNT_INFO_FG_IPV6;
		clnt_flags = dcc_clnt_info->flags;
		dcc_map_changed();
	}

	if (!dcc_resolve_unlock(dcc_emsg)) {
		dcc_error_msg("%s", dcc_emsg);
		dcc_ctxts_unlock();
		return 0;
	}
	dcc_ctxts_unlock();

	if (init_conn(1)
	    && (dcc_clnt_info->flags & DCC_CLNT_INFO_FG_IPV6) != new_use_ipv6) {
#ifdef NO_IPV6
		dcc_error_msg("IPv6 switch not changed;"
			      " No IPv6 support in this system?");
#else
		dcc_error_msg("IPv6 switch not changed.");
#endif
	}

	return 1;
}



static int
socks_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
	u_char new_use_socks;

	if (!init_map(1))
		return 0;

	if (arg[0] == '\0') {
		printf("    SOCKS %s\n",
		       (clnt_flags & DCC_CLNT_INFO_FG_SOCKS) ? "on" : "off");
		return 1;
	}

	if (!ck_priv_cmd(ce, 1, 0))
		return 0;

	if (!strcmp(arg, "off")) {
		new_use_socks = 0;
	} else if (!strcmp(arg, "on")) {
		new_use_socks = DCC_CLNT_INFO_FG_SOCKS;
	} else {
		return -1;
	}

	dcc_ctxts_lock();
	if (!dcc_resolve_lock(dcc_emsg)) {
		dcc_error_msg("%s", dcc_emsg);
		dcc_ctxts_unlock();
		return 0;
	}
	if (dcc_clnt_info
	    && (dcc_clnt_info->flags&DCC_CLNT_INFO_FG_SOCKS) != new_use_socks) {
		dcc_clnt_info->flags ^= DCC_CLNT_INFO_FG_SOCKS;
		clnt_flags = dcc_clnt_info->flags;
		dcc_map_changed();
	}

	if (!dcc_resolve_unlock(dcc_emsg)) {
		dcc_error_msg("%s", dcc_emsg);
		dcc_ctxts_unlock();
		return 0;
	}
	dcc_ctxts_unlock();
	return 1;
}



static int
passwd_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
	DCC_PASSWD new_passwd;

	if (arg[0] == '\0') {
		if (which_map == MAP_INFO)
			return info_cmd("", ce);
		if (passwd_set)
			printf("    password %s\n", passwd);
		else
			printf("    password not set\n");
		return 1;
	}

	arg = dcc_parse_word(0, new_passwd, sizeof(passwd),
			     arg, "password", 0, 0);
	if (!arg || *arg != '\0')
		return -1;
	strncpy(passwd, new_passwd, sizeof(passwd));
	passwd_set = 1;
	set_which_map(MAP_TMP);
	return 1;
}



static int
id_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	DCC_CLNT_ID id;

	if (arg[0] == '\0') {
		printf("    ID=%d\n", srvr_clnt_id);
		return 1;
	}

	id = dcc_get_id(0, arg, 0, 0);
	if (id == DCC_ID_INVALID)
		return -1;

	srvr.clnt_id = srvr_clnt_id = id;
	set_which_map(MAP_TMP);
	get_passwd(1);
	return 1;
}



static int
debug_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	char debug_str[24];
	char ttl_str[24];
	int new_ttl, new_debug;
	char *p;

	if (arg[0] == '\0') {
		if (!dcc_clnt_debug)
			snprintf(debug_str, sizeof(debug_str),
				 "debug off");
		else if (dcc_clnt_debug == 1)
			snprintf(debug_str, sizeof(debug_str),
				 "debug on");
		else
			snprintf(debug_str, sizeof(debug_str),
				 "debug on+%d\n", dcc_clnt_debug-1);
		if (dcc_debug_ttl != 0)
			snprintf(ttl_str, sizeof(ttl_str),
				 "    TTL=%d", dcc_debug_ttl);
		else
			ttl_str[0] = '\0';
		printf("    %s%s\n", debug_str, ttl_str);
		return 1;
	}

	new_ttl = dcc_debug_ttl;
	new_debug = dcc_clnt_debug;
	for (;;) {
		if (!CSTRCMP(arg, "off")) {
			new_debug = 0;
			arg += STRZ("off");
		} else if (!CSTRCMP(arg, "on")) {
			++new_debug;
			arg += STRZ("on");
		} else if (!CSTRCMP(arg, "ttl=")) {
			new_ttl = strtoul(arg+STRZ("ttl="), &p, 0);
#if defined(IPPROTO_IP) && defined(IP_TTL)
			if (new_ttl < 256)
				arg = p;
#else
			printf("    TTL setting not supported\n");
#endif
		}

		if (*arg == ' ' || *arg == '\t') {
			arg += strspn(arg, DCC_WHITESPACE);
		} else if (*arg == '\0') {
			break;
		} else {
			return -1;
		}
	}
	dcc_debug_ttl = new_ttl;
	if (dcc_debug_ttl != 0)
		set_which_map(MAP_TMP);
	dcc_clnt_debug = new_debug;
	if (dcc_clnt_debug > 1)
		printf("    debug on+%d\n", dcc_clnt_debug-1);
	return 1;
}



static int
delete_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	DCC_SRVR_CLASS *class;
	DCC_SRVR_NM nm, *nmp;
	DCC_SRVR_ADDR *addr;
	u_char del_grey;

	del_grey = grey_on;
	if (!dcc_parse_srvr_nm(dcc_emsg, &nm, &del_grey, arg, 0, 0)) {
		dcc_error_msg("%s", dcc_emsg);
		return 0;
	}

	/* map and lock */
	set_which_map(MAP_INFO);
	if (!init_map(1))
		return 0;
	if (!dcc_resolve_lock(dcc_emsg)) {
		dcc_ctxts_unlock();
		dcc_error_msg("%s", dcc_emsg);
		return 0;
	}

	class = DCC_GREY2CLASS(del_grey);
	for (nmp = class->nms; nmp <= LAST(class->nms); ++nmp) {
		if (strcasecmp(nmp->hostname, nm.hostname))
			continue;
		if (nmp->port == nm.port) {
			/* Found it.  Delete everything we've learned about
			 * its IP addresses so their values won't be saved
			 * during the re-measuring of RTT's */
			for (addr = class->addrs;
			     addr <= LAST(class->addrs);
			     ++addr) {
				if (addr->nm_inx == nmp - class->nms)
					memset(addr, 0, sizeof(*addr));
			}
			if (nmp != LAST(class->nms))
				memmove(nmp, nmp+1,
					(LAST(class->nms) - nmp)*sizeof(*nmp));
			memset(LAST(class->nms), 0, sizeof(*nmp));
			dcc_force_measure_rtt(class, 1);
			if (!dcc_resolve_unlock(dcc_emsg))
				dcc_error_msg("%s", dcc_emsg);
			dcc_ctxts_unlock();
			return 1;
		}
	}

	if (!dcc_resolve_unlock(dcc_emsg))
		dcc_error_msg("%s", dcc_emsg);
	dcc_ctxts_unlock();
	dcc_error_msg("entry not found");
	return 0;
}



static int
add_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	DCC_SRVR_CLASS *class;
	DCC_SRVR_NM nm, *nmp, *tgt_nmp;
	u_char add_grey;

	add_grey = grey_set && grey_on;

	if (0 >= dcc_parse_srvr_nm(dcc_emsg, &nm, &add_grey, arg, 0, 0)) {
		dcc_error_msg("%s", dcc_emsg);
		return 0;
	}
	if (nm.clnt_id == DCC_ID_ANON && add_grey) {
		dcc_error_msg("anonymous client-ID invalid"
			      " for Greylist server %s",
			      nm.hostname);
		return 0;
	}

	/* map and lock the information */
	set_which_map(MAP_INFO);
	if (!init_map(1))
		return 0;
	/* lock the right to resolve hostnames */
	if (!dcc_resolve_lock(dcc_emsg)) {
		dcc_ctxts_unlock();
		dcc_error_msg("%s", dcc_emsg);
		return 0;
	}

	/* look for the old entry or a new, free entry */
	class = DCC_GREY2CLASS(add_grey);
	tgt_nmp = 0;
	for (nmp = class->nms; nmp <= LAST(class->nms); ++nmp) {
		if (nmp->hostname[0] == '\0') {
			if (!tgt_nmp)
				tgt_nmp = nmp;
			continue;
		}
		if (!strcmp(nmp->hostname, nm.hostname)
		    && nmp->port == nm.port) {
			printf("    overwriting existing entry\n");
			tgt_nmp = nmp;
			break;
		}
	}

	if (tgt_nmp) {
		memcpy(tgt_nmp, &nm, sizeof(*tgt_nmp));
		dcc_force_measure_rtt(class, 1);
		if (!dcc_resolve_unlock(dcc_emsg))
			dcc_error_msg("%s", dcc_emsg);
		dcc_ctxts_unlock();
		return 1;
	}

	if (!dcc_resolve_unlock(dcc_emsg))
		dcc_error_msg("%s", dcc_emsg);
	dcc_ctxts_unlock();
	if (add_grey)
		dcc_error_msg("too many Greylist server names");
	else
		dcc_error_msg("too many DCC server names");
	return 0;
}



static void
add_new_nms(const DCC_SRVR_NM new_nms[DCC_MAX_SRVR_NMS],
	    DCC_SRVR_NM old_nms[DCC_MAX_SRVR_NMS])
{
	const DCC_SRVR_NM *new_nmp;
	DCC_SRVR_NM *old_nmp;

	for (new_nmp = new_nms;
	     new_nmp < &new_nms[DCC_MAX_SRVR_NMS]
	     && new_nmp->hostname[0] != '\0';
	     ++new_nmp) {
		for (old_nmp = old_nms;
		     old_nmp <= &old_nms[DCC_MAX_SRVR_NMS];
		     ++old_nmp) {
			if (old_nmp->hostname[0] == '\0'
			    || (!strcmp(old_nmp->hostname, new_nmp->hostname)
				&& old_nmp->port == new_nmp->port)) {
				memcpy(old_nmp, new_nmp, sizeof(*old_nmp));
				break;
			}
		}
	}
}



static int
load_cmd(const char *lfile, const CMD_TBL_ENTRY *ce UATTRIB)
{
	u_char new_clnt_flags, load_grey;
	int flags_set;
	u_char created;
	DCC_SRVR_NM new_nm;
	DCC_SRVR_NM dcc_nms[DCC_MAX_SRVR_NMS];
	int num_dcc_nms;
	DCC_SRVR_NM grey_nms[DCC_MAX_SRVR_NMS];
	int num_grey_nms;
	char buf[sizeof(DCC_SRVR_NM)*3];
	const char *bufp, *cp;
	FILE *f;
	int fd, lineno;

	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_clnt_flags = clnt_flags;
	num_dcc_nms = 0;
	memset(dcc_nms, 0, sizeof(dcc_nms));
	num_grey_nms = 0;
	memset(grey_nms, 0, sizeof(grey_nms));
	lineno = 0;
	for (;;) {
		bufp = fgets(buf, sizeof(buf), f);
		if (!bufp) {
			if (ferror(f)) {
				dcc_error_msg("fgets(%s): %s",
					      !lfile
					      ? "STDIN" : DCC_NM2PATH(lfile),
					      ERROR_STR());
				fclose(f);
				return 0;
			}
			break;
		}

		++lineno;

		/* 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 (!strncmp(DCC_INFO_USE_IPV4, cp,
				     sizeof(DCC_INFO_USE_IPV4)-1)) {
				cp += sizeof(DCC_INFO_USE_IPV4)-1;
				new_clnt_flags = 0;
			} else if (!strncmp(DCC_INFO_USE_IPV6, cp,
					    sizeof(DCC_INFO_USE_IPV6)-1)) {
				cp += sizeof(DCC_INFO_USE_IPV6)-1;
				new_clnt_flags = DCC_CLNT_INFO_FG_IPV6;
			} else {
				++flags_set;
			}
			if (flags_set == 1) {
				/* We found "IPv6 on" or "off".
				 * Loof for "use SOCKS" */
				cp += strspn(cp, DCC_WHITESPACE);
				if (*cp == '\0')
					continue;
				if (!strcasecmp(DCC_INFO_USE_SOCKS"\n", cp)) {
					new_clnt_flags|=DCC_CLNT_INFO_FG_SOCKS;
					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, lineno)) {
			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_lineno(lfile, lineno));
				fclose(f);
				return 0;
			}
			if (num_grey_nms >= DIM(grey_nms)) {
				dcc_error_msg("too many Greylist server names"
					      "%s",
					      fnm_lineno(lfile, lineno));
				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_lineno(lfile, lineno));
				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_lineno(lfile, lineno));
		return 0;
	}


	/* create the map and lock, install, and unlock the information */
	dcc_rel_priv();
	dcc_ctxts_lock();
	created = 0;
	if (0 < dcc_map_info(dcc_emsg, info_map_nm, -1)) {
		/* copy into an existing map */
		if (!dcc_info_lock(dcc_emsg)) {
			dcc_error_msg("%s", dcc_emsg);
			dcc_ctxts_unlock();
			return 0;
		}
	} else {
		/* create a new map */
		dcc_ctxts_unlock();
		if (!dcc_create_map(dcc_emsg, info_map_nm, 0,
				    0, 0, 0, 0, new_clnt_flags)) {
			dcc_error_msg("%s", dcc_emsg);
			return 0;
		}
		created = 1;
		printf("    created %s\n", dcc_info_nm);
		set_which_map(MAP_INFO);
		if (!init_map(1))
			return 0;
	}
	if (!dcc_resolve_lock(dcc_emsg)) {
		if (created)
			unlink(dcc_info_nm);
		dcc_error_msg("%s", dcc_emsg);
		dcc_ctxts_unlock();
		return 0;
	}

	/* merge the old and new entries */
	add_new_nms(grey_nms, dcc_clnt_info->grey.nms);
	add_new_nms(dcc_nms, dcc_clnt_info->dcc.nms);
	dcc_clnt_info->flags = clnt_flags = new_clnt_flags;

	dcc_map_changed();
	if (!dcc_resolve_unlock(dcc_emsg))
		dcc_error_msg("%s", dcc_emsg);
	dcc_ctxts_unlock();
	if (!lfile)
		printf("##################\n\n");

	set_which_map(MAP_INFO);
	if (!quiet)
		return info_cmd("", ce);
	return 1;
}



static int
info_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	DCC_CLNT_INFO info;
	u_char dcc, names;

	if (*arg == '\0') {
		names = 0;
	} else if (!strcmp(arg, "-N")) {
		names = 1;
	} else {
		return -1;
	}

	/* map, copy, and unlock the information
	 * prefer to talk to the server, but don't insist */
	if (which_map == MAP_TMP) {
		if (!init_conn(1))
			return 0;
	} else {
		if (!init_conn(1)) {
			set_which_map(MAP_TMP);
			return 0;
		}
	}

	/* snapshot the data and then release it while we print it */
	memcpy(&info, dcc_clnt_info, sizeof(info));
	dcc_ctxts_unlock();
	if (!dcc_info_unlock(dcc_emsg)) {
		dcc_error_msg("%s", dcc_emsg);
		return 0;
	}

	dcc_rel_priv();
	if (which_map == MAP_INFO) {
		if (info.dcc.nms[0].hostname[0] != '\0'
		    || !grey_on) {
			dcc_print_info(dcc_info_nm, &info, 0, names,
				       0 <= access(dcc_info_nm, R_OK));
			dcc = 1;
		} else {
			dcc = 0;
		}
		if (info.grey.nms[0].hostname[0] != '\0'
		    || grey_on) {
			if (dcc)
				fputs("\n################\n", stdout);
			dcc_print_info(dcc_info_nm, &info, 1, names,
				       0 <= access(dcc_info_nm, R_OK));
		}
	} else {
		dcc_print_info(0, &info, 0, names, 1);
	}
	putchar('\n');
	return 1;
}



static int
rtt_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
	if (!init_map(1))
		return 0;
	if (!dcc_resolve_lock(dcc_emsg)) {
		dcc_error_msg("%s", dcc_emsg);
	} else {
		dcc_force_measure_rtt(&dcc_clnt_info->dcc, 0);
		dcc_force_measure_rtt(&dcc_clnt_info->grey, 0);
		if (!dcc_resolve_unlock(dcc_emsg))
			dcc_error_msg("%s", dcc_emsg);
	}
	dcc_ctxts_unlock();

	return info_cmd(arg, ce);
}



/* delete a checksum */
static int				/* 1=ok, 0=bad checksum, -1=fatal */
delck_sub(DCC_EMSG emsg, DCC_WF *wf UATTRIB,
	  const char *fnm UATTRIB, int lineno UATTRIB,
	  DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts UATTRIB)
{
	struct timeval cmd_start, cmd_end;
	char type_buf[DCC_XHDR_MAX_TYPE_LEN];
	char ck_buf[sizeof(DCC_SUM)*3+2];
	DCC_DELETE del;
	union {
	    DCC_HDR	hdr;
	    DCC_ERROR	error;
	} resp;
	u_char result;

	printf(" deleting %s  %s\n",
	       dcc_type2str(type_buf, sizeof(type_buf), type, 0, 1),
	       dcc_ck2str(ck_buf, sizeof(ck_buf), type, sum));

	memset(&del, 0, sizeof(del));
	gettimeofday(&cmd_start, 0);
	del.date = htonl(cmd_start.tv_sec);
	del.ck.type = type;
	del.ck.len = sizeof(del.ck);
	memcpy(&del.ck.sum, sum, sizeof(DCC_SUM));
	result = dcc_clnt_op(emsg, ctxt, DCC_CLNT_FG_NO_FAIL,
			     0, 0, &del.hdr, sizeof(del),
			     DCC_OP_DELETE, &resp.hdr, sizeof(resp), 0);
	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(&resp.hdr));
			result = 0;
			break;
		}
	}

	if (dcc_clnt_debug) {
		printf("%.2f ms\n",
		       ((cmd_end.tv_sec-cmd_start.tv_sec)*1000.0
			+ (cmd_end.tv_usec-cmd_start.tv_usec)/1000.0));
	}
	return result;
}



/* delete a simple checksum */
static int
delck_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	char type_str[DCC_XHDR_MAX_TYPE_LEN+1];

	if (*arg == '\0')
		return -1;
	arg = dcc_parse_word(dcc_emsg, type_str, sizeof(type_str),
			     arg, 0, 0, 0);
	if (!arg)
		return -1;

	if (!init_conn(0))
		return 0;
	return 0 < dcc_parse_hex_ck(dcc_emsg, &cmn_wf, 0, 0, type_str,
				    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;
}



/* get the server's list of recent clients */
static int
clients_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
#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])
	u_char nonames, sort, ids, avg, have_max_clients, have_thold;
	u_int max_clients, thold;
	int total, subtotal;
	u_int num_clients, ct_size;
	struct ct {
	    struct ct *lt, *gt, *up;
	    time_t	last_used;
	    u_int32_t	requests;
	    u_int	rank;
	    u_int16_t	nops;
	    u_char	flags;
	    DCC_CLNT_ID	id;
	    DCC_SOCKU	su;
	} *ctree, **ctptr, *ctup, *ct, *ctnew;
	DCC_OPS result;
	DCC_ADMN_RESP_CLIENTS *cl;
	int cl_len, result_len;
	char name[MAXHOSTNAMELEN];
	char date_buf[40];
	struct tm tm;
	char *p;
	int i;

	max_clients = 10000;
	have_max_clients = 0;
	thold = 0;
	have_thold = 0;

	/* look for "-n", "-ns", "-n -s", etc. */
	nonames = 0;
	sort = 0;
	ids = 0;
	avg = 0;
	while (*arg != 0) {
		arg += strspn(arg, " \t");
		if (*arg == '-') {
			++arg;
			do {
				if (*arg == 'n') {
					nonames = 1;
				} else if (*arg == 's') {
					sort = 1;
				} else if (*arg == 'i') {
					ids = 1;
				} else if (*arg == 'a') {
					avg = 1;
				} else {
					return -1;
				}
			} while (*++arg != ' ' && *arg != '\t' && *arg != '\0');
			continue;
		}
		if (!have_max_clients
		    && (i = strtoul(arg, &p, 10)) != 0
		    && (*p == ' ' || *p == '\t' || *p == '\0')) {
			have_max_clients = 1;
			max_clients = i;
			arg = p;
			continue;
		}
		if (!have_thold
		    && (i = strtoul(arg, &p, 10)) > 0
		    && i < (1<<16)
		    && (*p == ' ' || *p == '\t' || *p == '\0')) {
			have_thold = 1;
			thold = i;
			arg = p;
			continue;
		}
		return -1;
	}

	if (!ids
	    && !ck_priv_cmd(ce, 0, 1))
		return 0;

	if (!init_conn(0))
		return 0;

	/* Collect all of the information before printing it to minimize
	 * the changes in the position of hosts and so deleted or missing
	 * entries. */
	total = 0;
	subtotal = 0;
	ct_size = 0;
	num_clients = 0;
	ctree = 0;
	for (;;) {
		memset(&op_resp, 0, sizeof(op_resp));
		gettimeofday(&op_start, 0);
		result_len = sizeof(op_resp);
		result = dcc_aop(dcc_emsg, ctxt, grey_on, -1,
				 ids ? DCC_AOP_CLIENTS_ID : DCC_AOP_CLIENTS,
				 (num_clients << 16) + thold,
				 sizeof(DCC_ADMN_RESP_VAL)
				 / sizeof(DCC_ADMN_RESP_CLIENTS),
				 avg, 0,
				 &op_resp, &result_len, &op_resp_su);
		if (result == DCC_OP_INVALID
		    || result == DCC_OP_ERROR) {
			dcc_error_msg("%s", dcc_emsg);
			break;
		}

		if (!num_clients)
			print_aop(-1);

		if (result_len == 1)
			break;

		for (cl = op_resp.clients;
		     (char *)cl < result_len+(char *)op_resp.clients;
		     cl = (DCC_ADMN_RESP_CLIENTS *)((char *)cl + cl_len)) {
			if (cl->flags & DCC_ADMN_RESP_CLIENTS_SKIP) {
				num_clients += CPY3(cl->requests);
				cl_len = (sizeof(*cl) - sizeof(cl->addr)
					  + sizeof(cl->addr.ipv4));
				continue;
			}

			++num_clients;
			if (++ct_size > 10000) {
				dcc_error_msg("too many clients");
				goto stop;
			}
			ctnew = dcc_malloc(sizeof(*ctnew));
			memset(ctnew, 0, sizeof(*ctnew));
			ctnew->flags = cl->flags;
			ctnew->id = CPY4(cl->id);
			ctnew->last_used = CPY4(cl->last_used);
			ctnew->requests = CPY3(cl->requests);
			total += ctnew->requests;
			ctnew->nops = CPY2(cl->nops);
			if (cl->flags & DCC_ADMN_RESP_CLIENTS_IPV6) {
				memcpy(&ctnew->su.ipv6.sin6_addr,
				       cl->addr.ipv6,
				       sizeof(ctnew->su.ipv6.sin6_addr));
				ctnew->su.sa.sa_family = AF_INET6;
				cl_len = (sizeof(*cl) - sizeof(cl->addr)
					  + sizeof(cl->addr.ipv6));
			} else {
				memcpy(&ctnew->su.ipv4.sin_addr,
				       cl->addr.ipv4,
				       sizeof(ctnew->su.ipv4.sin_addr));
				ctnew->su.sa.sa_family = AF_INET;
				cl_len = (sizeof(*cl) - sizeof(cl->addr)
					  + sizeof(cl->addr.ipv4));
			}

			/* add the new entry to the tree */
			ctptr = &ctree;
			ctup = 0;
			for (;;) {
				ct = *ctptr;
				if (!ct) {
					ctnew->up = ctup;
					*ctptr = ctnew;
					break;
				}
				i = !sort;
				if (!i) {
					i = (ct->flags
					     & DCC_ADMN_RESP_CLIENTS_BL);
					i -= (ctnew->flags
					      & DCC_ADMN_RESP_CLIENTS_BL);
				}
				if (!i) {
					i = ct->requests;
					i -= ctnew->requests;
				}
				ctup = ct;
				if (i >= 0) {
					ctptr = &ct->lt;
				} else {
					/* update the threshold if sorting */
					if (++ct->rank >= max_clients
					    && thold < ct->requests)
					    thold = ct->requests;
					ctptr = &ct->gt;
				}
			}
		}
		if ((char *)cl != result_len+(char *)op_resp.clients) {
			dcc_error_msg("wrong sized clients response; %d != %d",
				      result_len,
				      (int)((char *)cl
					    - (char *)op_resp.clients));
			break;
		}

		/* quit if we want only part of the list
		 * and  we have it */
		if (!sort && ct_size >= max_clients)
			break;
	}
stop:
	if (!total)
		total = 1;

	/* print the list in the tree */
	name[0] = '\0';
	num_clients = 0;
	for (ct = ctree; ct; ct = ctnew) {
		ctnew = ct->gt;
		if (ctnew) {
			ct->gt = 0;
			continue;
		}

		if (num_clients == 0) {
			if (sort) {
				fputs("              ops  nops ", stdout);
				if (ids)
					fputs("   ID ", stdout);
				fputs("   last\n", stdout);
			} else {
				fputs("    ops  nops    last           ID\n",
				      stdout);
			}
		}
		if (++num_clients <= max_clients) {
			if (sort) {
				subtotal += ct->requests;
				printf("%3d%% %3d%% ",
				       ct->requests*100/total,
				       subtotal*100/total);
			}
			printf("%7d %5d ", ct->requests, ct->nops);
			if (sort && ids)
				printf("%5d ", ct->id);
			strftime(date_buf, sizeof(date_buf), "%m/%d %X",
				 dcc_localtime(ct->last_used, &tm));
			printf("%s", date_buf);
			if (ct->flags & DCC_ADMN_RESP_CLIENTS_BL)
				fputs(" BLACKLIST", stdout);
			if (!sort)
				printf(" %5d", ct->id);
			if (!ids) {
				if (!nonames)
					dcc_su2name(name, sizeof(name),
						    &ct->su);
				printf(" %-16s %s",
				       dcc_su2str_opt(&ct->su, 0, '\0'), name);
			}
			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');
	return 1;
#undef CPY2
#undef CPY3
#undef CPY4
}



/* get and set the server's anonymous client delay */
static int
anon_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	int new_delay, old_delay, inflate;
	DCC_OPS result;
	int result_len;
	char *inflate_str, *p;

	inflate = 0;
	if (*arg == '\0') {
		new_delay = DCC_NO_ANON_DELAY;
	} else {
		if (!ck_priv_cmd(ce, 0, 1))
			return 0;
		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 != ',')) {
				dcc_error_msg("invalid delay: \"%s\"", arg);
				return 0;
			}
			if (*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 (!init_conn(0))
		return 0;

	memset(&op_resp, 0, sizeof(op_resp));
	gettimeofday(&op_start, 0);
	result_len = sizeof(op_resp);
	result = dcc_aop(dcc_emsg, ctxt, grey_on, -1, DCC_AOP_ANON_DELAY,
			 inflate, new_delay>>8, new_delay, 0,
			 &op_resp, &result_len, &op_resp_su);
	if (result == DCC_OP_INVALID
	    || result == DCC_OP_ERROR) {
		dcc_error_msg("%s", dcc_emsg);
		return 0;
	}

	old_delay = ((op_resp.anon_delay.delay[0]<<8)
		     + op_resp.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 = ((op_resp.anon_delay.inflate[0]<<24)
			   +(op_resp.anon_delay.inflate[1]<<16)
			   +(op_resp.anon_delay.inflate[2]<<8)
			   +op_resp.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, -1, 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, -1, 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, -1, 1);
}



/* get the flood counts for a server */
static int
flod_stats(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB)
{
	u_int32_t id, next_id;
	DCC_AOP_FLODS op;
	u_char heading;
	int sresult;

	if (!arg)
		return -1;
	if (!CSTRCMP(arg, "clear")) {
		arg += STRZ("clear");
		arg += strspn(arg, DCC_WHITESPACE);
		op = DCC_AOP_FLOD_STATS_CLEAR;
	} else {
		op = DCC_AOP_FLOD_STATS;
	}

	heading = 1;
	if (!strcasecmp(arg, "all")) {
		id = DCC_SRVR_ID_MAX+1;
		for (;;) {
			if (!start_aop(DCC_AOP_FLOD, id*256 + op, -1))
				return 0;
			sresult = sscanf(op_resp.string,
					 DCC_AOP_FLOD_STATS_ID, &next_id);
			if (1 == sresult
			    && id == next_id)
				return 1;
			fin_aop(-1, 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, -1, heading);
}



/* get the statistics from all known servers */
static int
stats_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
	DCC_SRVR_CLASS *class;
	int anum, srvrs_gen;
	DCC_AOPS aop;

	/* look for "clear" or "all" */
	anum = -1;
	aop = DCC_AOP_STATS;
	while (*arg != 0) {
		arg += strspn(arg, " \t");
		if (anum == -1
		    && !CSTRCMP(arg, "clear")) {
			arg += STRZ("clear");
			aop = DCC_AOP_STATS_CLEAR;
			if (!get_passwd(aop_tbl[aop].privileged)) {
				dcc_error_msg("\"stats clear\""
					      " is a privileged operation"
					      PRV_MSG,
					      DCC_NM2PATH(IDS_NM_DEF));
				return 0;
			}
		} else if (aop == DCC_AOP_STATS
			   && !CSTRCMP(arg, "all")) {
			arg += STRZ("all");
			anum = 0;
		}
		if (*arg != '\0' && *arg != ' ' && *arg != '\t')
			return -1;
	}

	if (!init_conn(0))
		return 0;
	class = DCC_GREY2CLASS(grey_on);
	srvrs_gen = class->gen;
	do {
		if (srvrs_gen != class->gen) {
			dcc_error_msg("list of servers changed");
			return 0;
		}
		/* skip dead servers */
		if (class->addrs[anum].srvr_id == DCC_ID_INVALID
		    && anum >= 0)
			continue;

		do_aop(aop, sizeof(op_resp), anum, 1);
		fflush(stderr);
		fflush(stdout);
	} while (anum >= 0 && ++anum < class->num_addrs);

	return 1;
}



/* restore tracing to default */
static int
trace_def(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB)
{
	if (!init_conn(0))
		return 0;

	return (do_aop(DCC_AOP_TRACE_ON, DCC_TRACE_ON_DEF_BITS, -1, 1)
		&& do_aop(DCC_AOP_TRACE_OFF, DCC_TRACE_OFF_DEF_BITS, -1, 1));

}
