xref: /freebsd/contrib/ntp/ntpq/ntpq-subs.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
1 /*
2  * ntpq-subs.c - subroutines which are called to perform ntpq commands.
3  */
4 #include <config.h>
5 #include <stdio.h>
6 #include <ctype.h>
7 #include <sys/types.h>
8 #include <sys/time.h>
9 
10 #include "ntpq.h"
11 #include "ntpq-opts.h"
12 
13 extern char	currenthost[];
14 extern int	currenthostisnum;
15 size_t		maxhostlen;
16 
17 /*
18  * Declarations for command handlers in here
19  */
20 static	associd_t checkassocid	(u_int32);
21 static	struct varlist *findlistvar (struct varlist *, char *);
22 static	void	doaddvlist	(struct varlist *, const char *);
23 static	void	dormvlist	(struct varlist *, const char *);
24 static	void	doclearvlist	(struct varlist *);
25 static	void	makequerydata	(struct varlist *, size_t *, char *);
26 static	int	doquerylist	(struct varlist *, int, associd_t, int,
27 				 u_short *, size_t *, const char **);
28 static	void	doprintvlist	(struct varlist *, FILE *);
29 static	void	addvars 	(struct parse *, FILE *);
30 static	void	rmvars		(struct parse *, FILE *);
31 static	void	clearvars	(struct parse *, FILE *);
32 static	void	showvars	(struct parse *, FILE *);
33 static	int	dolist		(struct varlist *, associd_t, int, int,
34 				 FILE *);
35 static	void	readlist	(struct parse *, FILE *);
36 static	void	writelist	(struct parse *, FILE *);
37 static	void	readvar 	(struct parse *, FILE *);
38 static	void	writevar	(struct parse *, FILE *);
39 static	void	clocklist	(struct parse *, FILE *);
40 static	void	clockvar	(struct parse *, FILE *);
41 static	int	findassidrange	(u_int32, u_int32, int *, int *,
42 				 FILE *);
43 static	void	mreadlist	(struct parse *, FILE *);
44 static	void	mreadvar	(struct parse *, FILE *);
45 static	void	printassoc	(int, FILE *);
46 static	void	associations	(struct parse *, FILE *);
47 static	void	lassociations	(struct parse *, FILE *);
48 static	void	passociations	(struct parse *, FILE *);
49 static	void	lpassociations	(struct parse *, FILE *);
50 
51 #ifdef	UNUSED
52 static	void	radiostatus (struct parse *, FILE *);
53 #endif	/* UNUSED */
54 
55 static	void	authinfo	(struct parse *, FILE *);
56 static	void	pstats	 	(struct parse *, FILE *);
57 static	long	when		(l_fp *, l_fp *, l_fp *);
58 static	char *	prettyinterval	(char *, size_t, long);
59 static	int	doprintpeers	(struct varlist *, int, int, size_t, const char *, FILE *, int);
60 static	int	dogetpeers	(struct varlist *, associd_t, FILE *, int);
61 static	void	dopeers 	(int, FILE *, int);
62 static	void	peers		(struct parse *, FILE *);
63 static	void	doapeers 	(int, FILE *, int);
64 static	void	apeers		(struct parse *, FILE *);
65 static	void	lpeers		(struct parse *, FILE *);
66 static	void	doopeers	(int, FILE *, int);
67 static	void	opeers		(struct parse *, FILE *);
68 static	void	lopeers 	(struct parse *, FILE *);
69 static	void	config		(struct parse *, FILE *);
70 static	void	saveconfig	(struct parse *, FILE *);
71 static	void	config_from_file(struct parse *, FILE *);
72 static	void	mrulist		(struct parse *, FILE *);
73 static	void	ifstats		(struct parse *, FILE *);
74 static	void	reslist		(struct parse *, FILE *);
75 static	void	sysstats	(struct parse *, FILE *);
76 static	void	sysinfo		(struct parse *, FILE *);
77 static	void	kerninfo	(struct parse *, FILE *);
78 static	void	monstats	(struct parse *, FILE *);
79 static	void	iostats		(struct parse *, FILE *);
80 static	void	timerstats	(struct parse *, FILE *);
81 
82 /*
83  * Commands we understand.	Ntpdc imports this.
84  */
85 struct xcmd opcmds[] = {
86 	{ "saveconfig", saveconfig, { NTP_STR, NO, NO, NO },
87 		{ "filename", "", "", ""},
88 		"save ntpd configuration to file, . for current config file"},
89 	{ "associations", associations, {  NO, NO, NO, NO },
90 	  { "", "", "", "" },
91 	  "print list of association ID's and statuses for the server's peers" },
92 	{ "passociations", passociations,   {  NO, NO, NO, NO },
93 	  { "", "", "", "" },
94 	  "print list of associations returned by last associations command" },
95 	{ "lassociations", lassociations,   {  NO, NO, NO, NO },
96 	  { "", "", "", "" },
97 	  "print list of associations including all client information" },
98 	{ "lpassociations", lpassociations, {  NO, NO, NO, NO },
99 	  { "", "", "", "" },
100 	  "print last obtained list of associations, including client information" },
101 	{ "addvars",    addvars,    { NTP_STR, NO, NO, NO },
102 	  { "name[=value][,...]", "", "", "" },
103 	  "add variables to the variable list or change their values" },
104 	{ "rmvars", rmvars,     { NTP_STR, NO, NO, NO },
105 	  { "name[,...]", "", "", "" },
106 	  "remove variables from the variable list" },
107 	{ "clearvars",  clearvars,  { NO, NO, NO, NO },
108 	  { "", "", "", "" },
109 	  "remove all variables from the variable list" },
110 	{ "showvars",   showvars,   { NO, NO, NO, NO },
111 	  { "", "", "", "" },
112 	  "print variables on the variable list" },
113 	{ "readlist",   readlist,   { OPT|NTP_UINT, NO, NO, NO },
114 	  { "assocID", "", "", "" },
115 	  "read the system or peer variables included in the variable list" },
116 	{ "rl",     readlist,   { OPT|NTP_UINT, NO, NO, NO },
117 	  { "assocID", "", "", "" },
118 	  "read the system or peer variables included in the variable list" },
119 	{ "writelist",  writelist,  { OPT|NTP_UINT, NO, NO, NO },
120 	  { "assocID", "", "", "" },
121 	  "write the system or peer variables included in the variable list" },
122 	{ "readvar", readvar,    { OPT|NTP_UINT, OPT|NTP_STR, OPT|NTP_STR, OPT|NTP_STR, },
123 	  { "assocID", "varname1", "varname2", "varname3" },
124 	  "read system or peer variables" },
125 	{ "rv",      readvar,    { OPT|NTP_UINT, OPT|NTP_STR, OPT|NTP_STR, OPT|NTP_STR, },
126 	  { "assocID", "varname1", "varname2", "varname3" },
127 	  "read system or peer variables" },
128 	{ "writevar",   writevar,   { NTP_UINT, NTP_STR, NO, NO },
129 	  { "assocID", "name=value,[...]", "", "" },
130 	  "write system or peer variables" },
131 	{ "mreadlist",  mreadlist,  { NTP_UINT, NTP_UINT, NO, NO },
132 	  { "assocIDlow", "assocIDhigh", "", "" },
133 	  "read the peer variables in the variable list for multiple peers" },
134 	{ "mrl",    mreadlist,  { NTP_UINT, NTP_UINT, NO, NO },
135 	  { "assocIDlow", "assocIDhigh", "", "" },
136 	  "read the peer variables in the variable list for multiple peers" },
137 	{ "mreadvar",   mreadvar,   { NTP_UINT, NTP_UINT, OPT|NTP_STR, NO },
138 	  { "assocIDlow", "assocIDhigh", "name=value[,...]", "" },
139 	  "read peer variables from multiple peers" },
140 	{ "mrv",    mreadvar,   { NTP_UINT, NTP_UINT, OPT|NTP_STR, NO },
141 	  { "assocIDlow", "assocIDhigh", "name=value[,...]", "" },
142 	  "read peer variables from multiple peers" },
143 	{ "clocklist",  clocklist,  { OPT|NTP_UINT, NO, NO, NO },
144 	  { "assocID", "", "", "" },
145 	  "read the clock variables included in the variable list" },
146 	{ "cl",     clocklist,  { OPT|NTP_UINT, NO, NO, NO },
147 	  { "assocID", "", "", "" },
148 	  "read the clock variables included in the variable list" },
149 	{ "clockvar",   clockvar,   { OPT|NTP_UINT, OPT|NTP_STR, NO, NO },
150 	  { "assocID", "name=value[,...]", "", "" },
151 	  "read clock variables" },
152 	{ "cv",     clockvar,   { OPT|NTP_UINT, OPT|NTP_STR, NO, NO },
153 	  { "assocID", "name=value[,...]", "", "" },
154 	  "read clock variables" },
155 	{ "pstats",    pstats,    { NTP_UINT, NO, NO, NO },
156 	  { "assocID", "", "", "" },
157 	  "show statistics for a peer" },
158 	{ "peers",  peers,      { OPT|IP_VERSION, NO, NO, NO },
159 	  { "-4|-6", "", "", "" },
160 	  "obtain and print a list of the server's peers [IP version]" },
161 	{ "apeers",  apeers,      { OPT|IP_VERSION, NO, NO, NO },
162 	  { "-4|-6", "", "", "" },
163 	  "obtain and print a list of the server's peers and their assocIDs [IP version]" },
164 	{ "lpeers", lpeers,     { OPT|IP_VERSION, NO, NO, NO },
165 	  { "-4|-6", "", "", "" },
166 	  "obtain and print a list of all peers and clients [IP version]" },
167 	{ "opeers", opeers,     { OPT|IP_VERSION, NO, NO, NO },
168 	  { "-4|-6", "", "", "" },
169 	  "print peer list the old way, with dstadr shown rather than refid [IP version]" },
170 	{ "lopeers", lopeers,   { OPT|IP_VERSION, NO, NO, NO },
171 	  { "-4|-6", "", "", "" },
172 	  "obtain and print a list of all peers and clients showing dstadr [IP version]" },
173 	{ ":config", config,   { NTP_STR, NO, NO, NO },
174 	  { "<configuration command line>", "", "", "" },
175 	  "send a remote configuration command to ntpd" },
176 	{ "config-from-file", config_from_file, { NTP_STR, NO, NO, NO },
177 	  { "<configuration filename>", "", "", "" },
178 	  "configure ntpd using the configuration filename" },
179 	{ "mrulist", mrulist, { OPT|NTP_STR, OPT|NTP_STR, OPT|NTP_STR, OPT|NTP_STR },
180 	  { "tag=value", "tag=value", "tag=value", "tag=value" },
181 	  "display the list of most recently seen source addresses, tags mincount=... resall=0x... resany=0x..." },
182 	{ "ifstats", ifstats, { NO, NO, NO, NO },
183 	  { "", "", "", "" },
184 	  "show statistics for each local address ntpd is using" },
185 	{ "reslist", reslist, { NO, NO, NO, NO },
186 	  { "", "", "", "" },
187 	  "show ntpd access control list" },
188 	{ "sysinfo", sysinfo, { NO, NO, NO, NO },
189 	  { "", "", "", "" },
190 	  "display system summary" },
191 	{ "kerninfo", kerninfo, { NO, NO, NO, NO },
192 	  { "", "", "", "" },
193 	  "display kernel loop and PPS statistics" },
194 	{ "sysstats", sysstats, { NO, NO, NO, NO },
195 	  { "", "", "", "" },
196 	  "display system uptime and packet counts" },
197 	{ "monstats", monstats, { NO, NO, NO, NO },
198 	  { "", "", "", "" },
199 	  "display monitor (mrulist) counters and limits" },
200 	{ "authinfo", authinfo, { NO, NO, NO, NO },
201 	  { "", "", "", "" },
202 	  "display symmetric authentication counters" },
203 	{ "iostats", iostats, { NO, NO, NO, NO },
204 	  { "", "", "", "" },
205 	  "display network input and output counters" },
206 	{ "timerstats", timerstats, { NO, NO, NO, NO },
207 	  { "", "", "", "" },
208 	  "display interval timer counters" },
209 	{ 0,		0,		{ NO, NO, NO, NO },
210 	  { "-4|-6", "", "", "" }, "" }
211 };
212 
213 
214 /*
215  * Variable list data space
216  */
217 #define MAXLINE		512	/* maximum length of a line */
218 #define MAXLIST		128	/* maximum variables in list */
219 #define LENHOSTNAME	256	/* host name limit */
220 
221 #define MRU_GOT_COUNT	0x1
222 #define MRU_GOT_LAST	0x2
223 #define MRU_GOT_FIRST	0x4
224 #define MRU_GOT_MV	0x8
225 #define MRU_GOT_RS	0x10
226 #define MRU_GOT_ADDR	0x20
227 #define MRU_GOT_ALL	(MRU_GOT_COUNT | MRU_GOT_LAST | MRU_GOT_FIRST \
228 			 | MRU_GOT_MV | MRU_GOT_RS | MRU_GOT_ADDR)
229 
230 /*
231  * mrulist() depends on MRUSORT_DEF and MRUSORT_RDEF being the first two
232  */
233 typedef enum mru_sort_order_tag {
234 	MRUSORT_DEF = 0,	/* lstint ascending */
235 	MRUSORT_R_DEF,		/* lstint descending */
236 	MRUSORT_AVGINT,		/* avgint ascending */
237 	MRUSORT_R_AVGINT,	/* avgint descending */
238 	MRUSORT_ADDR,		/* IPv4 asc. then IPv6 asc. */
239 	MRUSORT_R_ADDR,		/* IPv6 desc. then IPv4 desc. */
240 	MRUSORT_COUNT,		/* hit count ascending */
241 	MRUSORT_R_COUNT,	/* hit count descending */
242 	MRUSORT_MAX,		/* special: count of this enum */
243 } mru_sort_order;
244 
245 const char * const mru_sort_keywords[MRUSORT_MAX] = {
246 	"lstint",		/* MRUSORT_DEF */
247 	"-lstint",		/* MRUSORT_R_DEF */
248 	"avgint",		/* MRUSORT_AVGINT */
249 	"-avgint",		/* MRUSORT_R_AVGINT */
250 	"addr",			/* MRUSORT_ADDR */
251 	"-addr",		/* MRUSORT_R_ADDR */
252 	"count",		/* MRUSORT_COUNT */
253 	"-count",		/* MRUSORT_R_COUNT */
254 };
255 
256 typedef int (*qsort_cmp)(const void *, const void *);
257 
258 /*
259  * Old CTL_PST defines for version 2.
260  */
261 #define OLD_CTL_PST_CONFIG		0x80
262 #define OLD_CTL_PST_AUTHENABLE		0x40
263 #define OLD_CTL_PST_AUTHENTIC		0x20
264 #define OLD_CTL_PST_REACH		0x10
265 #define OLD_CTL_PST_SANE		0x08
266 #define OLD_CTL_PST_DISP		0x04
267 
268 #define OLD_CTL_PST_SEL_REJECT		0
269 #define OLD_CTL_PST_SEL_SELCAND 	1
270 #define OLD_CTL_PST_SEL_SYNCCAND	2
271 #define OLD_CTL_PST_SEL_SYSPEER 	3
272 
273 char flash2[] = " .+*    "; /* flash decode for version 2 */
274 char flash3[] = " x.-+#*o"; /* flash decode for peer status version 3 */
275 
276 struct varlist {
277 	const char *name;
278 	char *value;
279 } g_varlist[MAXLIST] = { { 0, 0 } };
280 
281 /*
282  * Imported from ntpq.c
283  */
284 extern int showhostnames;
285 extern int wideremote;
286 extern int rawmode;
287 extern struct servent *server_entry;
288 extern struct association *assoc_cache;
289 extern u_char pktversion;
290 
291 typedef struct mru_tag mru;
292 struct mru_tag {
293 	mru *		hlink;	/* next in hash table bucket */
294 	DECL_DLIST_LINK(mru, mlink);
295 	int		count;
296 	l_fp		last;
297 	l_fp		first;
298 	u_char		mode;
299 	u_char		ver;
300 	u_short		rs;
301 	sockaddr_u	addr;
302 };
303 
304 typedef struct ifstats_row_tag {
305 	u_int		ifnum;
306 	sockaddr_u	addr;
307 	sockaddr_u	bcast;
308 	int		enabled;
309 	u_int		flags;
310 	u_int		mcast_count;
311 	char		name[32];
312 	u_int		peer_count;
313 	u_int		received;
314 	u_int		sent;
315 	u_int		send_errors;
316 	u_int		ttl;
317 	u_int		uptime;
318 } ifstats_row;
319 
320 typedef struct reslist_row_tag {
321 	u_int		idx;
322 	sockaddr_u	addr;
323 	sockaddr_u	mask;
324 	u_long		hits;
325 	char		flagstr[128];
326 } reslist_row;
327 
328 typedef struct var_display_collection_tag {
329 	const char * const tag;		/* system variable */
330 	const char * const display;	/* descriptive text */
331 	u_char type;			/* NTP_STR, etc */
332 	union {
333 		char *		str;
334 		sockaddr_u	sau;	/* NTP_ADD */
335 		l_fp		lfp;	/* NTP_LFP */
336 	} v;				/* retrieved value */
337 } vdc;
338 #if !defined(MISSING_C99_STRUCT_INIT)
339 # define VDC_INIT(a, b, c) { .tag = a, .display = b, .type = c }
340 #else
341 # define VDC_INIT(a, b, c) { a, b, c }
342 #endif
343 /*
344  * other local function prototypes
345  */
346 static int	mrulist_ctrl_c_hook(void);
347 static mru *	add_mru(mru *);
348 static int	collect_mru_list(const char *, l_fp *);
349 static int	fetch_nonce(char *, size_t);
350 static int	qcmp_mru_avgint(const void *, const void *);
351 static int	qcmp_mru_r_avgint(const void *, const void *);
352 static int	qcmp_mru_addr(const void *, const void *);
353 static int	qcmp_mru_r_addr(const void *, const void *);
354 static int	qcmp_mru_count(const void *, const void *);
355 static int	qcmp_mru_r_count(const void *, const void *);
356 static void	validate_ifnum(FILE *, u_int, int *, ifstats_row *);
357 static void	another_ifstats_field(int *, ifstats_row *, FILE *);
358 static void	collect_display_vdc(associd_t as, vdc *table,
359 				    int decodestatus, FILE *fp);
360 
361 static	int	xprintf(FILE *,	char const *, ...) NTP_PRINTF(2, 3);
362 static	int	xputs(char const *, FILE *);
363 static	int	xputc(int, FILE *);
364 
365 /*
366  * static globals
367  */
368 static u_int	mru_count;
369 static u_int	mru_dupes;
370 volatile int	mrulist_interrupted;
371 static mru	mru_list;		/* listhead */
372 static mru **	hash_table;
373 
374 /*
375  * qsort comparison function table for mrulist().  The first two
376  * entries are NULL because they are handled without qsort().
377  */
378 static const qsort_cmp mru_qcmp_table[MRUSORT_MAX] = {
379 	NULL,			/* MRUSORT_DEF unused */
380 	NULL,			/* MRUSORT_R_DEF unused */
381 	&qcmp_mru_avgint,	/* MRUSORT_AVGINT */
382 	&qcmp_mru_r_avgint,	/* MRUSORT_R_AVGINT */
383 	&qcmp_mru_addr,		/* MRUSORT_ADDR */
384 	&qcmp_mru_r_addr,	/* MRUSORT_R_ADDR */
385 	&qcmp_mru_count,	/* MRUSORT_COUNT */
386 	&qcmp_mru_r_count,	/* MRUSORT_R_COUNT */
387 };
388 
389 /*
390  * NULL-pointer safe FILE I/O: use stderr if no file supplied.
391  */
392 static	int
393 xprintf(
394 	FILE *		ofp,
395 	char const *	fmt,
396 	...
397 	)
398 {
399 	va_list	va;
400 	int	rc;
401 
402 	va_start(va, fmt);
403 	rc = vfprintf((ofp ? ofp : stderr), fmt, va);
404 	va_end(va);
405 	return rc;
406 }
407 
408 static	int
409 xputs(
410 	char const *	str,
411 	FILE *		ofp
412 	)
413 {
414 	return fputs(str, (ofp ? ofp : stderr));
415 }
416 
417 static	int
418 xputc(
419 	int	ch,
420 	FILE *	ofp
421 	)
422 {
423 	return fputc(ch, (ofp ? ofp : stderr));
424 }
425 
426 /*
427  * checkassocid - return the association ID, checking to see if it is valid
428  */
429 static associd_t
430 checkassocid(
431 	u_int32 value
432 	)
433 {
434 	associd_t	associd;
435 	u_long		ulvalue;
436 
437 	associd = (associd_t)value;
438 	if (0 == associd || value != associd) {
439 		ulvalue = value;
440 		xprintf(stderr,
441 			"***Invalid association ID %lu specified\n",
442 			ulvalue);
443 		return 0;
444 	}
445 
446 	return associd;
447 }
448 
449 
450 /*
451  * findlistvar - Look for the named variable in a varlist.  If found,
452  *		 return a pointer to it.  Otherwise, if the list has
453  *		 slots available, return the pointer to the first free
454  *		 slot, or NULL if it's full.
455  */
456 static struct varlist *
457 findlistvar(
458 	struct varlist *list,
459 	char *name
460 	)
461 {
462 	struct varlist *vl;
463 
464 	for (vl = list; vl < list + MAXLIST && vl->name != NULL; vl++)
465 		if (!strcmp(name, vl->name))
466 			return vl;
467 	if (vl < list + MAXLIST)
468 		return vl;
469 
470 	return NULL;
471 }
472 
473 
474 /*
475  * doaddvlist - add variable(s) to the variable list
476  */
477 static void
478 doaddvlist(
479 	struct varlist *vlist,
480 	const char *vars
481 	)
482 {
483 	struct varlist *vl;
484 	size_t len;
485 	char *name;
486 	char *value;
487 
488 	len = strlen(vars);
489 	while (nextvar(&len, &vars, &name, &value)) {
490 		INSIST(name && value);
491 		vl = findlistvar(vlist, name);
492 		if (NULL == vl) {
493 			xprintf(stderr, "Variable list full\n");
494 			return;
495 		}
496 
497 		if (NULL == vl->name) {
498 			vl->name = estrdup(name);
499 		} else if (vl->value != NULL) {
500 			free(vl->value);
501 			vl->value = NULL;
502 		}
503 
504 		if (value != NULL)
505 			vl->value = estrdup(value);
506 	}
507 }
508 
509 
510 /*
511  * dormvlist - remove variable(s) from the variable list
512  */
513 static void
514 dormvlist(
515 	struct varlist *vlist,
516 	const char *vars
517 	)
518 {
519 	struct varlist *vl;
520 	size_t len;
521 	char *name;
522 	char *value;
523 
524 	len = strlen(vars);
525 	while (nextvar(&len, &vars, &name, &value)) {
526 		INSIST(name && value);
527 		vl = findlistvar(vlist, name);
528 		if (vl == 0 || vl->name == 0) {
529 			(void) xprintf(stderr, "Variable `%s' not found\n",
530 				       name);
531 		} else {
532 			free((void *)(intptr_t)vl->name);
533 			if (vl->value != 0)
534 			    free(vl->value);
535 			for ( ; (vl+1) < (g_varlist + MAXLIST)
536 				      && (vl+1)->name != 0; vl++) {
537 				vl->name = (vl+1)->name;
538 				vl->value = (vl+1)->value;
539 			}
540 			vl->name = vl->value = 0;
541 		}
542 	}
543 }
544 
545 
546 /*
547  * doclearvlist - clear a variable list
548  */
549 static void
550 doclearvlist(
551 	struct varlist *vlist
552 	)
553 {
554 	register struct varlist *vl;
555 
556 	for (vl = vlist; vl < vlist + MAXLIST && vl->name != 0; vl++) {
557 		free((void *)(intptr_t)vl->name);
558 		vl->name = 0;
559 		if (vl->value != 0) {
560 			free(vl->value);
561 			vl->value = 0;
562 		}
563 	}
564 }
565 
566 
567 /*
568  * makequerydata - form a data buffer to be included with a query
569  */
570 static void
571 makequerydata(
572 	struct varlist *vlist,
573 	size_t *datalen,
574 	char *data
575 	)
576 {
577 	register struct varlist *vl;
578 	register char *cp, *cpend;
579 	register size_t namelen, valuelen;
580 	register size_t totallen;
581 
582 	cp = data;
583 	cpend = data + *datalen;
584 
585 	for (vl = vlist; vl < vlist + MAXLIST && vl->name != 0; vl++) {
586 		namelen = strlen(vl->name);
587 		if (vl->value == 0)
588 			valuelen = 0;
589 		else
590 			valuelen = strlen(vl->value);
591 		totallen = namelen + valuelen + (valuelen != 0) + (cp != data);
592 		if (cp + totallen > cpend) {
593 		    xprintf(stderr,
594 			    "***Ignoring variables starting with `%s'\n",
595 			    vl->name);
596 		    break;
597 		}
598 
599 		if (cp != data)
600 			*cp++ = ',';
601 		memcpy(cp, vl->name, (size_t)namelen);
602 		cp += namelen;
603 		if (valuelen != 0) {
604 			*cp++ = '=';
605 			memcpy(cp, vl->value, (size_t)valuelen);
606 			cp += valuelen;
607 		}
608 	}
609 	*datalen = (size_t)(cp - data);
610 }
611 
612 
613 /*
614  * doquerylist - send a message including variables in a list
615  */
616 static int
617 doquerylist(
618 	struct varlist *vlist,
619 	int op,
620 	associd_t associd,
621 	int auth,
622 	u_short *rstatus,
623 	size_t *dsize,
624 	const char **datap
625 	)
626 {
627 	char data[CTL_MAX_DATA_LEN];
628 	size_t datalen;
629 
630 	datalen = sizeof(data);
631 	makequerydata(vlist, &datalen, data);
632 
633 	return doquery(op, associd, auth, datalen, data, rstatus, dsize,
634 		       datap);
635 }
636 
637 
638 /*
639  * doprintvlist - print the variables on a list
640  */
641 static void
642 doprintvlist(
643 	struct varlist *vlist,
644 	FILE *fp
645 	)
646 {
647 	size_t n;
648 
649 	if (NULL == vlist->name) {
650 		xprintf(fp, "No variables on list\n");
651 		return;
652 	}
653 	for (n = 0; n < MAXLIST && vlist[n].name != NULL; n++) {
654 		if (NULL == vlist[n].value)
655 			xprintf(fp, "%s\n", vlist[n].name);
656 		else
657 			xprintf(fp, "%s=%s\n", vlist[n].name,
658 				vlist[n].value);
659 	}
660 }
661 
662 /*
663  * addvars - add variables to the variable list
664  */
665 /*ARGSUSED*/
666 static void
667 addvars(
668 	struct parse *pcmd,
669 	FILE *fp
670 	)
671 {
672 	doaddvlist(g_varlist, pcmd->argval[0].string);
673 }
674 
675 
676 /*
677  * rmvars - remove variables from the variable list
678  */
679 /*ARGSUSED*/
680 static void
681 rmvars(
682 	struct parse *pcmd,
683 	FILE *fp
684 	)
685 {
686 	dormvlist(g_varlist, pcmd->argval[0].string);
687 }
688 
689 
690 /*
691  * clearvars - clear the variable list
692  */
693 /*ARGSUSED*/
694 static void
695 clearvars(
696 	struct parse *pcmd,
697 	FILE *fp
698 	)
699 {
700 	doclearvlist(g_varlist);
701 }
702 
703 
704 /*
705  * showvars - show variables on the variable list
706  */
707 /*ARGSUSED*/
708 static void
709 showvars(
710 	struct parse *pcmd,
711 	FILE *fp
712 	)
713 {
714 	doprintvlist(g_varlist, fp);
715 }
716 
717 
718 /*
719  * dolist - send a request with the given list of variables
720  */
721 static int
722 dolist(
723 	struct varlist *vlist,
724 	associd_t associd,
725 	int op,
726 	int type,
727 	FILE *fp
728 	)
729 {
730 	const char *datap;
731 	int res;
732 	size_t dsize;
733 	u_short rstatus;
734 	int quiet;
735 
736 	/*
737 	 * if we're asking for specific variables don't include the
738 	 * status header line in the output.
739 	 */
740 	if (old_rv)
741 		quiet = 0;
742 	else
743 		quiet = (vlist->name != NULL);
744 
745 	res = doquerylist(vlist, op, associd, 0, &rstatus, &dsize, &datap);
746 
747 	if (res != 0)
748 		return 0;
749 
750 	if (numhosts > 1)
751 		xprintf(fp, "server=%s ", currenthost);
752 	if (dsize == 0) {
753 		if (associd == 0)
754 			xprintf(fp, "No system%s variables returned\n",
755 				(type == TYPE_CLOCK) ? " clock" : "");
756 		else
757 			xprintf(fp,
758 				"No information returned for%s association %u\n",
759 				(type == TYPE_CLOCK) ? " clock" : "",
760 				associd);
761 		return 1;
762 	}
763 
764 	if (!quiet)
765 		xprintf(fp, "associd=%u ", associd);
766 	printvars(dsize, datap, (int)rstatus, type, quiet, fp);
767 	return 1;
768 }
769 
770 
771 /*
772  * readlist - send a read variables request with the variables on the list
773  */
774 static void
775 readlist(
776 	struct parse *pcmd,
777 	FILE *fp
778 	)
779 {
780 	associd_t	associd;
781 	int		type;
782 
783 	if (pcmd->nargs == 0) {
784 		associd = 0;
785 	} else {
786 	  /* HMS: I think we want the u_int32 target here, not the u_long */
787 		if (pcmd->argval[0].uval == 0)
788 			associd = 0;
789 		else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0)
790 			return;
791 	}
792 
793 	type = (0 == associd)
794 		   ? TYPE_SYS
795 		   : TYPE_PEER;
796 	dolist(g_varlist, associd, CTL_OP_READVAR, type, fp);
797 }
798 
799 
800 /*
801  * writelist - send a write variables request with the variables on the list
802  */
803 static void
804 writelist(
805 	struct parse *pcmd,
806 	FILE *fp
807 	)
808 {
809 	const char *datap;
810 	int res;
811 	associd_t associd;
812 	size_t dsize;
813 	u_short rstatus;
814 
815 	if (pcmd->nargs == 0) {
816 		associd = 0;
817 	} else {
818 		/* HMS: Do we really want uval here? */
819 		if (pcmd->argval[0].uval == 0)
820 			associd = 0;
821 		else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0)
822 			return;
823 	}
824 
825 	res = doquerylist(g_varlist, CTL_OP_WRITEVAR, associd, 1, &rstatus,
826 			  &dsize, &datap);
827 
828 	if (res != 0)
829 		return;
830 
831 	if (numhosts > 1)
832 		(void) xprintf(fp, "server=%s ", currenthost);
833 	if (dsize == 0)
834 		(void) xprintf(fp, "done! (no data returned)\n");
835 	else {
836 		(void) xprintf(fp,"associd=%u ", associd);
837 		printvars(dsize, datap, (int)rstatus,
838 			  (associd != 0) ? TYPE_PEER : TYPE_SYS, 0, fp);
839 	}
840 	return;
841 }
842 
843 
844 /*
845  * readvar - send a read variables request with the specified variables
846  */
847 static void
848 readvar(
849 	struct parse *pcmd,
850 	FILE *fp
851 	)
852 {
853 	associd_t	associd;
854 	size_t		tmpcount;
855 	size_t		u;
856 	int		type;
857 	struct varlist	tmplist[MAXLIST];
858 
859 
860 	/* HMS: uval? */
861 	if (pcmd->nargs == 0 || pcmd->argval[0].uval == 0)
862 		associd = 0;
863 	else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0)
864 		return;
865 
866 	ZERO(tmplist);
867 	if (pcmd->nargs > 1) {
868 		tmpcount = pcmd->nargs - 1;
869 		for (u = 0; u < tmpcount; u++)
870 			doaddvlist(tmplist, pcmd->argval[1 + u].string);
871 	}
872 
873 	type = (0 == associd)
874 		   ? TYPE_SYS
875 		   : TYPE_PEER;
876 	dolist(tmplist, associd, CTL_OP_READVAR, type, fp);
877 
878 	doclearvlist(tmplist);
879 }
880 
881 
882 /*
883  * writevar - send a write variables request with the specified variables
884  */
885 static void
886 writevar(
887 	struct parse *pcmd,
888 	FILE *fp
889 	)
890 {
891 	const char *datap;
892 	int res;
893 	associd_t associd;
894 	int type;
895 	size_t dsize;
896 	u_short rstatus;
897 	struct varlist tmplist[MAXLIST];
898 
899 	/* HMS: uval? */
900 	if (pcmd->argval[0].uval == 0)
901 		associd = 0;
902 	else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0)
903 		return;
904 
905 	ZERO(tmplist);
906 	doaddvlist(tmplist, pcmd->argval[1].string);
907 
908 	res = doquerylist(tmplist, CTL_OP_WRITEVAR, associd, 1, &rstatus,
909 			  &dsize, &datap);
910 
911 	doclearvlist(tmplist);
912 
913 	if (res != 0)
914 		return;
915 
916 	if (numhosts > 1)
917 		xprintf(fp, "server=%s ", currenthost);
918 	if (dsize == 0)
919 		xprintf(fp, "done! (no data returned)\n");
920 	else {
921 		xprintf(fp,"associd=%u ", associd);
922 		type = (0 == associd)
923 			   ? TYPE_SYS
924 			   : TYPE_PEER;
925 		printvars(dsize, datap, (int)rstatus, type, 0, fp);
926 	}
927 	return;
928 }
929 
930 
931 /*
932  * clocklist - send a clock variables request with the variables on the list
933  */
934 static void
935 clocklist(
936 	struct parse *pcmd,
937 	FILE *fp
938 	)
939 {
940 	associd_t associd;
941 
942 	/* HMS: uval? */
943 	if (pcmd->nargs == 0) {
944 		associd = 0;
945 	} else {
946 		if (pcmd->argval[0].uval == 0)
947 			associd = 0;
948 		else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0)
949 			return;
950 	}
951 
952 	dolist(g_varlist, associd, CTL_OP_READCLOCK, TYPE_CLOCK, fp);
953 }
954 
955 
956 /*
957  * clockvar - send a clock variables request with the specified variables
958  */
959 static void
960 clockvar(
961 	struct parse *pcmd,
962 	FILE *fp
963 	)
964 {
965 	associd_t associd;
966 	struct varlist tmplist[MAXLIST];
967 
968 	/* HMS: uval? */
969 	if (pcmd->nargs == 0 || pcmd->argval[0].uval == 0)
970 		associd = 0;
971 	else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0)
972 		return;
973 
974 	ZERO(tmplist);
975 	if (pcmd->nargs >= 2)
976 		doaddvlist(tmplist, pcmd->argval[1].string);
977 
978 	dolist(tmplist, associd, CTL_OP_READCLOCK, TYPE_CLOCK, fp);
979 
980 	doclearvlist(tmplist);
981 }
982 
983 
984 /*
985  * findassidrange - verify a range of association ID's
986  */
987 static int
988 findassidrange(
989 	u_int32	assid1,
990 	u_int32	assid2,
991 	int *	from,
992 	int *	to,
993 	FILE *	fp
994 	)
995 {
996 	associd_t	assids[2];
997 	int		ind[COUNTOF(assids)];
998 	u_int		i;
999 	size_t		a;
1000 
1001 
1002 	if (0 == numassoc)
1003 		dogetassoc(fp);
1004 
1005 	assids[0] = checkassocid(assid1);
1006 	if (0 == assids[0])
1007 		return 0;
1008 	assids[1] = checkassocid(assid2);
1009 	if (0 == assids[1])
1010 		return 0;
1011 
1012 	for (a = 0; a < COUNTOF(assids); a++) {
1013 		ind[a] = -1;
1014 		for (i = 0; i < numassoc; i++)
1015 			if (assoc_cache[i].assid == assids[a])
1016 				ind[a] = i;
1017 	}
1018 	for (a = 0; a < COUNTOF(assids); a++)
1019 		if (-1 == ind[a]) {
1020 			xprintf(stderr,
1021 				"***Association ID %u not found in list\n",
1022 				assids[a]);
1023 			return 0;
1024 		}
1025 
1026 	if (ind[0] < ind[1]) {
1027 		*from = ind[0];
1028 		*to = ind[1];
1029 	} else {
1030 		*to = ind[0];
1031 		*from = ind[1];
1032 	}
1033 	return 1;
1034 }
1035 
1036 
1037 
1038 /*
1039  * mreadlist - send a read variables request for multiple associations
1040  */
1041 static void
1042 mreadlist(
1043 	struct parse *pcmd,
1044 	FILE *fp
1045 	)
1046 {
1047 	int i;
1048 	int from;
1049 	int to;
1050 
1051 	if (!findassidrange(pcmd->argval[0].uval, pcmd->argval[1].uval,
1052 			    &from, &to, fp))
1053 		return;
1054 
1055 	for (i = from; i <= to; i++) {
1056 		if (i != from)
1057 			xprintf(fp, "\n");
1058 		if (!dolist(g_varlist, assoc_cache[i].assid,
1059 			    CTL_OP_READVAR, TYPE_PEER, fp))
1060 			return;
1061 	}
1062 	return;
1063 }
1064 
1065 
1066 /*
1067  * mreadvar - send a read variables request for multiple associations
1068  */
1069 static void
1070 mreadvar(
1071 	struct parse *pcmd,
1072 	FILE *fp
1073 	)
1074 {
1075 	int i;
1076 	int from;
1077 	int to;
1078 	struct varlist tmplist[MAXLIST];
1079 	struct varlist *pvars;
1080 
1081 	if (!findassidrange(pcmd->argval[0].uval, pcmd->argval[1].uval,
1082 				&from, &to, fp))
1083 		return;
1084 
1085 	ZERO(tmplist);
1086 	if (pcmd->nargs >= 3) {
1087 		doaddvlist(tmplist, pcmd->argval[2].string);
1088 		pvars = tmplist;
1089 	} else {
1090 		pvars = g_varlist;
1091 	}
1092 
1093 	for (i = from; i <= to; i++) {
1094 		if (!dolist(pvars, assoc_cache[i].assid, CTL_OP_READVAR,
1095 			    TYPE_PEER, fp))
1096 			break;
1097 	}
1098 
1099 	if (pvars == tmplist)
1100 		doclearvlist(tmplist);
1101 
1102 	return;
1103 }
1104 
1105 
1106 /*
1107  * dogetassoc - query the host for its list of associations
1108  */
1109 int
1110 dogetassoc(
1111 	FILE *fp
1112 	)
1113 {
1114 	const char *datap;
1115 	const u_short *pus;
1116 	int res;
1117 	size_t dsize;
1118 	u_short rstatus;
1119 
1120 	res = doquery(CTL_OP_READSTAT, 0, 0, 0, (char *)0, &rstatus,
1121 			  &dsize, &datap);
1122 
1123 	if (res != 0)
1124 		return 0;
1125 
1126 	if (dsize == 0) {
1127 		if (numhosts > 1)
1128 			xprintf(fp, "server=%s ", currenthost);
1129 		xprintf(fp, "No association ID's returned\n");
1130 		return 0;
1131 	}
1132 
1133 	if (dsize & 0x3) {
1134 		if (numhosts > 1)
1135 			xprintf(stderr, "server=%s ", currenthost);
1136 		xprintf(stderr,
1137 			"***Server returned %zu octets, should be multiple of 4\n",
1138 			dsize);
1139 		return 0;
1140 	}
1141 
1142 	numassoc = 0;
1143 
1144 	while (dsize > 0) {
1145 		if (numassoc >= assoc_cache_slots) {
1146 			grow_assoc_cache();
1147 		}
1148 		pus = (const void *)datap;
1149 		assoc_cache[numassoc].assid = ntohs(*pus);
1150 		datap += sizeof(*pus);
1151 		pus = (const void *)datap;
1152 		assoc_cache[numassoc].status = ntohs(*pus);
1153 		datap += sizeof(*pus);
1154 		dsize -= 2 * sizeof(*pus);
1155 		if (debug) {
1156 			xprintf(stderr, "[%u] ",
1157 				assoc_cache[numassoc].assid);
1158 		}
1159 		numassoc++;
1160 	}
1161 	if (debug) {
1162 		xprintf(stderr, "\n%d associations total\n", numassoc);
1163 	}
1164 	sortassoc();
1165 	return 1;
1166 }
1167 
1168 
1169 /*
1170  * printassoc - print the current list of associations
1171  */
1172 static void
1173 printassoc(
1174 	int showall,
1175 	FILE *fp
1176 	)
1177 {
1178 	register char *bp;
1179 	u_int i;
1180 	u_char statval;
1181 	int event;
1182 	u_long event_count;
1183 	const char *conf;
1184 	const char *reach;
1185 	const char *auth;
1186 	const char *condition = "";
1187 	const char *last_event;
1188 	char buf[128];
1189 	char numev[32];
1190 
1191 	if (numassoc == 0) {
1192 		(void) xprintf(fp, "No association ID's in list\n");
1193 		return;
1194 	}
1195 
1196 	/*
1197 	 * Output a header
1198 	 */
1199 	(void) xprintf(fp,
1200 			   "ind assid status  conf reach auth condition  last_event cnt\n");
1201 	(void) xprintf(fp,
1202 			   "===========================================================\n");
1203 	for (i = 0; i < numassoc; i++) {
1204 		statval = (u_char) CTL_PEER_STATVAL(assoc_cache[i].status);
1205 		if (!showall && !(statval & (CTL_PST_CONFIG|CTL_PST_REACH)))
1206 			continue;
1207 		event = CTL_PEER_EVENT(assoc_cache[i].status);
1208 		event_count = CTL_PEER_NEVNT(assoc_cache[i].status);
1209 		if (statval & CTL_PST_CONFIG)
1210 			conf = "yes";
1211 		else
1212 			conf = "no";
1213 		if (statval & CTL_PST_BCAST) {
1214 			reach = "none";
1215 			if (statval & CTL_PST_AUTHENABLE)
1216 				auth = "yes";
1217 			else
1218 				auth = "none";
1219 		} else {
1220 			if (statval & CTL_PST_REACH)
1221 				reach = "yes";
1222 			else
1223 				reach = "no";
1224 			if (statval & CTL_PST_AUTHENABLE) {
1225 				if (statval & CTL_PST_AUTHENTIC)
1226 					auth = "ok ";
1227 				else
1228 					auth = "bad";
1229 			} else {
1230 				auth = "none";
1231 			}
1232 		}
1233 		if (pktversion > NTP_OLDVERSION) {
1234 			switch (statval & 0x7) {
1235 
1236 			case CTL_PST_SEL_REJECT:
1237 				condition = "reject";
1238 				break;
1239 
1240 			case CTL_PST_SEL_SANE:
1241 				condition = "falsetick";
1242 				break;
1243 
1244 			case CTL_PST_SEL_CORRECT:
1245 				condition = "excess";
1246 				break;
1247 
1248 			case CTL_PST_SEL_SELCAND:
1249 				condition = "outlier";
1250 				break;
1251 
1252 			case CTL_PST_SEL_SYNCCAND:
1253 				condition = "candidate";
1254 				break;
1255 
1256 			case CTL_PST_SEL_EXCESS:
1257 				condition = "backup";
1258 				break;
1259 
1260 			case CTL_PST_SEL_SYSPEER:
1261 				condition = "sys.peer";
1262 				break;
1263 
1264 			case CTL_PST_SEL_PPS:
1265 				condition = "pps.peer";
1266 				break;
1267 			}
1268 		} else {
1269 			switch (statval & 0x3) {
1270 
1271 			case OLD_CTL_PST_SEL_REJECT:
1272 				if (!(statval & OLD_CTL_PST_SANE))
1273 					condition = "insane";
1274 				else if (!(statval & OLD_CTL_PST_DISP))
1275 					condition = "hi_disp";
1276 				else
1277 					condition = "";
1278 				break;
1279 
1280 			case OLD_CTL_PST_SEL_SELCAND:
1281 				condition = "sel_cand";
1282 				break;
1283 
1284 			case OLD_CTL_PST_SEL_SYNCCAND:
1285 				condition = "sync_cand";
1286 				break;
1287 
1288 			case OLD_CTL_PST_SEL_SYSPEER:
1289 				condition = "sys_peer";
1290 				break;
1291 			}
1292 		}
1293 		switch (PEER_EVENT|event) {
1294 
1295 		case PEVNT_MOBIL:
1296 			last_event = "mobilize";
1297 			break;
1298 
1299 		case PEVNT_DEMOBIL:
1300 			last_event = "demobilize";
1301 			break;
1302 
1303 		case PEVNT_REACH:
1304 			last_event = "reachable";
1305 			break;
1306 
1307 		case PEVNT_UNREACH:
1308 			last_event = "unreachable";
1309 			break;
1310 
1311 		case PEVNT_RESTART:
1312 			last_event = "restart";
1313 			break;
1314 
1315 		case PEVNT_REPLY:
1316 			last_event = "no_reply";
1317 			break;
1318 
1319 		case PEVNT_RATE:
1320 			last_event = "rate_exceeded";
1321 			break;
1322 
1323 		case PEVNT_DENY:
1324 			last_event = "access_denied";
1325 			break;
1326 
1327 		case PEVNT_ARMED:
1328 			last_event = "leap_armed";
1329 			break;
1330 
1331 		case PEVNT_NEWPEER:
1332 			last_event = "sys_peer";
1333 			break;
1334 
1335 		case PEVNT_CLOCK:
1336 			last_event = "clock_alarm";
1337 			break;
1338 
1339 		case PEVNT_AUTH:
1340 			last_event = "bad_auth";
1341 			break;
1342 
1343 		case PEVNT_POPCORN:
1344 			last_event = "popcorn";
1345 			break;
1346 
1347 		case PEVNT_XLEAVE:
1348 			last_event = "interleave";
1349 			break;
1350 
1351 		case PEVNT_XERR:
1352 			last_event = "xleave_err";
1353 			break;
1354 
1355 		default:
1356 			snprintf(numev, sizeof(numev), "<?%x?>", event);
1357 			last_event = numev;
1358 			break;
1359 		}
1360 		snprintf(buf, sizeof(buf),
1361 			 "%3d %5u  %04x   %3.3s  %4s  %4.4s %9.9s %11s %2lu",
1362 			 i + 1, assoc_cache[i].assid,
1363 			 assoc_cache[i].status, conf, reach, auth,
1364 			 condition, last_event, event_count);
1365 		bp = buf + strlen(buf);
1366 		while (bp > buf && ' ' == bp[-1])
1367 			--bp;
1368 		bp[0] = '\0';
1369 		xprintf(fp, "%s\n", buf);
1370 	}
1371 }
1372 
1373 
1374 /*
1375  * associations - get, record and print a list of associations
1376  */
1377 /*ARGSUSED*/
1378 static void
1379 associations(
1380 	struct parse *pcmd,
1381 	FILE *fp
1382 	)
1383 {
1384 	if (dogetassoc(fp))
1385 		printassoc(0, fp);
1386 }
1387 
1388 
1389 /*
1390  * lassociations - get, record and print a long list of associations
1391  */
1392 /*ARGSUSED*/
1393 static void
1394 lassociations(
1395 	struct parse *pcmd,
1396 	FILE *fp
1397 	)
1398 {
1399 	if (dogetassoc(fp))
1400 		printassoc(1, fp);
1401 }
1402 
1403 
1404 /*
1405  * passociations - print the association list
1406  */
1407 /*ARGSUSED*/
1408 static void
1409 passociations(
1410 	struct parse *pcmd,
1411 	FILE *fp
1412 	)
1413 {
1414 	printassoc(0, fp);
1415 }
1416 
1417 
1418 /*
1419  * lpassociations - print the long association list
1420  */
1421 /*ARGSUSED*/
1422 static void
1423 lpassociations(
1424 	struct parse *pcmd,
1425 	FILE *fp
1426 	)
1427 {
1428 	printassoc(1, fp);
1429 }
1430 
1431 
1432 /*
1433  *  saveconfig - dump ntp server configuration to server file
1434  */
1435 static void
1436 saveconfig(
1437 	struct parse *pcmd,
1438 	FILE *fp
1439 	)
1440 {
1441 	const char *datap;
1442 	int res;
1443 	size_t dsize;
1444 	u_short rstatus;
1445 
1446 	if (0 == pcmd->nargs)
1447 		return;
1448 
1449 	res = doquery(CTL_OP_SAVECONFIG, 0, 1,
1450 		      strlen(pcmd->argval[0].string),
1451 		      pcmd->argval[0].string, &rstatus, &dsize,
1452 		      &datap);
1453 
1454 	if (res != 0)
1455 		return;
1456 
1457 	if (0 == dsize)
1458 		xprintf(fp, "(no response message, curiously)");
1459 	else
1460 		xprintf(fp, "%.*s", (int)dsize, datap); /* cast is wobbly */
1461 }
1462 
1463 
1464 #ifdef	UNUSED
1465 /*
1466  * radiostatus - print the radio status returned by the server
1467  */
1468 /*ARGSUSED*/
1469 static void
1470 radiostatus(
1471 	struct parse *pcmd,
1472 	FILE *fp
1473 	)
1474 {
1475 	char *datap;
1476 	int res;
1477 	int dsize;
1478 	u_short rstatus;
1479 
1480 	res = doquery(CTL_OP_READCLOCK, 0, 0, 0, (char *)0, &rstatus,
1481 			  &dsize, &datap);
1482 
1483 	if (res != 0)
1484 		return;
1485 
1486 	if (numhosts > 1)
1487 		(void) xprintf(fp, "server=%s ", currenthost);
1488 	if (dsize == 0) {
1489 		(void) xprintf(fp, "No radio status string returned\n");
1490 		return;
1491 	}
1492 
1493 	asciize(dsize, datap, fp);
1494 }
1495 #endif	/* UNUSED */
1496 
1497 /*
1498  * when - return how long its been since his last packet arrived
1499  */
1500 static long
1501 when(
1502 	l_fp *ts,
1503 	l_fp *rec,
1504 	l_fp *reftime
1505 	)
1506 {
1507 	l_fp *lasttime;
1508 
1509 	if (rec->l_ui != 0)
1510 		lasttime = rec;
1511 	else if (reftime->l_ui != 0)
1512 		lasttime = reftime;
1513 	else
1514 		return 0;
1515 
1516 	if (ts->l_ui < lasttime->l_ui)
1517 		return -1;
1518 	return (ts->l_ui - lasttime->l_ui);
1519 }
1520 
1521 
1522 /*
1523  * Pretty-print an interval into the given buffer, in a human-friendly format.
1524  */
1525 static char *
1526 prettyinterval(
1527 	char *buf,
1528 	size_t cb,
1529 	long diff
1530 	)
1531 {
1532 	if (diff <= 0) {
1533 		buf[0] = '-';
1534 		buf[1] = 0;
1535 		return buf;
1536 	}
1537 
1538 	if (diff <= 2048) {
1539 		snprintf(buf, cb, "%u", (unsigned int)diff);
1540 		return buf;
1541 	}
1542 
1543 	diff = (diff + 29) / 60;
1544 	if (diff <= 300) {
1545 		snprintf(buf, cb, "%um", (unsigned int)diff);
1546 		return buf;
1547 	}
1548 
1549 	diff = (diff + 29) / 60;
1550 	if (diff <= 96) {
1551 		snprintf(buf, cb, "%uh", (unsigned int)diff);
1552 		return buf;
1553 	}
1554 
1555 	diff = (diff + 11) / 24;
1556 	if (diff <= 999) {
1557 		snprintf(buf, cb, "%ud", (unsigned int)diff);
1558 		return buf;
1559 	}
1560 
1561 	/* years are only approximated... */
1562 	diff = (long)floor(diff / 365.25 + 0.5);
1563 	if (diff <= 999) {
1564 		snprintf(buf, cb, "%uy", (unsigned int)diff);
1565 		return buf;
1566 	}
1567 	/* Ok, this amounts to infinity... */
1568 	strlcpy(buf, "INF", cb);
1569 	return buf;
1570 }
1571 
1572 static char
1573 decodeaddrtype(
1574 	sockaddr_u *sock
1575 	)
1576 {
1577 	char ch = '-';
1578 	u_int32 dummy;
1579 
1580 	switch(AF(sock)) {
1581 	case AF_INET:
1582 		dummy = SRCADR(sock);
1583 		ch = (char)(((dummy&0xf0000000)==0xe0000000) ? 'm' :
1584 			((dummy&0x000000ff)==0x000000ff) ? 'b' :
1585 			((dummy&0xffffffff)==0x7f000001) ? 'l' :
1586 			((dummy&0xffffffe0)==0x00000000) ? '-' :
1587 			'u');
1588 		break;
1589 	case AF_INET6:
1590 		if (IN6_IS_ADDR_MULTICAST(PSOCK_ADDR6(sock)))
1591 			ch = 'm';
1592 		else
1593 			ch = 'u';
1594 		break;
1595 	default:
1596 		ch = '-';
1597 		break;
1598 	}
1599 	return ch;
1600 }
1601 
1602 /*
1603  * A list of variables required by the peers command
1604  */
1605 struct varlist opeervarlist[] = {
1606 	{ "srcadr",	0 },	/* 0 */
1607 	{ "dstadr",	0 },	/* 1 */
1608 	{ "stratum",	0 },	/* 2 */
1609 	{ "hpoll",	0 },	/* 3 */
1610 	{ "ppoll",	0 },	/* 4 */
1611 	{ "reach",	0 },	/* 5 */
1612 	{ "delay",	0 },	/* 6 */
1613 	{ "offset",	0 },	/* 7 */
1614 	{ "jitter",	0 },	/* 8 */
1615 	{ "dispersion", 0 },	/* 9 */
1616 	{ "rec",	0 },	/* 10 */
1617 	{ "reftime",	0 },	/* 11 */
1618 	{ "srcport",	0 },	/* 12 */
1619 	{ "hmode",	0 },	/* 13 */
1620 	{ 0,		0 }
1621 };
1622 
1623 struct varlist peervarlist[] = {
1624 	{ "srcadr",	0 },	/* 0 */
1625 	{ "refid",	0 },	/* 1 */
1626 	{ "stratum",	0 },	/* 2 */
1627 	{ "hpoll",	0 },	/* 3 */
1628 	{ "ppoll",	0 },	/* 4 */
1629 	{ "reach",	0 },	/* 5 */
1630 	{ "delay",	0 },	/* 6 */
1631 	{ "offset",	0 },	/* 7 */
1632 	{ "jitter",	0 },	/* 8 */
1633 	{ "dispersion", 0 },	/* 9 */
1634 	{ "rec",	0 },	/* 10 */
1635 	{ "reftime",	0 },	/* 11 */
1636 	{ "srcport",	0 },	/* 12 */
1637 	{ "hmode",	0 },	/* 13 */
1638 	{ "srchost",	0 },	/* 14 */
1639 	{ 0,		0 }
1640 };
1641 
1642 struct varlist apeervarlist[] = {
1643 	{ "srcadr",	0 },	/* 0 */
1644 	{ "refid",	0 },	/* 1 */
1645 	{ "assid",	0 },	/* 2 */
1646 	{ "stratum",	0 },	/* 3 */
1647 	{ "hpoll",	0 },	/* 4 */
1648 	{ "ppoll",	0 },	/* 5 */
1649 	{ "reach",	0 },	/* 6 */
1650 	{ "delay",	0 },	/* 7 */
1651 	{ "offset",	0 },	/* 8 */
1652 	{ "jitter",	0 },	/* 9 */
1653 	{ "dispersion", 0 },	/* 10 */
1654 	{ "rec",	0 },	/* 11 */
1655 	{ "reftime",	0 },	/* 12 */
1656 	{ "srcport",	0 },	/* 13 */
1657 	{ "hmode",	0 },	/* 14 */
1658 	{ "srchost",	0 },	/* 15 */
1659 	{ 0,		0 }
1660 };
1661 
1662 
1663 /*
1664  * Decode an incoming data buffer and print a line in the peer list
1665  */
1666 static int
1667 doprintpeers(
1668 	struct varlist *pvl,
1669 	int associd,
1670 	int rstatus,
1671 	size_t datalen,
1672 	const char *data,
1673 	FILE *fp,
1674 	int af
1675 	)
1676 {
1677 	char *name;
1678 	char *value = NULL;
1679 	int c;
1680 	size_t len;
1681 	int have_srchost;
1682 	int have_dstadr;
1683 	int have_da_rid;
1684 	int have_jitter;
1685 	sockaddr_u srcadr;
1686 	sockaddr_u dstadr;
1687 	sockaddr_u dum_store;
1688 	sockaddr_u refidadr;
1689 	long hmode = 0;
1690 	u_long srcport = 0;
1691 	u_int32 u32;
1692 	const char *dstadr_refid = "0.0.0.0";
1693 	const char *serverlocal;
1694 	char *drbuf = NULL;
1695 	size_t drlen;
1696 	u_long stratum = 0;
1697 	long ppoll = 0;
1698 	long hpoll = 0;
1699 	u_long reach = 0;
1700 	l_fp estoffset;
1701 	l_fp estdelay;
1702 	l_fp estjitter;
1703 	l_fp estdisp;
1704 	l_fp reftime;
1705 	l_fp rec;
1706 	l_fp ts;
1707 	u_long poll_sec;
1708 	u_long flash = 0;
1709 	char type = '?';
1710 	char clock_name[LENHOSTNAME];
1711 	char whenbuf[12], pollbuf[12];
1712 	/* [Bug 3482] formally whenbuf & pollbuf should be able to hold
1713 	 * a full signed int. Not that we would use that much string
1714 	 * data for it...
1715 	 */
1716 	get_systime(&ts);
1717 
1718 	have_srchost = FALSE;
1719 	have_dstadr = FALSE;
1720 	have_da_rid = FALSE;
1721 	have_jitter = FALSE;
1722 	ZERO_SOCK(&srcadr);
1723 	ZERO_SOCK(&dstadr);
1724 	clock_name[0] = '\0';
1725 	ZERO(estoffset);
1726 	ZERO(estdelay);
1727 	ZERO(estjitter);
1728 	ZERO(estdisp);
1729 
1730 	while (nextvar(&datalen, &data, &name, &value)) {
1731 		INSIST(name && value);
1732 		if (!strcmp("srcadr", name) ||
1733 		    !strcmp("peeradr", name)) {
1734 			if (!decodenetnum(value, &srcadr))
1735 				xprintf(stderr, "malformed %s=%s\n",
1736 					name, value);
1737 		} else if (!strcmp("srchost", name)) {
1738 			if (pvl == peervarlist || pvl == apeervarlist) {
1739 				len = strlen(value);
1740 				if (2 < len &&
1741 				    (size_t)len < sizeof(clock_name)) {
1742 					/* strip quotes */
1743 					value++;
1744 					len -= 2;
1745 					memcpy(clock_name, value, len);
1746 					clock_name[len] = '\0';
1747 					have_srchost = TRUE;
1748 				}
1749 			}
1750 		} else if (!strcmp("dstadr", name)) {
1751 			if (decodenetnum(value, &dum_store)) {
1752 				type = decodeaddrtype(&dum_store);
1753 				have_dstadr = TRUE;
1754 				dstadr = dum_store;
1755 				if (pvl == opeervarlist) {
1756 					have_da_rid = TRUE;
1757 					dstadr_refid = trunc_left(stoa(&dstadr), 15);
1758 				}
1759 			}
1760 		} else if (!strcmp("hmode", name)) {
1761 			decodeint(value, &hmode);
1762 		} else if (!strcmp("refid", name)) {
1763 			if (   (pvl == peervarlist)
1764 			    && (drefid == REFID_IPV4)) {
1765 				have_da_rid = TRUE;
1766 				drlen = strlen(value);
1767 				if (0 == drlen) {
1768 					dstadr_refid = "";
1769 				} else if (drlen <= 4) {
1770 					ZERO(u32);
1771 					memcpy(&u32, value, drlen);
1772 					dstadr_refid = refid_str(u32, 1);
1773 				} else if (decodenetnum(value, &refidadr)) {
1774 					if (SOCK_UNSPEC(&refidadr))
1775 						dstadr_refid = "0.0.0.0";
1776 					else if (ISREFCLOCKADR(&refidadr)) {
1777 						dstadr_refid =
1778 						    refnumtoa(&refidadr);
1779 					} else {
1780 						dstadr_refid =
1781 						    stoa(&refidadr);
1782 					}
1783 				} else {
1784 					have_da_rid = FALSE;
1785 				}
1786 			} else if (   (pvl == apeervarlist)
1787 				   || (pvl == peervarlist)) {
1788 				/* no need to check drefid == REFID_HASH */
1789 				have_da_rid = TRUE;
1790 				drlen = strlen(value);
1791 				if (0 == drlen) {
1792 					dstadr_refid = "";
1793 				} else if (drlen <= 4) {
1794 					ZERO(u32);
1795 					memcpy(&u32, value, drlen);
1796 					dstadr_refid = refid_str(u32, 1);
1797 					//xprintf(stderr, "apeervarlist S1 refid: value=<%s>\n", value);
1798 				} else if (decodenetnum(value, &refidadr)) {
1799 					if (SOCK_UNSPEC(&refidadr))
1800 						dstadr_refid = "0.0.0.0";
1801 					else if (ISREFCLOCKADR(&refidadr)) {
1802 						dstadr_refid =
1803 							refnumtoa(&refidadr);
1804 						if (pvl == apeervarlist) {
1805 							/*
1806 							 * restrict refid to
1807 							 * 8 chars [Bug 3850]
1808 							 */
1809 							dstadr_refid =
1810 								trunc_right(
1811 									dstadr_refid,
1812 									8);
1813 						}
1814 					} else {
1815 						drbuf = emalloc(10);
1816 						snprintf(drbuf, 10, "%0x",
1817 							 SRCADR(&refidadr));
1818 						dstadr_refid = drbuf;
1819 					}
1820 				} else {
1821 					have_da_rid = FALSE;
1822 				}
1823 			}
1824 		} else if (!strcmp("stratum", name)) {
1825 			decodeuint(value, &stratum);
1826 		} else if (!strcmp("hpoll", name)) {
1827 			if (decodeint(value, &hpoll) && hpoll < 0)
1828 				hpoll = NTP_MINPOLL;
1829 		} else if (!strcmp("ppoll", name)) {
1830 			if (decodeint(value, &ppoll) && ppoll < 0)
1831 				ppoll = NTP_MINPOLL;
1832 		} else if (!strcmp("reach", name)) {
1833 			decodeuint(value, &reach);
1834 		} else if (!strcmp("delay", name)) {
1835 			decodetime(value, &estdelay);
1836 		} else if (!strcmp("offset", name)) {
1837 			decodetime(value, &estoffset);
1838 		} else if (!strcmp("jitter", name)) {
1839 			if ((pvl == peervarlist || pvl == apeervarlist)
1840 			    && decodetime(value, &estjitter))
1841 				have_jitter = 1;
1842 		} else if (!strcmp("rootdisp", name) ||
1843 			   !strcmp("dispersion", name)) {
1844 			decodetime(value, &estdisp);
1845 		} else if (!strcmp("rec", name)) {
1846 			decodets(value, &rec);
1847 		} else if (!strcmp("srcport", name) ||
1848 			   !strcmp("peerport", name)) {
1849 			decodeuint(value, &srcport);
1850 		} else if (!strcmp("reftime", name)) {
1851 			if (!decodets(value, &reftime))
1852 				L_CLR(&reftime);
1853 		} else if (!strcmp("flash", name)) {
1854 			decodeuint(value, &flash);
1855 		} else {
1856 			// xprintf(stderr, "UNRECOGNIZED name=%s ", name);
1857 		}
1858 	}
1859 
1860 	/*
1861 	 * hmode gives the best guidance for the t column.  If the response
1862 	 * did not include hmode we'll use the old decodeaddrtype() result.
1863 	 */
1864 	switch (hmode) {
1865 
1866 	case MODE_BCLIENT:
1867 		/* broadcastclient or multicastclient */
1868 		type = 'b';
1869 		break;
1870 
1871 	case MODE_BROADCAST:
1872 		/* broadcast or multicast server */
1873 		if (IS_MCAST(&srcadr))
1874 			type = 'M';
1875 		else
1876 			type = 'B';
1877 		break;
1878 
1879 	case MODE_CLIENT:
1880 		if (ISREFCLOCKADR(&srcadr))
1881 			type = 'l';	/* local refclock*/
1882 		else if (SOCK_UNSPEC(&srcadr))
1883 			type = 'p';	/* pool */
1884 		else if (IS_MCAST(&srcadr))
1885 			type = 'a';	/* manycastclient */
1886 		else
1887 			type = 'u';	/* unicast */
1888 		break;
1889 
1890 	case MODE_ACTIVE:
1891 		type = 's';		/* symmetric active */
1892 		break;			/* configured */
1893 
1894 	case MODE_PASSIVE:
1895 		type = 'S';		/* symmetric passive */
1896 		break;			/* ephemeral */
1897 	}
1898 
1899 	/*
1900 	 * Got everything, format the line
1901 	 */
1902 	poll_sec = 1 << min(ppoll, hpoll);
1903 	if (pktversion > NTP_OLDVERSION)
1904 		c = flash3[CTL_PEER_STATVAL(rstatus) & 0x7];
1905 	else
1906 		c = flash2[CTL_PEER_STATVAL(rstatus) & 0x3];
1907 	if (numhosts > 1) {
1908 		if ((pvl == peervarlist || pvl == apeervarlist)
1909 		    && have_dstadr) {
1910 			serverlocal = nntohost_col(&dstadr,
1911 			    (size_t)min(LIB_BUFLENGTH - 1, maxhostlen),
1912 			    TRUE);
1913 		} else {
1914 			if (currenthostisnum)
1915 				serverlocal = trunc_left(currenthost,
1916 							 maxhostlen);
1917 			else
1918 				serverlocal = currenthost;
1919 		}
1920 		xprintf(fp, "%-*s ", (int)maxhostlen, serverlocal);
1921 	}
1922 	if (AF_UNSPEC == af || AF(&srcadr) == af) {
1923 		if (!have_srchost)
1924 			strlcpy(clock_name, nntohost(&srcadr),
1925 				sizeof(clock_name));
1926 		/* wide and long source - space over on next line */
1927 		/* allow for host + sp if > 1 and regular tally + source + sp */
1928 		if (wideremote && 15 < strlen(clock_name))
1929 			xprintf(fp, "%c%s\n%*s", c, clock_name,
1930 				((numhosts > 1) ? (int)maxhostlen + 1 : 0)
1931 							+ 1 + 15 + 1, "");
1932 		else
1933 			xprintf(fp, "%c%-15.15s ", c, clock_name);
1934 		if ((flash & TEST12) && (pvl != opeervarlist)) {
1935 			drlen = xprintf(fp, "(loop)");
1936 		} else if (!have_da_rid) {
1937 			drlen = 0;
1938 		} else {
1939 			drlen = strlen(dstadr_refid);
1940 			makeascii(drlen, dstadr_refid, fp);
1941 		}
1942 		free(drbuf);
1943 		if (pvl == apeervarlist) {
1944 			while (drlen++ < 9)
1945 				xputc(' ', fp);
1946 			xprintf(fp, "%-6d", associd);
1947 		} else {
1948 			while (drlen++ < 15)
1949 				xputc(' ', fp);
1950 		}
1951 		xprintf(fp,
1952 			" %2ld %c %4.4s %4.4s  %3lo  %7.7s %8.7s %7.7s\n",
1953 			stratum, type,
1954 			prettyinterval(whenbuf, sizeof(whenbuf),
1955 				       when(&ts, &rec, &reftime)),
1956 			prettyinterval(pollbuf, sizeof(pollbuf),
1957 				       (int)poll_sec),
1958 			reach, ulfptoms(&estdelay, 3),
1959 			lfptoms(&estoffset, 3),
1960 			(have_jitter)
1961 			    ? ulfptoms(&estjitter, 3)
1962 			    : ulfptoms(&estdisp, 3));
1963 		return (1);
1964 	}
1965 	else
1966 		return(1);
1967 }
1968 
1969 
1970 /*
1971  * dogetpeers - given an association ID, read and print the spreadsheet
1972  *		peer variables.
1973  */
1974 static int
1975 dogetpeers(
1976 	struct varlist *pvl,
1977 	associd_t associd,
1978 	FILE *fp,
1979 	int af
1980 	)
1981 {
1982 	const char *datap;
1983 	int res;
1984 	size_t dsize;
1985 	u_short rstatus;
1986 
1987 #ifdef notdef
1988 	res = doquerylist(pvl, CTL_OP_READVAR, associd, 0, &rstatus,
1989 			  &dsize, &datap);
1990 #else
1991 	/*
1992 	 * Damn fuzzballs
1993 	 */
1994 	res = doquery(CTL_OP_READVAR, associd, 0, 0, NULL, &rstatus,
1995 			  &dsize, &datap);
1996 #endif
1997 
1998 	if (res != 0)
1999 		return 0;
2000 
2001 	if (dsize == 0) {
2002 		if (numhosts > 1)
2003 			xprintf(stderr, "server=%s ", currenthost);
2004 		xprintf(stderr,
2005 			"***No information returned for association %u\n",
2006 			associd);
2007 		return 0;
2008 	}
2009 
2010 	return doprintpeers(pvl, associd, (int)rstatus, dsize, datap,
2011 			    fp, af);
2012 }
2013 
2014 
2015 /*
2016  * peers - print a peer spreadsheet
2017  */
2018 static void
2019 dopeers(
2020 	int showall,
2021 	FILE *fp,
2022 	int af
2023 	)
2024 {
2025 	u_int		u;
2026 	char		fullname[LENHOSTNAME];
2027 	sockaddr_u	netnum;
2028 	const char *	name_or_num;
2029 	size_t		sl;
2030 
2031 	if (!dogetassoc(fp))
2032 		return;
2033 
2034 	if (numhosts > 1) {
2035 		for (u = 0; u < numhosts; u++) {
2036 			if (getnetnum(chosts[u].name, &netnum, fullname, af)) {
2037 				name_or_num = nntohost(&netnum);
2038 				sl = strlen(name_or_num);
2039 				maxhostlen = max(maxhostlen, sl);
2040 			}
2041 		}
2042 		xprintf(fp, "%-*.*s ", (int)maxhostlen, (int)maxhostlen,
2043 			"server (local)");
2044 	}
2045 	xprintf(fp,
2046 		"     remote           refid      st t when poll reach   delay   offset  jitter\n");
2047 	if (numhosts > 1)
2048 		for (u = 0; u <= maxhostlen; u++)
2049 			xprintf(fp, "=");
2050 	xprintf(fp,
2051 		"==============================================================================\n");
2052 
2053 	for (u = 0; u < numassoc; u++) {
2054 		if (!showall &&
2055 		    !(CTL_PEER_STATVAL(assoc_cache[u].status)
2056 		      & (CTL_PST_CONFIG|CTL_PST_REACH))) {
2057 			if (debug)
2058 				xprintf(stderr, "eliding [%d]\n",
2059 					(int)assoc_cache[u].assid);
2060 			continue;
2061 		}
2062 		if (!dogetpeers(peervarlist, (int)assoc_cache[u].assid,
2063 				fp, af))
2064 			return;
2065 	}
2066 	return;
2067 }
2068 
2069 
2070 /*
2071  * doapeers - print a peer spreadsheet with assocIDs
2072  */
2073 static void
2074 doapeers(
2075 	int showall,
2076 	FILE *fp,
2077 	int af
2078 	)
2079 {
2080 	u_int		u;
2081 	char		fullname[LENHOSTNAME];
2082 	sockaddr_u	netnum;
2083 	const char *	name_or_num;
2084 	size_t		sl;
2085 
2086 	if (!dogetassoc(fp))
2087 		return;
2088 
2089 	if (numhosts > 1) {
2090 		for (u = 0; u < numhosts; u++) {
2091 			if (getnetnum(chosts[u].name, &netnum, fullname, af)) {
2092 				name_or_num = nntohost(&netnum);
2093 				sl = strlen(name_or_num);
2094 				maxhostlen = max(maxhostlen, sl);
2095 			}
2096 		}
2097 		xprintf(fp, "%-*.*s ", (int)maxhostlen, (int)maxhostlen,
2098 			"server (local)");
2099 	}
2100 	xprintf(fp,
2101 		"     remote       refid   assid  st t when poll reach   delay   offset  jitter\n");
2102 	if (numhosts > 1)
2103 		for (u = 0; u <= maxhostlen; u++)
2104 			xprintf(fp, "=");
2105 	xprintf(fp,
2106 		"==============================================================================\n");
2107 
2108 	for (u = 0; u < numassoc; u++) {
2109 		if (!showall &&
2110 		    !(CTL_PEER_STATVAL(assoc_cache[u].status)
2111 		      & (CTL_PST_CONFIG|CTL_PST_REACH))) {
2112 			if (debug)
2113 				xprintf(stderr, "eliding [%d]\n",
2114 					(int)assoc_cache[u].assid);
2115 			continue;
2116 		}
2117 		if (!dogetpeers(apeervarlist, (int)assoc_cache[u].assid,
2118 				fp, af))
2119 			return;
2120 	}
2121 	return;
2122 }
2123 
2124 
2125 /*
2126  * peers - print a peer spreadsheet
2127  */
2128 /*ARGSUSED*/
2129 static void
2130 peers(
2131 	struct parse *pcmd,
2132 	FILE *fp
2133 	)
2134 {
2135 	if (drefid == REFID_HASH) {
2136 		apeers(pcmd, fp);
2137 	} else {
2138 		int af = 0;
2139 
2140 		if (pcmd->nargs == 1) {
2141 			if (pcmd->argval->ival == 6)
2142 				af = AF_INET6;
2143 			else
2144 				af = AF_INET;
2145 		}
2146 		dopeers(0, fp, af);
2147 	}
2148 }
2149 
2150 
2151 /*
2152  * apeers - print a peer spreadsheet, with assocIDs
2153  */
2154 /*ARGSUSED*/
2155 static void
2156 apeers(
2157 	struct parse *pcmd,
2158 	FILE *fp
2159 	)
2160 {
2161 	int af = 0;
2162 
2163 	if (pcmd->nargs == 1) {
2164 		if (pcmd->argval->ival == 6)
2165 			af = AF_INET6;
2166 		else
2167 			af = AF_INET;
2168 	}
2169 	doapeers(0, fp, af);
2170 }
2171 
2172 
2173 /*
2174  * lpeers - print a peer spreadsheet including all fuzzball peers
2175  */
2176 /*ARGSUSED*/
2177 static void
2178 lpeers(
2179 	struct parse *pcmd,
2180 	FILE *fp
2181 	)
2182 {
2183 	int af = 0;
2184 
2185 	if (pcmd->nargs == 1) {
2186 		if (pcmd->argval->ival == 6)
2187 			af = AF_INET6;
2188 		else
2189 			af = AF_INET;
2190 	}
2191 	dopeers(1, fp, af);
2192 }
2193 
2194 
2195 /*
2196  * opeers - print a peer spreadsheet
2197  */
2198 static void
2199 doopeers(
2200 	int showall,
2201 	FILE *fp,
2202 	int af
2203 	)
2204 {
2205 	u_int i;
2206 	char fullname[LENHOSTNAME];
2207 	sockaddr_u netnum;
2208 
2209 	if (!dogetassoc(fp))
2210 		return;
2211 
2212 	if (numhosts > 1) {
2213 		for (i = 0; i < numhosts; ++i) {
2214 			if (getnetnum(chosts[i].name, &netnum, fullname, af)) {
2215 				maxhostlen = max(maxhostlen, strlen(fullname));
2216 			}
2217 			xprintf(fp, "%-*.*s ", (int)maxhostlen, (int)maxhostlen,
2218 				"server");
2219 		}
2220 	}
2221 	xprintf(fp,
2222 	    "     remote           local      st t when poll reach   delay   offset    disp\n");
2223 	if (numhosts > 1)
2224 		for (i = 0; i <= maxhostlen; ++i)
2225 			xprintf(fp, "=");
2226 	xprintf(fp,
2227 	    "==============================================================================\n");
2228 
2229 	for (i = 0; i < numassoc; i++) {
2230 		if (!showall &&
2231 		    !(CTL_PEER_STATVAL(assoc_cache[i].status) &
2232 		      (CTL_PST_CONFIG | CTL_PST_REACH)))
2233 			continue;
2234 		if (!dogetpeers(opeervarlist, assoc_cache[i].assid, fp, af))
2235 			return;
2236 	}
2237 	return;
2238 }
2239 
2240 
2241 /*
2242  * opeers - print a peer spreadsheet the old way
2243  */
2244 /*ARGSUSED*/
2245 static void
2246 opeers(
2247 	struct parse *pcmd,
2248 	FILE *fp
2249 	)
2250 {
2251 	int af = 0;
2252 
2253 	if (pcmd->nargs == 1) {
2254 		if (pcmd->argval->ival == 6)
2255 			af = AF_INET6;
2256 		else
2257 			af = AF_INET;
2258 	}
2259 	doopeers(0, fp, af);
2260 }
2261 
2262 
2263 /*
2264  * lopeers - print a peer spreadsheet including all fuzzball peers
2265  */
2266 /*ARGSUSED*/
2267 static void
2268 lopeers(
2269 	struct parse *pcmd,
2270 	FILE *fp
2271 	)
2272 {
2273 	int af = 0;
2274 
2275 	if (pcmd->nargs == 1) {
2276 		if (pcmd->argval->ival == 6)
2277 			af = AF_INET6;
2278 		else
2279 			af = AF_INET;
2280 	}
2281 	doopeers(1, fp, af);
2282 }
2283 
2284 
2285 /*
2286  * config - send a configuration command to a remote host
2287  */
2288 static void
2289 config (
2290 	struct parse *pcmd,
2291 	FILE *fp
2292 	)
2293 {
2294 	const char *cfgcmd;
2295 	u_short rstatus;
2296 	size_t rsize;
2297 	const char *rdata;
2298 	char *resp;
2299 	int res;
2300 	int col;
2301 	int i;
2302 
2303 	cfgcmd = pcmd->argval[0].string;
2304 
2305 	if (debug > 2)
2306 		xprintf(stderr,
2307 			"In Config\n"
2308 			"Keyword = %s\n"
2309 			"Command = %s\n", pcmd->keyword, cfgcmd);
2310 
2311 	res = doquery(CTL_OP_CONFIGURE, 0, 1,
2312 		      strlen(cfgcmd), cfgcmd,
2313 		      &rstatus, &rsize, &rdata);
2314 
2315 	if (res != 0)
2316 		return;
2317 
2318 	if (rsize > 0 && '\n' == rdata[rsize - 1])
2319 		rsize--;
2320 
2321 	resp = emalloc(rsize + 1);
2322 	memcpy(resp, rdata, rsize);
2323 	resp[rsize] = '\0';
2324 
2325 	col = -1;
2326 	if (1 == sscanf(resp, "column %d syntax error", &col)
2327 	    && col >= 0 && (size_t)col <= strlen(cfgcmd) + 1) {
2328 		if (interactive)
2329 			xputs("             *", stdout); /* "ntpq> :config " */
2330 		else
2331 			printf("%s\n", cfgcmd);
2332 		for (i = 0; i < col; i++)
2333 			xputc('_', stdout);
2334 		xputs("^\n", stdout);
2335 	}
2336 	printf("%s\n", resp);
2337 	free(resp);
2338 }
2339 
2340 
2341 /*
2342  * config_from_file - remotely configure an ntpd daemon using the
2343  * specified configuration file
2344  * SK: This function is a kludge at best and is full of bad design
2345  * bugs:
2346  * 1. ntpq uses UDP, which means that there is no guarantee of in-order,
2347  *    error-free delivery.
2348  * 2. The maximum length of a packet is constrained, and as a result, the
2349  *    maximum length of a line in a configuration file is constrained.
2350  *    Longer lines will lead to unpredictable results.
2351  * 3. Since this function is sending a line at a time, we can't update
2352  *    the control key through the configuration file (YUCK!!)
2353  *
2354  * Pearly: There are a few places where 'size_t' is cast to 'int' based
2355  * on the assumption that 'int' can hold the size of the involved
2356  * buffers without overflow.
2357  */
2358 static void
2359 config_from_file (
2360 	struct parse *pcmd,
2361 	FILE *fp
2362 	)
2363 {
2364 	u_short rstatus;
2365 	size_t rsize;
2366 	const char *rdata;
2367 	char * cp;
2368 	int res;
2369 	FILE *config_fd;
2370 	char config_cmd[MAXLINE];
2371 	size_t config_len;
2372 	int i;
2373 	int retry_limit;
2374 
2375 	if (debug > 2)
2376 		xprintf(stderr,
2377 			"In Config\n"
2378 			"Keyword = %s\n"
2379 			"Filename = %s\n", pcmd->keyword,
2380 			pcmd->argval[0].string);
2381 
2382 	config_fd = fopen(pcmd->argval[0].string, "r");
2383 	if (NULL == config_fd) {
2384 		printf("ERROR!! Couldn't open file: %s\n",
2385 		       pcmd->argval[0].string);
2386 		return;
2387 	}
2388 
2389 	printf("Sending configuration file, one line at a time.\n");
2390 	i = 0;
2391 	while (fgets(config_cmd, MAXLINE, config_fd) != NULL) {
2392 		/* Eliminate comments first. */
2393 		cp = strchr(config_cmd, '#');
2394 		config_len = (NULL != cp)
2395 		    ? (size_t)(cp - config_cmd)
2396 		    : strlen(config_cmd);
2397 
2398 		/* [Bug 3015] make sure there's no trailing whitespace;
2399 		 * the fix for [Bug 2853] on the server side forbids
2400 		 * those. And don't transmit empty lines, as this would
2401 		 * just be waste.
2402 		 */
2403 		while (config_len != 0 &&
2404 		       (u_char)config_cmd[config_len-1] <= ' ')
2405 			--config_len;
2406 		config_cmd[config_len] = '\0';
2407 
2408 		++i;
2409 		if (0 == config_len)
2410 			continue;
2411 
2412 		retry_limit = 2;
2413 		do
2414 			res = doquery(CTL_OP_CONFIGURE, 0, 1,
2415 				      config_len, config_cmd,
2416 				      &rstatus, &rsize, &rdata);
2417 		while (res != 0 && retry_limit--);
2418 		if (res != 0) {
2419 			printf("Line No: %d query failed: %.*s\n"
2420 			       "Subsequent lines not sent.\n",
2421 			       i, (int)config_len, config_cmd);
2422 			fclose(config_fd);
2423 			return;
2424 		}
2425 
2426 		/* Right-strip the result code string, then output the
2427 		 * last line executed, with result code. */
2428 		while (rsize != 0 && (u_char)rdata[rsize - 1] <= ' ')
2429 			--rsize;
2430 		printf("Line No: %d %.*s: %.*s\n", i,
2431 		       (int)rsize, rdata,
2432 		       (int)config_len, config_cmd);
2433 	}
2434 	printf("Done sending file\n");
2435 	fclose(config_fd);
2436 }
2437 
2438 
2439 static int
2440 fetch_nonce(
2441 	char *	nonce,
2442 	size_t	cb_nonce
2443 	)
2444 {
2445 	const char	nonce_eq[] = "nonce=";
2446 	int		qres;
2447 	u_short		rstatus;
2448 	size_t		rsize;
2449 	const char *	rdata;
2450 	size_t		chars;
2451 
2452 	/*
2453 	 * Retrieve a nonce specific to this client to demonstrate to
2454 	 * ntpd that we're capable of receiving responses to our source
2455 	 * IP address, and thereby unlikely to be forging the source.
2456 	 */
2457 	qres = doquery(CTL_OP_REQ_NONCE, 0, 0, 0, NULL, &rstatus,
2458 		       &rsize, &rdata);
2459 	if (qres) {
2460 		xprintf(stderr, "nonce request failed\n");
2461 		return FALSE;
2462 	}
2463 
2464 	if (rsize <= sizeof(nonce_eq) - 1 ||
2465 	    strncmp(rdata, nonce_eq, sizeof(nonce_eq) - 1)) {
2466 		xprintf(stderr, "unexpected nonce response format: %.*s\n",
2467 			(int)rsize, rdata); /* cast is wobbly */
2468 		return FALSE;
2469 	}
2470 	chars = rsize - (sizeof(nonce_eq) - 1);
2471 	if (chars >= cb_nonce)
2472 		return FALSE;
2473 	memcpy(nonce, rdata + sizeof(nonce_eq) - 1, chars);
2474 	nonce[chars] = '\0';
2475 	while (chars > 0 &&
2476 	       ('\r' == nonce[chars - 1] || '\n' == nonce[chars - 1])) {
2477 		chars--;
2478 		nonce[chars] = '\0';
2479 	}
2480 
2481 	return TRUE;
2482 }
2483 
2484 
2485 /*
2486  * add_mru	Add and entry to mru list, hash table, and allocate
2487  *		and return a replacement.
2488  *		This is a helper for collect_mru_list().
2489  */
2490 static mru *
2491 add_mru(
2492 	mru *add
2493 	)
2494 {
2495 	u_short hash;
2496 	mru *mon;
2497 	mru *unlinked;
2498 
2499 
2500 	hash = NTP_HASH_ADDR(&add->addr);
2501 	/* see if we have it among previously received entries */
2502 	for (mon = hash_table[hash]; mon != NULL; mon = mon->hlink)
2503 		if (SOCK_EQ(&mon->addr, &add->addr))
2504 			break;
2505 	if (mon != NULL) {
2506 		if (!L_ISGEQ(&add->first, &mon->first)) {
2507 			xprintf(stderr,
2508 				"add_mru duplicate %s new first ts %08x.%08x precedes prior %08x.%08x\n",
2509 				sptoa(&add->addr), add->last.l_ui,
2510 				add->last.l_uf, mon->last.l_ui,
2511 				mon->last.l_uf);
2512 			exit(1);
2513 		}
2514 		UNLINK_DLIST(mon, mlink);
2515 		UNLINK_SLIST(unlinked, hash_table[hash], mon, hlink, mru);
2516 		INSIST(unlinked == mon);
2517 		mru_dupes++;
2518 		TRACE(2, ("(updated from %08x.%08x) ", mon->last.l_ui,
2519 		      mon->last.l_uf));
2520 	}
2521 	LINK_DLIST(mru_list, add, mlink);
2522 	LINK_SLIST(hash_table[hash], add, hlink);
2523 	TRACE(2, ("add_mru %08x.%08x c %d m %d v %d rest %x first %08x.%08x %s\n",
2524 	      add->last.l_ui, add->last.l_uf, add->count,
2525 	      (int)add->mode, (int)add->ver, (u_int)add->rs,
2526 	      add->first.l_ui, add->first.l_uf, sptoa(&add->addr)));
2527 	/* if we didn't update an existing entry, alloc replacement */
2528 	if (NULL == mon) {
2529 		mon = emalloc(sizeof(*mon));
2530 		mru_count++;
2531 	}
2532 	ZERO(*mon);
2533 
2534 	return mon;
2535 }
2536 
2537 
2538 /* MGOT macro is specific to collect_mru_list() */
2539 #define MGOT(bit)				\
2540 	do {					\
2541 		got |= (bit);			\
2542 		if (MRU_GOT_ALL == got) {	\
2543 			got = 0;		\
2544 			mon = add_mru(mon);	\
2545 			ci++;			\
2546 		}				\
2547 	} while (0)
2548 
2549 
2550 int
2551 mrulist_ctrl_c_hook(void)
2552 {
2553 	mrulist_interrupted = TRUE;
2554 	return TRUE;
2555 }
2556 
2557 
2558 static int
2559 collect_mru_list(
2560 	const char *	parms,
2561 	l_fp *		pnow
2562 	)
2563 {
2564 	const u_int sleep_msecs = 5;
2565 	static int ntpd_row_limit = MRU_ROW_LIMIT;
2566 	int c_mru_l_rc;		/* this function's return code */
2567 	u_char got;		/* MRU_GOT_* bits */
2568 	time_t next_report;
2569 	size_t cb;
2570 	mru *mon;
2571 	mru *head;
2572 	mru *recent;
2573 	int list_complete;
2574 	char nonce[128];
2575 	char buf[128];
2576 	char req_buf[CTL_MAX_DATA_LEN];
2577 	char *req;
2578 	char *req_end;
2579 	size_t chars;
2580 	int qres;
2581 	u_short rstatus;
2582 	size_t rsize;
2583 	const char *rdata;
2584 	int limit;
2585 	int frags;
2586 	int cap_frags;
2587 	char *tag;
2588 	char *val;
2589 	int si;		/* server index in response */
2590 	int ci;		/* client (our) index for validation */
2591 	int ri;		/* request index (.# suffix) */
2592 	int mv;
2593 	l_fp newest;
2594 	l_fp last_older;
2595 	sockaddr_u addr_older;
2596 	int have_now;
2597 	int have_addr_older;
2598 	int have_last_older;
2599 	u_int restarted_count;
2600 	u_int nonce_uses;
2601 	u_short hash;
2602 	mru *unlinked;
2603 
2604 	if (!fetch_nonce(nonce, sizeof(nonce)))
2605 		return FALSE;
2606 
2607 	nonce_uses = 0;
2608 	restarted_count = 0;
2609 	mru_count = 0;
2610 	INIT_DLIST(mru_list, mlink);
2611 	cb = NTP_HASH_SIZE * sizeof(*hash_table);
2612 	INSIST(NULL == hash_table);
2613 	hash_table = emalloc_zero(cb);
2614 
2615 	c_mru_l_rc = FALSE;
2616 	list_complete = FALSE;
2617 	have_now = FALSE;
2618 	cap_frags = TRUE;
2619 	got = 0;
2620 	ri = 0;
2621 	cb = sizeof(*mon);
2622 	mon = emalloc_zero(cb);
2623 	ZERO(*pnow);
2624 	ZERO(last_older);
2625 	next_report = time(NULL) + MRU_REPORT_SECS;
2626 
2627 	limit = min(3 * MAXFRAGS, ntpd_row_limit);
2628 	frags = MAXFRAGS;
2629 	snprintf(req_buf, sizeof(req_buf), "nonce=%s, frags=%d%s",
2630 		 nonce, frags, parms);
2631 	nonce_uses++;
2632 
2633 	while (TRUE) {
2634 		if (debug)
2635 			xprintf(stderr, "READ_MRU parms: %s\n", req_buf);
2636 
2637 		qres = doqueryex(CTL_OP_READ_MRU, 0, 0,
2638 				 strlen(req_buf), req_buf,
2639 				 &rstatus, &rsize, &rdata, TRUE);
2640 
2641 		if (CERR_UNKNOWNVAR == qres && ri > 0) {
2642 			/*
2643 			 * None of the supplied prior entries match, so
2644 			 * toss them from our list and try again.
2645 			 */
2646 			if (debug)
2647 				xprintf(stderr,
2648 					"no overlap between %d prior entries and server MRU list\n",
2649 					ri);
2650 			while (ri--) {
2651 				recent = HEAD_DLIST(mru_list, mlink);
2652 				INSIST(recent != NULL);
2653 				if (debug)
2654 					xprintf(stderr,
2655 						"tossing prior entry %s to resync\n",
2656 						sptoa(&recent->addr));
2657 				UNLINK_DLIST(recent, mlink);
2658 				hash = NTP_HASH_ADDR(&recent->addr);
2659 				UNLINK_SLIST(unlinked, hash_table[hash],
2660 					     recent, hlink, mru);
2661 				INSIST(unlinked == recent);
2662 				free(recent);
2663 				mru_count--;
2664 			}
2665 			if (NULL == HEAD_DLIST(mru_list, mlink)) {
2666 				restarted_count++;
2667 				if (restarted_count > 8) {
2668 					xprintf(stderr,
2669 						"Giving up after 8 restarts from the beginning.\n"
2670 						"With high-traffic NTP servers, this can occur if the\n"
2671 						"MRU list is limited to less than about 16 seconds' of\n"
2672 						"entries.  See the 'mru' ntp.conf directive to adjust.\n");
2673 					goto cleanup_return;
2674 				}
2675 				if (debug)
2676 					xprintf(stderr,
2677 						"--->   Restarting from the beginning, retry #%u\n",
2678 						restarted_count);
2679 			}
2680 		} else if (CERR_UNKNOWNVAR == qres) {
2681 			xprintf(stderr,
2682 				"CERR_UNKNOWNVAR from ntpd but no priors given.\n");
2683 			goto cleanup_return;
2684 		} else if (CERR_BADVALUE == qres) {
2685 			if (cap_frags) {
2686 				cap_frags = FALSE;
2687 				if (debug)
2688 					xprintf(stderr,
2689 						"Reverted to row limit from fragments limit.\n");
2690 			} else {
2691 				/* ntpd has lower cap on row limit */
2692 				ntpd_row_limit--;
2693 				limit = min(limit, ntpd_row_limit);
2694 				if (debug)
2695 					xprintf(stderr,
2696 						"Row limit reduced to %d following CERR_BADVALUE.\n",
2697 						limit);
2698 			}
2699 		} else if (ERR_INCOMPLETE == qres ||
2700 			   ERR_TIMEOUT == qres) {
2701 			/*
2702 			 * Reduce the number of rows/frags requested by
2703 			 * half to recover from lost response fragments.
2704 			 */
2705 			if (cap_frags) {
2706 				frags = max(2, frags / 2);
2707 				if (debug)
2708 					xprintf(stderr,
2709 						"Frag limit reduced to %d following incomplete response.\n",
2710 						frags);
2711 			} else {
2712 				limit = max(2, limit / 2);
2713 				if (debug)
2714 					xprintf(stderr,
2715 						"Row limit reduced to %d following incomplete response.\n",
2716 						limit);
2717 			}
2718 		} else if (qres) {
2719 			show_error_msg(qres, 0);
2720 			goto cleanup_return;
2721 		}
2722 		/*
2723 		 * This is a cheap cop-out implementation of rawmode
2724 		 * output for mrulist.  A better approach would be to
2725 		 * dump similar output after the list is collected by
2726 		 * ntpq with a continuous sequence of indexes.  This
2727 		 * cheap approach has indexes resetting to zero for
2728 		 * each query/response, and duplicates are not
2729 		 * coalesced.
2730 		 */
2731 		if (!qres && rawmode)
2732 			printvars(rsize, rdata, rstatus, TYPE_SYS, 1, stdout);
2733 		ci = 0;
2734 		have_addr_older = FALSE;
2735 		have_last_older = FALSE;
2736 		while (!qres && nextvar(&rsize, &rdata, &tag, &val)) {
2737 			INSIST(tag && val);
2738 			if (debug > 1)
2739 				xprintf(stderr, "nextvar gave: %s = %s\n",
2740 					tag, val);
2741 			switch(tag[0]) {
2742 
2743 			case 'a':
2744 				if (!strcmp(tag, "addr.older")) {
2745 					if (!have_last_older) {
2746 						xprintf(stderr,
2747 							"addr.older %s before last.older\n",
2748 							val);
2749 						goto cleanup_return;
2750 					}
2751 					if (!decodenetnum(val, &addr_older)) {
2752 						xprintf(stderr,
2753 							"addr.older %s garbled\n",
2754 							val);
2755 						goto cleanup_return;
2756 					}
2757 					hash = NTP_HASH_ADDR(&addr_older);
2758 					for (recent = hash_table[hash];
2759 					     recent != NULL;
2760 					     recent = recent->hlink)
2761 						if (ADDR_PORT_EQ(
2762 						      &addr_older,
2763 						      &recent->addr))
2764 							break;
2765 					if (NULL == recent) {
2766 						xprintf(stderr,
2767 							"addr.older %s not in hash table\n",
2768 							val);
2769 						goto cleanup_return;
2770 					}
2771 					if (!L_ISEQU(&last_older,
2772 						     &recent->last)) {
2773 						xprintf(stderr,
2774 							"last.older %08x.%08x mismatches %08x.%08x expected.\n",
2775 							last_older.l_ui,
2776 							last_older.l_uf,
2777 							recent->last.l_ui,
2778 							recent->last.l_uf);
2779 						goto cleanup_return;
2780 					}
2781 					have_addr_older = TRUE;
2782 				} else if (1 != sscanf(tag, "addr.%d", &si)
2783 					   || si != ci)
2784 					goto nomatch;
2785 				else if (decodenetnum(val, &mon->addr))
2786 					MGOT(MRU_GOT_ADDR);
2787 				break;
2788 
2789 			case 'l':
2790 				if (!strcmp(tag, "last.older")) {
2791 					if ('0' != val[0] ||
2792 					    'x' != val[1] ||
2793 					    !hextolfp(val + 2, &last_older)) {
2794 						xprintf(stderr,
2795 							"last.older %s garbled\n",
2796 							val);
2797 						goto cleanup_return;
2798 					}
2799 					have_last_older = TRUE;
2800 				} else if (!strcmp(tag, "last.newest")) {
2801 					if (0 != got) {
2802 						xprintf(stderr,
2803 							"last.newest %s before complete row, got = 0x%x\n",
2804 							val, (u_int)got);
2805 						goto cleanup_return;
2806 					}
2807 					if (!have_now) {
2808 						xprintf(stderr,
2809 							"last.newest %s before now=\n",
2810 							val);
2811 						goto cleanup_return;
2812 					}
2813 					head = HEAD_DLIST(mru_list, mlink);
2814 					if (NULL != head) {
2815 						if ('0' != val[0] ||
2816 						    'x' != val[1] ||
2817 						    !hextolfp(val + 2, &newest) ||
2818 						    !L_ISEQU(&newest,
2819 							     &head->last)) {
2820 							xprintf(stderr,
2821 								"last.newest %s mismatches %08x.%08x",
2822 								val,
2823 								head->last.l_ui,
2824 								head->last.l_uf);
2825 							goto cleanup_return;
2826 						}
2827 					}
2828 					list_complete = TRUE;
2829 				} else if (1 != sscanf(tag, "last.%d", &si) ||
2830 					   si != ci || '0' != val[0] ||
2831 					   'x' != val[1] ||
2832 					   !hextolfp(val + 2, &mon->last)) {
2833 					goto nomatch;
2834 				} else {
2835 					MGOT(MRU_GOT_LAST);
2836 					/*
2837 					 * allow interrupted retrieval,
2838 					 * using most recent retrieved
2839 					 * entry's last seen timestamp
2840 					 * as the end of operation.
2841 					 */
2842 					*pnow = mon->last;
2843 				}
2844 				break;
2845 
2846 			case 'f':
2847 				if (1 != sscanf(tag, "first.%d", &si) ||
2848 				    si != ci || '0' != val[0] ||
2849 				    'x' != val[1] ||
2850 				    !hextolfp(val + 2, &mon->first))
2851 					goto nomatch;
2852 				MGOT(MRU_GOT_FIRST);
2853 				break;
2854 
2855 			case 'n':
2856 				if (!strcmp(tag, "nonce")) {
2857 					strlcpy(nonce, val, sizeof(nonce));
2858 					nonce_uses = 0;
2859 					break; /* case */
2860 				} else if (strcmp(tag, "now") ||
2861 					   '0' != val[0] ||
2862 					   'x' != val[1] ||
2863 					    !hextolfp(val + 2, pnow))
2864 					goto nomatch;
2865 				have_now = TRUE;
2866 				break;
2867 
2868 			case 'c':
2869 				if (1 != sscanf(tag, "ct.%d", &si) ||
2870 				    si != ci ||
2871 				    1 != sscanf(val, "%d", &mon->count)
2872 				    || mon->count < 1)
2873 					goto nomatch;
2874 				MGOT(MRU_GOT_COUNT);
2875 				break;
2876 
2877 			case 'm':
2878 				if (1 != sscanf(tag, "mv.%d", &si) ||
2879 				    si != ci ||
2880 				    1 != sscanf(val, "%d", &mv))
2881 					goto nomatch;
2882 				mon->mode = PKT_MODE(mv);
2883 				mon->ver = PKT_VERSION(mv);
2884 				MGOT(MRU_GOT_MV);
2885 				break;
2886 
2887 			case 'r':
2888 				if (1 != sscanf(tag, "rs.%d", &si) ||
2889 				    si != ci ||
2890 				    1 != sscanf(val, "0x%hx", &mon->rs))
2891 					goto nomatch;
2892 				MGOT(MRU_GOT_RS);
2893 				break;
2894 
2895 			default:
2896 			nomatch:
2897 				/* empty stmt */ ;
2898 				/* ignore unknown tags */
2899 			}
2900 		}
2901 		if (have_now)
2902 			list_complete = TRUE;
2903 		if (list_complete) {
2904 			INSIST(0 == ri || have_addr_older);
2905 		}
2906 		if (mrulist_interrupted) {
2907 			printf("mrulist retrieval interrupted by operator.\n"
2908 			       "Displaying partial client list.\n");
2909 			fflush(stdout);
2910 		}
2911 		if (list_complete || mrulist_interrupted) {
2912 			xprintf(stderr,
2913 				"\rRetrieved %u unique MRU entries and %u updates.\n",
2914 				mru_count, mru_dupes);
2915 			fflush(stderr);
2916 			break;
2917 		}
2918 		if (time(NULL) >= next_report) {
2919 			next_report += MRU_REPORT_SECS;
2920 			xprintf(stderr, "\r%u (%u updates) ", mru_count,
2921 				mru_dupes);
2922 			fflush(stderr);
2923 		}
2924 
2925 		/*
2926 		 * Snooze for a bit between queries to let ntpd catch
2927 		 * up with other duties.
2928 		 */
2929 #ifdef SYS_WINNT
2930 		Sleep(sleep_msecs);
2931 #elif !defined(HAVE_NANOSLEEP)
2932 		sleep((sleep_msecs / 1000) + 1);
2933 #else
2934 		{
2935 			struct timespec interv = { 0,
2936 						   1000 * sleep_msecs };
2937 			nanosleep(&interv, NULL);
2938 		}
2939 #endif
2940 		/*
2941 		 * If there were no errors, increase the number of rows
2942 		 * to a maximum of 3 * MAXFRAGS (the most packets ntpq
2943 		 * can handle in one response), on the assumption that
2944 		 * no less than 3 rows fit in each packet, capped at
2945 		 * our best guess at the server's row limit.
2946 		 */
2947 		if (!qres) {
2948 			if (cap_frags) {
2949 				frags = min(MAXFRAGS, frags + 1);
2950 			} else {
2951 				limit = min3(3 * MAXFRAGS,
2952 					     ntpd_row_limit,
2953 					     max(limit + 1,
2954 					         limit * 33 / 32));
2955 			}
2956 		}
2957 		/*
2958 		 * prepare next query with as many address and last-seen
2959 		 * timestamps as will fit in a single packet.
2960 		 */
2961 		req = req_buf;
2962 		req_end = req_buf + sizeof(req_buf);
2963 #define REQ_ROOM	(req_end - req)
2964 		snprintf(req, REQ_ROOM, "nonce=%s, %s=%d%s", nonce,
2965 			 (cap_frags)
2966 			     ? "frags"
2967 			     : "limit",
2968 			 (cap_frags)
2969 			     ? frags
2970 			     : limit,
2971 			 parms);
2972 		req += strlen(req);
2973 		nonce_uses++;
2974 		if (nonce_uses >= 4) {
2975 			if (!fetch_nonce(nonce, sizeof(nonce)))
2976 				goto cleanup_return;
2977 			nonce_uses = 0;
2978 		}
2979 
2980 
2981 		for (ri = 0, recent = HEAD_DLIST(mru_list, mlink);
2982 		     recent != NULL;
2983 		     ri++, recent = NEXT_DLIST(mru_list, recent, mlink)) {
2984 
2985 			snprintf(buf, sizeof(buf),
2986 				 ", addr.%d=%s, last.%d=0x%08x.%08x",
2987 				 ri, sptoa(&recent->addr), ri,
2988 				 recent->last.l_ui, recent->last.l_uf);
2989 			chars = strlen(buf);
2990 			if ((size_t)REQ_ROOM <= chars)
2991 				break;
2992 			memcpy(req, buf, chars + 1);
2993 			req += chars;
2994 		}
2995 	}
2996 
2997 	c_mru_l_rc = TRUE;
2998 	goto retain_hash_table;
2999 
3000 cleanup_return:
3001 	free(hash_table);
3002 	hash_table = NULL;
3003 
3004 retain_hash_table:
3005 	if (mon != NULL)
3006 		free(mon);
3007 
3008 	return c_mru_l_rc;
3009 }
3010 
3011 
3012 /*
3013  * qcmp_mru_addr - sort MRU entries by remote address.
3014  *
3015  * All IPv4 addresses sort before any IPv6, addresses are sorted by
3016  * value within address family.
3017  */
3018 static int
3019 qcmp_mru_addr(
3020 	const void *v1,
3021 	const void *v2
3022 	)
3023 {
3024 	const mru * const *	ppm1 = v1;
3025 	const mru * const *	ppm2 = v2;
3026 	const mru *		pm1;
3027 	const mru *		pm2;
3028 	u_short			af1;
3029 	u_short			af2;
3030 	size_t			cmplen;
3031 	size_t			addr_off;
3032 
3033 	pm1 = *ppm1;
3034 	pm2 = *ppm2;
3035 
3036 	af1 = AF(&pm1->addr);
3037 	af2 = AF(&pm2->addr);
3038 
3039 	if (af1 != af2)
3040 		return (AF_INET == af1)
3041 			   ? -1
3042 			   : 1;
3043 
3044 	cmplen = SIZEOF_INADDR(af1);
3045 	addr_off = (AF_INET == af1)
3046 		      ? offsetof(struct sockaddr_in, sin_addr)
3047 		      : offsetof(struct sockaddr_in6, sin6_addr);
3048 
3049 	return memcmp((const char *)&pm1->addr + addr_off,
3050 		      (const char *)&pm2->addr + addr_off,
3051 		      cmplen);
3052 }
3053 
3054 
3055 static int
3056 qcmp_mru_r_addr(
3057 	const void *v1,
3058 	const void *v2
3059 	)
3060 {
3061 	return -qcmp_mru_addr(v1, v2);
3062 }
3063 
3064 
3065 /*
3066  * qcmp_mru_count - sort MRU entries by times seen (hit count).
3067  */
3068 static int
3069 qcmp_mru_count(
3070 	const void *v1,
3071 	const void *v2
3072 	)
3073 {
3074 	const mru * const *	ppm1 = v1;
3075 	const mru * const *	ppm2 = v2;
3076 	const mru *		pm1;
3077 	const mru *		pm2;
3078 
3079 	pm1 = *ppm1;
3080 	pm2 = *ppm2;
3081 
3082 	return (pm1->count < pm2->count)
3083 		   ? -1
3084 		   : ((pm1->count == pm2->count)
3085 			  ? 0
3086 			  : 1);
3087 }
3088 
3089 
3090 static int
3091 qcmp_mru_r_count(
3092 	const void *v1,
3093 	const void *v2
3094 	)
3095 {
3096 	return -qcmp_mru_count(v1, v2);
3097 }
3098 
3099 
3100 /*
3101  * qcmp_mru_avgint - sort MRU entries by average interval.
3102  */
3103 static int
3104 qcmp_mru_avgint(
3105 	const void *v1,
3106 	const void *v2
3107 	)
3108 {
3109 	const mru * const *	ppm1 = v1;
3110 	const mru * const *	ppm2 = v2;
3111 	const mru *		pm1;
3112 	const mru *		pm2;
3113 	l_fp			interval;
3114 	double			avg1;
3115 	double			avg2;
3116 
3117 	pm1 = *ppm1;
3118 	pm2 = *ppm2;
3119 
3120 	interval = pm1->last;
3121 	L_SUB(&interval, &pm1->first);
3122 	LFPTOD(&interval, avg1);
3123 	avg1 /= pm1->count;
3124 
3125 	interval = pm2->last;
3126 	L_SUB(&interval, &pm2->first);
3127 	LFPTOD(&interval, avg2);
3128 	avg2 /= pm2->count;
3129 
3130 	if (avg1 < avg2)
3131 		return -1;
3132 	else if (avg1 > avg2)
3133 		return 1;
3134 
3135 	/* secondary sort on lstint - rarely tested */
3136 	if (L_ISEQU(&pm1->last, &pm2->last))
3137 		return 0;
3138 	else if (L_ISGEQ(&pm1->last, &pm2->last))
3139 		return -1;
3140 	else
3141 		return 1;
3142 }
3143 
3144 
3145 static int
3146 qcmp_mru_r_avgint(
3147 	const void *v1,
3148 	const void *v2
3149 	)
3150 {
3151 	return -qcmp_mru_avgint(v1, v2);
3152 }
3153 
3154 
3155 /*
3156  * mrulist - ntpq's mrulist command to fetch an arbitrarily large Most
3157  *	     Recently Used (seen) remote address list from ntpd.
3158  *
3159  * Similar to ntpdc's monlist command, but not limited to a single
3160  * request/response, and thereby not limited to a few hundred remote
3161  * addresses.
3162  *
3163  * See ntpd/ntp_control.c read_mru_list() for comments on the way
3164  * CTL_OP_READ_MRU is designed to be used.
3165  *
3166  * mrulist intentionally differs from monlist in the way the avgint
3167  * column is calculated.  monlist includes the time after the last
3168  * packet from the client until the monlist query time in the average,
3169  * while mrulist excludes it.  That is, monlist's average interval grows
3170  * over time for remote addresses not heard from in some time, while it
3171  * remains unchanged in mrulist.  This also affects the avgint value for
3172  * entries representing a single packet, with identical first and last
3173  * timestamps.  mrulist shows 0 avgint, monlist shows a value identical
3174  * to lstint.
3175  */
3176 static void
3177 mrulist(
3178 	struct parse *	pcmd,
3179 	FILE *		fp
3180 	)
3181 {
3182 	const char mincount_eq[] =	"mincount=";
3183 	const char resall_eq[] =	"resall=";
3184 	const char resany_eq[] =	"resany=";
3185 	const char maxlstint_eq[] =	"maxlstint=";
3186 	const char laddr_eq[] =		"laddr=";
3187 	const char sort_eq[] =		"sort=";
3188 	mru_sort_order order;
3189 	size_t n;
3190 	char parms_buf[128];
3191 	char buf[24];
3192 	char *parms;
3193 	const char *arg;
3194 	size_t cb;
3195 	mru **sorted;
3196 	mru **ppentry;
3197 	mru *recent;
3198 	l_fp now;
3199 	l_fp interval;
3200 	double favgint;
3201 	double flstint;
3202 	int avgint;
3203 	int lstint;
3204 	size_t i;
3205 
3206 	mrulist_interrupted = FALSE;
3207 	push_ctrl_c_handler(&mrulist_ctrl_c_hook);
3208 	xprintf(stderr,
3209 		"Ctrl-C will stop MRU retrieval and display partial results.\n");
3210 	fflush(stderr);
3211 
3212 	order = MRUSORT_DEF;
3213 	parms_buf[0] = '\0';
3214 	parms = parms_buf;
3215 	for (i = 0; i < pcmd->nargs; i++) {
3216 		arg = pcmd->argval[i].string;
3217 		if (arg != NULL) {
3218 			cb = strlen(arg) + 1;
3219 			if ((!strncmp(resall_eq, arg, sizeof(resall_eq)
3220 			    - 1) || !strncmp(resany_eq, arg,
3221 			    sizeof(resany_eq) - 1) || !strncmp(
3222 			    mincount_eq, arg, sizeof(mincount_eq) - 1)
3223 			    || !strncmp(laddr_eq, arg, sizeof(laddr_eq)
3224 			    - 1) || !strncmp(maxlstint_eq, arg,
3225 			    sizeof(laddr_eq) - 1)) && parms + cb + 2 <=
3226 			    parms_buf + sizeof(parms_buf)) {
3227 				/* these are passed intact to ntpd */
3228 				memcpy(parms, ", ", 2);
3229 				parms += 2;
3230 				memcpy(parms, arg, cb);
3231 				parms += cb - 1;
3232 			} else if (!strncmp(sort_eq, arg,
3233 					    sizeof(sort_eq) - 1)) {
3234 				arg += sizeof(sort_eq) - 1;
3235 				for (n = 0;
3236 				     n < COUNTOF(mru_sort_keywords);
3237 				     n++)
3238 					if (!strcmp(mru_sort_keywords[n],
3239 						    arg))
3240 						break;
3241 				if (n < COUNTOF(mru_sort_keywords))
3242 					order = n;
3243 			} else if (!strcmp("limited", arg) ||
3244 				   !strcmp("kod", arg)) {
3245 				/* transform to resany=... */
3246 				snprintf(buf, sizeof(buf),
3247 					 ", resany=0x%x",
3248 					 ('k' == arg[0])
3249 					     ? RES_KOD
3250 					     : RES_LIMITED);
3251 				cb = 1 + strlen(buf);
3252 				if (parms + cb <
3253 					parms_buf + sizeof(parms_buf)) {
3254 					memcpy(parms, buf, cb);
3255 					parms += cb - 1;
3256 				}
3257 			} else
3258 				xprintf(stderr,
3259 					"ignoring unrecognized mrulist parameter: %s\n",
3260 					arg);
3261 		}
3262 	}
3263 	parms = parms_buf;
3264 
3265 	if (!collect_mru_list(parms, &now))
3266 		return;
3267 
3268 	/* display the results */
3269 	if (rawmode)
3270 		goto cleanup_return;
3271 
3272 	/* construct an array of entry pointers in default order */
3273 	sorted = eallocarray(mru_count, sizeof(*sorted));
3274 	ppentry = sorted;
3275 	if (MRUSORT_R_DEF != order) {
3276 		ITER_DLIST_BEGIN(mru_list, recent, mlink, mru)
3277 			INSIST(ppentry < sorted + mru_count);
3278 			*ppentry = recent;
3279 			ppentry++;
3280 		ITER_DLIST_END()
3281 	} else {
3282 		REV_ITER_DLIST_BEGIN(mru_list, recent, mlink, mru)
3283 			INSIST(ppentry < sorted + mru_count);
3284 			*ppentry = recent;
3285 			ppentry++;
3286 		REV_ITER_DLIST_END()
3287 	}
3288 
3289 	if (ppentry - sorted != (int)mru_count) {
3290 		xprintf(stderr,
3291 			"mru_count %u should match MRU list depth %ld.\n",
3292 			mru_count, (long)(ppentry - sorted));
3293 		free(sorted);
3294 		goto cleanup_return;
3295 	}
3296 
3297 	/* re-sort sorted[] if not default or reverse default */
3298 	if (MRUSORT_R_DEF < order)
3299 		qsort(sorted, mru_count, sizeof(sorted[0]),
3300 		      mru_qcmp_table[order]);
3301 
3302 	mrulist_interrupted = FALSE;
3303 	printf(	"lstint avgint rstr r m v  count rport remote address\n"
3304 		"==============================================================================\n");
3305 		/* '=' x 78 */
3306 	for (ppentry = sorted; ppentry < sorted + mru_count; ppentry++) {
3307 		recent = *ppentry;
3308 		interval = now;
3309 		L_SUB(&interval, &recent->last);
3310 		LFPTOD(&interval, flstint);
3311 		lstint = (int)(flstint + 0.5);
3312 		interval = recent->last;
3313 		L_SUB(&interval, &recent->first);
3314 		LFPTOD(&interval, favgint);
3315 		favgint /= recent->count;
3316 		avgint = (int)(favgint + 0.5);
3317 		xprintf(fp, "%6d %6d %4hx %c %d %d %6d %5u %s\n",
3318 			lstint, avgint, recent->rs,
3319 			(RES_KOD & recent->rs)
3320 			    ? 'K'
3321 			    : (RES_LIMITED & recent->rs)
3322 				  ? 'L'
3323 				  : '.',
3324 			(int)recent->mode, (int)recent->ver,
3325 			recent->count, SRCPORT(&recent->addr),
3326 			nntohost(&recent->addr));
3327 		if (showhostnames)
3328 			fflush(fp);
3329 		if (mrulist_interrupted) {
3330 			xputs("\n --interrupted--\n", fp);
3331 			fflush(fp);
3332 			break;
3333 		}
3334 	}
3335 	fflush(fp);
3336 	if (debug) {
3337 		xprintf(stderr,
3338 			"--- completed, freeing sorted[] pointers\n");
3339 		fflush(stderr);
3340 	}
3341 	free(sorted);
3342 
3343 cleanup_return:
3344 	if (debug) {
3345 		xprintf(stderr, "... freeing MRU entries\n");
3346 		fflush(stderr);
3347 	}
3348 	ITER_DLIST_BEGIN(mru_list, recent, mlink, mru)
3349 		free(recent);
3350 	ITER_DLIST_END()
3351 	if (debug) {
3352 		xprintf(stderr, "... freeing hash_table[]\n");
3353 		fflush(stderr);
3354 	}
3355 	free(hash_table);
3356 	hash_table = NULL;
3357 	INIT_DLIST(mru_list, mlink);
3358 
3359 	pop_ctrl_c_handler(&mrulist_ctrl_c_hook);
3360 }
3361 
3362 
3363 /*
3364  * validate_ifnum - helper for ifstats()
3365  *
3366  * Ensures rows are received in order and complete.
3367  */
3368 static void
3369 validate_ifnum(
3370 	FILE *		fp,
3371 	u_int		ifnum,
3372 	int *		pfields,
3373 	ifstats_row *	prow
3374 	)
3375 {
3376 	if (prow->ifnum == ifnum)
3377 		return;
3378 	if (prow->ifnum + 1 <= ifnum) {
3379 		if (*pfields < IFSTATS_FIELDS)
3380 			xprintf(fp, "Warning: incomplete row with %d (of %d) fields\n",
3381 				*pfields, IFSTATS_FIELDS);
3382 		*pfields = 0;
3383 		prow->ifnum = ifnum;
3384 		return;
3385 	}
3386 	xprintf(stderr,
3387 		"received if index %u, have %d of %d fields for index %u, aborting.\n",
3388 		ifnum, *pfields, IFSTATS_FIELDS, prow->ifnum);
3389 	exit(1);
3390 }
3391 
3392 
3393 /*
3394  * another_ifstats_field - helper for ifstats()
3395  *
3396  * If all fields for the row have been received, print it.
3397  */
3398 static void
3399 another_ifstats_field(
3400 	int *		pfields,
3401 	ifstats_row *	prow,
3402 	FILE *		fp
3403 	)
3404 {
3405 	u_int ifnum;
3406 
3407 	(*pfields)++;
3408 	/* we understand 12 tags */
3409 	if (IFSTATS_FIELDS > *pfields)
3410 		return;
3411 	/*
3412 	"    interface name                                        send\n"
3413 	" #  address/broadcast     drop flag ttl mc received sent failed peers   uptime\n"
3414 	"==============================================================================\n");
3415 	 */
3416 	xprintf(fp,
3417 		"%3u %-24.24s %c %4x %3u %2u %6u %6u %6u %5u %8d\n"
3418 		"    %s\n",
3419 		prow->ifnum, prow->name,
3420 		(prow->enabled)
3421 		    ? '.'
3422 		    : 'D',
3423 		prow->flags, prow->ttl, prow->mcast_count,
3424 		prow->received, prow->sent, prow->send_errors,
3425 		prow->peer_count, prow->uptime, sptoa(&prow->addr));
3426 	if (!SOCK_UNSPEC(&prow->bcast))
3427 		xprintf(fp, "    %s\n", sptoa(&prow->bcast));
3428 	ifnum = prow->ifnum;
3429 	ZERO(*prow);
3430 	prow->ifnum = ifnum;
3431 }
3432 
3433 
3434 /*
3435  * ifstats - ntpq -c ifstats modeled on ntpdc -c ifstats.
3436  */
3437 static void
3438 ifstats(
3439 	struct parse *	pcmd,
3440 	FILE *		fp
3441 	)
3442 {
3443 	const char	addr_fmt[] =	"addr.%u";
3444 	const char	bcast_fmt[] =	"bcast.%u";
3445 	const char	en_fmt[] =	"en.%u";	/* enabled */
3446 	const char	flags_fmt[] =	"flags.%u";
3447 	const char	mc_fmt[] =	"mc.%u";	/* mcast count */
3448 	const char	name_fmt[] =	"name.%u";
3449 	const char	pc_fmt[] =	"pc.%u";	/* peer count */
3450 	const char	rx_fmt[] =	"rx.%u";
3451 	const char	tl_fmt[] =	"tl.%u";	/* ttl */
3452 	const char	tx_fmt[] =	"tx.%u";
3453 	const char	txerr_fmt[] =	"txerr.%u";
3454 	const char	up_fmt[] =	"up.%u";	/* uptime */
3455 	const char *	datap;
3456 	int		qres;
3457 	size_t		dsize;
3458 	u_short		rstatus;
3459 	char *		tag;
3460 	char *		val;
3461 	int		fields;
3462 	u_int		ui;
3463 	ifstats_row	row;
3464 	int		comprende;
3465 	size_t		len;
3466 
3467 	qres = doquery(CTL_OP_READ_ORDLIST_A, 0, TRUE, 0, NULL, &rstatus,
3468 		       &dsize, &datap);
3469 	if (qres)	/* message already displayed */
3470 		return;
3471 
3472 	xprintf(fp,
3473 		"    interface name                                        send\n"
3474 		" #  address/broadcast     drop flag ttl mc received sent failed peers   uptime\n"
3475 		"==============================================================================\n");
3476 		/* '=' x 78 */
3477 
3478 	ZERO(row);
3479 	fields = 0;
3480 	ui = 0;
3481 	while (nextvar(&dsize, &datap, &tag, &val)) {
3482 		INSIST(tag && val);
3483 		if (debug > 1)
3484 		    xprintf(stderr, "nextvar gave: %s = %s\n", tag, val);
3485 		comprende = FALSE;
3486 		switch(tag[0]) {
3487 
3488 		case 'a':
3489 			if (1 == sscanf(tag, addr_fmt, &ui) &&
3490 			    decodenetnum(val, &row.addr))
3491 				comprende = TRUE;
3492 			break;
3493 
3494 		case 'b':
3495 			if (1 == sscanf(tag, bcast_fmt, &ui) &&
3496 			    ('\0' == *val ||
3497 			     decodenetnum(val, &row.bcast)))
3498 				comprende = TRUE;
3499 			break;
3500 
3501 		case 'e':
3502 			if (1 == sscanf(tag, en_fmt, &ui) &&
3503 			    1 == sscanf(val, "%d", &row.enabled))
3504 				comprende = TRUE;
3505 			break;
3506 
3507 		case 'f':
3508 			if (1 == sscanf(tag, flags_fmt, &ui) &&
3509 			    1 == sscanf(val, "0x%x", &row.flags))
3510 				comprende = TRUE;
3511 			break;
3512 
3513 		case 'm':
3514 			if (1 == sscanf(tag, mc_fmt, &ui) &&
3515 			    1 == sscanf(val, "%u", &row.mcast_count))
3516 				comprende = TRUE;
3517 			break;
3518 
3519 		case 'n':
3520 			if (1 == sscanf(tag, name_fmt, &ui)) {
3521 				/* strip quotes */
3522 				len = strlen(val);
3523 				if (len >= 2 &&
3524 				    len - 2 < sizeof(row.name)) {
3525 					len -= 2;
3526 					memcpy(row.name, val + 1, len);
3527 					row.name[len] = '\0';
3528 					comprende = TRUE;
3529 				}
3530 			}
3531 			break;
3532 
3533 		case 'p':
3534 			if (1 == sscanf(tag, pc_fmt, &ui) &&
3535 			    1 == sscanf(val, "%u", &row.peer_count))
3536 				comprende = TRUE;
3537 			break;
3538 
3539 		case 'r':
3540 			if (1 == sscanf(tag, rx_fmt, &ui) &&
3541 			    1 == sscanf(val, "%u", &row.received))
3542 				comprende = TRUE;
3543 			break;
3544 
3545 		case 't':
3546 			if (1 == sscanf(tag, tl_fmt, &ui) &&
3547 			    1 == sscanf(val, "%u", &row.ttl))
3548 				comprende = TRUE;
3549 			else if (1 == sscanf(tag, tx_fmt, &ui) &&
3550 				 1 == sscanf(val, "%u", &row.sent))
3551 				comprende = TRUE;
3552 			else if (1 == sscanf(tag, txerr_fmt, &ui) &&
3553 				 1 == sscanf(val, "%u", &row.send_errors))
3554 				comprende = TRUE;
3555 			break;
3556 
3557 		case 'u':
3558 			if (1 == sscanf(tag, up_fmt, &ui) &&
3559 			    1 == sscanf(val, "%u", &row.uptime))
3560 				comprende = TRUE;
3561 			break;
3562 		}
3563 
3564 		if (comprende) {
3565 			/* error out if rows out of order */
3566 			validate_ifnum(fp, ui, &fields, &row);
3567 			/* if the row is complete, print it */
3568 			another_ifstats_field(&fields, &row, fp);
3569 		}
3570 	}
3571 	if (fields != IFSTATS_FIELDS)
3572 		xprintf(fp, "Warning: incomplete row with %d (of %d) fields\n",
3573 			fields, IFSTATS_FIELDS);
3574 
3575 	fflush(fp);
3576 }
3577 
3578 
3579 /*
3580  * validate_reslist_idx - helper for reslist()
3581  *
3582  * Ensures rows are received in order and complete.
3583  */
3584 static void
3585 validate_reslist_idx(
3586 	FILE *		fp,
3587 	u_int		idx,
3588 	int *		pfields,
3589 	reslist_row *	prow
3590 	)
3591 {
3592 	if (prow->idx == idx)
3593 		return;
3594 	if (prow->idx + 1 == idx) {
3595 		if (*pfields < RESLIST_FIELDS)
3596 			xprintf(fp, "Warning: incomplete row with %d (of %d) fields",
3597 				*pfields, RESLIST_FIELDS);
3598 		*pfields = 0;
3599 		prow->idx = idx;
3600 		return;
3601 	}
3602 	xprintf(stderr,
3603 		"received reslist index %u, have %d of %d fields for index %u, aborting.\n",
3604 		idx, *pfields, RESLIST_FIELDS, prow->idx);
3605 	exit(1);
3606 }
3607 
3608 
3609 /*
3610  * another_reslist_field - helper for reslist()
3611  *
3612  * If all fields for the row have been received, print it.
3613  */
3614 static void
3615 another_reslist_field(
3616 	int *		pfields,
3617 	reslist_row *	prow,
3618 	FILE *		fp
3619 	)
3620 {
3621 	char	addrmaskstr[128];
3622 	int	prefix;	/* subnet mask as prefix bits count */
3623 	u_int	idx;
3624 
3625 	(*pfields)++;
3626 	/* we understand 4 tags */
3627 	if (RESLIST_FIELDS > *pfields)
3628 		return;
3629 
3630 	prefix = sockaddr_masktoprefixlen(&prow->mask);
3631 	if (prefix >= 0)
3632 		snprintf(addrmaskstr, sizeof(addrmaskstr), "%s/%d",
3633 			 stoa(&prow->addr), prefix);
3634 	else
3635 		snprintf(addrmaskstr, sizeof(addrmaskstr), "%s %s",
3636 			 stoa(&prow->addr), stoa(&prow->mask));
3637 
3638 	/*
3639 	"   hits    addr/prefix or addr mask\n"
3640 	"           restrictions\n"
3641 	"==============================================================================\n");
3642 	 */
3643 	xprintf(fp,
3644 		"%10lu %s\n"
3645 		"           %s\n",
3646 		prow->hits, addrmaskstr, prow->flagstr);
3647 	idx = prow->idx;
3648 	ZERO(*prow);
3649 	prow->idx = idx;
3650 }
3651 
3652 
3653 /*
3654  * reslist - ntpq -c reslist modeled on ntpdc -c reslist.
3655  */
3656 static void
3657 reslist(
3658 	struct parse *	pcmd,
3659 	FILE *		fp
3660 	)
3661 {
3662 	const char addr_fmtu[] =	"addr.%u";
3663 	const char mask_fmtu[] =	"mask.%u";
3664 	const char hits_fmt[] =		"hits.%u";
3665 	const char flags_fmt[] =	"flags.%u";
3666 	const char qdata[] =		"addr_restrictions";
3667 	const int qdata_chars =		COUNTOF(qdata) - 1;
3668 	const char *	datap;
3669 	int		qres;
3670 	size_t		dsize;
3671 	u_short		rstatus;
3672 	char *		tag;
3673 	char *		val;
3674 	int		fields;
3675 	u_int		ui;
3676 	reslist_row	row;
3677 	int		comprende;
3678 	size_t		len;
3679 
3680 	qres = doquery(CTL_OP_READ_ORDLIST_A, 0, TRUE, qdata_chars,
3681 		       qdata, &rstatus, &dsize, &datap);
3682 	if (qres)	/* message already displayed */
3683 		return;
3684 
3685 	xprintf(fp,
3686 		"   hits    addr/prefix or addr mask\n"
3687 		"           restrictions\n"
3688 		"==============================================================================\n");
3689 		/* '=' x 78 */
3690 
3691 	ZERO(row);
3692 	fields = 0;
3693 	ui = 0;
3694 	while (nextvar(&dsize, &datap, &tag, &val)) {
3695 		INSIST(tag && val);
3696 		if (debug > 1)
3697 			xprintf(stderr, "nextvar gave: %s = %s\n", tag, val);
3698 		comprende = FALSE;
3699 		switch(tag[0]) {
3700 
3701 		case 'a':
3702 			if (1 == sscanf(tag, addr_fmtu, &ui) &&
3703 			    decodenetnum(val, &row.addr))
3704 				comprende = TRUE;
3705 			break;
3706 
3707 		case 'f':
3708 			if (1 == sscanf(tag, flags_fmt, &ui)) {
3709 				if (NULL == val) {
3710 					row.flagstr[0] = '\0';
3711 					comprende = TRUE;
3712 				} else if ((len = strlen(val)) < sizeof(row.flagstr)) {
3713 					memcpy(row.flagstr, val, len);
3714 					row.flagstr[len] = '\0';
3715 					comprende = TRUE;
3716 				} else {
3717 					 /* no flags, and still !comprende */
3718 					row.flagstr[0] = '\0';
3719 				}
3720 			}
3721 			break;
3722 
3723 		case 'h':
3724 			if (1 == sscanf(tag, hits_fmt, &ui) &&
3725 			    1 == sscanf(val, "%lu", &row.hits))
3726 				comprende = TRUE;
3727 			break;
3728 
3729 		case 'm':
3730 			if (1 == sscanf(tag, mask_fmtu, &ui) &&
3731 			    decodenetnum(val, &row.mask))
3732 				comprende = TRUE;
3733 			break;
3734 		}
3735 
3736 		if (comprende) {
3737 			/* error out if rows out of order */
3738 			validate_reslist_idx(fp, ui, &fields, &row);
3739 			/* if the row is complete, print it */
3740 			another_reslist_field(&fields, &row, fp);
3741 		}
3742 	}
3743 	if (fields != RESLIST_FIELDS)
3744 		xprintf(fp, "Warning: incomplete row with %d (of %d) fields",
3745 			fields, RESLIST_FIELDS);
3746 
3747 	fflush(fp);
3748 }
3749 
3750 
3751 /*
3752  * collect_display_vdc
3753  */
3754 static void
3755 collect_display_vdc(
3756 	associd_t	as,
3757 	vdc *		table,
3758 	int		decodestatus,
3759 	FILE *		fp
3760 	)
3761 {
3762 	static const char * const suf[2] = { "adr", "port" };
3763 	static const char * const leapbits[4] = { "00", "01",
3764 						  "10", "11" };
3765 	struct varlist vl[MAXLIST];
3766 	char tagbuf[32];
3767 	vdc *pvdc;
3768 	u_short rstatus;
3769 	size_t rsize;
3770 	const char *rdata;
3771 	int qres;
3772 	char *tag;
3773 	char *val;
3774 	u_int n;
3775 	size_t len;
3776 	int match;
3777 	u_long ul;
3778 	int vtype;
3779 	sockaddr_u sau;
3780 
3781 	ZERO(vl);
3782 	for (pvdc = table; pvdc->tag != NULL; pvdc++) {
3783 		ZERO(pvdc->v);
3784 		if (NTP_ADD != pvdc->type) {
3785 			doaddvlist(vl, pvdc->tag);
3786 		} else {
3787 			for (n = 0; n < COUNTOF(suf); n++) {
3788 				snprintf(tagbuf, sizeof(tagbuf), "%s%s",
3789 					 pvdc->tag, suf[n]);
3790 				doaddvlist(vl, tagbuf);
3791 			}
3792 		}
3793 	}
3794 	qres = doquerylist(vl, CTL_OP_READVAR, as, 0, &rstatus, &rsize,
3795 			   &rdata);
3796 	doclearvlist(vl);
3797 	if (qres)
3798 		return;		/* error msg already displayed */
3799 
3800 	/*
3801 	 * iterate over the response variables filling vdc_table with
3802 	 * the retrieved values.
3803 	 */
3804 	while (nextvar(&rsize, &rdata, &tag, &val)) {
3805 		INSIST(tag && val);
3806 		n = 0;
3807 		for (pvdc = table; pvdc->tag != NULL; pvdc++) {
3808 			len = strlen(pvdc->tag);
3809 			if (strncmp(tag, pvdc->tag, len))
3810 				continue;
3811 			if (NTP_ADD != pvdc->type) {
3812 				if ('\0' != tag[len])
3813 					continue;
3814 				break;
3815 			}
3816 			match = FALSE;
3817 			for (n = 0; n < COUNTOF(suf); n++) {
3818 				if (strcmp(tag + len, suf[n]))
3819 					continue;
3820 				match = TRUE;
3821 				break;
3822 			}
3823 			if (match)
3824 				break;
3825 		}
3826 		if (NULL == pvdc->tag)
3827 			continue;
3828 		switch (pvdc->type) {
3829 
3830 		case NTP_STR:
3831 			/* strip surrounding double quotes */
3832 			if ('"' == val[0]) {
3833 				len = strlen(val);
3834 				if (len > 0 && '"' == val[len - 1]) {
3835 					val[len - 1] = '\0';
3836 					val++;
3837 				}
3838 			}
3839 			/* fallthru */
3840 		case NTP_REFID:	/* fallthru */
3841 		case NTP_MODE:	/* fallthru */
3842 		case NTP_2BIT:
3843 			pvdc->v.str = estrdup(val);
3844 			break;
3845 
3846 		case NTP_LFP:
3847 			decodets(val, &pvdc->v.lfp);
3848 			break;
3849 
3850 		case NTP_ADP:
3851 			if (!decodenetnum(val, &pvdc->v.sau))
3852 				xprintf(stderr, "malformed %s=%s\n",
3853 					pvdc->tag, val);
3854 			break;
3855 
3856 		case NTP_ADD:
3857 			if (0 == n) {	/* adr */
3858 				if (!decodenetnum(val, &pvdc->v.sau))
3859 					xprintf(stderr,
3860 						"malformed %s=%s\n",
3861 						pvdc->tag, val);
3862 			} else {	/* port */
3863 				if (atouint(val, &ul))
3864 					SET_PORT(&pvdc->v.sau,
3865 						 (u_short)ul);
3866 			}
3867 			break;
3868 		}
3869 	}
3870 
3871 	/* and display */
3872 	if (decodestatus) {
3873 		vtype = (0 == as)
3874 			    ? TYPE_SYS
3875 			    : TYPE_PEER;
3876 		xprintf(fp, "associd=%u status=%04x %s,\n", as, rstatus,
3877 			statustoa(vtype, rstatus));
3878 	}
3879 
3880 	for (pvdc = table; pvdc->tag != NULL; pvdc++) {
3881 		switch (pvdc->type) {
3882 
3883 		case NTP_STR:
3884 			if (pvdc->v.str != NULL) {
3885 				xprintf(fp, "%s  %s\n", pvdc->display,
3886 					pvdc->v.str);
3887 				free(pvdc->v.str);
3888 				pvdc->v.str = NULL;
3889 			}
3890 			break;
3891 
3892 		case NTP_ADD:	/* fallthru */
3893 		case NTP_ADP:
3894 			xprintf(fp, "%s  %s\n", pvdc->display,
3895 				nntohostp(&pvdc->v.sau));
3896 			break;
3897 
3898 		case NTP_LFP:
3899 			xprintf(fp, "%s  %s\n", pvdc->display,
3900 				prettydate(&pvdc->v.lfp));
3901 			break;
3902 
3903 		case NTP_MODE:
3904 			atouint(pvdc->v.str, &ul);
3905 			xprintf(fp, "%s  %s\n", pvdc->display,
3906 				modetoa((int)ul));
3907 			free(pvdc->v.str);
3908 			pvdc->v.str = NULL;
3909 			break;
3910 
3911 		case NTP_2BIT:
3912 			atouint(pvdc->v.str, &ul);
3913 			xprintf(fp, "%s  %s\n", pvdc->display,
3914 				leapbits[ul & 0x3]);
3915 			free(pvdc->v.str);
3916 			pvdc->v.str = NULL;
3917 			break;
3918 
3919 		case NTP_REFID:
3920 			if (!decodenetnum(pvdc->v.str, &sau)) {
3921 				fprintf(fp, "%s  %s\n", pvdc->display,    /* Text fmt */
3922 					pvdc->v.str);
3923 			} else if (drefid == REFID_IPV4) {
3924 				fprintf(fp, "%s  %s\n", pvdc->display,    /* IPv4 fmt */
3925 					stoa(&sau));
3926 			} else {
3927 				fprintf (fp, "%s  0x%08x\n", pvdc->display,	   /* Hex / hash */
3928 					 ntohl(addr2refid(&sau)));
3929 			}
3930 			free(pvdc->v.str);
3931 			pvdc->v.str = NULL;
3932 			break;
3933 
3934 		default:
3935 			xprintf(stderr, "unexpected vdc type %d for %s\n",
3936 				pvdc->type, pvdc->tag);
3937 			break;
3938 		}
3939 	}
3940 }
3941 
3942 
3943 /*
3944  * sysstats - implements ntpq -c sysstats modeled on ntpdc -c sysstats
3945  */
3946 static void
3947 sysstats(
3948 	struct parse *pcmd,
3949 	FILE *fp
3950 	)
3951 {
3952     static vdc sysstats_vdc[] = {
3953 	VDC_INIT("ss_uptime",		"uptime:               ", NTP_STR),
3954 	VDC_INIT("ss_reset",		"sysstats reset:       ", NTP_STR),
3955 	VDC_INIT("ss_received",		"packets received:     ", NTP_STR),
3956 	VDC_INIT("ss_thisver",		"current version:      ", NTP_STR),
3957 	VDC_INIT("ss_oldver",		"older version:        ", NTP_STR),
3958 	VDC_INIT("ss_badformat",	"bad length or format: ", NTP_STR),
3959 	VDC_INIT("ss_badauth",		"authentication failed:", NTP_STR),
3960 	VDC_INIT("ss_declined",		"declined:             ", NTP_STR),
3961 	VDC_INIT("ss_restricted",	"restricted:           ", NTP_STR),
3962 	VDC_INIT("ss_limited",		"rate limited:         ", NTP_STR),
3963 	VDC_INIT("ss_kodsent",		"KoD responses:        ", NTP_STR),
3964 	VDC_INIT("ss_processed",	"processed for time:   ", NTP_STR),
3965 #if 0
3966 	VDC_INIT("ss_lamport",		"Lamport violations:    ", NTP_STR),
3967 	VDC_INIT("ss_tsrounding",	"bad timestamp rounding:", NTP_STR),
3968 #endif
3969 	VDC_INIT(NULL,			NULL,			  0)
3970     };
3971 
3972 	collect_display_vdc(0, sysstats_vdc, FALSE, fp);
3973 }
3974 
3975 
3976 /*
3977  * sysinfo - modeled on ntpdc's sysinfo
3978  */
3979 static void
3980 sysinfo(
3981 	struct parse *pcmd,
3982 	FILE *fp
3983 	)
3984 {
3985     static vdc sysinfo_vdc[] = {
3986 	VDC_INIT("peeradr",		"system peer:      ", NTP_ADP),
3987 	VDC_INIT("peermode",		"system peer mode: ", NTP_MODE),
3988 	VDC_INIT("leap",		"leap indicator:   ", NTP_2BIT),
3989 	VDC_INIT("stratum",		"stratum:          ", NTP_STR),
3990 	VDC_INIT("precision",		"log2 precision:   ", NTP_STR),
3991 	VDC_INIT("rootdelay",		"root delay:       ", NTP_STR),
3992 	VDC_INIT("rootdisp",		"root dispersion:  ", NTP_STR),
3993 	VDC_INIT("refid",		"reference ID:     ", NTP_REFID),
3994 	VDC_INIT("reftime",		"reference time:   ", NTP_LFP),
3995 	VDC_INIT("sys_jitter",		"system jitter:    ", NTP_STR),
3996 	VDC_INIT("clk_jitter",		"clock jitter:     ", NTP_STR),
3997 	VDC_INIT("clk_wander",		"clock wander:     ", NTP_STR),
3998 	VDC_INIT("bcastdelay",		"broadcast delay:  ", NTP_STR),
3999 	VDC_INIT("authdelay",		"symm. auth. delay:", NTP_STR),
4000 	VDC_INIT(NULL,			NULL,		      0)
4001     };
4002 
4003 	collect_display_vdc(0, sysinfo_vdc, TRUE, fp);
4004 }
4005 
4006 
4007 /*
4008  * kerninfo - modeled on ntpdc's kerninfo
4009  */
4010 static void
4011 kerninfo(
4012 	struct parse *pcmd,
4013 	FILE *fp
4014 	)
4015 {
4016     static vdc kerninfo_vdc[] = {
4017 	VDC_INIT("koffset",		"pll offset:          ", NTP_STR),
4018 	VDC_INIT("kfreq",		"pll frequency:       ", NTP_STR),
4019 	VDC_INIT("kmaxerr",		"maximum error:       ", NTP_STR),
4020 	VDC_INIT("kesterr",		"estimated error:     ", NTP_STR),
4021 	VDC_INIT("kstflags",		"kernel status:       ", NTP_STR),
4022 	VDC_INIT("ktimeconst",		"pll time constant:   ", NTP_STR),
4023 	VDC_INIT("kprecis",		"precision:           ", NTP_STR),
4024 	VDC_INIT("kfreqtol",		"frequency tolerance: ", NTP_STR),
4025 	VDC_INIT("kppsfreq",		"pps frequency:       ", NTP_STR),
4026 	VDC_INIT("kppsstab",		"pps stability:       ", NTP_STR),
4027 	VDC_INIT("kppsjitter",		"pps jitter:          ", NTP_STR),
4028 	VDC_INIT("kppscalibdur",	"calibration interval ", NTP_STR),
4029 	VDC_INIT("kppscalibs",		"calibration cycles:  ", NTP_STR),
4030 	VDC_INIT("kppsjitexc",		"jitter exceeded:     ", NTP_STR),
4031 	VDC_INIT("kppsstbexc",		"stability exceeded:  ", NTP_STR),
4032 	VDC_INIT("kppscaliberrs",	"calibration errors:  ", NTP_STR),
4033 	VDC_INIT(NULL,			NULL,			 0)
4034     };
4035 
4036 	collect_display_vdc(0, kerninfo_vdc, TRUE, fp);
4037 }
4038 
4039 
4040 /*
4041  * monstats - implements ntpq -c monstats
4042  */
4043 static void
4044 monstats(
4045 	struct parse *pcmd,
4046 	FILE *fp
4047 	)
4048 {
4049     static vdc monstats_vdc[] = {
4050 	VDC_INIT("mru_enabled",		"enabled:            ", NTP_STR),
4051 	VDC_INIT("mru_depth",		"addresses:          ", NTP_STR),
4052 	VDC_INIT("mru_deepest",		"peak addresses:     ", NTP_STR),
4053 	VDC_INIT("mru_maxdepth",	"maximum addresses:  ", NTP_STR),
4054 	VDC_INIT("mru_mindepth",	"reclaim above count:", NTP_STR),
4055 	VDC_INIT("mru_maxage",		"reclaim older than: ", NTP_STR),
4056 	VDC_INIT("mru_mem",		"kilobytes:          ", NTP_STR),
4057 	VDC_INIT("mru_maxmem",		"maximum kilobytes:  ", NTP_STR),
4058 	VDC_INIT(NULL,			NULL,			0)
4059     };
4060 
4061 	collect_display_vdc(0, monstats_vdc, FALSE, fp);
4062 }
4063 
4064 
4065 /*
4066  * iostats - ntpq -c iostats - network input and output counters
4067  */
4068 static void
4069 iostats(
4070 	struct parse *pcmd,
4071 	FILE *fp
4072 	)
4073 {
4074     static vdc iostats_vdc[] = {
4075 	VDC_INIT("iostats_reset",	"time since reset:     ", NTP_STR),
4076 	VDC_INIT("total_rbuf",		"receive buffers:      ", NTP_STR),
4077 	VDC_INIT("free_rbuf",		"free receive buffers: ", NTP_STR),
4078 	VDC_INIT("used_rbuf",		"used receive buffers: ", NTP_STR),
4079 	VDC_INIT("rbuf_lowater",	"low water refills:    ", NTP_STR),
4080 	VDC_INIT("io_dropped",		"dropped packets:      ", NTP_STR),
4081 	VDC_INIT("io_ignored",		"ignored packets:      ", NTP_STR),
4082 	VDC_INIT("io_received",		"received packets:     ", NTP_STR),
4083 	VDC_INIT("io_sent",		"packets sent:         ", NTP_STR),
4084 	VDC_INIT("io_sendfailed",	"packet send failures: ", NTP_STR),
4085 	VDC_INIT("io_wakeups",		"input wakeups:        ", NTP_STR),
4086 	VDC_INIT("io_goodwakeups",	"useful input wakeups: ", NTP_STR),
4087 	VDC_INIT(NULL,			NULL,			  0)
4088     };
4089 
4090 	collect_display_vdc(0, iostats_vdc, FALSE, fp);
4091 }
4092 
4093 
4094 /*
4095  * timerstats - ntpq -c timerstats - interval timer counters
4096  */
4097 static void
4098 timerstats(
4099 	struct parse *pcmd,
4100 	FILE *fp
4101 	)
4102 {
4103     static vdc timerstats_vdc[] = {
4104 	VDC_INIT("timerstats_reset",	"time since reset:  ", NTP_STR),
4105 	VDC_INIT("timer_overruns",	"timer overruns:    ", NTP_STR),
4106 	VDC_INIT("timer_xmts",		"calls to transmit: ", NTP_STR),
4107 	VDC_INIT(NULL,			NULL,		       0)
4108     };
4109 
4110 	collect_display_vdc(0, timerstats_vdc, FALSE, fp);
4111 }
4112 
4113 
4114 /*
4115  * authinfo - implements ntpq -c authinfo
4116  */
4117 static void
4118 authinfo(
4119 	struct parse *pcmd,
4120 	FILE *fp
4121 	)
4122 {
4123     static vdc authinfo_vdc[] = {
4124 	VDC_INIT("authreset",		"time since reset:", NTP_STR),
4125 	VDC_INIT("authkeys",		"stored keys:     ", NTP_STR),
4126 	VDC_INIT("authfreek",		"free keys:       ", NTP_STR),
4127 	VDC_INIT("authklookups",	"key lookups:     ", NTP_STR),
4128 	VDC_INIT("authknotfound",	"keys not found:  ", NTP_STR),
4129 	VDC_INIT("authkuncached",	"uncached keys:   ", NTP_STR),
4130 	VDC_INIT("authkexpired",	"expired keys:    ", NTP_STR),
4131 	VDC_INIT("authencrypts",	"encryptions:     ", NTP_STR),
4132 	VDC_INIT("authdecrypts",	"decryptions:     ", NTP_STR),
4133 	VDC_INIT(NULL,			NULL,		     0)
4134     };
4135 
4136 	collect_display_vdc(0, authinfo_vdc, FALSE, fp);
4137 }
4138 
4139 
4140 /*
4141  * pstats - show statistics for a peer
4142  */
4143 static void
4144 pstats(
4145 	struct parse *pcmd,
4146 	FILE *fp
4147 	)
4148 {
4149     static vdc pstats_vdc[] = {
4150 	VDC_INIT("src",		"remote host:         ", NTP_ADD),
4151 	VDC_INIT("dst",		"local address:       ", NTP_ADD),
4152 	VDC_INIT("timerec",	"time last received:  ", NTP_STR),
4153 	VDC_INIT("timer",	"time until next send:", NTP_STR),
4154 	VDC_INIT("timereach",	"reachability change: ", NTP_STR),
4155 	VDC_INIT("sent",	"packets sent:        ", NTP_STR),
4156 	VDC_INIT("received",	"packets received:    ", NTP_STR),
4157 	VDC_INIT("badauth",	"bad authentication:  ", NTP_STR),
4158 	VDC_INIT("bogusorg",	"bogus origin:        ", NTP_STR),
4159 	VDC_INIT("oldpkt",	"duplicate:           ", NTP_STR),
4160 	VDC_INIT("seldisp",	"bad dispersion:      ", NTP_STR),
4161 	VDC_INIT("selbroken",	"bad reference time:  ", NTP_STR),
4162 	VDC_INIT("candidate",	"candidate order:     ", NTP_STR),
4163 	VDC_INIT(NULL,		NULL,			 0)
4164     };
4165 	associd_t associd;
4166 
4167 	associd = checkassocid(pcmd->argval[0].uval);
4168 	if (0 == associd)
4169 		return;
4170 
4171 	collect_display_vdc(associd, pstats_vdc, TRUE, fp);
4172 }
4173