/*
 * ntpq - query an NTP server using mode 6 commands
 */
#include <config.h>
#include <ctype.h>
#include <signal.h>
#include <setjmp.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef SYS_WINNT
# include <mswsock.h>
# define PATH_DEVNULL	"NUL:"
#else
# define PATH_DEVNULL	"/dev/null"
#endif
#include <isc/net.h>
#include <isc/result.h>

#include "ntpq.h"
#include "ntp_assert.h"
#include "ntp_stdlib.h"
#include "ntp_unixtime.h"
#include "ntp_calendar.h"
#include "ntp_select.h"
#include "ntp_assert.h"
#include "lib_strbuf.h"
#include "ntp_lineedit.h"
#include "ntp_debug.h"
#ifdef OPENSSL
# include "openssl/evp.h"
# include "openssl/objects.h"
# include "openssl/err.h"
# ifdef SYS_WINNT
#  include "openssl/opensslv.h"
#  if !defined(HAVE_EVP_MD_DO_ALL_SORTED) && OPENSSL_VERSION_NUMBER > 0x10000000L
#     define HAVE_EVP_MD_DO_ALL_SORTED	1
#  endif
# endif
# include "libssl_compat.h"
# ifdef HAVE_OPENSSL_CMAC_H
#  include <openssl/cmac.h>
#  define CMAC "AES128CMAC"
# endif
#endif
#include <ssl_applink.c>

#include "ntp_libopts.h"
#include "safecast.h"

#ifdef SYS_VXWORKS		/* vxWorks needs mode flag -casey*/
# define open(name, flags)   open(name, flags, 0777)
# define SERVER_PORT_NUM     123
#endif

/* we use COMMAND as an autogen keyword */
#ifdef COMMAND
# undef COMMAND
#endif

/*
 * Because we potentially understand a lot of commands we will run
 * interactive if connected to a terminal.
 */
int interactive = 0;		/* set to 1 when we should prompt */
const char *prompt = "ntpq> ";	/* prompt to ask him about */

/*
 * use old readvars behavior?  --old-rv processing in ntpq resets
 * this value based on the presence or absence of --old-rv.  It is
 * initialized to 1 here to maintain backward compatibility with
 * libntpq clients such as ntpsnmpd, which are free to reset it as
 * desired.
 */
int	old_rv = 1;

/*
 * How should we display the refid?
 * REFID_HASH, REFID_IPV4
 */
te_Refid drefid = -1;

/*
 * for get_systime()
 */
s_char	sys_precision;		/* local clock precision (log2 s) */

/*
 * Keyid used for authenticated requests.  Obtained on the fly.
 */
u_long info_auth_keyid = 0;

static	int	info_auth_keytype = NID_md5;	/* MD5 */
static	size_t	info_auth_hashlen = 16;		/* MD5 */
u_long	current_time;		/* needed by authkeys; not used */

/*
 * Flag which indicates we should always send authenticated requests
 */
int always_auth = 0;

/*
 * Flag which indicates raw mode output.
 */
int rawmode = 0;

/*
 * Packet version number we use
 */
u_char pktversion = NTP_OLDVERSION + 1;


/*
 * Format values
 */
#define	PADDING	0
#define	HA	1	/* host address */
#define	NA	2	/* network address */
#define	LP	3	/* leap (print in binary) */
#define	RF	4	/* refid (sometimes string, sometimes not) */
#define	AU	5	/* array of unsigned times */
#define FX	6	/* test flags */
#define TS	7	/* l_fp timestamp in hex */
#define	OC	8	/* integer, print in octal */
#define	AS	9	/* array of signed times */
#define	SN	10	/* signed number: must display +/- sign */
#define	EOV	255	/* end of table */

/*
 * For the most part ntpq simply displays what ntpd provides in the
 * mostly plain-text mode 6 responses.  A few variable names are by
 * default "cooked" to provide more human-friendly output.
 */
const var_format cookedvars[] = {
	{ "leap",		LP },
	{ "reach",		OC },
	{ "refid",		RF },
	{ "reftime",		TS },
	{ "clock",		TS },
	{ "org",		TS },
	{ "rec",		TS },
	{ "xmt",		TS },
	{ "flash",		FX },
	{ "srcadr",		HA },
	{ "peeradr",		HA },	/* compat with others */
	{ "dstadr",		NA },
	{ "filtdelay",		AU },
	{ "filtoffset",		AS },
	{ "filtdisp",		AU },
	{ "filterror",		AU },	/* compat with others */
	{ "offset",		SN },
	{ "frequency",		SN }
};



/*
 * flasher bits
 */
static const char *tstflagnames[] = {
	"pkt_dup",		/* TEST1 */
	"pkt_bogus",		/* TEST2 */
	"pkt_unsync",		/* TEST3 */
	"pkt_denied",		/* TEST4 */
	"pkt_auth",		/* TEST5 */
	"pkt_stratum",		/* TEST6 */
	"pkt_header",		/* TEST7 */
	"pkt_autokey",		/* TEST8 */
	"pkt_crypto",		/* TEST9 */
	"peer_stratum",		/* TEST10 */
	"peer_dist",		/* TEST11 */
	"peer_loop",		/* TEST12 */
	"peer_unreach"		/* TEST13 */
};


int		ntpqmain	(int,	char **);
/*
 * Built in command handler declarations
 */
static	int	openhost	(const char *, int);
static	void	dump_hex_printable(const void *, size_t);
static	int	sendpkt		(void *, size_t);
static	int	getresponse	(int, int, u_short *, size_t *, const char **, int);
static	int	sendrequest	(int, associd_t, int, size_t, const char *);
static	char *	tstflags	(u_long);
#ifndef BUILD_AS_LIB
static	void	getcmds		(void);
#ifndef SYS_WINNT
static	int	abortcmd	(void);
#endif	/* SYS_WINNT */
static	void	docmd		(const char *);
static	void	tokenize	(const char *, char **, int *);
static	int	getarg		(const char *, int, arg_v *);
#endif	/* BUILD_AS_LIB */
static	int	findcmd		(const char *, struct xcmd *,
				 struct xcmd *, struct xcmd **);
static	int	rtdatetolfp	(char *, l_fp *);
static	int	decodearr	(char *, int *, l_fp *, int);
static	void	help		(struct parse *, FILE *);
static	int	helpsort	(const void *, const void *);
static	void	printusage	(struct xcmd *, FILE *);
static	void	timeout		(struct parse *, FILE *);
static	void	auth_delay	(struct parse *, FILE *);
static	void	host		(struct parse *, FILE *);
static	void	ntp_poll	(struct parse *, FILE *);
static	void	keyid		(struct parse *, FILE *);
static	void	keytype		(struct parse *, FILE *);
static	void	passwd		(struct parse *, FILE *);
static	void	hostnames	(struct parse *, FILE *);
static	void	setdebug	(struct parse *, FILE *);
static	void	quit		(struct parse *, FILE *);
static	void	showdrefid	(struct parse *, FILE *);
static	void	version		(struct parse *, FILE *);
static	void	raw		(struct parse *, FILE *);
static	void	cooked		(struct parse *, FILE *);
static	void	authenticate	(struct parse *, FILE *);
static	void	ntpversion	(struct parse *, FILE *);
static	void	warning		(const char *, ...) NTP_PRINTF(1, 2);
static	void	error		(const char *, ...) NTP_PRINTF(1, 2);
static	u_long	getkeyid	(const char *);
static	void	atoascii	(const char *, size_t, char *, size_t);
static	void	cookedprint	(int, size_t, const char *, int, int, FILE *);
static	void	rawprint	(int, size_t, const char *, int, int, FILE *);
static	void	startoutput	(void);
static	void	output		(FILE *, const char *, const char *);
static	void	endoutput	(FILE *);
static	void	outputarr	(FILE *, char *, int, l_fp *, int);
static	int	assoccmp	(const void *, const void *);
	u_short	varfmt		(const char *);
	void	ntpq_custom_opt_handler(tOptions *, tOptDesc *);

#ifndef BUILD_AS_LIB
static	char   *list_digest_names(void);
static	void	on_ctrlc	(void);
static	int	my_easprintf	(char**, const char *, ...) NTP_PRINTF(2, 3);
#ifdef OPENSSL
static	char   *insert_cmac	(char *list);
# ifdef HAVE_EVP_MD_DO_ALL_SORTED
static	void	list_md_fn	(const EVP_MD *m, const char *from,
				 const char *to, void *arg);
# endif /* HAVE_EVP_MD_DO_ALL_SORTED */
#endif /* OPENSSL */
#endif /* !defined(BUILD_AS_LIB) */


/* read a character from memory and expand to integer */
static inline int
pgetc(
	const char *cp
	)
{
	return (int)*(const unsigned char*)cp;
}



/*
 * Built-in commands we understand
 */
struct xcmd builtins[] = {
	{ "?",		help,		{  OPT|NTP_STR, NO, NO, NO },
	  { "command", "", "", "" },
	  "tell the use and syntax of commands" },
	{ "help",	help,		{  OPT|NTP_STR, NO, NO, NO },
	  { "command", "", "", "" },
	  "tell the use and syntax of commands" },
	{ "timeout",	timeout,	{ OPT|NTP_UINT, NO, NO, NO },
	  { "msec", "", "", "" },
	  "set the primary receive time out" },
	{ "delay",	auth_delay,	{ OPT|NTP_INT, NO, NO, NO },
	  { "msec", "", "", "" },
	  "set the delay added to encryption time stamps" },
	{ "host",	host,		{ OPT|NTP_STR, OPT|NTP_STR, NO, NO },
	  { "-4|-6", "hostname", "", "" },
	  "specify the host whose NTP server we talk to" },
	{ "poll",	ntp_poll,	{ OPT|NTP_UINT, OPT|NTP_STR, NO, NO },
	  { "n", "verbose", "", "" },
	  "poll an NTP server in client mode `n' times" },
	{ "passwd",	passwd,		{ OPT|NTP_STR, NO, NO, NO },
	  { "", "", "", "" },
	  "specify a password to use for authenticated requests"},
	{ "hostnames",	hostnames,	{ OPT|NTP_STR, NO, NO, NO },
	  { "yes|no", "", "", "" },
	  "specify whether hostnames or net numbers are printed"},
	{ "debug",	setdebug,	{ OPT|NTP_STR, NO, NO, NO },
	  { "no|more|less", "", "", "" },
	  "set/change debugging level" },
	{ "quit",	quit,		{ NO, NO, NO, NO },
	  { "", "", "", "" },
	  "exit ntpq" },
	{ "exit",	quit,		{ NO, NO, NO, NO },
	  { "", "", "", "" },
	  "exit ntpq" },
	{ "keyid",	keyid,		{ OPT|NTP_UINT, NO, NO, NO },
	  { "key#", "", "", "" },
	  "set keyid to use for authenticated requests" },
	{ "drefid",	showdrefid,	{ OPT|NTP_STR, NO, NO, NO },
	  { "hash|ipv4", "", "", "" },
	  "display refid's as IPv4 or hash" },
	{ "version",	version,	{ NO, NO, NO, NO },
	  { "", "", "", "" },
	  "print version number" },
	{ "raw",	raw,		{ NO, NO, NO, NO },
	  { "", "", "", "" },
	  "do raw mode variable output" },
	{ "cooked",	cooked,		{ NO, NO, NO, NO },
	  { "", "", "", "" },
	  "do cooked mode variable output" },
	{ "authenticate", authenticate,	{ OPT|NTP_STR, NO, NO, NO },
	  { "yes|no", "", "", "" },
	  "always authenticate requests to this server" },
	{ "ntpversion",	ntpversion,	{ OPT|NTP_UINT, NO, NO, NO },
	  { "version number", "", "", "" },
	  "set the NTP version number to use for requests" },
	{ "keytype",	keytype,	{ OPT|NTP_STR, NO, NO, NO },
	  { "key type %s", "", "", "" },
	  NULL },
	{ 0,		0,		{ NO, NO, NO, NO },
	  { "", "", "", "" }, "" }
};


/*
 * Default values we use.
 */
#define	DEFHOST		"localhost"	/* default host name */
#define	DEFTIMEOUT	5		/* wait 5 seconds for 1st pkt */
#define	DEFSTIMEOUT	3		/* and 3 more for each additional */
/*
 * Requests are automatically retried once, so total timeout with no
 * response is a bit over 2 * DEFTIMEOUT, or 10 seconds.  At the other
 * extreme, a request eliciting 32 packets of responses each for some
 * reason nearly DEFSTIMEOUT seconds after the prior in that series,
 * with a single packet dropped, would take around 32 * DEFSTIMEOUT, or
 * 93 seconds to fail each of two times, or 186 seconds.
 * Some commands involve a series of requests, such as "peers" and
 * "mrulist", so the cumulative timeouts are even longer for those.
 */
#define	DEFDELAY	0x51EB852	/* 20 milliseconds, l_fp fraction */
#define	LENHOSTNAME	256		/* host name is 256 characters long */
#define	MAXCMDS		100		/* maximum commands on cmd line */
#define	MAXHOSTS	200		/* maximum hosts on cmd line */
#define	MAXLINE		512		/* maximum line length */
#define	MAXTOKENS	(1+MAXARGS+2)	/* maximum number of usable tokens */
#define	MAXVARLEN	256		/* maximum length of a variable name */
#define	MAXVALLEN	2048		/* maximum length of a variable value */
#define	MAXOUTLINE	72		/* maximum length of an output line */
#define SCREENWIDTH	76		/* nominal screen width in columns */

/*
 * Some variables used and manipulated locally
 */
struct sock_timeval tvout = { DEFTIMEOUT, 0 };	/* time out for reads */
struct sock_timeval tvsout = { DEFSTIMEOUT, 0 };/* secondary time out */
l_fp delay_time;				/* delay time */
char currenthost[LENHOSTNAME];			/* current host name */
int currenthostisnum;				/* is prior text from IP? */
struct sockaddr_in hostaddr;			/* host address */
int showhostnames = 1;				/* show host names by default */
int wideremote = 0;				/* show wide remote names? */

