/* * ntpq - query an NTP server using mode 6 commands */ #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_FCNTL_H # include #endif #ifdef SYS_WINNT # include # define PATH_DEVNULL "NUL:" #else # define PATH_DEVNULL "/dev/null" #endif #include #include #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 # define CMAC "AES128CMAC" # endif #endif #include #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) * + + *--------------- * 68 bytes */ static const char s_xdig[16] = "0123456789ABCDEF"; char lbuf[68]; int ch, rowlen; const u_char * cdata = data; char *xptr, *pptr; while (len) { memset(lbuf, ' ', sizeof(lbuf)); xptr = lbuf; pptr = lbuf + 3*16 + 2; rowlen = (len > 16) ? 16 : (int)len; len -= rowlen; do { ch = *cdata++; *xptr++ = s_xdig[ch >> 4 ]; *xptr++ = s_xdig[ch & 0x0F]; if (++xptr == lbuf + 3*8) ++xptr; *pptr++ = isprint(ch) ? (char)ch : '.'; } while (--rowlen); *pptr++ = '\n'; *pptr = '\0'; fputs(lbuf, stdout); } } /* XXX ELIMINATE sendpkt similar in ntpq.c, ntpdc.c, ntp_io.c, ntptrace.c */ /* * sendpkt - send a packet to the remote host */ static int sendpkt( void * xdata, size_t xdatalen ) { if (debug >= 3) printf("Sending %zu octets\n", xdatalen); if (send(sockfd, xdata, xdatalen, 0) == -1) { warning("write to %s failed", currenthost); return -1; } if (debug >= 4) { printf("Request packet:\n"); dump_hex_printable(xdata, xdatalen); } return 0; } /* * getresponse - get a (series of) response packet(s) and return the data */ static int getresponse( int opcode, int associd, u_short *rstatus, size_t *rsize, const char **rdata, int timeo ) { struct ntp_control rpkt; struct sock_timeval tvo; u_short offsets[MAXFRAGS+1]; u_short counts[MAXFRAGS+1]; u_short offset; u_short count; size_t numfrags; size_t f; size_t ff; int seenlastfrag; int shouldbesize; fd_set fds; int n; int errcode; /* absolute timeout checks. Not 'time_t' by intention! */ uint32_t tobase; /* base value for timeout */ uint32_t tospan; /* timeout span (max delay) */ uint32_t todiff; /* current delay */ memset(offsets, 0, sizeof(offsets)); memset(counts , 0, sizeof(counts )); /* * This is pretty tricky. We may get between 1 and MAXFRAG packets * back in response to the request. We peel the data out of * each packet and collect it in one long block. When the last * packet in the sequence is received we'll know how much data we * should have had. Note we use one long time out, should reconsider. */ *rsize = 0; if (rstatus) *rstatus = 0; *rdata = (char *)pktdata; numfrags = 0; seenlastfrag = 0; tobase = (uint32_t)time(NULL); FD_ZERO(&fds); /* * Loop until we have an error or a complete response. Nearly all * code paths to loop again use continue. */ for (;;) { if (numfrags == 0) tvo = tvout; else tvo = tvsout; tospan = (uint32_t)tvo.tv_sec + (tvo.tv_usec != 0); FD_SET(sockfd, &fds); n = select(sockfd+1, &fds, NULL, NULL, &tvo); if (n == -1) { #if !defined(SYS_WINNT) && defined(EINTR) /* Windows does not know about EINTR (until very * recently) and the handling of console events * is *very* different from POSIX/UNIX signal * handling anyway. * * Under non-windows targets we map EINTR as * 'last packet was received' and try to exit * the receive sequence. */ if (errno == EINTR) { seenlastfrag = 1; goto maybe_final; } #endif warning("select fails"); return -1; } /* * Check if this is already too late. Trash the data and * fake a timeout if this is so. */ todiff = (((uint32_t)time(NULL)) - tobase) & 0x7FFFFFFFu; if ((n > 0) && (todiff > tospan)) { n = recv(sockfd, (char *)&rpkt, sizeof(rpkt), 0); n -= n; /* faked timeout return from 'select()', * execute RMW cycle on 'n' */ } if (n <= 0) { /* * Timed out. Return what we have */ if (numfrags == 0) { if (timeo) fprintf(stderr, "%s: timed out, nothing received\n", currenthost); return ERR_TIMEOUT; } if (timeo) fprintf(stderr, "%s: timed out with incomplete data\n", currenthost); if (debug) { fprintf(stderr, "ERR_INCOMPLETE: Received fragments:\n"); for (f = 0; f < numfrags; f++) fprintf(stderr, "%2u: %5d %5d\t%3d octets\n", (u_int)f, offsets[f], offsets[f] + counts[f], counts[f]); fprintf(stderr, "last fragment %sreceived\n", (seenlastfrag) ? "" : "not "); } return ERR_INCOMPLETE; } n = recv(sockfd, (char *)&rpkt, sizeof(rpkt), 0); if (n < 0) { warning("read"); return -1; } if (debug >= 4) { printf("Response packet:\n"); dump_hex_printable(&rpkt, n); } /* * Check for format errors. Bug proofing. */ if (n < (int)CTL_HEADER_LEN) { if (debug) printf("Short (%d byte) packet received\n", n); continue; } if (PKT_VERSION(rpkt.li_vn_mode) > NTP_VERSION || PKT_VERSION(rpkt.li_vn_mode) < NTP_OLDVERSION) { if (debug) printf("Packet received with version %d\n", PKT_VERSION(rpkt.li_vn_mode)); continue; } if (PKT_MODE(rpkt.li_vn_mode) != MODE_CONTROL) { if (debug) printf("Packet received with mode %d\n", PKT_MODE(rpkt.li_vn_mode)); continue; } if (!CTL_ISRESPONSE(rpkt.r_m_e_op)) { if (debug) printf("Received request packet, wanted response\n"); continue; } /* * Check opcode and sequence number for a match. * Could be old data getting to us. */ if (ntohs(rpkt.sequence) != sequence) { if (debug) printf("Received sequnce number %d, wanted %d\n", ntohs(rpkt.sequence), sequence); continue; } if (CTL_OP(rpkt.r_m_e_op) != opcode) { if (debug) printf( "Received opcode %d, wanted %d (sequence number okay)\n", CTL_OP(rpkt.r_m_e_op), opcode); continue; } /* * Check the error code. If non-zero, return it. */ if (CTL_ISERROR(rpkt.r_m_e_op)) { errcode = (ntohs(rpkt.status) >> 8) & 0xff; if (CTL_ISMORE(rpkt.r_m_e_op)) TRACE(1, ("Error code %d received on not-final packet\n", errcode)); if (errcode == CERR_UNSPEC) return ERR_UNSPEC; return errcode; } /* * Check the association ID to make sure it matches what * we sent. */ if (ntohs(rpkt.associd) != associd) { TRACE(1, ("Association ID %d doesn't match expected %d\n", ntohs(rpkt.associd), associd)); /* * Hack for silly fuzzballs which, at the time of writing, * return an assID of sys.peer when queried for system variables. */ #ifdef notdef continue; #endif } /* * Collect offset and count. Make sure they make sense. */ offset = ntohs(rpkt.offset); count = ntohs(rpkt.count); /* * validate received payload size is padded to next 32-bit * boundary and no smaller than claimed by rpkt.count */ if (n & 0x3) { TRACE(1, ("Response packet not padded, size = %d\n", n)); continue; } shouldbesize = (CTL_HEADER_LEN + count + 3) & ~3; if (n < shouldbesize) { printf("Response packet claims %u octets payload, above %ld received\n", count, (long)(n - CTL_HEADER_LEN)); return ERR_INCOMPLETE; } if (debug >= 3 && shouldbesize > n) { u_int32 key; u_int32 *lpkt; int maclen; /* * Usually we ignore authentication, but for debugging purposes * we watch it here. */ /* round to 8 octet boundary */ shouldbesize = (shouldbesize + 7) & ~7; maclen = n - shouldbesize; if (maclen >= (int)MIN_MAC_LEN) { printf( "Packet shows signs of authentication (total %d, data %d, mac %d)\n", n, shouldbesize, maclen); lpkt = (u_int32 *)&rpkt; printf("%08lx %08lx %08lx %08lx %08lx %08lx\n", (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) - 3]), (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) - 2]), (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) - 1]), (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32)]), (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) + 1]), (u_long)ntohl(lpkt[(n - maclen)/sizeof(u_int32) + 2])); key = ntohl(lpkt[(n - maclen) / sizeof(u_int32)]); printf("Authenticated with keyid %lu\n", (u_long)key); if (key != 0 && key != info_auth_keyid) { printf("We don't know that key\n"); } else { if (authdecrypt(key, (u_int32 *)&rpkt, n - maclen, maclen)) { printf("Auth okay!\n"); } else { printf("Auth failed!\n"); } } } } TRACE(2, ("Got packet, size = %d\n", n)); if (count > (n - CTL_HEADER_LEN)) { TRACE(1, ("Received count of %u octets, data in packet is %ld\n", count, (long)n - CTL_HEADER_LEN)); continue; } if (count == 0 && CTL_ISMORE(rpkt.r_m_e_op)) { TRACE(1, ("Received count of 0 in non-final fragment\n")); continue; } if (offset + count > sizeof(pktdata)) { TRACE(1, ("Offset %u, count %u, too big for buffer\n", offset, count)); return ERR_TOOMUCH; } if (seenlastfrag && !CTL_ISMORE(rpkt.r_m_e_op)) { TRACE(1, ("Received second last fragment packet\n")); continue; } /* * So far, so good. Record this fragment, making sure it doesn't * overlap anything. */ TRACE(2, ("Packet okay\n")); if (numfrags > (MAXFRAGS - 1)) { TRACE(2, ("Number of fragments exceeds maximum %d\n", MAXFRAGS - 1)); return ERR_TOOMUCH; } /* * Find the position for the fragment relative to any * previously received. */ for (f = 0; f < numfrags && offsets[f] < offset; f++) { /* empty body */ ; } if (f < numfrags && offset == offsets[f]) { TRACE(1, ("duplicate %u octets at %u ignored, prior %u at %u\n", count, offset, counts[f], offsets[f])); continue; } if (f > 0 && (offsets[f-1] + counts[f-1]) > offset) { TRACE(1, ("received frag at %u overlaps with %u octet frag at %u\n", offset, counts[f-1], offsets[f-1])); continue; } if (f < numfrags && (offset + count) > offsets[f]) { TRACE(1, ("received %u octet frag at %u overlaps with frag at %u\n", count, offset, offsets[f])); continue; } for (ff = numfrags; ff > f; ff--) { offsets[ff] = offsets[ff-1]; counts[ff] = counts[ff-1]; } offsets[f] = offset; counts[f] = count; numfrags++; /* * Got that stuffed in right. Figure out if this was the last. * Record status info out of the last packet. */ if (!CTL_ISMORE(rpkt.r_m_e_op)) { seenlastfrag = 1; if (rstatus != 0) *rstatus = ntohs(rpkt.status); } /* * Copy the data into the data buffer, and bump the * timout base in case we need more. */ memcpy((char *)pktdata + offset, &rpkt.u, count); tobase = (uint32_t)time(NULL); /* * If we've seen the last fragment, look for holes in the sequence. * If there aren't any, we're done. */ #if !defined(SYS_WINNT) && defined(EINTR) maybe_final: #endif if (seenlastfrag && offsets[0] == 0) { for (f = 1; f < numfrags; f++) if (offsets[f-1] + counts[f-1] != offsets[f]) break; if (f == numfrags) { *rsize = offsets[f-1] + counts[f-1]; TRACE(1, ("%lu packets reassembled into response\n", (u_long)numfrags)); return 0; } } } /* giant for (;;) collecting response packets */ } /* getresponse() */ /* * sendrequest - format and send a request packet */ static int sendrequest( int opcode, associd_t associd, int auth, size_t qsize, const char *qdata ) { struct ntp_control qpkt; size_t pktsize; u_long key_id; char * pass; size_t maclen; /* * Check to make sure the data will fit in one packet */ if (qsize > CTL_MAX_DATA_LEN) { fprintf(stderr, "***Internal error! qsize (%zu) too large\n", qsize); return 1; } /* * Fill in the packet */ qpkt.li_vn_mode = PKT_LI_VN_MODE(0, pktversion, MODE_CONTROL); qpkt.r_m_e_op = (u_char)(opcode & CTL_OP_MASK); qpkt.sequence = htons(sequence); qpkt.status = 0; qpkt.associd = htons((u_short)associd); qpkt.offset = 0; qpkt.count = htons((u_short)qsize); pktsize = CTL_HEADER_LEN; /* * If we have data, copy and pad it out to a 32-bit boundary. */ if (qsize > 0) { memcpy(&qpkt.u, qdata, (size_t)qsize); pktsize += qsize; while (pktsize & (sizeof(u_int32) - 1)) { qpkt.u.data[qsize++] = 0; pktsize++; } } /* * If it isn't authenticated we can just send it. Otherwise * we're going to have to think about it a little. */ if (!auth && !always_auth) { return sendpkt(&qpkt, pktsize); } /* * Pad out packet to a multiple of 8 octets to be sure * receiver can handle it. */ while (pktsize & 7) { qpkt.u.data[qsize++] = 0; pktsize++; } /* * Get the keyid and the password if we don't have one. */ if (info_auth_keyid == 0) { key_id = getkeyid("Keyid: "); if (key_id == 0 || key_id > NTP_MAXKEY) { fprintf(stderr, "Invalid key identifier\n"); return 1; } info_auth_keyid = key_id; } if (!authistrusted(info_auth_keyid)) { pass = getpass_keytype(info_auth_keytype); if ('\0' == pass[0]) { fprintf(stderr, "Invalid password\n"); return 1; } authusekey(info_auth_keyid, info_auth_keytype, (u_char *)pass); authtrust(info_auth_keyid, 1); } /* * Do the encryption. */ maclen = authencrypt(info_auth_keyid, (void *)&qpkt, pktsize); if (!maclen) { fprintf(stderr, "Key not found\n"); return 1; } else if ((size_t)maclen != (info_auth_hashlen + sizeof(keyid_t))) { fprintf(stderr, "%zu octet MAC, %zu expected with %zu octet digest\n", maclen, (info_auth_hashlen + sizeof(keyid_t)), info_auth_hashlen); return 1; } return sendpkt((char *)&qpkt, pktsize + maclen); } /* * show_error_msg - display the error text for a mode 6 error response. */ void show_error_msg( int m6resp, associd_t associd ) { if (numhosts > 1) fprintf(stderr, "server=%s ", currenthost); switch (m6resp) { case CERR_BADFMT: fprintf(stderr, "***Server reports a bad format request packet\n"); break; case CERR_PERMISSION: fprintf(stderr, "***Server disallowed request (authentication?)\n"); break; case CERR_BADOP: fprintf(stderr, "***Server reports a bad opcode in request\n"); break; case CERR_BADASSOC: fprintf(stderr, "***Association ID %d unknown to server\n", associd); break; case CERR_UNKNOWNVAR: fprintf(stderr, "***A request variable unknown to the server\n"); break; case CERR_BADVALUE: fprintf(stderr, "***Server indicates a request variable was bad\n"); break; case ERR_UNSPEC: fprintf(stderr, "***Server returned an unspecified error\n"); break; case ERR_TIMEOUT: fprintf(stderr, "***Request timed out\n"); break; case ERR_INCOMPLETE: fprintf(stderr, "***Response from server was incomplete\n"); break; case ERR_TOOMUCH: fprintf(stderr, "***Buffer size exceeded for returned data\n"); break; default: fprintf(stderr, "***Server returns unknown error code %d\n", m6resp); } } /* * doquery - send a request and process the response, displaying * error messages for any error responses. */ int doquery( int opcode, associd_t associd, int auth, size_t qsize, const char *qdata, u_short *rstatus, size_t *rsize, const char **rdata ) { return doqueryex(opcode, associd, auth, qsize, qdata, rstatus, rsize, rdata, FALSE); } /* * doqueryex - send a request and process the response, optionally * displaying error messages for any error responses. */ int doqueryex( int opcode, associd_t associd, int auth, size_t qsize, const char *qdata, u_short *rstatus, size_t *rsize, const char **rdata, int quiet ) { int res; int done; /* * Check to make sure host is open */ if (!havehost) { fprintf(stderr, "***No host open, use `host' command\n"); return -1; } done = 0; sequence++; again: /* * send a request */ res = sendrequest(opcode, associd, auth, qsize, qdata); if (res != 0) return res; /* * Get the response. If we got a standard error, print a message */ res = getresponse(opcode, associd, rstatus, rsize, rdata, done); if (res > 0) { if (!done && (res == ERR_TIMEOUT || res == ERR_INCOMPLETE)) { if (res == ERR_INCOMPLETE) { /* * better bump the sequence so we don't * get confused about differing fragments. */ sequence++; } done = 1; goto again; } if (!quiet) show_error_msg(res, associd); } return res; } #ifndef BUILD_AS_LIB /* * getcmds - read commands from the standard input and execute them */ static void getcmds(void) { char * line; int count; ntp_readline_init(interactive ? prompt : NULL); for (;;) { line = ntp_readline(&count); if (NULL == line) break; docmd(line); free(line); } ntp_readline_uninit(); } #endif /* !BUILD_AS_LIB */ #if !defined(SYS_WINNT) && !defined(BUILD_AS_LIB) /* * abortcmd - catch interrupts and abort the current command */ static int abortcmd(void) { if (current_output == stdout) (void) fflush(stdout); putc('\n', stderr); (void) fflush(stderr); if (jump) { jump = 0; LONGJMP(interrupt_buf, 1); } return TRUE; } #endif /* !SYS_WINNT && !BUILD_AS_LIB */ #ifndef BUILD_AS_LIB /* * docmd - decode the command line and execute a command */ static void docmd( const char *cmdline ) { char *tokens[1+MAXARGS+2]; struct parse pcmd; int ntok; static int i; struct xcmd *xcmd; int executeonly = 0; /* * Tokenize the command line. If nothing on it, return. */ tokenize(cmdline, tokens, &ntok); if (ntok == 0) return; /* * If command prefixed by '~', then quiet output */ if (*tokens[0] == '~') { executeonly++; tokens[0]++; } /* * Find the appropriate command description. */ i = findcmd(tokens[0], builtins, opcmds, &xcmd); if (i == 0) { (void) fprintf(stderr, "***Command `%s' unknown\n", tokens[0]); return; } else if (i >= 2) { (void) fprintf(stderr, "***Command `%s' ambiguous\n", tokens[0]); return; } /* Warn about ignored extra args */ for (i = MAXARGS + 1; i < ntok ; ++i) { fprintf(stderr, "***Extra arg `%s' ignored\n", tokens[i]); } /* * Save the keyword, then walk through the arguments, interpreting * as we go. */ pcmd.keyword = tokens[0]; pcmd.nargs = 0; for (i = 0; i < MAXARGS && xcmd->arg[i] != NO; i++) { if ((i+1) >= ntok) { if (!(xcmd->arg[i] & OPT)) { printusage(xcmd, stderr); return; } break; } if ((xcmd->arg[i] & OPT) && (*tokens[i+1] == '>')) break; if (!getarg(tokens[i+1], (int)xcmd->arg[i], &pcmd.argval[i])) return; pcmd.nargs++; } i++; if (i < ntok && *tokens[i] == '>') { char *fname; if (*(tokens[i]+1) != '\0') fname = tokens[i]+1; else if ((i+1) < ntok) fname = tokens[i+1]; else { (void) fprintf(stderr, "***No file for redirect\n"); return; } current_output = fopen(fname, "w"); if (current_output == NULL) { (void) fprintf(stderr, "***Error opening %s: ", fname); perror(""); return; } } else if (executeonly) { /* Redirect all output to null */ current_output = fopen(PATH_DEVNULL, "w"); if (current_output == NULL) { (void) fprintf(stderr, "***Error redirecting output to /dev/null: "); perror(""); return; } } else { current_output = stdout; } if (interactive) { if ( ! SETJMP(interrupt_buf)) { jump = 1; (xcmd->handler)(&pcmd, current_output); jump = 0; } else { fflush(current_output); fputs("\n >>> command aborted <<<\n", stderr); fflush(stderr); } } else { jump = 0; (xcmd->handler)(&pcmd, current_output); } if ((NULL != current_output) && (stdout != current_output)) { (void)fclose(current_output); current_output = NULL; } } /* * tokenize - turn a command line into tokens * * SK: Modified to allow a quoted string * * HMS: If the first character of the first token is a ':' then (after * eating inter-token whitespace) the 2nd token is the rest of the line. */ static void tokenize( const char *line, char **tokens, int *ntok ) { register const char *cp; register char *sp; static char tspace[MAXLINE]; sp = tspace; cp = line; for (*ntok = 0; *ntok < MAXTOKENS; (*ntok)++) { tokens[*ntok] = sp; /* Skip inter-token whitespace */ while (ISSPACE(*cp)) cp++; /* If we're at EOL we're done */ if (ISEOL(*cp)) break; /* If this is the 2nd token and the first token begins * with a ':', then just grab to EOL. */ if (*ntok == 1 && tokens[0][0] == ':') { do { if (sp - tspace >= MAXLINE) goto toobig; *sp++ = *cp++; } while (!ISEOL(*cp)); } /* Check if this token begins with a double quote. * If yes, continue reading till the next double quote */ else if (*cp == '\"') { ++cp; do { if (sp - tspace >= MAXLINE) goto toobig; *sp++ = *cp++; } while ((*cp != '\"') && !ISEOL(*cp)); /* HMS: a missing closing " should be an error */ } else { do { if (sp - tspace >= MAXLINE) goto toobig; *sp++ = *cp++; } while ((*cp != '\"') && !ISSPACE(*cp) && !ISEOL(*cp)); /* HMS: Why check for a " in the previous line? */ } if (sp - tspace >= MAXLINE) goto toobig; *sp++ = '\0'; } return; toobig: *ntok = 0; fprintf(stderr, "***Line `%s' is too big\n", line); return; } /* * getarg - interpret an argument token */ static int getarg( const char *str, int code, arg_v *argp ) { u_long ul; switch (code & ~OPT) { case NTP_STR: argp->string = str; break; case NTP_ADD: if (!getnetnum(str, &argp->netnum, NULL, 0)) return 0; break; case NTP_UINT: if ('&' == str[0]) { if (!atouint(&str[1], &ul)) { fprintf(stderr, "***Association index `%s' invalid/undecodable\n", str); return 0; } if (0 == numassoc) { dogetassoc(stdout); if (0 == numassoc) { fprintf(stderr, "***No associations found, `%s' unknown\n", str); return 0; } } ul = min(ul, numassoc); argp->uval = assoc_cache[ul - 1].assid; break; } if (!atouint(str, &argp->uval)) { fprintf(stderr, "***Illegal unsigned value %s\n", str); return 0; } break; case NTP_INT: if (!atoint(str, &argp->ival)) { fprintf(stderr, "***Illegal integer value %s\n", str); return 0; } break; case IP_VERSION: if (!strcmp("-6", str)) { argp->ival = 6; } else if (!strcmp("-4", str)) { argp->ival = 4; } else { fprintf(stderr, "***Version must be either 4 or 6\n"); return 0; } break; } return 1; } #endif /* !BUILD_AS_LIB */ /* * findcmd - find a command in a command description table */ static int findcmd( const char * str, struct xcmd * clist1, struct xcmd * clist2, struct xcmd ** cmd ) { struct xcmd *cl; size_t clen; int nmatch; struct xcmd *nearmatch = NULL; struct xcmd *clist; clen = strlen(str); nmatch = 0; if (clist1 != 0) clist = clist1; else if (clist2 != 0) clist = clist2; else return 0; again: for (cl = clist; cl->keyword != 0; cl++) { /* do a first character check, for efficiency */ if (*str != *(cl->keyword)) continue; if (strncmp(str, cl->keyword, (unsigned)clen) == 0) { /* * Could be extact match, could be approximate. * Is exact if the length of the keyword is the * same as the str. */ if (*((cl->keyword) + clen) == '\0') { *cmd = cl; return 1; } nmatch++; nearmatch = cl; } } /* * See if there is more to do. If so, go again. Sorry about the * goto, too much looking at BSD sources... */ if (clist == clist1 && clist2 != 0) { clist = clist2; goto again; } /* * If we got extactly 1 near match, use it, else return number * of matches. */ if (nmatch == 1) { *cmd = nearmatch; return 1; } return nmatch; } /* * getnetnum - given a host name, return its net number * and (optional) full name */ int getnetnum( const char *hname, sockaddr_u *num, char *fullhost, int af ) { struct addrinfo hints, *ai = NULL; ZERO(hints); hints.ai_flags = AI_CANONNAME; #ifdef AI_ADDRCONFIG hints.ai_flags |= AI_ADDRCONFIG; #endif /* * decodenetnum only works with addresses, but handles syntax * that getaddrinfo doesn't: [2001::1]:1234 */ if (decodenetnum(hname, num)) { if (fullhost != NULL) getnameinfo(&num->sa, SOCKLEN(num), fullhost, LENHOSTNAME, NULL, 0, 0); return 1; } else if (getaddrinfo(hname, "ntp", &hints, &ai) == 0) { INSIST(sizeof(*num) >= ai->ai_addrlen); memcpy(num, ai->ai_addr, ai->ai_addrlen); if (fullhost != NULL) { if (ai->ai_canonname != NULL) strlcpy(fullhost, ai->ai_canonname, LENHOSTNAME); else getnameinfo(&num->sa, SOCKLEN(num), fullhost, LENHOSTNAME, NULL, 0, 0); } freeaddrinfo(ai); return 1; } fprintf(stderr, "***Can't find host %s\n", hname); return 0; } /* * nntohost - convert network number to host name. This routine enforces * the showhostnames setting. */ const char * nntohost( sockaddr_u *netnum ) { return nntohost_col(netnum, LIB_BUFLENGTH - 1, FALSE); } /* * nntohost_col - convert network number to host name in fixed width. * This routine enforces the showhostnames setting. * When displaying hostnames longer than the width, * the first part of the hostname is displayed. When * displaying numeric addresses longer than the width, * Such as IPv6 addresses, the caller decides whether * the first or last of the numeric address is used. */ const char * nntohost_col( sockaddr_u * addr, size_t width, int preserve_lowaddrbits ) { const char * out; if (!showhostnames || SOCK_UNSPEC(addr)) { if (preserve_lowaddrbits) out = trunc_left(stoa(addr), width); else out = trunc_right(stoa(addr), width); } else if (ISREFCLOCKADR(addr)) { out = refnumtoa(addr); } else { out = trunc_right(socktohost(addr), width); } return out; } /* * nntohostp() is the same as nntohost() plus a :port suffix */ const char * nntohostp( sockaddr_u *netnum ) { const char * hostn; char * buf; if (!showhostnames || SOCK_UNSPEC(netnum)) return sptoa(netnum); else if (ISREFCLOCKADR(netnum)) return refnumtoa(netnum); hostn = socktohost(netnum); LIB_GETBUF(buf); snprintf(buf, LIB_BUFLENGTH, "%s:%u", hostn, SRCPORT(netnum)); return buf; } /* * rtdatetolfp - decode an RT-11 date into an l_fp */ static int rtdatetolfp( char *str, l_fp *lfp ) { register char *cp; register int i; struct calendar cal; char buf[4]; cal.yearday = 0; /* * An RT-11 date looks like: * * d[d]-Mth-y[y] hh:mm:ss * * (No docs, but assume 4-digit years are also legal...) * * d[d]-Mth-y[y[y[y]]] hh:mm:ss */ cp = str; if (!isdigit(pgetc(cp))) { if (*cp == '-') { /* * Catch special case */ L_CLR(lfp); return 1; } return 0; } cal.monthday = (u_char) (*cp++ - '0'); /* ascii dependent */ if (isdigit(pgetc(cp))) { cal.monthday = (u_char)((cal.monthday << 3) + (cal.monthday << 1)); cal.monthday = (u_char)(cal.monthday + *cp++ - '0'); } if (*cp++ != '-') return 0; for (i = 0; i < 3; i++) buf[i] = *cp++; buf[3] = '\0'; for (i = 0; i < 12; i++) if (STREQ(buf, months[i])) break; if (i == 12) return 0; cal.month = (u_char)(i + 1); if (*cp++ != '-') return 0; if (!isdigit(pgetc(cp))) return 0; cal.year = (u_short)(*cp++ - '0'); if (isdigit(pgetc(cp))) { cal.year = (u_short)((cal.year << 3) + (cal.year << 1)); cal.year = (u_short)(*cp++ - '0'); } if (isdigit(pgetc(cp))) { cal.year = (u_short)((cal.year << 3) + (cal.year << 1)); cal.year = (u_short)(cal.year + *cp++ - '0'); } if (isdigit(pgetc(cp))) { cal.year = (u_short)((cal.year << 3) + (cal.year << 1)); cal.year = (u_short)(cal.year + *cp++ - '0'); } /* * Catch special case. If cal.year == 0 this is a zero timestamp. */ if (cal.year == 0) { L_CLR(lfp); return 1; } if (*cp++ != ' ' || !isdigit(pgetc(cp))) return 0; cal.hour = (u_char)(*cp++ - '0'); if (isdigit(pgetc(cp))) { cal.hour = (u_char)((cal.hour << 3) + (cal.hour << 1)); cal.hour = (u_char)(cal.hour + *cp++ - '0'); } if (*cp++ != ':' || !isdigit(pgetc(cp))) return 0; cal.minute = (u_char)(*cp++ - '0'); if (isdigit(pgetc(cp))) { cal.minute = (u_char)((cal.minute << 3) + (cal.minute << 1)); cal.minute = (u_char)(cal.minute + *cp++ - '0'); } if (*cp++ != ':' || !isdigit(pgetc(cp))) return 0; cal.second = (u_char)(*cp++ - '0'); if (isdigit(pgetc(cp))) { cal.second = (u_char)((cal.second << 3) + (cal.second << 1)); cal.second = (u_char)(cal.second + *cp++ - '0'); } /* * For RT-11, 1972 seems to be the pivot year */ if (cal.year < 72) cal.year += 2000; if (cal.year < 100) cal.year += 1900; /* check for complaints from 'caltontp()'! */ lfp->l_uf = 0; errno = 0; lfp->l_ui = caltontp(&cal); return (errno == 0); } /* * decodets - decode a timestamp into an l_fp format number, with * consideration of fuzzball formats. */ int decodets( char *str, l_fp *lfp ) { char *cp; char buf[30]; size_t b; /* * If it starts with a 0x, decode as hex. */ if (*str == '0' && (*(str+1) == 'x' || *(str+1) == 'X')) return hextolfp(str+2, lfp); /* * If it starts with a '"', try it as an RT-11 date. */ if (*str == '"') { cp = str + 1; b = 0; while ('"' != *cp && '\0' != *cp && b < COUNTOF(buf) - 1) buf[b++] = *cp++; buf[b] = '\0'; return rtdatetolfp(buf, lfp); } /* * Might still be hex. Check out the first character. Talk * about heuristics! */ if ((*str >= 'A' && *str <= 'F') || (*str >= 'a' && *str <= 'f')) return hextolfp(str, lfp); /* * Try it as a decimal. If this fails, try as an unquoted * RT-11 date. This code should go away eventually. */ if (atolfp(str, lfp)) return 1; return rtdatetolfp(str, lfp); } /* * decodetime - decode a time value. It should be in milliseconds */ int decodetime( char *str, l_fp *lfp ) { return mstolfp(str, lfp); } /* * decodeint - decode an integer */ int decodeint( char *str, long *val ) { if (*str == '0') { if (*(str+1) == 'x' || *(str+1) == 'X') return hextoint(str+2, (u_long *)val); return octtoint(str, (u_long *)val); } return atoint(str, val); } /* * decodeuint - decode an unsigned integer */ int decodeuint( char *str, u_long *val ) { if (*str == '0') { if (*(str + 1) == 'x' || *(str + 1) == 'X') return (hextoint(str + 2, val)); return (octtoint(str, val)); } return (atouint(str, val)); } /* * decodearr - decode an array of time values */ static int decodearr( char *cp, int *narr, l_fp *lfpa, int amax ) { char *bp; char buf[60]; *narr = 0; while (*narr < amax && *cp) { if (isspace(pgetc(cp))) { do ++cp; while (*cp && isspace(pgetc(cp))); } else { bp = buf; do { if (bp != (buf + sizeof(buf) - 1)) *bp++ = *cp; ++cp; } while (*cp && !isspace(pgetc(cp))); *bp = '\0'; if (!decodetime(buf, lfpa)) return 0; ++(*narr); ++lfpa; } } return 1; } /* * Finally, the built in command handlers */ /* * help - tell about commands, or details of a particular command */ static void help( struct parse *pcmd, FILE *fp ) { struct xcmd *xcp = NULL; /* quiet warning */ const char *cmd; const char *list[100]; size_t word, words; size_t row, rows; size_t col, cols; size_t length; if (pcmd->nargs == 0) { words = 0; for (xcp = builtins; xcp->keyword != NULL; xcp++) { if (*(xcp->keyword) != '?' && words < COUNTOF(list)) list[words++] = xcp->keyword; } for (xcp = opcmds; xcp->keyword != NULL; xcp++) if (words < COUNTOF(list)) list[words++] = xcp->keyword; qsort((void *)list, words, sizeof(list[0]), helpsort); col = 0; for (word = 0; word < words; word++) { length = strlen(list[word]); col = max(col, length); } cols = SCREENWIDTH / ++col; rows = (words + cols - 1) / cols; fprintf(fp, "ntpq commands:\n"); for (row = 0; row < rows; row++) { for (word = row; word < words; word += rows) fprintf(fp, "%-*.*s", (int)col, (int)col - 1, list[word]); fprintf(fp, "\n"); } } else { cmd = pcmd->argval[0].string; words = findcmd(cmd, builtins, opcmds, &xcp); if (words == 0) { fprintf(stderr, "Command `%s' is unknown\n", cmd); return; } else if (words >= 2) { fprintf(stderr, "Command `%s' is ambiguous\n", cmd); return; } fprintf(fp, "function: %s\n", xcp->comment); printusage(xcp, fp); } } /* * helpsort - do hostname qsort comparisons */ static int helpsort( const void *t1, const void *t2 ) { const char * const * name1 = t1; const char * const * name2 = t2; return strcmp(*name1, *name2); } /* * printusage - print usage information for a command */ static void printusage( struct xcmd *xcp, FILE *fp ) { register int i; /* XXX: Do we need to warn about extra args here too? */ (void) fprintf(fp, "usage: %s", xcp->keyword); for (i = 0; i < MAXARGS && xcp->arg[i] != NO; i++) { if (xcp->arg[i] & OPT) (void) fprintf(fp, " [ %s ]", xcp->desc[i]); else (void) fprintf(fp, " %s", xcp->desc[i]); } (void) fprintf(fp, "\n"); } /* * timeout - set time out time */ static void timeout( struct parse *pcmd, FILE *fp ) { int val; if (pcmd->nargs == 0) { val = (int)tvout.tv_sec * 1000 + tvout.tv_usec / 1000; (void) fprintf(fp, "primary timeout %d ms\n", val); } else { tvout.tv_sec = pcmd->argval[0].uval / 1000; tvout.tv_usec = (pcmd->argval[0].uval - ((long)tvout.tv_sec * 1000)) * 1000; } } /* * auth_delay - set delay for auth requests */ static void auth_delay( struct parse *pcmd, FILE *fp ) { int isneg; u_long val; if (pcmd->nargs == 0) { val = delay_time.l_ui * 1000 + delay_time.l_uf / 4294967; (void) fprintf(fp, "delay %lu ms\n", val); } else { if (pcmd->argval[0].ival < 0) { isneg = 1; val = (u_long)(-pcmd->argval[0].ival); } else { isneg = 0; val = (u_long)pcmd->argval[0].ival; } delay_time.l_ui = val / 1000; val %= 1000; delay_time.l_uf = val * 4294967; /* 2**32/1000 */ if (isneg) L_NEG(&delay_time); } } /* * host - set the host we are dealing with. */ static void host( struct parse *pcmd, FILE *fp ) { int i; if (pcmd->nargs == 0) { if (havehost) (void) fprintf(fp, "current host is %s\n", currenthost); else (void) fprintf(fp, "no current host\n"); return; } i = 0; ai_fam_templ = ai_fam_default; if (pcmd->nargs == 2) { if (!strcmp("-4", pcmd->argval[i].string)) ai_fam_templ = AF_INET; else if (!strcmp("-6", pcmd->argval[i].string)) ai_fam_templ = AF_INET6; else goto no_change; i = 1; } if (openhost(pcmd->argval[i].string, ai_fam_templ)) { fprintf(fp, "current host set to %s\n", currenthost); } else { no_change: if (havehost) fprintf(fp, "current host remains %s\n", currenthost); else fprintf(fp, "still no current host\n"); } } /* * poll - do one (or more) polls of the host via NTP */ /*ARGSUSED*/ static void ntp_poll( struct parse *pcmd, FILE *fp ) { (void) fprintf(fp, "poll not implemented yet\n"); } /* * showdrefid2str - return a string explanation of the value of drefid */ static const char * showdrefid2str(void) { switch (drefid) { case REFID_HASH: return "hash"; case REFID_IPV4: return "ipv4"; default: return "Unknown"; } } /* * drefid - display/change "display hash" */ static void showdrefid( struct parse *pcmd, FILE *fp ) { if (pcmd->nargs == 0) { (void) fprintf(fp, "drefid value is %s\n", showdrefid2str()); return; } else if (STREQ(pcmd->argval[0].string, "hash")) { drefid = REFID_HASH; } else if (STREQ(pcmd->argval[0].string, "ipv4")) { drefid = REFID_IPV4; } else { (void) fprintf(fp, "What?\n"); return; } (void) fprintf(fp, "drefid value set to %s\n", showdrefid2str()); } /* * keyid - get a keyid to use for authenticating requests */ static void keyid( struct parse *pcmd, FILE *fp ) { if (pcmd->nargs == 0) { if (info_auth_keyid == 0) (void) fprintf(fp, "no keyid defined\n"); else (void) fprintf(fp, "keyid is %lu\n", (u_long)info_auth_keyid); } else { /* allow zero so that keyid can be cleared. */ if(pcmd->argval[0].uval > NTP_MAXKEY) (void) fprintf(fp, "Invalid key identifier\n"); info_auth_keyid = pcmd->argval[0].uval; } } /* * keytype - get type of key to use for authenticating requests */ static void keytype( struct parse *pcmd, FILE *fp ) { const char * digest_name; size_t digest_len; int key_type; if (!pcmd->nargs) { fprintf(fp, "keytype is %s with %lu octet digests\n", keytype_name(info_auth_keytype), (u_long)info_auth_hashlen); return; } digest_name = pcmd->argval[0].string; digest_len = 0; key_type = keytype_from_text(digest_name, &digest_len); if (!key_type) { fprintf(fp, "keytype is not valid. " #ifdef OPENSSL "Type \"help keytype\" for the available digest types.\n"); #else "Only \"md5\" is available.\n"); #endif return; } info_auth_keytype = key_type; info_auth_hashlen = digest_len; } /* * passwd - get an authentication key */ /*ARGSUSED*/ static void passwd( struct parse *pcmd, FILE *fp ) { const char *pass; if (info_auth_keyid == 0) { info_auth_keyid = getkeyid("Keyid: "); if (info_auth_keyid == 0) { (void)fprintf(fp, "Keyid must be defined\n"); return; } } if (pcmd->nargs >= 1) pass = pcmd->argval[0].string; else { pass = getpass_keytype(info_auth_keytype); if ('\0' == pass[0]) { fprintf(fp, "Password unchanged\n"); return; } } authusekey(info_auth_keyid, info_auth_keytype, (const u_char *)pass); authtrust(info_auth_keyid, 1); } /* * hostnames - set the showhostnames flag */ static void hostnames( struct parse *pcmd, FILE *fp ) { if (pcmd->nargs == 0) { if (showhostnames) (void) fprintf(fp, "hostnames being shown\n"); else (void) fprintf(fp, "hostnames not being shown\n"); } else { if (STREQ(pcmd->argval[0].string, "yes")) showhostnames = 1; else if (STREQ(pcmd->argval[0].string, "no")) showhostnames = 0; else (void)fprintf(stderr, "What?\n"); } } /* * setdebug - set/change debugging level */ static void setdebug( struct parse *pcmd, FILE *fp ) { if (pcmd->nargs == 0) { (void) fprintf(fp, "debug level is %d\n", debug); return; } else if (STREQ(pcmd->argval[0].string, "no")) { debug = 0; } else if (STREQ(pcmd->argval[0].string, "more")) { debug++; } else if (STREQ(pcmd->argval[0].string, "less")) { debug--; } else { (void) fprintf(fp, "What?\n"); return; } (void) fprintf(fp, "debug level set to %d\n", debug); } /* * quit - stop this nonsense */ /*ARGSUSED*/ static void quit( struct parse *pcmd, FILE *fp ) { if (havehost) closesocket(sockfd); /* cleanliness next to godliness */ exit(0); } /* * version - print the current version number */ /*ARGSUSED*/ static void version( struct parse *pcmd, FILE *fp ) { (void) fprintf(fp, "%s\n", Version); return; } /* * raw - set raw mode output */ /*ARGSUSED*/ static void raw( struct parse *pcmd, FILE *fp ) { rawmode = 1; (void) fprintf(fp, "Output set to raw\n"); } /* * cooked - set cooked mode output */ /*ARGSUSED*/ static void cooked( struct parse *pcmd, FILE *fp ) { rawmode = 0; (void) fprintf(fp, "Output set to cooked\n"); return; } /* * authenticate - always authenticate requests to this host */ static void authenticate( struct parse *pcmd, FILE *fp ) { if (pcmd->nargs == 0) { if (always_auth) { (void) fprintf(fp, "authenticated requests being sent\n"); } else (void) fprintf(fp, "unauthenticated requests being sent\n"); } else { if (STREQ(pcmd->argval[0].string, "yes")) { always_auth = 1; } else if (STREQ(pcmd->argval[0].string, "no")) { always_auth = 0; } else (void)fprintf(stderr, "What?\n"); } } /* * ntpversion - choose the NTP version to use */ static void ntpversion( struct parse *pcmd, FILE *fp ) { if (pcmd->nargs == 0) { (void) fprintf(fp, "NTP version being claimed is %d\n", pktversion); } else { if (pcmd->argval[0].uval < NTP_OLDVERSION || pcmd->argval[0].uval > NTP_VERSION) { (void) fprintf(stderr, "versions %d to %d, please\n", NTP_OLDVERSION, NTP_VERSION); } else { pktversion = (u_char) pcmd->argval[0].uval; } } } static void __attribute__((__format__(__printf__, 1, 0))) vwarning(const char *fmt, va_list ap) { int serrno = errno; (void) fprintf(stderr, "%s: ", progname); vfprintf(stderr, fmt, ap); (void) fprintf(stderr, ": %s\n", strerror(serrno)); } /* * warning - print a warning message */ static void __attribute__((__format__(__printf__, 1, 2))) warning( const char *fmt, ... ) { va_list ap; va_start(ap, fmt); vwarning(fmt, ap); va_end(ap); } /* * error - print a message and exit */ static void __attribute__((__format__(__printf__, 1, 2))) error( const char *fmt, ... ) { va_list ap; va_start(ap, fmt); vwarning(fmt, ap); va_end(ap); exit(1); } /* * getkeyid - prompt the user for a keyid to use */ static u_long getkeyid( const char *keyprompt ) { int c; FILE *fi; char pbuf[20]; size_t i; size_t ilim; #ifndef SYS_WINNT if ((fi = fdopen(open("/dev/tty", 2), "r")) == NULL) #else if ((fi = _fdopen(open("CONIN$", _O_TEXT), "r")) == NULL) #endif /* SYS_WINNT */ fi = stdin; else setbuf(fi, (char *)NULL); fprintf(stderr, "%s", keyprompt); fflush(stderr); for (i = 0, ilim = COUNTOF(pbuf) - 1; i < ilim && (c = getc(fi)) != '\n' && c != EOF; ) pbuf[i++] = (char)c; pbuf[i] = '\0'; if (fi != stdin) fclose(fi); return (u_long) atoi(pbuf); } /* * atoascii - printable-ize possibly ascii data using the character * transformations cat -v uses. */ static void atoascii( const char *in, size_t in_octets, char *out, size_t out_octets ) { const u_char * pchIn; const u_char * pchInLimit; u_char * pchOut; u_char c; pchIn = (const u_char *)in; pchInLimit = pchIn + in_octets; pchOut = (u_char *)out; if (NULL == pchIn) { if (0 < out_octets) *pchOut = '\0'; return; } #define ONEOUT(c) \ do { \ if (0 == --out_octets) { \ *pchOut = '\0'; \ return; \ } \ *pchOut++ = (c); \ } while (0) for ( ; pchIn < pchInLimit; pchIn++) { c = *pchIn; if ('\0' == c) break; if (c & 0x80) { ONEOUT('M'); ONEOUT('-'); c &= 0x7f; } if (c < ' ') { ONEOUT('^'); ONEOUT((u_char)(c + '@')); } else if (0x7f == c) { ONEOUT('^'); ONEOUT('?'); } else ONEOUT(c); } ONEOUT('\0'); #undef ONEOUT } /* * makeascii - print possibly ascii data using the character * transformations that cat -v uses. */ void makeascii( size_t length, const char *data, FILE *fp ) { const u_char *data_u_char; const u_char *cp; int c; data_u_char = (const u_char *)data; for (cp = data_u_char; cp < data_u_char + length; cp++) { c = (int)*cp; if (c & 0x80) { putc('M', fp); putc('-', fp); c &= 0x7f; } if (c < ' ') { putc('^', fp); putc(c + '@', fp); } else if (0x7f == c) { putc('^', fp); putc('?', fp); } else putc(c, fp); } } /* * asciize - same thing as makeascii except add a newline */ void asciize( int length, char *data, FILE *fp ) { makeascii(length, data, fp); putc('\n', fp); } /* * truncate string to fit clipping excess at end. * "too long" -> "too l" * Used for hostnames. */ const char * trunc_right( const char * src, size_t width ) { size_t sl; char * out; sl = strlen(src); if (sl > width && LIB_BUFLENGTH - 1 > width && width > 0) { LIB_GETBUF(out); memcpy(out, src, width); out[width] = '\0'; return out; } return src; } /* * truncate string to fit by preserving right side and using '_' to hint * "too long" -> "_long" * Used for local IPv6 addresses, where low bits differentiate. */ const char * trunc_left( const char * src, size_t width ) { size_t sl; char * out; sl = strlen(src); if (sl > width && LIB_BUFLENGTH - 1 > width && width > 1) { LIB_GETBUF(out); out[0] = '_'; memcpy(&out[1], &src[sl + 1 - width], width); return out; } return src; } /* * Some circular buffer space */ #define CBLEN 80 #define NUMCB 6 char circ_buf[NUMCB][CBLEN]; int nextcb = 0; /* -------------------------------------------------------------------- * Parsing a response value list * * This sounds simple (and it actually is not really hard) but it has * some pitfalls. * * Rule1: CR/LF is never embedded in an item * Rule2: An item is a name, optionally followed by a value * Rule3: The value is separated from the name by a '=' * Rule4: Items are separated by a ',' * Rule5: values can be quoted by '"', in which case they can contain * arbitrary characters but *not* '"', CR and LF * * There are a few implementations out there that require a somewhat * relaxed attitude when parsing a value list, especially since we want * to copy names and values into local buffers. If these would overflow, * the item should be skipped without terminating the parsing sequence. * * Also, for empty values, there might be a '=' after the name or not; * we treat that equivalent. * * Parsing an item definitely breaks on a CR/LF. If an item is not * followed by a comma (','), parsing stops. In the middle of a quoted * character sequence CR/LF terminates the parsing finally without * returning a value. * * White space and other noise is ignored when parsing the data buffer; * only CR, LF, ',', '=' and '"' are characters with a special meaning. * White space is stripped from the names and values *after* working * through the buffer, before making the local copies. If whitespace * stripping results in an empty name, parsing resumes. */ /* * nextvar parsing helpers */ /* predicate: allowed chars inside a quoted string */ static int/*BOOL*/ cp_qschar(int ch) { return ch && (ch != '"' && ch != '\r' && ch != '\n'); } /* predicate: allowed chars inside an unquoted string */ static int/*BOOL*/ cp_uqchar(int ch) { return ch && (ch != ',' && ch != '"' && ch != '\r' && ch != '\n'); } /* predicate: allowed chars inside a value name */ static int/*BOOL*/ cp_namechar(int ch) { return ch && (ch != ',' && ch != '=' && ch != '\r' && ch != '\n'); } /* predicate: characters *between* list items. We're relaxed here. */ static int/*BOOL*/ cp_ivspace(int ch) { return (ch == ',' || (ch > 0 && ch <= ' ')); } /* get current character (or NUL when on end) */ static inline int pf_getch( const char ** datap, const char * endp ) { return (*datap != endp) ? *(const unsigned char*)*datap : '\0'; } /* get next character (or NUL when on end) */ static inline int pf_nextch( const char ** datap, const char * endp ) { return (*datap != endp && ++(*datap) != endp) ? *(const unsigned char*)*datap : '\0'; } static size_t str_strip( const char ** datap, size_t len ) { static const char empty[] = ""; if (*datap && len) { const char * cpl = *datap; const char * cpr = cpl + len; while (cpl != cpr && *(const unsigned char*)cpl <= ' ') ++cpl; while (cpl != cpr && *(const unsigned char*)(cpr - 1) <= ' ') --cpr; *datap = cpl; len = (size_t)(cpr - cpl); } else { *datap = empty; len = 0; } return len; } static void pf_error( const char * what, const char * where, const char * whend ) { # ifndef BUILD_AS_LIB FILE * ofp = (debug > 0) ? stdout : stderr; size_t len = (size_t)(whend - where); if (len > 50) /* *must* fit into an 'int'! */ len = 50; fprintf(ofp, "nextvar: %s: '%.*s'\n", what, (int)len, where); # else /*defined(BUILD_AS_LIB)*/ UNUSED_ARG(what); UNUSED_ARG(where); UNUSED_ARG(whend); # endif /*defined(BUILD_AS_LIB)*/ } /* * nextvar - find the next variable in the buffer */ int/*BOOL*/ nextvar( size_t *datalen, const char **datap, char **vname, char **vvalue ) { enum PState { sDone, sInit, sName, sValU, sValQ }; static char name[MAXVARLEN], value[MAXVALLEN]; const char *cp, *cpend; const char *np, *vp; size_t nlen, vlen; int ch; enum PState st; cpend = *datap + *datalen; again: np = vp = NULL; nlen = vlen = 0; st = sInit; ch = pf_getch(datap, cpend); while (st != sDone) { switch (st) { case sInit: /* handle inter-item chars */ while (cp_ivspace(ch)) ch = pf_nextch(datap, cpend); if (cp_namechar(ch)) { np = *datap; cp = np; st = sName; ch = pf_nextch(datap, cpend); } else { goto final_done; } break; case sName: /* collect name */ while (cp_namechar(ch)) ch = pf_nextch(datap, cpend); nlen = (size_t)(*datap - np); if (ch == '=') { ch = pf_nextch(datap, cpend); vp = *datap; st = sValU; } else { if (ch != ',') *datap = cpend; st = sDone; } break; case sValU: /* collect unquoted part(s) of value */ while (cp_uqchar(ch)) ch = pf_nextch(datap, cpend); if (ch == '"') { ch = pf_nextch(datap, cpend); st = sValQ; } else { vlen = (size_t)(*datap - vp); if (ch != ',') *datap = cpend; st = sDone; } break; case sValQ: /* collect quoted part(s) of value */ while (cp_qschar(ch)) ch = pf_nextch(datap, cpend); if (ch == '"') { ch = pf_nextch(datap, cpend); st = sValU; } else { pf_error("no closing quote, stop", cp, cpend); goto final_done; } break; default: pf_error("state machine error, stop", *datap, cpend); goto final_done; } } /* If name or value do not fit their buffer, croak and start * over. If there's no name at all after whitespace stripping, * redo silently. */ nlen = str_strip(&np, nlen); vlen = str_strip(&vp, vlen); if (nlen == 0) { goto again; } if (nlen >= sizeof(name)) { pf_error("runaway name", np, cpend); goto again; } if (vlen >= sizeof(value)) { pf_error("runaway value", vp, cpend); goto again; } /* copy name and value into NUL-terminated buffers */ memcpy(name, np, nlen); name[nlen] = '\0'; *vname = name; memcpy(value, vp, vlen); value[vlen] = '\0'; *vvalue = value; /* check if there's more to do or if we are finshed */ *datalen = (size_t)(cpend - *datap); return TRUE; final_done: *datap = cpend; *datalen = 0; return FALSE; } u_short varfmt(const char * varname) { u_int n; for (n = 0; n < COUNTOF(cookedvars); n++) if (!strcmp(varname, cookedvars[n].varname)) return cookedvars[n].fmt; return PADDING; } /* * printvars - print variables returned in response packet */ void printvars( size_t length, const char *data, int status, int sttype, int quiet, FILE *fp ) { if (rawmode) rawprint(sttype, length, data, status, quiet, fp); else cookedprint(sttype, length, data, status, quiet, fp); } /* * rawprint - do a printout of the data in raw mode */ static void rawprint( int datatype, size_t length, const char *data, int status, int quiet, FILE *fp ) { const char *cp; const char *cpend; /* * Essentially print the data as is. We reformat unprintables, though. */ cp = data; cpend = data + length; if (!quiet) (void) fprintf(fp, "status=0x%04x,\n", status); while (cp < cpend) { if (*cp == '\r') { /* * If this is a \r and the next character is a * \n, supress this, else pretty print it. Otherwise * just output the character. */ if (cp == (cpend - 1) || *(cp + 1) != '\n') makeascii(1, cp, fp); } else if (isspace(pgetc(cp)) || isprint(pgetc(cp))) putc(*cp, fp); else makeascii(1, cp, fp); cp++; } } /* * Global data used by the cooked output routines */ int out_chars; /* number of characters output */ int out_linecount; /* number of characters output on this line */ /* * startoutput - get ready to do cooked output */ static void startoutput(void) { out_chars = 0; out_linecount = 0; } /* * output - output a variable=value combination */ static void output( FILE *fp, const char *name, const char *value ) { int len; /* strlen of "name=value" */ len = size2int_sat(strlen(name) + 1 + strlen(value)); if (out_chars != 0) { out_chars += 2; if ((out_linecount + len + 2) > MAXOUTLINE) { fputs(",\n", fp); out_linecount = 0; } else { fputs(", ", fp); out_linecount += 2; } } fputs(name, fp); putc('=', fp); fputs(value, fp); out_chars += len; out_linecount += len; } /* * endoutput - terminate a block of cooked output */ static void endoutput( FILE *fp ) { if (out_chars != 0) putc('\n', fp); } /* * outputarr - output an array of values */ static void outputarr( FILE *fp, char *name, int narr, l_fp *lfp, int issigned ) { char *bp; char *cp; size_t i; size_t len; char buf[256]; bp = buf; /* * Hack to align delay and offset values */ for (i = (int)strlen(name); i < 11; i++) *bp++ = ' '; for (i = narr; i > 0; i--) { if (i != (size_t)narr) *bp++ = ' '; cp = (issigned ? lfptoms(lfp, 2) : ulfptoms(lfp, 2)); len = strlen(cp); if (len > 7) { cp[7] = '\0'; len = 7; } while (len < 7) { *bp++ = ' '; len++; } while (*cp != '\0') *bp++ = *cp++; lfp++; } *bp = '\0'; output(fp, name, buf); } static char * tstflags( u_long val ) { # if CBLEN < 10 # error CBLEN is too small -- increase! # endif char *cp, *s; size_t cb, i; int l; s = cp = circ_buf[nextcb]; if (++nextcb >= NUMCB) nextcb = 0; cb = sizeof(circ_buf[0]); l = snprintf(cp, cb, "%02lx", val); if (l < 0 || (size_t)l >= cb) goto fail; cp += l; cb -= l; if (!val) { l = strlcat(cp, " ok", cb); if ((size_t)l >= cb) goto fail; cp += l; cb -= l; } else { const char *sep; sep = " "; for (i = 0; i < COUNTOF(tstflagnames); i++) { if (val & 0x1) { l = snprintf(cp, cb, "%s%s", sep, tstflagnames[i]); if (l < 0) goto fail; if ((size_t)l >= cb) { cp += cb - 4; cb = 4; l = strlcpy (cp, "...", cb); cp += l; cb -= l; break; } sep = ", "; cp += l; cb -= l; } val >>= 1; } } return s; fail: *cp = '\0'; return s; } /* * cookedprint - output variables in cooked mode */ static void cookedprint( int datatype, size_t length, const char *data, int status, int quiet, FILE *fp ) { char *name; char *value; char output_raw; int fmt; l_fp lfp; sockaddr_u hval; u_long uval; int narr; size_t len; l_fp lfparr[8]; char b[12]; char bn[2 * MAXVARLEN]; char bv[2 * MAXVALLEN]; UNUSED_ARG(datatype); if (!quiet) fprintf(fp, "status=%04x %s,\n", status, statustoa(datatype, status)); startoutput(); while (nextvar(&length, &data, &name, &value)) { fmt = varfmt(name); output_raw = 0; switch (fmt) { case PADDING: output_raw = '*'; break; case TS: if (!value || !decodets(value, &lfp)) output_raw = '?'; else output(fp, name, prettydate(&lfp)); break; case HA: /* fallthru */ case NA: if (!value || !decodenetnum(value, &hval)) { output_raw = '?'; } else if (fmt == HA){ output(fp, name, nntohost(&hval)); } else { output(fp, name, stoa(&hval)); } break; case RF: if (!value) { output_raw = '?'; } else if (decodenetnum(value, &hval)) { if (datatype == TYPE_CLOCK && IS_IPV4(&hval)) { /* * Workaround to override numeric refid formats * for refclocks received from faulty nptd servers * and output them as text. */ int i; unsigned char *str = (unsigned char *)&(hval.sa4).sin_addr; char refid_buf[5]; for (i=0; i<4 && str[i]; i++) refid_buf[i] = (isprint(str[i]) ? str[i] : '?'); refid_buf[i] = 0; /* Null terminator */ output(fp, name, refid_buf); } else if (ISREFCLOCKADR(&hval)) { output(fp, name, refnumtoa(&hval)); } else { if (drefid == REFID_IPV4) { output(fp, name, stoa(&hval)); } else { char refid_buf[12]; snprintf (refid_buf, sizeof(refid_buf), "0x%08x", ntohl(addr2refid(&hval))); output(fp, name, refid_buf); } } } else if (strlen(value) <= 4) { output(fp, name, value); } else { output_raw = '?'; } break; case LP: if (!value || !decodeuint(value, &uval) || uval > 3) { output_raw = '?'; } else { b[0] = (0x2 & uval) ? '1' : '0'; b[1] = (0x1 & uval) ? '1' : '0'; b[2] = '\0'; output(fp, name, b); } break; case OC: if (!value || !decodeuint(value, &uval)) { output_raw = '?'; } else { snprintf(b, sizeof(b), "%03lo", uval); output(fp, name, b); } break; case AU: case AS: if (!value || !decodearr(value, &narr, lfparr, 8)) output_raw = '?'; else outputarr(fp, name, narr, lfparr, (fmt==AS)); break; case FX: if (!value || !decodeuint(value, &uval)) output_raw = '?'; else output(fp, name, tstflags(uval)); break; case SN: if (!value) output_raw = '?'; else if (isdigit(*value)) { /* number without sign */ bv[0] = '+'; atoascii (value, MAXVALLEN, bv+1, sizeof(bv)-1); output(fp, name, bv); } else output_raw = '*'; /* output as-is */ break; default: fprintf(stderr, "Internal error in cookedprint, %s=%s, fmt %d\n", name, value, fmt); output_raw = '?'; break; } if (output_raw != 0) { /* TALOS-CAN-0063: avoid buffer overrun */ atoascii(name, MAXVARLEN, bn, sizeof(bn)); if (output_raw != '*') { atoascii(value, MAXVALLEN, bv, sizeof(bv) - 1); len = strlen(bv); bv[len] = output_raw; bv[len+1] = '\0'; } else { atoascii(value, MAXVALLEN, bv, sizeof(bv)); } output(fp, bn, bv); } } endoutput(fp); } /* * sortassoc - sort associations in the cache into ascending order */ void sortassoc(void) { if (numassoc > 1) qsort(assoc_cache, (size_t)numassoc, sizeof(assoc_cache[0]), &assoccmp); } /* * assoccmp - compare two associations */ static int assoccmp( const void *t1, const void *t2 ) { const struct association *ass1 = t1; const struct association *ass2 = t2; if (ass1->assid < ass2->assid) return -1; if (ass1->assid > ass2->assid) return 1; return 0; } /* * grow_assoc_cache() - enlarge dynamic assoc_cache array * * The strategy is to add an assumed 4k page size at a time, leaving * room for malloc() bookkeeping overhead equivalent to 4 pointers. */ void grow_assoc_cache(void) { static size_t prior_sz; size_t new_sz; new_sz = prior_sz + 4 * 1024; if (0 == prior_sz) { new_sz -= 4 * sizeof(void *); } assoc_cache = erealloc_zero(assoc_cache, new_sz, prior_sz); prior_sz = new_sz; assoc_cache_slots = (u_int)(new_sz / sizeof(assoc_cache[0])); } /* * ntpq_custom_opt_handler - autoopts handler for -c and -p * * By default, autoopts loses the relative order of -c and -p options * on the command line. This routine replaces the default handler for * those routines and builds a list of commands to execute preserving * the order. */ void ntpq_custom_opt_handler( tOptions *pOptions, tOptDesc *pOptDesc ) { switch (pOptDesc->optValue) { default: fprintf(stderr, "ntpq_custom_opt_handler unexpected option '%c' (%d)\n", pOptDesc->optValue, pOptDesc->optValue); exit(1); case 'c': if ((pOptDesc->fOptState & OPTST_SET_MASK) == OPTST_DEFINED) defcmds++; ADDCMD(pOptDesc->pzLastArg); break; case 'p': if ((pOptDesc->fOptState & OPTST_SET_MASK) == OPTST_DEFINED) defcmds++; ADDCMD("peers"); break; } } /* * Obtain list of digest names */ #if defined(OPENSSL) && !defined(HAVE_EVP_MD_DO_ALL_SORTED) # if defined(_MSC_VER) && OPENSSL_VERSION_NUMBER >= 0x10100000L # define HAVE_EVP_MD_DO_ALL_SORTED # endif #endif #ifdef OPENSSL # ifdef HAVE_EVP_MD_DO_ALL_SORTED # define K_PER_LINE 8 # define K_NL_PFX_STR "\n " # define K_DELIM_STR ", " struct hstate { char *list; char const **seen; int idx; }; # ifndef BUILD_AS_LIB static void list_md_fn(const EVP_MD *m, const char *from, const char *to, void *arg) { size_t len, n; const char *name, **seen; struct hstate *hstate = arg; /* m is MD obj, from is name or alias, to is base name for alias */ if (!m || !from || to) { return; /* Ignore aliases */ } /* Discard MACs that NTP won't accept. */ /* Keep this consistent with keytype_from_text() in ssl_init.c. */ if (EVP_MD_size(m) > MAX_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) */