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