int ai_fam_templ;				/* address family */
int ai_fam_default;				/* default address family */
SOCKET sockfd;					/* fd socket is opened on */
int havehost = 0;				/* set to 1 when host open */
int s_port = 0;
struct servent *server_entry = NULL;		/* server entry for ntp */


/*
 * Sequence number used for requests.  It is incremented before
 * it is used.
 */
u_short sequence;

/*
 * Holds data returned from queries.  Declare buffer long to be sure of
 * alignment.
 */
#define	DATASIZE	(MAXFRAGS*480)	/* maximum amount of data */
long pktdata[DATASIZE/sizeof(long)];

/*
 * assoc_cache[] is a dynamic array which allows references to
 * associations using &1 ... &N for n associations, avoiding manual
 * lookup of the current association IDs for a given ntpd.  It also
 * caches the status word for each association, retrieved incidentally.
 */
struct association *	assoc_cache;
u_int assoc_cache_slots;/* count of allocated array entries */
u_int numassoc;		/* number of cached associations */

/*
 * For commands typed on the command line (with the -c option)
 */
size_t numcmds = 0;
size_t defcmds = 0;        /* Options on the command line are 'defined'! */
char *ccmds[MAXCMDS];
#define	ADDCMD(cp)	if (numcmds < MAXCMDS) ccmds[numcmds++] = estrdup(cp)

/*
 * When multiple hosts are specified.
 */

u_int numhosts;

chost chosts[MAXHOSTS];
#define	ADDHOST(cp)						\
	do {							\
		if (numhosts < MAXHOSTS) {			\
			chosts[numhosts].name = (cp);		\
			chosts[numhosts].fam = ai_fam_templ;	\
			numhosts++;				\
		}						\
	} while (0)

/*
 * Macro definitions we use
 */
#define	ISSPACE(c)	((c) == ' ' || (c) == '\t')
#define	ISEOL(c)	((c) == '\n' || (c) == '\r' || (c) == '\0')
#define	STREQ(a, b)	(*(a) == *(b) && strcmp((a), (b)) == 0)

/*
 * Jump buffer for longjumping back to the command level.
 *
 * Since we do this from a signal handler, we use 'sig{set,long}jmp()'
 * if available. The signal is blocked by default during the excution of
 * a signal handler, and it is unspecified if '{set,long}jmp()' save and
 * restore the signal mask. They do on BSD, it depends on the GLIBC
 * version on Linux, and the gods know what happens on other OSes...
 *
 * So we use the 'sig{set,long}jmp()' functions where available, because
 * for them the semantics are well-defined. If we have to fall back to
 * '{set,long}jmp()', the CTRL-C handling might be a bit erratic.
 */
#if HAVE_DECL_SIGSETJMP && HAVE_DECL_SIGLONGJMP
# define JMP_BUF	sigjmp_buf
# define SETJMP(x)	sigsetjmp((x), 1)
# define LONGJMP(x, v)	siglongjmp((x),(v))
#else
# define JMP_BUF	jmp_buf
# define SETJMP(x)	setjmp((x))
# define LONGJMP(x, v)	longjmp((x),(v))
#endif

#ifndef BUILD_AS_LIB
static	JMP_BUF		interrupt_buf;
static	volatile int	jump = 0;
#endif

/*
 * Points at file being currently printed into
 */
FILE *current_output = NULL;

/*
 * Command table imported from ntpdc_ops.c
 */
extern struct xcmd opcmds[];

char const *progname;

#ifdef NO_MAIN_ALLOWED
#ifndef BUILD_AS_LIB
CALL(ntpq,"ntpq",ntpqmain);

void clear_globals(void)
{
	extern int ntp_optind;
	showhostnames = 0;	/* don'tshow host names by default */
	ntp_optind = 0;
	server_entry = NULL;	/* server entry for ntp */
	havehost = 0;		/* set to 1 when host open */
	numassoc = 0;		/* number of cached associations */
	numcmds = 0;
	numhosts = 0;
}
#endif /* !BUILD_AS_LIB */
#endif /* NO_MAIN_ALLOWED */

/*
 * main - parse arguments and handle options
 */
#ifndef NO_MAIN_ALLOWED
int
main(
	int argc,
	char *argv[]
	)
{
	return ntpqmain(argc, argv);
}
#endif


#ifndef BUILD_AS_LIB
int
ntpqmain(
	int argc,
	char *argv[]
	)
{
	u_int ihost;
	size_t icmd;


#ifdef SYS_VXWORKS
	clear_globals();
	taskPrioritySet(taskIdSelf(), 100 );
#endif

	delay_time.l_ui = 0;
	delay_time.l_uf = DEFDELAY;

	init_lib();	/* sets up ipv4_works, ipv6_works */
	ssl_applink();
	init_auth();

	/* Check to see if we have IPv6. Otherwise default to IPv4 */
	if (!ipv6_works)
		ai_fam_default = AF_INET;

	/* Fixup keytype's help based on available digest names */

	{
	    char *list;
	    char *msg;

	    list = list_digest_names();

	    for (icmd = 0; icmd < sizeof(builtins)/sizeof(*builtins); icmd++) {
		if (strcmp("keytype", builtins[icmd].keyword) == 0) {
		    break;
		}
	    }

	    /* CID: 1295478 */
	    /* This should only "trip" if "keytype" is removed from builtins */
	    INSIST(icmd < sizeof(builtins)/sizeof(*builtins));

#ifdef OPENSSL
	    builtins[icmd].desc[0] = "digest-name";
	    my_easprintf(&msg,
			 "set key type to use for authenticated requests, one of:%s",
			 list);
#else
	    builtins[icmd].desc[0] = "md5";
	    my_easprintf(&msg,
			 "set key type to use for authenticated requests (%s)",
			 list);
#endif
	    builtins[icmd].comment = msg;
	    free(list);
	}

	progname = argv[0];

	{
		int optct = ntpOptionProcess(&ntpqOptions, argc, argv);
		argc -= optct;
		argv += optct;
	}

	/*
	 * Process options other than -c and -p, which are specially
	 * handled by ntpq_custom_opt_handler().
	 */

	debug = OPT_VALUE_SET_DEBUG_LEVEL;

	if (HAVE_OPT(IPV4))
		ai_fam_templ = AF_INET;
	else if (HAVE_OPT(IPV6))
		ai_fam_templ = AF_INET6;
	else
		ai_fam_templ = ai_fam_default;

	if (HAVE_OPT(INTERACTIVE))
		interactive = 1;

	if (HAVE_OPT(NUMERIC))
		showhostnames = 0;

	if (HAVE_OPT(WIDE))
		wideremote = 1;

	old_rv = HAVE_OPT(OLD_RV);

	drefid = OPT_VALUE_REFID;

	if (0 == argc) {
		ADDHOST(DEFHOST);
	} else {
		for (ihost = 0; ihost < (u_int)argc; ihost++) {
			if ('-' == *argv[ihost]) {
				//
				// If I really cared I'd also check:
				// 0 == argv[ihost][2]
				//
				// and there are other cases as well...
				//
				if ('4' == argv[ihost][1]) {
					ai_fam_templ = AF_INET;
					continue;
				} else if ('6' == argv[ihost][1]) {
					ai_fam_templ = AF_INET6;
					continue;
				} else {
					// XXX Throw a usage error
				}
			}
			ADDHOST(argv[ihost]);
		}
	}

	if (defcmds == 0 && interactive == 0
	    && isatty(fileno(stdin)) && isatty(fileno(stderr))) {
		interactive = 1;
	}

	set_ctrl_c_hook(on_ctrlc);
#ifndef SYS_WINNT /* Under NT cannot handle SIGINT, WIN32 spawns a handler */
	if (interactive)
		push_ctrl_c_handler(abortcmd);
#endif /* SYS_WINNT */

	if (numcmds > 0) {
		for (ihost = 0; ihost < numhosts; ihost++) {
			if (openhost(chosts[ihost].name, chosts[ihost].fam)) {
				if (ihost && current_output)
					fputc('\n', current_output);
				for (icmd = 0; icmd < numcmds; icmd++) {
					if (icmd && current_output)
						fputc('\n', current_output);
					docmd(ccmds[icmd]);
				}
			}
		}
		/* Release memory allocated in ADDCMD */
		for (icmd = 0; icmd < numcmds; icmd++)
			free(ccmds[icmd]);
	}

	if (defcmds == 0) {        /* No command line commands, so go interactive */
		(void) openhost(chosts[0].name, chosts[0].fam);
		getcmds();
	}
#ifdef SYS_WINNT
	WSACleanup();
#endif /* SYS_WINNT */
	return 0;
}
#endif /* !BUILD_AS_LIB */

/*
 * openhost - open a socket to a host
 */
static	int
openhost(
	const char *hname,
	int	    fam
	)
{
	const char svc[] = "ntp";
	char temphost[LENHOSTNAME];
	int a_info;
	struct addrinfo hints, *ai;
	sockaddr_u addr;
	size_t octets;
	const char *cp;
	char name[LENHOSTNAME];

	/*
	 * We need to get by the [] if they were entered
	 */
	if (*hname == '[') {
		cp = strchr(hname + 1, ']');
		if (!cp || (octets = (size_t)(cp - hname) - 1) >= sizeof(name)) {
			errno = EINVAL;
			warning("%s", "bad hostname/address");
			return 0;
		}
		memcpy(name, hname + 1, octets);
		name[octets] = '\0';
		hname = name;
	}

	/*
	 * First try to resolve it as an ip address and if that fails,
	 * do a fullblown (dns) lookup. That way we only use the dns
	 * when it is needed and work around some implementations that
	 * will return an "IPv4-mapped IPv6 address" address if you
	 * give it an IPv4 address to lookup.
	 */
	ZERO(hints);
	hints.ai_family = fam;
	hints.ai_protocol = IPPROTO_UDP;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = Z_AI_NUMERICHOST;
	ai = NULL;

	a_info = getaddrinfo(hname, svc, &hints, &ai);
	if (a_info == EAI_NONAME
#ifdef EAI_NODATA
	    || a_info == EAI_NODATA
#endif
	   ) {
		hints.ai_flags = AI_CANONNAME;
#ifdef AI_ADDRCONFIG
		hints.ai_flags |= AI_ADDRCONFIG;
#endif
		a_info = getaddrinfo(hname, svc, &hints, &ai);
	}
#ifdef AI_ADDRCONFIG
	/*
	 * Some older implementations don't like AI_ADDRCONFIG.
	 * Some versions of Windows return WSANO_DATA when there is no
	 * global address and AI_ADDRCONFIG is used.  AI_ADDRCONFIG
	 * is useful to short-circuit DNS lookups for IP protocols
	 * for which the host has no local addresses.  Windows
	 * unfortunately instead interprets AI_ADDRCONFIG to relate
	 * to off-host connectivity and so fails lookup when
	 * localhost works.
	 * To further muddy matters, some versions of WS2tcpip.h
	 * comment out #define EAI_NODATA WSANODATA claiming it
	 * was removed from RFC 2553bis and mentioning a need to
	 * contact the authors to find out why, but "helpfully"
	 * #defines EAI_NODATA EAI_NONAME   (== WSAHOST_NOT_FOUND)
	 * So we get more ugly platform-specific workarounds.
	 */
	if (
#if defined(WIN32)
	    WSANO_DATA == a_info || EAI_NONAME == a_info ||
#endif
	    EAI_BADFLAGS == a_info) {
		hints.ai_flags &= ~AI_ADDRCONFIG;
		a_info = getaddrinfo(hname, svc, &hints, &ai);
	}
#endif
	if (a_info != 0) {
		fprintf(stderr, "%s\n", gai_strerror(a_info));
		return 0;
	}

	INSIST(ai != NULL);
	ZERO(addr);
	octets = min(sizeof(addr), ai->ai_addrlen);
	memcpy(&addr, ai->ai_addr, octets);

	if (ai->ai_canonname == NULL) {
		strlcpy(temphost, stoa(&addr), sizeof(temphost));
		currenthostisnum = TRUE;
	} else {
		strlcpy(temphost, ai->ai_canonname, sizeof(temphost));
		currenthostisnum = FALSE;
	}

	if (debug > 2)
		printf("Opening host %s (%s)\n",
			temphost,
			(ai->ai_family == AF_INET)
			? "AF_INET"
			: (ai->ai_family == AF_INET6)
			  ? "AF_INET6"
			  : "AF-???"
			);

	if (havehost == 1) {
		if (debug > 2)
			printf("Closing old host %s\n", currenthost);
		closesocket(sockfd);
		havehost = 0;
	}
	strlcpy(currenthost, temphost, sizeof(currenthost));

	/* port maps to the same location in both families */
	s_port = NSRCPORT(&addr);
#ifdef SYS_VXWORKS
	((struct sockaddr_in6 *)&hostaddr)->sin6_port = htons(SERVER_PORT_NUM);
	if (ai->ai_family == AF_INET)
		*(struct sockaddr_in *)&hostaddr=
			*((struct sockaddr_in *)ai->ai_addr);
	else
		*(struct sockaddr_in6 *)&hostaddr=
			*((struct sockaddr_in6 *)ai->ai_addr);
#endif /* SYS_VXWORKS */

#ifdef SYS_WINNT
	{
		int optionValue = SO_SYNCHRONOUS_NONALERT;
		int err;

		err = setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE,
				 (void *)&optionValue, sizeof(optionValue));
		if (err) {
			mfprintf(stderr,
				 "setsockopt(SO_SYNCHRONOUS_NONALERT)"
				 " error: %m\n");
			freeaddrinfo(ai);
			exit(1);
		}
	}
#endif /* SYS_WINNT */

	sockfd = socket(ai->ai_family, ai->ai_socktype,
			ai->ai_protocol);
	if (sockfd == INVALID_SOCKET) {
		error("socket");
		freeaddrinfo(ai);
		return 0;
	}


#ifdef NEED_RCVBUF_SLOP
# ifdef SO_RCVBUF
	{ int rbufsize = DATASIZE + 2048;	/* 2K for slop */
	if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF,
		       (void *)&rbufsize, sizeof(int)) == -1)
		error("setsockopt");
	}
# endif
#endif

	if
#ifdef SYS_VXWORKS
	   (connect(sockfd, (struct sockaddr *)&hostaddr,
		    sizeof(hostaddr)) == -1)
