| /* |
| 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-2021 |
| * |
| * 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; |
| |
| /* ================================================== */ |
| /* 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(); |
| } |
| |
| 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 |
| 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_EXP1 ? REQ_ADDSRC_EF_EXP1 : 0) | |
| (data.params.sel_options & SRC_SELECT_PREFER ? REQ_ADDSRC_PREFER : 0) | |
| (data.params.sel_options & SRC_SELECT_NOSELECT ? REQ_ADDSRC_NOSELECT : 0) | |
| (data.params.sel_options & SRC_SELECT_TRUST ? REQ_ADDSRC_TRUST : 0) | |
| (data.params.sel_options & SRC_SELECT_REQUIRE ? REQ_ADDSRC_REQUIRE : 0)); |
| msg->data.ntp_source.filter_length = htonl(data.params.filter_length); |
| msg->data.ntp_source.cert_set = htonl(data.params.cert_set); |
| 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" |
| "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", "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(unsigned long s) |
| { |
| unsigned long d; |
| |
| if (s == (uint32_t)-1) { |
| printf(" -"); |
| } else if (s < 1200) { |
| printf("%4lu", s); |
| } else if (s < 36000) { |
| printf("%3lum", s / 60); |
| } else if (s < 345600) { |
| printf("%3luh", s / 3600); |
| } else { |
| d = s / 86400; |
| if (d > 999) { |
| printf("%3luy", d / 365); |
| } else { |
| printf("%3lud", 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 long long_uinteger; |
| unsigned int uinteger; |
| 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': /* interval with unit */ |
| long_uinteger = va_arg(ap, unsigned long); |
| print_seconds(long_uinteger); |
| 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 */ |
| long_uinteger = va_arg(ap, unsigned long); |
| printf("%08lX", long_uinteger); |
| 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': /* unsigned long in decimal */ |
| long_uinteger = va_arg(ap, unsigned long); |
| printf("%*lu", width, long_uinteger); |
| break; |
| case 'V': /* timespec as seconds since epoch */ |
| ts = va_arg(ap, struct timespec *); |
| printf("%s", UTI_TimespecToString(ts)); |
| 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; |
| |
| 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; |
| |
| format_name(name, sizeof (name), 25, |
| mode == RPY_SD_MD_REF && ip_addr.family == IPADDR_INET4, |
| ip_addr.addr.in4, 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), |
| (unsigned long)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, |
| (unsigned long)ntohl(reply.data.sourcestats.n_samples), |
| (unsigned long)ntohl(reply.data.sourcestats.n_runs), |
| (unsigned long)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", |
| (unsigned long)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, |
| (unsigned long)ntohl(reply.data.auth_data.key_id), |
| ntohs(reply.data.auth_data.key_type), |
| ntohs(reply.data.auth_data.key_length), |
| (unsigned long)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", |
| UTI_IPToString(&remote_addr), (unsigned long)UTI_IPToRefid(&remote_addr), |
| ntohs(reply.data.ntp_data.remote_port), |
| UTI_IPToString(&local_addr), (unsigned long)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), |
| (unsigned long)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, |
| (unsigned long)ntohl(reply.data.ntp_data.total_tx_count), |
| (unsigned long)ntohl(reply.data.ntp_data.total_rx_count), |
| (unsigned long)ntohl(reply.data.ntp_data.total_valid_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, M - missing samples, d/D - large distance,\n"); |
| printf( " / ~ - jittery, w/W - waits for others, T - not trusted,\n"); |
| printf( "| x - falseticker, P - not preferred, U - waits for update,\n"); |
| printf( "| S - stale, O - orphan, + - 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' : '-', |
| '-', |
| (unsigned long)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_STATS3, 0)) |
| return 0; |
| |
| print_report("NTP packets received : %U\n" |
| "NTP packets dropped : %U\n" |
| "Command packets received : %U\n" |
| "Command packets dropped : %U\n" |
| "Client log records dropped : %U\n" |
| "NTS-KE connections accepted: %U\n" |
| "NTS-KE connections dropped : %U\n" |
| "Authenticated NTP packets : %U\n" |
| "Interleaved NTP packets : %U\n" |
| "NTP timestamps held : %U\n" |
| "NTP timestamp span : %U\n", |
| (unsigned long)ntohl(reply.data.server_stats.ntp_hits), |
| (unsigned long)ntohl(reply.data.server_stats.ntp_drops), |
| (unsigned long)ntohl(reply.data.server_stats.cmd_hits), |
| (unsigned long)ntohl(reply.data.server_stats.cmd_drops), |
| (unsigned long)ntohl(reply.data.server_stats.log_drops), |
| (unsigned long)ntohl(reply.data.server_stats.nke_hits), |
| (unsigned long)ntohl(reply.data.server_stats.nke_drops), |
| (unsigned long)ntohl(reply.data.server_stats.ntp_auth_hits), |
| (unsigned long)ntohl(reply.data.server_stats.ntp_interleaved_hits), |
| (unsigned long)ntohl(reply.data.server_stats.ntp_timestamps), |
| (unsigned long)ntohl(reply.data.server_stats.ntp_span_seconds), |
| 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), |
| (unsigned long)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, |
| (unsigned long)ntohl(client->ntp_hits), |
| (unsigned long)ntohl(client->ntp_drops), |
| client->ntp_interval, |
| client->ntp_timeout_interval, |
| (unsigned long)ntohl(client->last_ntp_hit_ago), |
| (unsigned long)ntohl(nke ? client->nke_hits : client->cmd_hits), |
| (unsigned long)ntohl(nke ? client->nke_drops : client->cmd_drops), |
| nke ? client->nke_interval : client->cmd_interval, |
| (unsigned long)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 = %lu\n", (unsigned long)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", |
| (unsigned long)ntohl(reply.data.activity.online), |
| (unsigned long)ntohl(reply.data.activity.offline), |
| (unsigned long)ntohl(reply.data.activity.burst_online), |
| (unsigned long)ntohl(reply.data.activity.burst_offline), |
| (unsigned long)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_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, (unsigned long)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, "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); |
| } |
| fflush(stderr); |
| fflush(stdout); |
| return ret; |
| } |
| |
| /* ================================================== */ |
| |
| static int |
| process_args(int argc, char **argv, int multi) |
| { |
| int total_length, i, ret = 0; |
| char *line; |
| |
| total_length = 0; |
| for(i=0; i<argc; i++) { |
| total_length += strlen(argv[i]) + 1; |
| } |
| |
| line = (char *) Malloc((2 + total_length) * sizeof(char)); |
| |
| for (i = 0; i < argc; i++) { |
| line[0] = '\0'; |
| if (multi) { |
| strcat(line, argv[i]); |
| } else { |
| for (; i < argc; i++) { |
| strcat(line, argv[i]); |
| if (i + 1 < argc) |
| strcat(line, " "); |
| } |
| } |
| |
| ret = process_line(line); |
| if (!ret || quit) |
| break; |
| } |
| |
| Free(line); |
| |
| 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-2021 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" |
| #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, "+46acdf: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 '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; |
| } |
| |
| |