/* * ntpq-subs.c - subroutines which are called to perform ntpq commands. */ #include #include #include #include #include #include "ntpq.h" #include "ntpq-opts.h" extern char currenthost[]; extern int currenthostisnum; size_t maxhostlen; /* * Declarations for command handlers in here */ static associd_t checkassocid (u_int32); static struct varlist *findlistvar (struct varlist *, char *); static void doaddvlist (struct varlist *, const char *); static void dormvlist (struct varlist *, const char *); static void doclearvlist (struct varlist *); static void makequerydata (struct varlist *, size_t *, char *); static int doquerylist (struct varlist *, int, associd_t, int, u_short *, size_t *, const char **); static void doprintvlist (struct varlist *, FILE *); static void addvars (struct parse *, FILE *); static void rmvars (struct parse *, FILE *); static void clearvars (struct parse *, FILE *); static void showvars (struct parse *, FILE *); static int dolist (struct varlist *, associd_t, int, int, FILE *); static void readlist (struct parse *, FILE *); static void writelist (struct parse *, FILE *); static void readvar (struct parse *, FILE *); static void writevar (struct parse *, FILE *); static void clocklist (struct parse *, FILE *); static void clockvar (struct parse *, FILE *); static int findassidrange (u_int32, u_int32, int *, int *, FILE *); static void mreadlist (struct parse *, FILE *); static void mreadvar (struct parse *, FILE *); static void printassoc (int, FILE *); static void associations (struct parse *, FILE *); static void lassociations (struct parse *, FILE *); static void passociations (struct parse *, FILE *); static void lpassociations (struct parse *, FILE *); #ifdef UNUSED static void radiostatus (struct parse *, FILE *); #endif /* UNUSED */ static void authinfo (struct parse *, FILE *); static void pstats (struct parse *, FILE *); static long when (l_fp *, l_fp *, l_fp *); static char * prettyinterval (char *, size_t, long); static int doprintpeers (struct varlist *, int, int, size_t, const char *, FILE *, int); static int dogetpeers (struct varlist *, associd_t, FILE *, int); static void dopeers (int, FILE *, int); static void peers (struct parse *, FILE *); static void doapeers (int, FILE *, int); static void apeers (struct parse *, FILE *); static void lpeers (struct parse *, FILE *); static void doopeers (int, FILE *, int); static void opeers (struct parse *, FILE *); static void lopeers (struct parse *, FILE *); static void config (struct parse *, FILE *); static void saveconfig (struct parse *, FILE *); static void config_from_file(struct parse *, FILE *); static void mrulist (struct parse *, FILE *); static void ifstats (struct parse *, FILE *); static void reslist (struct parse *, FILE *); static void sysstats (struct parse *, FILE *); static void sysinfo (struct parse *, FILE *); static void kerninfo (struct parse *, FILE *); static void monstats (struct parse *, FILE *); static void iostats (struct parse *, FILE *); static void timerstats (struct parse *, FILE *); /* * Commands we understand. Ntpdc imports this. */ struct xcmd opcmds[] = { { "saveconfig", saveconfig, { NTP_STR, NO, NO, NO }, { "filename", "", "", ""}, "save ntpd configuration to file, . for current config file"}, { "associations", associations, { NO, NO, NO, NO }, { "", "", "", "" }, "print list of association ID's and statuses for the server's peers" }, { "passociations", passociations, { NO, NO, NO, NO }, { "", "", "", "" }, "print list of associations returned by last associations command" }, { "lassociations", lassociations, { NO, NO, NO, NO }, { "", "", "", "" }, "print list of associations including all client information" }, { "lpassociations", lpassociations, { NO, NO, NO, NO }, { "", "", "", "" }, "print last obtained list of associations, including client information" }, { "addvars", addvars, { NTP_STR, NO, NO, NO }, { "name[=value][,...]", "", "", "" }, "add variables to the variable list or change their values" }, { "rmvars", rmvars, { NTP_STR, NO, NO, NO }, { "name[,...]", "", "", "" }, "remove variables from the variable list" }, { "clearvars", clearvars, { NO, NO, NO, NO }, { "", "", "", "" }, "remove all variables from the variable list" }, { "showvars", showvars, { NO, NO, NO, NO }, { "", "", "", "" }, "print variables on the variable list" }, { "readlist", readlist, { OPT|NTP_UINT, NO, NO, NO }, { "assocID", "", "", "" }, "read the system or peer variables included in the variable list" }, { "rl", readlist, { OPT|NTP_UINT, NO, NO, NO }, { "assocID", "", "", "" }, "read the system or peer variables included in the variable list" }, { "writelist", writelist, { OPT|NTP_UINT, NO, NO, NO }, { "assocID", "", "", "" }, "write the system or peer variables included in the variable list" }, { "readvar", readvar, { OPT|NTP_UINT, OPT|NTP_STR, OPT|NTP_STR, OPT|NTP_STR, }, { "assocID", "varname1", "varname2", "varname3" }, "read system or peer variables" }, { "rv", readvar, { OPT|NTP_UINT, OPT|NTP_STR, OPT|NTP_STR, OPT|NTP_STR, }, { "assocID", "varname1", "varname2", "varname3" }, "read system or peer variables" }, { "writevar", writevar, { NTP_UINT, NTP_STR, NO, NO }, { "assocID", "name=value,[...]", "", "" }, "write system or peer variables" }, { "mreadlist", mreadlist, { NTP_UINT, NTP_UINT, NO, NO }, { "assocIDlow", "assocIDhigh", "", "" }, "read the peer variables in the variable list for multiple peers" }, { "mrl", mreadlist, { NTP_UINT, NTP_UINT, NO, NO }, { "assocIDlow", "assocIDhigh", "", "" }, "read the peer variables in the variable list for multiple peers" }, { "mreadvar", mreadvar, { NTP_UINT, NTP_UINT, OPT|NTP_STR, NO }, { "assocIDlow", "assocIDhigh", "name=value[,...]", "" }, "read peer variables from multiple peers" }, { "mrv", mreadvar, { NTP_UINT, NTP_UINT, OPT|NTP_STR, NO }, { "assocIDlow", "assocIDhigh", "name=value[,...]", "" }, "read peer variables from multiple peers" }, { "clocklist", clocklist, { OPT|NTP_UINT, NO, NO, NO }, { "assocID", "", "", "" }, "read the clock variables included in the variable list" }, { "cl", clocklist, { OPT|NTP_UINT, NO, NO, NO }, { "assocID", "", "", "" }, "read the clock variables included in the variable list" }, { "clockvar", clockvar, { OPT|NTP_UINT, OPT|NTP_STR, NO, NO }, { "assocID", "name=value[,...]", "", "" }, "read clock variables" }, { "cv", clockvar, { OPT|NTP_UINT, OPT|NTP_STR, NO, NO }, { "assocID", "name=value[,...]", "", "" }, "read clock variables" }, { "pstats", pstats, { NTP_UINT, NO, NO, NO }, { "assocID", "", "", "" }, "show statistics for a peer" }, { "peers", peers, { OPT|IP_VERSION, NO, NO, NO }, { "-4|-6", "", "", "" }, "obtain and print a list of the server's peers [IP version]" }, { "apeers", apeers, { OPT|IP_VERSION, NO, NO, NO }, { "-4|-6", "", "", "" }, "obtain and print a list of the server's peers and their assocIDs [IP version]" }, { "lpeers", lpeers, { OPT|IP_VERSION, NO, NO, NO }, { "-4|-6", "", "", "" }, "obtain and print a list of all peers and clients [IP version]" }, { "opeers", opeers, { OPT|IP_VERSION, NO, NO, NO }, { "-4|-6", "", "", "" }, "print peer list the old way, with dstadr shown rather than refid [IP version]" }, { "lopeers", lopeers, { OPT|IP_VERSION, NO, NO, NO }, { "-4|-6", "", "", "" }, "obtain and print a list of all peers and clients showing dstadr [IP version]" }, { ":config", config, { NTP_STR, NO, NO, NO }, { "", "", "", "" }, "send a remote configuration command to ntpd" }, { "config-from-file", config_from_file, { NTP_STR, NO, NO, NO }, { "", "", "", "" }, "configure ntpd using the configuration filename" }, { "mrulist", mrulist, { OPT|NTP_STR, OPT|NTP_STR, OPT|NTP_STR, OPT|NTP_STR }, { "tag=value", "tag=value", "tag=value", "tag=value" }, "display the list of most recently seen source addresses, tags mincount=... resall=0x... resany=0x..." }, { "ifstats", ifstats, { NO, NO, NO, NO }, { "", "", "", "" }, "show statistics for each local address ntpd is using" }, { "reslist", reslist, { NO, NO, NO, NO }, { "", "", "", "" }, "show ntpd access control list" }, { "sysinfo", sysinfo, { NO, NO, NO, NO }, { "", "", "", "" }, "display system summary" }, { "kerninfo", kerninfo, { NO, NO, NO, NO }, { "", "", "", "" }, "display kernel loop and PPS statistics" }, { "sysstats", sysstats, { NO, NO, NO, NO }, { "", "", "", "" }, "display system uptime and packet counts" }, { "monstats", monstats, { NO, NO, NO, NO }, { "", "", "", "" }, "display monitor (mrulist) counters and limits" }, { "authinfo", authinfo, { NO, NO, NO, NO }, { "", "", "", "" }, "display symmetric authentication counters" }, { "iostats", iostats, { NO, NO, NO, NO }, { "", "", "", "" }, "display network input and output counters" }, { "timerstats", timerstats, { NO, NO, NO, NO }, { "", "", "", "" }, "display interval timer counters" }, { 0, 0, { NO, NO, NO, NO }, { "-4|-6", "", "", "" }, "" } }; /* * Variable list data space */ #define MAXLINE 512 /* maximum length of a line */ #define MAXLIST 128 /* maximum variables in list */ #define LENHOSTNAME 256 /* host name limit */ #define MRU_GOT_COUNT 0x1 #define MRU_GOT_LAST 0x2 #define MRU_GOT_FIRST 0x4 #define MRU_GOT_MV 0x8 #define MRU_GOT_RS 0x10 #define MRU_GOT_ADDR 0x20 #define MRU_GOT_ALL (MRU_GOT_COUNT | MRU_GOT_LAST | MRU_GOT_FIRST \ | MRU_GOT_MV | MRU_GOT_RS | MRU_GOT_ADDR) /* * mrulist() depends on MRUSORT_DEF and MRUSORT_RDEF being the first two */ typedef enum mru_sort_order_tag { MRUSORT_DEF = 0, /* lstint ascending */ MRUSORT_R_DEF, /* lstint descending */ MRUSORT_AVGINT, /* avgint ascending */ MRUSORT_R_AVGINT, /* avgint descending */ MRUSORT_ADDR, /* IPv4 asc. then IPv6 asc. */ MRUSORT_R_ADDR, /* IPv6 desc. then IPv4 desc. */ MRUSORT_COUNT, /* hit count ascending */ MRUSORT_R_COUNT, /* hit count descending */ MRUSORT_MAX, /* special: count of this enum */ } mru_sort_order; const char * const mru_sort_keywords[MRUSORT_MAX] = { "lstint", /* MRUSORT_DEF */ "-lstint", /* MRUSORT_R_DEF */ "avgint", /* MRUSORT_AVGINT */ "-avgint", /* MRUSORT_R_AVGINT */ "addr", /* MRUSORT_ADDR */ "-addr", /* MRUSORT_R_ADDR */ "count", /* MRUSORT_COUNT */ "-count", /* MRUSORT_R_COUNT */ }; typedef int (*qsort_cmp)(const void *, const void *); /* * Old CTL_PST defines for version 2. */ #define OLD_CTL_PST_CONFIG 0x80 #define OLD_CTL_PST_AUTHENABLE 0x40 #define OLD_CTL_PST_AUTHENTIC 0x20 #define OLD_CTL_PST_REACH 0x10 #define OLD_CTL_PST_SANE 0x08 #define OLD_CTL_PST_DISP 0x04 #define OLD_CTL_PST_SEL_REJECT 0 #define OLD_CTL_PST_SEL_SELCAND 1 #define OLD_CTL_PST_SEL_SYNCCAND 2 #define OLD_CTL_PST_SEL_SYSPEER 3 char flash2[] = " .+* "; /* flash decode for version 2 */ char flash3[] = " x.-+#*o"; /* flash decode for peer status version 3 */ struct varlist { const char *name; char *value; } g_varlist[MAXLIST] = { { 0, 0 } }; /* * Imported from ntpq.c */ extern int showhostnames; extern int wideremote; extern int rawmode; extern struct servent *server_entry; extern struct association *assoc_cache; extern u_char pktversion; typedef struct mru_tag mru; struct mru_tag { mru * hlink; /* next in hash table bucket */ DECL_DLIST_LINK(mru, mlink); int count; l_fp last; l_fp first; u_char mode; u_char ver; u_short rs; sockaddr_u addr; }; typedef struct ifstats_row_tag { u_int ifnum; sockaddr_u addr; sockaddr_u bcast; int enabled; u_int flags; u_int mcast_count; char name[32]; u_int peer_count; u_int received; u_int sent; u_int send_errors; u_int ttl; u_int uptime; } ifstats_row; typedef struct reslist_row_tag { u_int idx; sockaddr_u addr; sockaddr_u mask; u_long hits; char flagstr[128]; } reslist_row; typedef struct var_display_collection_tag { const char * const tag; /* system variable */ const char * const display; /* descriptive text */ u_char type; /* NTP_STR, etc */ union { char * str; sockaddr_u sau; /* NTP_ADD */ l_fp lfp; /* NTP_LFP */ } v; /* retrieved value */ } vdc; #if !defined(MISSING_C99_STRUCT_INIT) # define VDC_INIT(a, b, c) { .tag = a, .display = b, .type = c } #else # define VDC_INIT(a, b, c) { a, b, c } #endif /* * other local function prototypes */ static int mrulist_ctrl_c_hook(void); static mru * add_mru(mru *); static int collect_mru_list(const char *, l_fp *); static int fetch_nonce(char *, size_t); static int qcmp_mru_avgint(const void *, const void *); static int qcmp_mru_r_avgint(const void *, const void *); static int qcmp_mru_addr(const void *, const void *); static int qcmp_mru_r_addr(const void *, const void *); static int qcmp_mru_count(const void *, const void *); static int qcmp_mru_r_count(const void *, const void *); static void validate_ifnum(FILE *, u_int, int *, ifstats_row *); static void another_ifstats_field(int *, ifstats_row *, FILE *); static void collect_display_vdc(associd_t as, vdc *table, int decodestatus, FILE *fp); static int xprintf(FILE *, char const *, ...) NTP_PRINTF(2, 3); static int xputs(char const *, FILE *); static int xputc(int, FILE *); /* * static globals */ static u_int mru_count; static u_int mru_dupes; volatile int mrulist_interrupted; static mru mru_list; /* listhead */ static mru ** hash_table; /* * qsort comparison function table for mrulist(). The first two * entries are NULL because they are handled without qsort(). */ static const qsort_cmp mru_qcmp_table[MRUSORT_MAX] = { NULL, /* MRUSORT_DEF unused */ NULL, /* MRUSORT_R_DEF unused */ &qcmp_mru_avgint, /* MRUSORT_AVGINT */ &qcmp_mru_r_avgint, /* MRUSORT_R_AVGINT */ &qcmp_mru_addr, /* MRUSORT_ADDR */ &qcmp_mru_r_addr, /* MRUSORT_R_ADDR */ &qcmp_mru_count, /* MRUSORT_COUNT */ &qcmp_mru_r_count, /* MRUSORT_R_COUNT */ }; /* * NULL-pointer safe FILE I/O: use stderr if no file supplied. */ static int xprintf( FILE * ofp, char const * fmt, ... ) { va_list va; int rc; va_start(va, fmt); rc = vfprintf((ofp ? ofp : stderr), fmt, va); va_end(va); return rc; } static int xputs( char const * str, FILE * ofp ) { return fputs(str, (ofp ? ofp : stderr)); } static int xputc( int ch, FILE * ofp ) { return fputc(ch, (ofp ? ofp : stderr)); } /* * checkassocid - return the association ID, checking to see if it is valid */ static associd_t checkassocid( u_int32 value ) { associd_t associd; u_long ulvalue; associd = (associd_t)value; if (0 == associd || value != associd) { ulvalue = value; xprintf(stderr, "***Invalid association ID %lu specified\n", ulvalue); return 0; } return associd; } /* * findlistvar - Look for the named variable in a varlist. If found, * return a pointer to it. Otherwise, if the list has * slots available, return the pointer to the first free * slot, or NULL if it's full. */ static struct varlist * findlistvar( struct varlist *list, char *name ) { struct varlist *vl; for (vl = list; vl < list + MAXLIST && vl->name != NULL; vl++) if (!strcmp(name, vl->name)) return vl; if (vl < list + MAXLIST) return vl; return NULL; } /* * doaddvlist - add variable(s) to the variable list */ static void doaddvlist( struct varlist *vlist, const char *vars ) { struct varlist *vl; size_t len; char *name; char *value; len = strlen(vars); while (nextvar(&len, &vars, &name, &value)) { INSIST(name && value); vl = findlistvar(vlist, name); if (NULL == vl) { xprintf(stderr, "Variable list full\n"); return; } if (NULL == vl->name) { vl->name = estrdup(name); } else if (vl->value != NULL) { free(vl->value); vl->value = NULL; } if (value != NULL) vl->value = estrdup(value); } } /* * dormvlist - remove variable(s) from the variable list */ static void dormvlist( struct varlist *vlist, const char *vars ) { struct varlist *vl; size_t len; char *name; char *value; len = strlen(vars); while (nextvar(&len, &vars, &name, &value)) { INSIST(name && value); vl = findlistvar(vlist, name); if (vl == 0 || vl->name == 0) { (void) xprintf(stderr, "Variable `%s' not found\n", name); } else { free((void *)(intptr_t)vl->name); if (vl->value != 0) free(vl->value); for ( ; (vl+1) < (g_varlist + MAXLIST) && (vl+1)->name != 0; vl++) { vl->name = (vl+1)->name; vl->value = (vl+1)->value; } vl->name = vl->value = 0; } } } /* * doclearvlist - clear a variable list */ static void doclearvlist( struct varlist *vlist ) { register struct varlist *vl; for (vl = vlist; vl < vlist + MAXLIST && vl->name != 0; vl++) { free((void *)(intptr_t)vl->name); vl->name = 0; if (vl->value != 0) { free(vl->value); vl->value = 0; } } } /* * makequerydata - form a data buffer to be included with a query */ static void makequerydata( struct varlist *vlist, size_t *datalen, char *data ) { register struct varlist *vl; register char *cp, *cpend; register size_t namelen, valuelen; register size_t totallen; cp = data; cpend = data + *datalen; for (vl = vlist; vl < vlist + MAXLIST && vl->name != 0; vl++) { namelen = strlen(vl->name); if (vl->value == 0) valuelen = 0; else valuelen = strlen(vl->value); totallen = namelen + valuelen + (valuelen != 0) + (cp != data); if (cp + totallen > cpend) { xprintf(stderr, "***Ignoring variables starting with `%s'\n", vl->name); break; } if (cp != data) *cp++ = ','; memcpy(cp, vl->name, (size_t)namelen); cp += namelen; if (valuelen != 0) { *cp++ = '='; memcpy(cp, vl->value, (size_t)valuelen); cp += valuelen; } } *datalen = (size_t)(cp - data); } /* * doquerylist - send a message including variables in a list */ static int doquerylist( struct varlist *vlist, int op, associd_t associd, int auth, u_short *rstatus, size_t *dsize, const char **datap ) { char data[CTL_MAX_DATA_LEN]; size_t datalen; datalen = sizeof(data); makequerydata(vlist, &datalen, data); return doquery(op, associd, auth, datalen, data, rstatus, dsize, datap); } /* * doprintvlist - print the variables on a list */ static void doprintvlist( struct varlist *vlist, FILE *fp ) { size_t n; if (NULL == vlist->name) { xprintf(fp, "No variables on list\n"); return; } for (n = 0; n < MAXLIST && vlist[n].name != NULL; n++) { if (NULL == vlist[n].value) xprintf(fp, "%s\n", vlist[n].name); else xprintf(fp, "%s=%s\n", vlist[n].name, vlist[n].value); } } /* * addvars - add variables to the variable list */ /*ARGSUSED*/ static void addvars( struct parse *pcmd, FILE *fp ) { doaddvlist(g_varlist, pcmd->argval[0].string); } /* * rmvars - remove variables from the variable list */ /*ARGSUSED*/ static void rmvars( struct parse *pcmd, FILE *fp ) { dormvlist(g_varlist, pcmd->argval[0].string); } /* * clearvars - clear the variable list */ /*ARGSUSED*/ static void clearvars( struct parse *pcmd, FILE *fp ) { doclearvlist(g_varlist); } /* * showvars - show variables on the variable list */ /*ARGSUSED*/ static void showvars( struct parse *pcmd, FILE *fp ) { doprintvlist(g_varlist, fp); } /* * dolist - send a request with the given list of variables */ static int dolist( struct varlist *vlist, associd_t associd, int op, int type, FILE *fp ) { const char *datap; int res; size_t dsize; u_short rstatus; int quiet; /* * if we're asking for specific variables don't include the * status header line in the output. */ if (old_rv) quiet = 0; else quiet = (vlist->name != NULL); res = doquerylist(vlist, op, associd, 0, &rstatus, &dsize, &datap); if (res != 0) return 0; if (numhosts > 1) xprintf(fp, "server=%s ", currenthost); if (dsize == 0) { if (associd == 0) xprintf(fp, "No system%s variables returned\n", (type == TYPE_CLOCK) ? " clock" : ""); else xprintf(fp, "No information returned for%s association %u\n", (type == TYPE_CLOCK) ? " clock" : "", associd); return 1; } if (!quiet) xprintf(fp, "associd=%u ", associd); printvars(dsize, datap, (int)rstatus, type, quiet, fp); return 1; } /* * readlist - send a read variables request with the variables on the list */ static void readlist( struct parse *pcmd, FILE *fp ) { associd_t associd; int type; if (pcmd->nargs == 0) { associd = 0; } else { /* HMS: I think we want the u_int32 target here, not the u_long */ if (pcmd->argval[0].uval == 0) associd = 0; else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0) return; } type = (0 == associd) ? TYPE_SYS : TYPE_PEER; dolist(g_varlist, associd, CTL_OP_READVAR, type, fp); } /* * writelist - send a write variables request with the variables on the list */ static void writelist( struct parse *pcmd, FILE *fp ) { const char *datap; int res; associd_t associd; size_t dsize; u_short rstatus; if (pcmd->nargs == 0) { associd = 0; } else { /* HMS: Do we really want uval here? */ if (pcmd->argval[0].uval == 0) associd = 0; else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0) return; } res = doquerylist(g_varlist, CTL_OP_WRITEVAR, associd, 1, &rstatus, &dsize, &datap); if (res != 0) return; if (numhosts > 1) (void) xprintf(fp, "server=%s ", currenthost); if (dsize == 0) (void) xprintf(fp, "done! (no data returned)\n"); else { (void) xprintf(fp,"associd=%u ", associd); printvars(dsize, datap, (int)rstatus, (associd != 0) ? TYPE_PEER : TYPE_SYS, 0, fp); } return; } /* * readvar - send a read variables request with the specified variables */ static void readvar( struct parse *pcmd, FILE *fp ) { associd_t associd; size_t tmpcount; size_t u; int type; struct varlist tmplist[MAXLIST]; /* HMS: uval? */ if (pcmd->nargs == 0 || pcmd->argval[0].uval == 0) associd = 0; else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0) return; ZERO(tmplist); if (pcmd->nargs > 1) { tmpcount = pcmd->nargs - 1; for (u = 0; u < tmpcount; u++) doaddvlist(tmplist, pcmd->argval[1 + u].string); } type = (0 == associd) ? TYPE_SYS : TYPE_PEER; dolist(tmplist, associd, CTL_OP_READVAR, type, fp); doclearvlist(tmplist); } /* * writevar - send a write variables request with the specified variables */ static void writevar( struct parse *pcmd, FILE *fp ) { const char *datap; int res; associd_t associd; int type; size_t dsize; u_short rstatus; struct varlist tmplist[MAXLIST]; /* HMS: uval? */ if (pcmd->argval[0].uval == 0) associd = 0; else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0) return; ZERO(tmplist); doaddvlist(tmplist, pcmd->argval[1].string); res = doquerylist(tmplist, CTL_OP_WRITEVAR, associd, 1, &rstatus, &dsize, &datap); doclearvlist(tmplist); if (res != 0) return; if (numhosts > 1) xprintf(fp, "server=%s ", currenthost); if (dsize == 0) xprintf(fp, "done! (no data returned)\n"); else { xprintf(fp,"associd=%u ", associd); type = (0 == associd) ? TYPE_SYS : TYPE_PEER; printvars(dsize, datap, (int)rstatus, type, 0, fp); } return; } /* * clocklist - send a clock variables request with the variables on the list */ static void clocklist( struct parse *pcmd, FILE *fp ) { associd_t associd; /* HMS: uval? */ if (pcmd->nargs == 0) { associd = 0; } else { if (pcmd->argval[0].uval == 0) associd = 0; else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0) return; } dolist(g_varlist, associd, CTL_OP_READCLOCK, TYPE_CLOCK, fp); } /* * clockvar - send a clock variables request with the specified variables */ static void clockvar( struct parse *pcmd, FILE *fp ) { associd_t associd; struct varlist tmplist[MAXLIST]; /* HMS: uval? */ if (pcmd->nargs == 0 || pcmd->argval[0].uval == 0) associd = 0; else if ((associd = checkassocid(pcmd->argval[0].uval)) == 0) return; ZERO(tmplist); if (pcmd->nargs >= 2) doaddvlist(tmplist, pcmd->argval[1].string); dolist(tmplist, associd, CTL_OP_READCLOCK, TYPE_CLOCK, fp); doclearvlist(tmplist); } /* * findassidrange - verify a range of association ID's */ static int findassidrange( u_int32 assid1, u_int32 assid2, int * from, int * to, FILE * fp ) { associd_t assids[2]; int ind[COUNTOF(assids)]; u_int i; size_t a; if (0 == numassoc) dogetassoc(fp); assids[0] = checkassocid(assid1); if (0 == assids[0]) return 0; assids[1] = checkassocid(assid2); if (0 == assids[1]) return 0; for (a = 0; a < COUNTOF(assids); a++) { ind[a] = -1; for (i = 0; i < numassoc; i++) if (assoc_cache[i].assid == assids[a]) ind[a] = i; } for (a = 0; a < COUNTOF(assids); a++) if (-1 == ind[a]) { xprintf(stderr, "***Association ID %u not found in list\n", assids[a]); return 0; } if (ind[0] < ind[1]) { *from = ind[0]; *to = ind[1]; } else { *to = ind[0]; *from = ind[1]; } return 1; } /* * mreadlist - send a read variables request for multiple associations */ static void mreadlist( struct parse *pcmd, FILE *fp ) { int i; int from; int to; if (!findassidrange(pcmd->argval[0].uval, pcmd->argval[1].uval, &from, &to, fp)) return; for (i = from; i <= to; i++) { if (i != from) xprintf(fp, "\n"); if (!dolist(g_varlist, assoc_cache[i].assid, CTL_OP_READVAR, TYPE_PEER, fp)) return; } return; } /* * mreadvar - send a read variables request for multiple associations */ static void mreadvar( struct parse *pcmd, FILE *fp ) { int i; int from; int to; struct varlist tmplist[MAXLIST]; struct varlist *pvars; if (!findassidrange(pcmd->argval[0].uval, pcmd->argval[1].uval, &from, &to, fp)) return; ZERO(tmplist); if (pcmd->nargs >= 3) { doaddvlist(tmplist, pcmd->argval[2].string); pvars = tmplist; } else { pvars = g_varlist; } for (i = from; i <= to; i++) { if (!dolist(pvars, assoc_cache[i].assid, CTL_OP_READVAR, TYPE_PEER, fp)) break; } if (pvars == tmplist) doclearvlist(tmplist); return; } /* * dogetassoc - query the host for its list of associations */ int dogetassoc( FILE *fp ) { const char *datap; const u_short *pus; int res; size_t dsize; u_short rstatus; res = doquery(CTL_OP_READSTAT, 0, 0, 0, (char *)0, &rstatus, &dsize, &datap); if (res != 0) return 0; if (dsize == 0) { if (numhosts > 1) xprintf(fp, "server=%s ", currenthost); xprintf(fp, "No association ID's returned\n"); return 0; } if (dsize & 0x3) { if (numhosts > 1) xprintf(stderr, "server=%s ", currenthost); xprintf(stderr, "***Server returned %zu octets, should be multiple of 4\n", dsize); return 0; } numassoc = 0; while (dsize > 0) { if (numassoc >= assoc_cache_slots) { grow_assoc_cache(); } pus = (const void *)datap; assoc_cache[numassoc].assid = ntohs(*pus); datap += sizeof(*pus); pus = (const void *)datap; assoc_cache[numassoc].status = ntohs(*pus); datap += sizeof(*pus); dsize -= 2 * sizeof(*pus); if (debug) { xprintf(stderr, "[%u] ", assoc_cache[numassoc].assid); } numassoc++; } if (debug) { xprintf(stderr, "\n%d associations total\n", numassoc); } sortassoc(); return 1; } /* * printassoc - print the current list of associations */ static void printassoc( int showall, FILE *fp ) { register char *bp; u_int i; u_char statval; int event; u_long event_count; const char *conf; const char *reach; const char *auth; const char *condition = ""; const char *last_event; char buf[128]; char numev[32]; if (numassoc == 0) { (void) xprintf(fp, "No association ID's in list\n"); return; } /* * Output a header */ (void) xprintf(fp, "ind assid status conf reach auth condition last_event cnt\n"); (void) xprintf(fp, "===========================================================\n"); for (i = 0; i < numassoc; i++) { statval = (u_char) CTL_PEER_STATVAL(assoc_cache[i].status); if (!showall && !(statval & (CTL_PST_CONFIG|CTL_PST_REACH))) continue; event = CTL_PEER_EVENT(assoc_cache[i].status); event_count = CTL_PEER_NEVNT(assoc_cache[i].status); if (statval & CTL_PST_CONFIG) conf = "yes"; else conf = "no"; if (statval & CTL_PST_BCAST) { reach = "none"; if (statval & CTL_PST_AUTHENABLE) auth = "yes"; else auth = "none"; } else { if (statval & CTL_PST_REACH) reach = "yes"; else reach = "no"; if (statval & CTL_PST_AUTHENABLE) { if (statval & CTL_PST_AUTHENTIC) auth = "ok "; else auth = "bad"; } else { auth = "none"; } } if (pktversion > NTP_OLDVERSION) { switch (statval & 0x7) { case CTL_PST_SEL_REJECT: condition = "reject"; break; case CTL_PST_SEL_SANE: condition = "falsetick"; break; case CTL_PST_SEL_CORRECT: condition = "excess"; break; case CTL_PST_SEL_SELCAND: condition = "outlier"; break; case CTL_PST_SEL_SYNCCAND: condition = "candidate"; break; case CTL_PST_SEL_EXCESS: condition = "backup"; break; case CTL_PST_SEL_SYSPEER: condition = "sys.peer"; break; case CTL_PST_SEL_PPS: condition = "pps.peer"; break; } } else { switch (statval & 0x3) { case OLD_CTL_PST_SEL_REJECT: if (!(statval & OLD_CTL_PST_SANE)) condition = "insane"; else if (!(statval & OLD_CTL_PST_DISP)) condition = "hi_disp"; else condition = ""; break; case OLD_CTL_PST_SEL_SELCAND: condition = "sel_cand"; break; case OLD_CTL_PST_SEL_SYNCCAND: condition = "sync_cand"; break; case OLD_CTL_PST_SEL_SYSPEER: condition = "sys_peer"; break; } } switch (PEER_EVENT|event) { case PEVNT_MOBIL: last_event = "mobilize"; break; case PEVNT_DEMOBIL: last_event = "demobilize"; break; case PEVNT_REACH: last_event = "reachable"; break; case PEVNT_UNREACH: last_event = "unreachable"; break; case PEVNT_RESTART: last_event = "restart"; break; case PEVNT_REPLY: last_event = "no_reply"; break; case PEVNT_RATE: last_event = "rate_exceeded"; break; case PEVNT_DENY: last_event = "access_denied"; break; case PEVNT_ARMED: last_event = "leap_armed"; break; case PEVNT_NEWPEER: last_event = "sys_peer"; break; case PEVNT_CLOCK: last_event = "clock_alarm"; break; case PEVNT_AUTH: last_event = "bad_auth"; break; case PEVNT_POPCORN: last_event = "popcorn"; break; case PEVNT_XLEAVE: last_event = "interleave"; break; case PEVNT_XERR: last_event = "xleave_err"; break; default: snprintf(numev, sizeof(numev), "", event); last_event = numev; break; } snprintf(buf, sizeof(buf), "%3d %5u %04x %3.3s %4s %4.4s %9.9s %11s %2lu", i + 1, assoc_cache[i].assid, assoc_cache[i].status, conf, reach, auth, condition, last_event, event_count); bp = buf + strlen(buf); while (bp > buf && ' ' == bp[-1]) --bp; bp[0] = '\0'; xprintf(fp, "%s\n", buf); } } /* * associations - get, record and print a list of associations */ /*ARGSUSED*/ static void associations( struct parse *pcmd, FILE *fp ) { if (dogetassoc(fp)) printassoc(0, fp); } /* * lassociations - get, record and print a long list of associations */ /*ARGSUSED*/ static void lassociations( struct parse *pcmd, FILE *fp ) { if (dogetassoc(fp)) printassoc(1, fp); } /* * passociations - print the association list */ /*ARGSUSED*/ static void passociations( struct parse *pcmd, FILE *fp ) { printassoc(0, fp); } /* * lpassociations - print the long association list */ /*ARGSUSED*/ static void lpassociations( struct parse *pcmd, FILE *fp ) { printassoc(1, fp); } /* * saveconfig - dump ntp server configuration to server file */ static void saveconfig( struct parse *pcmd, FILE *fp ) { const char *datap; int res; size_t dsize; u_short rstatus; if (0 == pcmd->nargs) return; res = doquery(CTL_OP_SAVECONFIG, 0, 1, strlen(pcmd->argval[0].string), pcmd->argval[0].string, &rstatus, &dsize, &datap); if (res != 0) return; if (0 == dsize) xprintf(fp, "(no response message, curiously)"); else xprintf(fp, "%.*s", (int)dsize, datap); /* cast is wobbly */ } #ifdef UNUSED /* * radiostatus - print the radio status returned by the server */ /*ARGSUSED*/ static void radiostatus( struct parse *pcmd, FILE *fp ) { char *datap; int res; int dsize; u_short rstatus; res = doquery(CTL_OP_READCLOCK, 0, 0, 0, (char *)0, &rstatus, &dsize, &datap); if (res != 0) return; if (numhosts > 1) (void) xprintf(fp, "server=%s ", currenthost); if (dsize == 0) { (void) xprintf(fp, "No radio status string returned\n"); return; } asciize(dsize, datap, fp); } #endif /* UNUSED */ /* * when - return how long its been since his last packet arrived */ static long when( l_fp *ts, l_fp *rec, l_fp *reftime ) { l_fp *lasttime; if (rec->l_ui != 0) lasttime = rec; else if (reftime->l_ui != 0) lasttime = reftime; else return 0; if (ts->l_ui < lasttime->l_ui) return -1; return (ts->l_ui - lasttime->l_ui); } /* * Pretty-print an interval into the given buffer, in a human-friendly format. */ static char * prettyinterval( char *buf, size_t cb, long diff ) { if (diff <= 0) { buf[0] = '-'; buf[1] = 0; return buf; } if (diff <= 2048) { snprintf(buf, cb, "%u", (unsigned int)diff); return buf; } diff = (diff + 29) / 60; if (diff <= 300) { snprintf(buf, cb, "%um", (unsigned int)diff); return buf; } diff = (diff + 29) / 60; if (diff <= 96) { snprintf(buf, cb, "%uh", (unsigned int)diff); return buf; } diff = (diff + 11) / 24; if (diff <= 999) { snprintf(buf, cb, "%ud", (unsigned int)diff); return buf; } /* years are only approximated... */ diff = (long)floor(diff / 365.25 + 0.5); if (diff <= 999) { snprintf(buf, cb, "%uy", (unsigned int)diff); return buf; } /* Ok, this amounts to infinity... */ strlcpy(buf, "INF", cb); return buf; } static char decodeaddrtype( sockaddr_u *sock ) { char ch = '-'; u_int32 dummy; switch(AF(sock)) { case AF_INET: dummy = SRCADR(sock); ch = (char)(((dummy&0xf0000000)==0xe0000000) ? 'm' : ((dummy&0x000000ff)==0x000000ff) ? 'b' : ((dummy&0xffffffff)==0x7f000001) ? 'l' : ((dummy&0xffffffe0)==0x00000000) ? '-' : 'u'); break; case AF_INET6: if (IN6_IS_ADDR_MULTICAST(PSOCK_ADDR6(sock))) ch = 'm'; else ch = 'u'; break; default: ch = '-'; break; } return ch; } /* * A list of variables required by the peers command */ struct varlist opeervarlist[] = { { "srcadr", 0 }, /* 0 */ { "dstadr", 0 }, /* 1 */ { "stratum", 0 }, /* 2 */ { "hpoll", 0 }, /* 3 */ { "ppoll", 0 }, /* 4 */ { "reach", 0 }, /* 5 */ { "delay", 0 }, /* 6 */ { "offset", 0 }, /* 7 */ { "jitter", 0 }, /* 8 */ { "dispersion", 0 }, /* 9 */ { "rec", 0 }, /* 10 */ { "reftime", 0 }, /* 11 */ { "srcport", 0 }, /* 12 */ { "hmode", 0 }, /* 13 */ { 0, 0 } }; struct varlist peervarlist[] = { { "srcadr", 0 }, /* 0 */ { "refid", 0 }, /* 1 */ { "stratum", 0 }, /* 2 */ { "hpoll", 0 }, /* 3 */ { "ppoll", 0 }, /* 4 */ { "reach", 0 }, /* 5 */ { "delay", 0 }, /* 6 */ { "offset", 0 }, /* 7 */ { "jitter", 0 }, /* 8 */ { "dispersion", 0 }, /* 9 */ { "rec", 0 }, /* 10 */ { "reftime", 0 }, /* 11 */ { "srcport", 0 }, /* 12 */ { "hmode", 0 }, /* 13 */ { "srchost", 0 }, /* 14 */ { 0, 0 } }; struct varlist apeervarlist[] = { { "srcadr", 0 }, /* 0 */ { "refid", 0 }, /* 1 */ { "assid", 0 }, /* 2 */ { "stratum", 0 }, /* 3 */ { "hpoll", 0 }, /* 4 */ { "ppoll", 0 }, /* 5 */ { "reach", 0 }, /* 6 */ { "delay", 0 }, /* 7 */ { "offset", 0 }, /* 8 */ { "jitter", 0 }, /* 9 */ { "dispersion", 0 }, /* 10 */ { "rec", 0 }, /* 11 */ { "reftime", 0 }, /* 12 */ { "srcport", 0 }, /* 13 */ { "hmode", 0 }, /* 14 */ { "srchost", 0 }, /* 15 */ { 0, 0 } }; /* * Decode an incoming data buffer and print a line in the peer list */ static int doprintpeers( struct varlist *pvl, int associd, int rstatus, size_t datalen, const char *data, FILE *fp, int af ) { char *name; char *value = NULL; int c; size_t len; int have_srchost; int have_dstadr; int have_da_rid; int have_jitter; sockaddr_u srcadr; sockaddr_u dstadr; sockaddr_u dum_store; sockaddr_u refidadr; long hmode = 0; u_long srcport = 0; u_int32 u32; const char *dstadr_refid = "0.0.0.0"; const char *serverlocal; char *drbuf = NULL; size_t drlen; u_long stratum = 0; long ppoll = 0; long hpoll = 0; u_long reach = 0; l_fp estoffset; l_fp estdelay; l_fp estjitter; l_fp estdisp; l_fp reftime; l_fp rec; l_fp ts; u_long poll_sec; u_long flash = 0; char type = '?'; char clock_name[LENHOSTNAME]; char whenbuf[12], pollbuf[12]; /* [Bug 3482] formally whenbuf & pollbuf should be able to hold * a full signed int. Not that we would use that much string * data for it... */ get_systime(&ts); have_srchost = FALSE; have_dstadr = FALSE; have_da_rid = FALSE; have_jitter = FALSE; ZERO_SOCK(&srcadr); ZERO_SOCK(&dstadr); clock_name[0] = '\0'; ZERO(estoffset); ZERO(estdelay); ZERO(estjitter); ZERO(estdisp); while (nextvar(&datalen, &data, &name, &value)) { INSIST(name && value); if (!strcmp("srcadr", name) || !strcmp("peeradr", name)) { if (!decodenetnum(value, &srcadr)) xprintf(stderr, "malformed %s=%s\n", name, value); } else if (!strcmp("srchost", name)) { if (pvl == peervarlist || pvl == apeervarlist) { len = strlen(value); if (2 < len && (size_t)len < sizeof(clock_name)) { /* strip quotes */ value++; len -= 2; memcpy(clock_name, value, len); clock_name[len] = '\0'; have_srchost = TRUE; } } } else if (!strcmp("dstadr", name)) { if (decodenetnum(value, &dum_store)) { type = decodeaddrtype(&dum_store); have_dstadr = TRUE; dstadr = dum_store; if (pvl == opeervarlist) { have_da_rid = TRUE; dstadr_refid = trunc_left(stoa(&dstadr), 15); } } } else if (!strcmp("hmode", name)) { decodeint(value, &hmode); } else if (!strcmp("refid", name)) { if ( (pvl == peervarlist) && (drefid == REFID_IPV4)) { have_da_rid = TRUE; drlen = strlen(value); if (0 == drlen) { dstadr_refid = ""; } else if (drlen <= 4) { ZERO(u32); memcpy(&u32, value, drlen); dstadr_refid = refid_str(u32, 1); } else if (decodenetnum(value, &refidadr)) { if (SOCK_UNSPEC(&refidadr)) dstadr_refid = "0.0.0.0"; else if (ISREFCLOCKADR(&refidadr)) { dstadr_refid = refnumtoa(&refidadr); } else { dstadr_refid = stoa(&refidadr); } } else { have_da_rid = FALSE; } } else if ( (pvl == apeervarlist) || (pvl == peervarlist)) { /* no need to check drefid == REFID_HASH */ have_da_rid = TRUE; drlen = strlen(value); if (0 == drlen) { dstadr_refid = ""; } else if (drlen <= 4) { ZERO(u32); memcpy(&u32, value, drlen); dstadr_refid = refid_str(u32, 1); //xprintf(stderr, "apeervarlist S1 refid: value=<%s>\n", value); } else if (decodenetnum(value, &refidadr)) { if (SOCK_UNSPEC(&refidadr)) dstadr_refid = "0.0.0.0"; else if (ISREFCLOCKADR(&refidadr)) { dstadr_refid = refnumtoa(&refidadr); if (pvl == apeervarlist) { /* * restrict refid to * 8 chars [Bug 3850] */ dstadr_refid = trunc_right( dstadr_refid, 8); } } else { drbuf = emalloc(10); snprintf(drbuf, 10, "%0x", SRCADR(&refidadr)); dstadr_refid = drbuf; } } else { have_da_rid = FALSE; } } } else if (!strcmp("stratum", name)) { decodeuint(value, &stratum); } else if (!strcmp("hpoll", name)) { if (decodeint(value, &hpoll) && hpoll < 0) hpoll = NTP_MINPOLL; } else if (!strcmp("ppoll", name)) { if (decodeint(value, &ppoll) && ppoll < 0) ppoll = NTP_MINPOLL; } else if (!strcmp("reach", name)) { decodeuint(value, &reach); } else if (!strcmp("delay", name)) { decodetime(value, &estdelay); } else if (!strcmp("offset", name)) { decodetime(value, &estoffset); } else if (!strcmp("jitter", name)) { if ((pvl == peervarlist || pvl == apeervarlist) && decodetime(value, &estjitter)) have_jitter = 1; } else if (!strcmp("rootdisp", name) || !strcmp("dispersion", name)) { decodetime(value, &estdisp); } else if (!strcmp("rec", name)) { decodets(value, &rec); } else if (!strcmp("srcport", name) || !strcmp("peerport", name)) { decodeuint(value, &srcport); } else if (!strcmp("reftime", name)) { if (!decodets(value, &reftime)) L_CLR(&reftime); } else if (!strcmp("flash", name)) { decodeuint(value, &flash); } else { // xprintf(stderr, "UNRECOGNIZED name=%s ", name); } } /* * hmode gives the best guidance for the t column. If the response * did not include hmode we'll use the old decodeaddrtype() result. */ switch (hmode) { case MODE_BCLIENT: /* broadcastclient or multicastclient */ type = 'b'; break; case MODE_BROADCAST: /* broadcast or multicast server */ if (IS_MCAST(&srcadr)) type = 'M'; else type = 'B'; break; case MODE_CLIENT: if (ISREFCLOCKADR(&srcadr)) type = 'l'; /* local refclock*/ else if (SOCK_UNSPEC(&srcadr)) type = 'p'; /* pool */ else if (IS_MCAST(&srcadr)) type = 'a'; /* manycastclient */ else type = 'u'; /* unicast */ break; case MODE_ACTIVE: type = 's'; /* symmetric active */ break; /* configured */ case MODE_PASSIVE: type = 'S'; /* symmetric passive */ break; /* ephemeral */ } /* * Got everything, format the line */ poll_sec = 1 << min(ppoll, hpoll); if (pktversion > NTP_OLDVERSION) c = flash3[CTL_PEER_STATVAL(rstatus) & 0x7]; else c = flash2[CTL_PEER_STATVAL(rstatus) & 0x3]; if (numhosts > 1) { if ((pvl == peervarlist || pvl == apeervarlist) && have_dstadr) { serverlocal = nntohost_col(&dstadr, (size_t)min(LIB_BUFLENGTH - 1, maxhostlen), TRUE); } else { if (currenthostisnum) serverlocal = trunc_left(currenthost, maxhostlen); else serverlocal = currenthost; } xprintf(fp, "%-*s ", (int)maxhostlen, serverlocal); } if (AF_UNSPEC == af || AF(&srcadr) == af) { if (!have_srchost) strlcpy(clock_name, nntohost(&srcadr), sizeof(clock_name)); /* wide and long source - space over on next line */ /* allow for host + sp if > 1 and regular tally + source + sp */ if (wideremote && 15 < strlen(clock_name)) xprintf(fp, "%c%s\n%*s", c, clock_name, ((numhosts > 1) ? (int)maxhostlen + 1 : 0) + 1 + 15 + 1, ""); else xprintf(fp, "%c%-15.15s ", c, clock_name); if ((flash & TEST12) && (pvl != opeervarlist)) { drlen = xprintf(fp, "(loop)"); } else if (!have_da_rid) { drlen = 0; } else { drlen = strlen(dstadr_refid); makeascii(drlen, dstadr_refid, fp); } free(drbuf); if (pvl == apeervarlist) { while (drlen++ < 9) xputc(' ', fp); xprintf(fp, "%-6d", associd); } else { while (drlen++ < 15) xputc(' ', fp); } xprintf(fp, " %2ld %c %4.4s %4.4s %3lo %7.7s %8.7s %7.7s\n", stratum, type, prettyinterval(whenbuf, sizeof(whenbuf), when(&ts, &rec, &reftime)), prettyinterval(pollbuf, sizeof(pollbuf), (int)poll_sec), reach, ulfptoms(&estdelay, 3), lfptoms(&estoffset, 3), (have_jitter) ? ulfptoms(&estjitter, 3) : ulfptoms(&estdisp, 3)); return (1); } else return(1); } /* * dogetpeers - given an association ID, read and print the spreadsheet * peer variables. */ static int dogetpeers( struct varlist *pvl, associd_t associd, FILE *fp, int af ) { const char *datap; int res; size_t dsize; u_short rstatus; #ifdef notdef res = doquerylist(pvl, CTL_OP_READVAR, associd, 0, &rstatus, &dsize, &datap); #else /* * Damn fuzzballs */ res = doquery(CTL_OP_READVAR, associd, 0, 0, NULL, &rstatus, &dsize, &datap); #endif if (res != 0) return 0; if (dsize == 0) { if (numhosts > 1) xprintf(stderr, "server=%s ", currenthost); xprintf(stderr, "***No information returned for association %u\n", associd); return 0; } return doprintpeers(pvl, associd, (int)rstatus, dsize, datap, fp, af); } /* * peers - print a peer spreadsheet */ static void dopeers( int showall, FILE *fp, int af ) { u_int u; char fullname[LENHOSTNAME]; sockaddr_u netnum; const char * name_or_num; size_t sl; if (!dogetassoc(fp)) return; if (numhosts > 1) { for (u = 0; u < numhosts; u++) { if (getnetnum(chosts[u].name, &netnum, fullname, af)) { name_or_num = nntohost(&netnum); sl = strlen(name_or_num); maxhostlen = max(maxhostlen, sl); } } xprintf(fp, "%-*.*s ", (int)maxhostlen, (int)maxhostlen, "server (local)"); } xprintf(fp, " remote refid st t when poll reach delay offset jitter\n"); if (numhosts > 1) for (u = 0; u <= maxhostlen; u++) xprintf(fp, "="); xprintf(fp, "==============================================================================\n"); for (u = 0; u < numassoc; u++) { if (!showall && !(CTL_PEER_STATVAL(assoc_cache[u].status) & (CTL_PST_CONFIG|CTL_PST_REACH))) { if (debug) xprintf(stderr, "eliding [%d]\n", (int)assoc_cache[u].assid); continue; } if (!dogetpeers(peervarlist, (int)assoc_cache[u].assid, fp, af)) return; } return; } /* * doapeers - print a peer spreadsheet with assocIDs */ static void doapeers( int showall, FILE *fp, int af ) { u_int u; char fullname[LENHOSTNAME]; sockaddr_u netnum; const char * name_or_num; size_t sl; if (!dogetassoc(fp)) return; if (numhosts > 1) { for (u = 0; u < numhosts; u++) { if (getnetnum(chosts[u].name, &netnum, fullname, af)) { name_or_num = nntohost(&netnum); sl = strlen(name_or_num); maxhostlen = max(maxhostlen, sl); } } xprintf(fp, "%-*.*s ", (int)maxhostlen, (int)maxhostlen, "server (local)"); } xprintf(fp, " remote refid assid st t when poll reach delay offset jitter\n"); if (numhosts > 1) for (u = 0; u <= maxhostlen; u++) xprintf(fp, "="); xprintf(fp, "==============================================================================\n"); for (u = 0; u < numassoc; u++) { if (!showall && !(CTL_PEER_STATVAL(assoc_cache[u].status) & (CTL_PST_CONFIG|CTL_PST_REACH))) { if (debug) xprintf(stderr, "eliding [%d]\n", (int)assoc_cache[u].assid); continue; } if (!dogetpeers(apeervarlist, (int)assoc_cache[u].assid, fp, af)) return; } return; } /* * peers - print a peer spreadsheet */ /*ARGSUSED*/ static void peers( struct parse *pcmd, FILE *fp ) { if (drefid == REFID_HASH) { apeers(pcmd, fp); } else { int af = 0; if (pcmd->nargs == 1) { if (pcmd->argval->ival == 6) af = AF_INET6; else af = AF_INET; } dopeers(0, fp, af); } } /* * apeers - print a peer spreadsheet, with assocIDs */ /*ARGSUSED*/ static void apeers( struct parse *pcmd, FILE *fp ) { int af = 0; if (pcmd->nargs == 1) { if (pcmd->argval->ival == 6) af = AF_INET6; else af = AF_INET; } doapeers(0, fp, af); } /* * lpeers - print a peer spreadsheet including all fuzzball peers */ /*ARGSUSED*/ static void lpeers( struct parse *pcmd, FILE *fp ) { int af = 0; if (pcmd->nargs == 1) { if (pcmd->argval->ival == 6) af = AF_INET6; else af = AF_INET; } dopeers(1, fp, af); } /* * opeers - print a peer spreadsheet */ static void doopeers( int showall, FILE *fp, int af ) { u_int i; char fullname[LENHOSTNAME]; sockaddr_u netnum; if (!dogetassoc(fp)) return; if (numhosts > 1) { for (i = 0; i < numhosts; ++i) { if (getnetnum(chosts[i].name, &netnum, fullname, af)) { maxhostlen = max(maxhostlen, strlen(fullname)); } xprintf(fp, "%-*.*s ", (int)maxhostlen, (int)maxhostlen, "server"); } } xprintf(fp, " remote local st t when poll reach delay offset disp\n"); if (numhosts > 1) for (i = 0; i <= maxhostlen; ++i) xprintf(fp, "="); xprintf(fp, "==============================================================================\n"); for (i = 0; i < numassoc; i++) { if (!showall && !(CTL_PEER_STATVAL(assoc_cache[i].status) & (CTL_PST_CONFIG | CTL_PST_REACH))) continue; if (!dogetpeers(opeervarlist, assoc_cache[i].assid, fp, af)) return; } return; } /* * opeers - print a peer spreadsheet the old way */ /*ARGSUSED*/ static void opeers( struct parse *pcmd, FILE *fp ) { int af = 0; if (pcmd->nargs == 1) { if (pcmd->argval->ival == 6) af = AF_INET6; else af = AF_INET; } doopeers(0, fp, af); } /* * lopeers - print a peer spreadsheet including all fuzzball peers */ /*ARGSUSED*/ static void lopeers( struct parse *pcmd, FILE *fp ) { int af = 0; if (pcmd->nargs == 1) { if (pcmd->argval->ival == 6) af = AF_INET6; else af = AF_INET; } doopeers(1, fp, af); } /* * config - send a configuration command to a remote host */ static void config ( struct parse *pcmd, FILE *fp ) { const char *cfgcmd; u_short rstatus; size_t rsize; const char *rdata; char *resp; int res; int col; int i; cfgcmd = pcmd->argval[0].string; if (debug > 2) xprintf(stderr, "In Config\n" "Keyword = %s\n" "Command = %s\n", pcmd->keyword, cfgcmd); res = doquery(CTL_OP_CONFIGURE, 0, 1, strlen(cfgcmd), cfgcmd, &rstatus, &rsize, &rdata); if (res != 0) return; if (rsize > 0 && '\n' == rdata[rsize - 1]) rsize--; resp = emalloc(rsize + 1); memcpy(resp, rdata, rsize); resp[rsize] = '\0'; col = -1; if (1 == sscanf(resp, "column %d syntax error", &col) && col >= 0 && (size_t)col <= strlen(cfgcmd) + 1) { if (interactive) xputs(" *", stdout); /* "ntpq> :config " */ else printf("%s\n", cfgcmd); for (i = 0; i < col; i++) xputc('_', stdout); xputs("^\n", stdout); } printf("%s\n", resp); free(resp); } /* * config_from_file - remotely configure an ntpd daemon using the * specified configuration file * SK: This function is a kludge at best and is full of bad design * bugs: * 1. ntpq uses UDP, which means that there is no guarantee of in-order, * error-free delivery. * 2. The maximum length of a packet is constrained, and as a result, the * maximum length of a line in a configuration file is constrained. * Longer lines will lead to unpredictable results. * 3. Since this function is sending a line at a time, we can't update * the control key through the configuration file (YUCK!!) * * Pearly: There are a few places where 'size_t' is cast to 'int' based * on the assumption that 'int' can hold the size of the involved * buffers without overflow. */ static void config_from_file ( struct parse *pcmd, FILE *fp ) { u_short rstatus; size_t rsize; const char *rdata; char * cp; int res; FILE *config_fd; char config_cmd[MAXLINE]; size_t config_len; int i; int retry_limit; if (debug > 2) xprintf(stderr, "In Config\n" "Keyword = %s\n" "Filename = %s\n", pcmd->keyword, pcmd->argval[0].string); config_fd = fopen(pcmd->argval[0].string, "r"); if (NULL == config_fd) { printf("ERROR!! Couldn't open file: %s\n", pcmd->argval[0].string); return; } printf("Sending configuration file, one line at a time.\n"); i = 0; while (fgets(config_cmd, MAXLINE, config_fd) != NULL) { /* Eliminate comments first. */ cp = strchr(config_cmd, '#'); config_len = (NULL != cp) ? (size_t)(cp - config_cmd) : strlen(config_cmd); /* [Bug 3015] make sure there's no trailing whitespace; * the fix for [Bug 2853] on the server side forbids * those. And don't transmit empty lines, as this would * just be waste. */ while (config_len != 0 && (u_char)config_cmd[config_len-1] <= ' ') --config_len; config_cmd[config_len] = '\0'; ++i; if (0 == config_len) continue; retry_limit = 2; do res = doquery(CTL_OP_CONFIGURE, 0, 1, config_len, config_cmd, &rstatus, &rsize, &rdata); while (res != 0 && retry_limit--); if (res != 0) { printf("Line No: %d query failed: %.*s\n" "Subsequent lines not sent.\n", i, (int)config_len, config_cmd); fclose(config_fd); return; } /* Right-strip the result code string, then output the * last line executed, with result code. */ while (rsize != 0 && (u_char)rdata[rsize - 1] <= ' ') --rsize; printf("Line No: %d %.*s: %.*s\n", i, (int)rsize, rdata, (int)config_len, config_cmd); } printf("Done sending file\n"); fclose(config_fd); } static int fetch_nonce( char * nonce, size_t cb_nonce ) { const char nonce_eq[] = "nonce="; int qres; u_short rstatus; size_t rsize; const char * rdata; size_t chars; /* * Retrieve a nonce specific to this client to demonstrate to * ntpd that we're capable of receiving responses to our source * IP address, and thereby unlikely to be forging the source. */ qres = doquery(CTL_OP_REQ_NONCE, 0, 0, 0, NULL, &rstatus, &rsize, &rdata); if (qres) { xprintf(stderr, "nonce request failed\n"); return FALSE; } if (rsize <= sizeof(nonce_eq) - 1 || strncmp(rdata, nonce_eq, sizeof(nonce_eq) - 1)) { xprintf(stderr, "unexpected nonce response format: %.*s\n", (int)rsize, rdata); /* cast is wobbly */ return FALSE; } chars = rsize - (sizeof(nonce_eq) - 1); if (chars >= cb_nonce) return FALSE; memcpy(nonce, rdata + sizeof(nonce_eq) - 1, chars); nonce[chars] = '\0'; while (chars > 0 && ('\r' == nonce[chars - 1] || '\n' == nonce[chars - 1])) { chars--; nonce[chars] = '\0'; } return TRUE; } /* * add_mru Add and entry to mru list, hash table, and allocate * and return a replacement. * This is a helper for collect_mru_list(). */ static mru * add_mru( mru *add ) { u_short hash; mru *mon; mru *unlinked; hash = NTP_HASH_ADDR(&add->addr); /* see if we have it among previously received entries */ for (mon = hash_table[hash]; mon != NULL; mon = mon->hlink) if (SOCK_EQ(&mon->addr, &add->addr)) break; if (mon != NULL) { if (!L_ISGEQ(&add->first, &mon->first)) { xprintf(stderr, "add_mru duplicate %s new first ts %08x.%08x precedes prior %08x.%08x\n", sptoa(&add->addr), add->last.l_ui, add->last.l_uf, mon->last.l_ui, mon->last.l_uf); exit(1); } UNLINK_DLIST(mon, mlink); UNLINK_SLIST(unlinked, hash_table[hash], mon, hlink, mru); INSIST(unlinked == mon); mru_dupes++; TRACE(2, ("(updated from %08x.%08x) ", mon->last.l_ui, mon->last.l_uf)); } LINK_DLIST(mru_list, add, mlink); LINK_SLIST(hash_table[hash], add, hlink); TRACE(2, ("add_mru %08x.%08x c %d m %d v %d rest %x first %08x.%08x %s\n", add->last.l_ui, add->last.l_uf, add->count, (int)add->mode, (int)add->ver, (u_int)add->rs, add->first.l_ui, add->first.l_uf, sptoa(&add->addr))); /* if we didn't update an existing entry, alloc replacement */ if (NULL == mon) { mon = emalloc(sizeof(*mon)); mru_count++; } ZERO(*mon); return mon; } /* MGOT macro is specific to collect_mru_list() */ #define MGOT(bit) \ do { \ got |= (bit); \ if (MRU_GOT_ALL == got) { \ got = 0; \ mon = add_mru(mon); \ ci++; \ } \ } while (0) int mrulist_ctrl_c_hook(void) { mrulist_interrupted = TRUE; return TRUE; } static int collect_mru_list( const char * parms, l_fp * pnow ) { const u_int sleep_msecs = 5; static int ntpd_row_limit = MRU_ROW_LIMIT; int c_mru_l_rc; /* this function's return code */ u_char got; /* MRU_GOT_* bits */ time_t next_report; size_t cb; mru *mon; mru *head; mru *recent; int list_complete; char nonce[128]; char buf[128]; char req_buf[CTL_MAX_DATA_LEN]; char *req; char *req_end; size_t chars; int qres; u_short rstatus; size_t rsize; const char *rdata; int limit; int frags; int cap_frags; char *tag; char *val; int si; /* server index in response */ int ci; /* client (our) index for validation */ int ri; /* request index (.# suffix) */ int mv; l_fp newest; l_fp last_older; sockaddr_u addr_older; int have_now; int have_addr_older; int have_last_older; u_int restarted_count; u_int nonce_uses; u_short hash; mru *unlinked; if (!fetch_nonce(nonce, sizeof(nonce))) return FALSE; nonce_uses = 0; restarted_count = 0; mru_count = 0; INIT_DLIST(mru_list, mlink); cb = NTP_HASH_SIZE * sizeof(*hash_table); INSIST(NULL == hash_table); hash_table = emalloc_zero(cb); c_mru_l_rc = FALSE; list_complete = FALSE; have_now = FALSE; cap_frags = TRUE; got = 0; ri = 0; cb = sizeof(*mon); mon = emalloc_zero(cb); ZERO(*pnow); ZERO(last_older); next_report = time(NULL) + MRU_REPORT_SECS; limit = min(3 * MAXFRAGS, ntpd_row_limit); frags = MAXFRAGS; snprintf(req_buf, sizeof(req_buf), "nonce=%s, frags=%d%s", nonce, frags, parms); nonce_uses++; while (TRUE) { if (debug) xprintf(stderr, "READ_MRU parms: %s\n", req_buf); qres = doqueryex(CTL_OP_READ_MRU, 0, 0, strlen(req_buf), req_buf, &rstatus, &rsize, &rdata, TRUE); if (CERR_UNKNOWNVAR == qres && ri > 0) { /* * None of the supplied prior entries match, so * toss them from our list and try again. */ if (debug) xprintf(stderr, "no overlap between %d prior entries and server MRU list\n", ri); while (ri--) { recent = HEAD_DLIST(mru_list, mlink); INSIST(recent != NULL); if (debug) xprintf(stderr, "tossing prior entry %s to resync\n", sptoa(&recent->addr)); UNLINK_DLIST(recent, mlink); hash = NTP_HASH_ADDR(&recent->addr); UNLINK_SLIST(unlinked, hash_table[hash], recent, hlink, mru); INSIST(unlinked == recent); free(recent); mru_count--; } if (NULL == HEAD_DLIST(mru_list, mlink)) { restarted_count++; if (restarted_count > 8) { xprintf(stderr, "Giving up after 8 restarts from the beginning.\n" "With high-traffic NTP servers, this can occur if the\n" "MRU list is limited to less than about 16 seconds' of\n" "entries. See the 'mru' ntp.conf directive to adjust.\n"); goto cleanup_return; } if (debug) xprintf(stderr, "---> Restarting from the beginning, retry #%u\n", restarted_count); } } else if (CERR_UNKNOWNVAR == qres) { xprintf(stderr, "CERR_UNKNOWNVAR from ntpd but no priors given.\n"); goto cleanup_return; } else if (CERR_BADVALUE == qres) { if (cap_frags) { cap_frags = FALSE; if (debug) xprintf(stderr, "Reverted to row limit from fragments limit.\n"); } else { /* ntpd has lower cap on row limit */ ntpd_row_limit--; limit = min(limit, ntpd_row_limit); if (debug) xprintf(stderr, "Row limit reduced to %d following CERR_BADVALUE.\n", limit); } } else if (ERR_INCOMPLETE == qres || ERR_TIMEOUT == qres) { /* * Reduce the number of rows/frags requested by * half to recover from lost response fragments. */ if (cap_frags) { frags = max(2, frags / 2); if (debug) xprintf(stderr, "Frag limit reduced to %d following incomplete response.\n", frags); } else { limit = max(2, limit / 2); if (debug) xprintf(stderr, "Row limit reduced to %d following incomplete response.\n", limit); } } else if (qres) { show_error_msg(qres, 0); goto cleanup_return; } /* * This is a cheap cop-out implementation of rawmode * output for mrulist. A better approach would be to * dump similar output after the list is collected by * ntpq with a continuous sequence of indexes. This * cheap approach has indexes resetting to zero for * each query/response, and duplicates are not * coalesced. */ if (!qres && rawmode) printvars(rsize, rdata, rstatus, TYPE_SYS, 1, stdout); ci = 0; have_addr_older = FALSE; have_last_older = FALSE; while (!qres && nextvar(&rsize, &rdata, &tag, &val)) { INSIST(tag && val); if (debug > 1) xprintf(stderr, "nextvar gave: %s = %s\n", tag, val); switch(tag[0]) { case 'a': if (!strcmp(tag, "addr.older")) { if (!have_last_older) { xprintf(stderr, "addr.older %s before last.older\n", val); goto cleanup_return; } if (!decodenetnum(val, &addr_older)) { xprintf(stderr, "addr.older %s garbled\n", val); goto cleanup_return; } hash = NTP_HASH_ADDR(&addr_older); for (recent = hash_table[hash]; recent != NULL; recent = recent->hlink) if (ADDR_PORT_EQ( &addr_older, &recent->addr)) break; if (NULL == recent) { xprintf(stderr, "addr.older %s not in hash table\n", val); goto cleanup_return; } if (!L_ISEQU(&last_older, &recent->last)) { xprintf(stderr, "last.older %08x.%08x mismatches %08x.%08x expected.\n", last_older.l_ui, last_older.l_uf, recent->last.l_ui, recent->last.l_uf); goto cleanup_return; } have_addr_older = TRUE; } else if (1 != sscanf(tag, "addr.%d", &si) || si != ci) goto nomatch; else if (decodenetnum(val, &mon->addr)) MGOT(MRU_GOT_ADDR); break; case 'l': if (!strcmp(tag, "last.older")) { if ('0' != val[0] || 'x' != val[1] || !hextolfp(val + 2, &last_older)) { xprintf(stderr, "last.older %s garbled\n", val); goto cleanup_return; } have_last_older = TRUE; } else if (!strcmp(tag, "last.newest")) { if (0 != got) { xprintf(stderr, "last.newest %s before complete row, got = 0x%x\n", val, (u_int)got); goto cleanup_return; } if (!have_now) { xprintf(stderr, "last.newest %s before now=\n", val); goto cleanup_return; } head = HEAD_DLIST(mru_list, mlink); if (NULL != head) { if ('0' != val[0] || 'x' != val[1] || !hextolfp(val + 2, &newest) || !L_ISEQU(&newest, &head->last)) { xprintf(stderr, "last.newest %s mismatches %08x.%08x", val, head->last.l_ui, head->last.l_uf); goto cleanup_return; } } list_complete = TRUE; } else if (1 != sscanf(tag, "last.%d", &si) || si != ci || '0' != val[0] || 'x' != val[1] || !hextolfp(val + 2, &mon->last)) { goto nomatch; } else { MGOT(MRU_GOT_LAST); /* * allow interrupted retrieval, * using most recent retrieved * entry's last seen timestamp * as the end of operation. */ *pnow = mon->last; } break; case 'f': if (1 != sscanf(tag, "first.%d", &si) || si != ci || '0' != val[0] || 'x' != val[1] || !hextolfp(val + 2, &mon->first)) goto nomatch; MGOT(MRU_GOT_FIRST); break; case 'n': if (!strcmp(tag, "nonce")) { strlcpy(nonce, val, sizeof(nonce)); nonce_uses = 0; break; /* case */ } else if (strcmp(tag, "now") || '0' != val[0] || 'x' != val[1] || !hextolfp(val + 2, pnow)) goto nomatch; have_now = TRUE; break; case 'c': if (1 != sscanf(tag, "ct.%d", &si) || si != ci || 1 != sscanf(val, "%d", &mon->count) || mon->count < 1) goto nomatch; MGOT(MRU_GOT_COUNT); break; case 'm': if (1 != sscanf(tag, "mv.%d", &si) || si != ci || 1 != sscanf(val, "%d", &mv)) goto nomatch; mon->mode = PKT_MODE(mv); mon->ver = PKT_VERSION(mv); MGOT(MRU_GOT_MV); break; case 'r': if (1 != sscanf(tag, "rs.%d", &si) || si != ci || 1 != sscanf(val, "0x%hx", &mon->rs)) goto nomatch; MGOT(MRU_GOT_RS); break; default: nomatch: /* empty stmt */ ; /* ignore unknown tags */ } } if (have_now) list_complete = TRUE; if (list_complete) { INSIST(0 == ri || have_addr_older); } if (mrulist_interrupted) { printf("mrulist retrieval interrupted by operator.\n" "Displaying partial client list.\n"); fflush(stdout); } if (list_complete || mrulist_interrupted) { xprintf(stderr, "\rRetrieved %u unique MRU entries and %u updates.\n", mru_count, mru_dupes); fflush(stderr); break; } if (time(NULL) >= next_report) { next_report += MRU_REPORT_SECS; xprintf(stderr, "\r%u (%u updates) ", mru_count, mru_dupes); fflush(stderr); } /* * Snooze for a bit between queries to let ntpd catch * up with other duties. */ #ifdef SYS_WINNT Sleep(sleep_msecs); #elif !defined(HAVE_NANOSLEEP) sleep((sleep_msecs / 1000) + 1); #else { struct timespec interv = { 0, 1000 * sleep_msecs }; nanosleep(&interv, NULL); } #endif /* * If there were no errors, increase the number of rows * to a maximum of 3 * MAXFRAGS (the most packets ntpq * can handle in one response), on the assumption that * no less than 3 rows fit in each packet, capped at * our best guess at the server's row limit. */ if (!qres) { if (cap_frags) { frags = min(MAXFRAGS, frags + 1); } else { limit = min3(3 * MAXFRAGS, ntpd_row_limit, max(limit + 1, limit * 33 / 32)); } } /* * prepare next query with as many address and last-seen * timestamps as will fit in a single packet. */ req = req_buf; req_end = req_buf + sizeof(req_buf); #define REQ_ROOM (req_end - req) snprintf(req, REQ_ROOM, "nonce=%s, %s=%d%s", nonce, (cap_frags) ? "frags" : "limit", (cap_frags) ? frags : limit, parms); req += strlen(req); nonce_uses++; if (nonce_uses >= 4) { if (!fetch_nonce(nonce, sizeof(nonce))) goto cleanup_return; nonce_uses = 0; } for (ri = 0, recent = HEAD_DLIST(mru_list, mlink); recent != NULL; ri++, recent = NEXT_DLIST(mru_list, recent, mlink)) { snprintf(buf, sizeof(buf), ", addr.%d=%s, last.%d=0x%08x.%08x", ri, sptoa(&recent->addr), ri, recent->last.l_ui, recent->last.l_uf); chars = strlen(buf); if ((size_t)REQ_ROOM <= chars) break; memcpy(req, buf, chars + 1); req += chars; } } c_mru_l_rc = TRUE; goto retain_hash_table; cleanup_return: free(hash_table); hash_table = NULL; retain_hash_table: if (mon != NULL) free(mon); return c_mru_l_rc; } /* * qcmp_mru_addr - sort MRU entries by remote address. * * All IPv4 addresses sort before any IPv6, addresses are sorted by * value within address family. */ static int qcmp_mru_addr( const void *v1, const void *v2 ) { const mru * const * ppm1 = v1; const mru * const * ppm2 = v2; const mru * pm1; const mru * pm2; u_short af1; u_short af2; size_t cmplen; size_t addr_off; pm1 = *ppm1; pm2 = *ppm2; af1 = AF(&pm1->addr); af2 = AF(&pm2->addr); if (af1 != af2) return (AF_INET == af1) ? -1 : 1; cmplen = SIZEOF_INADDR(af1); addr_off = (AF_INET == af1) ? offsetof(struct sockaddr_in, sin_addr) : offsetof(struct sockaddr_in6, sin6_addr); return memcmp((const char *)&pm1->addr + addr_off, (const char *)&pm2->addr + addr_off, cmplen); } static int qcmp_mru_r_addr( const void *v1, const void *v2 ) { return -qcmp_mru_addr(v1, v2); } /* * qcmp_mru_count - sort MRU entries by times seen (hit count). */ static int qcmp_mru_count( const void *v1, const void *v2 ) { const mru * const * ppm1 = v1; const mru * const * ppm2 = v2; const mru * pm1; const mru * pm2; pm1 = *ppm1; pm2 = *ppm2; return (pm1->count < pm2->count) ? -1 : ((pm1->count == pm2->count) ? 0 : 1); } static int qcmp_mru_r_count( const void *v1, const void *v2 ) { return -qcmp_mru_count(v1, v2); } /* * qcmp_mru_avgint - sort MRU entries by average interval. */ static int qcmp_mru_avgint( const void *v1, const void *v2 ) { const mru * const * ppm1 = v1; const mru * const * ppm2 = v2; const mru * pm1; const mru * pm2; l_fp interval; double avg1; double avg2; pm1 = *ppm1; pm2 = *ppm2; interval = pm1->last; L_SUB(&interval, &pm1->first); LFPTOD(&interval, avg1); avg1 /= pm1->count; interval = pm2->last; L_SUB(&interval, &pm2->first); LFPTOD(&interval, avg2); avg2 /= pm2->count; if (avg1 < avg2) return -1; else if (avg1 > avg2) return 1; /* secondary sort on lstint - rarely tested */ if (L_ISEQU(&pm1->last, &pm2->last)) return 0; else if (L_ISGEQ(&pm1->last, &pm2->last)) return -1; else return 1; } static int qcmp_mru_r_avgint( const void *v1, const void *v2 ) { return -qcmp_mru_avgint(v1, v2); } /* * mrulist - ntpq's mrulist command to fetch an arbitrarily large Most * Recently Used (seen) remote address list from ntpd. * * Similar to ntpdc's monlist command, but not limited to a single * request/response, and thereby not limited to a few hundred remote * addresses. * * See ntpd/ntp_control.c read_mru_list() for comments on the way * CTL_OP_READ_MRU is designed to be used. * * mrulist intentionally differs from monlist in the way the avgint * column is calculated. monlist includes the time after the last * packet from the client until the monlist query time in the average, * while mrulist excludes it. That is, monlist's average interval grows * over time for remote addresses not heard from in some time, while it * remains unchanged in mrulist. This also affects the avgint value for * entries representing a single packet, with identical first and last * timestamps. mrulist shows 0 avgint, monlist shows a value identical * to lstint. */ static void mrulist( struct parse * pcmd, FILE * fp ) { const char mincount_eq[] = "mincount="; const char resall_eq[] = "resall="; const char resany_eq[] = "resany="; const char maxlstint_eq[] = "maxlstint="; const char laddr_eq[] = "laddr="; const char sort_eq[] = "sort="; mru_sort_order order; size_t n; char parms_buf[128]; char buf[24]; char *parms; const char *arg; size_t cb; mru **sorted; mru **ppentry; mru *recent; l_fp now; l_fp interval; double favgint; double flstint; int avgint; int lstint; size_t i; mrulist_interrupted = FALSE; push_ctrl_c_handler(&mrulist_ctrl_c_hook); xprintf(stderr, "Ctrl-C will stop MRU retrieval and display partial results.\n"); fflush(stderr); order = MRUSORT_DEF; parms_buf[0] = '\0'; parms = parms_buf; for (i = 0; i < pcmd->nargs; i++) { arg = pcmd->argval[i].string; if (arg != NULL) { cb = strlen(arg) + 1; if ((!strncmp(resall_eq, arg, sizeof(resall_eq) - 1) || !strncmp(resany_eq, arg, sizeof(resany_eq) - 1) || !strncmp( mincount_eq, arg, sizeof(mincount_eq) - 1) || !strncmp(laddr_eq, arg, sizeof(laddr_eq) - 1) || !strncmp(maxlstint_eq, arg, sizeof(laddr_eq) - 1)) && parms + cb + 2 <= parms_buf + sizeof(parms_buf)) { /* these are passed intact to ntpd */ memcpy(parms, ", ", 2); parms += 2; memcpy(parms, arg, cb); parms += cb - 1; } else if (!strncmp(sort_eq, arg, sizeof(sort_eq) - 1)) { arg += sizeof(sort_eq) - 1; for (n = 0; n < COUNTOF(mru_sort_keywords); n++) if (!strcmp(mru_sort_keywords[n], arg)) break; if (n < COUNTOF(mru_sort_keywords)) order = n; } else if (!strcmp("limited", arg) || !strcmp("kod", arg)) { /* transform to resany=... */ snprintf(buf, sizeof(buf), ", resany=0x%x", ('k' == arg[0]) ? RES_KOD : RES_LIMITED); cb = 1 + strlen(buf); if (parms + cb < parms_buf + sizeof(parms_buf)) { memcpy(parms, buf, cb); parms += cb - 1; } } else xprintf(stderr, "ignoring unrecognized mrulist parameter: %s\n", arg); } } parms = parms_buf; if (!collect_mru_list(parms, &now)) return; /* display the results */ if (rawmode) goto cleanup_return; /* construct an array of entry pointers in default order */ sorted = eallocarray(mru_count, sizeof(*sorted)); ppentry = sorted; if (MRUSORT_R_DEF != order) { ITER_DLIST_BEGIN(mru_list, recent, mlink, mru) INSIST(ppentry < sorted + mru_count); *ppentry = recent; ppentry++; ITER_DLIST_END() } else { REV_ITER_DLIST_BEGIN(mru_list, recent, mlink, mru) INSIST(ppentry < sorted + mru_count); *ppentry = recent; ppentry++; REV_ITER_DLIST_END() } if (ppentry - sorted != (int)mru_count) { xprintf(stderr, "mru_count %u should match MRU list depth %ld.\n", mru_count, (long)(ppentry - sorted)); free(sorted); goto cleanup_return; } /* re-sort sorted[] if not default or reverse default */ if (MRUSORT_R_DEF < order) qsort(sorted, mru_count, sizeof(sorted[0]), mru_qcmp_table[order]); mrulist_interrupted = FALSE; printf( "lstint avgint rstr r m v count rport remote address\n" "==============================================================================\n"); /* '=' x 78 */ for (ppentry = sorted; ppentry < sorted + mru_count; ppentry++) { recent = *ppentry; interval = now; L_SUB(&interval, &recent->last); LFPTOD(&interval, flstint); lstint = (int)(flstint + 0.5); interval = recent->last; L_SUB(&interval, &recent->first); LFPTOD(&interval, favgint); favgint /= recent->count; avgint = (int)(favgint + 0.5); xprintf(fp, "%6d %6d %4hx %c %d %d %6d %5u %s\n", lstint, avgint, recent->rs, (RES_KOD & recent->rs) ? 'K' : (RES_LIMITED & recent->rs) ? 'L' : '.', (int)recent->mode, (int)recent->ver, recent->count, SRCPORT(&recent->addr), nntohost(&recent->addr)); if (showhostnames) fflush(fp); if (mrulist_interrupted) { xputs("\n --interrupted--\n", fp); fflush(fp); break; } } fflush(fp); if (debug) { xprintf(stderr, "--- completed, freeing sorted[] pointers\n"); fflush(stderr); } free(sorted); cleanup_return: if (debug) { xprintf(stderr, "... freeing MRU entries\n"); fflush(stderr); } ITER_DLIST_BEGIN(mru_list, recent, mlink, mru) free(recent); ITER_DLIST_END() if (debug) { xprintf(stderr, "... freeing hash_table[]\n"); fflush(stderr); } free(hash_table); hash_table = NULL; INIT_DLIST(mru_list, mlink); pop_ctrl_c_handler(&mrulist_ctrl_c_hook); } /* * validate_ifnum - helper for ifstats() * * Ensures rows are received in order and complete. */ static void validate_ifnum( FILE * fp, u_int ifnum, int * pfields, ifstats_row * prow ) { if (prow->ifnum == ifnum) return; if (prow->ifnum + 1 <= ifnum) { if (*pfields < IFSTATS_FIELDS) xprintf(fp, "Warning: incomplete row with %d (of %d) fields\n", *pfields, IFSTATS_FIELDS); *pfields = 0; prow->ifnum = ifnum; return; } xprintf(stderr, "received if index %u, have %d of %d fields for index %u, aborting.\n", ifnum, *pfields, IFSTATS_FIELDS, prow->ifnum); exit(1); } /* * another_ifstats_field - helper for ifstats() * * If all fields for the row have been received, print it. */ static void another_ifstats_field( int * pfields, ifstats_row * prow, FILE * fp ) { u_int ifnum; (*pfields)++; /* we understand 12 tags */ if (IFSTATS_FIELDS > *pfields) return; /* " interface name send\n" " # address/broadcast drop flag ttl mc received sent failed peers uptime\n" "==============================================================================\n"); */ xprintf(fp, "%3u %-24.24s %c %4x %3u %2u %6u %6u %6u %5u %8d\n" " %s\n", prow->ifnum, prow->name, (prow->enabled) ? '.' : 'D', prow->flags, prow->ttl, prow->mcast_count, prow->received, prow->sent, prow->send_errors, prow->peer_count, prow->uptime, sptoa(&prow->addr)); if (!SOCK_UNSPEC(&prow->bcast)) xprintf(fp, " %s\n", sptoa(&prow->bcast)); ifnum = prow->ifnum; ZERO(*prow); prow->ifnum = ifnum; } /* * ifstats - ntpq -c ifstats modeled on ntpdc -c ifstats. */ static void ifstats( struct parse * pcmd, FILE * fp ) { const char addr_fmt[] = "addr.%u"; const char bcast_fmt[] = "bcast.%u"; const char en_fmt[] = "en.%u"; /* enabled */ const char flags_fmt[] = "flags.%u"; const char mc_fmt[] = "mc.%u"; /* mcast count */ const char name_fmt[] = "name.%u"; const char pc_fmt[] = "pc.%u"; /* peer count */ const char rx_fmt[] = "rx.%u"; const char tl_fmt[] = "tl.%u"; /* ttl */ const char tx_fmt[] = "tx.%u"; const char txerr_fmt[] = "txerr.%u"; const char up_fmt[] = "up.%u"; /* uptime */ const char * datap; int qres; size_t dsize; u_short rstatus; char * tag; char * val; int fields; u_int ui; ifstats_row row; int comprende; size_t len; qres = doquery(CTL_OP_READ_ORDLIST_A, 0, TRUE, 0, NULL, &rstatus, &dsize, &datap); if (qres) /* message already displayed */ return; xprintf(fp, " interface name send\n" " # address/broadcast drop flag ttl mc received sent failed peers uptime\n" "==============================================================================\n"); /* '=' x 78 */ ZERO(row); fields = 0; ui = 0; while (nextvar(&dsize, &datap, &tag, &val)) { INSIST(tag && val); if (debug > 1) xprintf(stderr, "nextvar gave: %s = %s\n", tag, val); comprende = FALSE; switch(tag[0]) { case 'a': if (1 == sscanf(tag, addr_fmt, &ui) && decodenetnum(val, &row.addr)) comprende = TRUE; break; case 'b': if (1 == sscanf(tag, bcast_fmt, &ui) && ('\0' == *val || decodenetnum(val, &row.bcast))) comprende = TRUE; break; case 'e': if (1 == sscanf(tag, en_fmt, &ui) && 1 == sscanf(val, "%d", &row.enabled)) comprende = TRUE; break; case 'f': if (1 == sscanf(tag, flags_fmt, &ui) && 1 == sscanf(val, "0x%x", &row.flags)) comprende = TRUE; break; case 'm': if (1 == sscanf(tag, mc_fmt, &ui) && 1 == sscanf(val, "%u", &row.mcast_count)) comprende = TRUE; break; case 'n': if (1 == sscanf(tag, name_fmt, &ui)) { /* strip quotes */ len = strlen(val); if (len >= 2 && len - 2 < sizeof(row.name)) { len -= 2; memcpy(row.name, val + 1, len); row.name[len] = '\0'; comprende = TRUE; } } break; case 'p': if (1 == sscanf(tag, pc_fmt, &ui) && 1 == sscanf(val, "%u", &row.peer_count)) comprende = TRUE; break; case 'r': if (1 == sscanf(tag, rx_fmt, &ui) && 1 == sscanf(val, "%u", &row.received)) comprende = TRUE; break; case 't': if (1 == sscanf(tag, tl_fmt, &ui) && 1 == sscanf(val, "%u", &row.ttl)) comprende = TRUE; else if (1 == sscanf(tag, tx_fmt, &ui) && 1 == sscanf(val, "%u", &row.sent)) comprende = TRUE; else if (1 == sscanf(tag, txerr_fmt, &ui) && 1 == sscanf(val, "%u", &row.send_errors)) comprende = TRUE; break; case 'u': if (1 == sscanf(tag, up_fmt, &ui) && 1 == sscanf(val, "%u", &row.uptime)) comprende = TRUE; break; } if (comprende) { /* error out if rows out of order */ validate_ifnum(fp, ui, &fields, &row); /* if the row is complete, print it */ another_ifstats_field(&fields, &row, fp); } } if (fields != IFSTATS_FIELDS) xprintf(fp, "Warning: incomplete row with %d (of %d) fields\n", fields, IFSTATS_FIELDS); fflush(fp); } /* * validate_reslist_idx - helper for reslist() * * Ensures rows are received in order and complete. */ static void validate_reslist_idx( FILE * fp, u_int idx, int * pfields, reslist_row * prow ) { if (prow->idx == idx) return; if (prow->idx + 1 == idx) { if (*pfields < RESLIST_FIELDS) xprintf(fp, "Warning: incomplete row with %d (of %d) fields", *pfields, RESLIST_FIELDS); *pfields = 0; prow->idx = idx; return; } xprintf(stderr, "received reslist index %u, have %d of %d fields for index %u, aborting.\n", idx, *pfields, RESLIST_FIELDS, prow->idx); exit(1); } /* * another_reslist_field - helper for reslist() * * If all fields for the row have been received, print it. */ static void another_reslist_field( int * pfields, reslist_row * prow, FILE * fp ) { char addrmaskstr[128]; int prefix; /* subnet mask as prefix bits count */ u_int idx; (*pfields)++; /* we understand 4 tags */ if (RESLIST_FIELDS > *pfields) return; prefix = sockaddr_masktoprefixlen(&prow->mask); if (prefix >= 0) snprintf(addrmaskstr, sizeof(addrmaskstr), "%s/%d", stoa(&prow->addr), prefix); else snprintf(addrmaskstr, sizeof(addrmaskstr), "%s %s", stoa(&prow->addr), stoa(&prow->mask)); /* " hits addr/prefix or addr mask\n" " restrictions\n" "==============================================================================\n"); */ xprintf(fp, "%10lu %s\n" " %s\n", prow->hits, addrmaskstr, prow->flagstr); idx = prow->idx; ZERO(*prow); prow->idx = idx; } /* * reslist - ntpq -c reslist modeled on ntpdc -c reslist. */ static void reslist( struct parse * pcmd, FILE * fp ) { const char addr_fmtu[] = "addr.%u"; const char mask_fmtu[] = "mask.%u"; const char hits_fmt[] = "hits.%u"; const char flags_fmt[] = "flags.%u"; const char qdata[] = "addr_restrictions"; const int qdata_chars = COUNTOF(qdata) - 1; const char * datap; int qres; size_t dsize; u_short rstatus; char * tag; char * val; int fields; u_int ui; reslist_row row; int comprende; size_t len; qres = doquery(CTL_OP_READ_ORDLIST_A, 0, TRUE, qdata_chars, qdata, &rstatus, &dsize, &datap); if (qres) /* message already displayed */ return; xprintf(fp, " hits addr/prefix or addr mask\n" " restrictions\n" "==============================================================================\n"); /* '=' x 78 */ ZERO(row); fields = 0; ui = 0; while (nextvar(&dsize, &datap, &tag, &val)) { INSIST(tag && val); if (debug > 1) xprintf(stderr, "nextvar gave: %s = %s\n", tag, val); comprende = FALSE; switch(tag[0]) { case 'a': if (1 == sscanf(tag, addr_fmtu, &ui) && decodenetnum(val, &row.addr)) comprende = TRUE; break; case 'f': if (1 == sscanf(tag, flags_fmt, &ui)) { if (NULL == val) { row.flagstr[0] = '\0'; comprende = TRUE; } else if ((len = strlen(val)) < sizeof(row.flagstr)) { memcpy(row.flagstr, val, len); row.flagstr[len] = '\0'; comprende = TRUE; } else { /* no flags, and still !comprende */ row.flagstr[0] = '\0'; } } break; case 'h': if (1 == sscanf(tag, hits_fmt, &ui) && 1 == sscanf(val, "%lu", &row.hits)) comprende = TRUE; break; case 'm': if (1 == sscanf(tag, mask_fmtu, &ui) && decodenetnum(val, &row.mask)) comprende = TRUE; break; } if (comprende) { /* error out if rows out of order */ validate_reslist_idx(fp, ui, &fields, &row); /* if the row is complete, print it */ another_reslist_field(&fields, &row, fp); } } if (fields != RESLIST_FIELDS) xprintf(fp, "Warning: incomplete row with %d (of %d) fields", fields, RESLIST_FIELDS); fflush(fp); } /* * collect_display_vdc */ static void collect_display_vdc( associd_t as, vdc * table, int decodestatus, FILE * fp ) { static const char * const suf[2] = { "adr", "port" }; static const char * const leapbits[4] = { "00", "01", "10", "11" }; struct varlist vl[MAXLIST]; char tagbuf[32]; vdc *pvdc; u_short rstatus; size_t rsize; const char *rdata; int qres; char *tag; char *val; u_int n; size_t len; int match; u_long ul; int vtype; sockaddr_u sau; ZERO(vl); for (pvdc = table; pvdc->tag != NULL; pvdc++) { ZERO(pvdc->v); if (NTP_ADD != pvdc->type) { doaddvlist(vl, pvdc->tag); } else { for (n = 0; n < COUNTOF(suf); n++) { snprintf(tagbuf, sizeof(tagbuf), "%s%s", pvdc->tag, suf[n]); doaddvlist(vl, tagbuf); } } } qres = doquerylist(vl, CTL_OP_READVAR, as, 0, &rstatus, &rsize, &rdata); doclearvlist(vl); if (qres) return; /* error msg already displayed */ /* * iterate over the response variables filling vdc_table with * the retrieved values. */ while (nextvar(&rsize, &rdata, &tag, &val)) { INSIST(tag && val); n = 0; for (pvdc = table; pvdc->tag != NULL; pvdc++) { len = strlen(pvdc->tag); if (strncmp(tag, pvdc->tag, len)) continue; if (NTP_ADD != pvdc->type) { if ('\0' != tag[len]) continue; break; } match = FALSE; for (n = 0; n < COUNTOF(suf); n++) { if (strcmp(tag + len, suf[n])) continue; match = TRUE; break; } if (match) break; } if (NULL == pvdc->tag) continue; switch (pvdc->type) { case NTP_STR: /* strip surrounding double quotes */ if ('"' == val[0]) { len = strlen(val); if (len > 0 && '"' == val[len - 1]) { val[len - 1] = '\0'; val++; } } /* fallthru */ case NTP_REFID: /* fallthru */ case NTP_MODE: /* fallthru */ case NTP_2BIT: pvdc->v.str = estrdup(val); break; case NTP_LFP: decodets(val, &pvdc->v.lfp); break; case NTP_ADP: if (!decodenetnum(val, &pvdc->v.sau)) xprintf(stderr, "malformed %s=%s\n", pvdc->tag, val); break; case NTP_ADD: if (0 == n) { /* adr */ if (!decodenetnum(val, &pvdc->v.sau)) xprintf(stderr, "malformed %s=%s\n", pvdc->tag, val); } else { /* port */ if (atouint(val, &ul)) SET_PORT(&pvdc->v.sau, (u_short)ul); } break; } } /* and display */ if (decodestatus) { vtype = (0 == as) ? TYPE_SYS : TYPE_PEER; xprintf(fp, "associd=%u status=%04x %s,\n", as, rstatus, statustoa(vtype, rstatus)); } for (pvdc = table; pvdc->tag != NULL; pvdc++) { switch (pvdc->type) { case NTP_STR: if (pvdc->v.str != NULL) { xprintf(fp, "%s %s\n", pvdc->display, pvdc->v.str); free(pvdc->v.str); pvdc->v.str = NULL; } break; case NTP_ADD: /* fallthru */ case NTP_ADP: xprintf(fp, "%s %s\n", pvdc->display, nntohostp(&pvdc->v.sau)); break; case NTP_LFP: xprintf(fp, "%s %s\n", pvdc->display, prettydate(&pvdc->v.lfp)); break; case NTP_MODE: atouint(pvdc->v.str, &ul); xprintf(fp, "%s %s\n", pvdc->display, modetoa((int)ul)); free(pvdc->v.str); pvdc->v.str = NULL; break; case NTP_2BIT: atouint(pvdc->v.str, &ul); xprintf(fp, "%s %s\n", pvdc->display, leapbits[ul & 0x3]); free(pvdc->v.str); pvdc->v.str = NULL; break; case NTP_REFID: if (!decodenetnum(pvdc->v.str, &sau)) { fprintf(fp, "%s %s\n", pvdc->display, /* Text fmt */ pvdc->v.str); } else if (drefid == REFID_IPV4) { fprintf(fp, "%s %s\n", pvdc->display, /* IPv4 fmt */ stoa(&sau)); } else { fprintf (fp, "%s 0x%08x\n", pvdc->display, /* Hex / hash */ ntohl(addr2refid(&sau))); } free(pvdc->v.str); pvdc->v.str = NULL; break; default: xprintf(stderr, "unexpected vdc type %d for %s\n", pvdc->type, pvdc->tag); break; } } } /* * sysstats - implements ntpq -c sysstats modeled on ntpdc -c sysstats */ static void sysstats( struct parse *pcmd, FILE *fp ) { static vdc sysstats_vdc[] = { VDC_INIT("ss_uptime", "uptime: ", NTP_STR), VDC_INIT("ss_reset", "sysstats reset: ", NTP_STR), VDC_INIT("ss_received", "packets received: ", NTP_STR), VDC_INIT("ss_thisver", "current version: ", NTP_STR), VDC_INIT("ss_oldver", "older version: ", NTP_STR), VDC_INIT("ss_badformat", "bad length or format: ", NTP_STR), VDC_INIT("ss_badauth", "authentication failed:", NTP_STR), VDC_INIT("ss_declined", "declined: ", NTP_STR), VDC_INIT("ss_restricted", "restricted: ", NTP_STR), VDC_INIT("ss_limited", "rate limited: ", NTP_STR), VDC_INIT("ss_kodsent", "KoD responses: ", NTP_STR), VDC_INIT("ss_processed", "processed for time: ", NTP_STR), #if 0 VDC_INIT("ss_lamport", "Lamport violations: ", NTP_STR), VDC_INIT("ss_tsrounding", "bad timestamp rounding:", NTP_STR), #endif VDC_INIT(NULL, NULL, 0) }; collect_display_vdc(0, sysstats_vdc, FALSE, fp); } /* * sysinfo - modeled on ntpdc's sysinfo */ static void sysinfo( struct parse *pcmd, FILE *fp ) { static vdc sysinfo_vdc[] = { VDC_INIT("peeradr", "system peer: ", NTP_ADP), VDC_INIT("peermode", "system peer mode: ", NTP_MODE), VDC_INIT("leap", "leap indicator: ", NTP_2BIT), VDC_INIT("stratum", "stratum: ", NTP_STR), VDC_INIT("precision", "log2 precision: ", NTP_STR), VDC_INIT("rootdelay", "root delay: ", NTP_STR), VDC_INIT("rootdisp", "root dispersion: ", NTP_STR), VDC_INIT("refid", "reference ID: ", NTP_REFID), VDC_INIT("reftime", "reference time: ", NTP_LFP), VDC_INIT("sys_jitter", "system jitter: ", NTP_STR), VDC_INIT("clk_jitter", "clock jitter: ", NTP_STR), VDC_INIT("clk_wander", "clock wander: ", NTP_STR), VDC_INIT("bcastdelay", "broadcast delay: ", NTP_STR), VDC_INIT("authdelay", "symm. auth. delay:", NTP_STR), VDC_INIT(NULL, NULL, 0) }; collect_display_vdc(0, sysinfo_vdc, TRUE, fp); } /* * kerninfo - modeled on ntpdc's kerninfo */ static void kerninfo( struct parse *pcmd, FILE *fp ) { static vdc kerninfo_vdc[] = { VDC_INIT("koffset", "pll offset: ", NTP_STR), VDC_INIT("kfreq", "pll frequency: ", NTP_STR), VDC_INIT("kmaxerr", "maximum error: ", NTP_STR), VDC_INIT("kesterr", "estimated error: ", NTP_STR), VDC_INIT("kstflags", "kernel status: ", NTP_STR), VDC_INIT("ktimeconst", "pll time constant: ", NTP_STR), VDC_INIT("kprecis", "precision: ", NTP_STR), VDC_INIT("kfreqtol", "frequency tolerance: ", NTP_STR), VDC_INIT("kppsfreq", "pps frequency: ", NTP_STR), VDC_INIT("kppsstab", "pps stability: ", NTP_STR), VDC_INIT("kppsjitter", "pps jitter: ", NTP_STR), VDC_INIT("kppscalibdur", "calibration interval ", NTP_STR), VDC_INIT("kppscalibs", "calibration cycles: ", NTP_STR), VDC_INIT("kppsjitexc", "jitter exceeded: ", NTP_STR), VDC_INIT("kppsstbexc", "stability exceeded: ", NTP_STR), VDC_INIT("kppscaliberrs", "calibration errors: ", NTP_STR), VDC_INIT(NULL, NULL, 0) }; collect_display_vdc(0, kerninfo_vdc, TRUE, fp); } /* * monstats - implements ntpq -c monstats */ static void monstats( struct parse *pcmd, FILE *fp ) { static vdc monstats_vdc[] = { VDC_INIT("mru_enabled", "enabled: ", NTP_STR), VDC_INIT("mru_depth", "addresses: ", NTP_STR), VDC_INIT("mru_deepest", "peak addresses: ", NTP_STR), VDC_INIT("mru_maxdepth", "maximum addresses: ", NTP_STR), VDC_INIT("mru_mindepth", "reclaim above count:", NTP_STR), VDC_INIT("mru_maxage", "reclaim older than: ", NTP_STR), VDC_INIT("mru_mem", "kilobytes: ", NTP_STR), VDC_INIT("mru_maxmem", "maximum kilobytes: ", NTP_STR), VDC_INIT(NULL, NULL, 0) }; collect_display_vdc(0, monstats_vdc, FALSE, fp); } /* * iostats - ntpq -c iostats - network input and output counters */ static void iostats( struct parse *pcmd, FILE *fp ) { static vdc iostats_vdc[] = { VDC_INIT("iostats_reset", "time since reset: ", NTP_STR), VDC_INIT("total_rbuf", "receive buffers: ", NTP_STR), VDC_INIT("free_rbuf", "free receive buffers: ", NTP_STR), VDC_INIT("used_rbuf", "used receive buffers: ", NTP_STR), VDC_INIT("rbuf_lowater", "low water refills: ", NTP_STR), VDC_INIT("io_dropped", "dropped packets: ", NTP_STR), VDC_INIT("io_ignored", "ignored packets: ", NTP_STR), VDC_INIT("io_received", "received packets: ", NTP_STR), VDC_INIT("io_sent", "packets sent: ", NTP_STR), VDC_INIT("io_sendfailed", "packet send failures: ", NTP_STR), VDC_INIT("io_wakeups", "input wakeups: ", NTP_STR), VDC_INIT("io_goodwakeups", "useful input wakeups: ", NTP_STR), VDC_INIT(NULL, NULL, 0) }; collect_display_vdc(0, iostats_vdc, FALSE, fp); } /* * timerstats - ntpq -c timerstats - interval timer counters */ static void timerstats( struct parse *pcmd, FILE *fp ) { static vdc timerstats_vdc[] = { VDC_INIT("timerstats_reset", "time since reset: ", NTP_STR), VDC_INIT("timer_overruns", "timer overruns: ", NTP_STR), VDC_INIT("timer_xmts", "calls to transmit: ", NTP_STR), VDC_INIT(NULL, NULL, 0) }; collect_display_vdc(0, timerstats_vdc, FALSE, fp); } /* * authinfo - implements ntpq -c authinfo */ static void authinfo( struct parse *pcmd, FILE *fp ) { static vdc authinfo_vdc[] = { VDC_INIT("authreset", "time since reset:", NTP_STR), VDC_INIT("authkeys", "stored keys: ", NTP_STR), VDC_INIT("authfreek", "free keys: ", NTP_STR), VDC_INIT("authklookups", "key lookups: ", NTP_STR), VDC_INIT("authknotfound", "keys not found: ", NTP_STR), VDC_INIT("authkuncached", "uncached keys: ", NTP_STR), VDC_INIT("authkexpired", "expired keys: ", NTP_STR), VDC_INIT("authencrypts", "encryptions: ", NTP_STR), VDC_INIT("authdecrypts", "decryptions: ", NTP_STR), VDC_INIT(NULL, NULL, 0) }; collect_display_vdc(0, authinfo_vdc, FALSE, fp); } /* * pstats - show statistics for a peer */ static void pstats( struct parse *pcmd, FILE *fp ) { static vdc pstats_vdc[] = { VDC_INIT("src", "remote host: ", NTP_ADD), VDC_INIT("dst", "local address: ", NTP_ADD), VDC_INIT("timerec", "time last received: ", NTP_STR), VDC_INIT("timer", "time until next send:", NTP_STR), VDC_INIT("timereach", "reachability change: ", NTP_STR), VDC_INIT("sent", "packets sent: ", NTP_STR), VDC_INIT("received", "packets received: ", NTP_STR), VDC_INIT("badauth", "bad authentication: ", NTP_STR), VDC_INIT("bogusorg", "bogus origin: ", NTP_STR), VDC_INIT("oldpkt", "duplicate: ", NTP_STR), VDC_INIT("seldisp", "bad dispersion: ", NTP_STR), VDC_INIT("selbroken", "bad reference time: ", NTP_STR), VDC_INIT("candidate", "candidate order: ", NTP_STR), VDC_INIT(NULL, NULL, 0) }; associd_t associd; associd = checkassocid(pcmd->argval[0].uval); if (0 == associd) return; collect_display_vdc(associd, pstats_vdc, TRUE, fp); }