#else
	   (connect(sockfd, (struct sockaddr *)ai->ai_addr,
		ai->ai_addrlen) == -1)
#endif /* SYS_VXWORKS */
	{
		error("connect");
		freeaddrinfo(ai);
		return 0;
	}
	freeaddrinfo(ai);
	havehost = 1;
	numassoc = 0;

	return 1;
}


static void
dump_hex_printable(
	const void *	data,
	size_t		len
	)
{
	/* every line shows at most 16 bytes, so we need a buffer of
	 *   4 * 16 (2 xdigits, 1 char, one sep for xdigits)
	 * + 2 * 1  (block separators)
	 * + <LF> + <NUL>
	 *---------------
	 *  68 bytes
	 */
	static const char s_xdig[16] = "0123456789ABCDEF";

	char lbuf[68];
	int  ch, rowlen;
	const u_char * cdata = data;
	char *xptr, *pptr;

	while (len) {
		memset(lbuf, ' ', sizeof(lbuf));
		xptr = lbuf;
		pptr = lbuf + 3*16 + 2;

		rowlen = (len > 16) ? 16 : (int)len;
		len -= rowlen;

		do {
			ch = *cdata++;

			*xptr++ = s_xdig[ch >> 4  ];
			*xptr++ = s_xdig[ch & 0x0F];
			if (++xptr == lbuf + 3*8)
				++xptr;

			*pptr++ = isprint(ch) ? (char)ch : '.';
		} while (--rowlen);

		*pptr++ = '\n';
		*pptr   = '\0';
		fputs(lbuf, stdout);
	}
}


/* XXX ELIMINATE sendpkt similar in ntpq.c, ntpdc.c, ntp_io.c, ntptrace.c */
/*
 * sendpkt - send a packet to the remote host
 */
static int
sendpkt(
	void *	xdata,
	size_t	xdatalen
	)
{
	if (debug >= 3)
		printf("Sending %zu octets\n", xdatalen);

	if (send(sockfd, xdata, xdatalen, 0) == -1) {
		warning("write to %s failed", currenthost);
		return -1;
	}

	if (debug >= 4) {
		printf("Request packet:\n");
		dump_hex_printable(xdata, xdatalen);
	}
	return 0;
}

/*
 * getresponse - get a (series of) response packet(s) and return the data
 */
static int
getresponse(
	int opcode,
	int associd,
	u_short *rstatus,
	size_t *rsize,
	const char **rdata,
	int timeo
	)
{
	struct ntp_control rpkt;
	struct sock_timeval tvo;
	u_short offsets[MAXFRAGS+1];
	u_short counts[MAXFRAGS+1];
	u_short offset;
	u_short count;
	size_t numfrags;
	size_t f;
	size_t ff;
	int seenlastfrag;
	int shouldbesize;
	fd_set fds;
	int n;
	int errcode;
	/* absolute timeout checks. Not 'time_t' by intention! */
	uint32_t tobase;	/* base value for timeout */
	uint32_t tospan;	/* timeout span (max delay) */
	uint32_t todiff;	/* current delay */

	memset(offsets, 0, sizeof(offsets));
	memset(counts , 0, sizeof(counts ));

	/*
	 * This is pretty tricky.  We may get between 1 and MAXFRAG packets
	 * back in response to the request.  We peel the data out of
	 * each packet and collect it in one long block.  When the last
	 * packet in the sequence is received we'll know how much data we
	 * should have had.  Note we use one long time out, should reconsider.
	 */
	*rsize = 0;
	if (rstatus)
		*rstatus = 0;
	*rdata = (char *)pktdata;

	numfrags = 0;
	seenlastfrag = 0;

	tobase = (uint32_t)time(NULL);

	FD_ZERO(&fds);

	/*
	 * Loop until we have an error or a complete response.  Nearly all
	 * code paths to loop again use continue.
	 */
	for (;;) {

		if (numfrags == 0)
			tvo = tvout;
		else
			tvo = tvsout;
		tospan = (uint32_t)tvo.tv_sec + (tvo.tv_usec != 0);

		FD_SET(sockfd, &fds);
		n = select(sockfd+1, &fds, NULL, NULL, &tvo);
		if (n == -1) {
#if !defined(SYS_WINNT) && defined(EINTR)
			/* Windows does not know about EINTR (until very
			 * recently) and the handling of console events
			 * is *very* different from POSIX/UNIX signal
			 * handling anyway.
			 *
			 * Under non-windows targets we map EINTR as
			 * 'last packet was received' and try to exit
			 * the receive sequence.
			 */
			if (errno == EINTR) {
				seenlastfrag = 1;
				goto maybe_final;
			}
#endif
			warning("select fails");
			return -1;
		}

		/*
		 * Check if this is already too late. Trash the data and
		 * fake a timeout if this is so.
		 */
		todiff = (((uint32_t)time(NULL)) - tobase) & 0x7FFFFFFFu;
		if ((n > 0) && (todiff > tospan)) {
			n = recv(sockfd, (char *)&rpkt, sizeof(rpkt), 0);
			n -= n; /* faked timeout return from 'select()',
				 * execute RMW cycle on 'n'
				 */
		}

		if (n <= 0) {
			/*
			 * Timed out.  Return what we have
			 */
			if (numfrags == 0) {
				if (timeo)
					fprintf(stderr,
						"%s: timed out, nothing received\n",
						currenthost);
				return ERR_TIMEOUT;
			}
			if (timeo)
				fprintf(stderr,
					"%s: timed out with incomplete data\n",
					currenthost);
			if (debug) {
				fprintf(stderr,
					"ERR_INCOMPLETE: Received fragments:\n");
				for (f = 0; f < numfrags; f++)
					fprintf(stderr,
						"%2u: %5d %5d\t%3d octets\n",
						(u_int)f, offsets[f],
						offsets[f] +
						counts[f],
						counts[f]);
				fprintf(stderr,
					"last fragment %sreceived\n",
					(seenlastfrag)
					    ? ""
					    : "not ");
			}
			return ERR_INCOMPLETE;
		}

		n = recv(sockfd, (char *)&rpkt, sizeof(rpkt), 0);
		if (n < 0) {
			warning("read");
			return -1;
		}

		if (debug >= 4) {
			printf("Response packet:\n");
			dump_hex_printable(&rpkt, n);
		}

		/*
		 * Check for format errors.  Bug proofing.
		 */
		if (n < (int)CTL_HEADER_LEN) {
			if (debug)
				printf("Short (%d byte) packet received\n", n);
			continue;
		}
		if (PKT_VERSION(rpkt.li_vn_mode) > NTP_VERSION
		    || PKT_VERSION(rpkt.li_vn_mode) < NTP_OLDVERSION) {
			if (debug)
				printf("Packet received with version %d\n",
				       PKT_VERSION(rpkt.li_vn_mode));
			continue;
		}
		if (PKT_MODE(rpkt.li_vn_mode) != MODE_CONTROL) {
			if (debug)
				printf("Packet received with mode %d\n",
				       PKT_MODE(rpkt.li_vn_mode));
			continue;
		}
		if (!CTL_ISRESPONSE(rpkt.r_m_e_op)) {
			if (debug)
				printf("Received request packet, wanted response\n");
			continue;
		}

		/*
		 * Check opcode and sequence number for a match.
		 * Could be old data getting to us.
		 */
		if (ntohs(rpkt.sequence) != sequence) {
			if (debug)
				printf("Received sequnce number %d, wanted %d\n",
				       ntohs(rpkt.sequence), sequence);
			continue;
		}
		if (CTL_OP(rpkt.r_m_e_op) != opcode) {
			if (debug)
			    printf(
				    "Received opcode %d, wanted %d (sequence number okay)\n",
				    CTL_OP(rpkt.r_m_e_op), opcode);
			continue;
		}

		/*
		 * Check the error code.  If non-zero, return it.
		 */
		if (CTL_ISERROR(rpkt.r_m_e_op)) {
			errcode = (ntohs(rpkt.status) >> 8) & 0xff;
			if (CTL_ISMORE(rpkt.r_m_e_op))
				TRACE(1, ("Error code %d received on not-final packet\n",
					  errcode));
			if (errcode == CERR_UNSPEC)
				return ERR_UNSPEC;
			return errcode;
		}

		/*
		 * Check the association ID to make sure it matches what
		 * we sent.
		 */
		if (ntohs(rpkt.associd) != associd) {
			TRACE(1, ("Association ID %d doesn't match expected %d\n",
				  ntohs(rpkt.associd), associd));
			/*
			 * Hack for silly fuzzballs which, at the time of writing,
			 * return an assID of sys.peer when queried for system variables.
			 */
#ifdef notdef
			continue;
#endif
		}

		/*
		 * Collect offset and count.  Make sure they make sense.
		 */
		offset = ntohs(rpkt.offset);
		count = ntohs(rpkt.count);

		/*
		 * validate received payload size is padded to next 32-bit
		 * boundary and no smaller than claimed by rpkt.count
		 */
		if (n & 0x3) {
			TRACE(1, ("Response packet not padded, size = %d\n",
				  n));
			continue;
		}

		shouldbesize = (CTL_HEADER_LEN + count + 3) & ~3;

		if (n < shouldbesize) {
			printf("Response packet claims %u octets payload, above %ld received\n",
			       count, (long)(n - CTL_HEADER_LEN));
			return ERR_INCOMPLETE;
		}

		if (debug >= 3 && shouldbesize > n) {
			u_int32 key;
			u_int32 *lpkt;
			int maclen;

			/*
			 * Usually we ignore authentication, but for debugging purposes
			 * we watch it here.
			 */
			/* round to 8 octet boundary */
			shouldbesize = (shouldbesize + 7) & ~7;

			maclen = n - shouldbesize;
			if (maclen >= (int)MIN_MAC_LEN) {
				printf(
					"Packet shows signs of authentication (total %d, data %d, mac %d)\n",
					n, shouldbesize, maclen);
				lpkt = (u_int32 *)&rpkt;
				printf("%08lx %08lx %08lx %08lx %08lx %08lx\n",
				       (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) - 3]),
				       (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) - 2]),
				       (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) - 1]),
				       (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32)]),
				       (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) + 1]),
				       (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) + 2]));
				key = ntohl(lpkt[(n - maclen) / sizeof(u_int32)]);
				printf("Authenticated with keyid %lu\n", (u_long)key);
				if (key != 0 && key != info_auth_keyid) {
					printf("We don't know that key\n");
				} else {
					if (authdecrypt(key, (u_int32 *)&rpkt,
					    n - maclen, maclen)) {
						printf("Auth okay!\n");
					} else {
						printf("Auth failed!\n");
					}
				}
			}
		}

		TRACE(2, ("Got packet, size = %d\n", n));
		if (count > (n - CTL_HEADER_LEN)) {
			TRACE(1, ("Received count of %u octets, data in packet is %ld\n",
				  count, (long)n - CTL_HEADER_LEN));
			continue;
		}
		if (count == 0 && CTL_ISMORE(rpkt.r_m_e_op)) {
			TRACE(1, ("Received count of 0 in non-final fragment\n"));
			continue;
		}
		if (offset + count > sizeof(pktdata)) {
			TRACE(1, ("Offset %u, count %u, too big for buffer\n",
				  offset, count));
			return ERR_TOOMUCH;
		}
		if (seenlastfrag && !CTL_ISMORE(rpkt.r_m_e_op)) {
			TRACE(1, ("Received second last fragment packet\n"));
			continue;
		}

		/*
		 * So far, so good.  Record this fragment, making sure it doesn't
		 * overlap anything.
		 */
		TRACE(2, ("Packet okay\n"));

		if (numfrags > (MAXFRAGS - 1)) {
			TRACE(2, ("Number of fragments exceeds maximum %d\n",
				  MAXFRAGS - 1));
			return ERR_TOOMUCH;
		}

		/*
		 * Find the position for the fragment relative to any
		 * previously received.
		 */
		for (f = 0;
		     f < numfrags && offsets[f] < offset;
		     f++) {
			/* empty body */ ;
		}

		if (f < numfrags && offset == offsets[f]) {
			TRACE(1, ("duplicate %u octets at %u ignored, prior %u at %u\n",
				  count, offset, counts[f], offsets[f]));
			continue;
		}

		if (f > 0 && (offsets[f-1] + counts[f-1]) > offset) {
			TRACE(1, ("received frag at %u overlaps with %u octet frag at %u\n",
				  offset, counts[f-1], offsets[f-1]));
			continue;
		}

		if (f < numfrags && (offset + count) > offsets[f]) {
			TRACE(1, ("received %u octet frag at %u overlaps with frag at %u\n",
				  count, offset, offsets[f]));
			continue;
		}

		for (ff = numfrags; ff > f; ff--) {
			offsets[ff] = offsets[ff-1];
			counts[ff] = counts[ff-1];
		}
		offsets[f] = offset;
		counts[f] = count;
		numfrags++;

		/*
		 * Got that stuffed in right.  Figure out if this was the last.
		 * Record status info out of the last packet.
		 */
		if (!CTL_ISMORE(rpkt.r_m_e_op)) {
			seenlastfrag = 1;
			if (rstatus != 0)
				*rstatus = ntohs(rpkt.status);
		}

		/*
		 * Copy the data into the data buffer, and bump the
		 * timout base in case we need more.
		 */
		memcpy((char *)pktdata + offset, &rpkt.u, count);
		tobase = (uint32_t)time(NULL);

		/*
		 * If we've seen the last fragment, look for holes in the sequence.
		 * If there aren't any, we're done.
		 */
#if !defined(SYS_WINNT) && defined(EINTR)
		maybe_final:
#endif

		if (seenlastfrag && offsets[0] == 0) {
			for (f = 1; f < numfrags; f++)
				if (offsets[f-1] + counts[f-1] !=
				    offsets[f])
					break;
			if (f == numfrags) {
				*rsize = offsets[f-1] + counts[f-1];
				TRACE(1, ("%lu packets reassembled into response\n",
					  (u_long)numfrags));
				return 0;
			}
		}
	}  /* giant for (;;) collecting response packets */
}  /* getresponse() */


/*
 * sendrequest - format and send a request packet
 */
