1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# 4# ntpq - query an NTP server using mode 6 commands 5# 6# Freely translated from the old C ntpq code by ESR. The idea was to 7# cleanly separate ntpq-that-was into a thin front-end layer handling 8# mainly command interpretation and a back-end that presents the take 9# from ntpd as objects that can be re-used by other front 10# ends. Reusable pieces live in pylib. 11# 12# SPDX-License-Identifier: BSD-2-Clause 13from __future__ import print_function, division 14 15import cmd 16import getopt 17import os 18import re 19import resource 20import socket 21import sys 22import time 23 24try: 25 import ntp.control 26 import ntp.ntpc 27 import ntp.packet 28 import ntp.util 29 import ntp.poly 30except ImportError as e: 31 sys.stderr.write( 32 "ntpq: can't find Python NTP library -- check PYTHONPATH.\n") 33 sys.stderr.write("%s\n" % e) 34 sys.exit(1) 35 36# This used to force UTF-8 encoding, but that breaks the readline system. 37# Unfortunately sometimes sys.stdout.encoding lies about the encoding, 38# so expect random false positives. 39ntp.util.check_unicode() 40 41version = ntp.util.stdversion() 42 43# Flags for forming descriptors. 44OPT = 0x80 # this argument is optional, or'd with type */ 45NO = 0x0 46NTP_STR = 0x1 # string argument 47NTP_UINT = 0x2 # unsigned integer 48NTP_INT = 0x3 # signed integer 49NTP_ADD = 0x4 # IP network address 50IP_VERSION = 0x5 # IP version 51NTP_ADP = 0x6 # IP address and port 52NTP_LFP = 0x7 # NTP timestamp 53NTP_MODE = 0x8 # peer mode 54NTP_2BIT = 0x9 # leap bits 55NTP_FLOAT = 0xa # Float value 56NTP_UPTIME = 0xb # uptime in days H:M:S (no frac) 57 58 59class Ntpq(cmd.Cmd): 60 "ntpq command interpreter" 61 62 def __init__(self, session): 63 cmd.Cmd.__init__(self) 64 self.session = session 65 self.prompt = "ntpq> " if os.isatty(0) else "" 66 self.interactive = False # set to True when we should prompt 67 # self.auth_keyid = 0# Keyid used for authentication. 68 # self.auth_keytype = "NID_md5"# MD5 (FIXME: string value is a dummy) 69 # self.auth_hashlen = 16# MD5 70 # I do not know if the preceding are there for a specific reason 71 # so I am leaving them, and possibly duplicating them. 72 self.rawmode = False # Flag which indicates raw mode output. 73 self.directmode = False # Flag for direct MRU output. 74 self.showhostnames = 1 # If & 1, display names 75 self.showunits = False # If False, show old style float 76 self.auth_delay = 20 # delay time (default 20msec) 77 self.wideremote = False # show wide remote names? 78 self.ccmds = [] # Queued commands 79 self.chosts = [] # Command-line hosts 80 self.peers = [] # Data from NTP peers. 81 self.debug = 0 82 self.logfp = sys.stderr 83 self.pktversion = ntp.magic.NTP_OLDVERSION + 1 84 self.uservars = ntp.util.OrderedDict() 85 self.ai_family = socket.AF_UNSPEC 86 self.termwidth = ntp.util.termsize()[0] 87 88 def get_names(self): 89 # Overrides a method in Cmd 90 return [x.replace('hot_', ':').replace('config_from_file', 91 'config-from-file') 92 for x in dir(self.__class__)] 93 94 def emptyline(self): 95 "Called when an empty line is entered in response to the prompt." 96 pass 97 98 def precmd(self, line): 99 if line.startswith(":config"): 100 line = "hot_" + line[1:] 101 elif line.startswith("config-from-file"): 102 line = line.replace("config-from-file", "config_from_file") 103 return line 104 105 def default(self, line): 106 "Called on an input line when the command prefix is not recognized." 107 cmd, arg, line = self.parseline(line) 108 try: 109 dotext = 'do_'+cmd 110 cmdprefixlist = [a[3:] for a in self.get_names() 111 if a.startswith(dotext)] 112 if len(cmdprefixlist) == 1: 113 line = line.replace(cmd, cmdprefixlist[0]) 114 cmd = cmdprefixlist[0] 115 elif len(cmdprefixlist) > 1: 116 self.warn("***Command `%s' ambiguous" % cmd) 117 return 118 elif not cmdprefixlist: 119 self.warn("***Command `%s' unknown" % cmd) 120 return 121 122 if cmd == "help" and arg: 123 helptext = 'help_'+arg 124 if helptext not in self.get_names(): 125 argprefixlist = [a[5:] for a in self.get_names() 126 if a.startswith(helptext)] 127 if len(argprefixlist) == 1: 128 line = line.replace(arg, argprefixlist.pop()) 129 elif len(argprefixlist) > 1: 130 self.warn("Command `%s' is ambiguous" % arg) 131 return 132 elif not argprefixlist: 133 self.warn("Command `%s' is unknown" % arg) 134 return 135 136 self.onecmd(line) 137 except TypeError: 138 self.warn("Command `%s' is unknown" % line) 139 140 def do_help(self, arg): 141 if arg: 142 helptext = 'help_'+arg 143 if helptext not in self.get_names(): 144 argprefixlist = [a[5:] for a in self.get_names() 145 if a.startswith(helptext)] 146 if len(argprefixlist) == 1: 147 arg = argprefixlist.pop() 148 elif len(argprefixlist) > 1: 149 self.warn("Command `%s' is ambiguous." % arg) 150 return 151 cmd.Cmd.do_help(self, arg) 152 153 def say(self, msg): 154 try: 155 sys.stdout.write(ntp.poly.polyunicode(msg)) 156 except UnicodeEncodeError as e: 157 self.warn("Unicode failure: %s" % str(e)) 158 self.warn("msg:\n" + repr(msg)) 159 raise e 160 sys.stdout.flush() # In case we're piping the output 161 162 def warn(self, msg): 163 sys.stderr.write(ntp.poly.polystr(msg) + "\n") 164 165 def help_help(self): 166 self.say("""\ 167function: tell the use and syntax of commands 168usage: help [ command ] 169""") 170 171 # Unexposed helper tables and functions begin here 172 173 def __dogetassoc(self): 174 try: 175 self.peers = self.session.readstat() 176 except ntp.packet.ControlException as e: 177 self.warn(e.message) 178 return False 179 except IOError as e: 180 self.warn(e.strerror) 181 return False 182 183 if not self.peers: 184 if self.chosts: 185 self.say("server=%s " % self.session.hostname) 186 self.say("No association IDs returned\n") 187 return False 188 189 if self.debug: 190 self.warn("\n%d associations total" % len(self.peers)) 191 # sortassoc() 192 return True 193 194 def __printassoc(self, showall): 195 if not self.peers: 196 self.say("No association IDs in list\n") 197 return 198 self.say( 199 "\nind assid status conf reach auth condition last_event cnt\n") 200 self.say( 201 "===========================================================\n") 202 for (i, peer) in enumerate(self.peers): 203 statval = ntp.control.CTL_PEER_STATVAL(peer.status) 204 if (not showall and 205 (statval & (ntp.control.CTL_PST_CONFIG | 206 ntp.control.CTL_PST_REACH)) == 0): 207 continue 208 sw = ntp.util.PeerStatusWord(peer.status) 209 display = "%3d %5u %04x %3.3s %4s %4.4s %9.9s %11s %2lu" \ 210 % (i + 1, peer.associd, peer.status, sw.conf, sw.reach, 211 sw.auth, sw.condition, sw.last_event, sw.event_count) 212 self.say(display + "\n") 213 214 def __dopeers(self, showall, mode): 215 if not self.__dogetassoc(): 216 return 217 report = ntp.util.PeerSummary(mode, 218 self.pktversion, 219 self.showhostnames, 220 self.wideremote, 221 self.showunits, 222 termwidth=interpreter.termwidth, 223 debug=interpreter.debug, 224 logfp=self.logfp) 225 try: 226 maxhostlen = 0 227 if len(self.chosts) > 1: 228 maxhostlen = max([len(host) for (host, _af) in self.chosts]) 229 self.say("%-*.*s " 230 % (maxhostlen, maxhostlen+1, "server")) 231 self.say(report.header() + "\n") 232 if len(self.chosts) > 1: 233 maxhostlen = max([len(host) for (host, _af) in self.chosts]) 234 self.say("=" * (maxhostlen + 1)) 235 self.say(("=" * report.width()) + "\n") 236 for peer in self.peers: 237 if (not showall and 238 not (ntp.control.CTL_PEER_STATVAL(peer.status) & 239 (ntp.control.CTL_PST_CONFIG | 240 ntp.control.CTL_PST_REACH))): 241 if self.debug: 242 self.warn("eliding [%d]\n" % peer.associd) 243 continue 244 try: 245 variables = self.session.readvar(peer.associd, raw=True) 246 except ntp.packet.ControlException as e: 247 self.warn(e.message) 248 return 249 except IOError as e: 250 self.warn(e.strerror) 251 return 252 if not variables: 253 if len(self.chosts) > 1: 254 self.warn("server=%s " % self.session.hostname) 255 self.warn("***No information returned for association %d" 256 % peer.associd) 257 continue 258 if len(self.chosts) > 1: 259 self.say(ntp.util.PeerSummary.high_truncate( 260 self.session.hostname, maxhostlen) + " " * 261 (maxhostlen + 1 - len(self.session.hostname))) 262 self.say(report.summary(self.session.rstatus, 263 variables, peer.associd)) 264 except KeyboardInterrupt: 265 pass 266 267 def __assoc_valid(self, line, required=False): 268 "Process a numeric associd or index." 269 # FIXME: This does a useless call to __dogetassoc() when associd == 0 270 # No big deal most of the time. Just a useless packet exchange. 271 if not line: 272 if required: 273 self.warn("An associd argument is required.") 274 return -1 275 else: 276 return 0 277 if not self.peers: 278 self.__dogetassoc() 279 if line.startswith("&"): 280 try: 281 idx = int(line[1:].split()[0]) 282 except ValueError: 283 self.warn("Invalid index literal.") 284 return -1 285 if idx < 0 or idx >= 2**16-1: 286 self.warn("%d is not a valid association number." % idx) 287 return -1 288 elif idx not in range(1, len(self.peers)+1): 289 self.warn("No such association as %d." % idx) 290 return -1 291 else: 292 return self.peers[idx - 1].associd 293 else: 294 try: 295 associd = int(line.split()[0]) 296 except ValueError: 297 self.warn("Invalid associd literal.") 298 return -1 299 if (associd != 0 and 300 associd not in [peer.associd for peer in self.peers]): 301 self.warn("Unknown associd.") 302 return -1 303 else: 304 return associd 305 306 def __assoc_range_valid(self, line): 307 "Try to get a range of assoc IDs." 308 tokens = line.split() 309 if len(tokens) < 2: 310 return () 311 lo = self.__assoc_valid(tokens[0]) 312 hi = self.__assoc_valid(tokens[1]) 313 if lo < 0 or hi < 0 or hi < lo: 314 return () 315 if lo == hi: 316 return(lo,) 317 return range(lo, hi+1) 318 319 def printvars(self, variables, dtype, quiet): 320 "Dump variables in raw (actually, semi-cooked) mode." 321 if self.rawmode: 322 if not quiet: 323 self.say("status=0x%04x,\n" % self.session.rstatus) 324 # C ntpq not only suppressed \r but tried to visibilize 325 # high-half characters. We won't do that unless somebody 326 # files a bug, Mode 6 never seems to generate those in 327 # variable fetches. 328 text = ntp.poly.polystr(session.response.replace( 329 ",\r\n", ",\n")) 330 else: 331 if not quiet: 332 self.say("status=%04x %s,\n" 333 % (self.session.rstatus, 334 ntp.ntpc.statustoa(dtype, self.session.rstatus))) 335 text = ntp.util.cook(variables, self.showunits) 336 text = text.replace("'", '"') 337 self.say(text) 338 339 def __dolist(self, varlist, associd, op, type, quiet=False): 340 "List variables associated with a specified peer." 341 try: 342 variables = self.session.readvar(associd, varlist, op, raw=True) 343 except ntp.packet.ControlException as e: 344 self.warn(e.message) 345 return False 346 except IOError as e: 347 self.warn(e.strerror) 348 return False 349 if len(self.chosts) > 1: 350 self.say("server=%s " % self.session.hostname) 351 if not variables: 352 if associd == 0: 353 self.say("No system%s variables returned\n" 354 % " clock" if (type == ntp.ntpc.TYPE_CLOCK) else "") 355 else: 356 self.say("No information returned for%s association %d\n" 357 % (" clock" if (type == ntp.ntpc.TYPE_CLOCK) else "", 358 associd)) 359 return True 360 if not quiet: 361 self.say("associd=%d " % associd) 362 self.printvars(variables, type, not (not varlist)) 363 return True 364 365 # Unexposed helper tables and functions end here 366 367 def do_units(self, _unused): 368 "toggle unit display" 369 self.showunits = not self.showunits 370 371 def help_units(self): 372 self.say("""\ 373function: toggle unit display 374usage: units 375""") 376 377 def do_EOF(self, _unused): 378 "exit ntpq" 379 self.say("\n") 380 return True 381 382 def do_timeout(self, line): 383 "set the primary receive time out" 384 if line: 385 try: 386 self.session.primary_timeout = int(line) 387 except ValueError: 388 self.warn("What?") 389 self.say("primary timeout %d ms\n" % self.session.primary_timeout) 390 391 def help_timeout(self): 392 self.say("""\ 393function: set the primary receive time out 394usage: timeout [ msec ] 395""") 396 397 def collect_display2(self, variables): 398 "Query and display a collection of variables from the system." 399 try: 400 queried = self.session.readvar(0, 401 [v[0] for v in variables] + 402 [v[0] + '_r' for v in variables], 403 raw=True) 404 except ntp.packet.ControlException as e: 405 if ntp.control.CERR_UNKNOWNVAR == e.errorcode: 406 self.warn("Unknown variable. Trying one at a time.") 407 varlist = [v[0] for v in variables] + \ 408 [v[0] + '_r' for v in variables] 409 items = [] 410 for var in varlist: 411 try: 412 queried = self.session.readvar(0, [var], 413 raw=True) 414 for (name, (value, rawvalue)) in queried.items(): 415 items.append((name, (value, rawvalue))) 416 except ntp.packet.ControlException as e: 417 if ntp.control.CERR_UNKNOWNVAR == e.errorcode: 418 items.append((var, "???")) 419 continue 420 raise e 421 queried = ntp.util.OrderedDict(items) 422 else: 423 self.warn(e.message) 424 return 425 except IOError as e: 426 self.warn(e.strerror) 427 return 428 if self.rawmode: 429 self.say(self.session.response) 430 return 431 try: 432 for (name, legend, fmt) in variables: 433 if name not in queried: 434 continue 435 value = queried[name][0] 436 rawvalue = queried[name][1] 437 value2 = queried[name + '_r'][0] 438 rawvalue2 = queried[name + '_r'][1] 439 if fmt in (NTP_UINT, NTP_INT, NTP_FLOAT): 440 if self.showunits: 441 displayvalue = ntp.util.unitifyvar(rawvalue, name) 442 displayvalue2 = ntp.util.unitifyvar(rawvalue2, name) 443 else: 444 displayvalue = value 445 displayvalue2 = value2 446 self.say("%13s \t%9d\t%9d\n" % 447 (legend, displayvalue, displayvalue2)) 448 elif fmt == NTP_UPTIME: 449 self.say("%13s %s\t%s\n" % (legend, ntp.util.prettyuptime( 450 value), ntp.util.prettyuptime(value2))) 451 else: 452 self.warn("unexpected vc type %s for %s, value %s" 453 % (fmt, name, value, value2)) 454 except KeyboardInterrupt: 455 self.warn("display interrupted") 456 457 def collect_display(self, associd, variables, decodestatus): 458 "Query and display a collection of variables from the system." 459 try: 460 queried = self.session.readvar(associd, 461 [v[0] for v in variables], 462 raw=True) 463 except ntp.packet.ControlException as e: 464 if ntp.control.CERR_UNKNOWNVAR == e.errorcode: 465 self.warn("Unknown variable. Trying one at a time.") 466 varlist = [v[0] for v in variables] 467 items = [] 468 for var in varlist: 469 try: 470 queried = self.session.readvar(associd, [var], 471 raw=True) 472 for (name, (value, rawvalue)) in queried.items(): 473 items.append((name, (value, rawvalue))) 474 except ntp.packet.ControlException as e: 475 if ntp.control.CERR_UNKNOWNVAR == e.errorcode: 476 items.append((var, "???")) 477 continue 478 raise e 479 queried = ntp.util.OrderedDict(items) 480 else: 481 self.warn(e.message) 482 return 483 except IOError as e: 484 self.warn(e.strerror) 485 return 486 if self.rawmode: 487 self.say(self.session.response) 488 return 489 if decodestatus: 490 if associd == 0: 491 statype = ntp.ntpc.TYPE_SYS 492 else: 493 statype = ntp.ntpc.TYPE_PEER 494 self.say("associd=%u status=%04x %s,\n" 495 % (associd, self.session.rstatus, 496 ntp.ntpc.statustoa(statype, self.session.rstatus))) 497 try: 498 for (name, legend, fmt) in variables: 499 if name not in queried: 500 continue 501 value = queried[name][0] 502 rawvalue = queried[name][1] 503 if fmt in (NTP_ADD, NTP_ADP): 504 if self.showhostnames & 1: # if & 1, display names 505 if self.debug: 506 self.say("DNS lookup begins...") 507 value = ntp.util.canonicalize_dns( 508 value, family=self.ai_family) 509 if self.debug: 510 self.say("DNS lookup complete.") 511 self.say("%s %s\n" % (legend, value)) 512 elif fmt == NTP_STR: 513 if value: 514 self.say("%s %s\n" % (legend, value)) 515 elif fmt in (NTP_UINT, NTP_INT, NTP_FLOAT): 516 if self.showunits: 517 displayvalue = ntp.util.unitifyvar(rawvalue, name) 518 else: 519 displayvalue = value 520 self.say("%s %s\n" % (legend, displayvalue)) 521 elif fmt == NTP_LFP: 522 self.say("%s %s\n" % (legend, ntp.ntpc.prettydate(value))) 523 elif fmt == NTP_2BIT: 524 self.say("%s %s\n" 525 % (legend, ("00", "01", "10", "11")[value])) 526 elif fmt == NTP_MODE: 527 modes = ( 528 "unspec", "sym_active", "sym_passive", "client", 529 "server", 530 "broadcast", "control", "private", "bclient" 531 ) 532 try: 533 self.say("%s %s\n" % (legend, modes[value])) 534 except IndexError: 535 self.say("%s %s%d\n" % (legend, "mode#", value)) 536 else: 537 self.warn("unexpected vc type %s for %s, value %s" 538 % (fmt, name, value)) 539 except KeyboardInterrupt: 540 self.warn("display interrupted") 541 542 def do_delay(self, line): 543 "set the delay added to encryption time stamps" 544 if not line: 545 self.say("delay %d ms\n" % self.auth_delay) 546 else: 547 try: 548 self.auth_delay = int(line) 549 if self.auth_delay < 0: 550 raise ValueError 551 except ValueError: 552 self.say("Huh?") 553 554 def help_delay(self): 555 self.say("""\ 556function: set the delay added to encryption time stamps 557usage: delay [ msec ] 558""") 559 560 def do_host(self, line): 561 "specify the host whose NTP server we talk to" 562 if not line: 563 if self.session.havehost(): 564 self.say("current host is %s\n" % self.session.hostname) 565 else: 566 self.say("no current host\n") 567 else: 568 tokens = line.split() 569 if tokens[0] == '-4': 570 session.ai_family = socket.AF_INET 571 tokens.pop(0) 572 elif tokens[0] == '-6': 573 session.ai_family = socket.AF_INET6 574 tokens.pop(0) 575 try: 576 if (tokens and 577 self.session.openhost(tokens[0], session.ai_family)): 578 self.say("current host set to %s\n" 579 % self.session.hostname) 580 elif self.session.havehost(): 581 self.say("current host remains %s\n" 582 % self.session.hostname) 583 else: 584 self.say("still no current host\n") 585 except KeyboardInterrupt: 586 self.warn("lookup interrupted") 587 588 def help_host(self): 589 self.say("""\ 590function: specify the host whose NTP server we talk to 591usage: host [-4|-6] [hostname] 592""") 593 594 def do_poll(self, line): 595 "poll an NTP server in client mode `n' times" 596 # And it's not in the C version, so we're off the hook here 597 self.warn("WARNING: poll not implemented yet") 598 599 def help_poll(self): 600 self.say("""\ 601function: poll an NTP server in client mode `n' times 602usage: poll [n] [verbose] 603""") 604 605 def do_passwd(self, line): 606 "specify a password to use for authenticated requests" 607 try: 608 self.session.password() 609 except ntp.packet.ControlException as e: 610 self.warn(e.message) 611 except IOError: 612 self.warn("***Can't read control key from /etc/ntp.conf") 613 614 def help_passwd(self): 615 self.say("""\ 616function: specify a password to use for authenticated requests 617usage: passwd [] 618""") 619 620 def do_hostnames(self, line): 621 "specify whether hostnames or net numbers are printed" 622 if not line: 623 pass 624 elif line == "yes": 625 self.showhostnames = 1 626 elif line == "no": 627 self.showhostnames = 0 628 elif line == 'hostnum': 629 self.showhostnames = 2 630 elif line == 'hostname': 631 self.showhostnames = 3 632 else: 633 self.say("What?\n") 634 pass 635 if self.showhostnames & 1: 636 self.say('resolved hostnames being shown\n') 637 else: 638 self.say('resolved hostnames not being shown\n') 639 if self.showhostnames & 2: 640 self.say('supplied hostnames being shown\n') 641 else: 642 self.say('supplied hostnames not being shown\n') 643 644 def help_hostnames(self): 645 self.say("""\ 646function: specify whether hostnames or net numbers are printed 647usage: hostnames [yes|no|hostname|hostnum] 648""") 649 650 def do_debug(self, line): 651 "set/change debugging level" 652 if not line: 653 pass 654 elif line == "more": 655 self.debug += 1 656 elif line == "less": 657 if self.debug > 0: 658 self.debug -= 1 659 elif line == "no": 660 self.debug = 0 661 else: 662 try: 663 self.debug = int(line) # C version didn't implement this 664 except ValueError: 665 self.warn("What?") 666 self.session.debug = self.debug 667 self.say("debug level is %d\n" % self.debug) 668 669 def do_logfile(self, line): 670 """view/change logfile. \"<stderr>\" will log to stderr 671 instead of a file""" 672 if not line: 673 self.say(repr(self.logfp.name) + "\n") 674 return 675 if self.logfp != sys.stderr: 676 self.logfp.close() 677 if line == "<stderr>": 678 self.logfp = self.session.logfp = sys.stderr 679 else: 680 try: 681 logfp = open(line, "a", 1) # 1 => line buffered 682 self.logfp = self.session.logfp = logfp 683 self.say("Logfile set to %s\n" % line) 684 except IOError: 685 self.warn("Could not open %s for logging." % line) 686 687 def help_debug(self): 688 self.say("""\ 689function: set/change debugging level 690usage: debug [no|more|less|n] 691""") 692 693 def do_exit(self, line): 694 "exit ntpq" 695 return True 696 697 def help_exit(self): 698 self.say("""\ 699function: exit ntpq 700usage: exit 701""") 702 do_quit = do_exit 703 704 def help_quit(self): 705 self.say("""\ 706function: exit ntpq 707usage: quit 708""") 709 710 def do_keyid(self, line): 711 "set keyid to use for authenticated requests" 712 if line: 713 try: 714 self.session.keyid = int(line) 715 except ValueError: 716 self.warn("What?") 717 if self.session.keyid is None: 718 self.say("no keyid defined\n") 719 else: 720 self.say("keyid is %d\n" % self.session.keyid) 721 722 def help_keyid(self): 723 self.say("""\ 724function: set keyid to use for authenticated requests 725usage: keyid [key#] 726""") 727 728 def do_version(self, line): 729 "print version number" 730 self.say(version + "\n") 731 732 def help_version(self): 733 self.say("""\ 734function: print version number 735usage: version 736""") 737 738 def do_direct(self, line): 739 "toggle direct mode output" 740 self.directmode = not self.directmode 741 if self.directmode: 742 self.say("Direct mode is on\n") 743 else: 744 self.say("Direct mode is off\n") 745 746 def help_direct(self): 747 self.say("""\ 748function: toggle direct-mode MRU output 749usage: direct 750""") 751 752 def do_raw(self, line): 753 "do raw mode variable output" 754 self.rawmode = True 755 self.say("Output set to raw\n") 756 757 def help_raw(self): 758 self.say("""\ 759function: do raw mode variable output 760usage: raw 761""") 762 763 def do_cooked(self, line): 764 "do cooked mode variable output" 765 self.rawmode = False 766 self.say("Output set to cooked\n") 767 768 def help_cooked(self): 769 self.say("""\ 770function: do cooked mode variable output 771usage: cooked 772""") 773 774 def do_authenticate(self, line): 775 "always authenticate requests to this server" 776 if not line: 777 pass 778 elif line == "yes": 779 self.session.always_auth = True 780 elif line == "no": 781 self.session.always_auth = False 782 else: 783 self.warn("What?") 784 if self.session.always_auth: 785 self.say("authenticated requests being sent\n") 786 else: 787 self.say("unauthenticated requests being sent\n") 788 789 def help_authenticate(self): 790 self.say("""\ 791function: always authenticate requests to this server 792usage: authenticate [yes|no] 793""") 794 795 def do_ntpversion(self, line): 796 "set the NTP version number to use for requests" 797 if not line: 798 pass 799 else: 800 try: 801 newversion = int(line) 802 if (newversion >= ntp.magic.NTP_OLDVERSION and 803 newversion <= ntp.magic.NTP_VERSION): 804 self.pktversion = newversion 805 else: 806 self.warn("versions %d to %d, please" 807 % (ntp.magic.NTP_OLDVERSION, 808 ntp.magic.NTP_VERSION)) 809 except ValueError: 810 self.warn("What?") 811 self.say("NTP version being claimed is %d\n" % self.pktversion) 812 813 def help_ntpversion(self): 814 self.say("""\ 815function: set the NTP version number to use for requests 816usage: ntpversion [version number] 817""") 818 819 def do_keytype(self, line): 820 "set key type to use for authenticated requests" 821 if not line: 822 self.say("Keytype: %s\n" % self.session.keytype) 823 elif line.upper() in ['AES', 'AES128CMAC']: 824 self.session.keytype = 'AES-128' 825 elif not ntp.ntpc.checkname(line.upper()): 826 self.warn("Keytype %s is not supported by openSSL or ntpq.\n" % line) 827 else: 828 self.session.keytype = line.upper() 829 830 def help_keytype(self): 831 self.say("""\ 832function: set key type to use for authenticated requests, one of: 833 DSA, MD4, MD5, MDC2, RIPEMD160, SHA-1, AES-CMAC 834usage: keytype [digest-name] 835""") 836 837 def do_associations(self, line): 838 "print list of association IDs and statuses for the server's peers" 839 if self.__dogetassoc(): 840 self.__printassoc(showall=True) 841 842 def help_associations(self): 843 self.say("""\ 844function: print list of association IDs and statuses for the server's peers 845usage: associations 846""") 847 848 def do_passociations(self, line): 849 "print list of associations returned by last associations command" 850 self.__printassoc(showall=True) 851 852 def help_passociations(self): 853 self.say("""\ 854function: print list of associations returned by last associations command 855usage: passociations 856""") 857 858 def do_lassociations(self, line): 859 "print list of associations including all client information" 860 if self.__dogetassoc(): 861 self.__printassoc(showall=True) 862 863 def help_lassociations(self): 864 self.say("""\ 865function: print list of associations including all client information 866usage: lassociations 867""") 868 869 def do_lpassociations(self, line): 870 """\ 871print last obtained list of associations, including client information 872""" 873 self.__printassoc(showall=True) 874 875 def help_lpassociations(self): 876 self.say("""\ 877function: print last obtained list of associations, including 878 client information 879usage: lpassociations 880""") 881 882 def do_addvars(self, line): 883 "add variables to the variable list or change their values" 884 if not line: 885 self.warn("usage: addvars name[=value][,...]\n") 886 return 887 vars_to_add = line.split(',') 888 for add_var in vars_to_add: 889 try: 890 (name, val) = add_var.split("=") 891 except ValueError: 892 (name, val) = (add_var, "") 893 self.uservars[name.strip()] = val.strip() 894 895 def help_addvars(self): 896 self.say("""\ 897function: add variables to the variable list or change their values 898usage: addvars name[=value][,...] 899""") 900 901 def do_rmvars(self, line): 902 "remove variables from the variable list" 903 if not line: 904 self.warn("usage: rmvars name[,...]\n") 905 return 906 vars_to_rm = line.split(',') 907 for rm_var in vars_to_rm: 908 if rm_var not in self.uservars: 909 self.warn("%s is not in the variable list" % rm_var) 910 else: 911 del self.uservars[rm_var] 912 913 def help_rmvars(self): 914 self.say("""\ 915function: remove variables from the variable list 916usage: rmvars name[,...] 917""") 918 919 def do_clearvars(self, line): 920 "remove all variables from the variable list" 921 self.uservars.clear() 922 923 def help_clearvars(self): 924 self.say("""\ 925function: remove all variables from the variable list 926usage: clearvars 927""") 928 929 def do_showvars(self, line): 930 "print variables on the variable list" 931 if not self.uservars: 932 self.say("No variables on list.\n") 933 for (name, value) in self.uservars.items(): 934 if value: 935 self.say("%s=%s\n" % (name, value)) 936 else: 937 self.say(name + "\n") 938 939 def help_showvars(self): 940 self.say("""\ 941function: print variables on the variable list 942usage: showvars 943""") 944 945 def do_readlist(self, line): 946 "read the system or peer variables included in the variable list" 947 associd = self.__assoc_valid(line) 948 if associd >= 0: 949 qtype = ntp.ntpc.TYPE_SYS if associd == 0 else ntp.ntpc.TYPE_PEER 950 self.__dolist(self.uservars.keys(), associd, 951 ntp.control.CTL_OP_READVAR, qtype) 952 953 def help_readlist(self): 954 self.say("""\ 955function: read the system or peer variables included in the variable list 956usage: readlist [assocID] 957""") 958 959 def do_rl(self, line): 960 "read the system or peer variables included in the variable list" 961 self.do_readlist(line) 962 963 def help_rl(self): 964 self.say("""\ 965function: read the system or peer variables included in the variable list 966usage: rl [assocID] 967""") 968 969 def do_writelist(self, line): 970 "write the system or peer variables included in the variable list" 971 pass 972 973 def help_writelist(self): 974 self.say("""\ 975function: write the system or peer variables included in the variable list 976usage: writelist [ assocID ] 977""") 978 979 def do_readvar(self, line): 980 "read system or peer variables" 981 associd = self.__assoc_valid(line) 982 if associd >= 0: 983 qtype = ntp.ntpc.TYPE_SYS if associd == 0 else ntp.ntpc.TYPE_PEER 984 silence = bool(len(line.split()) >= 2) 985 # Some scripts written for C ntpq need associd printed here 986 self.__dolist(line.split()[1:], associd, 987 ntp.control.CTL_OP_READVAR, qtype, quiet=silence) 988 989 def help_readvar(self): 990 self.say("""\ 991function: read system or peer variables 992usage: readvar [assocID] [varname1] [varname2] [varname3] 993""") 994 995 def do_rv(self, line): 996 "read system or peer variables" 997 self.do_readvar(line) 998 999 def help_rv(self): 1000 self.say("""\ 1001function: read system or peer variables 1002usage: rv [assocID] [varname1] [varname2] [varname3] 1003""") 1004 1005 def do_writevar(self, line): 1006 "write system or peer variables" 1007 pass 1008 1009 def help_writevar(self): 1010 self.say("""\ 1011function: write system or peer variables 1012usage: writevar assocID name=value,[...] 1013""") 1014 1015 def do_mreadlist(self, line): 1016 "read the peer variables in the variable list for multiple peers" 1017 if not line: 1018 self.warn("usage: mreadlist assocIDlow assocIDhigh\n") 1019 return 1020 idrange = self.__assoc_range_valid(line) 1021 if not idrange: 1022 return 1023 for associd in idrange: 1024 if associd != idrange[0]: 1025 self.say("\n") 1026 if not self.__dolist(self.uservars, 1027 associd, ntp.control.CTL_OP_READVAR, 1028 ntp.ntpc.TYPE_PEER): 1029 return 1030 1031 def help_mreadlist(self): 1032 self.say("""\ 1033function: read the peer variables in the variable list for multiple peers 1034usage: mreadlist assocIDlow assocIDhigh 1035""") 1036 1037 def do_mrl(self, line): 1038 "read the peer variables in the variable list for multiple peers" 1039 if not line: 1040 self.warn("usage: mrl assocIDlow assocIDhigh") 1041 return 1042 self.do_mreadlist(line) 1043 1044 def help_mrl(self): 1045 self.say("""\ 1046function: read the peer variables in the variable list for multiple peers 1047usage: mrl assocIDlow assocIDhigh 1048""") 1049 1050 def do_mreadvar(self, line): 1051 "read peer variables from multiple peers" 1052 if not line: 1053 self.warn("usage: mreadvar assocIDlow assocIDhigh " 1054 "[ name=value[,...] ]") 1055 return 1056 idrange = self.__assoc_range_valid(line) 1057 if not idrange: 1058 return 1059 varlist = line.split()[2:] 1060 for associd in idrange: 1061 if associd != idrange[0]: 1062 self.say("\n") 1063 if not self.__dolist(varlist, associd, 1064 ntp.control.CTL_OP_READVAR, 1065 ntp.ntpc.TYPE_PEER): 1066 return 1067 1068 def help_mreadvar(self): 1069 self.say("""\ 1070function: read peer variables from multiple peers 1071usage: mreadvar assocIDlow assocIDhigh [name=value[,...]] 1072""") 1073 1074 def do_mrv(self, line): 1075 "read peer variables from multiple peers" 1076 if not line: 1077 self.warn( 1078 "usage: mrv assocIDlow assocIDhigh [name=value[,...]]") 1079 return 1080 self.do_mreadvar(line) 1081 1082 def help_mrv(self): 1083 self.say("""\ 1084function: read peer variables from multiple peers 1085usage: mrv assocIDlow assocIDhigh [name=value[,...]] 1086""") 1087 1088 def do_clocklist(self, line): 1089 "read the clock variables included in the variable list" 1090 assoc = self.__assoc_valid(line) 1091 if assoc >= 0: 1092 self.__dolist(self.uservars.keys(), 1093 assoc, ntp.control.CTL_OP_READCLOCK, 1094 ntp.ntpc.TYPE_CLOCK) 1095 1096 def help_clocklist(self): 1097 self.say("""\ 1098function: read the clock variables included in the variable list 1099usage: clocklist [assocID] 1100""") 1101 1102 def do_cl(self, line): 1103 "read the clock variables included in the variable list" 1104 self.do_clocklist(line) 1105 1106 def help_cl(self): 1107 self.say("""\ 1108function: read the clock variables included in the variable list 1109usage: cl [assocID] 1110""") 1111 1112 def do_clockvar(self, line): 1113 "read clock variables" 1114 assoc = self.__assoc_valid(line) 1115 if assoc == 0: 1116 self.warn("This command requires the association ID of a clock.") 1117 elif assoc > 0: 1118 self.__dolist(line.split()[1:], assoc, 1119 ntp.control.CTL_OP_READCLOCK, ntp.ntpc.TYPE_CLOCK) 1120 1121 def help_clockvar(self): 1122 self.say("""\ 1123function: read clock variables 1124usage: clockvar [assocID] [name=value[,...]] 1125""") 1126 1127 def do_cv(self, line): 1128 "read clock variables" 1129 self.do_clockvar(line) 1130 1131 def help_cv(self): 1132 self.say("""\ 1133function: read clock variables 1134usage: cv [ assocID ] [ name=value[,...] ] 1135""") 1136 1137 def do_pstats(self, line): 1138 "show statistics for a peer" 1139 pstats = ( 1140 ("srcadr", "remote host: ", NTP_ADD), 1141 ("dstadr", "local address: ", NTP_ADD), 1142 ("timerec", "time last received: ", NTP_INT), 1143 ("timer", "time until next send: ", NTP_INT), 1144 ("timereach", "reachability change: ", NTP_INT), 1145 ("sent", "packets sent: ", NTP_INT), 1146 ("received", "packets received: ", NTP_INT), 1147 ("badauth", "bad authentication: ", NTP_INT), 1148 ("bogusorg", "bogus origin: ", NTP_INT), 1149 ("oldpkt", "duplicate: ", NTP_INT), 1150 ("seldisp", "bad dispersion: ", NTP_INT), 1151 ("selbroken", "bad reference time: ", NTP_INT), 1152 ("candidate", "candidate order: ", NTP_INT), 1153 ("ntscookies", "count of nts cookies: ", NTP_INT), 1154 ) 1155 if not line: 1156 self.warn("usage: pstats assocID") 1157 return 1158 associd = self.__assoc_valid(line) 1159 if associd >= 0: 1160 self.collect_display(associd=associd, 1161 variables=pstats, decodestatus=True) 1162 1163 def help_pstats(self): 1164 self.say("""\ 1165function: show statistics for a peer 1166usage: pstats assocID 1167""") 1168 1169 def do_peers(self, line): 1170 "obtain and print a list of the server's peers [IP version]" 1171 self.__dopeers(showall=True, mode="peers") 1172 1173 def help_peers(self): 1174 self.say("""\ 1175function: obtain and print a list of the server's peers [IP version] 1176usage: peers 1177""") 1178 1179 def do_rpeers(self, line): 1180 "obtain and print a list of the server's peers (dextral)" 1181 self.__dopeers(showall=True, mode="rpeers") 1182 1183 def help_rpeers(self): 1184 self.say("""\ 1185function: obtain and print a list of the server's peers (dextral) 1186usage: rpeers 1187""") 1188 1189 def do_apeers(self, line): 1190 """ 1191obtain and print a list of the server's peers and their assocIDs [IP version] 1192""" 1193 self.__dopeers(showall=True, mode="apeers") 1194 1195 def help_apeers(self): 1196 self.say("""\ 1197function: obtain and print a list of the server's peers and their 1198 assocIDs [IP version] 1199usage: apeers 1200""") 1201 1202 def do_lpeers(self, line): 1203 "obtain and print a list of all peers and clients [IP version]" 1204 self.__dopeers(showall=True, mode="peers") 1205 1206 def help_lpeers(self): 1207 self.say("""\ 1208function: obtain and print a list of all peers and clients [IP version] 1209usage: lpeers 1210""") 1211 1212 def do_opeers(self, line): 1213 """ 1214print peer list the old way, with dstadr shown rather than refid [IP version] 1215""" 1216 self.__dopeers(showall=True, mode="opeers") 1217 1218 def help_opeers(self): 1219 self.say("""\ 1220function: print peer list the old way, with dstadr shown rather than 1221 refid [IP version] 1222usage: opeers 1223""") 1224 1225 def do_lopeers(self, line): 1226 """obtain and print a list of all peers and clients showing 1227 dstadr [IP version]""" 1228 self.__dopeers(showall=True, mode="opeers") 1229 1230 def help_lopeers(self): 1231 self.say("""\ 1232function: obtain and print a list of all peers and clients showing 1233 dstadr [IP version] 1234usage: lopeers 1235""") 1236 1237 def do_hot_config(self, line): 1238 "send a remote configuration command to ntpd" 1239 try: 1240 self.session.password() 1241 except ntp.packet.ControlException as e: 1242 self.warn(e.message) 1243 return 1244 except IOError: 1245 self.warn("***Can't read control key from /etc/ntp.conf") 1246 return 1247 if self.debug > 2: 1248 self.warn("In Config\nKeyword = :config\nCommand = %s" % line) 1249 try: 1250 self.session.config(line) 1251 self.session.response = ntp.poly.polystr(self.session.response) 1252 m = re.match("column ([0-9]+) syntax error", self.session.response) 1253 if m: 1254 col = int(m.group(1)) 1255 if col >= 0 and col <= len(line): 1256 if self.interactive: 1257 self.say("_" * (len(self.prompt) + 2 + col)) 1258 else: 1259 self.say(line + "\n") 1260 self.say("_" * (col - 1)) 1261 self.say("^\n") 1262 self.say(self.session.response + "\n") 1263 except ntp.packet.ControlException as e: 1264 self.warn(e.message) 1265 1266 def help_hot_config(self): 1267 self.say("""\ 1268function: send a remote configuration command to ntpd 1269usage: config <configuration command line> 1270""") 1271 1272 def do_config_from_file(self, line): 1273 "configure ntpd using the configuration filename" 1274 try: 1275 with open(line) as rfp: 1276 self.say("%s\n" % self.session.config(rfp.read())) 1277 except IOError: 1278 self.warn("Could not read %s" % line) 1279 1280 def help_config_from_file(self): 1281 self.say("""\ 1282function: configure ntpd using the configuration filename 1283usage: config_from_file <configuration filename> 1284""") 1285 1286 def printdirect(self, entries): 1287 for entry in entries: 1288 self.say(self.formatter.summary(entry) + "\n") 1289 1290 def do_noflake(self): 1291 """Disables the dropping of control packets by ntpq for testing.""" 1292 self.session.flakey = False 1293 1294 def help_noflake(self): 1295 """Print help for noflake.""" 1296 self.say("""\ 1297function: Disables the dropping of received packets by ntpq for testing. 1298usage: noflake 1299""") 1300 1301 def do_doflake(self, line): 1302 """Drop some received packets for testing. 1303 1304 Probabilities 0 and 1 should be certainly accepted 1305 and discarded respectively. No default, but 0.1 1306 should be a one in ten loss rate. 1307 """ 1308 try: 1309 _ = float(line) 1310 if not 0 < _ < 1: 1311 raise ValueError 1312 self.session.flakey = _ 1313 except ValueError: 1314 self.say('Flakiness must be a (positive) float less than 1.') 1315 1316 def help_doflake(self): 1317 """Print help for doflake.""" 1318 self.say("""\ 1319function: Enables the dropping of control packets by 1320ntpq for testing. Probabilities 0 and 1 should be 1321certainly accepted and discarded respectively. No 1322default, but 0.1 should be a one in ten loss rate. 1323usage: doflake <probability> 1324""") 1325 1326 def do_mrulist(self, line): 1327 """display the list of most recently seen source addresses, 1328 tags mincount=... resall=0x... resany=0x...""" 1329 cmdvars = {} 1330 for item in line.split(" "): 1331 if not item: 1332 continue 1333 if '=' not in item: 1334 cmdvars[item] = True 1335 else: 1336 eq = item.index("=") 1337 var = item[:eq].strip() 1338 val = item[eq+1:].strip() 1339 try: 1340 val = int(val, 0) 1341 except ValueError: 1342 try: 1343 val = float(val) 1344 except ValueError: 1345 if val[0] == '"' and val[-1] == '"': 1346 val = val[1:-1] 1347 cmdvars[var] = val 1348 1349 if not self.directmode: 1350 self.say("Ctrl-C will stop MRU retrieval and display " 1351 "partial results.\n") 1352 if self.rawmode: 1353 mruhook = lambda v: self.printvars(variables=v, 1354 dtype=ntp.ntpc.TYPE_SYS, 1355 quiet=True) 1356 else: 1357 mruhook = None 1358 try: 1359 formatter = ntp.util.MRUSummary(interpreter.showhostnames, 1360 wideremote=True) 1361 if self.directmode: 1362 formatter.now = None 1363 self.formatter = formatter 1364 if session.debug: 1365 formatter.logfp = session.logfp 1366 formatter.debug = session.debug 1367 self.session.slots = 0 1368 self.session.start = time.time() 1369 direct = self.printdirect if self.directmode else None 1370 span = self.session.mrulist(variables=cmdvars, 1371 rawhook=mruhook, direct=direct) 1372 if not self.directmode and not self.rawmode: 1373 if not span.is_complete(): 1374 self.say("mrulist retrieval interrupted by operator.\n" 1375 "Displaying partial client list.\n") 1376 span.now = time.time() 1377 try: 1378 delta1 = time.time() - self.session.start 1379 self.say(ntp.util.MRUSummary.header + "\n") 1380 self.say(("=" * len(ntp.util.MRUSummary.header)) + "\n") 1381 # reversed puts most recent entries at the top if no sort= 1382 # see sort comments in pylib/packet.py 1383 formatter.now = span.now 1384 for entry in reversed(span.entries): 1385 self.say(formatter.summary(entry) + "\n") 1386 self.say("# Collected %d slots in %.3f seconds\n" 1387 % (self.session.slots, delta1)) 1388 except KeyboardInterrupt: 1389 pass 1390 delta2 = time.time() - self.session.start 1391 self.say("# Processed %d slots in %.3f seconds\n" 1392 % (self.session.slots, delta2)) 1393 usage = resource.getrusage(resource.RUSAGE_SELF) 1394 rusage_denom = 1024. 1395 if sys.platform == 'darwin': 1396 # OSX uses bytes, while every other platform uses kilobytes 1397 rusage_denom = rusage_denom * rusage_denom 1398 self.say("# Used %d megabytes of memory\n" 1399 % (usage.ru_maxrss/rusage_denom)) 1400 except ntp.packet.ControlException as e: 1401 # Giving up after 8 restarts from the beginning. 1402 # With high-traffic NTP servers, this can occur if the 1403 # MRU list is limited to less than about 16 seconds' of 1404 # entries. See the 'mru' ntp.conf entry. 1405 self.warn(e.message) 1406 1407 def help_mrulist(self): 1408 self.say("""\ 1409function: display the list of most recently seen source addresses, 1410 tags mincount=... resall=0x... resany=0x... 1411usage: mrulist [tag=value] [tag=value] [tag=value] [tag=value] 1412""") 1413 1414 def do_ifstats(self, line): 1415 "show statistics for each local address ntpd is using" 1416 try: 1417 self.session.password() 1418 entries = self.session.ifstats() 1419 if self.rawmode: 1420 self.say(self.session.response + "\n") 1421 else: 1422 formatter = ntp.util.IfstatsSummary() 1423 self.say(ntp.util.IfstatsSummary.header) 1424 self.say(("=" * ntp.util.IfstatsSummary.width) + "\n") 1425 for (i, entry) in enumerate(entries): 1426 self.say(formatter.summary(i, entry)) 1427 except ntp.packet.ControlException as e: 1428 self.warn(e.message) 1429 return 1430 except IOError: 1431 self.warn("***Can't read control key from /etc/ntp.conf") 1432 1433 def help_ifstats(self): 1434 self.say("""\ 1435function: show statistics for each local address ntpd is using 1436usage: ifstats 1437""") 1438 1439 def do_reslist(self, line): 1440 "show ntpd access control list" 1441 try: 1442 self.session.password() 1443 entries = self.session.reslist() 1444 if self.rawmode: 1445 self.say(self.session.response + "\n") 1446 else: 1447 formatter = ntp.util.ReslistSummary() 1448 self.say(ntp.util.ReslistSummary.header) 1449 self.say(("=" * ntp.util.ReslistSummary.width) + "\n") 1450 for entry in entries: 1451 self.say(formatter.summary(entry)) 1452 except ntp.packet.ControlException as e: 1453 self.warn(e.message) 1454 return 1455 except IOError: 1456 self.warn("***Can't read control key from /etc/ntp.conf") 1457 1458 def help_reslist(self): 1459 self.say("""\ 1460function: show ntpd access control list 1461usage: reslist 1462""") 1463 1464# FIXME: This table should move to ntpd 1465# so the answers track when ntpd is updated 1466 def do_sysinfo(self, _line): 1467 "display system summary" 1468 sysinfo = ( 1469 ("peeradr", "system peer: ", NTP_ADP), 1470 ("peermode", "system peer mode: ", NTP_MODE), 1471 ("leap", "leap indicator: ", NTP_2BIT), 1472 ("stratum", "stratum: ", NTP_INT), 1473 ("precision", "log2 precision: ", NTP_INT), 1474 ("rootdelay", "root delay: ", NTP_FLOAT), 1475 ("rootdisp", "root dispersion: ", NTP_FLOAT), 1476 ("rootdist", "root distance ", NTP_FLOAT), 1477 ("refid", "reference ID: ", NTP_STR), 1478 ("reftime", "reference time: ", NTP_LFP), 1479 ("sys_jitter", "system jitter: ", NTP_FLOAT), 1480 ("clk_jitter", "clock jitter: ", NTP_FLOAT), 1481 ("clk_wander", "clock wander: ", NTP_FLOAT), 1482 ("authdelay", "symm. auth. delay:", NTP_FLOAT), 1483 ) 1484 self.collect_display(associd=0, variables=sysinfo, decodestatus=True) 1485 1486 def help_sysinfo(self): 1487 self.say("""\ 1488function: display system summary 1489usage: sysinfo 1490""") 1491 1492# FIXME: This table should move to ntpd 1493# so the answers track when ntpd is updated 1494 def do_kerninfo(self, _line): 1495 "display kernel loop and PPS statistics" 1496 kerninfo = ( 1497 ("koffset", "pll offset: ", NTP_FLOAT), 1498 ("kfreq", "pll frequency: ", NTP_FLOAT), 1499 ("kmaxerr", "maximum error: ", NTP_FLOAT), 1500 ("kesterr", "estimated error: ", NTP_FLOAT), 1501 ("kstflags", "kernel status: ", NTP_STR), 1502 ("ktimeconst", "pll time constant: ", NTP_INT), 1503 ("kprecis", "precision: ", NTP_FLOAT), 1504 ("kfreqtol", "frequency tolerance: ", NTP_INT), 1505 ("kppsfreq", "pps frequency: ", NTP_INT), 1506 ("kppsstab", "pps stability: ", NTP_INT), 1507 ("kppsjitter", "pps jitter: ", NTP_INT), 1508 ("kppscalibdur", "calibration interval ", NTP_INT), 1509 ("kppscalibs", "calibration cycles: ", NTP_INT), 1510 ("kppsjitexc", "jitter exceeded: ", NTP_INT), 1511 ("kppsstbexc", "stability exceeded: ", NTP_INT), 1512 ("kppscaliberrs", "calibration errors: ", NTP_INT), 1513 ) 1514 self.collect_display(associd=0, variables=kerninfo, decodestatus=True) 1515 1516 def help_kerninfo(self): 1517 self.say("""\ 1518function: display kernel loop and PPS statistics 1519usage: kerninfo 1520""") 1521 1522# FIXME: This table should move to ntpd 1523# so the answers track when ntpd is updated 1524 def do_sysstats(self, _line): 1525 "display system uptime and packet counts" 1526 sysstats = ( 1527 ("ss_uptime", "uptime: ", NTP_INT), 1528 ("ss_numctlreq", "control requests: ", NTP_INT), 1529 ("ss_reset", "sysstats reset: ", NTP_UPTIME), 1530 ("ss_received", "packets received: ", NTP_INT), 1531 ("ss_thisver", "current version: ", NTP_INT), 1532 ("ss_oldver", "older version: ", NTP_INT), 1533 ("ss_badformat", "bad length or format: ", NTP_INT), 1534 ("ss_badauth", "authentication failed:", NTP_INT), 1535 ("ss_declined", "declined: ", NTP_INT), 1536 ("ss_restricted", "restricted: ", NTP_INT), 1537 ("ss_limited", "rate limited: ", NTP_INT), 1538 ("ss_kodsent", "KoD responses: ", NTP_INT), 1539 ("ss_processed", "processed for time: ", NTP_INT), 1540 ) 1541 self.collect_display(associd=0, variables=sysstats, decodestatus=False) 1542 1543 def help_sysstats(self): 1544 self.say("""\ 1545function: display system uptime and packet counts 1546usage: sysstats 1547""") 1548 1549# FIXME: This table should move to ntpd 1550# so the answers track when ntpd is updated 1551 def do_monstats(self, _line): 1552 "display monitor (mrulist) counters and limits" 1553 monstats = ( 1554 ("mru_enabled", "enabled: ", NTP_INT), 1555 ("mru_hashslots", "hash slots in use: ", NTP_INT), 1556 ("mru_depth", "addresses in use: ", NTP_INT), 1557 ("mru_deepest", "peak addresses: ", NTP_INT), 1558 ("mru_maxdepth", "maximum addresses: ", NTP_INT), 1559 ("mru_mindepth", "reclaim above count: ", NTP_INT), 1560 ("mru_maxage", "reclaim maxage: ", NTP_INT), 1561 ("mru_minage", "reclaim minage: ", NTP_INT), 1562 ("mru_mem", "kilobytes: ", NTP_INT), 1563 ("mru_maxmem", "maximum kilobytes: ", NTP_INT), 1564 ("mru_exists", "alloc: exists: ", NTP_INT), 1565 ("mru_new", "alloc: new: ", NTP_INT), 1566 ("mru_recycleold", "alloc: recycle old: ", NTP_INT), 1567 ("mru_recyclefull", "alloc: recycle full: ", NTP_INT), 1568 ("mru_none", "alloc: none: ", NTP_INT), 1569 ("mru_oldest_age", "age of oldest slot: ", NTP_INT), 1570 ) 1571 self.collect_display(associd=0, variables=monstats, decodestatus=False) 1572 1573 def help_monstats(self): 1574 self.say("""\ 1575function: display monitor (mrulist) counters and limits 1576usage: monstats 1577""") 1578 1579# FIXME: This table should move to ntpd 1580# so the answers track when ntpd is updated 1581 def do_authinfo(self, _line): 1582 "display symmetric authentication counters" 1583 authinfo = ( 1584 ("authreset", "time since reset: ", NTP_INT), 1585 ("authkeys", "stored keys: ", NTP_INT), 1586 ("authfreek", "free keys: ", NTP_INT), 1587 ("authklookups", "key lookups: ", NTP_INT), 1588 ("authknotfound", "keys not found: ", NTP_INT), 1589 ("authencrypts", "encryptions: ", NTP_INT), 1590 ("authdigestencrypts", "digest encryptions: ", NTP_INT), 1591 ("authcmacencrypts", "CMAC encryptions: ", NTP_INT), 1592 ("authdecrypts", "decryptions: ", NTP_INT), 1593 ("authdigestdecrypts", "digest decryptions: ", NTP_INT), 1594 ("authdigestfails", "digest failures: ", NTP_INT), 1595 ("authcmacdecrypts", "CMAC decryptions: ", NTP_INT), 1596 ("authcmacfails", "CMAC failures: ", NTP_INT), 1597 # Old variables no longer supported. 1598 # Interesting if looking at an old system. 1599 ("authkuncached", "uncached keys: ", NTP_INT), 1600 ("authkexpired", "expired keys: ", NTP_INT), 1601 ) 1602 self.collect_display(associd=0, variables=authinfo, decodestatus=False) 1603 1604 def help_authinfo(self): 1605 self.say("""\ 1606function: display symmetric authentication counters 1607usage: authinfo 1608""") 1609 1610# FIXME: This table should move to ntpd 1611# so the answers track when ntpd is updated 1612 def do_ntsinfo(self, _line): 1613 "display NTS authentication counters" 1614 ntsinfo = ( 1615 ("nts_client_send", "NTS client sends: ", NTP_UINT), 1616 ("nts_client_recv_good", "NTS client recvs good: ", NTP_UINT), 1617 ("nts_client_recv_bad", "NTS client recvs w error: ", NTP_UINT), 1618 ("nts_server_recv_good", "NTS server recvs good: ", NTP_UINT), 1619 ("nts_server_recv_bad", "NTS server recvs w error: ", NTP_UINT), 1620 ("nts_server_send", "NTS server sends: ", NTP_UINT), 1621 ("nts_cookie_make", "NTS make cookies: ", NTP_UINT), 1622 ("nts_cookie_decode", "NTS decode cookies: ", NTP_UINT), 1623 ("nts_cookie_decode_old", "NTS decode cookies old: ", NTP_UINT), 1624 ("nts_cookie_decode_too_old", "NTS decode cookies too old:", NTP_UINT), 1625 ("nts_cookie_decode_error", "NTS decode cookies error: ", NTP_UINT), 1626 ("nts_ke_probes_good", "NTS KE client probes good: ", NTP_UINT), 1627 ("nts_ke_probes_bad", "NTS KE client probes bad: ", NTP_UINT), 1628 ("nts_ke_serves_good", "NTS KE serves good: ", NTP_UINT), 1629 ("nts_ke_serves_bad", "NTS KE serves bad: ", NTP_UINT), 1630 ) 1631 self.collect_display(associd=0, variables=ntsinfo, decodestatus=False) 1632 1633 def help_ntsinfo(self): 1634 self.say("""\ 1635function: display NTS authentication counters 1636usage: ntsinfo 1637""") 1638 1639 1640# FIXME: This table should move to ntpd 1641# so the answers track when ntpd is updated 1642 def do_iostats(self, _line): 1643 "display network input and output counters" 1644 iostats = ( 1645 ("iostats_reset", "time since reset: ", NTP_INT), 1646 ("total_rbuf", "receive buffers: ", NTP_INT), 1647 ("free_rbuf", "free receive buffers: ", NTP_INT), 1648 ("used_rbuf", "used receive buffers: ", NTP_INT), 1649 ("rbuf_lowater", "low water refills: ", NTP_INT), 1650 ("io_dropped", "dropped packets: ", NTP_INT), 1651 ("io_ignored", "ignored packets: ", NTP_INT), 1652 ("io_received", "received packets: ", NTP_INT), 1653 ("io_sent", "packets sent: ", NTP_INT), 1654 ("io_sendfailed", "packet send failures: ", NTP_INT), 1655 ("io_wakeups", "input wakeups: ", NTP_INT), 1656 ("io_goodwakeups", "useful input wakeups: ", NTP_INT), 1657 ) 1658 self.collect_display(associd=0, variables=iostats, decodestatus=False) 1659 1660 def help_iostats(self): 1661 self.say("""\ 1662function: display network input and output counters 1663usage: iostats 1664""") 1665 1666# FIXME: This table should move to ntpd 1667# so the answers track when ntpd is updated 1668 def do_timerstats(self, line): 1669 "display interval timer counters" 1670 timerstats = ( 1671 ("timerstats_reset", "time since reset: ", NTP_INT), 1672 ("timer_overruns", "timer overruns: ", NTP_INT), 1673 ("timer_xmts", "calls to transmit: ", NTP_INT), 1674 ) 1675 self.collect_display(associd=0, variables=timerstats, 1676 decodestatus=False) 1677 1678 def help_timerstats(self): 1679 self.say("""\ 1680function: display interval timer counters 1681usage: timerstats 1682""") 1683 1684 1685# Default values we use. 1686DEFHOST = "localhost" # default host name 1687 1688# 1689# main - parse arguments and handle options 1690# 1691 1692usage = ''' 1693USAGE: ntpq [-46dphinOV] [-c str] [-D lvl] [host ...] 1694 Flg Arg Option-Name Description 1695 -4 no ipv4 Force IPv4 DNS name resolution 1696 - prohibits the option 'ipv6' 1697 -6 no ipv6 Force IPv6 DNS name resolution 1698 - prohibits the option 'ipv4' 1699 -a Num authentication Enable authentication with the numbered key 1700 -c Str command Run a command and exit 1701 - may appear multiple times 1702 -d no debug-level Increase output debug message level 1703 - may appear multiple times 1704 -l Str logfile Logs debug messages to the provided filename 1705 -D Int set-debug-level Set the output debug message level 1706 - may appear multiple times 1707 -h no help Print a usage message. 1708 -p no peers Print a list of the peers 1709 -n no numeric Numeric host addresses 1710 -k Str keyfile Specify a keyfile. ntpq will look in this file 1711 for the key specified with -a 1712 -V opt version Output version information and exit 1713 -w no wide Enable wide display of addresses / hosts 1714 on a separate line 1715 -W Num width Force output width to this value instead of 1716 querying the terminal size 1717 -u no units Display time with units. 1718''' 1719 1720if __name__ == '__main__': 1721 bin_ver = "ntpsec-@NTPSEC_VERSION_EXTENDED@" 1722 if ntp.util.stdversion() != bin_ver: 1723 sys.stderr.write("Module/Binary version mismatch\n") 1724 sys.stderr.write("Binary: %s\n" % bin_ver) 1725 sys.stderr.write("Module: %s\n" % ntp.util.stdversion()) 1726 try: 1727 (options, arguments) = getopt.getopt( 1728 sys.argv[1:], 1729 "46a:c:dD:hk:npsSVwW:ul:", 1730 ["ipv4", "ipv6", "authentication=", 1731 "command=", "debug", "set-debug-level=", 1732 "help", "keyfile", "numeric", "peers", 1733 "version", "wide", "width=", "units", 1734 "logfile=", "srcname", "srcnumber"]) 1735 except getopt.GetoptError as e: 1736 sys.stderr.write("%s\n" % e) 1737 sys.stderr.write(usage) 1738 raise SystemExit(1) 1739 progname = os.path.basename(sys.argv[0]) 1740 ntp.ntpc.setprogname(progname) 1741 1742 session = ntp.packet.ControlSession() 1743 interpreter = Ntpq(session) 1744 1745 keyid = keyfile = None 1746 logfp = sys.stderr 1747 1748 for (switch, val) in options: 1749 if switch in ("-4", "--ipv4"): 1750 interpreter.ai_family = socket.AF_INET 1751 elif switch in ("-6", "--ipv6"): 1752 interpreter.ai_family = socket.AF_INET6 1753 elif switch in ("-a", "--authentication"): 1754 errmsg = "Error: -a parameter '%s' not a number\n" 1755 keyid = ntp.util.safeargcast(val, int, errmsg, usage) 1756 elif switch in ("-c", "--command"): 1757 interpreter.ccmds.append(val) 1758 elif switch in ("-d", "--debug"): 1759 interpreter.debug += 1 1760 session.debug += 1 1761 elif switch in ("-D", "--set-debug-level"): 1762 errmsg = "Error: -D parameter '%s' not a number\n" 1763 cast = ntp.util.safeargcast(val, int, errmsg, usage) 1764 session.debug = interpreter.debug = cast 1765 elif switch in ("-h", "--help"): 1766 sys.stderr.write(usage) 1767 raise SystemExit(0) 1768 elif switch in ("-n", "--numeric"): 1769 interpreter.showhostnames = 0 1770 elif switch in ("-p", "--peers"): 1771 interpreter.ccmds.append("peers") 1772 elif switch in ("-k", "--keyfile"): 1773 keyfile = val 1774 elif switch in ("-s", "--srcname"): 1775 interpreter.showhostnames = 3 1776 elif switch in ("-S", "--srcnumber"): 1777 interpreter.showhostnames = 2 1778 elif switch in ("-V", "--version"): 1779 sys.stdout.write("ntpq %s\n" % version) 1780 raise SystemExit(0) 1781 elif switch in ("-w", "--wide"): 1782 interpreter.wideremote = True 1783 elif switch in ("-W", "--width"): 1784 errmsg = "Error: -W parameter '%s' not a number\n" 1785 interpreter.termwidth = ntp.util.safeargcast(val, int, 1786 errmsg, usage) 1787 elif switch in ("-u", "--units"): 1788 interpreter.showunits = True 1789 elif switch in ("-l", "--logfile"): 1790 if logfp != sys.stderr: 1791 logfp.close() 1792 logfp = open(val, "a", 1) # 1 => line buffered 1793 1794 session.logfp = interpreter.logfp = logfp 1795 1796 if ntp.poly.forced_utf8 and interpreter.debug: 1797 interpreter.warn("\nforced UTF-8 output\n") 1798 1799 if keyfile is not None: # Have a -k, setup the auth 1800 credentials = None 1801 try: 1802 credentials = ntp.packet.Authenticator(keyfile) 1803 except (OSError, IOError): 1804 sys.stderr.write("ntpq: %s nonexistent or unreadable" % keyfile) 1805 raise SystemExit(1) 1806 if credentials: 1807 session.auth = credentials 1808 if keyid is not None: # Have an -a 1809 session.keyid = keyid 1810 1811 for token in arguments: 1812 if token.startswith("-"): 1813 if '4' == token[-1]: 1814 session.ai_family = socket.AF_INET 1815 elif '6' == token[-1]: 1816 session.ai_family = socket.AF_INET6 1817 else: 1818 interpreter.warn("%s: unexpected option-like thing." 1819 % progname) 1820 raise SystemExit(1) 1821 arguments.pop(0) 1822 else: 1823 interpreter.chosts.append((token, session.ai_family)) 1824 1825 if not arguments: 1826 interpreter.chosts.append((DEFHOST, session.ai_family)) 1827 1828 if (not interpreter.ccmds and 1829 not interpreter.interactive and 1830 os.isatty(0) and 1831 os.isatty(1)): 1832 interpreter.interactive = True 1833 1834 try: 1835 if not interpreter.ccmds: 1836 if len(interpreter.chosts) > 1: 1837 interpreter.warn( 1838 "ntpq can only work interactively on one host.") 1839 interpreter.chosts = interpreter.chosts[:1] 1840 session.openhost(*interpreter.chosts[0]) 1841 interpreter.cmdloop() 1842 else: 1843 for ihost in interpreter.chosts: 1844 if session.openhost(*ihost): 1845 for command in interpreter.ccmds: 1846 interpreter.onecmd(interpreter.precmd(command)) 1847 session.close() 1848 raise SystemExit(0) 1849 except (KeyboardInterrupt, EOFError): 1850 if os.isatty(0): 1851 interpreter.say("\n") 1852 except ntp.packet.ControlException as e: 1853 interpreter.warn(e.message) 1854 except IOError: 1855 sys.stderr.write("Bailing out...\n") 1856# end 1857