276da39af9
Approved by: roberto, delphij Security: VuXML: 0d0f3050-1f69-11e5-9ba9-d050996490d0 Security: http://bugs.ntp.org/show_bug.cgi?id=2853 Security: https://www.kb.cert.org/vuls/id/668167 Security: http://support.ntp.org/bin/view/Main/SecurityNotice#June_2015_NTP_Security_Vulnerabi
4015 lines
89 KiB
C
4015 lines
89 KiB
C
/*
|
|
* ntpq-subs.c - subroutines which are called to perform ntpq commands.
|
|
*/
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
|
|
#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 *, int *, char *);
|
|
static int doquerylist (struct varlist *, int, associd_t, int,
|
|
u_short *, int *, 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, int, 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 },
|
|
{ "<configuration command line>", "", "", "" },
|
|
"send a remote configuration command to ntpd" },
|
|
{ "config-from-file", config_from_file, { NTP_STR, NO, NO, NO },
|
|
{ "<configuration filename>", "", "", "" },
|
|
"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;
|
|
int mcast_count;
|
|
char name[32];
|
|
int peer_count;
|
|
int received;
|
|
int sent;
|
|
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
|
|
*/
|
|
void 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 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 */
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
fprintf(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;
|
|
int len;
|
|
char *name;
|
|
char *value;
|
|
|
|
len = strlen(vars);
|
|
while (nextvar(&len, &vars, &name, &value)) {
|
|
vl = findlistvar(vlist, name);
|
|
if (NULL == vl) {
|
|
fprintf(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;
|
|
int len;
|
|
char *name;
|
|
char *value;
|
|
|
|
len = strlen(vars);
|
|
while (nextvar(&len, &vars, &name, &value)) {
|
|
vl = findlistvar(vlist, name);
|
|
if (vl == 0 || vl->name == 0) {
|
|
(void) fprintf(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,
|
|
int *datalen,
|
|
char *data
|
|
)
|
|
{
|
|
register struct varlist *vl;
|
|
register char *cp, *cpend;
|
|
register int namelen, valuelen;
|
|
register int 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) {
|
|
fprintf(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 = 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,
|
|
int *dsize,
|
|
const char **datap
|
|
)
|
|
{
|
|
char data[CTL_MAX_DATA_LEN];
|
|
int 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) {
|
|
fprintf(fp, "No variables on list\n");
|
|
return;
|
|
}
|
|
for (n = 0; n < MAXLIST && vlist[n].name != NULL; n++) {
|
|
if (NULL == vlist[n].value)
|
|
fprintf(fp, "%s\n", vlist[n].name);
|
|
else
|
|
fprintf(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;
|
|
int 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)
|
|
fprintf(fp, "server=%s ", currenthost);
|
|
if (dsize == 0) {
|
|
if (associd == 0)
|
|
fprintf(fp, "No system%s variables returned\n",
|
|
(type == TYPE_CLOCK) ? " clock" : "");
|
|
else
|
|
fprintf(fp,
|
|
"No information returned for%s association %u\n",
|
|
(type == TYPE_CLOCK) ? " clock" : "",
|
|
associd);
|
|
return 1;
|
|
}
|
|
|
|
if (!quiet)
|
|
fprintf(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;
|
|
int 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) fprintf(fp, "server=%s ", currenthost);
|
|
if (dsize == 0)
|
|
(void) fprintf(fp, "done! (no data returned)\n");
|
|
else {
|
|
(void) fprintf(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;
|
|
u_int tmpcount;
|
|
u_int 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;
|
|
int 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)
|
|
fprintf(fp, "server=%s ", currenthost);
|
|
if (dsize == 0)
|
|
fprintf(fp, "done! (no data returned)\n");
|
|
else {
|
|
fprintf(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]) {
|
|
fprintf(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)
|
|
fprintf(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;
|
|
int 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)
|
|
fprintf(fp, "server=%s ", currenthost);
|
|
fprintf(fp, "No association ID's returned\n");
|
|
return 0;
|
|
}
|
|
|
|
if (dsize & 0x3) {
|
|
if (numhosts > 1)
|
|
fprintf(stderr, "server=%s ", currenthost);
|
|
fprintf(stderr,
|
|
"***Server returned %d 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) {
|
|
fprintf(stderr, "[%u] ",
|
|
assoc_cache[numassoc].assid);
|
|
}
|
|
numassoc++;
|
|
}
|
|
if (debug) {
|
|
fprintf(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];
|
|
|
|
if (numassoc == 0) {
|
|
(void) fprintf(fp, "No association ID's in list\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Output a header
|
|
*/
|
|
(void) fprintf(fp,
|
|
"\nind assid status conf reach auth condition last_event cnt\n");
|
|
(void) fprintf(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 = "outlyer";
|
|
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;
|
|
|
|
default:
|
|
last_event = "";
|
|
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';
|
|
fprintf(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;
|
|
int 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)
|
|
fprintf(fp, "(no response message, curiously)");
|
|
else
|
|
fprintf(fp, "%.*s", dsize, datap);
|
|
}
|
|
|
|
|
|
#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) fprintf(fp, "server=%s ", currenthost);
|
|
if (dsize == 0) {
|
|
(void) fprintf(fp, "No radio status string returned\n");
|
|
return;
|
|
}
|
|
|
|
asciize(dsize, datap, fp);
|
|
}
|
|
#endif /* UNUSED */
|
|
|
|
/*
|
|
* when - print 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;
|
|
|
|
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, "%ld", diff);
|
|
return buf;
|
|
}
|
|
|
|
diff = (diff + 29) / 60;
|
|
if (diff <= 300) {
|
|
snprintf(buf, cb, "%ldm", diff);
|
|
return buf;
|
|
}
|
|
|
|
diff = (diff + 29) / 60;
|
|
if (diff <= 96) {
|
|
snprintf(buf, cb, "%ldh", diff);
|
|
return buf;
|
|
}
|
|
|
|
diff = (diff + 11) / 24;
|
|
snprintf(buf, cb, "%ldd", diff);
|
|
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,
|
|
int datalen,
|
|
const char *data,
|
|
FILE *fp,
|
|
int af
|
|
)
|
|
{
|
|
char *name;
|
|
char *value = NULL;
|
|
int c;
|
|
int 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;
|
|
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;
|
|
char type = '?';
|
|
char whenbuf[8], pollbuf[8];
|
|
char clock_name[LENHOSTNAME];
|
|
|
|
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)) {
|
|
if (!strcmp("srcadr", name) ||
|
|
!strcmp("peeradr", name)) {
|
|
if (!decodenetnum(value, &srcadr))
|
|
fprintf(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) {
|
|
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) {
|
|
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);
|
|
//fprintf(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);
|
|
else {
|
|
char *buf = emalloc(10);
|
|
int i = ntohl(refidadr.sa4.sin_addr.s_addr);
|
|
|
|
snprintf(buf, 10,
|
|
"%0x", i);
|
|
dstadr_refid = buf;
|
|
//fprintf(stderr, "apeervarlist refid: value=<%x>\n", i);
|
|
}
|
|
//fprintf(stderr, "apeervarlist refid: value=<%s>\n", value);
|
|
} 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 {
|
|
// fprintf(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;
|
|
}
|
|
fprintf(fp, "%-*s ", (int)maxhostlen, serverlocal);
|
|
}
|
|
if (AF_UNSPEC == af || AF(&srcadr) == af) {
|
|
if (!have_srchost)
|
|
strlcpy(clock_name, nntohost(&srcadr),
|
|
sizeof(clock_name));
|
|
if (wideremote && 15 < strlen(clock_name))
|
|
fprintf(fp, "%c%s\n ", c, clock_name);
|
|
else
|
|
fprintf(fp, "%c%-15.15s ", c, clock_name);
|
|
if (!have_da_rid) {
|
|
drlen = 0;
|
|
} else {
|
|
drlen = strlen(dstadr_refid);
|
|
makeascii(drlen, dstadr_refid, fp);
|
|
}
|
|
if (pvl == apeervarlist) {
|
|
while (drlen++ < 9)
|
|
fputc(' ', fp);
|
|
fprintf(fp, "%-6d", associd);
|
|
} else {
|
|
while (drlen++ < 15)
|
|
fputc(' ', fp);
|
|
}
|
|
fprintf(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, lfptoms(&estdelay, 3),
|
|
lfptoms(&estoffset, 3),
|
|
(have_jitter)
|
|
? lfptoms(&estjitter, 3)
|
|
: lfptoms(&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;
|
|
int 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)
|
|
fprintf(stderr, "server=%s ", currenthost);
|
|
fprintf(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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
if (numhosts > 1)
|
|
fprintf(fp, "%-*.*s ", (int)maxhostlen, (int)maxhostlen,
|
|
"server (local)");
|
|
fprintf(fp,
|
|
" remote refid st t when poll reach delay offset jitter\n");
|
|
if (numhosts > 1)
|
|
for (u = 0; u <= maxhostlen; u++)
|
|
fprintf(fp, "=");
|
|
fprintf(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)
|
|
fprintf(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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
if (numhosts > 1)
|
|
fprintf(fp, "%-*.*s ", (int)maxhostlen, (int)maxhostlen,
|
|
"server (local)");
|
|
fprintf(fp,
|
|
" remote refid assid st t when poll reach delay offset jitter\n");
|
|
if (numhosts > 1)
|
|
for (u = 0; u <= maxhostlen; u++)
|
|
fprintf(fp, "=");
|
|
fprintf(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)
|
|
fprintf(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
|
|
)
|
|
{
|
|
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;
|
|
|
|
for (i = 0; i < numhosts; ++i) {
|
|
if (getnetnum(chosts[i].name, &netnum, fullname, af))
|
|
if (strlen(fullname) > maxhostlen)
|
|
maxhostlen = strlen(fullname);
|
|
}
|
|
if (numhosts > 1)
|
|
fprintf(fp, "%-*.*s ", (int)maxhostlen, (int)maxhostlen,
|
|
"server");
|
|
fprintf(fp,
|
|
" remote local st t when poll reach delay offset disp\n");
|
|
if (numhosts > 1)
|
|
for (i = 0; i <= maxhostlen; ++i)
|
|
fprintf(fp, "=");
|
|
fprintf(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;
|
|
int rsize;
|
|
const char *rdata;
|
|
char *resp;
|
|
int res;
|
|
int col;
|
|
int i;
|
|
|
|
cfgcmd = pcmd->argval[0].string;
|
|
|
|
if (debug > 2)
|
|
fprintf(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) {
|
|
printf("______"); /* "ntpq> " */
|
|
printf("________"); /* ":config " */
|
|
} else
|
|
printf("%s\n", cfgcmd);
|
|
for (i = 1; i < col; i++)
|
|
putchar('_');
|
|
printf("^\n");
|
|
}
|
|
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!!)
|
|
*/
|
|
static void
|
|
config_from_file (
|
|
struct parse *pcmd,
|
|
FILE *fp
|
|
)
|
|
{
|
|
u_short rstatus;
|
|
int rsize;
|
|
const char *rdata;
|
|
int res;
|
|
FILE *config_fd;
|
|
char config_cmd[MAXLINE];
|
|
size_t config_len;
|
|
int i;
|
|
int retry_limit;
|
|
|
|
if (debug > 2)
|
|
fprintf(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) {
|
|
config_len = strlen(config_cmd);
|
|
/* ensure even the last line has newline, if possible */
|
|
if (config_len > 0 &&
|
|
config_len + 2 < sizeof(config_cmd) &&
|
|
'\n' != config_cmd[config_len - 1])
|
|
config_cmd[config_len++] = '\n';
|
|
++i;
|
|
retry_limit = 2;
|
|
do
|
|
res = doquery(CTL_OP_CONFIGURE, 0, 1,
|
|
strlen(config_cmd), config_cmd,
|
|
&rstatus, &rsize, &rdata);
|
|
while (res != 0 && retry_limit--);
|
|
if (res != 0) {
|
|
printf("Line No: %d query failed: %s", i,
|
|
config_cmd);
|
|
printf("Subsequent lines not sent.\n");
|
|
fclose(config_fd);
|
|
return;
|
|
}
|
|
|
|
if (rsize > 0 && '\n' == rdata[rsize - 1])
|
|
rsize--;
|
|
if (rsize > 0 && '\r' == rdata[rsize - 1])
|
|
rsize--;
|
|
printf("Line No: %d %.*s: %s", i, rsize, rdata,
|
|
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;
|
|
int rsize;
|
|
const char * rdata;
|
|
int 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) {
|
|
fprintf(stderr, "nonce request failed\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if ((size_t)rsize <= sizeof(nonce_eq) - 1 ||
|
|
strncmp(rdata, nonce_eq, sizeof(nonce_eq) - 1)) {
|
|
fprintf(stderr, "unexpected nonce response format: %.*s\n",
|
|
rsize, rdata);
|
|
return FALSE;
|
|
}
|
|
chars = rsize - (sizeof(nonce_eq) - 1);
|
|
if (chars >= (int)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)) {
|
|
fprintf(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);
|
|
NTP_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)
|
|
|
|
|
|
void
|
|
mrulist_ctrl_c_hook(void)
|
|
{
|
|
mrulist_interrupted = 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;
|
|
int chars;
|
|
int qres;
|
|
u_short rstatus;
|
|
int 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);
|
|
NTP_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);
|
|
mrulist_interrupted = FALSE;
|
|
set_ctrl_c_hook(&mrulist_ctrl_c_hook);
|
|
fprintf(stderr,
|
|
"Ctrl-C will stop MRU retrieval and display partial results.\n");
|
|
fflush(stderr);
|
|
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)
|
|
fprintf(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)
|
|
fprintf(stderr,
|
|
"no overlap between %d prior entries and server MRU list\n",
|
|
ri);
|
|
while (ri--) {
|
|
recent = HEAD_DLIST(mru_list, mlink);
|
|
NTP_INSIST(recent != NULL);
|
|
if (debug)
|
|
fprintf(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);
|
|
NTP_INSIST(unlinked == recent);
|
|
free(recent);
|
|
mru_count--;
|
|
}
|
|
if (NULL == HEAD_DLIST(mru_list, mlink)) {
|
|
restarted_count++;
|
|
if (restarted_count > 8) {
|
|
fprintf(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)
|
|
fprintf(stderr,
|
|
"---> Restarting from the beginning, retry #%u\n",
|
|
restarted_count);
|
|
}
|
|
} else if (CERR_UNKNOWNVAR == qres) {
|
|
fprintf(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)
|
|
fprintf(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)
|
|
fprintf(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)
|
|
fprintf(stderr,
|
|
"Frag limit reduced to %d following incomplete response.\n",
|
|
frags);
|
|
} else {
|
|
limit = max(2, limit / 2);
|
|
if (debug)
|
|
fprintf(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)) {
|
|
if (debug > 1)
|
|
fprintf(stderr, "nextvar gave: %s = %s\n",
|
|
tag, val);
|
|
switch(tag[0]) {
|
|
|
|
case 'a':
|
|
if (!strcmp(tag, "addr.older")) {
|
|
if (!have_last_older) {
|
|
fprintf(stderr,
|
|
"addr.older %s before last.older\n",
|
|
val);
|
|
goto cleanup_return;
|
|
}
|
|
if (!decodenetnum(val, &addr_older)) {
|
|
fprintf(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) {
|
|
fprintf(stderr,
|
|
"addr.older %s not in hash table\n",
|
|
val);
|
|
goto cleanup_return;
|
|
}
|
|
if (!L_ISEQU(&last_older,
|
|
&recent->last)) {
|
|
fprintf(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)) {
|
|
fprintf(stderr,
|
|
"last.older %s garbled\n",
|
|
val);
|
|
goto cleanup_return;
|
|
}
|
|
have_last_older = TRUE;
|
|
} else if (!strcmp(tag, "last.newest")) {
|
|
if (0 != got) {
|
|
fprintf(stderr,
|
|
"last.newest %s before complete row, got = 0x%x\n",
|
|
val, (u_int)got);
|
|
goto cleanup_return;
|
|
}
|
|
if (!have_now) {
|
|
fprintf(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)) {
|
|
fprintf(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) {
|
|
NTP_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) {
|
|
fprintf(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;
|
|
fprintf(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 (REQ_ROOM - chars < 1)
|
|
break;
|
|
memcpy(req, buf, chars + 1);
|
|
req += chars;
|
|
}
|
|
}
|
|
|
|
set_ctrl_c_hook(NULL);
|
|
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;
|
|
|
|
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
|
|
fprintf(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)
|
|
NTP_INSIST(ppentry < sorted + mru_count);
|
|
*ppentry = recent;
|
|
ppentry++;
|
|
ITER_DLIST_END()
|
|
} else {
|
|
REV_ITER_DLIST_BEGIN(mru_list, recent, mlink, mru)
|
|
NTP_INSIST(ppentry < sorted + mru_count);
|
|
*ppentry = recent;
|
|
ppentry++;
|
|
REV_ITER_DLIST_END()
|
|
}
|
|
|
|
if (ppentry - sorted != (int)mru_count) {
|
|
fprintf(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]);
|
|
|
|
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);
|
|
fprintf(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);
|
|
}
|
|
fflush(fp);
|
|
if (debug) {
|
|
fprintf(stderr,
|
|
"--- completed, freeing sorted[] pointers\n");
|
|
fflush(stderr);
|
|
}
|
|
free(sorted);
|
|
|
|
cleanup_return:
|
|
if (debug) {
|
|
fprintf(stderr, "... freeing MRU entries\n");
|
|
fflush(stderr);
|
|
}
|
|
ITER_DLIST_BEGIN(mru_list, recent, mlink, mru)
|
|
free(recent);
|
|
ITER_DLIST_END()
|
|
if (debug) {
|
|
fprintf(stderr, "... freeing hash_table[]\n");
|
|
fflush(stderr);
|
|
}
|
|
free(hash_table);
|
|
hash_table = NULL;
|
|
INIT_DLIST(mru_list, mlink);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
fprintf(fp, "Warning: incomplete row with %d (of %d) fields",
|
|
*pfields, IFSTATS_FIELDS);
|
|
*pfields = 0;
|
|
prow->ifnum = ifnum;
|
|
return;
|
|
}
|
|
fprintf(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");
|
|
*/
|
|
fprintf(fp,
|
|
"%3u %-24.24s %c %4x %3d %2d %6d %6d %6d %5d %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))
|
|
fprintf(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;
|
|
int 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;
|
|
|
|
fprintf(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)) {
|
|
if (debug > 1)
|
|
fprintf(stderr, "nextvar gave: %s = %s\n", tag,
|
|
(NULL == val)
|
|
? ""
|
|
: 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) &&
|
|
(NULL == 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, "%d", &row.mcast_count))
|
|
comprende = TRUE;
|
|
break;
|
|
|
|
case 'n':
|
|
if (1 == sscanf(tag, name_fmt, &ui)) {
|
|
/* strip quotes */
|
|
INSIST(val);
|
|
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, "%d", &row.peer_count))
|
|
comprende = TRUE;
|
|
break;
|
|
|
|
case 'r':
|
|
if (1 == sscanf(tag, rx_fmt, &ui) &&
|
|
1 == sscanf(val, "%d", &row.received))
|
|
comprende = TRUE;
|
|
break;
|
|
|
|
case 't':
|
|
if (1 == sscanf(tag, tl_fmt, &ui) &&
|
|
1 == sscanf(val, "%d", &row.ttl))
|
|
comprende = TRUE;
|
|
else if (1 == sscanf(tag, tx_fmt, &ui) &&
|
|
1 == sscanf(val, "%d", &row.sent))
|
|
comprende = TRUE;
|
|
else if (1 == sscanf(tag, txerr_fmt, &ui) &&
|
|
1 == sscanf(val, "%d", &row.send_errors))
|
|
comprende = TRUE;
|
|
break;
|
|
|
|
case 'u':
|
|
if (1 == sscanf(tag, up_fmt, &ui) &&
|
|
1 == sscanf(val, "%d", &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)
|
|
fprintf(fp, "Warning: incomplete row with %d (of %d) fields",
|
|
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)
|
|
fprintf(fp, "Warning: incomplete row with %d (of %d) fields",
|
|
*pfields, RESLIST_FIELDS);
|
|
*pfields = 0;
|
|
prow->idx = idx;
|
|
return;
|
|
}
|
|
fprintf(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");
|
|
*/
|
|
fprintf(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;
|
|
int 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;
|
|
|
|
fprintf(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)) {
|
|
if (debug > 1)
|
|
fprintf(stderr, "nextvar gave: %s = %s\n", tag,
|
|
(NULL == val)
|
|
? ""
|
|
: 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 {
|
|
len = strlen(val);
|
|
memcpy(row.flagstr, val, len);
|
|
row.flagstr[len] = '\0';
|
|
comprende = TRUE;
|
|
}
|
|
}
|
|
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)
|
|
fprintf(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;
|
|
int rsize;
|
|
const char *rdata;
|
|
int qres;
|
|
char *tag;
|
|
char *val;
|
|
u_int n;
|
|
size_t len;
|
|
int match;
|
|
u_long ul;
|
|
int vtype;
|
|
|
|
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)) {
|
|
if (NULL == val)
|
|
continue;
|
|
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_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))
|
|
fprintf(stderr, "malformed %s=%s\n",
|
|
pvdc->tag, val);
|
|
break;
|
|
|
|
case NTP_ADD:
|
|
if (0 == n) { /* adr */
|
|
if (!decodenetnum(val, &pvdc->v.sau))
|
|
fprintf(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;
|
|
fprintf(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) {
|
|
fprintf(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:
|
|
fprintf(fp, "%s %s\n", pvdc->display,
|
|
nntohostp(&pvdc->v.sau));
|
|
break;
|
|
|
|
case NTP_LFP:
|
|
fprintf(fp, "%s %s\n", pvdc->display,
|
|
prettydate(&pvdc->v.lfp));
|
|
break;
|
|
|
|
case NTP_MODE:
|
|
atouint(pvdc->v.str, &ul);
|
|
fprintf(fp, "%s %s\n", pvdc->display,
|
|
modetoa((int)ul));
|
|
break;
|
|
|
|
case NTP_2BIT:
|
|
atouint(pvdc->v.str, &ul);
|
|
fprintf(fp, "%s %s\n", pvdc->display,
|
|
leapbits[ul & 0x3]);
|
|
break;
|
|
|
|
default:
|
|
fprintf(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),
|
|
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_STR),
|
|
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);
|
|
}
|