static int
sendrequest(
	int opcode,
	associd_t associd,
	int auth,
	size_t qsize,
	const char *qdata
	)
{
	struct ntp_control qpkt;
	size_t	pktsize;
	u_long	key_id;
	char *	pass;
	size_t	maclen;

	/*
	 * Check to make sure the data will fit in one packet
	 */
	if (qsize > CTL_MAX_DATA_LEN) {
		fprintf(stderr,
			"***Internal error!  qsize (%zu) too large\n",
			qsize);
		return 1;
	}

	/*
	 * Fill in the packet
	 */
	qpkt.li_vn_mode = PKT_LI_VN_MODE(0, pktversion, MODE_CONTROL);
	qpkt.r_m_e_op = (u_char)(opcode & CTL_OP_MASK);
	qpkt.sequence = htons(sequence);
	qpkt.status = 0;
	qpkt.associd = htons((u_short)associd);
	qpkt.offset = 0;
	qpkt.count = htons((u_short)qsize);

	pktsize = CTL_HEADER_LEN;

	/*
	 * If we have data, copy and pad it out to a 32-bit boundary.
	 */
	if (qsize > 0) {
		memcpy(&qpkt.u, qdata, (size_t)qsize);
		pktsize += qsize;
		while (pktsize & (sizeof(u_int32) - 1)) {
			qpkt.u.data[qsize++] = 0;
			pktsize++;
		}
	}

	/*
	 * If it isn't authenticated we can just send it.  Otherwise
	 * we're going to have to think about it a little.
	 */
	if (!auth && !always_auth) {
		return sendpkt(&qpkt, pktsize);
	}

	/*
	 * Pad out packet to a multiple of 8 octets to be sure
	 * receiver can handle it.
	 */
	while (pktsize & 7) {
		qpkt.u.data[qsize++] = 0;
		pktsize++;
	}

	/*
	 * Get the keyid and the password if we don't have one.
	 */
	if (info_auth_keyid == 0) {
		key_id = getkeyid("Keyid: ");
		if (key_id == 0 || key_id > NTP_MAXKEY) {
			fprintf(stderr,
				"Invalid key identifier\n");
			return 1;
		}
		info_auth_keyid = key_id;
	}
	if (!authistrusted(info_auth_keyid)) {
		pass = getpass_keytype(info_auth_keytype);
		if ('\0' == pass[0]) {
			fprintf(stderr, "Invalid password\n");
			return 1;
		}
		authusekey(info_auth_keyid, info_auth_keytype,
			   (u_char *)pass);
		authtrust(info_auth_keyid, 1);
	}

	/*
	 * Do the encryption.
	 */
	maclen = authencrypt(info_auth_keyid, (void *)&qpkt, pktsize);
	if (!maclen) {
		fprintf(stderr, "Key not found\n");
		return 1;
	} else if ((size_t)maclen != (info_auth_hashlen + sizeof(keyid_t))) {
		fprintf(stderr,
			"%zu octet MAC, %zu expected with %zu octet digest\n",
			maclen, (info_auth_hashlen + sizeof(keyid_t)),
			info_auth_hashlen);
		return 1;
	}

	return sendpkt((char *)&qpkt, pktsize + maclen);
}


/*
 * show_error_msg - display the error text for a mode 6 error response.
 */
void
show_error_msg(
	int		m6resp,
	associd_t	associd
	)
{
	if (numhosts > 1)
		fprintf(stderr, "server=%s ", currenthost);

	switch (m6resp) {

	case CERR_BADFMT:
		fprintf(stderr,
		    "***Server reports a bad format request packet\n");
		break;

	case CERR_PERMISSION:
		fprintf(stderr,
		    "***Server disallowed request (authentication?)\n");
		break;

	case CERR_BADOP:
		fprintf(stderr,
		    "***Server reports a bad opcode in request\n");
		break;

	case CERR_BADASSOC:
		fprintf(stderr,
		    "***Association ID %d unknown to server\n",
		    associd);
		break;

	case CERR_UNKNOWNVAR:
		fprintf(stderr,
		    "***A request variable unknown to the server\n");
		break;

	case CERR_BADVALUE:
		fprintf(stderr,
		    "***Server indicates a request variable was bad\n");
		break;

	case ERR_UNSPEC:
		fprintf(stderr,
		    "***Server returned an unspecified error\n");
		break;

	case ERR_TIMEOUT:
		fprintf(stderr, "***Request timed out\n");
		break;

	case ERR_INCOMPLETE:
		fprintf(stderr,
		    "***Response from server was incomplete\n");
		break;

	case ERR_TOOMUCH:
		fprintf(stderr,
		    "***Buffer size exceeded for returned data\n");
		break;

	default:
		fprintf(stderr,
		    "***Server returns unknown error code %d\n",
		    m6resp);
	}
}

/*
 * doquery - send a request and process the response, displaying
 *	     error messages for any error responses.
 */
int
doquery(
	int opcode,
	associd_t associd,
	int auth,
	size_t qsize,
	const char *qdata,
	u_short *rstatus,
	size_t *rsize,
	const char **rdata
	)
{
	return doqueryex(opcode, associd, auth, qsize, qdata, rstatus,
			 rsize, rdata, FALSE);
}


/*
 * doqueryex - send a request and process the response, optionally
 *	       displaying error messages for any error responses.
 */
int
doqueryex(
	int opcode,
	associd_t associd,
	int auth,
	size_t qsize,
	const char *qdata,
	u_short *rstatus,
	size_t *rsize,
	const char **rdata,
	int quiet
	)
{
	int res;
	int done;

	/*
	 * Check to make sure host is open
	 */
	if (!havehost) {
		fprintf(stderr, "***No host open, use `host' command\n");
		return -1;
	}

	done = 0;
	sequence++;

    again:
	/*
	 * send a request
	 */
	res = sendrequest(opcode, associd, auth, qsize, qdata);
	if (res != 0)
		return res;

	/*
	 * Get the response.  If we got a standard error, print a message
	 */
	res = getresponse(opcode, associd, rstatus, rsize, rdata, done);

	if (res > 0) {
		if (!done && (res == ERR_TIMEOUT || res == ERR_INCOMPLETE)) {
			if (res == ERR_INCOMPLETE) {
				/*
				 * better bump the sequence so we don't
				 * get confused about differing fragments.
				 */
				sequence++;
			}
			done = 1;
			goto again;
		}
		if (!quiet)
			show_error_msg(res, associd);

	}
	return res;
}


#ifndef BUILD_AS_LIB
/*
 * getcmds - read commands from the standard input and execute them
 */
static void
getcmds(void)
{
	char *	line;
	int	count;

	ntp_readline_init(interactive ? prompt : NULL);

	for (;;) {
		line = ntp_readline(&count);
		if (NULL == line)
			break;
		docmd(line);
		free(line);
	}

	ntp_readline_uninit();
}
#endif /* !BUILD_AS_LIB */


#if !defined(SYS_WINNT) && !defined(BUILD_AS_LIB)
/*
 * abortcmd - catch interrupts and abort the current command
 */
static int
abortcmd(void)
{
	if (current_output == stdout)
		(void) fflush(stdout);
	putc('\n', stderr);
	(void) fflush(stderr);
	if (jump) {
		jump = 0;
		LONGJMP(interrupt_buf, 1);
	}
	return TRUE;
}
#endif	/* !SYS_WINNT && !BUILD_AS_LIB */


#ifndef	BUILD_AS_LIB
/*
 * docmd - decode the command line and execute a command
 */
static void
docmd(
	const char *cmdline
	)
{
	char *tokens[1+MAXARGS+2];
	struct parse pcmd;
	int ntok;
	static int i;
	struct xcmd *xcmd;
	int executeonly = 0;

	/*
	 * Tokenize the command line.  If nothing on it, return.
	 */
	tokenize(cmdline, tokens, &ntok);
	if (ntok == 0)
	    return;

	/*
	 * If command prefixed by '~', then quiet output
	 */
	if (*tokens[0] == '~') {
		executeonly++;
		tokens[0]++;
	}

	/*
	 * Find the appropriate command description.
	 */
	i = findcmd(tokens[0], builtins, opcmds, &xcmd);
	if (i == 0) {
		(void) fprintf(stderr, "***Command `%s' unknown\n",
			       tokens[0]);
		return;
	} else if (i >= 2) {
		(void) fprintf(stderr, "***Command `%s' ambiguous\n",
			       tokens[0]);
		return;
	}

	/* Warn about ignored extra args */
	for (i = MAXARGS + 1; i < ntok ; ++i) {
		fprintf(stderr, "***Extra arg `%s' ignored\n", tokens[i]);
	}

	/*
	 * Save the keyword, then walk through the arguments, interpreting
	 * as we go.
	 */
	pcmd.keyword = tokens[0];
	pcmd.nargs = 0;
	for (i = 0; i < MAXARGS && xcmd->arg[i] != NO; i++) {
		if ((i+1) >= ntok) {
			if (!(xcmd->arg[i] & OPT)) {
				printusage(xcmd, stderr);
				return;
			}
			break;
		}
		if ((xcmd->arg[i] & OPT) && (*tokens[i+1] == '>'))
			break;
		if (!getarg(tokens[i+1], (int)xcmd->arg[i], &pcmd.argval[i]))
			return;
		pcmd.nargs++;
	}

	i++;
	if (i < ntok && *tokens[i] == '>') {
		char *fname;

		if (*(tokens[i]+1) != '\0')
			fname = tokens[i]+1;
		else if ((i+1) < ntok)
			fname = tokens[i+1];
		else {
			(void) fprintf(stderr, "***No file for redirect\n");
			return;
		}

		current_output = fopen(fname, "w");
		if (current_output == NULL) {
			(void) fprintf(stderr, "***Error opening %s: ", fname);
			perror("");
			return;
		}
	} else if (executeonly) {		/* Redirect all output to null */
		current_output = fopen(PATH_DEVNULL, "w");
		if (current_output == NULL) {
			(void) fprintf(stderr, "***Error redirecting output to /dev/null: ");
			perror("");
			return;
		}
	} else {
		current_output = stdout;
	}

	if (interactive) {
		if ( ! SETJMP(interrupt_buf)) {
			jump = 1;
			(xcmd->handler)(&pcmd, current_output);
			jump = 0;
		} else {
			fflush(current_output);
			fputs("\n >>> command aborted <<<\n", stderr);
			fflush(stderr);
		}

	} else {
		jump = 0;
		(xcmd->handler)(&pcmd, current_output);
	}
	if ((NULL != current_output) && (stdout != current_output)) {
		(void)fclose(current_output);
		current_output = NULL;
	}
}


/*
 * tokenize - turn a command line into tokens
 *
 * SK: Modified to allow a quoted string
 *
 * HMS: If the first character of the first token is a ':' then (after
 * eating inter-token whitespace) the 2nd token is the rest of the line.
 */

static void
tokenize(
	const char *line,
	char **tokens,
	int *ntok
	)
{
	register const char *cp;
	register char *sp;
	static char tspace[MAXLINE];

	sp = tspace;
	cp = line;
	for (*ntok = 0; *ntok < MAXTOKENS; (*ntok)++) {
		tokens[*ntok] = sp;

		/* Skip inter-token whitespace */
		while (ISSPACE(*cp))
		    cp++;

		/* If we're at EOL we're done */
		if (ISEOL(*cp))
		    break;

		/* If this is the 2nd token and the first token begins
		 * with a ':', then just grab to EOL.
		 */

		if (*ntok == 1 && tokens[0][0] == ':') {
			do {
				if (sp - tspace >= MAXLINE)
					goto toobig;
				*sp++ = *cp++;
			} while (!ISEOL(*cp));
		}

		/* Check if this token begins with a double quote.
		 * If yes, continue reading till the next double quote
		 */
		else if (*cp == '\"') {
			++cp;
			do {
				if (sp - tspace >= MAXLINE)
					goto toobig;
				*sp++ = *cp++;
			} while ((*cp != '\"') && !ISEOL(*cp));
			/* HMS: a missing closing " should be an error */
		}
		else {
			do {
				if (sp - tspace >= MAXLINE)
					goto toobig;
				*sp++ = *cp++;
			} while ((*cp != '\"') && !ISSPACE(*cp) && !ISEOL(*cp));
			/* HMS: Why check for a " in the previous line? */
		}

		if (sp - tspace >= MAXLINE)
			goto toobig;
		*sp++ = '\0';
	}
	return;

  toobig:
	*ntok = 0;
	fprintf(stderr,
		"***Line `%s' is too big\n",
		line);
	return;
}


/*
 * getarg - interpret an argument token
 */
static int
getarg(
	const char *str,
	int code,
	arg_v *argp
	)
{
	u_long ul;

	switch (code & ~OPT) {
	case NTP_STR:
		argp->string = str;
		break;

	case NTP_ADD:
		if (!getnetnum(str, &argp->netnum, NULL, 0))
			return 0;
		break;

	case NTP_UINT:
		if ('&' == str[0]) {
			if (!atouint(&str[1], &ul)) {
				fprintf(stderr,
					"***Association index `%s' invalid/undecodable\n",
					str);
				return 0;
			}
			if (0 == numassoc) {
				dogetassoc(stdout);
				if (0 == numassoc) {
					fprintf(stderr,
						"***No associations found, `%s' unknown\n",
						str);
					return 0;
				}
			}
			ul = min(ul, numassoc);
			argp->uval = assoc_cache[ul - 1].assid;
			break;
		}
		if (!atouint(str, &argp->uval)) {
			fprintf(stderr, "***Illegal unsigned value %s\n",
				str);
			return 0;
		}
		break;

	case NTP_INT:
		if (!atoint(str, &argp->ival)) {
			fprintf(stderr, "***Illegal integer value %s\n",
				str);
			return 0;
		}
		break;

	case IP_VERSION:
		if (!strcmp("-6", str)) {
			argp->ival = 6;
		} else if (!strcmp("-4", str)) {
			argp->ival = 4;
		} else {
			fprintf(stderr, "***Version must be either 4 or 6\n");
			return 0;
		}
		break;
	}

	return 1;
}
#endif	/* !BUILD_AS_LIB */


/*
 * findcmd - find a command in a command description table
 */
