/* 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-2025 * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ********************************************************************** ======================================================================= Command line client for configuring the daemon and obtaining status from it whilst running. */ #include "config.h" #include "sysincl.h" #include "array.h" #include "candm.h" #include "cmac.h" #include "logging.h" #include "memory.h" #include "nameserv.h" #include "getdate.h" #include "cmdparse.h" #include "pktlength.h" #include "socket.h" #include "util.h" #ifdef FEAT_READLINE #include #endif #define MAX_UNIX_SOCKET_LENGTH (sizeof ((struct sockaddr_un *)NULL)->sun_path) /* ================================================== */ struct Address { SCK_AddressType type; union { IPSockAddr ip; char *path; } addr; }; static ARR_Instance server_addresses; static int sock_fd = -1; static char sock_dir1[MAX_UNIX_SOCKET_LENGTH]; static char sock_dir2[MAX_UNIX_SOCKET_LENGTH]; 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); } else { /* simulate the user has entered an empty line */ *line = '\0'; } Free(cmd); 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); } /* ================================================== */ /* Bind a Unix domain socket and connect it to the server chronyd socket. Access to the sockets is restricted by the permissions and ownership of the directory in which they are bound (by default /var/run/chrony). The location of the chronyc socket follows the location of the chronyd socket. Due to the possibility of chronyc running under a different user, the permissions of the chronyc socket need to be loosened on most systems (illumos is an exception) to allow chronyd to send a response to the chronyc socket. Unfortunately, there does not seem to be a safe and portable way to do that directly (e.g. a compromised chronyd process could replace it with a symlink and cause a privileged chronyc process to change the permissions of something else). The workaround is to bind the socket in a randomly named subdirectory hidden in another subdirectory to avoid matching an existing path and force the bind()/chmod() call to fail if the visible upper directory is replaced. Note that the socket cannot be moved once bound, because the source address that chronyd will see would not match the actual path and the responses would not reach chronyc. */ static int open_unix_socket(char *server_path) { char *sock_dir0, sock_path[MAX_UNIX_SOCKET_LENGTH], rand_dir[16 + 1]; int i, s, dir_fd1 = -1; struct stat st; /* Check if the server socket is accessible */ s = SCK_OpenUnixDatagramSocket(server_path, NULL, 0); if (s < 0) return -1; SCK_CloseSocket(s); /* Generate the random hidden component of the socket path */ for (i = 0; i + 1 < sizeof (rand_dir); i++) { do UTI_GetRandomBytesUrandom(&rand_dir[i], sizeof (rand_dir[i])); while (!isalnum((unsigned char)rand_dir[i])); } rand_dir[i] = '\0'; /* Format the paths of the subdirectories and socket: sock_dir0 = dirname(server_path) sock_dir1 = sock_dir0/chronyc.$PID sock_dir2 = sock_dir0/chronyc.$PID/$RANDOM sock_path = sock_dir0/chronyc.$PID/$RANDOM/sock */ sock_dir0 = UTI_PathToDir(server_path); if (snprintf(sock_dir1, sizeof (sock_dir1), "%s/chronyc.%d", sock_dir0, (int)getpid()) >= sizeof (sock_dir1) || snprintf(sock_dir2, sizeof (sock_dir2), "%s/%s", sock_dir1, rand_dir) >= sizeof (sock_dir2) || snprintf(sock_path, sizeof (sock_path), "%s/sock", sock_dir2) >= sizeof (sock_path)) { LOG(LOGS_ERR, "Server socket path %s is too long", server_path); Free(sock_dir0); goto error1; } Free(sock_dir0); if (mkdir(sock_dir1, 0711) < 0 && errno != EEXIST) { LOG(LOGS_ERR, "Could not create %s : %s", sock_dir1, strerror(errno)); goto error1; } dir_fd1 = open(sock_dir1, O_RDONLY | O_NOFOLLOW); if (dir_fd1 < 0 || fstat(dir_fd1, &st) < 0) { LOG(LOGS_ERR, "Could not open/stat %s : %s", sock_dir1, strerror(errno)); goto error2; } if (!S_ISDIR(st.st_mode) || (st.st_mode & 0777 & ~0711) != 0 || st.st_uid != geteuid()) { LOG(LOGS_ERR, "Unexpected mode/owner of %s", sock_dir1); goto error2; } if (mkdirat(dir_fd1, rand_dir, 0711) < 0) { LOG(LOGS_ERR, "Could not create %s : %s", sock_dir2, strerror(errno)); goto error2; } s = SCK_OpenUnixDatagramSocket(server_path, sock_path, 0); if (s < 0) { LOG(LOGS_ERR, "Could not bind/connect client Unix socket"); goto error3; } if (chmod(sock_path, 0666) < 0 || chmod(sock_dir2, 0711) < 0 || fchmod(dir_fd1, 0711) < 0) { LOG(LOGS_ERR, "Could not change socket or directory permissions : %s", strerror(errno)); SCK_RemoveSocket(s); SCK_CloseSocket(s); goto error3; } close(dir_fd1); return s; error3: rmdir(sock_dir2); error2: rmdir(sock_dir1); error1: if (dir_fd1 >= 0) close(dir_fd1); sock_dir1[0] = '\0'; sock_dir2[0] = '\0'; return -1; } /* ================================================== */ /* Initialise the socket used to talk to the daemon */ static int open_socket(struct Address *addr) { switch (addr->type) { case SCK_ADDR_IP: sock_fd = SCK_OpenUdpSocket(&addr->addr.ip, NULL, NULL, 0); break; case SCK_ADDR_UNIX: sock_fd = open_unix_socket(addr->addr.path); 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); if (sock_dir2[0] != '\0') rmdir(sock_dir2); if (sock_dir1[0] != '\0') rmdir(sock_dir1); sock_dir2[0] = '\0'; sock_dir1[0] = '\0'; 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 parse_source_address_or_refid(char *s, IPAddr *address, uint32_t *ref_id) { address->family = IPADDR_UNSPEC; *ref_id = 0; /* Don't allow hostnames to avoid conflicts with reference IDs */ if (UTI_StringToIdIP(s, address) || UTI_StringToIP(s, address)) return 1; if (CPS_ParseRefid(s, ref_id) > 0) 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) { double distance = 0.0, activate = 0.0, wait_synced = 0.0, wait_unsynced = 0.0; int on_off, stratum = 0, orphan = 0; if (!strcmp(line, "off")) { on_off = 0; } else if (CPS_ParseLocal(line, &stratum, &orphan, &distance, &activate, &wait_synced, &wait_unsynced) == CPS_Success) { on_off = 1; } else { LOG(LOGS_ERR, "Invalid syntax for local command"); return 0; } msg->command = htons(REQ_LOCAL3); 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); msg->data.local.activate = UTI_FloatHostToNetwork(activate); msg->data.local.wait_synced = UTI_FloatHostToNetwork(wait_synced); msg->data.local.wait_unsynced = UTI_FloatHostToNetwork(wait_unsynced); 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; CPS_Status status; IPAddr ip_addr; int result = 0, 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 CPS_Success: /* 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); BRIEF_ASSERT(strlen(data.name) < sizeof (msg->data.ntp_source.name)); 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) | (data.family == IPADDR_INET4 ? REQ_ADDSRC_IPV4 : 0) | (data.family == IPADDR_INET6 ? REQ_ADDSRC_IPV6 : 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); msg->data.ntp_source.max_unreach = htonl(data.params.max_unreach); result = 1; break; case CPS_InvalidOption: LOG(LOGS_ERR, "Invalid %s add command", "option in"); break; case CPS_InvalidValue: LOG(LOGS_ERR, "Invalid %s add command", "value in"); break; default: LOG(LOGS_ERR, "Invalid %s add command", "syntax for"); break; } return result; } /* ================================================== */ static int process_cmd_delete(CMD_Request *msg, char *line) { char *hostname; int ok = 0; IPAddr address; msg->command = htons(REQ_DEL_SOURCE); hostname = line; CPS_SplitWord(line); if (!*hostname) { LOG(LOGS_ERR, "Invalid syntax for address"); ok = 0; } else { if (!parse_source_address(hostname, &address)) { LOG(LOGS_ERR, "Could not get address for hostname"); ok = 0; } else { UTI_IPHostToNetwork(&address, &msg->data.del_source.ip_addr); ok = 1; } } return ok; } /* ================================================== */ static void give_help(void) { int line, len; const char *s, cols[] = "System clock:\0\0" "tracking\0Display system time information\0" "makestep\0Correct clock by stepping immediately\0" "makestep \0Configure automatic clock stepping\0" "maxupdateskew \0Modify maximum valid skew to update frequency\0" "waitsync [ [ [ []]]]\0" "Wait until synchronised in specified limits\0" "\0\0" "Time sources:\0\0" "sources [-a] [-v]\0Display information about current sources\0" "sourcestats [-a] [-v]\0Display statistics about collected measurements\0" "selectdata [-a] [-v]\0Display information about source selection\0" "selectopts <+|-options>\0Modify selection options\0" "reselect\0Force reselecting synchronisation source\0" "reselectdist \0Modify reselection distance\0" "offset \0Modify offset correction\0" "\0\0" "NTP sources:\0\0" "activity\0Check how many NTP sources are online/offline\0" "authdata [-a] [-v]\0Display information about authentication\0" "ntpdata [
]\0Display information about last valid measurement\0" "add server [options]\0Add new NTP server\0" "add pool [options]\0Add new pool of NTP servers\0" "add peer [options]\0Add new NTP peer\0" "delete
\0Remove server or peer\0" "burst / [[/]
]\0Start rapid set of measurements\0" "maxdelay
\0Modify maximum valid sample delay\0" "maxdelayratio
\0Modify maximum valid delay/minimum ratio\0" "maxdelaydevratio
\0Modify maximum valid delay/deviation ratio\0" "minpoll
\0Modify minimum polling interval\0" "maxpoll
\0Modify maximum polling interval\0" "minstratum
\0Modify minimum stratum\0" "offline [[/]
]\0Set sources in subnet to offline status\0" "online [[/]
]\0Set sources in subnet to online status\0" "onoffline\0Set all sources to online or offline status\0" "\0according to network configuration\0" "polltarget
\0Modify poll target\0" "refresh\0Refresh IP addresses\0" "reload sources\0Re-read *.sources files\0" "sourcename
\0Display original name\0" "\0\0" "Manual time input:\0\0" "manual off|on|reset\0Disable/enable/reset settime command\0" "manual list\0Show previous settime entries\0" "manual delete \0Delete previous settime entry\0" "settime