summaryrefslogtreecommitdiffstats
path: root/client.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 12:48:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 12:48:01 +0000
commitb2d2d555a704148968cb7e566735a2a1b1a2f189 (patch)
tree18549ff498338f40ecf7aa327620abf4c1c3ee43 /client.c
parentInitial commit. (diff)
downloadchrony-b2d2d555a704148968cb7e566735a2a1b1a2f189.tar.xz
chrony-b2d2d555a704148968cb7e566735a2a1b1a2f189.zip
Adding upstream version 4.5.upstream/4.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'client.c')
-rw-r--r--client.c3538
1 files changed, 3538 insertions, 0 deletions
diff --git a/client.c b/client.c
new file mode 100644
index 0000000..7cfefba
--- /dev/null
+++ b/client.c
@@ -0,0 +1,3538 @@
+/*
+ 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 <editline/readline.h>
+#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 <threshold> <updates>\0Configure automatic clock stepping\0"
+ "maxupdateskew <skew>\0Modify maximum valid skew to update frequency\0"
+ "waitsync [<max-tries> [<max-correction> [<max-skew> [<interval>]]]]\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 <address|refid> <+|-options>\0Modify selection options\0"
+ "reselect\0Force reselecting synchronisation source\0"
+ "reselectdist <dist>\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 [<address>]\0Display information about last valid measurement\0"
+ "add server <name> [options]\0Add new NTP server\0"
+ "add pool <name> [options]\0Add new pool of NTP servers\0"
+ "add peer <name> [options]\0Add new NTP peer\0"
+ "delete <address>\0Remove server or peer\0"
+ "burst <n-good>/<n-max> [[<mask>/]<address>]\0Start rapid set of measurements\0"
+ "maxdelay <address> <delay>\0Modify maximum valid sample delay\0"
+ "maxdelayratio <address> <ratio>\0Modify maximum valid delay/minimum ratio\0"
+ "maxdelaydevratio <address> <ratio>\0Modify maximum valid delay/deviation ratio\0"
+ "minpoll <address> <poll>\0Modify minimum polling interval\0"
+ "maxpoll <address> <poll>\0Modify maximum polling interval\0"
+ "minstratum <address> <stratum>\0Modify minimum stratum\0"
+ "offline [[<mask>/]<address>]\0Set sources in subnet to offline status\0"
+ "online [[<mask>/]<address>]\0Set sources in subnet to online status\0"
+ "onoffline\0Set all sources to online or offline status\0"
+ "\0according to network configuration\0"
+ "polltarget <address> <target>\0Modify poll target\0"
+ "refresh\0Refresh IP addresses\0"
+ "reload sources\0Re-read *.sources files\0"
+ "sourcename <address>\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 <index>\0Delete previous settime entry\0"
+ "settime <time>\0Set daemon time\0"
+ "\0(e.g. Sep 25, 2015 16:30:05 or 16:30:05)\0"
+ "\0\0NTP access:\0\0"
+ "accheck <address>\0Check whether address is allowed\0"
+ "clients [-p <packets>] [-k] [-r]\0Report on clients that accessed the server\0"
+ "serverstats\0Display statistics of the server\0"
+ "allow [<subnet>]\0Allow access to subnet as a default\0"
+ "allow all [<subnet>]\0Allow access to subnet and all children\0"
+ "deny [<subnet>]\0Deny access to subnet as a default\0"
+ "deny all [<subnet>]\0Deny access to subnet and all children\0"
+ "local [options]\0Serve time even when not synchronised\0"
+ "local off\0Don't serve time when not synchronised\0"
+ "smoothtime reset|activate\0Reset/activate time smoothing\0"
+ "smoothing\0Display current time smoothing state\0"
+ "\0\0"
+ "Monitoring access:\0\0"
+ "cmdaccheck <address>\0Check whether address is allowed\0"
+ "cmdallow [<subnet>]\0Allow access to subnet as a default\0"
+ "cmdallow all [<subnet>]\0Allow access to subnet and all children\0"
+ "cmddeny [<subnet>]\0Deny access to subnet as a default\0"
+ "cmddeny all [<subnet>]\0Deny access to subnet and all children\0"
+ "\0\0"
+ "Real-time clock:\0\0"
+ "rtcdata\0Print current RTC performance parameters\0"
+ "trimrtc\0Correct RTC relative to system clock\0"
+ "writertc\0Save RTC performance parameters to file\0"
+ "\0\0"
+ "Other daemon commands:\0\0"
+ "cyclelogs\0Close and re-open log files\0"
+ "dump\0Dump measurements and NTS keys/cookies\0"
+ "rekey\0Re-read keys\0"
+ "reset sources\0Drop all measurements\0"
+ "shutdown\0Stop daemon\0"
+ "\0\0"
+ "Client commands:\0\0"
+ "dns -n|+n\0Disable/enable resolving IP addresses to hostnames\0"
+ "dns -4|-6|-46\0Resolve hostnames only to IPv4/IPv6/both addresses\0"
+ "timeout <milliseconds>\0Set initial response timeout\0"
+ "retries <retries>\0Set maximum number of retries\0"
+ "keygen [<id> [<type> [<bits>]]]\0Generate key for key file\0"
+ "exit|quit\0Leave the program\0"
+ "help\0Generate this help\0"
+ "\0";
+
+ /* Indent the second column */
+ for (s = cols, line = 0; s < cols + sizeof (cols); s += len + 1, line++) {
+ len = strlen(s);
+ printf(line % 2 == 0 ? (len >= 28 ? "%s\n%28s" : "%-28s%s") : "%s%s\n",
+ s, "");
+ }
+}
+
+/* ================================================== */
+/* Tab-completion when editline is available */
+
+#ifdef FEAT_READLINE
+
+enum {
+ TAB_COMPLETE_BASE_CMDS,
+ TAB_COMPLETE_ADD_OPTS,
+ TAB_COMPLETE_MANUAL_OPTS,
+ TAB_COMPLETE_RELOAD_OPTS,
+ TAB_COMPLETE_RESET_OPTS,
+ TAB_COMPLETE_SOURCES_OPTS,
+ TAB_COMPLETE_SOURCESTATS_OPTS,
+ TAB_COMPLETE_AUTHDATA_OPTS,
+ TAB_COMPLETE_SELECTDATA_OPTS,
+ TAB_COMPLETE_MAX_INDEX
+};
+
+static int tab_complete_index;
+
+static char *
+command_name_generator(const char *text, int state)
+{
+ const char *name, **names[TAB_COMPLETE_MAX_INDEX];
+ const char *base_commands[] = {
+ "accheck", "activity", "add", "allow", "authdata", "burst",
+ "clients", "cmdaccheck", "cmdallow", "cmddeny", "cyclelogs", "delete",
+ "deny", "dns", "dump", "exit", "help", "keygen", "local", "makestep",
+ "manual", "maxdelay", "maxdelaydevratio", "maxdelayratio", "maxpoll",
+ "maxupdateskew", "minpoll", "minstratum", "ntpdata", "offline", "online", "onoffline",
+ "polltarget", "quit", "refresh", "rekey", "reload", "reselect", "reselectdist", "reset",
+ "retries", "rtcdata", "selectdata", "selectopts", "serverstats", "settime",
+ "shutdown", "smoothing", "smoothtime", "sourcename", "sources", "sourcestats",
+ "timeout", "tracking", "trimrtc", "waitsync", "writertc",
+ NULL
+ };
+ const char *add_options[] = { "peer", "pool", "server", NULL };
+ const char *manual_options[] = { "on", "off", "delete", "list", "reset", NULL };
+ const char *reset_options[] = { "sources", NULL };
+ const char *reload_options[] = { "sources", NULL };
+ const char *common_source_options[] = { "-a", "-v", NULL };
+ static int list_index, len;
+
+ names[TAB_COMPLETE_BASE_CMDS] = base_commands;
+ names[TAB_COMPLETE_ADD_OPTS] = add_options;
+ names[TAB_COMPLETE_MANUAL_OPTS] = manual_options;
+ names[TAB_COMPLETE_RELOAD_OPTS] = reload_options;
+ names[TAB_COMPLETE_RESET_OPTS] = reset_options;
+ names[TAB_COMPLETE_AUTHDATA_OPTS] = common_source_options;
+ names[TAB_COMPLETE_SELECTDATA_OPTS] = common_source_options;
+ names[TAB_COMPLETE_SOURCES_OPTS] = common_source_options;
+ names[TAB_COMPLETE_SOURCESTATS_OPTS] = common_source_options;
+
+ if (!state) {
+ list_index = 0;
+ len = strlen(text);
+ }
+
+ while ((name = names[tab_complete_index][list_index++])) {
+ if (strncmp(name, text, len) == 0) {
+ return Strdup(name);
+ }
+ }
+
+ return NULL;
+}
+
+/* ================================================== */
+
+static char **
+command_name_completion(const char *text, int start, int end)
+{
+ char first[32];
+
+ snprintf(first, MIN(sizeof (first), start + 1), "%s", rl_line_buffer);
+ rl_attempted_completion_over = 1;
+
+ if (!strcmp(first, "add ")) {
+ tab_complete_index = TAB_COMPLETE_ADD_OPTS;
+ } else if (!strcmp(first, "authdata ")) {
+ tab_complete_index = TAB_COMPLETE_AUTHDATA_OPTS;
+ } else if (!strcmp(first, "manual ")) {
+ tab_complete_index = TAB_COMPLETE_MANUAL_OPTS;
+ } else if (!strcmp(first, "reload ")) {
+ tab_complete_index = TAB_COMPLETE_RELOAD_OPTS;
+ } else if (!strcmp(first, "reset ")) {
+ tab_complete_index = TAB_COMPLETE_RESET_OPTS;
+ } else if (!strcmp(first, "selectdata ")) {
+ tab_complete_index = TAB_COMPLETE_SELECTDATA_OPTS;
+ } else if (!strcmp(first, "sources ")) {
+ tab_complete_index = TAB_COMPLETE_SOURCES_OPTS;
+ } else if (!strcmp(first, "sourcestats ")) {
+ tab_complete_index = TAB_COMPLETE_SOURCESTATS_OPTS;
+ } else if (first[0] == '\0') {
+ tab_complete_index = TAB_COMPLETE_BASE_CMDS;
+ } else {
+ return NULL;
+ }
+
+ return rl_completion_matches(text, command_name_generator);
+}
+#endif
+
+/* ================================================== */
+
+static int max_retries = 2;
+static int initial_timeout = 1000;
+static int proto_version = PROTO_VERSION_NUMBER;
+
+/* This is the core protocol module. Complete particular fields in
+ the outgoing packet, send it, wait for a response, handle retries,
+ etc. Returns a Boolean indicating whether the protocol was
+ successful or not.*/
+
+static int
+submit_request(CMD_Request *request, CMD_Reply *reply)
+{
+ int select_status;
+ int recv_status;
+ int read_length;
+ int command_length;
+ int padding_length;
+ struct timespec ts_now, ts_start;
+ struct timeval tv;
+ int n_attempts, new_attempt;
+ double timeout;
+ fd_set rdfd;
+
+ request->pkt_type = PKT_TYPE_CMD_REQUEST;
+ request->res1 = 0;
+ request->res2 = 0;
+ request->pad1 = 0;
+ request->pad2 = 0;
+
+ n_attempts = 0;
+ new_attempt = 1;
+
+ do {
+ if (gettimeofday(&tv, NULL))
+ return 0;
+
+ if (new_attempt) {
+ new_attempt = 0;
+
+ if (n_attempts > max_retries)
+ return 0;
+
+ UTI_TimevalToTimespec(&tv, &ts_start);
+
+ UTI_GetRandomBytes(&request->sequence, sizeof (request->sequence));
+ request->attempt = htons(n_attempts);
+ request->version = proto_version;
+ command_length = PKL_CommandLength(request);
+ padding_length = PKL_CommandPaddingLength(request);
+ assert(command_length > 0 && command_length > padding_length);
+
+ n_attempts++;
+
+ /* Zero the padding to not send any uninitialized data */
+ memset(((char *)request) + command_length - padding_length, 0, padding_length);
+
+ if (sock_fd < 0) {
+ DEBUG_LOG("No socket to send request");
+ return 0;
+ }
+
+ if (SCK_Send(sock_fd, (void *)request, command_length, 0) < 0)
+ return 0;
+ }
+
+ UTI_TimevalToTimespec(&tv, &ts_now);
+
+ /* Check if the clock wasn't stepped back */
+ if (UTI_CompareTimespecs(&ts_now, &ts_start) < 0)
+ ts_start = ts_now;
+
+ timeout = initial_timeout / 1000.0 * (1U << (n_attempts - 1)) -
+ UTI_DiffTimespecsToDouble(&ts_now, &ts_start);
+ DEBUG_LOG("Timeout %f seconds", timeout);
+
+ /* Avoid calling select() with an invalid timeout */
+ if (timeout <= 0.0) {
+ new_attempt = 1;
+ continue;
+ }
+
+ UTI_DoubleToTimeval(timeout, &tv);
+
+ FD_ZERO(&rdfd);
+ FD_SET(sock_fd, &rdfd);
+
+ if (quit)
+ return 0;
+
+ select_status = select(sock_fd + 1, &rdfd, NULL, NULL, &tv);
+
+ if (select_status < 0) {
+ DEBUG_LOG("select failed : %s", strerror(errno));
+ return 0;
+ } else if (select_status == 0) {
+ /* Timeout must have elapsed, try a resend? */
+ new_attempt = 1;
+ } else {
+ recv_status = SCK_Receive(sock_fd, reply, sizeof (*reply), 0);
+
+ if (recv_status < 0) {
+ new_attempt = 1;
+ } else {
+ read_length = recv_status;
+
+ /* Check if the header is valid */
+ if (read_length < offsetof(CMD_Reply, data) ||
+ (reply->version != proto_version &&
+ !(reply->version >= PROTO_VERSION_MISMATCH_COMPAT_CLIENT &&
+ ntohs(reply->status) == STT_BADPKTVERSION)) ||
+ reply->pkt_type != PKT_TYPE_CMD_REPLY ||
+ reply->res1 != 0 ||
+ reply->res2 != 0 ||
+ reply->command != request->command ||
+ reply->sequence != request->sequence) {
+ DEBUG_LOG("Invalid reply");
+ continue;
+ }
+
+#if PROTO_VERSION_NUMBER == 6
+ /* Protocol version 5 is similar to 6 except there is no padding.
+ If a version 5 reply with STT_BADPKTVERSION is received,
+ switch our version and try again. */
+ if (proto_version == PROTO_VERSION_NUMBER &&
+ reply->version == PROTO_VERSION_NUMBER - 1) {
+ proto_version = PROTO_VERSION_NUMBER - 1;
+ n_attempts--;
+ new_attempt = 1;
+ continue;
+ }
+#else
+#error unknown compatibility with PROTO_VERSION - 1
+#endif
+
+ /* Check that the packet contains all data it is supposed to have.
+ Unknown responses will always pass this test as their expected
+ length is zero. */
+ if (read_length < PKL_ReplyLength(reply)) {
+ DEBUG_LOG("Reply too short");
+ new_attempt = 1;
+ continue;
+ }
+
+ /* Good packet received, print out results */
+ DEBUG_LOG("Reply cmd=%d reply=%d stat=%d",
+ ntohs(reply->command), ntohs(reply->reply), ntohs(reply->status));
+ break;
+ }
+ }
+ } while (1);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+request_reply(CMD_Request *request, CMD_Reply *reply, int requested_reply, int verbose)
+{
+ int status;
+
+ while (!submit_request(request, reply)) {
+ /* Try connecting to other addresses before giving up */
+ if (open_io())
+ continue;
+ printf("506 Cannot talk to daemon\n");
+ return 0;
+ }
+
+ status = ntohs(reply->status);
+
+ if (verbose || status != STT_SUCCESS) {
+ switch (status) {
+ case STT_SUCCESS:
+ printf("200 OK");
+ break;
+ case STT_ACCESSALLOWED:
+ printf("208 Access allowed");
+ break;
+ case STT_ACCESSDENIED:
+ printf("209 Access denied");
+ break;
+ case STT_FAILED:
+ printf("500 Failure");
+ break;
+ case STT_UNAUTH:
+ printf("501 Not authorised");
+ break;
+ case STT_INVALID:
+ printf("502 Invalid command");
+ break;
+ case STT_NOSUCHSOURCE:
+ printf("503 No such source");
+ break;
+ case STT_INVALIDTS:
+ printf("504 Duplicate or stale logon detected");
+ break;
+ case STT_NOTENABLED:
+ printf("505 Facility not enabled in daemon");
+ break;
+ case STT_BADSUBNET:
+ printf("507 Bad subnet");
+ break;
+ case STT_NOHOSTACCESS:
+ printf("510 No command access from this host");
+ break;
+ case STT_SOURCEALREADYKNOWN:
+ printf("511 Source already present");
+ break;
+ case STT_TOOMANYSOURCES:
+ printf("512 Too many sources present");
+ break;
+ case STT_NORTC:
+ printf("513 RTC driver not running");
+ break;
+ case STT_BADRTCFILE:
+ printf("514 Can't write RTC parameters");
+ break;
+ case STT_INVALIDAF:
+ printf("515 Invalid address family");
+ break;
+ case STT_BADSAMPLE:
+ printf("516 Sample index out of range");
+ break;
+ case STT_BADPKTVERSION:
+ printf("517 Protocol version mismatch");
+ break;
+ case STT_BADPKTLENGTH:
+ printf("518 Packet length mismatch");
+ break;
+ case STT_INACTIVE:
+ printf("519 Client logging is not active in the daemon");
+ break;
+ case STT_INVALIDNAME:
+ printf("521 Invalid name");
+ break;
+ default:
+ printf("520 Got unexpected error from daemon");
+ }
+ printf("\n");
+ }
+
+ if (status != STT_SUCCESS &&
+ status != STT_ACCESSALLOWED && status != STT_ACCESSDENIED) {
+ return 0;
+ }
+
+ if (ntohs(reply->reply) != requested_reply) {
+ printf("508 Bad reply from daemon\n");
+ return 0;
+ }
+
+ /* Make sure an unknown response was not requested */
+ assert(PKL_ReplyLength(reply));
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+print_seconds(uint32_t s)
+{
+ uint32_t d;
+
+ if (s == (uint32_t)-1) {
+ printf(" -");
+ } else if (s < 1200) {
+ printf("%4"PRIu32, s);
+ } else if (s < 36000) {
+ printf("%3"PRIu32"m", s / 60);
+ } else if (s < 345600) {
+ printf("%3"PRIu32"h", s / 3600);
+ } else {
+ d = s / 86400;
+ if (d > 999) {
+ printf("%3"PRIu32"y", d / 365);
+ } else {
+ printf("%3"PRIu32"d", d);
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+print_nanoseconds(double s)
+{
+ s = fabs(s);
+
+ if (s < 9999.5e-9) {
+ printf("%4.0fns", s * 1e9);
+ } else if (s < 9999.5e-6) {
+ printf("%4.0fus", s * 1e6);
+ } else if (s < 9999.5e-3) {
+ printf("%4.0fms", s * 1e3);
+ } else if (s < 999.5) {
+ printf("%5.1fs", s);
+ } else if (s < 99999.5) {
+ printf("%5.0fs", s);
+ } else if (s < 99999.5 * 60) {
+ printf("%5.0fm", s / 60);
+ } else if (s < 99999.5 * 3600) {
+ printf("%5.0fh", s / 3600);
+ } else if (s < 99999.5 * 3600 * 24) {
+ printf("%5.0fd", s / (3600 * 24));
+ } else {
+ printf("%5.0fy", s / (3600 * 24 * 365));
+ }
+}
+
+/* ================================================== */
+
+static void
+print_signed_nanoseconds(double s)
+{
+ double x;
+
+ x = fabs(s);
+
+ if (x < 9999.5e-9) {
+ printf("%+5.0fns", s * 1e9);
+ } else if (x < 9999.5e-6) {
+ printf("%+5.0fus", s * 1e6);
+ } else if (x < 9999.5e-3) {
+ printf("%+5.0fms", s * 1e3);
+ } else if (x < 999.5) {
+ printf("%+6.1fs", s);
+ } else if (x < 99999.5) {
+ printf("%+6.0fs", s);
+ } else if (x < 99999.5 * 60) {
+ printf("%+6.0fm", s / 60);
+ } else if (x < 99999.5 * 3600) {
+ printf("%+6.0fh", s / 3600);
+ } else if (x < 99999.5 * 3600 * 24) {
+ printf("%+6.0fd", s / (3600 * 24));
+ } else {
+ printf("%+6.0fy", s / (3600 * 24 * 365));
+ }
+}
+
+/* ================================================== */
+
+static void
+print_freq_ppm(double f)
+{
+ if (fabs(f) < 99999.5) {
+ printf("%10.3f", f);
+ } else {
+ printf("%10.0f", f);
+ }
+}
+
+/* ================================================== */
+
+static void
+print_signed_freq_ppm(double f)
+{
+ if (fabs(f) < 99999.5) {
+ printf("%+10.3f", f);
+ } else {
+ printf("%+10.0f", f);
+ }
+}
+
+/* ================================================== */
+
+static void
+print_clientlog_interval(int rate)
+{
+ if (rate >= 127) {
+ printf(" -");
+ } else {
+ printf("%2d", rate);
+ }
+}
+
+/* ================================================== */
+
+static void
+print_header(const char *header)
+{
+ int len;
+
+ if (csv_mode)
+ return;
+
+ printf("%s\n", header);
+
+ len = strlen(header);
+ while (len--)
+ printf("=");
+ printf("\n");
+}
+
+/* ================================================== */
+
+#define REPORT_END 0x1234
+
+/* Print a report. The syntax of the format is similar to printf(), but not all
+ specifiers are supported and some are different! */
+
+static void
+print_report(const char *format, ...)
+{
+ char buf[256];
+ va_list ap;
+ int i, field, sign, width, prec, spec;
+ const char *string;
+ unsigned int uinteger;
+ uint64_t uinteger64;
+ uint32_t uinteger32;
+ int integer;
+ struct timespec *ts;
+ struct tm *tm;
+ double dbl;
+
+ va_start(ap, format);
+
+ for (field = 0; ; field++) {
+ /* Search for text between format specifiers and print it
+ if not in the CSV mode */
+ for (i = 0; i < sizeof (buf) && format[i] != '%' && format[i] != '\0'; i++)
+ buf[i] = format[i];
+
+ if (i >= sizeof (buf))
+ break;
+
+ buf[i] = '\0';
+
+ if (!csv_mode)
+ printf("%s", buf);
+
+ if (format[i] == '\0' || format[i + 1] == '\0')
+ break;
+
+ format += i + 1;
+
+ sign = 0;
+ width = 0;
+ prec = 5;
+
+ if (*format == '+' || *format == '-') {
+ sign = 1;
+ format++;
+ }
+
+ if (isdigit((unsigned char)*format)) {
+ width = atoi(format);
+ while (isdigit((unsigned char)*format))
+ format++;
+ }
+
+ if (*format == '.') {
+ format++;
+ prec = atoi(format);
+ while (isdigit((unsigned char)*format))
+ format++;
+ }
+
+ spec = *format;
+ format++;
+
+ /* Disable human-readable formatting in the CSV mode */
+ if (csv_mode) {
+ sign = width = 0;
+
+ if (field > 0)
+ printf(",");
+
+ switch (spec) {
+ case 'C':
+ spec = 'd';
+ break;
+ case 'F':
+ case 'P':
+ prec = 3;
+ spec = 'f';
+ break;
+ case 'O':
+ case 'S':
+ prec = 9;
+ spec = 'f';
+ break;
+ case 'I':
+ spec = 'U';
+ break;
+ case 'T':
+ spec = 'V';
+ break;
+ }
+ }
+
+ switch (spec) {
+ case 'B': /* boolean */
+ integer = va_arg(ap, int);
+ printf("%s", integer ? "Yes" : "No");
+ break;
+ case 'C': /* clientlog interval */
+ integer = va_arg(ap, int);
+ print_clientlog_interval(integer);
+ break;
+ case 'F': /* absolute frequency in ppm with fast/slow keyword */
+ case 'O': /* absolute offset in seconds with fast/slow keyword */
+ dbl = va_arg(ap, double);
+ printf("%*.*f %s %s", width, prec, fabs(dbl),
+ spec == 'O' ? "seconds" : "ppm",
+ (dbl > 0.0) ^ (spec != 'O') ? "slow" : "fast");
+ break;
+ case 'I': /* uint32_t interval with unit */
+ uinteger32 = va_arg(ap, uint32_t);
+ print_seconds(uinteger32);
+ break;
+ case 'L': /* leap status */
+ integer = va_arg(ap, int);
+ switch (integer) {
+ case LEAP_Normal:
+ string = width != 1 ? "Normal" : "N";
+ break;
+ case LEAP_InsertSecond:
+ string = width != 1 ? "Insert second" : "+";
+ break;
+ case LEAP_DeleteSecond:
+ string = width != 1 ? "Delete second" : "-";
+ break;
+ case LEAP_Unsynchronised:
+ string = width != 1 ? "Not synchronised" : "?";
+ break;
+ default:
+ string = width != 1 ? "Invalid" : "?";
+ break;
+ }
+ printf("%s", string);
+ break;
+ case 'M': /* NTP mode */
+ integer = va_arg(ap, int);
+ switch (integer) {
+ case MODE_ACTIVE:
+ string = "Symmetric active";
+ break;
+ case MODE_PASSIVE:
+ string = "Symmetric passive";
+ break;
+ case MODE_SERVER:
+ string = "Server";
+ break;
+ default:
+ string = "Invalid";
+ break;
+ }
+ printf("%s", string);
+ break;
+ case 'N': /* Timestamp source */
+ integer = va_arg(ap, int);
+ switch (integer) {
+ case 'D':
+ string = "Daemon";
+ break;
+ case 'K':
+ string = "Kernel";
+ break;
+ case 'H':
+ string = "Hardware";
+ break;
+ default:
+ string = "Invalid";
+ break;
+ }
+ printf("%s", string);
+ break;
+ case 'P': /* frequency in ppm */
+ dbl = va_arg(ap, double);
+ if (sign)
+ print_signed_freq_ppm(dbl);
+ else
+ print_freq_ppm(dbl);
+ break;
+ case 'R': /* reference ID in hexdecimal */
+ uinteger32 = va_arg(ap, uint32_t);
+ printf("%08"PRIX32, uinteger32);
+ break;
+ case 'S': /* offset with unit */
+ dbl = va_arg(ap, double);
+ if (sign)
+ print_signed_nanoseconds(dbl);
+ else
+ print_nanoseconds(dbl);
+ break;
+ case 'T': /* timespec as date and time in UTC */
+ ts = va_arg(ap, struct timespec *);
+ tm = gmtime(&ts->tv_sec);
+ if (!tm)
+ break;
+ strftime(buf, sizeof (buf), "%a %b %d %T %Y", tm);
+ printf("%s", buf);
+ break;
+ case 'U': /* uint32_t in decimal */
+ uinteger32 = va_arg(ap, uint32_t);
+ printf("%*"PRIu32, width, uinteger32);
+ break;
+ case 'V': /* timespec as seconds since epoch */
+ ts = va_arg(ap, struct timespec *);
+ printf("%s", UTI_TimespecToString(ts));
+ break;
+ case 'Q': /* uint64_t in decimal */
+ uinteger64 = va_arg(ap, uint64_t);
+ printf("%*"PRIu64, width, uinteger64);
+ break;
+ case 'b': /* unsigned int in binary */
+ uinteger = va_arg(ap, unsigned int);
+ for (i = prec - 1; i >= 0; i--)
+ printf("%c", uinteger & 1U << i ? '1' : '0');
+ break;
+
+ /* Classic printf specifiers */
+ case 'c': /* character */
+ integer = va_arg(ap, int);
+ printf("%c", integer);
+ break;
+ case 'd': /* signed int in decimal */
+ integer = va_arg(ap, int);
+ printf("%*d", width, integer);
+ break;
+ case 'f': /* double */
+ dbl = va_arg(ap, double);
+ printf(sign ? "%+*.*f" : "%*.*f", width, prec, dbl);
+ break;
+ case 'o': /* unsigned int in octal */
+ uinteger = va_arg(ap, unsigned int);
+ printf("%*o", width, uinteger);
+ break;
+ case 's': /* string */
+ string = va_arg(ap, const char *);
+ if (sign)
+ printf("%-*s", width, string);
+ else
+ printf("%*s", width, string);
+ break;
+ case 'u': /* unsigned int in decimal */
+ uinteger = va_arg(ap, unsigned int);
+ printf("%*u", width, uinteger);
+ break;
+ }
+ }
+
+ /* Require terminating argument to catch bad type conversions */
+ if (va_arg(ap, int) != REPORT_END)
+ assert(0);
+
+ va_end(ap);
+
+ if (csv_mode)
+ printf("\n");
+}
+
+/* ================================================== */
+
+static void
+print_info_field(const char *format, ...)
+{
+ va_list ap;
+
+ if (csv_mode)
+ return;
+
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+}
+
+/* ================================================== */
+
+static int
+get_source_name(IPAddr *ip_addr, char *buf, int size)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ int i;
+
+ request.command = htons(REQ_NTP_SOURCE_NAME);
+ UTI_IPHostToNetwork(ip_addr, &request.data.ntp_source_name.ip_addr);
+ if (!request_reply(&request, &reply, RPY_NTP_SOURCE_NAME, 0) ||
+ reply.data.ntp_source_name.name[sizeof (reply.data.ntp_source_name.name) - 1] != '\0' ||
+ snprintf(buf, size, "%s", (char *)reply.data.ntp_source_name.name) >= size)
+ return 0;
+
+ /* Make sure the name is printable */
+ for (i = 0; i < size && buf[i] != '\0'; i++) {
+ if (!isgraph((unsigned char)buf[i]))
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+format_name(char *buf, int size, int trunc_dns, int ref, uint32_t ref_id,
+ int source, IPAddr *ip_addr)
+{
+ if (ref) {
+ snprintf(buf, size, "%s", UTI_RefidToString(ref_id));
+ } else if (source && source_names) {
+ if (!get_source_name(ip_addr, buf, size))
+ snprintf(buf, size, "?");
+ } else if (no_dns || csv_mode) {
+ snprintf(buf, size, "%s", UTI_IPToString(ip_addr));
+ } else {
+ DNS_IPAddress2Name(ip_addr, buf, size);
+ if (trunc_dns > 0 && strlen(buf) > trunc_dns) {
+ buf[trunc_dns - 1] = '>';
+ buf[trunc_dns] = '\0';
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_sources_options(char *line, int *all, int *verbose)
+{
+ char *opt;
+
+ *all = *verbose = 0;
+
+ while (*line) {
+ opt = line;
+ line = CPS_SplitWord(line);
+ if (!strcmp(opt, "-a"))
+ *all = 1;
+ else if (!strcmp(opt, "-v"))
+ *verbose = !csv_mode;
+ }
+}
+
+/* ================================================== */
+
+static int
+process_cmd_sourcename(char *line)
+{
+ IPAddr ip_addr;
+ char name[256];
+
+ if (!parse_source_address(line, &ip_addr)) {
+ LOG(LOGS_ERR, "Could not read address");
+ return 0;
+ }
+
+ if (!get_source_name(&ip_addr, name, sizeof (name)))
+ return 0;
+
+ print_report("%s\n", name, REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_sources(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip_addr;
+ uint32_t i, mode, n_sources;
+ char name[256], mode_ch, state_ch;
+ int all, verbose, ref;
+
+ parse_sources_options(line, &all, &verbose);
+
+ request.command = htons(REQ_N_SOURCES);
+ if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
+ return 0;
+
+ n_sources = ntohl(reply.data.n_sources.n_sources);
+
+ if (verbose) {
+ printf("\n");
+ printf(" .-- Source mode '^' = server, '=' = peer, '#' = local clock.\n");
+ printf(" / .- Source state '*' = current best, '+' = combined, '-' = not combined,\n");
+ printf("| / 'x' = may be in error, '~' = too variable, '?' = unusable.\n");
+ printf("|| .- xxxx [ yyyy ] +/- zzzz\n");
+ printf("|| Reachability register (octal) -. | xxxx = adjusted offset,\n");
+ printf("|| Log2(Polling interval) --. | | yyyy = measured offset,\n");
+ printf("|| \\ | | zzzz = estimated error.\n");
+ printf("|| | | \\\n");
+ }
+
+ print_header("MS Name/IP address Stratum Poll Reach LastRx Last sample ");
+
+ /* "MS NNNNNNNNNNNNNNNNNNNNNNNNNNN SS PP RRR RRRR SSSSSSS[SSSSSSS] +/- SSSSSS" */
+
+ for (i = 0; i < n_sources; i++) {
+ request.command = htons(REQ_SOURCE_DATA);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
+ return 0;
+
+ mode = ntohs(reply.data.source_data.mode);
+ UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &ip_addr);
+ if (!all && ip_addr.family == IPADDR_ID)
+ continue;
+
+ ref = mode == RPY_SD_MD_REF && ip_addr.family == IPADDR_INET4;
+ format_name(name, sizeof (name), 25, ref, ref ? ip_addr.addr.in4 : 0, 1, &ip_addr);
+
+ switch (mode) {
+ case RPY_SD_MD_CLIENT:
+ mode_ch = '^';
+ break;
+ case RPY_SD_MD_PEER:
+ mode_ch = '=';
+ break;
+ case RPY_SD_MD_REF:
+ mode_ch = '#';
+ break;
+ default:
+ mode_ch = ' ';
+ }
+
+ switch (ntohs(reply.data.source_data.state)) {
+ case RPY_SD_ST_SELECTED:
+ state_ch = '*';
+ break;
+ case RPY_SD_ST_NONSELECTABLE:
+ state_ch = '?';
+ break;
+ case RPY_SD_ST_FALSETICKER:
+ state_ch = 'x';
+ break;
+ case RPY_SD_ST_JITTERY:
+ state_ch = '~';
+ break;
+ case RPY_SD_ST_UNSELECTED:
+ state_ch = '+';
+ break;
+ case RPY_SD_ST_SELECTABLE:
+ state_ch = '-';
+ break;
+ default:
+ state_ch = ' ';
+ }
+
+ switch (ntohs(reply.data.source_data.flags)) {
+ default:
+ break;
+ }
+
+ print_report("%c%c %-27s %2d %2d %3o %I %+S[%+S] +/- %S\n",
+ mode_ch, state_ch, name,
+ ntohs(reply.data.source_data.stratum),
+ (int16_t)ntohs(reply.data.source_data.poll),
+ ntohs(reply.data.source_data.reachability),
+ ntohl(reply.data.source_data.since_sample),
+ UTI_FloatNetworkToHost(reply.data.source_data.latest_meas),
+ UTI_FloatNetworkToHost(reply.data.source_data.orig_latest_meas),
+ UTI_FloatNetworkToHost(reply.data.source_data.latest_meas_err),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_sourcestats(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ uint32_t i, n_sources;
+ int all, verbose;
+ char name[256];
+ IPAddr ip_addr;
+
+ parse_sources_options(line, &all, &verbose);
+
+ request.command = htons(REQ_N_SOURCES);
+ if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
+ return 0;
+
+ n_sources = ntohl(reply.data.n_sources.n_sources);
+
+ if (verbose) {
+ printf(" .- Number of sample points in measurement set.\n");
+ printf(" / .- Number of residual runs with same sign.\n");
+ printf(" | / .- Length of measurement set (time).\n");
+ printf(" | | / .- Est. clock freq error (ppm).\n");
+ printf(" | | | / .- Est. error in freq.\n");
+ printf(" | | | | / .- Est. offset.\n");
+ printf(" | | | | | | On the -.\n");
+ printf(" | | | | | | samples. \\\n");
+ printf(" | | | | | | |\n");
+ }
+
+ print_header("Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev");
+
+ /* "NNNNNNNNNNNNNNNNNNNNNNNNN NP NR SSSS FFFFFFFFFF SSSSSSSSSS SSSSSSS SSSSSS" */
+
+ for (i = 0; i < n_sources; i++) {
+ request.command = htons(REQ_SOURCESTATS);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SOURCESTATS, 0))
+ return 0;
+
+ UTI_IPNetworkToHost(&reply.data.sourcestats.ip_addr, &ip_addr);
+ if (!all && ip_addr.family == IPADDR_ID)
+ continue;
+
+ format_name(name, sizeof (name), 25, ip_addr.family == IPADDR_UNSPEC,
+ ntohl(reply.data.sourcestats.ref_id), 1, &ip_addr);
+
+ print_report("%-25s %3U %3U %I %+P %P %+S %S\n",
+ name,
+ ntohl(reply.data.sourcestats.n_samples),
+ ntohl(reply.data.sourcestats.n_runs),
+ ntohl(reply.data.sourcestats.span_seconds),
+ UTI_FloatNetworkToHost(reply.data.sourcestats.resid_freq_ppm),
+ UTI_FloatNetworkToHost(reply.data.sourcestats.skew_ppm),
+ UTI_FloatNetworkToHost(reply.data.sourcestats.est_offset),
+ UTI_FloatNetworkToHost(reply.data.sourcestats.sd),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_tracking(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip_addr;
+ uint32_t ref_id;
+ char name[256];
+ struct timespec ref_time;
+
+ request.command = htons(REQ_TRACKING);
+ if (!request_reply(&request, &reply, RPY_TRACKING, 0))
+ return 0;
+
+ ref_id = ntohl(reply.data.tracking.ref_id);
+
+ UTI_IPNetworkToHost(&reply.data.tracking.ip_addr, &ip_addr);
+ format_name(name, sizeof (name), sizeof (name),
+ ip_addr.family == IPADDR_UNSPEC, ref_id, 1, &ip_addr);
+
+ UTI_TimespecNetworkToHost(&reply.data.tracking.ref_time, &ref_time);
+
+ print_report("Reference ID : %R (%s)\n"
+ "Stratum : %u\n"
+ "Ref time (UTC) : %T\n"
+ "System time : %.9O of NTP time\n"
+ "Last offset : %+.9f seconds\n"
+ "RMS offset : %.9f seconds\n"
+ "Frequency : %.3F\n"
+ "Residual freq : %+.3f ppm\n"
+ "Skew : %.3f ppm\n"
+ "Root delay : %.9f seconds\n"
+ "Root dispersion : %.9f seconds\n"
+ "Update interval : %.1f seconds\n"
+ "Leap status : %L\n",
+ ref_id, name,
+ ntohs(reply.data.tracking.stratum),
+ &ref_time,
+ UTI_FloatNetworkToHost(reply.data.tracking.current_correction),
+ UTI_FloatNetworkToHost(reply.data.tracking.last_offset),
+ UTI_FloatNetworkToHost(reply.data.tracking.rms_offset),
+ UTI_FloatNetworkToHost(reply.data.tracking.freq_ppm),
+ UTI_FloatNetworkToHost(reply.data.tracking.resid_freq_ppm),
+ UTI_FloatNetworkToHost(reply.data.tracking.skew_ppm),
+ UTI_FloatNetworkToHost(reply.data.tracking.root_delay),
+ UTI_FloatNetworkToHost(reply.data.tracking.root_dispersion),
+ UTI_FloatNetworkToHost(reply.data.tracking.last_update_interval),
+ ntohs(reply.data.tracking.leap_status), REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_authdata(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip_addr;
+ uint32_t i, source_mode, n_sources;
+ int all, verbose;
+ const char *mode_str;
+ char name[256];
+
+ parse_sources_options(line, &all, &verbose);
+
+ request.command = htons(REQ_N_SOURCES);
+ if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
+ return 0;
+
+ n_sources = ntohl(reply.data.n_sources.n_sources);
+
+ if (verbose) {
+ printf( " .- Auth. mechanism (NTS, SK - symmetric key)\n");
+ printf( " | Key length -. Cookie length (bytes) -.\n");
+ printf( " | (bits) | Num. of cookies --. |\n");
+ printf( " | | Key est. attempts | |\n");
+ printf( " | | | | |\n");
+ }
+
+ print_header("Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen");
+
+ /* "NNNNNNNNNNNNNNNNNNNNNNNNNNN MMMM KKKKK AAAA LLLL LLLL AAAA NNNN CCCC LLLL" */
+
+ for (i = 0; i < n_sources; i++) {
+ request.command = htons(REQ_SOURCE_DATA);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
+ return 0;
+
+ source_mode = ntohs(reply.data.source_data.mode);
+ if (source_mode != RPY_SD_MD_CLIENT && source_mode != RPY_SD_MD_PEER)
+ continue;
+
+ UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &ip_addr);
+ if (!all && ip_addr.family == IPADDR_ID)
+ continue;
+
+ request.command = htons(REQ_AUTH_DATA);
+ request.data.auth_data.ip_addr = reply.data.source_data.ip_addr;
+ if (!request_reply(&request, &reply, RPY_AUTH_DATA, 0))
+ return 0;
+
+ format_name(name, sizeof (name), 25, 0, 0, 1, &ip_addr);
+
+ switch (ntohs(reply.data.auth_data.mode)) {
+ case RPY_AD_MD_NONE:
+ mode_str = "-";
+ break;
+ case RPY_AD_MD_SYMMETRIC:
+ mode_str = "SK";
+ break;
+ case RPY_AD_MD_NTS:
+ mode_str = "NTS";
+ break;
+ default:
+ mode_str = "?";
+ break;
+ }
+
+ print_report("%-27s %4s %5U %4d %4d %I %4d %4d %4d %4d\n",
+ name, mode_str,
+ ntohl(reply.data.auth_data.key_id),
+ ntohs(reply.data.auth_data.key_type),
+ ntohs(reply.data.auth_data.key_length),
+ ntohl(reply.data.auth_data.last_ke_ago),
+ ntohs(reply.data.auth_data.ke_attempts),
+ ntohs(reply.data.auth_data.nak),
+ ntohs(reply.data.auth_data.cookies),
+ ntohs(reply.data.auth_data.cookie_length),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_ntpdata(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr remote_addr, local_addr;
+ struct timespec ref_time;
+ uint32_t i, n_sources;
+ uint16_t mode;
+ int specified_addr;
+
+ if (*line) {
+ specified_addr = 1;
+ n_sources = 1;
+ } else {
+ specified_addr = 0;
+ request.command = htons(REQ_N_SOURCES);
+ if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
+ return 0;
+ n_sources = ntohl(reply.data.n_sources.n_sources);
+ }
+
+ for (i = 0; i < n_sources; i++) {
+ if (specified_addr) {
+ if (!parse_source_address(line, &remote_addr)) {
+ LOG(LOGS_ERR, "Could not get address for hostname");
+ return 0;
+ }
+ } else {
+ request.command = htons(REQ_SOURCE_DATA);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
+ return 0;
+
+ mode = ntohs(reply.data.source_data.mode);
+ if (mode != RPY_SD_MD_CLIENT && mode != RPY_SD_MD_PEER)
+ continue;
+
+ UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &remote_addr);
+ if (!UTI_IsIPReal(&remote_addr))
+ continue;
+ }
+
+ request.command = htons(REQ_NTP_DATA);
+ UTI_IPHostToNetwork(&remote_addr, &request.data.ntp_data.ip_addr);
+ if (!request_reply(&request, &reply, RPY_NTP_DATA, 0))
+ return 0;
+
+ UTI_IPNetworkToHost(&reply.data.ntp_data.remote_addr, &remote_addr);
+ UTI_IPNetworkToHost(&reply.data.ntp_data.local_addr, &local_addr);
+ UTI_TimespecNetworkToHost(&reply.data.ntp_data.ref_time, &ref_time);
+
+ if (!specified_addr && !csv_mode)
+ printf("\n");
+
+ print_report("Remote address : %s (%R)\n"
+ "Remote port : %u\n"
+ "Local address : %s (%R)\n"
+ "Leap status : %L\n"
+ "Version : %u\n"
+ "Mode : %M\n"
+ "Stratum : %u\n"
+ "Poll interval : %d (%.0f seconds)\n"
+ "Precision : %d (%.9f seconds)\n"
+ "Root delay : %.6f seconds\n"
+ "Root dispersion : %.6f seconds\n"
+ "Reference ID : %R (%s)\n"
+ "Reference time : %T\n"
+ "Offset : %+.9f seconds\n"
+ "Peer delay : %.9f seconds\n"
+ "Peer dispersion : %.9f seconds\n"
+ "Response time : %.9f seconds\n"
+ "Jitter asymmetry: %+.2f\n"
+ "NTP tests : %.3b %.3b %.4b\n"
+ "Interleaved : %B\n"
+ "Authenticated : %B\n"
+ "TX timestamping : %N\n"
+ "RX timestamping : %N\n"
+ "Total TX : %U\n"
+ "Total RX : %U\n"
+ "Total valid RX : %U\n"
+ "Total good RX : %U\n",
+ UTI_IPToString(&remote_addr), UTI_IPToRefid(&remote_addr),
+ ntohs(reply.data.ntp_data.remote_port),
+ UTI_IPToString(&local_addr), UTI_IPToRefid(&local_addr),
+ reply.data.ntp_data.leap, reply.data.ntp_data.version,
+ reply.data.ntp_data.mode, reply.data.ntp_data.stratum,
+ reply.data.ntp_data.poll, UTI_Log2ToDouble(reply.data.ntp_data.poll),
+ reply.data.ntp_data.precision, UTI_Log2ToDouble(reply.data.ntp_data.precision),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.root_delay),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.root_dispersion),
+ ntohl(reply.data.ntp_data.ref_id), reply.data.ntp_data.stratum <= 1 ?
+ UTI_RefidToString(ntohl(reply.data.ntp_data.ref_id)) : "",
+ &ref_time,
+ UTI_FloatNetworkToHost(reply.data.ntp_data.offset),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.peer_delay),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.peer_dispersion),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.response_time),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.jitter_asymmetry),
+ ntohs(reply.data.ntp_data.flags) >> 7,
+ ntohs(reply.data.ntp_data.flags) >> 4,
+ ntohs(reply.data.ntp_data.flags),
+ ntohs(reply.data.ntp_data.flags) & RPY_NTP_FLAG_INTERLEAVED,
+ ntohs(reply.data.ntp_data.flags) & RPY_NTP_FLAG_AUTHENTICATED,
+ reply.data.ntp_data.tx_tss_char, reply.data.ntp_data.rx_tss_char,
+ ntohl(reply.data.ntp_data.total_tx_count),
+ ntohl(reply.data.ntp_data.total_rx_count),
+ ntohl(reply.data.ntp_data.total_valid_count),
+ ntohl(reply.data.ntp_data.total_good_count),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_selectdata(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ uint32_t i, n_sources;
+ int all, verbose, conf_options, eff_options;
+ char name[256];
+ IPAddr ip_addr;
+
+ parse_sources_options(line, &all, &verbose);
+
+ request.command = htons(REQ_N_SOURCES);
+ if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
+ return 0;
+
+ n_sources = ntohl(reply.data.n_sources.n_sources);
+
+ if (verbose) {
+ printf( " . State: N - noselect, s - unsynchronised, M - missing samples,\n");
+ printf( " / d/D - large distance, ~ - jittery, w/W - waits for others,\n");
+ printf( "| S - stale, O - orphan, T - not trusted, P - not preferred,\n");
+ printf( "| U - waits for update,, x - falseticker, + - combined, * - best.\n");
+ printf( "| Effective options ---------. (N - noselect, P - prefer\n");
+ printf( "| Configured options ----. \\ T - trust, R - require)\n");
+ printf( "| Auth. enabled (Y/N) -. \\ \\ Offset interval --.\n");
+ printf( "| | | | |\n");
+ }
+
+ print_header("S Name/IP Address Auth COpts EOpts Last Score Interval Leap");
+
+ /* "S NNNNNNNNNNNNNNNNNNNNNNNNN A OOOO- OOOO- LLLL SSSSS IIIIIII IIIIIII L" */
+
+ for (i = 0; i < n_sources; i++) {
+ request.command = htons(REQ_SELECT_DATA);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SELECT_DATA, 0))
+ return 0;
+
+ UTI_IPNetworkToHost(&reply.data.select_data.ip_addr, &ip_addr);
+ if (!all && ip_addr.family == IPADDR_ID)
+ continue;
+
+ format_name(name, sizeof (name), 25, ip_addr.family == IPADDR_UNSPEC,
+ ntohl(reply.data.select_data.ref_id), 1, &ip_addr);
+
+ conf_options = ntohs(reply.data.select_data.conf_options);
+ eff_options = ntohs(reply.data.select_data.eff_options);
+
+ print_report("%c %-25s %c %c%c%c%c%c %c%c%c%c%c %I %5.1f %+S %+S %1L\n",
+ reply.data.select_data.state_char,
+ name,
+ reply.data.select_data.authentication ? 'Y' : 'N',
+ conf_options & RPY_SD_OPTION_NOSELECT ? 'N' : '-',
+ conf_options & RPY_SD_OPTION_PREFER ? 'P' : '-',
+ conf_options & RPY_SD_OPTION_TRUST ? 'T' : '-',
+ conf_options & RPY_SD_OPTION_REQUIRE ? 'R' : '-',
+ '-',
+ eff_options & RPY_SD_OPTION_NOSELECT ? 'N' : '-',
+ eff_options & RPY_SD_OPTION_PREFER ? 'P' : '-',
+ eff_options & RPY_SD_OPTION_TRUST ? 'T' : '-',
+ eff_options & RPY_SD_OPTION_REQUIRE ? 'R' : '-',
+ '-',
+ ntohl(reply.data.select_data.last_sample_ago),
+ UTI_FloatNetworkToHost(reply.data.select_data.score),
+ UTI_FloatNetworkToHost(reply.data.select_data.lo_limit),
+ UTI_FloatNetworkToHost(reply.data.select_data.hi_limit),
+ reply.data.select_data.leap,
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_serverstats(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+
+ request.command = htons(REQ_SERVER_STATS);
+ if (!request_reply(&request, &reply, RPY_SERVER_STATS4, 0))
+ return 0;
+
+ print_report("NTP packets received : %Q\n"
+ "NTP packets dropped : %Q\n"
+ "Command packets received : %Q\n"
+ "Command packets dropped : %Q\n"
+ "Client log records dropped : %Q\n"
+ "NTS-KE connections accepted: %Q\n"
+ "NTS-KE connections dropped : %Q\n"
+ "Authenticated NTP packets : %Q\n"
+ "Interleaved NTP packets : %Q\n"
+ "NTP timestamps held : %Q\n"
+ "NTP timestamp span : %Q\n"
+ "NTP daemon RX timestamps : %Q\n"
+ "NTP daemon TX timestamps : %Q\n"
+ "NTP kernel RX timestamps : %Q\n"
+ "NTP kernel TX timestamps : %Q\n"
+ "NTP hardware RX timestamps : %Q\n"
+ "NTP hardware TX timestamps : %Q\n",
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_drops),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.cmd_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.cmd_drops),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.log_drops),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.nke_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.nke_drops),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_auth_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_interleaved_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_span_seconds),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_daemon_rx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_daemon_tx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_kernel_rx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_kernel_tx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_hw_rx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_hw_tx_timestamps),
+ REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_smoothing(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ uint32_t flags;
+
+ request.command = htons(REQ_SMOOTHING);
+ if (!request_reply(&request, &reply, RPY_SMOOTHING, 0))
+ return 0;
+
+ flags = ntohl(reply.data.smoothing.flags);
+
+ print_report("Active : %B %s\n"
+ "Offset : %+.9f seconds\n"
+ "Frequency : %+.6f ppm\n"
+ "Wander : %+.6f ppm per second\n"
+ "Last update : %.1f seconds ago\n"
+ "Remaining time : %.1f seconds\n",
+ !!(flags & RPY_SMT_FLAG_ACTIVE),
+ flags & RPY_SMT_FLAG_LEAPONLY ? "(leap second only)" : "",
+ UTI_FloatNetworkToHost(reply.data.smoothing.offset),
+ UTI_FloatNetworkToHost(reply.data.smoothing.freq_ppm),
+ UTI_FloatNetworkToHost(reply.data.smoothing.wander_ppm),
+ UTI_FloatNetworkToHost(reply.data.smoothing.last_update_ago),
+ UTI_FloatNetworkToHost(reply.data.smoothing.remaining_time),
+ REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_smoothtime(CMD_Request *msg, const char *line)
+{
+ if (!strcmp(line, "reset")) {
+ msg->data.smoothtime.option = htonl(REQ_SMOOTHTIME_RESET);
+ } else if (!strcmp(line, "activate")) {
+ msg->data.smoothtime.option = htonl(REQ_SMOOTHTIME_ACTIVATE);
+ } else {
+ LOG(LOGS_ERR, "Invalid syntax for smoothtime command");
+ return 0;
+ }
+
+ msg->command = htons(REQ_SMOOTHTIME);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_rtcreport(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ struct timespec ref_time;
+
+ request.command = htons(REQ_RTCREPORT);
+ if (!request_reply(&request, &reply, RPY_RTC, 0))
+ return 0;
+
+ UTI_TimespecNetworkToHost(&reply.data.rtc.ref_time, &ref_time);
+
+ print_report("RTC ref time (UTC) : %T\n"
+ "Number of samples : %u\n"
+ "Number of runs : %u\n"
+ "Sample span period : %I\n"
+ "RTC is fast by : %12.6f seconds\n"
+ "RTC gains time at : %9.3f ppm\n",
+ &ref_time,
+ ntohs(reply.data.rtc.n_samples),
+ ntohs(reply.data.rtc.n_runs),
+ ntohl(reply.data.rtc.span_seconds),
+ UTI_FloatNetworkToHost(reply.data.rtc.rtc_seconds_fast),
+ UTI_FloatNetworkToHost(reply.data.rtc.rtc_gain_rate_ppm),
+ REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_clients(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip;
+ uint32_t i, n_clients, next_index, n_indices, min_hits, reset;
+ RPY_ClientAccesses_Client *client;
+ char header[80], name[50], *opt, *arg;
+ int nke;
+
+ next_index = 0;
+ min_hits = 0;
+ reset = 0;
+ nke = 0;
+
+ while (*line) {
+ opt = line;
+ line = CPS_SplitWord(line);
+ if (strcmp(opt, "-k") == 0) {
+ nke = 1;
+ } else if (strcmp(opt, "-p") == 0) {
+ arg = line;
+ line = CPS_SplitWord(line);
+ if (sscanf(arg, "%"SCNu32, &min_hits) != 1) {
+ LOG(LOGS_ERR, "Invalid syntax for clients command");
+ return 0;
+ }
+ } else if (strcmp(opt, "-r") == 0) {
+ reset = 1;
+ }
+ }
+
+ snprintf(header, sizeof (header),
+ "Hostname NTP Drop Int IntL Last %6s Drop Int Last",
+ nke ? "NTS-KE" : "Cmd");
+ print_header(header);
+
+ while (1) {
+ request.command = htons(REQ_CLIENT_ACCESSES_BY_INDEX3);
+ request.data.client_accesses_by_index.first_index = htonl(next_index);
+ request.data.client_accesses_by_index.n_clients = htonl(MAX_CLIENT_ACCESSES);
+ request.data.client_accesses_by_index.min_hits = htonl(min_hits);
+ request.data.client_accesses_by_index.reset = htonl(reset);
+
+ if (!request_reply(&request, &reply, RPY_CLIENT_ACCESSES_BY_INDEX3, 0))
+ return 0;
+
+ n_clients = ntohl(reply.data.client_accesses_by_index.n_clients);
+ n_indices = ntohl(reply.data.client_accesses_by_index.n_indices);
+
+ for (i = 0; i < n_clients && i < MAX_CLIENT_ACCESSES; i++) {
+ client = &reply.data.client_accesses_by_index.clients[i];
+
+ UTI_IPNetworkToHost(&client->ip, &ip);
+
+ /* UNSPEC means the record could not be found in the daemon's tables.
+ We shouldn't ever generate this case, but ignore it if we do. */
+ if (ip.family == IPADDR_UNSPEC)
+ continue;
+
+ format_name(name, sizeof (name), 25, 0, 0, 0, &ip);
+
+ print_report("%-25s %6U %5U %C %C %I %6U %5U %C %I\n",
+ name,
+ ntohl(client->ntp_hits),
+ ntohl(client->ntp_drops),
+ client->ntp_interval,
+ client->ntp_timeout_interval,
+ ntohl(client->last_ntp_hit_ago),
+ ntohl(nke ? client->nke_hits : client->cmd_hits),
+ ntohl(nke ? client->nke_drops : client->cmd_drops),
+ nke ? client->nke_interval : client->cmd_interval,
+ ntohl(nke ? client->last_nke_hit_ago : client->last_cmd_hit_ago),
+ REPORT_END);
+ }
+
+ /* Set the next index to probe based on what the server tells us */
+ next_index = ntohl(reply.data.client_accesses_by_index.next_index);
+
+ if (next_index >= n_indices || n_clients < MAX_CLIENT_ACCESSES)
+ break;
+ }
+
+ return 1;
+}
+
+
+/* ================================================== */
+/* Process the manual list command */
+static int
+process_cmd_manual_list(const char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ uint32_t i, n_samples;
+ RPY_ManualListSample *sample;
+ struct timespec when;
+
+ request.command = htons(REQ_MANUAL_LIST);
+ if (!request_reply(&request, &reply, RPY_MANUAL_LIST2, 0))
+ return 0;
+
+ n_samples = ntohl(reply.data.manual_list.n_samples);
+ print_info_field("210 n_samples = %"PRIu32"\n", n_samples);
+
+ print_header("# Date Time(UTC) Slewed Original Residual");
+
+ for (i = 0; i < n_samples && i < MAX_MANUAL_LIST_SAMPLES; i++) {
+ sample = &reply.data.manual_list.samples[i];
+ UTI_TimespecNetworkToHost(&sample->when, &when);
+
+ print_report("%2d %s %10.2f %10.2f %10.2f\n",
+ i, UTI_TimeToLogForm(when.tv_sec),
+ UTI_FloatNetworkToHost(sample->slewed_offset),
+ UTI_FloatNetworkToHost(sample->orig_offset),
+ UTI_FloatNetworkToHost(sample->residual),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_manual_delete(CMD_Request *msg, const char *line)
+{
+ int index;
+
+ if (sscanf(line, "%d", &index) != 1) {
+ LOG(LOGS_ERR, "Bad syntax for manual delete command");
+ return 0;
+ }
+
+ msg->command = htons(REQ_MANUAL_DELETE);
+ msg->data.manual_delete.index = htonl(index);
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_settime(char *line)
+{
+ struct timespec ts;
+ time_t now, new_time;
+ CMD_Request request;
+ CMD_Reply reply;
+ double dfreq_ppm, new_afreq_ppm;
+ double offset;
+
+ now = time(NULL);
+ new_time = get_date(line, &now);
+
+ if (new_time == -1) {
+ printf("510 - Could not parse date string\n");
+ } else {
+ ts.tv_sec = new_time;
+ ts.tv_nsec = 0;
+ UTI_TimespecHostToNetwork(&ts, &request.data.settime.ts);
+ request.command = htons(REQ_SETTIME);
+ if (request_reply(&request, &reply, RPY_MANUAL_TIMESTAMP2, 1)) {
+ offset = UTI_FloatNetworkToHost(reply.data.manual_timestamp.offset);
+ dfreq_ppm = UTI_FloatNetworkToHost(reply.data.manual_timestamp.dfreq_ppm);
+ new_afreq_ppm = UTI_FloatNetworkToHost(reply.data.manual_timestamp.new_afreq_ppm);
+ printf("Clock was %.2f seconds fast. Frequency change = %.2fppm, new frequency = %.2fppm\n",
+ offset, dfreq_ppm, new_afreq_ppm);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* ================================================== */
+
+static void
+process_cmd_rekey(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_REKEY);
+}
+
+/* ================================================== */
+
+static int
+process_cmd_makestep(CMD_Request *msg, char *line)
+{
+ int limit;
+ double threshold;
+
+ if (*line) {
+ if (sscanf(line, "%lf %d", &threshold, &limit) != 2) {
+ LOG(LOGS_ERR, "Bad syntax for makestep command");
+ return 0;
+ }
+ msg->command = htons(REQ_MODIFY_MAKESTEP);
+ msg->data.modify_makestep.limit = htonl(limit);
+ msg->data.modify_makestep.threshold = UTI_FloatHostToNetwork(threshold);
+ } else {
+ msg->command = htons(REQ_MAKESTEP);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_activity(const char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+
+ request.command = htons(REQ_ACTIVITY);
+ if (!request_reply(&request, &reply, RPY_ACTIVITY, 0))
+ return 0;
+
+ print_info_field("200 OK\n");
+
+ print_report("%U sources online\n"
+ "%U sources offline\n"
+ "%U sources doing burst (return to online)\n"
+ "%U sources doing burst (return to offline)\n"
+ "%U sources with unknown address\n",
+ ntohl(reply.data.activity.online),
+ ntohl(reply.data.activity.offline),
+ ntohl(reply.data.activity.burst_online),
+ ntohl(reply.data.activity.burst_offline),
+ ntohl(reply.data.activity.unresolved),
+ REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_reselectdist(CMD_Request *msg, char *line)
+{
+ double dist;
+ int ok;
+ msg->command = htons(REQ_RESELECTDISTANCE);
+ if (sscanf(line, "%lf", &dist) == 1) {
+ msg->data.reselect_distance.distance = UTI_FloatHostToNetwork(dist);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+ return ok;
+}
+
+/* ================================================== */
+
+static void
+process_cmd_reselect(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_RESELECT);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_refresh(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_REFRESH);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_shutdown(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_SHUTDOWN);
+}
+
+/* ================================================== */
+
+static int
+process_cmd_reload(CMD_Request *msg, char *line)
+{
+ if (!strcmp(line, "sources")) {
+ msg->command = htons(REQ_RELOAD_SOURCES);
+ } else {
+ LOG(LOGS_ERR, "Invalid syntax for reload command");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_reset(CMD_Request *msg, char *line)
+{
+ if (!strcmp(line, "sources")) {
+ msg->command = htons(REQ_RESET_SOURCES);
+ } else {
+ LOG(LOGS_ERR, "Invalid syntax for reset command");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_selectopts(CMD_Request *msg, char *line)
+{
+ int mask, options, option;
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ char *src, *opt;
+
+ src = line;
+ line = CPS_SplitWord(line);
+ ref_id = 0;
+
+ /* Don't allow hostnames to avoid conflicts with reference IDs */
+ if (!UTI_StringToIdIP(src, &ip_addr) && !UTI_StringToIP(src, &ip_addr)) {
+ ip_addr.family = IPADDR_UNSPEC;
+ if (CPS_ParseRefid(src, &ref_id) == 0) {
+ LOG(LOGS_ERR, "Invalid syntax for selectopts command");
+ return 0;
+ }
+ }
+
+ mask = options = 0;
+
+ while (*line != '\0') {
+ opt = line;
+ line = CPS_SplitWord(line);
+
+ if ((opt[0] != '+' && opt[0] != '-') || (option = CPS_GetSelectOption(opt + 1)) == 0) {
+ LOG(LOGS_ERR, "Invalid syntax for selectopts command");
+ return 0;
+ }
+
+ mask |= option;
+ if (opt[0] == '+')
+ options |= option;
+ }
+
+ UTI_IPHostToNetwork(&ip_addr, &msg->data.modify_select_opts.address);
+ msg->data.modify_select_opts.ref_id = htonl(ref_id);
+ msg->data.modify_select_opts.mask = htonl(mask);
+ msg->data.modify_select_opts.options = htonl(convert_addsrc_sel_options(options));
+
+ msg->command = htons(REQ_MODIFY_SELECTOPTS);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_waitsync(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip_addr;
+ uint32_t ref_id;
+ double correction, skew_ppm, max_correction, max_skew_ppm, interval;
+ int ret = 0, max_tries, i;
+ struct timeval timeout;
+
+ max_tries = 0;
+ max_correction = 0.0;
+ max_skew_ppm = 0.0;
+ interval = 10.0;
+
+ if (sscanf(line, "%d %lf %lf %lf", &max_tries, &max_correction, &max_skew_ppm, &interval))
+ ;
+
+ /* Don't allow shorter interval than 0.1 seconds */
+ if (interval < 0.1)
+ interval = 0.1;
+
+ request.command = htons(REQ_TRACKING);
+
+ for (i = 1; ; i++) {
+ if (request_reply(&request, &reply, RPY_TRACKING, 0)) {
+ ref_id = ntohl(reply.data.tracking.ref_id);
+ UTI_IPNetworkToHost(&reply.data.tracking.ip_addr, &ip_addr);
+
+ correction = UTI_FloatNetworkToHost(reply.data.tracking.current_correction);
+ correction = fabs(correction);
+ skew_ppm = UTI_FloatNetworkToHost(reply.data.tracking.skew_ppm);
+
+ print_report("try: %d, refid: %R, correction: %.9f, skew: %.3f\n",
+ i, ref_id, correction, skew_ppm, REPORT_END);
+
+ if ((ip_addr.family != IPADDR_UNSPEC ||
+ (ref_id != 0 && ref_id != 0x7f7f0101L /* LOCAL refid */)) &&
+ (max_correction == 0.0 || correction <= max_correction) &&
+ (max_skew_ppm == 0.0 || skew_ppm <= max_skew_ppm)) {
+ ret = 1;
+ }
+ }
+
+ if (!ret && (!max_tries || i < max_tries) && !quit) {
+ UTI_DoubleToTimeval(interval, &timeout);
+ if (select(0, NULL, NULL, NULL, &timeout))
+ break;
+ } else {
+ break;
+ }
+ }
+ return ret;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_dns(const char *line)
+{
+ if (!strcmp(line, "-46")) {
+ DNS_SetAddressFamily(IPADDR_UNSPEC);
+ } else if (!strcmp(line, "-4")) {
+ DNS_SetAddressFamily(IPADDR_INET4);
+ } else if (!strcmp(line, "-6")) {
+ DNS_SetAddressFamily(IPADDR_INET6);
+ } else if (!strcmp(line, "-n")) {
+ no_dns = 1;
+ } else if (!strcmp(line, "+n")) {
+ no_dns = 0;
+ } else {
+ LOG(LOGS_ERR, "Unrecognized dns command");
+ return 0;
+ }
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_timeout(const char *line)
+{
+ int timeout;
+
+ timeout = atoi(line);
+ if (timeout < 100) {
+ LOG(LOGS_ERR, "Timeout %d is too short", timeout);
+ return 0;
+ }
+ initial_timeout = timeout;
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_retries(const char *line)
+{
+ int retries;
+
+ retries = atoi(line);
+ if (retries < 0 || retries > 30) {
+ LOG(LOGS_ERR, "Invalid maximum number of retries");
+ return 0;
+ }
+ max_retries = retries;
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_keygen(char *line)
+{
+ unsigned int i, args, cmac_length, length, id = 1, bits = 160;
+ unsigned char key[512];
+ const char *type;
+ char *words[3];
+
+#ifdef FEAT_SECHASH
+ type = "SHA1";
+#else
+ type = "MD5";
+#endif
+
+ args = UTI_SplitString(line, words, 3);
+ if (args >= 2)
+ type = words[1];
+
+ if (args > 3 ||
+ (args >= 1 && sscanf(words[0], "%u", &id) != 1) ||
+ (args >= 3 && sscanf(words[2], "%u", &bits) != 1)) {
+ LOG(LOGS_ERR, "Invalid syntax for keygen command");
+ return 0;
+ }
+
+#ifdef HAVE_CMAC
+ cmac_length = CMC_GetKeyLength(UTI_CmacNameToAlgorithm(type));
+#else
+ cmac_length = 0;
+#endif
+
+ if (HSH_GetHashId(UTI_HashNameToAlgorithm(type)) >= 0) {
+ length = (bits + 7) / 8;
+ } else if (cmac_length > 0) {
+ length = cmac_length;
+ } else {
+ LOG(LOGS_ERR, "Unknown hash function or cipher %s", type);
+ return 0;
+ }
+
+ length = CLAMP(10, length, sizeof (key));
+
+ UTI_GetRandomBytesUrandom(key, length);
+
+ printf("%u %s HEX:", id, type);
+ for (i = 0; i < length; i++)
+ printf("%02hhX", key[i]);
+ printf("\n");
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_line(char *line)
+{
+ char *command;
+ int do_normal_submit;
+ int ret;
+ CMD_Request tx_message;
+ CMD_Reply rx_message;
+
+ ret = 0;
+
+ do_normal_submit = 1;
+
+ CPS_NormalizeLine(line);
+
+ if (!*line) {
+ fflush(stderr);
+ fflush(stdout);
+ return 1;
+ };
+
+ command = line;
+ line = CPS_SplitWord(line);
+
+ if (!strcmp(command, "accheck")) {
+ do_normal_submit = process_cmd_accheck(&tx_message, line);
+ } else if (!strcmp(command, "activity")) {
+ do_normal_submit = 0;
+ ret = process_cmd_activity(line);
+ } else if (!strcmp(command, "add")) {
+ do_normal_submit = process_cmd_add_source(&tx_message, line);
+ } else if (!strcmp(command, "allow")) {
+ do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_ALLOW, REQ_ALLOWALL);
+ } else if (!strcmp(command, "authdata")) {
+ do_normal_submit = 0;
+ ret = process_cmd_authdata(line);
+ } else if (!strcmp(command, "burst")) {
+ do_normal_submit = process_cmd_burst(&tx_message, line);
+ } else if (!strcmp(command, "clients")) {
+ ret = process_cmd_clients(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "cmdaccheck")) {
+ do_normal_submit = process_cmd_cmdaccheck(&tx_message, line);
+ } else if (!strcmp(command, "cmdallow")) {
+ do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_CMDALLOW, REQ_CMDALLOWALL);
+ } else if (!strcmp(command, "cmddeny")) {
+ do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_CMDDENY, REQ_CMDDENYALL);
+ } else if (!strcmp(command, "cyclelogs")) {
+ process_cmd_cyclelogs(&tx_message, line);
+ } else if (!strcmp(command, "delete")) {
+ do_normal_submit = process_cmd_delete(&tx_message, line);
+ } else if (!strcmp(command, "deny")) {
+ do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_DENY, REQ_DENYALL);
+ } else if (!strcmp(command, "dfreq")) {
+ do_normal_submit = process_cmd_dfreq(&tx_message, line);
+ } else if (!strcmp(command, "dns")) {
+ ret = process_cmd_dns(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "doffset")) {
+ do_normal_submit = process_cmd_doffset(&tx_message, line);
+ } else if (!strcmp(command, "dump")) {
+ process_cmd_dump(&tx_message, line);
+ } else if (!strcmp(command, "exit")) {
+ do_normal_submit = 0;
+ quit = 1;
+ ret = 1;
+ } else if (!strcmp(command, "help")) {
+ do_normal_submit = 0;
+ give_help();
+ ret = 1;
+ } else if (!strcmp(command, "keygen")) {
+ ret = process_cmd_keygen(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "local")) {
+ do_normal_submit = process_cmd_local(&tx_message, line);
+ } else if (!strcmp(command, "makestep")) {
+ do_normal_submit = process_cmd_makestep(&tx_message, line);
+ } else if (!strcmp(command, "manual")) {
+ if (!strncmp(line, "list", 4)) {
+ do_normal_submit = 0;
+ ret = process_cmd_manual_list(CPS_SplitWord(line));
+ } else if (!strncmp(line, "delete", 6)) {
+ do_normal_submit = process_cmd_manual_delete(&tx_message, CPS_SplitWord(line));
+ } else {
+ do_normal_submit = process_cmd_manual(&tx_message, line);
+ }
+ } else if (!strcmp(command, "maxdelay")) {
+ do_normal_submit = process_cmd_maxdelay(&tx_message, line);
+ } else if (!strcmp(command, "maxdelaydevratio")) {
+ do_normal_submit = process_cmd_maxdelaydevratio(&tx_message, line);
+ } else if (!strcmp(command, "maxdelayratio")) {
+ do_normal_submit = process_cmd_maxdelayratio(&tx_message, line);
+ } else if (!strcmp(command, "maxpoll")) {
+ do_normal_submit = process_cmd_maxpoll(&tx_message, line);
+ } else if (!strcmp(command, "maxupdateskew")) {
+ do_normal_submit = process_cmd_maxupdateskew(&tx_message, line);
+ } else if (!strcmp(command, "minpoll")) {
+ do_normal_submit = process_cmd_minpoll(&tx_message, line);
+ } else if (!strcmp(command, "minstratum")) {
+ do_normal_submit = process_cmd_minstratum(&tx_message, line);
+ } else if (!strcmp(command, "ntpdata")) {
+ do_normal_submit = 0;
+ ret = process_cmd_ntpdata(line);
+ } else if (!strcmp(command, "offline")) {
+ do_normal_submit = process_cmd_offline(&tx_message, line);
+ } else if (!strcmp(command, "online")) {
+ do_normal_submit = process_cmd_online(&tx_message, line);
+ } else if (!strcmp(command, "onoffline")) {
+ process_cmd_onoffline(&tx_message, line);
+ } else if (!strcmp(command, "polltarget")) {
+ do_normal_submit = process_cmd_polltarget(&tx_message, line);
+ } else if (!strcmp(command, "quit")) {
+ do_normal_submit = 0;
+ quit = 1;
+ ret = 1;
+ } else if (!strcmp(command, "refresh")) {
+ process_cmd_refresh(&tx_message, line);
+ } else if (!strcmp(command, "rekey")) {
+ process_cmd_rekey(&tx_message, line);
+ } else if (!strcmp(command, "reload")) {
+ do_normal_submit = process_cmd_reload(&tx_message, line);
+ } else if (!strcmp(command, "reselect")) {
+ process_cmd_reselect(&tx_message, line);
+ } else if (!strcmp(command, "reselectdist")) {
+ do_normal_submit = process_cmd_reselectdist(&tx_message, line);
+ } else if (!strcmp(command, "reset")) {
+ do_normal_submit = process_cmd_reset(&tx_message, line);
+ } else if (!strcmp(command, "retries")) {
+ ret = process_cmd_retries(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "rtcdata")) {
+ do_normal_submit = 0;
+ ret = process_cmd_rtcreport(line);
+ } else if (!strcmp(command, "selectdata")) {
+ do_normal_submit = 0;
+ ret = process_cmd_selectdata(line);
+ } else if (!strcmp(command, "selectopts")) {
+ do_normal_submit = process_cmd_selectopts(&tx_message, line);
+ } else if (!strcmp(command, "serverstats")) {
+ do_normal_submit = 0;
+ ret = process_cmd_serverstats(line);
+ } else if (!strcmp(command, "settime")) {
+ do_normal_submit = 0;
+ ret = process_cmd_settime(line);
+ } else if (!strcmp(command, "shutdown")) {
+ process_cmd_shutdown(&tx_message, line);
+ } else if (!strcmp(command, "smoothing")) {
+ do_normal_submit = 0;
+ ret = process_cmd_smoothing(line);
+ } else if (!strcmp(command, "smoothtime")) {
+ do_normal_submit = process_cmd_smoothtime(&tx_message, line);
+ } else if (!strcmp(command, "sourcename")) {
+ do_normal_submit = 0;
+ ret = process_cmd_sourcename(line);
+ } else if (!strcmp(command, "sources")) {
+ do_normal_submit = 0;
+ ret = process_cmd_sources(line);
+ } else if (!strcmp(command, "sourcestats")) {
+ do_normal_submit = 0;
+ ret = process_cmd_sourcestats(line);
+ } else if (!strcmp(command, "timeout")) {
+ ret = process_cmd_timeout(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "tracking")) {
+ ret = process_cmd_tracking(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "trimrtc")) {
+ process_cmd_trimrtc(&tx_message, line);
+ } else if (!strcmp(command, "waitsync")) {
+ ret = process_cmd_waitsync(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "writertc")) {
+ process_cmd_writertc(&tx_message, line);
+ } else if (!strcmp(command, "authhash") ||
+ !strcmp(command, "password")) {
+ /* Warn, but don't return error to not break scripts */
+ LOG(LOGS_WARN, "Authentication is no longer supported");
+ do_normal_submit = 0;
+ ret = 1;
+ } else {
+ LOG(LOGS_ERR, "Unrecognized command");
+ do_normal_submit = 0;
+ }
+
+ if (do_normal_submit) {
+ ret = request_reply(&tx_message, &rx_message, RPY_NULL, 1);
+ }
+
+ if (end_dot) {
+ printf(".\n");
+ }
+
+ fflush(stderr);
+
+ if (fflush(stdout) != 0 || ferror(stdout) != 0) {
+ LOG(LOGS_ERR, "Could not write to stdout");
+
+ /* Return error for commands that print data */
+ if (!do_normal_submit)
+ return 0;
+ }
+
+ return ret;
+}
+
+/* ================================================== */
+
+#define MAX_LINE_LENGTH 2048
+
+static int
+process_args(int argc, char **argv, int multi)
+{
+ char line[MAX_LINE_LENGTH];
+ int i, l, ret = 0;
+
+ for (i = l = 0; i < argc; i++) {
+ l += snprintf(line + l, sizeof (line) - l, "%s ", argv[i]);
+ if (l >= sizeof (line)) {
+ LOG(LOGS_ERR, "Command too long");
+ return 0;
+ }
+
+ if (!multi && i + 1 < argc)
+ continue;
+
+ ret = process_line(line);
+ if (!ret || quit)
+ break;
+
+ l = 0;
+ }
+
+ return ret;
+}
+
+/* ================================================== */
+
+static void
+signal_handler(int signum)
+{
+ quit = 1;
+}
+
+/* ================================================== */
+
+static void
+display_gpl(void)
+{
+ printf("chrony version %s\n"
+ "Copyright (C) 1997-2003, 2007, 2009-2023 Richard P. Curnow and others\n"
+ "chrony comes with ABSOLUTELY NO WARRANTY. This is free software, and\n"
+ "you are welcome to redistribute it under certain conditions. See the\n"
+ "GNU General Public License version 2 for details.\n\n",
+ CHRONY_VERSION);
+}
+
+/* ================================================== */
+
+static void
+print_help(const char *progname)
+{
+ printf("Usage: %s [OPTION]... [COMMAND]...\n\n"
+ "Options:\n"
+ " -4\t\tUse IPv4 addresses only\n"
+ " -6\t\tUse IPv6 addresses only\n"
+ " -n\t\tDon't resolve hostnames\n"
+ " -N\t\tPrint original source names\n"
+ " -c\t\tEnable CSV format\n"
+ " -e\t\tEnd responses with dot\n"
+#if DEBUG > 0
+ " -d\t\tEnable debug messages\n"
+#endif
+ " -m\t\tAccept multiple commands\n"
+ " -h HOST\tSpecify server (%s)\n"
+ " -p PORT\tSpecify UDP port (%d)\n"
+ " -v, --version\tPrint version and exit\n"
+ " --help\tPrint usage and exit\n",
+ progname, DEFAULT_COMMAND_SOCKET",127.0.0.1,::1", DEFAULT_CANDM_PORT);
+}
+
+/* ================================================== */
+
+static void
+print_version(void)
+{
+ printf("chronyc (chrony) version %s (%s)\n", CHRONY_VERSION, CHRONYC_FEATURES);
+}
+
+/* ================================================== */
+
+int
+main(int argc, char **argv)
+{
+ char *line;
+ const char *progname = argv[0];
+ const char *hostnames = NULL;
+ int opt, ret = 1, multi = 0, family = IPADDR_UNSPEC;
+ int port = DEFAULT_CANDM_PORT;
+
+ /* Parse long command-line options */
+ for (optind = 1; optind < argc; optind++) {
+ if (!strcmp("--help", argv[optind])) {
+ print_help(progname);
+ return 0;
+ } else if (!strcmp("--version", argv[optind])) {
+ print_version();
+ return 0;
+ }
+ }
+
+ optind = 1;
+
+ /* Parse short command-line options */
+ while ((opt = getopt(argc, argv, "+46acdef:h:mnNp:v")) != -1) {
+ switch (opt) {
+ case '4':
+ case '6':
+ family = opt == '4' ? IPADDR_INET4 : IPADDR_INET6;
+ break;
+ case 'a':
+ case 'f':
+ /* For compatibility only */
+ break;
+ case 'c':
+ csv_mode = 1;
+ break;
+ case 'd':
+#if DEBUG > 0
+ log_min_severity = LOGS_DEBUG;
+#endif
+ break;
+ case 'e':
+ end_dot = 1;
+ break;
+ case 'h':
+ hostnames = optarg;
+ break;
+ case 'm':
+ multi = 1;
+ break;
+ case 'n':
+ no_dns = 1;
+ break;
+ case 'N':
+ source_names = 1;
+ break;
+ case 'p':
+ port = atoi(optarg);
+ break;
+ case 'v':
+ print_version();
+ return 0;
+ default:
+ print_help(progname);
+ return 1;
+ }
+ }
+
+ if (isatty(0) && isatty(1) && isatty(2)) {
+ on_terminal = 1;
+ }
+
+ if (on_terminal && optind == argc) {
+ display_gpl();
+ }
+
+ DNS_SetAddressFamily(family);
+
+ if (!hostnames) {
+ hostnames = DEFAULT_COMMAND_SOCKET",127.0.0.1,::1";
+ }
+
+ UTI_SetQuitSignalsHandler(signal_handler, 0);
+
+ SCK_Initialise(IPADDR_UNSPEC);
+ server_addresses = get_addresses(hostnames, port);
+
+ if (!open_io())
+ LOG_FATAL("Could not open connection to daemon");
+
+ if (optind < argc) {
+ ret = process_args(argc - optind, argv + optind, multi);
+ } else {
+ do {
+ line = read_line();
+ if (line && !quit) {
+ ret = process_line(line);
+ }else {
+ /* supply the final '\n' when user exits via ^D */
+ if( on_terminal ) printf("\n");
+ }
+ } while (line && !quit);
+ }
+
+ close_io();
+ free_addresses(server_addresses);
+ SCK_Finalise();
+
+ return !ret;
+}
+
+