static int
findcmd(
	const char *	str,
	struct xcmd *	clist1,
	struct xcmd *	clist2,
	struct xcmd **	cmd
	)
{
	struct xcmd *cl;
	size_t clen;
	int nmatch;
	struct xcmd *nearmatch = NULL;
	struct xcmd *clist;

	clen = strlen(str);
	nmatch = 0;
	if (clist1 != 0)
	    clist = clist1;
	else if (clist2 != 0)
	    clist = clist2;
	else
	    return 0;

    again:
	for (cl = clist; cl->keyword != 0; cl++) {
		/* do a first character check, for efficiency */
		if (*str != *(cl->keyword))
		    continue;
		if (strncmp(str, cl->keyword, (unsigned)clen) == 0) {
			/*
			 * Could be extact match, could be approximate.
			 * Is exact if the length of the keyword is the
			 * same as the str.
			 */
			if (*((cl->keyword) + clen) == '\0') {
				*cmd = cl;
				return 1;
			}
			nmatch++;
			nearmatch = cl;
		}
	}

	/*
	 * See if there is more to do.  If so, go again.  Sorry about the
	 * goto, too much looking at BSD sources...
	 */
	if (clist == clist1 && clist2 != 0) {
		clist = clist2;
		goto again;
	}

	/*
	 * If we got extactly 1 near match, use it, else return number
	 * of matches.
	 */
	if (nmatch == 1) {
		*cmd = nearmatch;
		return 1;
	}
	return nmatch;
}


/*
 * getnetnum - given a host name, return its net number
 *	       and (optional) full name
 */
int
getnetnum(
	const char *hname,
	sockaddr_u *num,
	char *fullhost,
	int af
	)
{
	struct addrinfo hints, *ai = NULL;

	ZERO(hints);
	hints.ai_flags = AI_CANONNAME;
#ifdef AI_ADDRCONFIG
	hints.ai_flags |= AI_ADDRCONFIG;
#endif

	/*
	 * decodenetnum only works with addresses, but handles syntax
	 * that getaddrinfo doesn't:  [2001::1]:1234
	 */
	if (decodenetnum(hname, num)) {
		if (fullhost != NULL)
			getnameinfo(&num->sa, SOCKLEN(num), fullhost,
				    LENHOSTNAME, NULL, 0, 0);
		return 1;
	} else if (getaddrinfo(hname, "ntp", &hints, &ai) == 0) {
		INSIST(sizeof(*num) >= ai->ai_addrlen);
		memcpy(num, ai->ai_addr, ai->ai_addrlen);
		if (fullhost != NULL) {
			if (ai->ai_canonname != NULL)
				strlcpy(fullhost, ai->ai_canonname,
					LENHOSTNAME);
			else
				getnameinfo(&num->sa, SOCKLEN(num),
					    fullhost, LENHOSTNAME, NULL,
					    0, 0);
		}
		freeaddrinfo(ai);
		return 1;
	}
	fprintf(stderr, "***Can't find host %s\n", hname);

	return 0;
}


/*
 * nntohost - convert network number to host name.  This routine enforces
 *	       the showhostnames setting.
 */
const char *
nntohost(
	sockaddr_u *netnum
	)
{
	return nntohost_col(netnum, LIB_BUFLENGTH - 1, FALSE);
}


/*
 * nntohost_col - convert network number to host name in fixed width.
 *		  This routine enforces the showhostnames setting.
 *		  When displaying hostnames longer than the width,
 *		  the first part of the hostname is displayed.  When
 *		  displaying numeric addresses longer than the width,
 *		  Such as IPv6 addresses, the caller decides whether
 *		  the first or last of the numeric address is used.
 */
const char *
nntohost_col(
	sockaddr_u *	addr,
	size_t		width,
	int		preserve_lowaddrbits
	)
{
	const char *	out;

	if (!showhostnames || SOCK_UNSPEC(addr)) {
		if (preserve_lowaddrbits)
			out = trunc_left(stoa(addr), width);
		else
			out = trunc_right(stoa(addr), width);
	} else if (ISREFCLOCKADR(addr)) {
		out = refnumtoa(addr);
	} else {
		out = trunc_right(socktohost(addr), width);
	}
	return out;
}


/*
 * nntohostp() is the same as nntohost() plus a :port suffix
 */
const char *
nntohostp(
	sockaddr_u *netnum
	)
{
	const char *	hostn;
	char *		buf;

	if (!showhostnames || SOCK_UNSPEC(netnum))
		return sptoa(netnum);
	else if (ISREFCLOCKADR(netnum))
		return refnumtoa(netnum);

	hostn = socktohost(netnum);
	LIB_GETBUF(buf);
	snprintf(buf, LIB_BUFLENGTH, "%s:%u", hostn, SRCPORT(netnum));

	return buf;
}

/*
 * rtdatetolfp - decode an RT-11 date into an l_fp
 */
static int
rtdatetolfp(
	char *str,
	l_fp *lfp
	)
{
	register char *cp;
	register int i;
	struct calendar cal;
	char buf[4];

	cal.yearday = 0;

	/*
	 * An RT-11 date looks like:
	 *
	 * d[d]-Mth-y[y] hh:mm:ss
	 *
	 * (No docs, but assume 4-digit years are also legal...)
	 *
	 * d[d]-Mth-y[y[y[y]]] hh:mm:ss
	 */
	cp = str;
	if (!isdigit(pgetc(cp))) {
		if (*cp == '-') {
			/*
			 * Catch special case
			 */
			L_CLR(lfp);
			return 1;
		}
		return 0;
	}

	cal.monthday = (u_char) (*cp++ - '0');	/* ascii dependent */
	if (isdigit(pgetc(cp))) {
		cal.monthday = (u_char)((cal.monthday << 3) + (cal.monthday << 1));
		cal.monthday = (u_char)(cal.monthday + *cp++ - '0');
	}

	if (*cp++ != '-')
	    return 0;

	for (i = 0; i < 3; i++)
	    buf[i] = *cp++;
	buf[3] = '\0';

	for (i = 0; i < 12; i++)
	    if (STREQ(buf, months[i]))
		break;
	if (i == 12)
	    return 0;
	cal.month = (u_char)(i + 1);

	if (*cp++ != '-')
	    return 0;

	if (!isdigit(pgetc(cp)))
	    return 0;
	cal.year = (u_short)(*cp++ - '0');
	if (isdigit(pgetc(cp))) {
		cal.year = (u_short)((cal.year << 3) + (cal.year << 1));
		cal.year = (u_short)(*cp++ - '0');
	}
	if (isdigit(pgetc(cp))) {
		cal.year = (u_short)((cal.year << 3) + (cal.year << 1));
		cal.year = (u_short)(cal.year + *cp++ - '0');
	}
	if (isdigit(pgetc(cp))) {
		cal.year = (u_short)((cal.year << 3) + (cal.year << 1));
		cal.year = (u_short)(cal.year + *cp++ - '0');
	}

	/*
	 * Catch special case.  If cal.year == 0 this is a zero timestamp.
	 */
	if (cal.year == 0) {
		L_CLR(lfp);
		return 1;
	}

	if (*cp++ != ' ' || !isdigit(pgetc(cp)))
	    return 0;
	cal.hour = (u_char)(*cp++ - '0');
	if (isdigit(pgetc(cp))) {
		cal.hour = (u_char)((cal.hour << 3) + (cal.hour << 1));
		cal.hour = (u_char)(cal.hour + *cp++ - '0');
	}

	if (*cp++ != ':' || !isdigit(pgetc(cp)))
	    return 0;
	cal.minute = (u_char)(*cp++ - '0');
	if (isdigit(pgetc(cp))) {
		cal.minute = (u_char)((cal.minute << 3) + (cal.minute << 1));
		cal.minute = (u_char)(cal.minute + *cp++ - '0');
	}

	if (*cp++ != ':' || !isdigit(pgetc(cp)))
	    return 0;
	cal.second = (u_char)(*cp++ - '0');
	if (isdigit(pgetc(cp))) {
		cal.second = (u_char)((cal.second << 3) + (cal.second << 1));
		cal.second = (u_char)(cal.second + *cp++ - '0');
	}

	/*
	 * For RT-11, 1972 seems to be the pivot year
	 */
	if (cal.year < 72)
		cal.year += 2000;
	if (cal.year < 100)
		cal.year += 1900;

	/* check for complaints from 'caltontp()'! */
	lfp->l_uf = 0;
	errno = 0;
	lfp->l_ui = caltontp(&cal);
	return (errno == 0);
}


/*
 * decodets - decode a timestamp into an l_fp format number, with
 *	      consideration of fuzzball formats.
 */
int
decodets(
	char *str,
	l_fp *lfp
	)
{
	char *cp;
	char buf[30];
	size_t b;

	/*
	 * If it starts with a 0x, decode as hex.
	 */
	if (*str == '0' && (*(str+1) == 'x' || *(str+1) == 'X'))
		return hextolfp(str+2, lfp);

	/*
	 * If it starts with a '"', try it as an RT-11 date.
	 */
	if (*str == '"') {
		cp = str + 1;
		b = 0;
		while ('"' != *cp && '\0' != *cp &&
		       b < COUNTOF(buf) - 1)
			buf[b++] = *cp++;
		buf[b] = '\0';
		return rtdatetolfp(buf, lfp);
	}

	/*
	 * Might still be hex.  Check out the first character.  Talk
	 * about heuristics!
	 */
	if ((*str >= 'A' && *str <= 'F') || (*str >= 'a' && *str <= 'f'))
		return hextolfp(str, lfp);

	/*
	 * Try it as a decimal.  If this fails, try as an unquoted
	 * RT-11 date.  This code should go away eventually.
	 */
	if (atolfp(str, lfp))
		return 1;

	return rtdatetolfp(str, lfp);
}


/*
 * decodetime - decode a time value.  It should be in milliseconds
 */
int
decodetime(
	char *str,
	l_fp *lfp
	)
{
	return mstolfp(str, lfp);
}


/*
 * decodeint - decode an integer
 */
int
decodeint(
	char *str,
	long *val
	)
{
	if (*str == '0') {
		if (*(str+1) == 'x' || *(str+1) == 'X')
		    return hextoint(str+2, (u_long *)val);
		return octtoint(str, (u_long *)val);
	}
	return atoint(str, val);
}


/*
 * decodeuint - decode an unsigned integer
 */
int
decodeuint(
	char *str,
	u_long *val
	)
{
	if (*str == '0') {
		if (*(str + 1) == 'x' || *(str + 1) == 'X')
			return (hextoint(str + 2, val));
		return (octtoint(str, val));
	}
	return (atouint(str, val));
}


/*
 * decodearr - decode an array of time values
 */
static int
decodearr(
	char *cp,
	int  *narr,
	l_fp *lfpa,
	int   amax
	)
{
	char *bp;
	char buf[60];

	*narr = 0;

	while (*narr < amax && *cp) {
		if (isspace(pgetc(cp))) {
			do
				++cp;
			while (*cp && isspace(pgetc(cp)));
		} else {
			bp = buf;
			do {
				if (bp != (buf + sizeof(buf) - 1))
					*bp++ = *cp;
				++cp;
			} while (*cp && !isspace(pgetc(cp)));
			*bp = '\0';

			if (!decodetime(buf, lfpa))
				return 0;
			++(*narr);
			++lfpa;
		}
	}
	return 1;
}


/*
 * Finally, the built in command handlers
 */

/*
 * help - tell about commands, or details of a particular command
 */
static void
help(
	struct parse *pcmd,
	FILE *fp
	)
{
	struct xcmd *xcp = NULL;	/* quiet warning */
	const char *cmd;
	const char *list[100];
	size_t word, words;
	size_t row, rows;
	size_t col, cols;
	size_t length;

	if (pcmd->nargs == 0) {
		words = 0;
		for (xcp = builtins; xcp->keyword != NULL; xcp++) {
			if (*(xcp->keyword) != '?' &&
			    words < COUNTOF(list))
				list[words++] = xcp->keyword;
		}
		for (xcp = opcmds; xcp->keyword != NULL; xcp++)
			if (words < COUNTOF(list))
				list[words++] = xcp->keyword;

		qsort((void *)list, words, sizeof(list[0]), helpsort);
		col = 0;
		for (word = 0; word < words; word++) {
			length = strlen(list[word]);
			col = max(col, length);
		}

		cols = SCREENWIDTH / ++col;
		rows = (words + cols - 1) / cols;

		fprintf(fp, "ntpq commands:\n");

		for (row = 0; row < rows; row++) {
			for (word = row; word < words; word += rows)
				fprintf(fp, "%-*.*s", (int)col,
					(int)col - 1, list[word]);
			fprintf(fp, "\n");
		}
	} else {
		cmd = pcmd->argval[0].string;
		words = findcmd(cmd, builtins, opcmds, &xcp);
		if (words == 0) {
			fprintf(stderr,
				"Command `%s' is unknown\n", cmd);
			return;
		} else if (words >= 2) {
			fprintf(stderr,
				"Command `%s' is ambiguous\n", cmd);
			return;
		}
		fprintf(fp, "function: %s\n", xcp->comment);
		printusage(xcp, fp);
	}
}


/*
 * helpsort - do hostname qsort comparisons
 */
static int
helpsort(
	const void *t1,
	const void *t2
	)
{
	const char * const *	name1 = t1;
	const char * const *	name2 = t2;

	return strcmp(*name1, *name2);
}


/*
 * printusage - print usage information for a command
 */
static void
printusage(
	struct xcmd *xcp,
	FILE *fp
	)
{
	register int i;

	/* XXX: Do we need to warn about extra args here too? */

	(void) fprintf(fp, "usage: %s", xcp->keyword);
	for (i = 0; i < MAXARGS && xcp->arg[i] != NO; i++) {
		if (xcp->arg[i] & OPT)
		    (void) fprintf(fp, " [ %s ]", xcp->desc[i]);
		else
		    (void) fprintf(fp, " %s", xcp->desc[i]);
	}
	(void) fprintf(fp, "\n");
}


/*
 * timeout - set time out time
 */
