| /* |
| chronyd/chronyc - Programs for keeping computer clocks accurate. |
| |
| ********************************************************************** |
| * Copyright (C) Richard P. Curnow 1997-2003 |
| * Copyright (C) Miroslav Lichvar 2013-2014, 2016, 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. |
| * |
| ********************************************************************** |
| |
| ======================================================================= |
| |
| Module for parsing various forms of directive and command lines that |
| are common to the configuration file and to the command client. |
| |
| */ |
| |
| #include "config.h" |
| |
| #include "sysincl.h" |
| |
| #include "cmdparse.h" |
| #include "memory.h" |
| #include "nameserv.h" |
| #include "ntp.h" |
| #include "util.h" |
| |
| /* ================================================== */ |
| |
| int |
| CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src) |
| { |
| char *hostname, *cmd; |
| uint32_t ef_type; |
| int n; |
| |
| src->port = SRC_DEFAULT_PORT; |
| src->params.minpoll = SRC_DEFAULT_MINPOLL; |
| src->params.maxpoll = SRC_DEFAULT_MAXPOLL; |
| src->params.connectivity = SRC_ONLINE; |
| src->params.auto_offline = 0; |
| src->params.presend_minpoll = SRC_DEFAULT_PRESEND_MINPOLL; |
| src->params.burst = 0; |
| src->params.iburst = 0; |
| src->params.min_stratum = SRC_DEFAULT_MINSTRATUM; |
| src->params.poll_target = SRC_DEFAULT_POLLTARGET; |
| src->params.version = 0; |
| src->params.max_sources = SRC_DEFAULT_MAXSOURCES; |
| src->params.min_samples = SRC_DEFAULT_MINSAMPLES; |
| src->params.max_samples = SRC_DEFAULT_MAXSAMPLES; |
| src->params.filter_length = 0; |
| src->params.interleaved = 0; |
| src->params.sel_options = 0; |
| src->params.nts = 0; |
| src->params.nts_port = SRC_DEFAULT_NTSPORT; |
| src->params.copy = 0; |
| src->params.ext_fields = 0; |
| src->params.authkey = INACTIVE_AUTHKEY; |
| src->params.cert_set = SRC_DEFAULT_CERTSET; |
| src->params.max_delay = SRC_DEFAULT_MAXDELAY; |
| src->params.max_delay_ratio = SRC_DEFAULT_MAXDELAYRATIO; |
| src->params.max_delay_dev_ratio = SRC_DEFAULT_MAXDELAYDEVRATIO; |
| src->params.min_delay = 0.0; |
| src->params.asymmetry = SRC_DEFAULT_ASYMMETRY; |
| src->params.offset = 0.0; |
| |
| hostname = line; |
| line = CPS_SplitWord(line); |
| |
| if (!*hostname) |
| return 0; |
| |
| src->name = hostname; |
| |
| /* Parse options */ |
| for (; *line; line += n) { |
| cmd = line; |
| line = CPS_SplitWord(line); |
| n = 0; |
| |
| if (!strcasecmp(cmd, "auto_offline")) { |
| src->params.auto_offline = 1; |
| } else if (!strcasecmp(cmd, "burst")) { |
| src->params.burst = 1; |
| } else if (!strcasecmp(cmd, "copy")) { |
| src->params.copy = 1; |
| } else if (!strcasecmp(cmd, "iburst")) { |
| src->params.iburst = 1; |
| } else if (!strcasecmp(cmd, "offline")) { |
| src->params.connectivity = SRC_OFFLINE; |
| } else if (!strcasecmp(cmd, "noselect")) { |
| src->params.sel_options |= SRC_SELECT_NOSELECT; |
| } else if (!strcasecmp(cmd, "prefer")) { |
| src->params.sel_options |= SRC_SELECT_PREFER; |
| } else if (!strcasecmp(cmd, "require")) { |
| src->params.sel_options |= SRC_SELECT_REQUIRE; |
| } else if (!strcasecmp(cmd, "trust")) { |
| src->params.sel_options |= SRC_SELECT_TRUST; |
| } else if (!strcasecmp(cmd, "certset")) { |
| if (sscanf(line, "%"SCNu32"%n", &src->params.cert_set, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "key")) { |
| if (sscanf(line, "%"SCNu32"%n", &src->params.authkey, &n) != 1 || |
| src->params.authkey == INACTIVE_AUTHKEY) |
| return 0; |
| } else if (!strcasecmp(cmd, "asymmetry")) { |
| if (sscanf(line, "%lf%n", &src->params.asymmetry, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "extfield")) { |
| if (sscanf(line, "%"SCNx32"%n", &ef_type, &n) != 1) |
| return 0; |
| switch (ef_type) { |
| case NTP_EF_EXP1: |
| src->params.ext_fields |= NTP_EF_FLAG_EXP1; |
| break; |
| default: |
| return 0; |
| } |
| } else if (!strcasecmp(cmd, "filter")) { |
| if (sscanf(line, "%d%n", &src->params.filter_length, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "maxdelay")) { |
| if (sscanf(line, "%lf%n", &src->params.max_delay, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "maxdelayratio")) { |
| if (sscanf(line, "%lf%n", &src->params.max_delay_ratio, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "maxdelaydevratio")) { |
| if (sscanf(line, "%lf%n", &src->params.max_delay_dev_ratio, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "maxpoll")) { |
| if (sscanf(line, "%d%n", &src->params.maxpoll, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "maxsamples")) { |
| if (sscanf(line, "%d%n", &src->params.max_samples, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "maxsources")) { |
| if (sscanf(line, "%d%n", &src->params.max_sources, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "mindelay")) { |
| if (sscanf(line, "%lf%n", &src->params.min_delay, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "minpoll")) { |
| if (sscanf(line, "%d%n", &src->params.minpoll, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "minsamples")) { |
| if (sscanf(line, "%d%n", &src->params.min_samples, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "minstratum")) { |
| if (sscanf(line, "%d%n", &src->params.min_stratum, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "nts")) { |
| src->params.nts = 1; |
| } else if (!strcasecmp(cmd, "ntsport")) { |
| if (sscanf(line, "%d%n", &src->params.nts_port, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "offset")) { |
| if (sscanf(line, "%lf%n", &src->params.offset, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "port")) { |
| if (sscanf(line, "%d%n", &src->port, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "polltarget")) { |
| if (sscanf(line, "%d%n", &src->params.poll_target, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "presend")) { |
| if (sscanf(line, "%d%n", &src->params.presend_minpoll, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "version")) { |
| if (sscanf(line, "%d%n", &src->params.version, &n) != 1) |
| return 0; |
| } else if (!strcasecmp(cmd, "xleave")) { |
| src->params.interleaved = 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits) |
| { |
| char *p, *net, *slash; |
| uint32_t a, b, c; |
| int bits, len, n; |
| |
| p = CPS_SplitWord(line); |
| |
| if (strcmp(line, "all") == 0) { |
| *all = 1; |
| net = p; |
| p = CPS_SplitWord(p); |
| } else { |
| *all = 0; |
| net = line; |
| } |
| |
| /* Make sure there are no other arguments */ |
| if (*p) |
| return 0; |
| |
| /* No specified address or network means all IPv4 and IPv6 addresses */ |
| if (!*net) { |
| ip->family = IPADDR_UNSPEC; |
| *subnet_bits = 0; |
| return 1; |
| } |
| |
| slash = strchr(net, '/'); |
| if (slash) { |
| if (sscanf(slash + 1, "%d%n", &bits, &len) != 1 || slash[len + 1] || bits < 0) |
| return 0; |
| *slash = '\0'; |
| } else { |
| bits = -1; |
| } |
| |
| if (UTI_StringToIP(net, ip)) { |
| if (bits >= 0) |
| *subnet_bits = bits; |
| else |
| *subnet_bits = ip->family == IPADDR_INET6 ? 128 : 32; |
| return 1; |
| } |
| |
| /* Check for a shortened IPv4 network notation using only 1, 2, or 3 decimal |
| numbers. This is different than the numbers-and-dots notation accepted |
| by inet_aton()! */ |
| |
| a = b = c = 0; |
| n = sscanf(net, "%"PRIu32"%n.%"PRIu32"%n.%"PRIu32"%n", &a, &len, &b, &len, &c, &len); |
| |
| if (n > 0 && !net[len]) { |
| if (a > 255 || b > 255 || c > 255) |
| return 0; |
| |
| ip->family = IPADDR_INET4; |
| ip->addr.in4 = (a << 24) | (b << 16) | (c << 8); |
| |
| if (bits >= 0) |
| *subnet_bits = bits; |
| else |
| *subnet_bits = n * 8; |
| |
| return 1; |
| } |
| |
| /* The last possibility is a hostname */ |
| if (bits < 0 && DNS_Name2IPAddress(net, ip, 1) == DNS_Success) { |
| *subnet_bits = ip->family == IPADDR_INET6 ? 128 : 32; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance) |
| { |
| int n; |
| char *cmd; |
| |
| *stratum = 10; |
| *distance = 1.0; |
| *orphan = 0; |
| |
| while (*line) { |
| cmd = line; |
| line = CPS_SplitWord(line); |
| |
| if (!strcasecmp(cmd, "stratum")) { |
| if (sscanf(line, "%d%n", stratum, &n) != 1 || |
| *stratum >= NTP_MAX_STRATUM || *stratum <= 0) |
| return 0; |
| } else if (!strcasecmp(cmd, "orphan")) { |
| *orphan = 1; |
| n = 0; |
| } else if (!strcasecmp(cmd, "distance")) { |
| if (sscanf(line, "%lf%n", distance, &n) != 1) |
| return 0; |
| } else { |
| return 0; |
| } |
| |
| line += n; |
| } |
| |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| void |
| CPS_NormalizeLine(char *line) |
| { |
| char *p, *q; |
| int space = 1, first = 1; |
| |
| /* Remove white-space at beginning and replace white-spaces with space char */ |
| for (p = q = line; *p; p++) { |
| if (isspace((unsigned char)*p)) { |
| if (!space) |
| *q++ = ' '; |
| space = 1; |
| continue; |
| } |
| |
| /* Discard comment lines */ |
| if (first && strchr("!;#%", *p)) |
| break; |
| |
| *q++ = *p; |
| space = first = 0; |
| } |
| |
| /* Strip trailing space */ |
| if (q > line && q[-1] == ' ') |
| q--; |
| |
| *q = '\0'; |
| } |
| |
| /* ================================================== */ |
| |
| char * |
| CPS_SplitWord(char *line) |
| { |
| char *p = line, *q = line; |
| |
| /* Skip white-space before the word */ |
| while (*q && isspace((unsigned char)*q)) |
| q++; |
| |
| /* Move the word to the beginning */ |
| while (*q && !isspace((unsigned char)*q)) |
| *p++ = *q++; |
| |
| /* Find the next word */ |
| while (*q && isspace((unsigned char)*q)) |
| q++; |
| |
| *p = '\0'; |
| |
| /* Return pointer to the next word or NUL */ |
| return q; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| CPS_ParseKey(char *line, uint32_t *id, const char **type, char **key) |
| { |
| char *s1, *s2, *s3, *s4; |
| |
| s1 = line; |
| s2 = CPS_SplitWord(s1); |
| s3 = CPS_SplitWord(s2); |
| s4 = CPS_SplitWord(s3); |
| |
| /* Require two or three words */ |
| if (!*s2 || *s4) |
| return 0; |
| |
| if (sscanf(s1, "%"SCNu32, id) != 1) |
| return 0; |
| |
| if (*s3) { |
| *type = s2; |
| *key = s3; |
| } else { |
| *type = "MD5"; |
| *key = s2; |
| } |
| |
| return 1; |
| } |