/*      $NetBSD: ntpq.c,v 1.23 2024/08/18 20:47:19 christos Exp $       */

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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



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


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

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


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



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


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

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

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


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

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

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

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

/*
* When multiple hosts are specified.
*/

u_int numhosts;

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

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

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

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

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

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

char const *progname;

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

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

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


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


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

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

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

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

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

       {
           char *list;
           char *msg;

           list = list_digest_names();

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

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

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

       progname = argv[0];

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

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

       debug = OPT_VALUE_SET_DEBUG_LEVEL;

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

       if (HAVE_OPT(INTERACTIVE))
               interactive = 1;

       if (HAVE_OPT(NUMERIC))
               showhostnames = 0;

       if (HAVE_OPT(WIDE))
               wideremote = 1;

       old_rv = HAVE_OPT(OLD_RV);

       drefid = OPT_VALUE_REFID;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

       return 1;
}


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

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

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

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

               do {
                       ch = *cdata++;

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

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

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


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

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

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

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

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

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

       numfrags = 0;
       seenlastfrag = 0;

       tobase = (uint32_t)time(NULL);

       FD_ZERO(&fds);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

       pktsize = CTL_HEADER_LEN;

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

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

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

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

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

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


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

       switch (m6resp) {

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

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

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

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

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

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

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

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

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

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

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

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


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

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

       done = 0;
       sequence++;

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

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

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

       }
       return res;
}


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

       ntp_readline_init(interactive ? prompt : NULL);

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

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


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


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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

       return 0;
}


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


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

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


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

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

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

       return buf;
}

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

       cal.yearday = 0;

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

       return rtdatetolfp(str, lfp);
}


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


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


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


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

       *narr = 0;

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

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


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

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

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

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

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

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

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


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

       return strcmp(*name1, *name2);
}


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

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

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


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

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


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

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

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

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


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

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

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


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


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


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


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

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

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

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

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

       info_auth_keytype = key_type;
       info_auth_hashlen = digest_len;
}


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

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


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



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


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


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

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


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


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


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


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


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

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


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

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

       return (u_long) atoi(pbuf);
}


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

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

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

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

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

#undef ONEOUT
}


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

       data_u_char = (const u_char *)data;

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

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


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


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


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

               return out;
       }

       return src;
}


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


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

               return out;
       }

       return src;
}


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

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

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

/*
* nextvar parsing helpers
*/

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

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

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

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

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

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

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

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

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

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

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

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

#   else  /*defined(BUILD_AS_LIB)*/

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

#   endif /*defined(BUILD_AS_LIB)*/
}

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

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

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

       cpend = *datap + *datalen;

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

       return PADDING;
}


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


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

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

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

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


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


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


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

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

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

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


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


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

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

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

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

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

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

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

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

       return s;

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

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

       UNUSED_ARG(datatype);

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

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

               case PADDING:
                       output_raw = '*';
                       break;

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

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

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

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

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

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

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

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

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

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


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


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

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


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

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


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

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

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

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

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

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

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


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

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

       /* Discard MACs that NTP won't accept. */
       /* Keep this consistent with keytype_from_text() in ssl_init.c. */
       if ((size_t)EVP_MD_size(m) > MAX_MDG_LEN) {
               return;
       }

       name = EVP_MD_name(m);
       len = strlen(name) + 1;

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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



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

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

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

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

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

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

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