static void
timeout(
	struct parse *pcmd,
	FILE *fp
	)
{
	int val;

	if (pcmd->nargs == 0) {
		val = (int)tvout.tv_sec * 1000 + tvout.tv_usec / 1000;
		(void) fprintf(fp, "primary timeout %d ms\n", val);
	} else {
		tvout.tv_sec = pcmd->argval[0].uval / 1000;
		tvout.tv_usec = (pcmd->argval[0].uval - ((long)tvout.tv_sec * 1000))
			* 1000;
	}
}


/*
 * auth_delay - set delay for auth requests
 */
static void
auth_delay(
	struct parse *pcmd,
	FILE *fp
	)
{
	int isneg;
	u_long val;

	if (pcmd->nargs == 0) {
		val = delay_time.l_ui * 1000 + delay_time.l_uf / 4294967;
		(void) fprintf(fp, "delay %lu ms\n", val);
	} else {
		if (pcmd->argval[0].ival < 0) {
			isneg = 1;
			val = (u_long)(-pcmd->argval[0].ival);
		} else {
			isneg = 0;
			val = (u_long)pcmd->argval[0].ival;
		}

		delay_time.l_ui = val / 1000;
		val %= 1000;
		delay_time.l_uf = val * 4294967;	/* 2**32/1000 */

		if (isneg)
		    L_NEG(&delay_time);
	}
}


/*
 * host - set the host we are dealing with.
 */
static void
host(
	struct parse *pcmd,
	FILE *fp
	)
{
	int i;

	if (pcmd->nargs == 0) {
		if (havehost)
			(void) fprintf(fp, "current host is %s\n",
					   currenthost);
		else
			(void) fprintf(fp, "no current host\n");
		return;
	}

	i = 0;
	ai_fam_templ = ai_fam_default;
	if (pcmd->nargs == 2) {
		if (!strcmp("-4", pcmd->argval[i].string))
			ai_fam_templ = AF_INET;
		else if (!strcmp("-6", pcmd->argval[i].string))
			ai_fam_templ = AF_INET6;
		else
			goto no_change;
		i = 1;
	}
	if (openhost(pcmd->argval[i].string, ai_fam_templ)) {
		fprintf(fp, "current host set to %s\n", currenthost);
	} else {
    no_change:
		if (havehost)
			fprintf(fp, "current host remains %s\n",
				currenthost);
		else
			fprintf(fp, "still no current host\n");
	}
}


/*
 * poll - do one (or more) polls of the host via NTP
 */
/*ARGSUSED*/
static void
ntp_poll(
	struct parse *pcmd,
	FILE *fp
	)
{
	(void) fprintf(fp, "poll not implemented yet\n");
}


/*
 * showdrefid2str - return a string explanation of the value of drefid
 */
static const char *
showdrefid2str(void)
{
	switch (drefid) {
	    case REFID_HASH:
	    	return "hash";
	    case REFID_IPV4:
	    	return "ipv4";
	    default:
	    	return "Unknown";
	}
}


/*
 * drefid - display/change "display hash"
 */
static void
showdrefid(
	struct parse *pcmd,
	FILE *fp
	)
{
	if (pcmd->nargs == 0) {
		(void) fprintf(fp, "drefid value is %s\n", showdrefid2str());
		return;
	} else if (STREQ(pcmd->argval[0].string, "hash")) {
		drefid = REFID_HASH;
	} else if (STREQ(pcmd->argval[0].string, "ipv4")) {
		drefid = REFID_IPV4;
	} else {
		(void) fprintf(fp, "What?\n");
		return;
	}
	(void) fprintf(fp, "drefid value set to %s\n", showdrefid2str());
}


/*
 * keyid - get a keyid to use for authenticating requests
 */
static void
keyid(
	struct parse *pcmd,
	FILE *fp
	)
{
	if (pcmd->nargs == 0) {
		if (info_auth_keyid == 0)
		    (void) fprintf(fp, "no keyid defined\n");
		else
		    (void) fprintf(fp, "keyid is %lu\n", (u_long)info_auth_keyid);
	} else {
		/* allow zero so that keyid can be cleared. */
		if(pcmd->argval[0].uval > NTP_MAXKEY)
		    (void) fprintf(fp, "Invalid key identifier\n");
		info_auth_keyid = pcmd->argval[0].uval;
	}
}

/*
 * keytype - get type of key to use for authenticating requests
 */
static void
keytype(
	struct parse *pcmd,
	FILE *fp
	)
{
	const char *	digest_name;
	size_t		digest_len;
	int		key_type;

	if (!pcmd->nargs) {
		fprintf(fp, "keytype is %s with %lu octet digests\n",
			keytype_name(info_auth_keytype),
			(u_long)info_auth_hashlen);
		return;
	}

	digest_name = pcmd->argval[0].string;
	digest_len = 0;
	key_type = keytype_from_text(digest_name, &digest_len);

	if (!key_type) {
		fprintf(fp, "keytype is not valid. "
#ifdef OPENSSL
			"Type \"help keytype\" for the available digest types.\n");
#else
			"Only \"md5\" is available.\n");
#endif
		return;
	}

	info_auth_keytype = key_type;
	info_auth_hashlen = digest_len;
}


/*
 * passwd - get an authentication key
 */
/*ARGSUSED*/
static void
passwd(
	struct parse *pcmd,
	FILE *fp
	)
{
	const char *pass;

	if (info_auth_keyid == 0) {
		info_auth_keyid = getkeyid("Keyid: ");
		if (info_auth_keyid == 0) {
			(void)fprintf(fp, "Keyid must be defined\n");
			return;
		}
	}
	if (pcmd->nargs >= 1)
		pass = pcmd->argval[0].string;
	else {
		pass = getpass_keytype(info_auth_keytype);
		if ('\0' == pass[0]) {
			fprintf(fp, "Password unchanged\n");
			return;
		}
	}
	authusekey(info_auth_keyid, info_auth_keytype,
		   (const u_char *)pass);
	authtrust(info_auth_keyid, 1);
}


/*
 * hostnames - set the showhostnames flag
 */
static void
hostnames(
	struct parse *pcmd,
	FILE *fp
	)
{
	if (pcmd->nargs == 0) {
		if (showhostnames)
		    (void) fprintf(fp, "hostnames being shown\n");
		else
		    (void) fprintf(fp, "hostnames not being shown\n");
	} else {
		if (STREQ(pcmd->argval[0].string, "yes"))
		    showhostnames = 1;
		else if (STREQ(pcmd->argval[0].string, "no"))
		    showhostnames = 0;
		else
		    (void)fprintf(stderr, "What?\n");
	}
}



/*
 * setdebug - set/change debugging level
 */
static void
setdebug(
	struct parse *pcmd,
	FILE *fp
	)
{
	if (pcmd->nargs == 0) {
		(void) fprintf(fp, "debug level is %d\n", debug);
		return;
	} else if (STREQ(pcmd->argval[0].string, "no")) {
		debug = 0;
	} else if (STREQ(pcmd->argval[0].string, "more")) {
		debug++;
	} else if (STREQ(pcmd->argval[0].string, "less")) {
		debug--;
	} else {
		(void) fprintf(fp, "What?\n");
		return;
	}
	(void) fprintf(fp, "debug level set to %d\n", debug);
}


/*
 * quit - stop this nonsense
 */
/*ARGSUSED*/
static void
quit(
	struct parse *pcmd,
	FILE *fp
	)
{
	if (havehost)
	    closesocket(sockfd);	/* cleanliness next to godliness */
	exit(0);
}


/*
 * version - print the current version number
 */
/*ARGSUSED*/
static void
version(
	struct parse *pcmd,
	FILE *fp
	)
{

	(void) fprintf(fp, "%s\n", Version);
	return;
}


/*
 * raw - set raw mode output
 */
/*ARGSUSED*/
static void
raw(
	struct parse *pcmd,
	FILE *fp
	)
{
	rawmode = 1;
	(void) fprintf(fp, "Output set to raw\n");
}


/*
 * cooked - set cooked mode output
 */
/*ARGSUSED*/
static void
cooked(
	struct parse *pcmd,
	FILE *fp
	)
{
	rawmode = 0;
	(void) fprintf(fp, "Output set to cooked\n");
	return;
}


/*
 * authenticate - always authenticate requests to this host
 */
static void
authenticate(
	struct parse *pcmd,
	FILE *fp
	)
{
	if (pcmd->nargs == 0) {
		if (always_auth) {
			(void) fprintf(fp,
				       "authenticated requests being sent\n");
		} else
		    (void) fprintf(fp,
				   "unauthenticated requests being sent\n");
	} else {
		if (STREQ(pcmd->argval[0].string, "yes")) {
			always_auth = 1;
		} else if (STREQ(pcmd->argval[0].string, "no")) {
			always_auth = 0;
		} else
		    (void)fprintf(stderr, "What?\n");
	}
}


/*
 * ntpversion - choose the NTP version to use
 */
static void
ntpversion(
	struct parse *pcmd,
	FILE *fp
	)
{
	if (pcmd->nargs == 0) {
		(void) fprintf(fp,
			       "NTP version being claimed is %d\n", pktversion);
	} else {
		if (pcmd->argval[0].uval < NTP_OLDVERSION
		    || pcmd->argval[0].uval > NTP_VERSION) {
			(void) fprintf(stderr, "versions %d to %d, please\n",
				       NTP_OLDVERSION, NTP_VERSION);
		} else {
			pktversion = (u_char) pcmd->argval[0].uval;
		}
	}
}


static void __attribute__((__format__(__printf__, 1, 0)))
vwarning(const char *fmt, va_list ap)
{
	int serrno = errno;
	(void) fprintf(stderr, "%s: ", progname);
	vfprintf(stderr, fmt, ap);
	(void) fprintf(stderr, ": %s\n", strerror(serrno));
}

/*
 * warning - print a warning message
 */
static void __attribute__((__format__(__printf__, 1, 2)))
warning(
	const char *fmt,
	...
	)
{
	va_list ap;
	va_start(ap, fmt);
	vwarning(fmt, ap);
	va_end(ap);
}


/*
 * error - print a message and exit
 */
static void __attribute__((__format__(__printf__, 1, 2)))
error(
	const char *fmt,
	...
	)
{
	va_list ap;
	va_start(ap, fmt);
	vwarning(fmt, ap);
	va_end(ap);
	exit(1);
}
/*
 * getkeyid - prompt the user for a keyid to use
 */
static u_long
getkeyid(
	const char *keyprompt
	)
{
	int c;
	FILE *fi;
	char pbuf[20];
	size_t i;
	size_t ilim;

#ifndef SYS_WINNT
	if ((fi = fdopen(open("/dev/tty", 2), "r")) == NULL)
#else
	if ((fi = _fdopen(open("CONIN$", _O_TEXT), "r")) == NULL)
#endif /* SYS_WINNT */
		fi = stdin;
	else
		setbuf(fi, (char *)NULL);
	fprintf(stderr, "%s", keyprompt); fflush(stderr);
	for (i = 0, ilim = COUNTOF(pbuf) - 1;
	     i < ilim && (c = getc(fi)) != '\n' && c != EOF;
	     )
		pbuf[i++] = (char)c;
	pbuf[i] = '\0';
	if (fi != stdin)
		fclose(fi);

	return (u_long) atoi(pbuf);
}


/*
 * atoascii - printable-ize possibly ascii data using the character
 *	      transformations cat -v uses.
 */
static void
atoascii(
	const char *in,
	size_t in_octets,
	char *out,
	size_t out_octets
	)
{
	const u_char *	pchIn;
	const u_char *	pchInLimit;
	u_char *	pchOut;
	u_char		c;

	pchIn = (const u_char *)in;
	pchInLimit = pchIn + in_octets;
	pchOut = (u_char *)out;

	if (NULL == pchIn) {
		if (0 < out_octets)
			*pchOut = '\0';
		return;
	}

#define	ONEOUT(c)					\
do {							\
	if (0 == --out_octets) {			\
		*pchOut = '\0';				\
		return;					\
	}						\
	*pchOut++ = (c);				\
} while (0)

	for (	; pchIn < pchInLimit; pchIn++) {
		c = *pchIn;
		if ('\0' == c)
			break;
		if (c & 0x80) {
			ONEOUT('M');
			ONEOUT('-');
			c &= 0x7f;
		}
		if (c < ' ') {
			ONEOUT('^');
			ONEOUT((u_char)(c + '@'));
		} else if (0x7f == c) {
			ONEOUT('^');
			ONEOUT('?');
		} else
			ONEOUT(c);
	}
	ONEOUT('\0');

#undef ONEOUT
}


/*
 * makeascii - print possibly ascii data using the character
 *	       transformations that cat -v uses.
 */
void
makeascii(
	size_t length,
	const char *data,
	FILE *fp
	)
{
	const u_char *data_u_char;
	const u_char *cp;
	int c;

	data_u_char = (const u_char *)data;

	for (cp = data_u_char; cp < data_u_char + length; cp++) {
		c = (int)*cp;
		if (c & 0x80) {
			putc('M', fp);
			putc('-', fp);
			c &= 0x7f;
		}

		if (c < ' ') {
			putc('^', fp);
			putc(c + '@', fp);
		} else if (0x7f == c) {
			putc('^', fp);
			putc('?', fp);
		} else
			putc(c, fp);
	}
}


/*
 * asciize - same thing as makeascii except add a newline
 */
void
asciize(
	int length,
	char *data,
	FILE *fp
	)
{
	makeascii(length, data, fp);
	putc('\n', fp);
}


/*
 * truncate string to fit clipping excess at end.
 *	"too long"	->	"too l"
 * Used for hostnames.
 */
const char *
trunc_right(
	const char *	src,
	size_t		width
	)
{
	size_t	sl;
	char *	out;


	sl = strlen(src);
	if (sl > width && LIB_BUFLENGTH - 1 > width && width > 0) {
		LIB_GETBUF(out);
		memcpy(out, src, width);
		out[width] = '\0';

		return out;
	}

	return src;
}


/*
 * truncate string to fit by preserving right side and using '_' to hint
 *	"too long"	->	"_long"
 * Used for local IPv6 addresses, where low bits differentiate.
 */
