/* chronyd/chronyc - Programs for keeping computer clocks accurate. ********************************************************************** * Copyright (C) Richard P. Curnow 1997-2003 * Copyright (C) Lonnie Abelbeck 2016, 2018 * Copyright (C) Miroslav Lichvar 2009-2023 * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ********************************************************************** ======================================================================= Command line client for configuring the daemon and obtaining status from it whilst running. */ #include "config.h" #include "sysincl.h" #include "array.h" #include "candm.h" #include "cmac.h" #include "logging.h" #include "memory.h" #include "nameserv.h" #include "getdate.h" #include "cmdparse.h" #include "pktlength.h" #include "socket.h" #include "util.h" #ifdef FEAT_READLINE #include #endif /* ================================================== */ struct Address { SCK_AddressType type; union { IPSockAddr ip; char *path; } addr; }; static ARR_Instance server_addresses; static int sock_fd = -1; static volatile int quit = 0; static int on_terminal = 0; static int no_dns = 0; static int source_names = 0; static int csv_mode = 0; static int end_dot = 0; /* ================================================== */ /* Log a message. This is a minimalistic replacement of the logging.c implementation to avoid linking with it and other modules. */ LOG_Severity log_min_severity = LOGS_INFO; void LOG_Message(LOG_Severity severity, #if DEBUG > 0 int line_number, const char *filename, const char *function_name, #endif const char *format, ...) { va_list ap; if (severity < log_min_severity) return; va_start(ap, format); vfprintf(stderr, format, ap); putc('\n', stderr); va_end(ap); } /* ================================================== */ /* Read a single line of commands from standard input */ #ifdef FEAT_READLINE static char **command_name_completion(const char *text, int start, int end); #endif static char * read_line(void) { static char line[2048]; static const char *prompt = "chronyc> "; if (on_terminal) { #ifdef FEAT_READLINE char *cmd; rl_attempted_completion_function = command_name_completion; rl_basic_word_break_characters = " \t\n\r"; /* save line only if not empty */ cmd = readline(prompt); if( cmd == NULL ) return( NULL ); /* user pressed return */ if( *cmd != '\0' ) { strncpy(line, cmd, sizeof(line) - 1); line[sizeof(line) - 1] = '\0'; add_history(cmd); /* free the buffer allocated by readline */ Free(cmd); } else { /* simulate the user has entered an empty line */ *line = '\0'; } return( line ); #else printf("%s", prompt); fflush(stdout); #endif } if (fgets(line, sizeof(line), stdin)) { return line; } else { return NULL; } } /* ================================================== */ static ARR_Instance get_addresses(const char *hostnames, int port) { struct Address *addr; ARR_Instance addrs; char *hostname, *s1, *s2; IPAddr ip_addrs[DNS_MAX_ADDRESSES]; int i; addrs = ARR_CreateInstance(sizeof (*addr)); s1 = Strdup(hostnames); /* Parse the comma-separated list of hostnames */ for (hostname = s1; hostname && *hostname; hostname = s2) { s2 = strchr(hostname, ','); if (s2) *s2++ = '\0'; /* hostname starting with / is considered a path of Unix domain socket */ if (hostname[0] == '/') { addr = ARR_GetNewElement(addrs); addr->type = SCK_ADDR_UNIX; addr->addr.path = Strdup(hostname); } else { if (DNS_Name2IPAddress(hostname, ip_addrs, DNS_MAX_ADDRESSES) != DNS_Success) { DEBUG_LOG("Could not get IP address for %s", hostname); continue; } for (i = 0; i < DNS_MAX_ADDRESSES && ip_addrs[i].family != IPADDR_UNSPEC; i++) { addr = ARR_GetNewElement(addrs); addr->type = SCK_ADDR_IP; addr->addr.ip.ip_addr = ip_addrs[i]; addr->addr.ip.port = port; DEBUG_LOG("Resolved %s to %s", hostname, UTI_IPToString(&ip_addrs[i])); } } } Free(s1); return addrs; } /* ================================================== */ static void free_addresses(ARR_Instance addresses) { struct Address *addr; unsigned int i; for (i = 0; i < ARR_GetSize(addresses); i++) { addr = ARR_GetElement(addresses, i); if (addr->type == SCK_ADDR_UNIX) Free(addr->addr.path); } ARR_DestroyInstance(addresses); } /* ================================================== */ /* Initialise the socket used to talk to the daemon */ static int open_socket(struct Address *addr) { char *dir, *local_addr; size_t local_addr_len; switch (addr->type) { case SCK_ADDR_IP: sock_fd = SCK_OpenUdpSocket(&addr->addr.ip, NULL, NULL, 0); break; case SCK_ADDR_UNIX: /* Construct path of our socket. Use the same directory as the server socket and include our process ID to allow multiple chronyc instances running at the same time. */ dir = UTI_PathToDir(addr->addr.path); local_addr_len = strlen(dir) + 50; local_addr = Malloc(local_addr_len); snprintf(local_addr, local_addr_len, "%s/chronyc.%d.sock", dir, (int)getpid()); sock_fd = SCK_OpenUnixDatagramSocket(addr->addr.path, local_addr, SCK_FLAG_ALL_PERMISSIONS); Free(dir); Free(local_addr); break; default: assert(0); } if (sock_fd < 0) return 0; return 1; } /* ================================================== */ static void close_io(void) { if (sock_fd < 0) return; SCK_RemoveSocket(sock_fd); SCK_CloseSocket(sock_fd); sock_fd = -1; } /* ================================================== */ static int open_io(void) { static unsigned int address_index = 0; struct Address *addr; /* If a socket is already opened, close it and try the next address */ if (sock_fd >= 0) { close_io(); address_index++; } /* Find an address for which a socket can be opened and connected */ for (; address_index < ARR_GetSize(server_addresses); address_index++) { addr = ARR_GetElement(server_addresses, address_index); if (open_socket(addr)) return 1; close_io(); } /* Start from the first address if called again */ address_index = 0; return 0; } /* ================================================== */ static void bits_to_mask(int bits, int family, IPAddr *mask) { int i; mask->family = family; switch (family) { case IPADDR_INET4: if (bits > 32 || bits < 0) bits = 32; if (bits > 0) { mask->addr.in4 = -1; mask->addr.in4 <<= 32 - bits; } else { mask->addr.in4 = 0; } break; case IPADDR_INET6: if (bits > 128 || bits < 0) bits = 128; for (i = 0; i < bits / 8; i++) mask->addr.in6[i] = 0xff; if (i < 16) mask->addr.in6[i++] = (0xff << (8 - bits % 8)) & 0xff; for (; i < 16; i++) mask->addr.in6[i] = 0x0; break; case IPADDR_ID: mask->family = IPADDR_UNSPEC; break; default: assert(0); } } /* ================================================== */ static int parse_source_address(char *word, IPAddr *address) { if (UTI_StringToIdIP(word, address)) return 1; if (DNS_Name2IPAddress(word, address, 1) == DNS_Success) return 1; return 0; } /* ================================================== */ static int read_mask_address(char *line, IPAddr *mask, IPAddr *address) { unsigned int bits; char *p, *q; p = line; if (!*p) { mask->family = address->family = IPADDR_UNSPEC; return 1; } else { q = strchr(p, '/'); if (q) { *q++ = 0; if (UTI_StringToIP(p, mask)) { p = q; if (UTI_StringToIP(p, address)) { if (address->family == mask->family) return 1; } else if (sscanf(p, "%u", &bits) == 1) { *address = *mask; bits_to_mask(bits, address->family, mask); return 1; } } } else { if (parse_source_address(p, address)) { bits_to_mask(-1, address->family, mask); return 1; } else { LOG(LOGS_ERR, "Could not get address for hostname"); return 0; } } } LOG(LOGS_ERR, "Invalid syntax for mask/address"); return 0; } /* ================================================== */ static int process_cmd_offline(CMD_Request *msg, char *line) { IPAddr mask, address; int ok; if (read_mask_address(line, &mask, &address)) { UTI_IPHostToNetwork(&mask, &msg->data.offline.mask); UTI_IPHostToNetwork(&address, &msg->data.offline.address); msg->command = htons(REQ_OFFLINE); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static int process_cmd_online(CMD_Request *msg, char *line) { IPAddr mask, address; int ok; if (read_mask_address(line, &mask, &address)) { UTI_IPHostToNetwork(&mask, &msg->data.online.mask); UTI_IPHostToNetwork(&address, &msg->data.online.address); msg->command = htons(REQ_ONLINE); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static void process_cmd_onoffline(CMD_Request *msg, char *line) { msg->command = htons(REQ_ONOFFLINE); } /* ================================================== */ static int read_address_integer(char *line, IPAddr *address, int *value) { char *hostname; int ok = 0; hostname = line; line = CPS_SplitWord(line); if (sscanf(line, "%d", value) != 1) { LOG(LOGS_ERR, "Invalid syntax for address value"); ok = 0; } else { if (!parse_source_address(hostname, address)) { LOG(LOGS_ERR, "Could not get address for hostname"); ok = 0; } else { ok = 1; } } return ok; } /* ================================================== */ static int read_address_double(char *line, IPAddr *address, double *value) { char *hostname; int ok = 0; hostname = line; line = CPS_SplitWord(line); if (sscanf(line, "%lf", value) != 1) { LOG(LOGS_ERR, "Invalid syntax for address value"); ok = 0; } else { if (!parse_source_address(hostname, address)) { LOG(LOGS_ERR, "Could not get address for hostname"); ok = 0; } else { ok = 1; } } return ok; } /* ================================================== */ static int process_cmd_minpoll(CMD_Request *msg, char *line) { IPAddr address; int minpoll; int ok; if (read_address_integer(line, &address, &minpoll)) { UTI_IPHostToNetwork(&address, &msg->data.modify_minpoll.address); msg->data.modify_minpoll.new_minpoll = htonl(minpoll); msg->command = htons(REQ_MODIFY_MINPOLL); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static int process_cmd_maxpoll(CMD_Request *msg, char *line) { IPAddr address; int maxpoll; int ok; if (read_address_integer(line, &address, &maxpoll)) { UTI_IPHostToNetwork(&address, &msg->data.modify_maxpoll.address); msg->data.modify_maxpoll.new_maxpoll = htonl(maxpoll); msg->command = htons(REQ_MODIFY_MAXPOLL); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static int process_cmd_maxdelay(CMD_Request *msg, char *line) { IPAddr address; double max_delay; int ok; if (read_address_double(line, &address, &max_delay)) { UTI_IPHostToNetwork(&address, &msg->data.modify_maxdelay.address); msg->data.modify_maxdelay.new_max_delay = UTI_FloatHostToNetwork(max_delay); msg->command = htons(REQ_MODIFY_MAXDELAY); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static int process_cmd_maxdelaydevratio(CMD_Request *msg, char *line) { IPAddr address; double max_delay_dev_ratio; int ok; if (read_address_double(line, &address, &max_delay_dev_ratio)) { UTI_IPHostToNetwork(&address, &msg->data.modify_maxdelaydevratio.address); msg->data.modify_maxdelayratio.new_max_delay_ratio = UTI_FloatHostToNetwork(max_delay_dev_ratio); msg->command = htons(REQ_MODIFY_MAXDELAYDEVRATIO); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static int process_cmd_maxdelayratio(CMD_Request *msg, char *line) { IPAddr address; double max_delay_ratio; int ok; if (read_address_double(line, &address, &max_delay_ratio)) { UTI_IPHostToNetwork(&address, &msg->data.modify_maxdelayratio.address); msg->data.modify_maxdelayratio.new_max_delay_ratio = UTI_FloatHostToNetwork(max_delay_ratio); msg->command = htons(REQ_MODIFY_MAXDELAYRATIO); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static int process_cmd_minstratum(CMD_Request *msg, char *line) { IPAddr address; int min_stratum; int ok; if (read_address_integer(line, &address, &min_stratum)) { UTI_IPHostToNetwork(&address, &msg->data.modify_minstratum.address); msg->data.modify_minstratum.new_min_stratum = htonl(min_stratum); msg->command = htons(REQ_MODIFY_MINSTRATUM); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static int process_cmd_polltarget(CMD_Request *msg, char *line) { IPAddr address; int poll_target; int ok; if (read_address_integer(line, &address, &poll_target)) { UTI_IPHostToNetwork(&address, &msg->data.modify_polltarget.address); msg->data.modify_polltarget.new_poll_target = htonl(poll_target); msg->command = htons(REQ_MODIFY_POLLTARGET); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static int process_cmd_maxupdateskew(CMD_Request *msg, char *line) { int ok; double new_max_update_skew; if (sscanf(line, "%lf", &new_max_update_skew) == 1) { msg->data.modify_maxupdateskew.new_max_update_skew = UTI_FloatHostToNetwork(new_max_update_skew); msg->command = htons(REQ_MODIFY_MAXUPDATESKEW); ok = 1; } else { ok = 0; } return ok; } /* ================================================== */ static void process_cmd_dump(CMD_Request *msg, char *line) { msg->command = htons(REQ_DUMP); msg->data.dump.pad = htonl(0); } /* ================================================== */ static void process_cmd_writertc(CMD_Request *msg, char *line) { msg->command = htons(REQ_WRITERTC); } /* ================================================== */ static void process_cmd_trimrtc(CMD_Request *msg, char *line) { msg->command = htons(REQ_TRIMRTC); } /* ================================================== */ static void process_cmd_cyclelogs(CMD_Request *msg, char *line) { msg->command = htons(REQ_CYCLELOGS); } /* ================================================== */ static int process_cmd_burst(CMD_Request *msg, char *line) { int n_good_samples, n_total_samples; char *s1, *s2; IPAddr address, mask; s1 = line; s2 = CPS_SplitWord(s1); CPS_SplitWord(s2); if (sscanf(s1, "%d/%d", &n_good_samples, &n_total_samples) != 2) { LOG(LOGS_ERR, "Invalid syntax for burst command"); return 0; } mask.family = address.family = IPADDR_UNSPEC; if (*s2 && !read_mask_address(s2, &mask, &address)) { return 0; } msg->command = htons(REQ_BURST); msg->data.burst.n_good_samples = ntohl(n_good_samples); msg->data.burst.n_total_samples = ntohl(n_total_samples); UTI_IPHostToNetwork(&mask, &msg->data.burst.mask); UTI_IPHostToNetwork(&address, &msg->data.burst.address); return 1; } /* ================================================== */ static int process_cmd_local(CMD_Request *msg, char *line) { int on_off, stratum = 0, orphan = 0; double distance = 0.0; if (!strcmp(line, "off")) { on_off = 0; } else if (CPS_ParseLocal(line, &stratum, &orphan, &distance)) { on_off = 1; } else { LOG(LOGS_ERR, "Invalid syntax for local command"); return 0; } msg->command = htons(REQ_LOCAL2); msg->data.local.on_off = htonl(on_off); msg->data.local.stratum = htonl(stratum); msg->data.local.distance = UTI_FloatHostToNetwork(distance); msg->data.local.orphan = htonl(orphan); return 1; } /* ================================================== */ static int process_cmd_manual(CMD_Request *msg, const char *line) { const char *p; p = line; if (!strcmp(p, "off")) { msg->data.manual.option = htonl(0); } else if (!strcmp(p, "on")) { msg->data.manual.option = htonl(1); } else if (!strcmp(p, "reset")) { msg->data.manual.option = htonl(2); } else { LOG(LOGS_ERR, "Invalid syntax for manual command"); return 0; } msg->command = htons(REQ_MANUAL); return 1; } /* ================================================== */ static int process_cmd_allowdeny(CMD_Request *msg, char *line, int cmd, int allcmd) { int all, subnet_bits; IPAddr ip; if (!CPS_ParseAllowDeny(line, &all, &ip, &subnet_bits)) { LOG(LOGS_ERR, "Could not read address"); return 0; } msg->command = htons(all ? allcmd : cmd); UTI_IPHostToNetwork(&ip, &msg->data.allow_deny.ip); msg->data.allow_deny.subnet_bits = htonl(subnet_bits); return 1; } /* ================================================== */ static int process_cmd_accheck(CMD_Request *msg, char *line) { IPAddr ip; msg->command = htons(REQ_ACCHECK); if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) { UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip); return 1; } else { LOG(LOGS_ERR, "Could not read address"); return 0; } } /* ================================================== */ static int process_cmd_cmdaccheck(CMD_Request *msg, char *line) { IPAddr ip; msg->command = htons(REQ_CMDACCHECK); if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) { UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip); return 1; } else { LOG(LOGS_ERR, "Could not read address"); return 0; } } /* ================================================== */ static int process_cmd_dfreq(CMD_Request *msg, char *line) { double dfreq; msg->command = htons(REQ_DFREQ); if (sscanf(line, "%lf", &dfreq) != 1) { LOG(LOGS_ERR, "Invalid value"); return 0; } msg->data.dfreq.dfreq = UTI_FloatHostToNetwork(dfreq); return 1; } /* ================================================== */ static int process_cmd_doffset(CMD_Request *msg, char *line) { double doffset; msg->command = htons(REQ_DOFFSET2); if (sscanf(line, "%lf", &doffset) != 1) { LOG(LOGS_ERR, "Invalid value"); return 0; } msg->data.doffset.doffset = UTI_FloatHostToNetwork(doffset); return 1; } /* ================================================== */ static int convert_addsrc_sel_options(int options) { return (options & SRC_SELECT_PREFER ? REQ_ADDSRC_PREFER : 0) | (options & SRC_SELECT_NOSELECT ? REQ_ADDSRC_NOSELECT : 0) | (options & SRC_SELECT_TRUST ? REQ_ADDSRC_TRUST : 0) | (options & SRC_SELECT_REQUIRE ? REQ_ADDSRC_REQUIRE : 0); } /* ================================================== */ static int process_cmd_add_source(CMD_Request *msg, char *line) { CPS_NTP_Source data; IPAddr ip_addr; int result = 0, status, type; const char *opt_name, *word; msg->command = htons(REQ_ADD_SOURCE); word = line; line = CPS_SplitWord(line); if (!strcasecmp(word, "server")) { type = REQ_ADDSRC_SERVER; } else if (!strcasecmp(word, "peer")) { type = REQ_ADDSRC_PEER; } else if (!strcasecmp(word, "pool")) { type = REQ_ADDSRC_POOL; } else { LOG(LOGS_ERR, "Invalid syntax for add command"); return 0; } status = CPS_ParseNTPSourceAdd(line, &data); switch (status) { case 0: LOG(LOGS_ERR, "Invalid syntax for add command"); break; default: /* Verify that the address is resolvable (chronyc and chronyd are assumed to be running on the same host) */ if (strlen(data.name) >= sizeof (msg->data.ntp_source.name) || DNS_Name2IPAddress(data.name, &ip_addr, 1) != DNS_Success) { LOG(LOGS_ERR, "Invalid host/IP address"); break; } opt_name = NULL; if (opt_name) { LOG(LOGS_ERR, "%s can't be set in chronyc", opt_name); break; } msg->data.ntp_source.type = htonl(type); if (strlen(data.name) >= sizeof (msg->data.ntp_source.name)) assert(0); strncpy((char *)msg->data.ntp_source.name, data.name, sizeof (msg->data.ntp_source.name)); msg->data.ntp_source.port = htonl(data.port); msg->data.ntp_source.minpoll = htonl(data.params.minpoll); msg->data.ntp_source.maxpoll = htonl(data.params.maxpoll); msg->data.ntp_source.presend_minpoll = htonl(data.params.presend_minpoll); msg->data.ntp_source.min_stratum = htonl(data.params.min_stratum); msg->data.ntp_source.poll_target = htonl(data.params.poll_target); msg->data.ntp_source.version = htonl(data.params.version); msg->data.ntp_source.max_sources = htonl(data.params.max_sources); msg->data.ntp_source.min_samples = htonl(data.params.min_samples); msg->data.ntp_source.max_samples = htonl(data.params.max_samples); msg->data.ntp_source.authkey = htonl(data.params.authkey); msg->data.ntp_source.nts_port = htonl(data.params.nts_port); msg->data.ntp_source.max_delay = UTI_FloatHostToNetwork(data.params.max_delay); msg->data.ntp_source.max_delay_ratio = UTI_FloatHostToNetwork(data.params.max_delay_ratio); msg->data.ntp_source.max_delay_dev_ratio = UTI_FloatHostToNetwork(data.params.max_delay_dev_ratio); msg->data.ntp_source.min_delay = UTI_FloatHostToNetwork(data.params.min_delay); msg->data.ntp_source.asymmetry = UTI_FloatHostToNetwork(data.params.asymmetry); msg->data.ntp_source.offset = UTI_FloatHostToNetwork(data.params.offset); msg->data.ntp_source.flags = htonl( (data.params.connectivity == SRC_ONLINE ? REQ_ADDSRC_ONLINE : 0) | (data.params.auto_offline ? REQ_ADDSRC_AUTOOFFLINE : 0) | (data.params.iburst ? REQ_ADDSRC_IBURST : 0) | (data.params.interleaved ? REQ_ADDSRC_INTERLEAVED : 0) | (data.params.burst ? REQ_ADDSRC_BURST : 0) | (data.params.nts ? REQ_ADDSRC_NTS : 0) | (data.params.copy ? REQ_ADDSRC_COPY : 0) | (data.params.ext_fields & NTP_EF_FLAG_EXP_MONO_ROOT ? REQ_ADDSRC_EF_EXP_MONO_ROOT : 0) | (data.params.ext_fields & NTP_EF_FLAG_EXP_NET_CORRECTION ? REQ_ADDSRC_EF_EXP_NET_CORRECTION : 0) | convert_addsrc_sel_options(data.params.sel_options)); msg->data.ntp_source.filter_length = htonl(data.params.filter_length); msg->data.ntp_source.cert_set = htonl(data.params.cert_set); msg->data.ntp_source.max_delay_quant = UTI_FloatHostToNetwork(data.params.max_delay_quant); memset(msg->data.ntp_source.reserved, 0, sizeof (msg->data.ntp_source.reserved)); result = 1; break; } return result; } /* ================================================== */ static int process_cmd_delete(CMD_Request *msg, char *line) { char *hostname; int ok = 0; IPAddr address; msg->command = htons(REQ_DEL_SOURCE); hostname = line; CPS_SplitWord(line); if (!*hostname) { LOG(LOGS_ERR, "Invalid syntax for address"); ok = 0; } else { if (!parse_source_address(hostname, &address)) { LOG(LOGS_ERR, "Could not get address for hostname"); ok = 0; } else { UTI_IPHostToNetwork(&address, &msg->data.del_source.ip_addr); ok = 1; } } return ok; } /* ================================================== */ static void give_help(void) { int line, len; const char *s, cols[] = "System clock:\0\0" "tracking\0Display system time information\0" "makestep\0Correct clock by stepping immediately\0" "makestep \0Configure automatic clock stepping\0" "maxupdateskew \0Modify maximum valid skew to update frequency\0" "waitsync [ [ [ []]]]\0" "Wait until synchronised in specified limits\0" "\0\0" "Time sources:\0\0" "sources [-a] [-v]\0Display information about current sources\0" "sourcestats [-a] [-v]\0Display statistics about collected measurements\0" "selectdata [-a] [-v]\0Display information about source selection\0" "selectopts <+|-options>\0Modify selection options\0" "reselect\0Force reselecting synchronisation source\0" "reselectdist \0Modify reselection distance\0" "\0\0" "NTP sources:\0\0" "activity\0Check how many NTP sources are online/offline\0" "authdata [-a] [-v]\0Display information about authentication\0" "ntpdata [
]\0Display information about last valid measurement\0" "add server [options]\0Add new NTP server\0" "add pool [options]\0Add new pool of NTP servers\0" "add peer [options]\0Add new NTP peer\0" "delete
\0Remove server or peer\0" "burst / [[/]
]\0Start rapid set of measurements\0" "maxdelay
\0Modify maximum valid sample delay\0" "maxdelayratio
\0Modify maximum valid delay/minimum ratio\0" "maxdelaydevratio
\0Modify maximum valid delay/deviation ratio\0" "minpoll
\0Modify minimum polling interval\0" "maxpoll
\0Modify maximum polling interval\0" "minstratum
\0Modify minimum stratum\0" "offline [[/]
]\0Set sources in subnet to offline status\0" "online [[/]
]\0Set sources in subnet to online status\0" "onoffline\0Set all sources to online or offline status\0" "\0according to network configuration\0" "polltarget
\0Modify poll target\0" "refresh\0Refresh IP addresses\0" "reload sources\0Re-read *.sources files\0" "sourcename
\0Display original name\0" "\0\0" "Manual time input:\0\0" "manual off|on|reset\0Disable/enable/reset settime command\0" "manual list\0Show previous settime entries\0" "manual delete \0Delete previous settime entry\0" "settime