const char *
trunc_left(
	const char *	src,
	size_t		width
	)
{
	size_t	sl;
	char *	out;


	sl = strlen(src);
	if (sl > width && LIB_BUFLENGTH - 1 > width && width > 1) {
		LIB_GETBUF(out);
		out[0] = '_';
		memcpy(&out[1], &src[sl + 1 - width], width);

		return out;
	}

	return src;
}


/*
 * Some circular buffer space
 */
#define	CBLEN	80
#define	NUMCB	6

char circ_buf[NUMCB][CBLEN];
int nextcb = 0;

/* --------------------------------------------------------------------
 * Parsing a response value list
 *
 * This sounds simple (and it actually is not really hard) but it has
 * some pitfalls.
 *
 * Rule1: CR/LF is never embedded in an item
 * Rule2: An item is a name, optionally followed by a value
 * Rule3: The value is separated from the name by a '='
 * Rule4: Items are separated by a ','
 * Rule5: values can be quoted by '"', in which case they can contain
 *        arbitrary characters but *not* '"', CR and LF
 *
 * There are a few implementations out there that require a somewhat
 * relaxed attitude when parsing a value list, especially since we want
 * to copy names and values into local buffers. If these would overflow,
 * the item should be skipped without terminating the parsing sequence.
 *
 * Also, for empty values, there might be a '=' after the name or not;
 * we treat that equivalent.
 *
 * Parsing an item definitely breaks on a CR/LF. If an item is not
 * followed by a comma (','), parsing stops. In the middle of a quoted
 * character sequence CR/LF terminates the parsing finally without
 * returning a value.
 *
 * White space and other noise is ignored when parsing the data buffer;
 * only CR, LF, ',', '=' and '"' are characters with a special meaning.
 * White space is stripped from the names and values *after* working
 * through the buffer, before making the local copies. If whitespace
 * stripping results in an empty name, parsing resumes.
 */

/*
 * nextvar parsing helpers
 */

/* predicate: allowed chars inside a quoted string */
static int/*BOOL*/ cp_qschar(int ch)
{
	return ch && (ch != '"' && ch != '\r' && ch != '\n');
}

/* predicate: allowed chars inside an unquoted string */
static int/*BOOL*/ cp_uqchar(int ch)
{
	return ch && (ch != ',' && ch != '"' && ch != '\r' && ch != '\n');
}

/* predicate: allowed chars inside a value name */
static int/*BOOL*/ cp_namechar(int ch)
{
	return ch && (ch != ',' && ch != '=' && ch != '\r' && ch != '\n');
}

/* predicate: characters *between* list items. We're relaxed here. */
static int/*BOOL*/ cp_ivspace(int ch)
{
	return (ch == ',' || (ch > 0 && ch <= ' '));
}

/* get current character (or NUL when on end) */
static inline int
pf_getch(
	const char **	datap,
	const char *	endp
	)
{
	return (*datap != endp)
	    ? *(const unsigned char*)*datap
	    : '\0';
}

/* get next character (or NUL when on end) */
static inline int
pf_nextch(
	const char **	datap,
	const char *	endp
	)
{
	return (*datap != endp && ++(*datap) != endp)
	    ? *(const unsigned char*)*datap
	    : '\0';
}

static size_t
str_strip(
	const char ** 	datap,
	size_t		len
	)
{
	static const char empty[] = "";

	if (*datap && len) {
		const char * cpl = *datap;
		const char * cpr = cpl + len;

		while (cpl != cpr && *(const unsigned char*)cpl <= ' ')
			++cpl;
		while (cpl != cpr && *(const unsigned char*)(cpr - 1) <= ' ')
			--cpr;
		*datap = cpl;
		len = (size_t)(cpr - cpl);
	} else {
		*datap = empty;
		len = 0;
	}
	return len;
}

static void
pf_error(
	const char *	what,
	const char *	where,
	const char *	whend
	)
{
#   ifndef BUILD_AS_LIB

	FILE *	ofp = (debug > 0) ? stdout : stderr;
	size_t	len = (size_t)(whend - where);

	if (len > 50) /* *must* fit into an 'int'! */
		len = 50;
	fprintf(ofp, "nextvar: %s: '%.*s'\n",
		what, (int)len, where);

#   else  /*defined(BUILD_AS_LIB)*/

	UNUSED_ARG(what);
	UNUSED_ARG(where);
	UNUSED_ARG(whend);

#   endif /*defined(BUILD_AS_LIB)*/
}

/*
 * nextvar - find the next variable in the buffer
 */
int/*BOOL*/
nextvar(
	size_t *datalen,
	const char **datap,
	char **vname,
	char **vvalue
	)
{
	enum PState 	{ sDone, sInit, sName, sValU, sValQ };

	static char	name[MAXVARLEN], value[MAXVALLEN];

	const char	*cp, *cpend;
	const char	*np, *vp;
	size_t		nlen, vlen;
	int		ch;
	enum PState	st;

	cpend = *datap + *datalen;

  again:
	np   = vp   = NULL;
	nlen = vlen = 0;

	st = sInit;
	ch = pf_getch(datap, cpend);

	while (st != sDone) {
		switch (st)
		{
		case sInit:	/* handle inter-item chars */
			while (cp_ivspace(ch))
				ch = pf_nextch(datap, cpend);
			if (cp_namechar(ch)) {
				np = *datap;
				cp = np;
				st = sName;
				ch = pf_nextch(datap, cpend);
			} else {
				goto final_done;
			}
			break;

		case sName:	/* collect name */
			while (cp_namechar(ch))
				ch = pf_nextch(datap, cpend);
			nlen = (size_t)(*datap - np);
			if (ch == '=') {
				ch = pf_nextch(datap, cpend);
				vp = *datap;
				st = sValU;
			} else {
				if (ch != ',')
					*datap = cpend;
				st = sDone;
			}
			break;

		case sValU:	/* collect unquoted part(s) of value */
			while (cp_uqchar(ch))
				ch = pf_nextch(datap, cpend);
			if (ch == '"') {
				ch = pf_nextch(datap, cpend);
				st = sValQ;
			} else {
				vlen = (size_t)(*datap - vp);
				if (ch != ',')
					*datap = cpend;
				st = sDone;
			}
			break;

		case sValQ:	/* collect quoted part(s) of value */
			while (cp_qschar(ch))
				ch = pf_nextch(datap, cpend);
			if (ch == '"') {
				ch = pf_nextch(datap, cpend);
				st = sValU;
			} else {
				pf_error("no closing quote, stop", cp, cpend);
				goto final_done;
			}
			break;

		default:
			pf_error("state machine error, stop", *datap, cpend);
			goto final_done;
		}
	}

	/* If name or value do not fit their buffer, croak and start
	 * over. If there's no name at all after whitespace stripping,
	 * redo silently.
	 */
	nlen = str_strip(&np, nlen);
	vlen = str_strip(&vp, vlen);

	if (nlen == 0) {
		goto again;
	}
	if (nlen >= sizeof(name)) {
		pf_error("runaway name", np, cpend);
		goto again;
	}
	if (vlen >= sizeof(value)) {
		pf_error("runaway value", vp, cpend);
		goto again;
	}

	/* copy name and value into NUL-terminated buffers */
	memcpy(name, np, nlen);
	name[nlen] = '\0';
	*vname = name;

	memcpy(value, vp, vlen);
	value[vlen] = '\0';
	*vvalue = value;

	/* check if there's more to do or if we are finshed */
	*datalen = (size_t)(cpend - *datap);
	return TRUE;

  final_done:
	*datap = cpend;
	*datalen = 0;
	return FALSE;
}


u_short
varfmt(const char * varname)
{
	u_int n;

	for (n = 0; n < COUNTOF(cookedvars); n++)
		if (!strcmp(varname, cookedvars[n].varname))
			return cookedvars[n].fmt;

	return PADDING;
}


/*
 * printvars - print variables returned in response packet
 */
void
printvars(
	size_t length,
	const char *data,
	int status,
	int sttype,
	int quiet,
	FILE *fp
	)
{
	if (rawmode)
	    rawprint(sttype, length, data, status, quiet, fp);
	else
	    cookedprint(sttype, length, data, status, quiet, fp);
}


/*
 * rawprint - do a printout of the data in raw mode
 */
static void
rawprint(
	int datatype,
	size_t length,
	const char *data,
	int status,
	int quiet,
	FILE *fp
	)
{
	const char *cp;
	const char *cpend;

	/*
	 * Essentially print the data as is.  We reformat unprintables, though.
	 */
	cp = data;
	cpend = data + length;

	if (!quiet)
		(void) fprintf(fp, "status=0x%04x,\n", status);

	while (cp < cpend) {
		if (*cp == '\r') {
			/*
			 * If this is a \r and the next character is a
			 * \n, supress this, else pretty print it.  Otherwise
			 * just output the character.
			 */
			if (cp == (cpend - 1) || *(cp + 1) != '\n')
			    makeascii(1, cp, fp);
		} else if (isspace(pgetc(cp)) || isprint(pgetc(cp)))
			putc(*cp, fp);
		else
			makeascii(1, cp, fp);
		cp++;
	}
}


/*
 * Global data used by the cooked output routines
 */
int out_chars;		/* number of characters output */
int out_linecount;	/* number of characters output on this line */


/*
 * startoutput - get ready to do cooked output
 */
static void
startoutput(void)
{
	out_chars = 0;
	out_linecount = 0;
}


/*
 * output - output a variable=value combination
 */
static void
output(
	FILE *fp,
	const char *name,
	const char *value
	)
{
	int len;

	/* strlen of "name=value" */
	len = size2int_sat(strlen(name) + 1 + strlen(value));

	if (out_chars != 0) {
		out_chars += 2;
		if ((out_linecount + len + 2) > MAXOUTLINE) {
			fputs(",\n", fp);
			out_linecount = 0;
		} else {
			fputs(", ", fp);
			out_linecount += 2;
		}
	}

	fputs(name, fp);
	putc('=', fp);
	fputs(value, fp);
	out_chars += len;
	out_linecount += len;
}


/*
 * endoutput - terminate a block of cooked output
 */
static void
endoutput(
	FILE *fp
	)
{
	if (out_chars != 0)
		putc('\n', fp);
}


/*
 * outputarr - output an array of values
 */
static void
outputarr(
	FILE *fp,
	char *name,
	int narr,
	l_fp *lfp,
	int issigned
	)
{
	char *bp;
	char *cp;
	size_t i;
	size_t len;
	char buf[256];

	bp = buf;
	/*
	 * Hack to align delay and offset values
	 */
	for (i = (int)strlen(name); i < 11; i++)
		*bp++ = ' ';

	for (i = narr; i > 0; i--) {
		if (i != (size_t)narr)
			*bp++ = ' ';
		cp = (issigned ? lfptoms(lfp, 2) : ulfptoms(lfp, 2));
		len = strlen(cp);
		if (len > 7) {
			cp[7] = '\0';
			len = 7;
		}
		while (len < 7) {
			*bp++ = ' ';
			len++;
		}
		while (*cp != '\0')
		    *bp++ = *cp++;
		lfp++;
	}
	*bp = '\0';
	output(fp, name, buf);
}

static char *
tstflags(
	u_long val
	)
{
#	if CBLEN < 10
#	 error CBLEN is too small -- increase!
#	endif

	char *cp, *s;
	size_t cb, i;
	int l;

	s = cp = circ_buf[nextcb];
	if (++nextcb >= NUMCB)
		nextcb = 0;
	cb = sizeof(circ_buf[0]);

	l = snprintf(cp, cb, "%02lx", val);
	if (l < 0 || (size_t)l >= cb)
		goto fail;
	cp += l;
	cb -= l;
	if (!val) {
		l = strlcat(cp, " ok", cb);
		if ((size_t)l >= cb)
			goto fail;
		cp += l;
		cb -= l;
	} else {
		const char *sep;

		sep = " ";
		for (i = 0; i < COUNTOF(tstflagnames); i++) {
			if (val & 0x1) {
				l = snprintf(cp, cb, "%s%s", sep,
					     tstflagnames[i]);
				if (l < 0)
					goto fail;
				if ((size_t)l >= cb) {
					cp += cb - 4;
					cb = 4;
					l = strlcpy (cp, "...", cb);
					cp += l;
					cb -= l;
					break;
				}
				sep = ", ";
				cp += l;
				cb -= l;
			}
			val >>= 1;
		}
	}

	return s;

  fail:
	*cp = '\0';
	return s;
}

/*
 * cookedprint - output variables in cooked mode
 */
static void
cookedprint(
	int datatype,
	size_t length,
	const char *data,
	int status,
	int quiet,
	FILE *fp
	)
{
	char *name;
	char *value;
	char output_raw;
	int fmt;
	l_fp lfp;
	sockaddr_u hval;
	u_long uval;
	int narr;
	size_t len;
	l_fp lfparr[8];
	char b[12];
	char bn[2 * MAXVARLEN];
	char bv[2 * MAXVALLEN];

	UNUSED_ARG(datatype);

	if (!quiet)
		fprintf(fp, "status=%04x %s,\n", status,
			statustoa(datatype, status));

	startoutput();
	while (nextvar(&length, &data, &name, &value)) {
		fmt = varfmt(name);
		output_raw = 0;
		switch (fmt) {

		case PADDING:
			output_raw = '*';
			break;

		case TS:
			if (!value || !decodets(value, &lfp))
				output_raw = '?';
			else
				output(fp, name, prettydate(&lfp));
			break;

		case HA:	/* fallthru */
		case NA:
			if (!value || !decodenetnum(value, &hval)) {
				output_raw = '?';
			} else if (fmt == HA){
				output(fp, name, nntohost(&hval));
			} else {
				output(fp, name, stoa(&hval));
			}
			break;

		case RF:
			if (!value) {
				output_raw = '?';
			} else if (decodenetnum(value, &hval)) {
				if (datatype == TYPE_CLOCK && IS_IPV4(&hval)) {
					/*
					 * Workaround to override numeric refid formats
					 * for refclocks received from faulty nptd servers
					 * and output them as text.
					 */
					int i;
					unsigned char *str = (unsigned char *)&(hval.sa4).sin_addr;
					char refid_buf[5];
					for (i=0; i<4 && str[i]; i++)
						refid_buf[i] = (isprint(str[i]) ? str[i] : '?');
					refid_buf[i] = 0; /* Null terminator */
					output(fp, name, refid_buf);
				} else if (ISREFCLOCKADR(&hval)) {
					output(fp, name, refnumtoa(&hval));
				} else {
					if (drefid == REFID_IPV4) {
						output(fp, name, stoa(&hval));
					} else {
						char refid_buf[12];
						snprintf (refid_buf, sizeof(refid_buf),
							  "0x%08x", ntohl(addr2refid(&hval)));
						output(fp, name, refid_buf);
					}
				}
			} else if (strlen(value) <= 4) {
				output(fp, name, value);
			} else {
				output_raw = '?';
			}
			break;

		case LP:
			if (!value || !decodeuint(value, &uval) || uval > 3) {
				output_raw = '?';
			} else {
				b[0] = (0x2 & uval)
					   ? '1'
					   : '0';
				b[1] = (0x1 & uval)
					   ? '1'
					   : '0';
				b[2] = '\0';
				output(fp, name, b);
			}
			break;

		case OC:
			if (!value || !decodeuint(value, &uval)) {
				output_raw = '?';
			} else {
				snprintf(b, sizeof(b), "%03lo", uval);
				output(fp, name, b);
			}
			break;

		case AU:
		case AS:
			if (!value || !decodearr(value, &narr, lfparr, 8))
				output_raw = '?';
			else
				outputarr(fp, name, narr, lfparr, (fmt==AS));
			break;

		case FX:
			if (!value || !decodeuint(value, &uval))
				output_raw = '?';
			else
				output(fp, name, tstflags(uval));
			break;

		case SN:
			if (!value)
				output_raw = '?';
			else if (isdigit(*value)) {	/* number without sign */
				bv[0] = '+';
				atoascii (value, MAXVALLEN, bv+1, sizeof(bv)-1);
				output(fp, name, bv);
			} else
				output_raw = '*';		/* output as-is */
			break;

		default:
			fprintf(stderr, "Internal error in cookedprint, %s=%s, fmt %d\n",
				name, value, fmt);
			output_raw = '?';
			break;
		}

		if (output_raw != 0) {
			/* TALOS-CAN-0063: avoid buffer overrun */
			atoascii(name, MAXVARLEN, bn, sizeof(bn));
			if (output_raw != '*') {
				atoascii(value, MAXVALLEN,
					 bv, sizeof(bv) - 1);
				len = strlen(bv);
				bv[len] = output_raw;
				bv[len+1] = '\0';
			} else {
				atoascii(value, MAXVALLEN,
					 bv, sizeof(bv));
			}
			output(fp, bn, bv);
		}
	}
	endoutput(fp);
}


/*
 * sortassoc - sort associations in the cache into ascending order
 */
void
sortassoc(void)
{
	if (numassoc > 1)
		qsort(assoc_cache, (size_t)numassoc,
		      sizeof(assoc_cache[0]), &assoccmp);
}


/*
 * assoccmp - compare two associations
 */
static int
assoccmp(
	const void *t1,
	const void *t2
	)
{
	const struct association *ass1 = t1;
	const struct association *ass2 = t2;

	if (ass1->assid < ass2->assid)
		return -1;
	if (ass1->assid > ass2->assid)
		return 1;
	return 0;
}


/*
 * grow_assoc_cache() - enlarge dynamic assoc_cache array
 *
 * The strategy is to add an assumed 4k page size at a time, leaving
 * room for malloc() bookkeeping overhead equivalent to 4 pointers.
 */
void
grow_assoc_cache(void)
{
	static size_t	prior_sz;
	size_t		new_sz;

	new_sz = prior_sz + 4 * 1024;
	if (0 == prior_sz) {
		new_sz -= 4 * sizeof(void *);
	}
	assoc_cache = erealloc_zero(assoc_cache, new_sz, prior_sz);
	prior_sz = new_sz;
	assoc_cache_slots = (u_int)(new_sz / sizeof(assoc_cache[0]));
}


/*
 * ntpq_custom_opt_handler - autoopts handler for -c and -p
 *
 * By default, autoopts loses the relative order of -c and -p options
 * on the command line.  This routine replaces the default handler for
 * those routines and builds a list of commands to execute preserving
 * the order.
 */
void
ntpq_custom_opt_handler(
	tOptions *pOptions,
	tOptDesc *pOptDesc
	)
{
	switch (pOptDesc->optValue) {

	default:
		fprintf(stderr,
			"ntpq_custom_opt_handler unexpected option '%c' (%d)\n",
			pOptDesc->optValue, pOptDesc->optValue);
		exit(1);

	case 'c':
		if ((pOptDesc->fOptState & OPTST_SET_MASK) == OPTST_DEFINED)
			defcmds++;
		ADDCMD(pOptDesc->pzLastArg);
		break;

	case 'p':
		if ((pOptDesc->fOptState & OPTST_SET_MASK) == OPTST_DEFINED)
			defcmds++;
		ADDCMD("peers");
		break;
	}
}
/*
 * Obtain list of digest names
 */

#if defined(OPENSSL) && !defined(HAVE_EVP_MD_DO_ALL_SORTED)
# if defined(_MSC_VER) && OPENSSL_VERSION_NUMBER >= 0x10100000L
#  define HAVE_EVP_MD_DO_ALL_SORTED
# endif
#endif

#ifdef OPENSSL
# ifdef HAVE_EVP_MD_DO_ALL_SORTED
#  define K_PER_LINE	8
#  define K_NL_PFX_STR	"\n    "
#  define K_DELIM_STR	", "

struct hstate {
	char *list;
	char const **seen;
	int idx;
};


#  ifndef BUILD_AS_LIB
static void
list_md_fn(const EVP_MD *m, const char *from, const char *to, void *arg)
{
	size_t 	       len, n;
	const char    *name, **seen;
	struct hstate *hstate = arg;
	const char    *cp;

	/* m is MD obj, from is name or alias, to is base name for alias */
	if (!m || !from || to)
		return; /* Ignore aliases */

	/* Discard MACs that NTP won't accept. */
	/* Keep this consistent with keytype_from_text() in ssl_init.c. */
	if (EVP_MD_size(m) > (MAX_MAC_LEN - sizeof(keyid_t)))
		return;

	name = EVP_MD_name(m);

	/* Lowercase names aren't accepted by keytype_from_text in ssl_init.c */

	for (cp = name; *cp; cp++)
		if (islower((unsigned char)*cp))
			return;

	len = (cp - name) + 1;

	/* There are duplicates.  Discard if name has been seen. */

	for (seen = hstate->seen; *seen; seen++)
		if (!strcmp(*seen, name))
			return;

	n = (seen - hstate->seen) + 2;
	hstate->seen = erealloc((void *)hstate->seen, n * sizeof(*seen));
	hstate->seen[n-2] = name;
	hstate->seen[n-1] = NULL;

	if (hstate->list != NULL)
		len += strlen(hstate->list);

	len += (hstate->idx >= K_PER_LINE)
	    ? strlen(K_NL_PFX_STR)
	    : strlen(K_DELIM_STR);

	if (hstate->list == NULL) {
		hstate->list = (char *)emalloc(len);
		hstate->list[0] = '\0';
	} else {
		hstate->list = (char *)erealloc(hstate->list, len);
	}

	sprintf(hstate->list + strlen(hstate->list), "%s%s",
		((hstate->idx >= K_PER_LINE) ? K_NL_PFX_STR : K_DELIM_STR),
		name);

	if (hstate->idx >= K_PER_LINE)
		hstate->idx = 1;
	else
		hstate->idx++;
}
#  endif /* !defined(BUILD_AS_LIB) */

#  ifndef BUILD_AS_LIB
/* Insert CMAC into SSL digests list */
static char *
insert_cmac(char *list)
{
#ifdef ENABLE_CMAC
	int insert;
	size_t len;


	/* If list empty, we need to insert CMAC on new line */
	insert = (!list || !*list);

	if (insert) {
		len = strlen(K_NL_PFX_STR) + strlen(CMAC);
		list = (char *)erealloc(list, len + 1);
		sprintf(list, "%s%s", K_NL_PFX_STR, CMAC);
	} else {	/* List not empty */
		/* Check if CMAC already in list - future proofing */
		const char *cmac_sn;
		char *cmac_p;

		cmac_sn = OBJ_nid2sn(NID_cmac);
		cmac_p = list;
		insert = cmac_sn != NULL && *cmac_sn != '\0';

		/* CMAC in list if found, followed by nul char or ',' */
		while (insert && NULL != (cmac_p = strstr(cmac_p, cmac_sn))) {
			cmac_p += strlen(cmac_sn);
			/* Still need to insert if not nul and not ',' */
			insert = *cmac_p && ',' != *cmac_p;
		}

		/* Find proper insertion point */
		if (insert) {
			char *last_nl;
			char *point;
			char *delim;
			int found;

			/* Default to start if list empty */
			found = 0;
			delim = list;
			len = strlen(list);

			/* While new lines */
			while (delim < list + len && *delim &&
			       !strncmp(K_NL_PFX_STR, delim, strlen(K_NL_PFX_STR))) {
				point = delim + strlen(K_NL_PFX_STR);

				/* While digest names on line */
				while (point < list + len && *point) {
					/* Another digest after on same or next line? */
					delim = strstr( point, K_DELIM_STR);
					last_nl = strstr( point, K_NL_PFX_STR);

					/* No - end of list */
					if (!delim && !last_nl) {
						delim = list + len;
					} else
						/* New line and no delim or before delim? */
						if (last_nl && (!delim || last_nl < delim)) {
							delim = last_nl;
						}

					/* Found insertion point where CMAC before entry? */
					if (strncmp(CMAC, point, delim - point) < 0) {
						found = 1;
						break;
					}

					if (delim < list + len && *delim &&
					    !strncmp(K_DELIM_STR, delim, strlen(K_DELIM_STR))) {
						point += strlen(K_DELIM_STR);
					} else {
						break;
					}
				} /* While digest names on line */
			} /* While new lines */

			/* If found in list */
			if (found) {
				/* insert cmac and delim */
				/* Space for list could move - save offset */
				ptrdiff_t p_offset = point - list;
				len += strlen(CMAC) + strlen(K_DELIM_STR);
				list = (char *)erealloc(list, len + 1);
				point = list + p_offset;
				/* move to handle src/dest overlap */
				memmove(point + strlen(CMAC) + strlen(K_DELIM_STR),
					point, strlen(point) + 1);
				memcpy(point, CMAC, strlen(CMAC));
				memcpy(point + strlen(CMAC), K_DELIM_STR, strlen(K_DELIM_STR));
			} else {	/* End of list */
				/* append delim and cmac */
				len += strlen(K_DELIM_STR) + strlen(CMAC);
				list = (char *)erealloc(list, len + 1);
				strcpy(list + strlen(list), K_DELIM_STR);
				strcpy(list + strlen(list), CMAC);
			}
		} /* insert */
	} /* List not empty */
#endif /*ENABLE_CMAC*/
	return list;
}
#  endif /* !defined(BUILD_AS_LIB) */
# endif
#endif


#ifndef BUILD_AS_LIB
static char *
list_digest_names(void)
{
	char *list = NULL;

#ifdef OPENSSL
# ifdef HAVE_EVP_MD_DO_ALL_SORTED
	struct hstate hstate = { NULL, NULL, K_PER_LINE+1 };

	/* replace calloc(1, sizeof(const char *)) */
	hstate.seen = emalloc_zero(sizeof(const char*));

	INIT_SSL();
	EVP_MD_do_all_sorted(list_md_fn, &hstate);
	list = hstate.list;
	free((void *)hstate.seen);

	list = insert_cmac(list);	/* Insert CMAC into SSL digests list */

# else
	list = (char *)emalloc(sizeof("md5, others (upgrade to OpenSSL-1.0 for full list)"));
	strcpy(list, "md5, others (upgrade to OpenSSL-1.0 for full list)");
# endif
#else
	list = (char *)emalloc(sizeof("md5"));
	strcpy(list, "md5");
#endif

	return list;
}
#endif /* !defined(BUILD_AS_LIB) */

#define CTRLC_STACK_MAX 4
static volatile size_t		ctrlc_stack_len = 0;
static volatile Ctrl_C_Handler	ctrlc_stack[CTRLC_STACK_MAX];



int/*BOOL*/
push_ctrl_c_handler(
	Ctrl_C_Handler func
	)
{
	size_t size = ctrlc_stack_len;
	if (func && (size < CTRLC_STACK_MAX)) {
		ctrlc_stack[size] = func;
		ctrlc_stack_len = size + 1;
		return TRUE;
	}
	return FALSE;
}

int/*BOOL*/
pop_ctrl_c_handler(
	Ctrl_C_Handler func
	)
{
	size_t size = ctrlc_stack_len;
	if (size) {
		--size;
		if (func == NULL || func == ctrlc_stack[size]) {
			ctrlc_stack_len = size;
			return TRUE;
		}
	}
	return FALSE;
}

#ifndef BUILD_AS_LIB
static void
on_ctrlc(void)
{
	size_t size = ctrlc_stack_len;
	while (size)
		if ((*ctrlc_stack[--size])())
			break;
}
#endif /* !defined(BUILD_AS_LIB) */

#ifndef BUILD_AS_LIB
static int
my_easprintf(
	char ** 	ppinto,
	const char *	fmt   ,
	...
	)
{
	va_list	va;
	int	prc;
	size_t	len = 128;
	char *	buf = emalloc(len);

  again:
	/* Note: we expect the memory allocation to fail long before the
	 * increment in buffer size actually overflows.
	 */
	buf = (buf) ? erealloc(buf, len) : emalloc(len);

	va_start(va, fmt);
	prc = vsnprintf(buf, len, fmt, va);
	va_end(va);

	if (prc < 0) {
		/* might be very old vsnprintf. Or actually MSVC... */
		len += len >> 1;
		goto again;
	}
	if ((size_t)prc >= len) {
		/* at least we have the proper size now... */
		len = (size_t)prc + 1;
		goto again;
	}
	if ((size_t)prc < (len - 32))
		buf = erealloc(buf, (size_t)prc + 1);
	*ppinto = buf;
	return prc;
}
#endif /* !defined(BUILD_AS_LIB) */
