From 2f4aa1c4125a9208b7543016eeecd9469943163f Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Fri, 14 Feb 2025 12:20:20 +0100 Subject: [PATCH] initial, unchanged sources from openbsd 7.6 --- Makefile | 19 + client.c | 513 ++++++++++++++++++++++ config.c | 186 ++++++++ constraint.c | 1167 ++++++++++++++++++++++++++++++++++++++++++++++++++ control.c | 452 +++++++++++++++++++ log.c | 202 +++++++++ log.h | 48 +++ ntp.c | 907 +++++++++++++++++++++++++++++++++++++++ ntp.h | 163 +++++++ ntp_dns.c | 251 +++++++++++ ntp_msg.c | 71 +++ ntpctl.8 | 79 ++++ ntpd.8 | 159 +++++++ ntpd.c | 924 +++++++++++++++++++++++++++++++++++++++ ntpd.conf.5 | 265 ++++++++++++ ntpd.h | 436 +++++++++++++++++++ parse.y | 841 ++++++++++++++++++++++++++++++++++++ sensors.c | 265 ++++++++++++ server.c | 203 +++++++++ util.c | 253 +++++++++++ 20 files changed, 7404 insertions(+) create mode 100644 Makefile create mode 100644 client.c create mode 100644 config.c create mode 100644 constraint.c create mode 100644 control.c create mode 100644 log.c create mode 100644 log.h create mode 100644 ntp.c create mode 100644 ntp.h create mode 100644 ntp_dns.c create mode 100644 ntp_msg.c create mode 100644 ntpctl.8 create mode 100644 ntpd.8 create mode 100644 ntpd.c create mode 100644 ntpd.conf.5 create mode 100644 ntpd.h create mode 100644 parse.y create mode 100644 sensors.c create mode 100644 server.c create mode 100644 util.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d9acd25 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +# $OpenBSD: Makefile,v 1.16 2015/11/20 18:53:42 tedu Exp $ + +PROG= ntpd +SRCS= ntpd.c log.c ntp.c ntp_msg.c parse.y config.c \ + server.c client.c sensors.c util.c ntp_dns.c \ + control.c constraint.c +CFLAGS+= -Wall -I${.CURDIR} +CFLAGS+= -fstack-protector-all +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +YFLAGS= +LDADD+= -lm -lutil -ltls -lssl -lcrypto +DPADD+= ${LIBUTIL} ${LIBCRYPTO} ${LIBSSL} ${LIBTLS} +LINKS= ${BINDIR}/ntpd ${BINDIR}/ntpctl +MAN= ntpd.8 ntpd.conf.5 ntpctl.8 + +.include diff --git a/client.c b/client.c new file mode 100644 index 0000000..a2915fd --- /dev/null +++ b/client.c @@ -0,0 +1,513 @@ +/* $OpenBSD: client.c,v 1.118 2023/12/20 15:36:36 otto Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * Copyright (c) 2004 Alexander Guy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +int client_update(struct ntp_peer *); +int auto_cmp(const void *, const void *); +void handle_auto(u_int8_t, double); +void set_deadline(struct ntp_peer *, time_t); + +void +set_next(struct ntp_peer *p, time_t t) +{ + p->next = getmonotime() + t; + p->deadline = 0; + p->poll = t; +} + +void +set_deadline(struct ntp_peer *p, time_t t) +{ + p->deadline = getmonotime() + t; + p->next = 0; +} + +int +client_peer_init(struct ntp_peer *p) +{ + p->query.fd = -1; + p->query.msg.status = MODE_CLIENT | (NTP_VERSION << 3); + p->query.xmttime = 0; + p->state = STATE_NONE; + p->shift = 0; + p->trustlevel = TRUSTLEVEL_PATHETIC; + p->lasterror = 0; + p->senderrors = 0; + + return (client_addr_init(p)); +} + +int +client_addr_init(struct ntp_peer *p) +{ + struct sockaddr_in *sa_in; + struct sockaddr_in6 *sa_in6; + struct ntp_addr *h; + + for (h = p->addr; h != NULL; h = h->next) { + switch (h->ss.ss_family) { + case AF_INET: + sa_in = (struct sockaddr_in *)&h->ss; + if (ntohs(sa_in->sin_port) == 0) + sa_in->sin_port = htons(123); + p->state = STATE_DNS_DONE; + break; + case AF_INET6: + sa_in6 = (struct sockaddr_in6 *)&h->ss; + if (ntohs(sa_in6->sin6_port) == 0) + sa_in6->sin6_port = htons(123); + p->state = STATE_DNS_DONE; + break; + default: + fatalx("king bula sez: wrong AF in client_addr_init"); + /* NOTREACHED */ + } + } + + p->query.fd = -1; + set_next(p, 0); + + return (0); +} + +int +client_nextaddr(struct ntp_peer *p) +{ + if (p->query.fd != -1) { + close(p->query.fd); + p->query.fd = -1; + } + + if (p->state == STATE_DNS_INPROGRESS) + return (-1); + + if (p->addr_head.a == NULL) { + priv_dns(IMSG_HOST_DNS, p->addr_head.name, p->id); + p->state = STATE_DNS_INPROGRESS; + return (-1); + } + + p->shift = 0; + p->trustlevel = TRUSTLEVEL_PATHETIC; + + if (p->addr == NULL) + p->addr = p->addr_head.a; + else if ((p->addr = p->addr->next) == NULL) + return (1); + + return (0); +} + +int +client_query(struct ntp_peer *p) +{ + int val; + + if (p->addr == NULL && client_nextaddr(p) == -1) { + if (conf->settime) + set_next(p, INTERVAL_AUIO_DNSFAIL); + else + set_next(p, MAXIMUM(SETTIME_TIMEOUT, + scale_interval(INTERVAL_QUERY_AGGRESSIVE))); + return (0); + } + + if (conf->status.synced && p->addr->notauth) { + peer_addr_head_clear(p); + client_nextaddr(p); + return (0); + } + + if (p->state < STATE_DNS_DONE || p->addr == NULL) + return (-1); + + if (p->query.fd == -1) { + struct sockaddr *sa = (struct sockaddr *)&p->addr->ss; + struct sockaddr *qa4 = (struct sockaddr *)&p->query_addr4; + struct sockaddr *qa6 = (struct sockaddr *)&p->query_addr6; + + if ((p->query.fd = socket(p->addr->ss.ss_family, SOCK_DGRAM, + 0)) == -1) + fatal("client_query socket"); + + if (p->addr->ss.ss_family == qa4->sa_family) { + if (bind(p->query.fd, qa4, SA_LEN(qa4)) == -1) + fatal("couldn't bind to IPv4 query address: %s", + log_sockaddr(qa4)); + } else if (p->addr->ss.ss_family == qa6->sa_family) { + if (bind(p->query.fd, qa6, SA_LEN(qa6)) == -1) + fatal("couldn't bind to IPv6 query address: %s", + log_sockaddr(qa6)); + } + + if (connect(p->query.fd, sa, SA_LEN(sa)) == -1) { + if (errno == ECONNREFUSED || errno == ENETUNREACH || + errno == EHOSTUNREACH || errno == EADDRNOTAVAIL) { + /* cycle through addresses, but do increase + senderrors */ + client_nextaddr(p); + if (p->addr == NULL) + p->addr = p->addr_head.a; + set_next(p, MAXIMUM(SETTIME_TIMEOUT, + scale_interval(INTERVAL_QUERY_AGGRESSIVE))); + p->senderrors++; + return (-1); + } else + fatal("client_query connect"); + } + val = IPTOS_LOWDELAY; + if (p->addr->ss.ss_family == AF_INET && setsockopt(p->query.fd, + IPPROTO_IP, IP_TOS, &val, sizeof(val)) == -1) + log_warn("setsockopt IPTOS_LOWDELAY"); + val = 1; + if (setsockopt(p->query.fd, SOL_SOCKET, SO_TIMESTAMP, + &val, sizeof(val)) == -1) + fatal("setsockopt SO_TIMESTAMP"); + } + + /* + * Send out a random 64-bit number as our transmit time. The NTP + * server will copy said number into the originate field on the + * response that it sends us. This is totally legal per the SNTP spec. + * + * The impact of this is two fold: we no longer send out the current + * system time for the world to see (which may aid an attacker), and + * it gives us a (not very secure) way of knowing that we're not + * getting spoofed by an attacker that can't capture our traffic + * but can spoof packets from the NTP server we're communicating with. + * + * Save the real transmit timestamp locally. + */ + + p->query.msg.xmttime.int_partl = arc4random(); + p->query.msg.xmttime.fractionl = arc4random(); + p->query.xmttime = gettime(); + + if (ntp_sendmsg(p->query.fd, NULL, &p->query.msg) == -1) { + p->senderrors++; + set_next(p, INTERVAL_QUERY_PATHETIC); + p->trustlevel = TRUSTLEVEL_PATHETIC; + return (-1); + } + + p->senderrors = 0; + p->state = STATE_QUERY_SENT; + set_deadline(p, QUERYTIME_MAX); + + return (0); +} + +int +auto_cmp(const void *a, const void *b) +{ + double at = *(const double *)a; + double bt = *(const double *)b; + return at < bt ? -1 : (at > bt ? 1 : 0); +} + +void +handle_auto(uint8_t trusted, double offset) +{ + static int count; + static double v[AUTO_REPLIES]; + + /* + * It happens the (constraint) resolves initially fail, don't give up + * but see if we get validated replies later. + */ + if (!trusted && conf->constraint_median == 0) + return; + + if (offset < AUTO_THRESHOLD) { + /* don't bother */ + priv_settime(0, "offset is negative or close enough"); + return; + } + /* collect some more */ + v[count++] = offset; + if (count < AUTO_REPLIES) + return; + + /* we have enough */ + qsort(v, count, sizeof(double), auto_cmp); + if (AUTO_REPLIES % 2 == 0) + offset = (v[AUTO_REPLIES / 2 - 1] + v[AUTO_REPLIES / 2]) / 2; + else + offset = v[AUTO_REPLIES / 2]; + priv_settime(offset, ""); +} + + +/* + * -1: Not processed, not an NTP message (e.g. icmp induced ECONNREFUSED) + * 0: Not prrocessed due to validation issues + * 1: NTP message validated and processed + */ +int +client_dispatch(struct ntp_peer *p, u_int8_t settime, u_int8_t automatic) +{ + struct ntp_msg msg; + struct msghdr somsg; + struct iovec iov[1]; + struct timeval tv; + char buf[NTP_MSGSIZE]; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(tv))]; + } cmsgbuf; + struct cmsghdr *cmsg; + ssize_t size; + double T1, T2, T3, T4, offset, delay; + time_t interval; + + memset(&somsg, 0, sizeof(somsg)); + iov[0].iov_base = buf; + iov[0].iov_len = sizeof(buf); + somsg.msg_iov = iov; + somsg.msg_iovlen = 1; + somsg.msg_control = cmsgbuf.buf; + somsg.msg_controllen = sizeof(cmsgbuf.buf); + + if ((size = recvmsg(p->query.fd, &somsg, 0)) == -1) { + if (errno == EHOSTUNREACH || errno == EHOSTDOWN || + errno == ENETUNREACH || errno == ENETDOWN || + errno == ECONNREFUSED || errno == EADDRNOTAVAIL || + errno == ENOPROTOOPT || errno == ENOENT) { + client_log_error(p, "recvmsg", errno); + set_next(p, error_interval()); + return (-1); + } else + fatal("recvfrom"); + } + + if (somsg.msg_flags & MSG_TRUNC) { + client_log_error(p, "recvmsg packet", EMSGSIZE); + set_next(p, error_interval()); + return (0); + } + + if (somsg.msg_flags & MSG_CTRUNC) { + client_log_error(p, "recvmsg control data", E2BIG); + set_next(p, error_interval()); + return (0); + } + + for (cmsg = CMSG_FIRSTHDR(&somsg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&somsg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_TIMESTAMP) { + memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv)); + T4 = gettime_from_timeval(&tv); + break; + } + } + if (cmsg == NULL) + fatal("SCM_TIMESTAMP"); + + ntp_getmsg((struct sockaddr *)&p->addr->ss, buf, size, &msg); + + if (msg.orgtime.int_partl != p->query.msg.xmttime.int_partl || + msg.orgtime.fractionl != p->query.msg.xmttime.fractionl) + return (0); + + if ((msg.status & LI_ALARM) == LI_ALARM || msg.stratum == 0 || + msg.stratum > NTP_MAXSTRATUM) { + char s[16]; + + if ((msg.status & LI_ALARM) == LI_ALARM) { + strlcpy(s, "alarm", sizeof(s)); + } else if (msg.stratum == 0) { + /* Kiss-o'-Death (KoD) packet */ + strlcpy(s, "KoD", sizeof(s)); + } else if (msg.stratum > NTP_MAXSTRATUM) { + snprintf(s, sizeof(s), "stratum %d", msg.stratum); + } + interval = error_interval(); + set_next(p, interval); + log_info("reply from %s: not synced (%s), next query %llds", + log_ntp_addr(p->addr), s, (long long)interval); + return (0); + } + + /* + * From RFC 2030 (with a correction to the delay math): + * + * Timestamp Name ID When Generated + * ------------------------------------------------------------ + * Originate Timestamp T1 time request sent by client + * Receive Timestamp T2 time request received by server + * Transmit Timestamp T3 time reply sent by server + * Destination Timestamp T4 time reply received by client + * + * The roundtrip delay d and local clock offset t are defined as + * + * d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2. + */ + + T1 = p->query.xmttime; + T2 = lfp_to_d(msg.rectime); + T3 = lfp_to_d(msg.xmttime); + + /* Detect liars */ + if (!p->trusted && conf->constraint_median != 0 && + (constraint_check(T2) != 0 || constraint_check(T3) != 0)) { + log_info("reply from %s: constraint check failed", + log_ntp_addr(p->addr)); + set_next(p, error_interval()); + return (0); + } + + p->reply[p->shift].offset = ((T2 - T1) + (T3 - T4)) / 2 - getoffset(); + p->reply[p->shift].delay = (T4 - T1) - (T3 - T2); + p->reply[p->shift].status.stratum = msg.stratum; + if (p->reply[p->shift].delay < 0) { + interval = error_interval(); + set_next(p, interval); + log_info("reply from %s: negative delay %fs, " + "next query %llds", + log_ntp_addr(p->addr), + p->reply[p->shift].delay, (long long)interval); + return (0); + } + p->reply[p->shift].error = (T2 - T1) - (T3 - T4); + p->reply[p->shift].rcvd = getmonotime(); + p->reply[p->shift].good = 1; + + p->reply[p->shift].status.leap = (msg.status & LIMASK); + p->reply[p->shift].status.precision = msg.precision; + p->reply[p->shift].status.rootdelay = sfp_to_d(msg.rootdelay); + p->reply[p->shift].status.rootdispersion = sfp_to_d(msg.dispersion); + p->reply[p->shift].status.refid = msg.refid; + p->reply[p->shift].status.reftime = lfp_to_d(msg.reftime); + p->reply[p->shift].status.poll = msg.ppoll; + + if (p->addr->ss.ss_family == AF_INET) { + p->reply[p->shift].status.send_refid = + ((struct sockaddr_in *)&p->addr->ss)->sin_addr.s_addr; + } else if (p->addr->ss.ss_family == AF_INET6) { + MD5_CTX context; + u_int8_t digest[MD5_DIGEST_LENGTH]; + + MD5Init(&context); + MD5Update(&context, ((struct sockaddr_in6 *)&p->addr->ss)-> + sin6_addr.s6_addr, sizeof(struct in6_addr)); + MD5Final(digest, &context); + memcpy((char *)&p->reply[p->shift].status.send_refid, digest, + sizeof(u_int32_t)); + } else + p->reply[p->shift].status.send_refid = msg.xmttime.fractionl; + + p->state = STATE_REPLY_RECEIVED; + + /* every received reply which we do not discard increases trust */ + if (p->trustlevel < TRUSTLEVEL_MAX) { + if (p->trustlevel < TRUSTLEVEL_BADPEER && + p->trustlevel + 1 >= TRUSTLEVEL_BADPEER) + log_info("peer %s now valid", + log_ntp_addr(p->addr)); + p->trustlevel++; + } + + offset = p->reply[p->shift].offset; + delay = p->reply[p->shift].delay; + + client_update(p); + if (settime) { + if (automatic) + handle_auto(p->trusted, p->reply[p->shift].offset); + else + priv_settime(p->reply[p->shift].offset, ""); + } + + if (p->trustlevel < TRUSTLEVEL_PATHETIC) + interval = scale_interval(INTERVAL_QUERY_PATHETIC); + else if (p->trustlevel < TRUSTLEVEL_AGGRESSIVE) + interval = (conf->settime && conf->automatic) ? + INTERVAL_QUERY_ULTRA_VIOLENCE : + scale_interval(INTERVAL_QUERY_AGGRESSIVE); + else + interval = scale_interval(INTERVAL_QUERY_NORMAL); + + log_debug("reply from %s: offset %f delay %f, " + "next query %llds", log_ntp_addr(p->addr), + offset, delay, (long long)interval); + + set_next(p, interval); + + if (++p->shift >= OFFSET_ARRAY_SIZE) + p->shift = 0; + + return (1); +} + +int +client_update(struct ntp_peer *p) +{ + int shift, best = -1, good = 0; + + /* + * clock filter + * find the offset which arrived with the lowest delay + * use that as the peer update + * invalidate it and all older ones + */ + + for (shift = 0; shift < OFFSET_ARRAY_SIZE; shift++) + if (p->reply[shift].good) { + good++; + if (best == -1 || + p->reply[shift].delay < p->reply[best].delay) + best = shift; + } + + if (best == -1 || good < 8) + return (-1); + + p->update = p->reply[best]; + if (priv_adjtime() == 0) { + for (shift = 0; shift < OFFSET_ARRAY_SIZE; shift++) + if (p->reply[shift].rcvd <= p->reply[best].rcvd) + p->reply[shift].good = 0; + } + return (0); +} + +void +client_log_error(struct ntp_peer *peer, const char *operation, int error) +{ + const char *address; + + address = log_ntp_addr(peer->addr); + if (peer->lasterror == error) { + log_debug("%s %s: %s", operation, address, strerror(error)); + return; + } + peer->lasterror = error; + log_warn("%s %s", operation, address); +} diff --git a/config.c b/config.c new file mode 100644 index 0000000..0dd2978 --- /dev/null +++ b/config.c @@ -0,0 +1,186 @@ +/* $OpenBSD: config.c,v 1.33 2020/04/12 14:20:56 otto Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "ntpd.h" + +struct ntp_addr *host_ip(const char *); +int host_dns1(const char *, struct ntp_addr **, int); + +static u_int32_t maxid = 0; +static u_int32_t constraint_maxid = 0; +int non_numeric; + +void +host(const char *s, struct ntp_addr **hn) +{ + struct ntp_addr *h; + + if (!strcmp(s, "*")) { + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + } else { + if ((h = host_ip(s)) == NULL) { + non_numeric = 1; + return; + } + } + + *hn = h; +} + +struct ntp_addr * +host_ip(const char *s) +{ + struct addrinfo hints, *res; + struct ntp_addr *h = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; /*dummy*/ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(s, "0", &hints, &res) == 0) { + if (res->ai_family == AF_INET || + res->ai_family == AF_INET6) { + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + memcpy(&h->ss, res->ai_addr, res->ai_addrlen); + } + freeaddrinfo(res); + } + + return (h); +} + +void +host_dns_free(struct ntp_addr *hn) +{ + struct ntp_addr *h = hn, *tmp; + while (h) { + tmp = h; + h = h->next; + free(tmp); + } +} + +int +host_dns1(const char *s, struct ntp_addr **hn, int notauth) +{ + struct addrinfo hints, *res0, *res; + int error, cnt = 0; + struct ntp_addr *h, *hh = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; /* DUMMY */ + hints.ai_flags = AI_ADDRCONFIG; + error = getaddrinfo(s, NULL, &hints, &res0); + if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME) + return (0); + if (error) { + log_warnx("could not parse \"%s\": %s", s, + gai_strerror(error)); + return (-1); + } + + for (res = res0; res && cnt < MAX_SERVERS_DNS; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + memcpy(&h->ss, res->ai_addr, res->ai_addrlen); + h->notauth = notauth; + + h->next = hh; + hh = h; + cnt++; + } + freeaddrinfo(res0); + + *hn = hh; + return (cnt); +} + +int +host_dns(const char *s, int synced, struct ntp_addr **hn) +{ + int error, save_opts; + + log_debug("trying to resolve %s", s); + error = host_dns1(s, hn, 0); + if (!synced && error <= 0) { + log_debug("no luck, trying to resolve %s without checking", s); + save_opts = _res.options; + _res.options |= RES_USE_CD; + error = host_dns1(s, hn, 1); + _res.options = save_opts; + } + log_debug("resolve %s done: %d", s, error); + return error; +} + +struct ntp_peer * +new_peer(void) +{ + struct ntp_peer *p; + + if ((p = calloc(1, sizeof(struct ntp_peer))) == NULL) + fatal("new_peer calloc"); + p->id = ++maxid; + + return (p); +} + +struct ntp_conf_sensor * +new_sensor(char *device) +{ + struct ntp_conf_sensor *s; + + if ((s = calloc(1, sizeof(struct ntp_conf_sensor))) == NULL) + fatal("new_sensor calloc"); + if ((s->device = strdup(device)) == NULL) + fatal("new_sensor strdup"); + + return (s); +} + +struct constraint * +new_constraint(void) +{ + struct constraint *p; + + if ((p = calloc(1, sizeof(struct constraint))) == NULL) + fatal("new_constraint calloc"); + p->id = ++constraint_maxid; + p->fd = -1; + + return (p); +} + diff --git a/constraint.c b/constraint.c new file mode 100644 index 0000000..8f91606 --- /dev/null +++ b/constraint.c @@ -0,0 +1,1167 @@ +/* $OpenBSD: constraint.c,v 1.56 2023/12/20 15:36:36 otto Exp $ */ + +/* + * Copyright (c) 2015 Reyk Floeter + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +#define IMF_FIXDATE "%a, %d %h %Y %T GMT" +#define X509_DATE "%Y-%m-%d %T UTC" + +int constraint_addr_init(struct constraint *); +void constraint_addr_head_clear(struct constraint *); +struct constraint * + constraint_byid(u_int32_t); +struct constraint * + constraint_byfd(int); +struct constraint * + constraint_bypid(pid_t); +int constraint_close(u_int32_t); +void constraint_update(void); +int constraint_cmp(const void *, const void *); + +void priv_constraint_close(int, int); +void priv_constraint_readquery(struct constraint *, struct ntp_addr_msg *, + uint8_t **); + +struct httpsdate * + httpsdate_init(const char *, const char *, const char *, + const char *, const u_int8_t *, size_t, int); +void httpsdate_free(void *); +int httpsdate_request(struct httpsdate *, struct timeval *, int); +void *httpsdate_query(const char *, const char *, const char *, + const char *, const u_int8_t *, size_t, + struct timeval *, struct timeval *, int); + +char *tls_readline(struct tls *, size_t *, size_t *, struct timeval *); + +u_int constraint_cnt; +extern u_int peer_cnt; +extern struct imsgbuf *ibuf; /* priv */ +extern struct imsgbuf *ibuf_main; /* chld */ + +struct httpsdate { + char *tls_addr; + char *tls_port; + char *tls_hostname; + char *tls_path; + char *tls_request; + struct tls_config *tls_config; + struct tls *tls_ctx; + struct tm tls_tm; +}; + +int +constraint_init(struct constraint *cstr) +{ + cstr->state = STATE_NONE; + cstr->fd = -1; + cstr->last = getmonotime(); + cstr->constraint = 0; + cstr->senderrors = 0; + + return (constraint_addr_init(cstr)); +} + +int +constraint_addr_init(struct constraint *cstr) +{ + struct sockaddr_in *sa_in; + struct sockaddr_in6 *sa_in6; + struct ntp_addr *h; + + if (cstr->state == STATE_DNS_INPROGRESS) + return (0); + + if (cstr->addr_head.a == NULL) { + priv_dns(IMSG_CONSTRAINT_DNS, cstr->addr_head.name, cstr->id); + cstr->state = STATE_DNS_INPROGRESS; + return (0); + } + + h = cstr->addr; + switch (h->ss.ss_family) { + case AF_INET: + sa_in = (struct sockaddr_in *)&h->ss; + if (ntohs(sa_in->sin_port) == 0) + sa_in->sin_port = htons(443); + cstr->state = STATE_DNS_DONE; + break; + case AF_INET6: + sa_in6 = (struct sockaddr_in6 *)&h->ss; + if (ntohs(sa_in6->sin6_port) == 0) + sa_in6->sin6_port = htons(443); + cstr->state = STATE_DNS_DONE; + break; + default: + /* XXX king bula sez it? */ + fatalx("wrong AF in constraint_addr_init"); + /* NOTREACHED */ + } + + return (1); +} + +void +constraint_addr_head_clear(struct constraint *cstr) +{ + host_dns_free(cstr->addr_head.a); + cstr->addr_head.a = NULL; + cstr->addr = NULL; +} + +int +constraint_query(struct constraint *cstr, int synced) +{ + time_t now; + struct ntp_addr_msg am; + struct iovec iov[3]; + int iov_cnt = 0; + + now = getmonotime(); + + switch (cstr->state) { + case STATE_DNS_DONE: + /* Proceed and query the time */ + break; + case STATE_DNS_TEMPFAIL: + if (now > cstr->last + (cstr->dnstries >= TRIES_AUTO_DNSFAIL ? + CONSTRAINT_RETRY_INTERVAL : INTERVAL_AUIO_DNSFAIL)) { + cstr->dnstries++; + /* Retry resolving the address */ + constraint_init(cstr); + return 0; + } + return (-1); + case STATE_QUERY_SENT: + if (cstr->last + CONSTRAINT_SCAN_TIMEOUT > now) { + /* The caller should expect a reply */ + return (0); + } + + /* Timeout, just kill the process to reset it. */ + imsg_compose(ibuf_main, IMSG_CONSTRAINT_KILL, + cstr->id, 0, -1, NULL, 0); + + cstr->state = STATE_TIMEOUT; + return (-1); + case STATE_INVALID: + if (cstr->last + CONSTRAINT_SCAN_INTERVAL > now) { + /* Nothing to do */ + return (-1); + } + + /* Reset and retry */ + cstr->senderrors = 0; + constraint_close(cstr->id); + break; + case STATE_REPLY_RECEIVED: + default: + /* Nothing to do */ + return (-1); + } + + cstr->last = now; + cstr->state = STATE_QUERY_SENT; + + memset(&am, 0, sizeof(am)); + memcpy(&am.a, cstr->addr, sizeof(am.a)); + am.synced = synced; + + iov[iov_cnt].iov_base = &am; + iov[iov_cnt++].iov_len = sizeof(am); + if (cstr->addr_head.name) { + am.namelen = strlen(cstr->addr_head.name) + 1; + iov[iov_cnt].iov_base = cstr->addr_head.name; + iov[iov_cnt++].iov_len = am.namelen; + } + if (cstr->addr_head.path) { + am.pathlen = strlen(cstr->addr_head.path) + 1; + iov[iov_cnt].iov_base = cstr->addr_head.path; + iov[iov_cnt++].iov_len = am.pathlen; + } + + imsg_composev(ibuf_main, IMSG_CONSTRAINT_QUERY, + cstr->id, 0, -1, iov, iov_cnt); + + return (0); +} + +void +priv_constraint_msg(u_int32_t id, u_int8_t *data, size_t len, int argc, + char **argv) +{ + struct ntp_addr_msg am; + struct ntp_addr *h; + struct constraint *cstr; + int pipes[2]; + int rv; + + if ((cstr = constraint_byid(id)) != NULL) { + log_warnx("IMSG_CONSTRAINT_QUERY repeated for id %d", id); + return; + } + + if (len < sizeof(am)) { + log_warnx("invalid IMSG_CONSTRAINT_QUERY received"); + return; + } + memcpy(&am, data, sizeof(am)); + if (len != (sizeof(am) + am.namelen + am.pathlen)) { + log_warnx("invalid IMSG_CONSTRAINT_QUERY received"); + return; + } + /* Additional imsg data is obtained in the unpriv child */ + + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal("calloc ntp_addr"); + memcpy(h, &am.a, sizeof(*h)); + h->next = NULL; + + cstr = new_constraint(); + cstr->id = id; + cstr->addr = h; + cstr->addr_head.a = h; + constraint_add(cstr); + constraint_cnt++; + + if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, AF_UNSPEC, + pipes) == -1) + fatal("%s pipes", __func__); + + /* Prepare and send constraint data to child. */ + cstr->fd = pipes[0]; + imsg_init(&cstr->ibuf, cstr->fd); + if (imsg_compose(&cstr->ibuf, IMSG_CONSTRAINT_QUERY, id, 0, -1, + data, len) == -1) + fatal("%s: imsg_compose", __func__); + do { + rv = imsg_flush(&cstr->ibuf); + } while (rv == -1 && errno == EAGAIN); + if (rv == -1) + fatal("imsg_flush"); + + /* + * Fork child handlers and make sure to do any sensitive work in the + * the (unprivileged) child. The parent should not do any parsing, + * certificate loading etc. + */ + cstr->pid = start_child(CONSTRAINT_PROC_NAME, pipes[1], argc, argv); +} + +void +priv_constraint_readquery(struct constraint *cstr, struct ntp_addr_msg *am, + uint8_t **data) +{ + struct ntp_addr *h; + uint8_t *dptr; + int n; + struct imsg imsg; + size_t mlen; + + /* Read the message our parent left us. */ + if (((n = imsg_read(&cstr->ibuf)) == -1 && errno != EAGAIN) || n == 0) + fatal("%s: imsg_read", __func__); + if (((n = imsg_get(&cstr->ibuf, &imsg)) == -1) || n == 0) + fatal("%s: imsg_get", __func__); + if (imsg.hdr.type != IMSG_CONSTRAINT_QUERY) + fatalx("%s: invalid message type", __func__); + + /* + * Copy the message contents just like our father: + * priv_constraint_msg(). + */ + mlen = imsg.hdr.len - IMSG_HEADER_SIZE; + if (mlen < sizeof(*am)) + fatalx("%s: mlen < sizeof(*am)", __func__); + + memcpy(am, imsg.data, sizeof(*am)); + if (mlen != (sizeof(*am) + am->namelen + am->pathlen)) + fatalx("%s: mlen < sizeof(*am) + am->namelen + am->pathlen", + __func__); + + if ((h = calloc(1, sizeof(*h))) == NULL || + (*data = calloc(1, mlen)) == NULL) + fatal("%s: calloc", __func__); + + memcpy(h, &am->a, sizeof(*h)); + h->next = NULL; + + cstr->id = imsg.hdr.peerid; + cstr->addr = h; + cstr->addr_head.a = h; + + dptr = imsg.data; + memcpy(*data, dptr + sizeof(*am), mlen - sizeof(*am)); + imsg_free(&imsg); +} + +void +priv_constraint_child(const char *pw_dir, uid_t pw_uid, gid_t pw_gid) +{ + struct constraint cstr; + struct ntp_addr_msg am; + uint8_t *data; + static char addr[NI_MAXHOST]; + struct timeval rectv, xmttv; + struct sigaction sa; + void *ctx; + struct iovec iov[2]; + int i, rv; + + log_procinit("constraint"); + + if (setpriority(PRIO_PROCESS, 0, 0) == -1) + log_warn("could not set priority"); + + /* load CA certs before chroot() */ + if ((conf->ca = tls_load_file(tls_default_ca_cert_file(), + &conf->ca_len, NULL)) == NULL) + fatalx("failed to load constraint ca"); + + if (chroot(pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + if (setgroups(1, &pw_gid) || + setresgid(pw_gid, pw_gid, pw_gid) || + setresuid(pw_uid, pw_uid, pw_uid)) + fatal("can't drop privileges"); + + /* Reset all signal handlers */ + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = SIG_DFL; + for (i = 1; i < _NSIG; i++) + sigaction(i, &sa, NULL); + + if (pledge("stdio inet", NULL) == -1) + fatal("pledge"); + + cstr.fd = CONSTRAINT_PASSFD; + imsg_init(&cstr.ibuf, cstr.fd); + priv_constraint_readquery(&cstr, &am, &data); + + /* + * Get the IP address as name and set the process title accordingly. + * This only converts an address into a string and does not trigger + * any DNS operation, so it is safe to be called without the dns + * pledge. + */ + if (getnameinfo((struct sockaddr *)&cstr.addr->ss, + SA_LEN((struct sockaddr *)&cstr.addr->ss), + addr, sizeof(addr), NULL, 0, + NI_NUMERICHOST) != 0) + fatalx("%s getnameinfo", __func__); + + log_debug("constraint request to %s", addr); + setproctitle("constraint from %s", addr); + (void)closefrom(CONSTRAINT_PASSFD + 1); + + /* + * Set the close-on-exec flag to prevent leaking the communication + * channel to any exec'ed child. In theory this could never happen, + * constraints don't exec children and pledge() prevents it, + * but we keep it as a safety belt; especially for portability. + */ + if (fcntl(CONSTRAINT_PASSFD, F_SETFD, FD_CLOEXEC) == -1) + fatal("%s fcntl F_SETFD", __func__); + + /* Get remaining data from imsg in the unpriv child */ + if (am.namelen) { + if ((cstr.addr_head.name = + get_string(data, am.namelen)) == NULL) + fatalx("invalid IMSG_CONSTRAINT_QUERY name"); + data += am.namelen; + } + if (am.pathlen) { + if ((cstr.addr_head.path = + get_string(data, am.pathlen)) == NULL) + fatalx("invalid IMSG_CONSTRAINT_QUERY path"); + } + + /* Run! */ + if ((ctx = httpsdate_query(addr, + CONSTRAINT_PORT, cstr.addr_head.name, cstr.addr_head.path, + conf->ca, conf->ca_len, &rectv, &xmttv, am.synced)) == NULL) { + /* Abort with failure but without warning */ + exit(1); + } + + iov[0].iov_base = &rectv; + iov[0].iov_len = sizeof(rectv); + iov[1].iov_base = &xmttv; + iov[1].iov_len = sizeof(xmttv); + imsg_composev(&cstr.ibuf, + IMSG_CONSTRAINT_RESULT, 0, 0, -1, iov, 2); + do { + rv = imsg_flush(&cstr.ibuf); + } while (rv == -1 && errno == EAGAIN); + + /* Tear down the TLS connection after sending the result */ + httpsdate_free(ctx); + + exit(0); +} + +void +priv_constraint_check_child(pid_t pid, int status) +{ + struct constraint *cstr; + int fail, sig; + char *signame; + + fail = sig = 0; + if (WIFSIGNALED(status)) { + sig = WTERMSIG(status); + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) + fail = 1; + } else + fatalx("unexpected cause of SIGCHLD"); + + if ((cstr = constraint_bypid(pid)) != NULL) { + if (sig) { + if (sig != SIGTERM) { + signame = strsignal(sig) ? + strsignal(sig) : "unknown"; + log_warnx("constraint %s; " + "terminated with signal %d (%s)", + log_ntp_addr(cstr->addr), sig, signame); + } + fail = 1; + } + + priv_constraint_close(cstr->fd, fail); + } +} + +void +priv_constraint_kill(u_int32_t id) +{ + struct constraint *cstr; + + if ((cstr = constraint_byid(id)) == NULL) { + log_warnx("IMSG_CONSTRAINT_KILL for invalid id %d", id); + return; + } + + kill(cstr->pid, SIGTERM); +} + +struct constraint * +constraint_byid(u_int32_t id) +{ + struct constraint *cstr; + + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (cstr->id == id) + return (cstr); + } + + return (NULL); +} + +struct constraint * +constraint_byfd(int fd) +{ + struct constraint *cstr; + + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (cstr->fd == fd) + return (cstr); + } + + return (NULL); +} + +struct constraint * +constraint_bypid(pid_t pid) +{ + struct constraint *cstr; + + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (cstr->pid == pid) + return (cstr); + } + + return (NULL); +} + +int +constraint_close(u_int32_t id) +{ + struct constraint *cstr; + + if ((cstr = constraint_byid(id)) == NULL) { + log_warn("%s: id %d: not found", __func__, id); + return (0); + } + + cstr->last = getmonotime(); + + if (cstr->addr == NULL || (cstr->addr = cstr->addr->next) == NULL) { + /* Either a pool or all addresses have been tried */ + cstr->addr = cstr->addr_head.a; + if (cstr->senderrors) + cstr->state = STATE_INVALID; + else if (cstr->state >= STATE_QUERY_SENT) + cstr->state = STATE_DNS_DONE; + + return (1); + } + + return (constraint_init(cstr)); +} + +void +priv_constraint_close(int fd, int fail) +{ + struct constraint *cstr; + u_int32_t id; + + if ((cstr = constraint_byfd(fd)) == NULL) { + log_warn("%s: fd %d: not found", __func__, fd); + return; + } + + id = cstr->id; + constraint_remove(cstr); + constraint_cnt--; + + imsg_compose(ibuf, IMSG_CONSTRAINT_CLOSE, id, 0, -1, + &fail, sizeof(fail)); +} + +void +constraint_add(struct constraint *cstr) +{ + TAILQ_INSERT_TAIL(&conf->constraints, cstr, entry); +} + +void +constraint_remove(struct constraint *cstr) +{ + TAILQ_REMOVE(&conf->constraints, cstr, entry); + + msgbuf_clear(&cstr->ibuf.w); + if (cstr->fd != -1) + close(cstr->fd); + free(cstr->addr_head.name); + free(cstr->addr_head.path); + free(cstr->addr); + free(cstr); +} + +void +constraint_purge(void) +{ + struct constraint *cstr, *ncstr; + + TAILQ_FOREACH_SAFE(cstr, &conf->constraints, entry, ncstr) + constraint_remove(cstr); +} + +int +priv_constraint_dispatch(struct pollfd *pfd) +{ + struct imsg imsg; + struct constraint *cstr; + ssize_t n; + struct timeval tv[2]; + + if ((cstr = constraint_byfd(pfd->fd)) == NULL) + return (0); + + if (!(pfd->revents & POLLIN)) + return (0); + + if (((n = imsg_read(&cstr->ibuf)) == -1 && errno != EAGAIN) || n == 0) { + /* there's a race between SIGCHLD delivery and reading imsg + but if we've seen the reply, we're good */ + priv_constraint_close(pfd->fd, cstr->state != + STATE_REPLY_RECEIVED); + return (1); + } + + for (;;) { + if ((n = imsg_get(&cstr->ibuf, &imsg)) == -1) { + priv_constraint_close(pfd->fd, 1); + return (1); + } + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CONSTRAINT_RESULT: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(tv)) + fatalx("invalid IMSG_CONSTRAINT received"); + + /* state is maintained by child, but we want to + remember we've seen the result */ + cstr->state = STATE_REPLY_RECEIVED; + /* forward imsg to ntp child, don't parse it here */ + imsg_compose(ibuf, imsg.hdr.type, + cstr->id, 0, -1, imsg.data, sizeof(tv)); + break; + default: + break; + } + imsg_free(&imsg); + } + + return (0); +} + +void +constraint_msg_result(u_int32_t id, u_int8_t *data, size_t len) +{ + struct constraint *cstr; + struct timeval tv[2]; + double offset; + + if ((cstr = constraint_byid(id)) == NULL) { + log_warnx("IMSG_CONSTRAINT_CLOSE with invalid constraint id"); + return; + } + + if (len != sizeof(tv)) { + log_warnx("invalid IMSG_CONSTRAINT received"); + return; + } + + memcpy(tv, data, len); + + offset = gettime_from_timeval(&tv[0]) - + gettime_from_timeval(&tv[1]); + + log_info("constraint reply from %s: offset %f", + log_ntp_addr(cstr->addr), + offset); + + cstr->state = STATE_REPLY_RECEIVED; + cstr->last = getmonotime(); + cstr->constraint = tv[0].tv_sec; + + constraint_update(); +} + +void +constraint_msg_close(u_int32_t id, u_int8_t *data, size_t len) +{ + struct constraint *cstr, *tmp; + int fail, cnt; + static int total_fails; + + if ((cstr = constraint_byid(id)) == NULL) { + log_warnx("IMSG_CONSTRAINT_CLOSE with invalid constraint id"); + return; + } + + if (len != sizeof(int)) { + log_warnx("invalid IMSG_CONSTRAINT_CLOSE received"); + return; + } + + memcpy(&fail, data, len); + + if (fail) { + log_debug("no constraint reply from %s" + " received in time, next query %ds", + log_ntp_addr(cstr->addr), + CONSTRAINT_SCAN_INTERVAL); + + cnt = 0; + TAILQ_FOREACH(tmp, &conf->constraints, entry) + cnt++; + if (cnt > 0 && ++total_fails >= cnt && + conf->constraint_median == 0) { + log_warnx("constraints configured but none available"); + total_fails = 0; + } + } + + if (fail || cstr->state < STATE_QUERY_SENT) { + cstr->senderrors++; + constraint_close(cstr->id); + } +} + +void +constraint_msg_dns(u_int32_t id, u_int8_t *data, size_t len) +{ + struct constraint *cstr, *ncstr = NULL; + u_int8_t *p; + struct ntp_addr *h; + + if ((cstr = constraint_byid(id)) == NULL) { + log_debug("IMSG_CONSTRAINT_DNS with invalid constraint id"); + return; + } + if (cstr->addr != NULL) { + log_warnx("IMSG_CONSTRAINT_DNS but addr != NULL!"); + return; + } + if (len == 0) { + log_debug("%s FAILED", __func__); + cstr->state = STATE_DNS_TEMPFAIL; + return; + } + + if (len % (sizeof(struct sockaddr_storage) + sizeof(int)) != 0) + fatalx("IMSG_CONSTRAINT_DNS len"); + + if (cstr->addr_head.pool) { + struct constraint *n, *tmp; + TAILQ_FOREACH_SAFE(n, &conf->constraints, entry, tmp) { + if (cstr->id == n->id) + continue; + if (cstr->addr_head.pool == n->addr_head.pool) + constraint_remove(n); + } + } + + p = data; + do { + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal("calloc ntp_addr"); + memcpy(&h->ss, p, sizeof(h->ss)); + p += sizeof(h->ss); + len -= sizeof(h->ss); + memcpy(&h->notauth, p, sizeof(int)); + p += sizeof(int); + len -= sizeof(int); + + if (ncstr == NULL || cstr->addr_head.pool) { + ncstr = new_constraint(); + ncstr->addr = h; + ncstr->addr_head.a = h; + ncstr->addr_head.name = strdup(cstr->addr_head.name); + ncstr->addr_head.path = strdup(cstr->addr_head.path); + if (ncstr->addr_head.name == NULL || + ncstr->addr_head.path == NULL) + fatal("calloc name"); + ncstr->addr_head.pool = cstr->addr_head.pool; + ncstr->state = STATE_DNS_DONE; + constraint_add(ncstr); + constraint_cnt += constraint_init(ncstr); + } else { + h->next = ncstr->addr; + ncstr->addr = h; + ncstr->addr_head.a = h; + } + } while (len); + + constraint_remove(cstr); +} + +int +constraint_cmp(const void *a, const void *b) +{ + time_t at = *(const time_t *)a; + time_t bt = *(const time_t *)b; + return at < bt ? -1 : (at > bt ? 1 : 0); +} + +void +constraint_update(void) +{ + struct constraint *cstr; + int cnt, i; + time_t *values; + time_t now; + + now = getmonotime(); + + cnt = 0; + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (cstr->state != STATE_REPLY_RECEIVED) + continue; + cnt++; + } + if (cnt == 0) + return; + + if ((values = calloc(cnt, sizeof(time_t))) == NULL) + fatal("calloc"); + + i = 0; + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (cstr->state != STATE_REPLY_RECEIVED) + continue; + values[i++] = cstr->constraint + (now - cstr->last); + } + + qsort(values, cnt, sizeof(time_t), constraint_cmp); + + /* calculate median */ + i = cnt / 2; + if (cnt % 2 == 0) + conf->constraint_median = (values[i - 1] + values[i]) / 2; + else + conf->constraint_median = values[i]; + + conf->constraint_last = now; + + free(values); +} + +void +constraint_reset(void) +{ + struct constraint *cstr; + + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (cstr->state == STATE_QUERY_SENT) + continue; + constraint_close(cstr->id); + constraint_addr_head_clear(cstr); + constraint_init(cstr); + } + conf->constraint_errors = 0; +} + +int +constraint_check(double val) +{ + struct timeval tv; + double diff; + time_t now; + + if (conf->constraint_median == 0) + return (0); + + /* Calculate the constraint with the current offset */ + now = getmonotime(); + tv.tv_sec = conf->constraint_median + (now - conf->constraint_last); + tv.tv_usec = 0; + diff = fabs(val - gettime_from_timeval(&tv)); + + if (diff > CONSTRAINT_MARGIN) { + if (conf->constraint_errors++ > + (CONSTRAINT_ERROR_MARGIN * peer_cnt)) { + constraint_reset(); + } + + return (-1); + } + + return (0); +} + +struct httpsdate * +httpsdate_init(const char *addr, const char *port, const char *hostname, + const char *path, const u_int8_t *ca, size_t ca_len, int synced) +{ + struct httpsdate *httpsdate = NULL; + + if ((httpsdate = calloc(1, sizeof(*httpsdate))) == NULL) + goto fail; + + if (hostname == NULL) + hostname = addr; + + if ((httpsdate->tls_addr = strdup(addr)) == NULL || + (httpsdate->tls_port = strdup(port)) == NULL || + (httpsdate->tls_hostname = strdup(hostname)) == NULL || + (httpsdate->tls_path = strdup(path)) == NULL) + goto fail; + + if (asprintf(&httpsdate->tls_request, + "HEAD %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", + httpsdate->tls_path, httpsdate->tls_hostname) == -1) + goto fail; + + if ((httpsdate->tls_config = tls_config_new()) == NULL) + goto fail; + if (tls_config_set_ca_mem(httpsdate->tls_config, ca, ca_len) == -1) + goto fail; + + /* + * Due to the fact that we're trying to determine a constraint for time + * we do our own certificate validity checking, since the automatic + * version is based on our wallclock, which may well be inaccurate... + */ + if (!synced) { + log_debug("constraints: using received time in certificate validation"); + tls_config_insecure_noverifytime(httpsdate->tls_config); + } + + return (httpsdate); + + fail: + httpsdate_free(httpsdate); + return (NULL); +} + +void +httpsdate_free(void *arg) +{ + struct httpsdate *httpsdate = arg; + if (httpsdate == NULL) + return; + if (httpsdate->tls_ctx) + tls_close(httpsdate->tls_ctx); + tls_free(httpsdate->tls_ctx); + tls_config_free(httpsdate->tls_config); + free(httpsdate->tls_addr); + free(httpsdate->tls_port); + free(httpsdate->tls_hostname); + free(httpsdate->tls_path); + free(httpsdate->tls_request); + free(httpsdate); +} + +int +httpsdate_request(struct httpsdate *httpsdate, struct timeval *when, int synced) +{ + char timebuf1[32], timebuf2[32]; + size_t outlen = 0, maxlength = CONSTRAINT_MAXHEADERLENGTH, len; + char *line, *p, *buf; + time_t httptime, notbefore, notafter; + struct tm *tm; + ssize_t ret; + + if ((httpsdate->tls_ctx = tls_client()) == NULL) + goto fail; + + if (tls_configure(httpsdate->tls_ctx, httpsdate->tls_config) == -1) + goto fail; + + /* + * libtls expects an address string, which can also be a DNS name, + * but we pass a pre-resolved IP address string in tls_addr so it + * does not trigger any DNS operation and is safe to be called + * without the dns pledge. + */ + if (tls_connect_servername(httpsdate->tls_ctx, httpsdate->tls_addr, + httpsdate->tls_port, httpsdate->tls_hostname) == -1) { + log_debug("tls connect failed: %s (%s): %s", + httpsdate->tls_addr, httpsdate->tls_hostname, + tls_error(httpsdate->tls_ctx)); + goto fail; + } + + buf = httpsdate->tls_request; + len = strlen(httpsdate->tls_request); + while (len > 0) { + ret = tls_write(httpsdate->tls_ctx, buf, len); + if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) + continue; + if (ret == -1) { + log_warnx("tls write failed: %s (%s): %s", + httpsdate->tls_addr, httpsdate->tls_hostname, + tls_error(httpsdate->tls_ctx)); + goto fail; + } + buf += ret; + len -= ret; + } + + while ((line = tls_readline(httpsdate->tls_ctx, &outlen, + &maxlength, when)) != NULL) { + line[strcspn(line, "\r\n")] = '\0'; + + if ((p = strchr(line, ' ')) == NULL || *p == '\0') + goto next; + *p++ = '\0'; + if (strcasecmp("Date:", line) != 0) + goto next; + + /* + * Expect the date/time format as IMF-fixdate which is + * mandated by HTTP/1.1 in the new RFC 7231 and was + * preferred by RFC 2616. Other formats would be RFC 850 + * or ANSI C's asctime() - the latter doesn't include + * the timezone which is required here. + */ + if (strptime(p, IMF_FIXDATE, + &httpsdate->tls_tm) == NULL) { + log_warnx("unsupported date format"); + free(line); + goto fail; + } + + free(line); + break; + next: + free(line); + } + if (httpsdate->tls_tm.tm_year == 0) + goto fail; + + /* If we are synced, we already checked the certificate validity */ + if (synced) + return 0; + + /* + * Now manually check the validity of the certificate presented in the + * TLS handshake, based on the time specified by the server's HTTP Date: + * header. + */ + notbefore = tls_peer_cert_notbefore(httpsdate->tls_ctx); + notafter = tls_peer_cert_notafter(httpsdate->tls_ctx); + if ((httptime = timegm(&httpsdate->tls_tm)) == -1) + goto fail; + if (httptime <= notbefore) { + if ((tm = gmtime(¬before)) == NULL) + goto fail; + if (strftime(timebuf1, sizeof(timebuf1), X509_DATE, tm) == 0) + goto fail; + if (strftime(timebuf2, sizeof(timebuf2), X509_DATE, + &httpsdate->tls_tm) == 0) + goto fail; + log_warnx("tls certificate not yet valid: %s (%s): " + "not before %s, now %s", httpsdate->tls_addr, + httpsdate->tls_hostname, timebuf1, timebuf2); + goto fail; + } + if (httptime >= notafter) { + if ((tm = gmtime(¬after)) == NULL) + goto fail; + if (strftime(timebuf1, sizeof(timebuf1), X509_DATE, tm) == 0) + goto fail; + if (strftime(timebuf2, sizeof(timebuf2), X509_DATE, + &httpsdate->tls_tm) == 0) + goto fail; + log_warnx("tls certificate expired: %s (%s): " + "not after %s, now %s", httpsdate->tls_addr, + httpsdate->tls_hostname, timebuf1, timebuf2); + goto fail; + } + + return (0); + + fail: + httpsdate_free(httpsdate); + return (-1); +} + +void * +httpsdate_query(const char *addr, const char *port, const char *hostname, + const char *path, const u_int8_t *ca, size_t ca_len, + struct timeval *rectv, struct timeval *xmttv, int synced) +{ + struct httpsdate *httpsdate; + struct timeval when; + time_t t; + + if ((httpsdate = httpsdate_init(addr, port, hostname, path, + ca, ca_len, synced)) == NULL) + return (NULL); + + if (httpsdate_request(httpsdate, &when, synced) == -1) + return (NULL); + + /* Return parsed date as local time */ + t = timegm(&httpsdate->tls_tm); + + /* Report parsed Date: as "received time" */ + rectv->tv_sec = t; + rectv->tv_usec = 0; + + /* And add delay as "transmit time" */ + xmttv->tv_sec = when.tv_sec; + xmttv->tv_usec = when.tv_usec; + + return (httpsdate); +} + +/* Based on SSL_readline in ftp/fetch.c */ +char * +tls_readline(struct tls *tls, size_t *lenp, size_t *maxlength, + struct timeval *when) +{ + size_t i, len; + char *buf, *q, c; + ssize_t ret; + + len = 128; + if ((buf = malloc(len)) == NULL) + fatal("Can't allocate memory for transfer buffer"); + for (i = 0; ; i++) { + if (i >= len - 1) { + if ((q = reallocarray(buf, len, 2)) == NULL) + fatal("Can't expand transfer buffer"); + buf = q; + len *= 2; + } + again: + ret = tls_read(tls, &c, 1); + if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) + goto again; + if (ret == -1) { + /* SSL read error, ignore */ + free(buf); + return (NULL); + } + + if (maxlength != NULL && (*maxlength)-- == 0) { + log_warnx("maximum length exceeded"); + free(buf); + return (NULL); + } + + buf[i] = c; + if (c == '\n') + break; + } + *lenp = i; + if (gettimeofday(when, NULL) == -1) + fatal("gettimeofday"); + return (buf); +} + +char * +get_string(u_int8_t *ptr, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + if (!(isprint(ptr[i]) || isspace(ptr[i]))) + break; + + return strndup(ptr, i); +} diff --git a/control.c b/control.c new file mode 100644 index 0000000..8249411 --- /dev/null +++ b/control.c @@ -0,0 +1,452 @@ +/* $OpenBSD: control.c,v 1.21 2024/04/23 13:34:51 jsg Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * Copyright (c) 2012 Mike Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +#define CONTROL_BACKLOG 5 + +#define square(x) ((x) * (x)) + +int +control_check(char *path) +{ + struct sockaddr_un sun; + int fd; + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + log_debug("control_check: socket check"); + return (-1); + } + + if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == 0) { + log_debug("control_check: socket in use"); + close(fd); + return (-1); + } + + close(fd); + + return (0); +} + +int +control_init(char *path) +{ + struct sockaddr_un sa; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) { + log_warn("control_init: socket"); + return (-1); + } + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + if (strlcpy(sa.sun_path, path, sizeof(sa.sun_path)) >= + sizeof(sa.sun_path)) + errx(1, "ctl socket name too long"); + + if (unlink(path) == -1) + if (errno != ENOENT) { + log_warn("control_init: unlink %s", path); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + log_warn("control_init: bind: %s", path); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("control_init: chmod"); + close(fd); + (void)unlink(path); + return (-1); + } + + session_socket_nonblockmode(fd); + + return (fd); +} + +int +control_listen(int fd) +{ + if (fd != -1 && listen(fd, CONTROL_BACKLOG) == -1) { + log_warn("control_listen: listen"); + return (-1); + } + + return (0); +} + +void +control_shutdown(int fd) +{ + close(fd); +} + +int +control_accept(int listenfd) +{ + int connfd; + socklen_t len; + struct sockaddr_un sa; + struct ctl_conn *ctl_conn; + + len = sizeof(sa); + if ((connfd = accept(listenfd, + (struct sockaddr *)&sa, &len)) == -1) { + if (errno != EWOULDBLOCK && errno != EINTR) + log_warn("control_accept: accept"); + return (0); + } + + session_socket_nonblockmode(connfd); + + if ((ctl_conn = calloc(1, sizeof(struct ctl_conn))) == NULL) { + log_warn("control_accept"); + close(connfd); + return (0); + } + + imsg_init(&ctl_conn->ibuf, connfd); + + TAILQ_INSERT_TAIL(&ctl_conns, ctl_conn, entry); + + return (1); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->ibuf.fd == fd) + break; + } + + return (c); +} + +int +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("control_close: fd %d: not found", fd); + return (0); + } + + msgbuf_clear(&c->ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + close(c->ibuf.fd); + free(c); + + return (1); +} + +int +control_dispatch_msg(struct pollfd *pfd, u_int *ctl_cnt) +{ + struct imsg imsg; + struct ctl_conn *c; + struct ntp_peer *p; + struct ntp_sensor *s; + struct ctl_show_status c_status; + struct ctl_show_peer c_peer; + struct ctl_show_sensor c_sensor; + int cnt; + ssize_t n; + + if ((c = control_connbyfd(pfd->fd)) == NULL) { + log_warn("control_dispatch_msg: fd %d: not found", pfd->fd); + return (0); + } + + if (pfd->revents & POLLOUT) + if (msgbuf_write(&c->ibuf.w) <= 0 && errno != EAGAIN) { + *ctl_cnt -= control_close(pfd->fd); + return (1); + } + + if (!(pfd->revents & POLLIN)) + return (0); + + if (((n = imsg_read(&c->ibuf)) == -1 && errno != EAGAIN) || n == 0) { + *ctl_cnt -= control_close(pfd->fd); + return (1); + } + + for (;;) { + if ((n = imsg_get(&c->ibuf, &imsg)) == -1) { + *ctl_cnt -= control_close(pfd->fd); + return (1); + } + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_SHOW_STATUS: + build_show_status(&c_status); + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_STATUS, 0, 0, -1, + &c_status, sizeof (c_status)); + break; + case IMSG_CTL_SHOW_PEERS: + cnt = 0; + TAILQ_FOREACH(p, &conf->ntp_peers, entry) { + build_show_peer(&c_peer, p); + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_PEERS, + 0, 0, -1, &c_peer, sizeof(c_peer)); + cnt++; + } + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_PEERS_END, + 0, 0, -1, &cnt, sizeof(cnt)); + break; + case IMSG_CTL_SHOW_SENSORS: + cnt = 0; + TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { + build_show_sensor(&c_sensor, s); + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_SENSORS, + 0, 0, -1, &c_sensor, sizeof(c_sensor)); + cnt++; + } + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_SENSORS_END, + 0, 0, -1, &cnt, sizeof(cnt)); + break; + case IMSG_CTL_SHOW_ALL: + build_show_status(&c_status); + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_STATUS, 0, 0, -1, + &c_status, sizeof (c_status)); + + cnt = 0; + TAILQ_FOREACH(p, &conf->ntp_peers, entry) { + build_show_peer(&c_peer, p); + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_PEERS, + 0, 0, -1, &c_peer, sizeof(c_peer)); + cnt++; + } + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_PEERS_END, + 0, 0, -1, &cnt, sizeof(cnt)); + + cnt = 0; + TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { + build_show_sensor(&c_sensor, s); + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_SENSORS, + 0, 0, -1, &c_sensor, sizeof(c_sensor)); + cnt++; + } + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_SENSORS_END, + 0, 0, -1, &cnt, sizeof(cnt)); + + imsg_compose(&c->ibuf, IMSG_CTL_SHOW_ALL_END, + 0, 0, -1, NULL, 0); + break; + default: + break; + } + imsg_free(&imsg); + } + return (0); +} + +void +session_socket_nonblockmode(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL)) == -1) + fatal("fcntl F_GETFL"); + + flags |= O_NONBLOCK; + + if ((flags = fcntl(fd, F_SETFL, flags)) == -1) + fatal("fcntl F_SETFL"); +} + +void +build_show_status(struct ctl_show_status *cs) +{ + struct ntp_peer *p; + struct ntp_sensor *s; + + cs->peercnt = cs->valid_peers = 0; + cs->sensorcnt = cs->valid_sensors = 0; + + TAILQ_FOREACH(p, &conf->ntp_peers, entry) { + cs->peercnt++; + if (p->trustlevel >= TRUSTLEVEL_BADPEER) + cs->valid_peers++; + } + TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { + cs->sensorcnt++; + if (s->update.good) + cs->valid_sensors++; + } + + cs->synced = conf->status.synced; + cs->stratum = conf->status.stratum; + cs->clock_offset = getoffset() * 1000.0; + cs->constraints = !TAILQ_EMPTY(&conf->constraints); + cs->constraint_median = conf->constraint_median; + cs->constraint_last = conf->constraint_last; + cs->constraint_errors = conf->constraint_errors; +} + +void +build_show_peer(struct ctl_show_peer *cp, struct ntp_peer *p) +{ + const char *a = "not resolved"; + const char *pool = "", *addr_head_name = ""; + const char *auth = ""; + int shift, best = -1, validdelaycnt = 0, jittercnt = 0; + time_t now; + + now = getmonotime(); + + if (p->addr) { + a = log_ntp_addr(p->addr); + if (p->addr->notauth) + auth = " (non-dnssec lookup)"; + } + if (p->addr_head.pool) + pool = "from pool "; + + if (0 != strcmp(a, p->addr_head.name) || p->addr_head.pool) + addr_head_name = p->addr_head.name; + + snprintf(cp->peer_desc, sizeof(cp->peer_desc), + "%s %s%s%s", a, pool, addr_head_name, auth); + + cp->offset = cp->delay = 0.0; + for (shift = 0; shift < OFFSET_ARRAY_SIZE; shift++) { + if (p->reply[shift].delay > 0.0) { + cp->offset += p->reply[shift].offset; + cp->delay += p->reply[shift].delay; + + if (best == -1 || + p->reply[shift].delay < p->reply[best].delay) + best = shift; + + validdelaycnt++; + } + } + + if (validdelaycnt > 1) { + cp->offset /= validdelaycnt; + cp->delay /= validdelaycnt; + } + + cp->jitter = 0.0; + if (best != -1) { + for (shift = 0; shift < OFFSET_ARRAY_SIZE; shift++) { + if (p->reply[shift].delay > 0.0 && shift != best) { + cp->jitter += square(p->reply[shift].delay - + p->reply[best].delay); + jittercnt++; + } + } + if (jittercnt > 1) + cp->jitter /= jittercnt; + cp->jitter = sqrt(cp->jitter); + } + + if (p->shift == 0) + shift = OFFSET_ARRAY_SIZE - 1; + else + shift = p->shift - 1; + + if (conf->status.synced == 1 && + p->reply[shift].status.send_refid == conf->status.refid) + cp->syncedto = 1; + else + cp->syncedto = 0; + + /* milliseconds to reduce number of leading zeroes */ + cp->offset *= 1000.0; + cp->delay *= 1000.0; + cp->jitter *= 1000.0; + + cp->weight = p->weight; + cp->trustlevel = p->trustlevel; + cp->stratum = p->reply[shift].status.stratum; + cp->next = p->next - now < 0 ? 0 : p->next - now; + cp->poll = p->poll; +} + +void +build_show_sensor(struct ctl_show_sensor *cs, struct ntp_sensor *s) +{ + time_t now; + u_int8_t shift; + u_int32_t refid; + + now = getmonotime(); + + memcpy(&refid, SENSOR_DEFAULT_REFID, sizeof(refid)); + refid = refid == s->refid ? 0 : s->refid; + + snprintf(cs->sensor_desc, sizeof(cs->sensor_desc), + "%s %.4s", s->device, (char *)&refid); + + if (s->shift == 0) + shift = SENSOR_OFFSETS - 1; + else + shift = s->shift - 1; + + if (conf->status.synced == 1 && + s->offsets[shift].status.send_refid == conf->status.refid) + cs->syncedto = 1; + else + cs->syncedto = 0; + + cs->weight = s->weight; + cs->good = s->update.good; + cs->stratum = s->offsets[shift].status.stratum; + cs->next = s->next - now < 0 ? 0 : s->next - now; + cs->poll = SENSOR_QUERY_INTERVAL; + cs->offset = s->offsets[shift].offset * 1000.0; + cs->correction = (double)s->correction / 1000.0; +} diff --git a/log.c b/log.c new file mode 100644 index 0000000..56fd38c --- /dev/null +++ b/log.c @@ -0,0 +1,202 @@ +/* $OpenBSD: log.c,v 1.19 2019/07/03 05:04:19 otto Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "log.h" + +static int dest; +static int verbose; +const char *log_procname; + +void +log_init(int n_dest, int n_verbose, int facility) +{ + extern char *__progname; + + dest = n_dest; + verbose = n_verbose; + log_procinit(__progname); + + if (dest & LOG_TO_SYSLOG) + openlog(__progname, LOG_PID | LOG_NDELAY, facility); + + tzset(); +} + +void +log_procinit(const char *procname) +{ + if (procname != NULL) + log_procname = procname; +} + +void +log_setverbose(int v) +{ + verbose = v; +} + +int +log_getverbose(void) +{ + return (verbose); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + int saved_errno = errno; + va_list ap2; + + va_copy(ap2, ap); + if (dest & LOG_TO_STDERR) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } + if (dest & LOG_TO_SYSLOG) + vsyslog(pri, fmt, ap2); + va_end(ap2); + + errno = saved_errno; +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + int saved_errno = errno; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_ERR, "%s", strerror(saved_errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, + strerror(saved_errno)) == -1) { + /* we tried it... */ + vlog(LOG_ERR, emsg, ap); + logit(LOG_ERR, "%s", strerror(saved_errno)); + } else { + vlog(LOG_ERR, nfmt, ap); + free(nfmt); + } + va_end(ap); + } + + errno = saved_errno; +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_ERR, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose > 1) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +static void +vfatalc(int code, const char *emsg, va_list ap) +{ + static char s[BUFSIZ]; + const char *sep; + + if (emsg != NULL) { + (void)vsnprintf(s, sizeof(s), emsg, ap); + sep = ": "; + } else { + s[0] = '\0'; + sep = ""; + } + if (code) + logit(LOG_CRIT, "%s: %s%s%s", + log_procname, s, sep, strerror(code)); + else + logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); +} + +void +fatal(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(errno, emsg, ap); + va_end(ap); + exit(1); +} + +void +fatalx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(0, emsg, ap); + va_end(ap); + exit(1); +} diff --git a/log.h b/log.h new file mode 100644 index 0000000..5d7a692 --- /dev/null +++ b/log.h @@ -0,0 +1,48 @@ +/* $OpenBSD: log.h,v 1.6 2021/12/13 18:28:40 deraadt Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LOG_H +#define LOG_H + +#include + +#define LOG_TO_STDERR (1<<0) +#define LOG_TO_SYSLOG (1<<1) + +void log_init(int, int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +#endif /* LOG_H */ diff --git a/ntp.c b/ntp.c new file mode 100644 index 0000000..178d51e --- /dev/null +++ b/ntp.c @@ -0,0 +1,907 @@ +/* $OpenBSD: ntp.c,v 1.174 2024/02/21 03:31:28 deraadt Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * Copyright (c) 2004 Alexander Guy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +#define PFD_PIPE_MAIN 0 +#define PFD_PIPE_DNS 1 +#define PFD_SOCK_CTL 2 +#define PFD_MAX 3 + +volatile sig_atomic_t ntp_quit = 0; +struct imsgbuf *ibuf_main; +static struct imsgbuf *ibuf_dns; +struct ntpd_conf *conf; +struct ctl_conns ctl_conns; +u_int peer_cnt; +u_int sensors_cnt; +extern u_int constraint_cnt; + +void ntp_sighdlr(int); +int ntp_dispatch_imsg(void); +int ntp_dispatch_imsg_dns(void); +void peer_add(struct ntp_peer *); +void peer_remove(struct ntp_peer *); +int inpool(struct sockaddr_storage *, + struct sockaddr_storage[MAX_SERVERS_DNS], size_t); + +void +ntp_sighdlr(int sig) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + ntp_quit = 1; + break; + } +} + +void +ntp_main(struct ntpd_conf *nconf, struct passwd *pw, int argc, char **argv) +{ + int a, b, nfds, i, j, idx_peers, timeout; + int nullfd, pipe_dns[2], idx_clients; + int ctls; + int fd_ctl; + int clear_cdns; + u_int pfd_elms = 0, idx2peer_elms = 0; + u_int listener_cnt, new_cnt, sent_cnt, trial_cnt; + u_int ctl_cnt; + struct pollfd *pfd = NULL; + struct servent *se; + struct listen_addr *la; + struct ntp_peer *p; + struct ntp_peer **idx2peer = NULL; + struct ntp_sensor *s, *next_s; + struct constraint *cstr; + struct timespec tp; + struct stat stb; + struct ctl_conn *cc; + time_t nextaction, last_sensor_scan = 0, now; + time_t last_action = 0, interval, last_cdns_reset = 0; + void *newp; + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNSPEC, + pipe_dns) == -1) + fatal("socketpair"); + + start_child(NTPDNS_PROC_NAME, pipe_dns[1], argc, argv); + + log_init(nconf->debug ? LOG_TO_STDERR : LOG_TO_SYSLOG, nconf->verbose, + LOG_DAEMON); + if (!nconf->debug && setsid() == -1) + fatal("setsid"); + log_procinit("ntp"); + + if ((se = getservbyname("ntp", "udp")) == NULL) + fatal("getservbyname"); + + /* Start control socket. */ + if ((fd_ctl = control_init(CTLSOCKET)) == -1) + fatalx("control socket init failed"); + if (control_listen(fd_ctl) == -1) + fatalx("control socket listen failed"); + if ((nullfd = open("/dev/null", O_RDWR)) == -1) + fatal(NULL); + + if (stat(pw->pw_dir, &stb) == -1) { + fatal("privsep dir %s could not be opened", pw->pw_dir); + } + if (stb.st_uid != 0 || (stb.st_mode & (S_IWGRP|S_IWOTH)) != 0) { + fatalx("bad privsep dir %s permissions: %o", + pw->pw_dir, stb.st_mode); + } + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + if (!nconf->debug) { + dup2(nullfd, STDIN_FILENO); + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + } + close(nullfd); + + setproctitle("ntp engine"); + + conf = nconf; + setup_listeners(se, conf, &listener_cnt); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + endservent(); + + /* The ntp process will want to open NTP client sockets -> "inet" */ + if (pledge("stdio inet", NULL) == -1) + err(1, "pledge"); + + signal(SIGTERM, ntp_sighdlr); + signal(SIGINT, ntp_sighdlr); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGCHLD, SIG_DFL); + + if ((ibuf_main = malloc(sizeof(struct imsgbuf))) == NULL) + fatal(NULL); + imsg_init(ibuf_main, PARENT_SOCK_FILENO); + if ((ibuf_dns = malloc(sizeof(struct imsgbuf))) == NULL) + fatal(NULL); + imsg_init(ibuf_dns, pipe_dns[0]); + + constraint_cnt = 0; + conf->constraint_median = 0; + conf->constraint_last = getmonotime(); + TAILQ_FOREACH(cstr, &conf->constraints, entry) + constraint_cnt += constraint_init(cstr); + + TAILQ_FOREACH(p, &conf->ntp_peers, entry) + client_peer_init(p); + + memset(&conf->status, 0, sizeof(conf->status)); + + conf->freq.num = 0; + conf->freq.samples = 0; + conf->freq.x = 0.0; + conf->freq.xx = 0.0; + conf->freq.xy = 0.0; + conf->freq.y = 0.0; + conf->freq.overall_offset = 0.0; + + conf->status.synced = 0; + clock_getres(CLOCK_REALTIME, &tp); + b = 1000000000 / tp.tv_nsec; /* convert to Hz */ + for (a = 0; b > 1; a--, b >>= 1) + ; + conf->status.precision = a; + conf->scale = 1; + + TAILQ_INIT(&ctl_conns); + sensor_init(); + + log_info("ntp engine ready"); + + ctl_cnt = 0; + peer_cnt = 0; + TAILQ_FOREACH(p, &conf->ntp_peers, entry) + peer_cnt++; + + while (ntp_quit == 0) { + if (peer_cnt > idx2peer_elms) { + if ((newp = reallocarray(idx2peer, peer_cnt, + sizeof(*idx2peer))) == NULL) { + /* panic for now */ + log_warn("could not resize idx2peer from %u -> " + "%u entries", idx2peer_elms, peer_cnt); + fatalx("exiting"); + } + idx2peer = newp; + idx2peer_elms = peer_cnt; + } + + new_cnt = PFD_MAX + + peer_cnt + listener_cnt + ctl_cnt; + if (new_cnt > pfd_elms) { + if ((newp = reallocarray(pfd, new_cnt, + sizeof(*pfd))) == NULL) { + /* panic for now */ + log_warn("could not resize pfd from %u -> " + "%u entries", pfd_elms, new_cnt); + fatalx("exiting"); + } + pfd = newp; + pfd_elms = new_cnt; + } + + memset(pfd, 0, sizeof(*pfd) * pfd_elms); + memset(idx2peer, 0, sizeof(*idx2peer) * idx2peer_elms); + nextaction = getmonotime() + 900; + pfd[PFD_PIPE_MAIN].fd = ibuf_main->fd; + pfd[PFD_PIPE_MAIN].events = POLLIN; + pfd[PFD_PIPE_DNS].fd = ibuf_dns->fd; + pfd[PFD_PIPE_DNS].events = POLLIN; + pfd[PFD_SOCK_CTL].fd = fd_ctl; + pfd[PFD_SOCK_CTL].events = POLLIN; + + i = PFD_MAX; + TAILQ_FOREACH(la, &conf->listen_addrs, entry) { + pfd[i].fd = la->fd; + pfd[i].events = POLLIN; + i++; + } + + idx_peers = i; + sent_cnt = trial_cnt = 0; + TAILQ_FOREACH(p, &conf->ntp_peers, entry) { + if (!p->trusted && constraint_cnt && + conf->constraint_median == 0) + continue; + + if (p->next > 0 && p->next <= getmonotime()) { + if (p->state > STATE_DNS_INPROGRESS) + trial_cnt++; + if (client_query(p) == 0) + sent_cnt++; + } + if (p->deadline > 0 && p->deadline <= getmonotime()) { + timeout = 300; + log_debug("no reply from %s received in time, " + "next query %ds", log_ntp_addr( p->addr), + timeout); + if (p->trustlevel >= TRUSTLEVEL_BADPEER && + (p->trustlevel /= 2) < TRUSTLEVEL_BADPEER) + log_info("peer %s now invalid", + log_ntp_addr(p->addr)); + if (client_nextaddr(p) == 1) { + peer_addr_head_clear(p); + client_nextaddr(p); + } + set_next(p, timeout); + } + if (p->senderrors > MAX_SEND_ERRORS) { + log_debug("failed to send query to %s, " + "next query %ds", log_ntp_addr(p->addr), + INTERVAL_QUERY_PATHETIC); + p->senderrors = 0; + if (client_nextaddr(p) == 1) { + peer_addr_head_clear(p); + client_nextaddr(p); + } + set_next(p, INTERVAL_QUERY_PATHETIC); + } + if (p->next > 0 && p->next < nextaction) + nextaction = p->next; + if (p->deadline > 0 && p->deadline < nextaction) + nextaction = p->deadline; + + if (p->state == STATE_QUERY_SENT && + p->query.fd != -1) { + pfd[i].fd = p->query.fd; + pfd[i].events = POLLIN; + idx2peer[i - idx_peers] = p; + i++; + } + } + idx_clients = i; + + if (!TAILQ_EMPTY(&conf->ntp_conf_sensors) && + (conf->trusted_sensors || constraint_cnt == 0 || + conf->constraint_median != 0)) { + if (last_sensor_scan == 0 || + last_sensor_scan + SENSOR_SCAN_INTERVAL <= getmonotime()) { + sensors_cnt = sensor_scan(); + last_sensor_scan = getmonotime(); + } + if (sensors_cnt == 0 && + nextaction > last_sensor_scan + SENSOR_SCAN_INTERVAL) + nextaction = last_sensor_scan + SENSOR_SCAN_INTERVAL; + sensors_cnt = 0; + TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { + if (conf->settime && s->offsets[0].offset) + priv_settime(s->offsets[0].offset, NULL); + sensors_cnt++; + if (s->next > 0 && s->next < nextaction) + nextaction = s->next; + } + } + + if (conf->settime && + ((trial_cnt > 0 && sent_cnt == 0) || + (peer_cnt == 0 && sensors_cnt == 0))) + priv_settime(0, "no valid peers configured"); + + clear_cdns = 1; + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + constraint_query(cstr, conf->status.synced); + if (cstr->state <= STATE_QUERY_SENT) + clear_cdns = 0; + } + + if (ibuf_main->w.queued > 0) + pfd[PFD_PIPE_MAIN].events |= POLLOUT; + if (ibuf_dns->w.queued > 0) + pfd[PFD_PIPE_DNS].events |= POLLOUT; + + TAILQ_FOREACH(cc, &ctl_conns, entry) { + pfd[i].fd = cc->ibuf.fd; + pfd[i].events = POLLIN; + if (cc->ibuf.w.queued > 0) + pfd[i].events |= POLLOUT; + i++; + } + ctls = i; + + now = getmonotime(); + if (conf->constraint_median == 0 && clear_cdns && + now - last_cdns_reset > CONSTRAINT_SCAN_INTERVAL) { + log_debug("Reset constraint info"); + constraint_reset(); + last_cdns_reset = now; + nextaction = now + CONSTRAINT_RETRY_INTERVAL; + } + timeout = nextaction - now; + if (timeout < 0) + timeout = 0; + + if ((nfds = poll(pfd, i, timeout ? timeout * 1000 : 1)) == -1) + if (errno != EINTR) { + log_warn("poll error"); + ntp_quit = 1; + } + + if (nfds > 0 && (pfd[PFD_PIPE_MAIN].revents & POLLOUT)) + if (msgbuf_write(&ibuf_main->w) <= 0 && + errno != EAGAIN) { + log_warn("pipe write error (to parent)"); + ntp_quit = 1; + } + + if (nfds > 0 && pfd[PFD_PIPE_MAIN].revents & (POLLIN|POLLERR)) { + nfds--; + if (ntp_dispatch_imsg() == -1) { + log_debug("pipe read error (from main)"); + ntp_quit = 1; + } + } + + if (nfds > 0 && (pfd[PFD_PIPE_DNS].revents & POLLOUT)) + if (msgbuf_write(&ibuf_dns->w) <= 0 && + errno != EAGAIN) { + log_warn("pipe write error (to dns engine)"); + ntp_quit = 1; + } + + if (nfds > 0 && pfd[PFD_PIPE_DNS].revents & (POLLIN|POLLERR)) { + nfds--; + if (ntp_dispatch_imsg_dns() == -1) { + log_warn("pipe read error (from dns engine)"); + ntp_quit = 1; + } + } + + if (nfds > 0 && pfd[PFD_SOCK_CTL].revents & (POLLIN|POLLERR)) { + nfds--; + ctl_cnt += control_accept(fd_ctl); + } + + for (j = PFD_MAX; nfds > 0 && j < idx_peers; j++) + if (pfd[j].revents & (POLLIN|POLLERR)) { + nfds--; + if (server_dispatch(pfd[j].fd, conf) == -1) { + log_warn("pipe write error (conf)"); + ntp_quit = 1; + } + } + + for (; nfds > 0 && j < idx_clients; j++) { + if (pfd[j].revents & (POLLIN|POLLERR)) { + struct ntp_peer *pp = idx2peer[j - idx_peers]; + + nfds--; + switch (client_dispatch(pp, conf->settime, + conf->automatic)) { + case -1: + log_debug("no reply from %s " + "received", log_ntp_addr(pp->addr)); + if (pp->trustlevel >= + TRUSTLEVEL_BADPEER && + (pp->trustlevel /= 2) < + TRUSTLEVEL_BADPEER) + log_info("peer %s now invalid", + log_ntp_addr(pp->addr)); + break; + case 0: /* invalid replies are ignored */ + break; + case 1: + last_action = now; + break; + } + } + } + + for (; nfds > 0 && j < ctls; j++) { + nfds -= control_dispatch_msg(&pfd[j], &ctl_cnt); + } + + for (s = TAILQ_FIRST(&conf->ntp_sensors); s != NULL; + s = next_s) { + next_s = TAILQ_NEXT(s, entry); + if (s->next <= now) { + last_action = now; + sensor_query(s); + } + } + + /* + * Compute maximum of scale_interval(INTERVAL_QUERY_NORMAL), + * if we did not process a time message for three times that + * interval, stop advertising we're synced. + */ + interval = INTERVAL_QUERY_NORMAL * conf->scale; + interval += SCALE_INTERVAL(interval) - 1; + if (conf->status.synced && last_action + 3 * interval < now) { + log_info("clock is now unsynced due to lack of replies"); + conf->status.synced = 0; + conf->scale = 1; + priv_dns(IMSG_UNSYNCED, NULL, 0); + } + } + + msgbuf_write(&ibuf_main->w); + msgbuf_clear(&ibuf_main->w); + free(ibuf_main); + msgbuf_write(&ibuf_dns->w); + msgbuf_clear(&ibuf_dns->w); + free(ibuf_dns); + + log_info("ntp engine exiting"); + exit(0); +} + +int +ntp_dispatch_imsg(void) +{ + struct imsg imsg; + int n; + + if (((n = imsg_read(ibuf_main)) == -1 && errno != EAGAIN) || n == 0) + return (-1); + + for (;;) { + if ((n = imsg_get(ibuf_main, &imsg)) == -1) + return (-1); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_ADJTIME: + memcpy(&n, imsg.data, sizeof(n)); + if (n == 1 && !conf->status.synced) { + log_info("clock is now synced"); + conf->status.synced = 1; + priv_dns(IMSG_SYNCED, NULL, 0); + constraint_reset(); + } else if (n == 0 && conf->status.synced) { + log_info("clock is now unsynced"); + conf->status.synced = 0; + priv_dns(IMSG_UNSYNCED, NULL, 0); + } + break; + case IMSG_CONSTRAINT_RESULT: + constraint_msg_result(imsg.hdr.peerid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + break; + case IMSG_CONSTRAINT_CLOSE: + constraint_msg_close(imsg.hdr.peerid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + break; + default: + break; + } + imsg_free(&imsg); + } + return (0); +} + +int +inpool(struct sockaddr_storage *a, + struct sockaddr_storage old[MAX_SERVERS_DNS], size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) { + if (a->ss_family != old[i].ss_family) + continue; + if (a->ss_family == AF_INET) { + if (((struct sockaddr_in *)a)->sin_addr.s_addr == + ((struct sockaddr_in *)&old[i])->sin_addr.s_addr) + return 1; + } else if (memcmp(&((struct sockaddr_in6 *)a)->sin6_addr, + &((struct sockaddr_in6 *)&old[i])->sin6_addr, + sizeof(struct in6_addr)) == 0) { + return 1; + } + } + return 0; +} + +int +ntp_dispatch_imsg_dns(void) +{ + struct imsg imsg; + struct sockaddr_storage existing[MAX_SERVERS_DNS]; + struct ntp_peer *peer, *npeer, *tmp; + u_int16_t dlen; + u_char *p; + struct ntp_addr *h; + size_t addrcount, peercount; + int n; + + if (((n = imsg_read(ibuf_dns)) == -1 && errno != EAGAIN) || n == 0) + return (-1); + + for (;;) { + if ((n = imsg_get(ibuf_dns, &imsg)) == -1) + return (-1); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_HOST_DNS: + TAILQ_FOREACH(peer, &conf->ntp_peers, entry) + if (peer->id == imsg.hdr.peerid) + break; + if (peer == NULL) { + log_warnx("IMSG_HOST_DNS with invalid peerID"); + break; + } + if (peer->addr != NULL) { + log_warnx("IMSG_HOST_DNS but addr != NULL!"); + break; + } + + if (peer->addr_head.pool) { + n = 0; + peercount = 0; + + TAILQ_FOREACH_SAFE(npeer, &conf->ntp_peers, + entry, tmp) { + if (npeer->addr_head.pool != + peer->addr_head.pool) + continue; + peercount++; + if (npeer->id == peer->id) + continue; + if (npeer->addr != NULL) + existing[n++] = npeer->addr->ss; + } + } + + dlen = imsg.hdr.len - IMSG_HEADER_SIZE; + if (dlen == 0) { /* no data -> temp error */ + log_debug("DNS lookup tempfail"); + peer->state = STATE_DNS_TEMPFAIL; + if (conf->tmpfail++ == TRIES_AUTO_DNSFAIL) + priv_settime(0, "of dns failures"); + break; + } + + p = (u_char *)imsg.data; + addrcount = dlen / (sizeof(struct sockaddr_storage) + + sizeof(int)); + + while (dlen >= sizeof(struct sockaddr_storage) + + sizeof(int)) { + if ((h = calloc(1, sizeof(struct ntp_addr))) == + NULL) + fatal(NULL); + memcpy(&h->ss, p, sizeof(h->ss)); + p += sizeof(h->ss); + dlen -= sizeof(h->ss); + memcpy(&h->notauth, p, sizeof(int)); + p += sizeof(int); + dlen -= sizeof(int); + if (peer->addr_head.pool) { + if (peercount > addrcount) { + free(h); + continue; + } + if (inpool(&h->ss, existing, + n)) { + free(h); + continue; + } + log_debug("Adding address %s to %s", + log_ntp_addr(h), peer->addr_head.name); + npeer = new_peer(); + npeer->weight = peer->weight; + npeer->query_addr4 = peer->query_addr4; + npeer->query_addr6 = peer->query_addr6; + h->next = NULL; + npeer->addr = h; + npeer->addr_head.a = h; + npeer->addr_head.name = + peer->addr_head.name; + npeer->addr_head.pool = + peer->addr_head.pool; + client_peer_init(npeer); + npeer->state = STATE_DNS_DONE; + peer_add(npeer); + peercount++; + } else { + h->next = peer->addr; + peer->addr = h; + peer->addr_head.a = peer->addr; + peer->state = STATE_DNS_DONE; + } + } + if (dlen != 0) + fatalx("IMSG_HOST_DNS: dlen != 0"); + if (peer->addr_head.pool) + peer_remove(peer); + else + client_addr_init(peer); + break; + case IMSG_CONSTRAINT_DNS: + constraint_msg_dns(imsg.hdr.peerid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + break; + case IMSG_PROBE_ROOT: + dlen = imsg.hdr.len - IMSG_HEADER_SIZE; + if (dlen != sizeof(int)) + fatalx("IMSG_PROBE_ROOT"); + memcpy(&n, imsg.data, sizeof(int)); + if (n < 0) + priv_settime(0, "dns probe failed"); + break; + default: + break; + } + imsg_free(&imsg); + } + return (0); +} + +void +peer_add(struct ntp_peer *p) +{ + TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry); + peer_cnt++; +} + +void +peer_remove(struct ntp_peer *p) +{ + TAILQ_REMOVE(&conf->ntp_peers, p, entry); + free(p); + peer_cnt--; +} + +void +peer_addr_head_clear(struct ntp_peer *p) +{ + host_dns_free(p->addr_head.a); + p->addr_head.a = NULL; + p->addr = NULL; +} + +static void +priv_adjfreq(double offset) +{ + double curtime, freq; + + if (!conf->status.synced){ + conf->freq.samples = 0; + return; + } + + conf->freq.samples++; + + if (conf->freq.samples <= 0) + return; + + conf->freq.overall_offset += offset; + offset = conf->freq.overall_offset; + + curtime = gettime_corrected(); + conf->freq.xy += offset * curtime; + conf->freq.x += curtime; + conf->freq.y += offset; + conf->freq.xx += curtime * curtime; + + if (conf->freq.samples % FREQUENCY_SAMPLES != 0) + return; + + freq = + (conf->freq.xy - conf->freq.x * conf->freq.y / conf->freq.samples) + / + (conf->freq.xx - conf->freq.x * conf->freq.x / conf->freq.samples); + + if (freq > MAX_FREQUENCY_ADJUST) + freq = MAX_FREQUENCY_ADJUST; + else if (freq < -MAX_FREQUENCY_ADJUST) + freq = -MAX_FREQUENCY_ADJUST; + + imsg_compose(ibuf_main, IMSG_ADJFREQ, 0, 0, -1, &freq, sizeof(freq)); + conf->filters |= FILTER_ADJFREQ; + conf->freq.xy = 0.0; + conf->freq.x = 0.0; + conf->freq.y = 0.0; + conf->freq.xx = 0.0; + conf->freq.samples = 0; + conf->freq.overall_offset = 0.0; + conf->freq.num++; +} + +int +priv_adjtime(void) +{ + struct ntp_peer *p; + struct ntp_sensor *s; + int offset_cnt = 0, i = 0, j; + struct ntp_offset **offsets; + double offset_median; + + TAILQ_FOREACH(p, &conf->ntp_peers, entry) { + if (p->trustlevel < TRUSTLEVEL_BADPEER) + continue; + if (!p->update.good) + return (1); + offset_cnt += p->weight; + } + + TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { + if (!s->update.good) + continue; + offset_cnt += s->weight; + } + + if (offset_cnt == 0) + return (1); + + if ((offsets = calloc(offset_cnt, sizeof(struct ntp_offset *))) == NULL) + fatal("calloc priv_adjtime"); + + TAILQ_FOREACH(p, &conf->ntp_peers, entry) { + if (p->trustlevel < TRUSTLEVEL_BADPEER) + continue; + for (j = 0; j < p->weight; j++) + offsets[i++] = &p->update; + } + + TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { + if (!s->update.good) + continue; + for (j = 0; j < s->weight; j++) + offsets[i++] = &s->update; + } + + qsort(offsets, offset_cnt, sizeof(struct ntp_offset *), offset_compare); + + i = offset_cnt / 2; + if (offset_cnt % 2 == 0) + if (offsets[i - 1]->delay < offsets[i]->delay) + i -= 1; + offset_median = offsets[i]->offset; + conf->status.rootdelay = offsets[i]->delay; + conf->status.stratum = offsets[i]->status.stratum; + conf->status.leap = offsets[i]->status.leap; + + imsg_compose(ibuf_main, IMSG_ADJTIME, 0, 0, -1, + &offset_median, sizeof(offset_median)); + + priv_adjfreq(offset_median); + + conf->status.reftime = gettime(); + conf->status.stratum++; /* one more than selected peer */ + if (conf->status.stratum > NTP_MAXSTRATUM) + conf->status.stratum = NTP_MAXSTRATUM; + update_scale(offset_median); + + conf->status.refid = offsets[i]->status.send_refid; + + free(offsets); + + TAILQ_FOREACH(p, &conf->ntp_peers, entry) { + for (i = 0; i < OFFSET_ARRAY_SIZE; i++) + p->reply[i].offset -= offset_median; + p->update.good = 0; + } + TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { + for (i = 0; i < SENSOR_OFFSETS; i++) + s->offsets[i].offset -= offset_median; + s->update.offset -= offset_median; + } + + return (0); +} + +int +offset_compare(const void *aa, const void *bb) +{ + const struct ntp_offset * const *a; + const struct ntp_offset * const *b; + + a = aa; + b = bb; + + if ((*a)->offset < (*b)->offset) + return (-1); + else if ((*a)->offset > (*b)->offset) + return (1); + else + return (0); +} + +void +priv_settime(double offset, char *msg) +{ + if (offset == 0) + log_info("cancel settime because %s", msg); + imsg_compose(ibuf_main, IMSG_SETTIME, 0, 0, -1, + &offset, sizeof(offset)); + conf->settime = 0; +} + +void +priv_dns(int cmd, char *name, u_int32_t peerid) +{ + u_int16_t dlen = 0; + + if (name != NULL) + dlen = strlen(name) + 1; + imsg_compose(ibuf_dns, cmd, peerid, 0, -1, name, dlen); +} + +void +update_scale(double offset) +{ + offset += getoffset(); + if (offset < 0) + offset = -offset; + + if (offset > QSCALE_OFF_MAX || !conf->status.synced || + conf->freq.num < 3) + conf->scale = 1; + else if (offset < QSCALE_OFF_MIN) + conf->scale = QSCALE_OFF_MAX / QSCALE_OFF_MIN; + else + conf->scale = QSCALE_OFF_MAX / offset; +} + +time_t +scale_interval(time_t requested) +{ + time_t interval, r; + + interval = requested * conf->scale; + r = arc4random_uniform(SCALE_INTERVAL(interval)); + return (interval + r); +} + +time_t +error_interval(void) +{ + time_t interval, r; + + interval = INTERVAL_QUERY_PATHETIC * QSCALE_OFF_MAX / QSCALE_OFF_MIN; + r = arc4random_uniform(interval / 10); + return (interval + r); +} diff --git a/ntp.h b/ntp.h new file mode 100644 index 0000000..20d67f3 --- /dev/null +++ b/ntp.h @@ -0,0 +1,163 @@ +/* $OpenBSD: ntp.h,v 1.15 2023/11/15 15:52:09 otto Exp $ */ + +/* + * Copyright (c) 2004 Henning Brauer + * Copyright (c) 2004 Alexander Guy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _NTP_H_ +#define _NTP_H_ + +/* Style borrowed from NTP ref/tcpdump and updated for SNTPv4 (RFC2030). */ + +/* + * RFC Section 3 + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Integer Part | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Fraction Part | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Integer Part | Fraction Part | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +struct l_fixedpt { + u_int32_t int_partl; + u_int32_t fractionl; +}; +#define L_DENOMINATOR (UINT32_MAX + 1ULL) + +struct s_fixedpt { + u_int16_t int_parts; + u_int16_t fractions; +}; +#define S_DENOMINATOR (UINT16_MAX + 1) + +/* RFC Section 4 + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |LI | VN | Mode| Stratum | Poll | Precision | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Synchronizing Distance | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Synchronizing Dispersion | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reference Clock Identifier | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Reference Timestamp (64 bits) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Originate Timestamp (64 bits) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Receive Timestamp (64 bits) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Transmit Timestamp (64 bits) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Key Identifier (optional) (32) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | | + * | Message Digest (optional) (128) | + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ + +#define NTP_DIGESTSIZE 16 +#define NTP_MSGSIZE_NOAUTH 48 +#define NTP_MSGSIZE (NTP_MSGSIZE_NOAUTH + 4 + NTP_DIGESTSIZE) + +struct ntp_msg { + u_int8_t status; /* status of local clock and leap info */ + u_int8_t stratum; /* Stratum level */ + u_int8_t ppoll; /* poll value */ + int8_t precision; + struct s_fixedpt rootdelay; + struct s_fixedpt dispersion; + u_int32_t refid; + struct l_fixedpt reftime; + struct l_fixedpt orgtime; + struct l_fixedpt rectime; + struct l_fixedpt xmttime; +} __packed; + +struct ntp_query { + int fd; + struct ntp_msg msg; + double xmttime; +}; + +/* + * Leap Second Codes (high order two bits) + */ +#define LI_NOWARNING (0 << 6) /* no warning */ +#define LI_PLUSSEC (1 << 6) /* add a second (61 seconds) */ +#define LI_MINUSSEC (2 << 6) /* minus a second (59 seconds) */ +#define LI_ALARM (3 << 6) /* alarm condition */ + +/* + * Status Masks + */ +#define MODEMASK (7 << 0) +#define VERSIONMASK (7 << 3) +#define LIMASK (3 << 6) + +/* + * Mode values + */ +#define MODE_RES0 0 /* reserved */ +#define MODE_SYM_ACT 1 /* symmetric active */ +#define MODE_SYM_PAS 2 /* symmetric passive */ +#define MODE_CLIENT 3 /* client */ +#define MODE_SERVER 4 /* server */ +#define MODE_BROADCAST 5 /* broadcast */ +#define MODE_RES1 6 /* reserved for NTP control message */ +#define MODE_RES2 7 /* reserved for private use */ + +#define JAN_1970 2208988800UL /* 1970 - 1900 in seconds */ + +/* + * The era we're in if we have no reason to assume otherwise. + * If lfp_to_d() sees an offset <= INT32_MAX the era is is assumed to be + * NTP_ERA + 1. + * Once the actual year is well into era 1, (after 2036) define NTP_ERA to 1 + * and adapt (remove) the test in lfp_to_d(). + * Once more than half of era 1 has elapsed (after 2104), re-inroduce the test + * to move to era 2 if offset <= INT32_MAX, repeat for each half era. + */ +#define NTP_ERA 0 + +#define SECS_IN_ERA (UINT32_MAX + 1ULL) + +#define NTP_VERSION 4 +#define NTP_MAXSTRATUM 15 + +#endif /* _NTP_H_ */ diff --git a/ntp_dns.c b/ntp_dns.c new file mode 100644 index 0000000..8928b8c --- /dev/null +++ b/ntp_dns.c @@ -0,0 +1,251 @@ +/* $OpenBSD: ntp_dns.c,v 1.28 2023/04/19 12:58:16 jsg Exp $ */ + +/* + * Copyright (c) 2003-2008 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +volatile sig_atomic_t quit_dns = 0; +static struct imsgbuf *ibuf_dns; +extern int non_numeric; + +void sighdlr_dns(int); +int dns_dispatch_imsg(struct ntpd_conf *); +int probe_root_ns(void); +void probe_root(void); + +void +sighdlr_dns(int sig) +{ + switch (sig) { + case SIGTERM: + case SIGINT: + quit_dns = 1; + break; + } +} + +void +ntp_dns(struct ntpd_conf *nconf, struct passwd *pw) +{ + struct pollfd pfd[1]; + int nfds, nullfd; + + res_init(); + if (setpriority(PRIO_PROCESS, 0, 0) == -1) + log_warn("could not set priority"); + + log_init(nconf->debug ? LOG_TO_STDERR : LOG_TO_SYSLOG, nconf->verbose, + LOG_DAEMON); + if (!nconf->debug && setsid() == -1) + fatal("setsid"); + log_procinit("dns"); + + if ((nullfd = open("/dev/null", O_RDWR)) == -1) + fatal(NULL); + + if (!nconf->debug) { + dup2(nullfd, STDIN_FILENO); + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + } + close(nullfd); + + setproctitle("dns engine"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + signal(SIGTERM, sighdlr_dns); + signal(SIGINT, sighdlr_dns); + signal(SIGHUP, SIG_IGN); + + if ((ibuf_dns = malloc(sizeof(struct imsgbuf))) == NULL) + fatal(NULL); + imsg_init(ibuf_dns, PARENT_SOCK_FILENO); + + if (pledge("stdio dns", NULL) == -1) + err(1, "pledge"); + + if (non_numeric) + probe_root(); + else + log_debug("all addresses numeric, no dns probe"); + + while (quit_dns == 0) { + pfd[0].fd = ibuf_dns->fd; + pfd[0].events = POLLIN; + if (ibuf_dns->w.queued) + pfd[0].events |= POLLOUT; + + if ((nfds = poll(pfd, 1, INFTIM)) == -1) + if (errno != EINTR) { + log_warn("poll error"); + quit_dns = 1; + } + + if (nfds > 0 && (pfd[0].revents & POLLOUT)) + if (msgbuf_write(&ibuf_dns->w) <= 0 && + errno != EAGAIN) { + log_warn("pipe write error (to ntp engine)"); + quit_dns = 1; + } + + if (nfds > 0 && pfd[0].revents & POLLIN) { + nfds--; + if (dns_dispatch_imsg(nconf) == -1) + quit_dns = 1; + } + } + + msgbuf_clear(&ibuf_dns->w); + free(ibuf_dns); + exit(0); +} + +int +dns_dispatch_imsg(struct ntpd_conf *nconf) +{ + struct imsg imsg; + int n, cnt; + char *name; + struct ntp_addr *h, *hn; + struct ibuf *buf; + const char *str; + size_t len; + + if (((n = imsg_read(ibuf_dns)) == -1 && errno != EAGAIN) || n == 0) + return (-1); + + for (;;) { + if ((n = imsg_get(ibuf_dns, &imsg)) == -1) + return (-1); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_HOST_DNS: + case IMSG_CONSTRAINT_DNS: + if (imsg.hdr.type == IMSG_HOST_DNS) + str = "IMSG_HOST_DNS"; + else + str = "IMSG_CONSTRAINT_DNS"; + name = imsg.data; + if (imsg.hdr.len < 1 + IMSG_HEADER_SIZE) + fatalx("invalid %s received", str); + len = imsg.hdr.len - 1 - IMSG_HEADER_SIZE; + if (name[len] != '\0' || + strlen(name) != len) + fatalx("invalid %s received", str); + if ((cnt = host_dns(name, nconf->status.synced, + &hn)) == -1) + break; + buf = imsg_create(ibuf_dns, imsg.hdr.type, + imsg.hdr.peerid, 0, + cnt * (sizeof(struct sockaddr_storage) + sizeof(int))); + if (cnt > 0) { + if (buf) { + for (h = hn; h != NULL; h = h->next) { + if (imsg_add(buf, &h->ss, + sizeof(h->ss)) == -1) { + buf = NULL; + break; + } + if (imsg_add(buf, &h->notauth, + sizeof(int)) == -1) { + buf = NULL; + break; + } + } + } + host_dns_free(hn); + hn = NULL; + } + if (buf) + imsg_close(ibuf_dns, buf); + break; + case IMSG_SYNCED: + nconf->status.synced = 1; + break; + case IMSG_UNSYNCED: + nconf->status.synced = 0; + break; + default: + break; + } + imsg_free(&imsg); + } + return (0); +} + +int +probe_root_ns(void) +{ + int ret; + int old_retrans, old_retry, old_options; + unsigned char buf[4096]; + + old_retrans = _res.retrans; + old_retry = _res.retry; + old_options = _res.options; + _res.retrans = 1; + _res.retry = 1; + _res.options |= RES_USE_CD; + + ret = res_query(".", C_IN, T_NS, buf, sizeof(buf)); + + _res.retrans = old_retrans; + _res.retry = old_retry; + _res.options = old_options; + + return ret; +} + +void +probe_root(void) +{ + int n; + + n = probe_root_ns(); + if (n < 0) { + /* give programs like unwind a second chance */ + sleep(1); + n = probe_root_ns(); + } + if (imsg_compose(ibuf_dns, IMSG_PROBE_ROOT, 0, 0, -1, &n, + sizeof(int)) == -1) + fatalx("probe_root"); +} diff --git a/ntp_msg.c b/ntp_msg.c new file mode 100644 index 0000000..6a9f6bc --- /dev/null +++ b/ntp_msg.c @@ -0,0 +1,71 @@ +/* $OpenBSD: ntp_msg.c,v 1.22 2016/09/03 11:52:06 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * Copyright (c) 2004 Alexander Guy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "ntpd.h" + +int +ntp_getmsg(struct sockaddr *sa, char *p, ssize_t len, struct ntp_msg *msg) +{ + if (len != NTP_MSGSIZE_NOAUTH && len != NTP_MSGSIZE) { + log_debug("malformed packet received from %s", + log_sockaddr(sa)); + return (-1); + } + + memcpy(msg, p, sizeof(*msg)); + + return (0); +} + +int +ntp_sendmsg(int fd, struct sockaddr *sa, struct ntp_msg *msg) +{ + socklen_t sa_len; + ssize_t n; + + if (sa != NULL) + sa_len = SA_LEN(sa); + else + sa_len = 0; + + n = sendto(fd, msg, sizeof(*msg), 0, sa, sa_len); + if (n == -1) { + if (errno == ENOBUFS || errno == EHOSTUNREACH || + errno == ENETDOWN || errno == EHOSTDOWN) { + /* logging is futile */ + return (-1); + } + log_warn("sendto"); + return (-1); + } + + if (n != sizeof(*msg)) { + log_warnx("ntp_sendmsg: only %zd of %zu bytes sent", n, + sizeof(*msg)); + return (-1); + } + + return (0); +} diff --git a/ntpctl.8 b/ntpctl.8 new file mode 100644 index 0000000..ff1e487 --- /dev/null +++ b/ntpctl.8 @@ -0,0 +1,79 @@ +.\" $OpenBSD: ntpctl.8,v 1.9 2023/03/02 17:09:53 jmc Exp $ +.\" +.\" Copyright (c) 2012 Mike Miller +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN +.\" AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 2 2023 $ +.Dt NTPCTL 8 +.Os +.Sh NAME +.Nm ntpctl +.Nd control the NTP daemon +.Sh SYNOPSIS +.Nm ntpctl +.Fl s Cm all | peers | Sensors | status +.Sh DESCRIPTION +The +.Nm +program displays information about the running +.Xr ntpd 8 +daemon. +.Pp +The options are as follows: +.Bl -tag -width "-s modifierX" +.It Fl s Cm all | peers | Sensors | status +Used to display information about the running daemon. +Keywords may be abbreviated. +.Pp +.Cm all +shows all data available. +.Pp +.Cm peers +shows the following information about each peer: weight, trustlevel, +stratum, number of seconds until the next poll, polling interval +in seconds, and offset, network delay and network jitter in milliseconds. +When the system clock is synced to a peer, an asterisk +is displayed to the left of the weight column for that peer. +.Pp +.Cm Sensors +shows the following information about each sensor: weight, sensor "good" +status, stratum, and offset and the configured correction in +milliseconds. +When the system clock is synced to a sensor, an asterisk +is displayed to the left of the weight column for that sensor. +.Pp +.Cm status +shows the status of peers and sensors, and whether the system clock is synced. +When the system clock is synced, the stratum is displayed. +When the system clock is not synced, the offset of the system clock, +as reported by the +.Xr adjtime 2 +system call, is displayed. +When the median constraint is set, the offset to the local time is displayed. +.El +.Sh FILES +.Bl -tag -width "/var/run/ntpd.sockXXX" -compact +.It Pa /var/run/ntpd.sock +Socket file for communication with +.Xr ntpd 8 . +.El +.Sh SEE ALSO +.Xr adjtime 2 , +.Xr ntpd.conf 5 , +.Xr ntpd 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 5.5 . diff --git a/ntpd.8 b/ntpd.8 new file mode 100644 index 0000000..6e7f515 --- /dev/null +++ b/ntpd.8 @@ -0,0 +1,159 @@ +.\" $OpenBSD: ntpd.8,v 1.49 2023/03/02 17:09:53 jmc Exp $ +.\" +.\" Copyright (c) 2003, 2004, 2006 Henning Brauer +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN +.\" AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 2 2023 $ +.Dt NTPD 8 +.Os +.Sh NAME +.Nm ntpd +.Nd Network Time Protocol (NTP) daemon +.Sh SYNOPSIS +.Nm ntpd +.Bk -words +.Op Fl dnv +.Op Fl f Ar file +.Ek +.Sh DESCRIPTION +The +.Nm +daemon synchronizes the local clock to one or more remote NTP servers +or local timedelta sensors. +.Nm +can also act as an NTP server itself, +redistributing the local time. +It implements the Simple Network Time Protocol version 4, +as described in RFC 5905, +and the Network Time Protocol version 3, +as described in RFC 1305. +Time can also be fetched from TLS HTTPS servers to reduce the +impact of unauthenticated NTP +man-in-the-middle attacks. +.Pp +The options are as follows: +.Bl -tag -width "-f fileXXX" +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl f Ar file +Use +.Ar file +as the configuration file, +instead of the default +.Pa /etc/ntpd.conf . +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.It Fl v +This option allows +.Nm +to send DEBUG priority messages to syslog. +.El +.Pp +.Nm +uses the +.Xr adjtime 2 +system call to correct the local system time without causing time jumps. +Adjustments of 32ms and greater are logged using +.Xr syslog 3 . +The threshold value is chosen to avoid having local clock drift +thrash the log files. +Should +.Nm +be started with the +.Fl d +or +.Fl v +option, all calls to +.Xr adjtime 2 +will be logged. +.Pp +At boot, +.Nm +will stay for a maximum of 15 seconds in the foreground and make efforts to +verify and correct the time if constraints are configured and +satisfied or if trusted servers or sensors return results, +and if the clock is not being moved backwards. +.Pp +After the local clock is synchronized, +.Nm +adjusts the clock frequency using the +.Xr adjfreq 2 +system call to compensate for systematic drift. +.Pp +.Nm +is started at boot time by default via +.Va ntpd_flags +in +.Pa /etc/rc.conf . +See +.Xr rc 8 +and +.Xr rc.conf 8 +for more information on the boot process +and enabling daemons. +.Pp +When +.Nm +starts up, it reads settings from its configuration file, +typically +.Xr ntpd.conf 5 , +and its initial clock drift from +.Pa /var/db/ntpd.drift . +Clock drift is periodically written to the drift file thereafter. +.Sh FILES +.Bl -tag -width "/var/db/ntpd.driftXXX" -compact +.It Pa /etc/ntpd.conf +Default configuration file. +.It Pa /var/db/ntpd.drift +Drift file. +.It Pa /var/run/ntpd.sock +Socket file for communication with +.Xr ntpctl 8 . +.El +.Sh SEE ALSO +.Xr date 1 , +.Xr adjfreq 2 , +.Xr adjtime 2 , +.Xr ntpd.conf 5 , +.Xr ntpctl 8 , +.Xr rc 8 , +.Xr rc.conf 8 , +.Xr rdate 8 +.Sh STANDARDS +.Rs +.%A David L. Mills +.%D March 1992 +.%R RFC 1305 +.%T Network Time Protocol (Version 3): Specification, Implementation and Analysis +.Re +.Pp +.Rs +.%A David L. Mills +.%A Jim Martin +.%A Jack Burbank +.%A William Kasch +.%D June 2010 +.%R RFC 5905 +.%T Network Time Protocol Version 4: Protocol and Algorithms Specification +.Re +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 3.6 . diff --git a/ntpd.c b/ntpd.c new file mode 100644 index 0000000..7f81bb9 --- /dev/null +++ b/ntpd.c @@ -0,0 +1,924 @@ +/* $OpenBSD: ntpd.c,v 1.133 2024/05/21 05:00:48 jsg Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * Copyright (c) 2012 Mike Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +void sighdlr(int); +__dead void usage(void); +int auto_preconditions(const struct ntpd_conf *); +int main(int, char *[]); +void check_child(void); +int dispatch_imsg(struct ntpd_conf *, int, char **); +void reset_adjtime(void); +int ntpd_adjtime(double); +void ntpd_adjfreq(double, int); +void ntpd_settime(double); +void readfreq(void); +int writefreq(double); +void ctl_main(int, char*[]); +const char *ctl_lookup_option(char *, const char **); +void show_status_msg(struct imsg *); +void show_peer_msg(struct imsg *, int); +void show_sensor_msg(struct imsg *, int); + +volatile sig_atomic_t quit = 0; +volatile sig_atomic_t reconfig = 0; +volatile sig_atomic_t sigchld = 0; +struct imsgbuf *ibuf; +int timeout = INFTIM; + +extern u_int constraint_cnt; + +const char *showopt; + +static const char *ctl_showopt_list[] = { + "peers", "Sensors", "status", "all", NULL +}; + +void +sighdlr(int sig) +{ + switch (sig) { + case SIGTERM: + case SIGINT: + quit = 1; + break; + case SIGCHLD: + sigchld = 1; + break; + case SIGHUP: + reconfig = 1; + break; + } +} + +__dead void +usage(void) +{ + extern char *__progname; + + if (strcmp(__progname, "ntpctl") == 0) + fprintf(stderr, + "usage: ntpctl -s all | peers | Sensors | status\n"); + else + fprintf(stderr, "usage: %s [-dnv] [-f file]\n", + __progname); + exit(1); +} + +int +auto_preconditions(const struct ntpd_conf *cnf) +{ + int mib[2] = { CTL_KERN, KERN_SECURELVL }; + int constraints, securelevel; + size_t sz = sizeof(int); + + if (sysctl(mib, 2, &securelevel, &sz, NULL, 0) == -1) + err(1, "sysctl"); + constraints = !TAILQ_EMPTY(&cnf->constraints); + return !cnf->settime && (constraints || cnf->trusted_peers || + conf->trusted_sensors) && securelevel == 0; +} + +#define POLL_MAX 8 +#define PFD_PIPE 0 +#define PFD_MAX 1 + +int +main(int argc, char *argv[]) +{ + struct ntpd_conf lconf; + struct pollfd *pfd = NULL; + pid_t pid; + const char *conffile; + int ch, nfds, i, j; + int pipe_chld[2]; + extern char *__progname; + u_int pfd_elms = 0, new_cnt; + struct constraint *cstr; + struct passwd *pw; + void *newp; + int argc0 = argc, logdest; + char **argv0 = argv; + char *pname = NULL; + time_t settime_deadline; + int sopt = 0; + + if (strcmp(__progname, "ntpctl") == 0) { + ctl_main(argc, argv); + /* NOTREACHED */ + } + + conffile = CONFFILE; + + memset(&lconf, 0, sizeof(lconf)); + + while ((ch = getopt(argc, argv, "df:nP:sSv")) != -1) { + switch (ch) { + case 'd': + lconf.debug = 1; + break; + case 'f': + conffile = optarg; + break; + case 'n': + lconf.debug = 1; + lconf.noaction = 1; + break; + case 'P': + pname = optarg; + break; + case 's': + case 'S': + sopt = ch; + break; + case 'v': + lconf.verbose++; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + /* log to stderr until daemonized */ + logdest = LOG_TO_STDERR; + if (!lconf.debug) + logdest |= LOG_TO_SYSLOG; + + log_init(logdest, lconf.verbose, LOG_DAEMON); + + if (sopt) { + log_warnx("-%c option no longer works and will be removed soon.", + sopt); + log_warnx("Please reconfigure to use constraints or trusted servers."); + } + + argc -= optind; + argv += optind; + if (argc > 0) + usage(); + + if (parse_config(conffile, &lconf)) + exit(1); + + if (lconf.noaction) { + fprintf(stderr, "configuration OK\n"); + exit(0); + } + + if (geteuid()) + errx(1, "need root privileges"); + + if ((pw = getpwnam(NTPD_USER)) == NULL) + errx(1, "unknown user %s", NTPD_USER); + + lconf.automatic = auto_preconditions(&lconf); + if (lconf.automatic) + lconf.settime = 1; + + if (pname != NULL) { + /* Remove our proc arguments, so child doesn't need to. */ + if (sanitize_argv(&argc0, &argv0) == -1) + fatalx("sanitize_argv"); + + if (strcmp(NTP_PROC_NAME, pname) == 0) + ntp_main(&lconf, pw, argc0, argv0); + else if (strcmp(NTPDNS_PROC_NAME, pname) == 0) + ntp_dns(&lconf, pw); + else if (strcmp(CONSTRAINT_PROC_NAME, pname) == 0) + priv_constraint_child(pw->pw_dir, pw->pw_uid, + pw->pw_gid); + else + fatalx("%s: invalid process name '%s'", __func__, + pname); + + fatalx("%s: process '%s' failed", __func__, pname); + } else { + if ((control_check(CTLSOCKET)) == -1) + fatalx("ntpd already running"); + } + + if (setpriority(PRIO_PROCESS, 0, -20) == -1) + warn("can't set priority"); + reset_adjtime(); + + logdest = lconf.debug ? LOG_TO_STDERR : LOG_TO_SYSLOG; + if (!lconf.settime) { + log_init(logdest, lconf.verbose, LOG_DAEMON); + if (!lconf.debug) + if (daemon(1, 0)) + fatal("daemon"); + } else { + settime_deadline = getmonotime(); + timeout = 100; + } + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNSPEC, + pipe_chld) == -1) + fatal("socketpair"); + + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + signal(SIGCHLD, sighdlr); + + /* fork child process */ + start_child(NTP_PROC_NAME, pipe_chld[1], argc0, argv0); + + log_procinit("[priv]"); + readfreq(); + + signal(SIGTERM, sighdlr); + signal(SIGINT, sighdlr); + signal(SIGHUP, sighdlr); + + constraint_purge(); + + if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL) + fatal(NULL); + imsg_init(ibuf, pipe_chld[0]); + + constraint_cnt = 0; + + /* + * Constraint processes are forked with certificates in memory, + * then privdrop into chroot before speaking to the outside world. + */ + if (unveil("/usr/sbin/ntpd", "x") == -1) + err(1, "unveil /usr/sbin/ntpd"); + if (pledge("stdio settime proc exec", NULL) == -1) + err(1, "pledge"); + + while (quit == 0) { + new_cnt = PFD_MAX + constraint_cnt; + if (new_cnt > pfd_elms) { + if ((newp = reallocarray(pfd, new_cnt, + sizeof(*pfd))) == NULL) { + /* panic for now */ + log_warn("could not resize pfd from %u -> " + "%u entries", pfd_elms, new_cnt); + fatalx("exiting"); + } + pfd = newp; + pfd_elms = new_cnt; + } + + memset(pfd, 0, sizeof(*pfd) * pfd_elms); + pfd[PFD_PIPE].fd = ibuf->fd; + pfd[PFD_PIPE].events = POLLIN; + if (ibuf->w.queued) + pfd[PFD_PIPE].events |= POLLOUT; + + i = PFD_MAX; + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + pfd[i].fd = cstr->fd; + pfd[i].events = POLLIN; + i++; + } + + if ((nfds = poll(pfd, i, timeout)) == -1) + if (errno != EINTR) { + log_warn("poll error"); + quit = 1; + } + + if (nfds == 0 && lconf.settime && + getmonotime() > settime_deadline + SETTIME_TIMEOUT) { + lconf.settime = 0; + timeout = INFTIM; + log_init(logdest, lconf.verbose, LOG_DAEMON); + log_warnx("no reply received in time, skipping initial " + "time setting"); + if (!lconf.debug) + if (daemon(1, 0)) + fatal("daemon"); + } + + if (nfds > 0 && (pfd[PFD_PIPE].revents & POLLOUT)) + if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) { + log_warn("pipe write error (to child)"); + quit = 1; + } + + if (nfds > 0 && pfd[PFD_PIPE].revents & POLLIN) { + nfds--; + if (dispatch_imsg(&lconf, argc0, argv0) == -1) + quit = 1; + } + + for (j = PFD_MAX; nfds > 0 && j < i; j++) { + nfds -= priv_constraint_dispatch(&pfd[j]); + } + + if (sigchld) { + check_child(); + sigchld = 0; + } + } + + signal(SIGCHLD, SIG_DFL); + + /* Close socket and start shutdown. */ + close(ibuf->fd); + + do { + if ((pid = wait(NULL)) == -1 && + errno != EINTR && errno != ECHILD) + fatal("wait"); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + msgbuf_clear(&ibuf->w); + free(ibuf); + log_info("Terminating"); + return (0); +} + +void +check_child(void) +{ + int status; + pid_t pid; + + do { + pid = waitpid(WAIT_ANY, &status, WNOHANG); + if (pid <= 0) + continue; + + priv_constraint_check_child(pid, status); + } while (pid > 0 || (pid == -1 && errno == EINTR)); +} + +int +dispatch_imsg(struct ntpd_conf *lconf, int argc, char **argv) +{ + struct imsg imsg; + int n; + double d; + + if (((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) || n == 0) + return (-1); + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + return (-1); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_ADJTIME: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(d)) + fatalx("invalid IMSG_ADJTIME received"); + memcpy(&d, imsg.data, sizeof(d)); + n = ntpd_adjtime(d); + imsg_compose(ibuf, IMSG_ADJTIME, 0, 0, -1, + &n, sizeof(n)); + break; + case IMSG_ADJFREQ: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(d)) + fatalx("invalid IMSG_ADJFREQ received"); + memcpy(&d, imsg.data, sizeof(d)); + ntpd_adjfreq(d, 1); + break; + case IMSG_SETTIME: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(d)) + fatalx("invalid IMSG_SETTIME received"); + if (!lconf->settime) + break; + log_init(lconf->debug ? LOG_TO_STDERR : LOG_TO_SYSLOG, + lconf->verbose, LOG_DAEMON); + memcpy(&d, imsg.data, sizeof(d)); + ntpd_settime(d); + /* daemonize now */ + if (!lconf->debug) + if (daemon(1, 0)) + fatal("daemon"); + lconf->settime = 0; + timeout = INFTIM; + break; + case IMSG_CONSTRAINT_QUERY: + priv_constraint_msg(imsg.hdr.peerid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE, + argc, argv); + break; + case IMSG_CONSTRAINT_KILL: + priv_constraint_kill(imsg.hdr.peerid); + break; + default: + break; + } + imsg_free(&imsg); + } + return (0); +} + +void +reset_adjtime(void) +{ + struct timeval tv; + + timerclear(&tv); + if (adjtime(&tv, NULL) == -1) + log_warn("reset adjtime failed"); +} + +int +ntpd_adjtime(double d) +{ + struct timeval tv, olddelta; + int synced = 0; + static int firstadj = 1; + + d += getoffset(); + if (d >= (double)LOG_NEGLIGIBLE_ADJTIME / 1000 || + d <= -1 * (double)LOG_NEGLIGIBLE_ADJTIME / 1000) + log_info("adjusting local clock by %fs", d); + else + log_debug("adjusting local clock by %fs", d); + d_to_tv(d, &tv); + if (adjtime(&tv, &olddelta) == -1) + log_warn("adjtime failed"); + else if (!firstadj && olddelta.tv_sec == 0 && olddelta.tv_usec == 0) + synced = 1; + firstadj = 0; + return (synced); +} + +void +ntpd_adjfreq(double relfreq, int wrlog) +{ + int64_t curfreq; + double ppmfreq; + int r; + + if (adjfreq(NULL, &curfreq) == -1) { + log_warn("adjfreq failed"); + return; + } + + /* + * adjfreq's unit is ns/s shifted left 32; convert relfreq to + * that unit before adding. We log values in part per million. + */ + curfreq += relfreq * 1e9 * (1LL << 32); + r = writefreq(curfreq / 1e9 / (1LL << 32)); + ppmfreq = relfreq * 1e6; + if (wrlog) { + if (ppmfreq >= LOG_NEGLIGIBLE_ADJFREQ || + ppmfreq <= -LOG_NEGLIGIBLE_ADJFREQ) + log_info("adjusting clock frequency by %f to %fppm%s", + ppmfreq, curfreq / 1e3 / (1LL << 32), + r ? "" : " (no drift file)"); + else + log_debug("adjusting clock frequency by %f to %fppm%s", + ppmfreq, curfreq / 1e3 / (1LL << 32), + r ? "" : " (no drift file)"); + } + + if (adjfreq(&curfreq, NULL) == -1) + log_warn("adjfreq failed"); +} + +void +ntpd_settime(double d) +{ + struct timeval tv, curtime; + char buf[80]; + time_t tval; + + if (d == 0) + return; + + if (gettimeofday(&curtime, NULL) == -1) { + log_warn("gettimeofday"); + return; + } + d_to_tv(d, &tv); + curtime.tv_usec += tv.tv_usec + 1000000; + curtime.tv_sec += tv.tv_sec - 1 + (curtime.tv_usec / 1000000); + curtime.tv_usec %= 1000000; + + if (settimeofday(&curtime, NULL) == -1) { + log_warn("settimeofday"); + return; + } + tval = curtime.tv_sec; + strftime(buf, sizeof(buf), "%a %b %e %H:%M:%S %Z %Y", + localtime(&tval)); + log_info("set local clock to %s (offset %fs)", buf, d); +} + +static FILE *freqfp; + +void +readfreq(void) +{ + int64_t current; + int fd; + double d; + + fd = open(DRIFTFILE, O_RDWR); + if (fd == -1) { + log_warnx("creating new %s", DRIFTFILE); + current = 0; + if (adjfreq(¤t, NULL) == -1) + log_warn("adjfreq reset failed"); + freqfp = fopen(DRIFTFILE, "w"); + return; + } + + freqfp = fdopen(fd, "r+"); + + /* if we're adjusting frequency already, don't override */ + if (adjfreq(NULL, ¤t) == -1) + log_warn("adjfreq failed"); + else if (current == 0 && freqfp) { + if (fscanf(freqfp, "%lf", &d) == 1) { + d /= 1e6; /* scale from ppm */ + ntpd_adjfreq(d, 0); + } else + log_warnx("%s is empty", DRIFTFILE); + } +} + +int +writefreq(double d) +{ + int r; + static int warnonce = 1; + + if (freqfp == NULL) + return 0; + rewind(freqfp); + r = fprintf(freqfp, "%.3f\n", d * 1e6); /* scale to ppm */ + if (r < 0 || fflush(freqfp) != 0) { + if (warnonce) { + log_warnx("can't write %s", DRIFTFILE); + warnonce = 0; + } + clearerr(freqfp); + return 0; + } + ftruncate(fileno(freqfp), ftello(freqfp)); + fsync(fileno(freqfp)); + return 1; +} + +void +ctl_main(int argc, char *argv[]) +{ + struct sockaddr_un sa; + struct imsg imsg; + struct imsgbuf *ibuf_ctl; + int fd, n, done, ch, action; + char *sockname; + + sockname = CTLSOCKET; + + if (argc < 2) { + usage(); + /* NOTREACHED */ + } + + while ((ch = getopt(argc, argv, "s:")) != -1) { + switch (ch) { + case 's': + showopt = ctl_lookup_option(optarg, ctl_showopt_list); + if (showopt == NULL) { + warnx("Unknown show modifier '%s'", optarg); + usage(); + } + break; + default: + usage(); + /* NOTREACHED */ + } + } + + action = -1; + if (showopt != NULL) { + switch (*showopt) { + case 'p': + action = CTL_SHOW_PEERS; + break; + case 's': + action = CTL_SHOW_STATUS; + break; + case 'S': + action = CTL_SHOW_SENSORS; + break; + case 'a': + action = CTL_SHOW_ALL; + break; + } + } + if (action == -1) + usage(); + /* NOTREACHED */ + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "ntpctl: socket"); + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + if (strlcpy(sa.sun_path, sockname, sizeof(sa.sun_path)) >= + sizeof(sa.sun_path)) + errx(1, "ctl socket name too long"); + if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) + err(1, "connect: %s", sockname); + + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); + + if ((ibuf_ctl = malloc(sizeof(struct imsgbuf))) == NULL) + err(1, NULL); + imsg_init(ibuf_ctl, fd); + + switch (action) { + case CTL_SHOW_STATUS: + imsg_compose(ibuf_ctl, IMSG_CTL_SHOW_STATUS, + 0, 0, -1, NULL, 0); + break; + case CTL_SHOW_PEERS: + imsg_compose(ibuf_ctl, IMSG_CTL_SHOW_PEERS, + 0, 0, -1, NULL, 0); + break; + case CTL_SHOW_SENSORS: + imsg_compose(ibuf_ctl, IMSG_CTL_SHOW_SENSORS, + 0, 0, -1, NULL, 0); + break; + case CTL_SHOW_ALL: + imsg_compose(ibuf_ctl, IMSG_CTL_SHOW_ALL, + 0, 0, -1, NULL, 0); + break; + default: + errx(1, "invalid action"); + break; /* NOTREACHED */ + } + + while (ibuf_ctl->w.queued) + if (msgbuf_write(&ibuf_ctl->w) <= 0 && errno != EAGAIN) + err(1, "ibuf_ctl: msgbuf_write error"); + + done = 0; + while (!done) { + if ((n = imsg_read(ibuf_ctl)) == -1 && errno != EAGAIN) + err(1, "ibuf_ctl: imsg_read error"); + if (n == 0) + errx(1, "ntpctl: pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf_ctl, &imsg)) == -1) + err(1, "ibuf_ctl: imsg_get error"); + if (n == 0) + break; + + switch (action) { + case CTL_SHOW_STATUS: + show_status_msg(&imsg); + done = 1; + break; + case CTL_SHOW_PEERS: + show_peer_msg(&imsg, 0); + if (imsg.hdr.type == + IMSG_CTL_SHOW_PEERS_END) + done = 1; + break; + case CTL_SHOW_SENSORS: + show_sensor_msg(&imsg, 0); + if (imsg.hdr.type == + IMSG_CTL_SHOW_SENSORS_END) + done = 1; + break; + case CTL_SHOW_ALL: + switch (imsg.hdr.type) { + case IMSG_CTL_SHOW_STATUS: + show_status_msg(&imsg); + break; + case IMSG_CTL_SHOW_PEERS: + show_peer_msg(&imsg, 1); + break; + case IMSG_CTL_SHOW_SENSORS: + show_sensor_msg(&imsg, 1); + break; + case IMSG_CTL_SHOW_PEERS_END: + case IMSG_CTL_SHOW_SENSORS_END: + /* do nothing */ + break; + case IMSG_CTL_SHOW_ALL_END: + done=1; + break; + default: + /* no action taken */ + break; + } + default: + /* no action taken */ + break; + } + imsg_free(&imsg); + } + } + close(fd); + free(ibuf_ctl); + exit(0); +} + +const char * +ctl_lookup_option(char *cmd, const char **list) +{ + const char *item = NULL; + if (cmd != NULL && *cmd) + for (; *list; list++) + if (!strncmp(cmd, *list, strlen(cmd))) { + if (item == NULL) + item = *list; + else + errx(1, "%s is ambiguous", cmd); + } + return (item); +} + +void +show_status_msg(struct imsg *imsg) +{ + struct ctl_show_status *cstatus; + double clock_offset; + struct timeval tv; + + if (imsg->hdr.len != IMSG_HEADER_SIZE + sizeof(struct ctl_show_status)) + fatalx("invalid IMSG_CTL_SHOW_STATUS received"); + + cstatus = (struct ctl_show_status *)imsg->data; + + if (cstatus->peercnt > 0) + printf("%d/%d peers valid, ", + cstatus->valid_peers, cstatus->peercnt); + + if (cstatus->sensorcnt > 0) + printf("%d/%d sensors valid, ", + cstatus->valid_sensors, cstatus->sensorcnt); + + if (cstatus->constraint_median) { + tv.tv_sec = cstatus->constraint_median + + (getmonotime() - cstatus->constraint_last); + tv.tv_usec = 0; + d_to_tv(gettime_from_timeval(&tv) - gettime(), &tv); + printf("constraint offset %llds", (long long)tv.tv_sec); + if (cstatus->constraint_errors) + printf(" (%d errors)", + cstatus->constraint_errors); + printf(", "); + } else if (cstatus->constraints) + printf("constraints configured but none available, "); + + if (cstatus->peercnt + cstatus->sensorcnt == 0) + printf("no peers and no sensors configured\n"); + + if (cstatus->synced == 1) + printf("clock synced, stratum %u\n", cstatus->stratum); + else { + printf("clock unsynced"); + clock_offset = cstatus->clock_offset < 0 ? + -1.0 * cstatus->clock_offset : cstatus->clock_offset; + if (clock_offset > 5e-7) + printf(", clock offset is %.3fms\n", + cstatus->clock_offset); + else + printf("\n"); + } +} + +void +show_peer_msg(struct imsg *imsg, int calledfromshowall) +{ + struct ctl_show_peer *cpeer; + int cnt; + char stratum[3]; + static int firsttime = 1; + + if (imsg->hdr.type == IMSG_CTL_SHOW_PEERS_END) { + if (imsg->hdr.len != IMSG_HEADER_SIZE + sizeof(cnt)) + fatalx("invalid IMSG_CTL_SHOW_PEERS_END received"); + memcpy(&cnt, imsg->data, sizeof(cnt)); + if (cnt == 0) + printf("no peers configured\n"); + return; + } + + if (imsg->hdr.len != IMSG_HEADER_SIZE + sizeof(struct ctl_show_peer)) + fatalx("invalid IMSG_CTL_SHOW_PEERS received"); + + cpeer = (struct ctl_show_peer *)imsg->data; + + if (strlen(cpeer->peer_desc) > MAX_DISPLAY_WIDTH - 1) + fatalx("peer_desc is too long"); + + if (firsttime) { + firsttime = 0; + if (calledfromshowall) + printf("\n"); + printf("peer\n wt tl st next poll " + "offset delay jitter\n"); + } + + if (cpeer->stratum > 0) + snprintf(stratum, sizeof(stratum), "%2u", cpeer->stratum); + else + strlcpy(stratum, " -", sizeof (stratum)); + + printf("%s\n %1s %2u %2u %2s %4llds %4llds", + cpeer->peer_desc, cpeer->syncedto == 1 ? "*" : " ", + cpeer->weight, cpeer->trustlevel, stratum, + (long long)cpeer->next, (long long)cpeer->poll); + + if (cpeer->trustlevel >= TRUSTLEVEL_BADPEER) + printf(" %12.3fms %9.3fms %8.3fms\n", cpeer->offset, + cpeer->delay, cpeer->jitter); + else + printf(" ---- peer not valid ----\n"); + +} + +void +show_sensor_msg(struct imsg *imsg, int calledfromshowall) +{ + struct ctl_show_sensor *csensor; + int cnt; + static int firsttime = 1; + + if (imsg->hdr.type == IMSG_CTL_SHOW_SENSORS_END) { + if (imsg->hdr.len != IMSG_HEADER_SIZE + sizeof(cnt)) + fatalx("invalid IMSG_CTL_SHOW_SENSORS_END received"); + memcpy(&cnt, imsg->data, sizeof(cnt)); + if (cnt == 0) + printf("no sensors configured\n"); + return; + } + + if (imsg->hdr.len != IMSG_HEADER_SIZE + sizeof(struct ctl_show_sensor)) + fatalx("invalid IMSG_CTL_SHOW_SENSORS received"); + + csensor = (struct ctl_show_sensor *)imsg->data; + + if (strlen(csensor->sensor_desc) > MAX_DISPLAY_WIDTH - 1) + fatalx("sensor_desc is too long"); + + if (firsttime) { + firsttime = 0; + if (calledfromshowall) + printf("\n"); + printf("sensor\n wt gd st next poll " + "offset correction\n"); + } + + printf("%s\n %1s %2u %2u %2u %4llds %4llds", + csensor->sensor_desc, csensor->syncedto == 1 ? "*" : " ", + csensor->weight, csensor->good, csensor->stratum, + (long long)csensor->next, (long long)csensor->poll); + + if (csensor->good == 1) + printf(" %11.3fms %9.3fms\n", + csensor->offset, csensor->correction); + else + printf(" - sensor not valid -\n"); + +} diff --git a/ntpd.conf.5 b/ntpd.conf.5 new file mode 100644 index 0000000..8ad372a --- /dev/null +++ b/ntpd.conf.5 @@ -0,0 +1,265 @@ +.\" $OpenBSD: ntpd.conf.5,v 1.49 2023/03/02 17:09:53 jmc Exp $ +.\" +.\" Copyright (c) 2003, 2004 Henning Brauer +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN +.\" AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 2 2023 $ +.Dt NTPD.CONF 5 +.Os +.Sh NAME +.Nm ntpd.conf +.Nd NTP daemon configuration file +.Sh DESCRIPTION +This manual page describes the format of the +.Xr ntpd 8 +configuration file. +.Pp +.Nm +has the following format: +.Pp +Empty lines and lines beginning with the +.Sq # +character are ignored. +.Pp +Keywords may be specified multiple times within the configuration file. +The basic configuration options are as follows: +.Bl -tag -width Ds +.It Xo Ic listen on Ar address +.Op Ic rtable Ar table-id +.Xc +Specify a local IP address or a hostname the +.Xr ntpd 8 +daemon should listen on. +If it appears multiple times, +.Xr ntpd 8 +will listen on each given address. +If +.Sq * +is given as an address, +.Xr ntpd 8 +will listen on all local addresses using the specified routing table. +.Xr ntpd 8 +does not listen on any address by default. +The optional +.Ic rtable +keyword will specify which routing table to listen on. +By default +.Xr ntpd 8 +will listen using the current routing table. +For example: +.Bd -literal -offset indent +listen on * +.Ed +.Pp +or +.Bd -literal -offset indent +listen on 127.0.0.1 +listen on ::1 +listen on 127.0.0.1 rtable 4 +.Ed +.It Ic query from Ar sourceaddr +Specify a local IP address the +.Xr ntpd 8 +daemon should use for outgoing queries to subsequently specified servers, +which is useful on machines with multiple interfaces. +For example: +.Bd -literal -offset indent +query from 192.0.2.1 +query from 2001:db8::1 +.Ed +.It Xo Ic sensor Ar device +.Op Ic correction Ar microseconds +.Op Ic refid Ar ID-string +.Op Ic stratum Ar stratum-value +.Op Ic trusted +.Op Ic weight Ar weight-value +.Xc +Specify a timedelta sensor device +.Xr ntpd 8 +should use. +The sensor can be specified multiple times: +.Xr ntpd 8 +will use each given sensor that actually exists. +Non-existent sensors are ignored. +If +.Sq * +is given as device name, +.Xr ntpd 8 +will use all timedelta sensors it finds. +.Xr ntpd 8 +does not use any timedelta sensor by default. +For example: +.Bd -literal -offset indent +sensor * +sensor nmea0 +.Ed +.Pp +A +.Ic correction +in microseconds can be given to compensate +for the sensor's offset. +The maximum correction is 127 seconds. +For example, if a DCF77 receiver is lagging 70ms behind +actual time: +.Bd -literal -offset indent +sensor udcf0 correction 70000 +.Ed +.Pp +A +.Ic refid +.Ar ID-string +of up to 4 ASCII characters can be +given to publish the sensor type to clients. +RFC 2030 suggests some common reference identifiers, but new identifiers +"can be contrived as appropriate." +If an +.Ar ID-string +is not given, +.Xr ntpd 8 +will use a generic reference ID. +For example: +.Bd -literal -offset indent +sensor nmea0 refid GPS +.Ed +.Pp +The +.Ic stratum +keyword can be used to change the stratum value from the default of 1. +.Pp +The +.Ic trusted +keyword indicates the time learned is secure, trustworthy, +and not vulnerable to man-in-the-middle attacks, so +.Ic constraints +validation is skipped. +This is useful for boot-time correction in environments where +.Ic constraints +cannot be used. +.Pp +The +.Ic weight +keyword permits finer control over the relative importance +of time sources (servers or sensor devices). +Weights are specified in the range 1 to 10; +if no weight is given, +the default is 1. +A server with a weight of 5, for example, +will have five times more influence on time offset calculation +than a server with a weight of 1. +.It Xo Ic server Ar address +.Op Ic trusted +.Op Ic weight Ar weight-value +.Xc +Specify the IP address or the hostname of an NTP +server to synchronize to. +If it appears multiple times, +.Xr ntpd 8 +will try to synchronize to all of the servers specified. +If a hostname resolves to multiple IPv4 and/or IPv6 addresses, +.Xr ntpd 8 +uses the first address. +If it does not get a reply, +.Xr ntpd 8 +retries with the next address and continues to do so until a working address +is found. +For example: +.Bd -literal -offset indent +server 10.0.0.2 weight 5 +server ntp.example.org weight 1 +.Ed +.Pp +To provide redundancy, it is good practice to configure multiple servers. +In general, best accuracy is obtained by using servers that have a low +network latency. +.It Xo Ic servers Ar address +.Op Ic trusted +.Op Ic weight Ar weight-value +.Xc +As with +.Cm server , +specify the IP address or hostname of an NTP server to synchronize to. +If it appears multiple times, +.Xr ntpd 8 +will try to synchronize to all of the servers specified. +Should the hostname resolve to multiple IP addresses, +.Xr ntpd 8 +will try to synchronize to all of them. +For example: +.Bd -literal -offset indent +servers pool.ntp.org +servers pool.ntp.org weight 5 +.Ed +.El +.Sh CONSTRAINTS +.Xr ntpd 8 +can be configured to query the +.Sq Date +from trusted HTTPS servers via TLS. +This time information is not used for precision but acts as an +authenticated constraint, +thereby reducing the impact of unauthenticated NTP +man-in-the-middle attacks. +Received NTP packets with time information falling outside of a range +near the constraint will be discarded and such NTP servers +will be marked as invalid. +.Bl -tag -width Ds +.It Ic constraint from Ar url [ip...] +Specify the URL, IP address or the hostname of an HTTPS server to +provide a constraint. +If the url is followed by one or more addresses, the url and addresses will be +tried until a working one is found. +The url path and expected certificate name is always taken from the +url specified. +If +.Ic constraint from +is used more than once, +.Xr ntpd 8 +will calculate a median constraint from all the servers specified. +.Bd -literal -offset indent +server ntp.example.org +constraint from www.example.com +constraint from "https://9.9.9.9" "2620:fe::9" +.Ed +.It Ic constraints from Ar url +As with +.Ic constraint from , +specify the URL, IP address or the hostname of an HTTPS server to +provide a constraint. +Should the hostname resolve to multiple IP addresses, +.Xr ntpd 8 +will calculate a median constraint from all of them. +For example: +.Bd -literal -offset indent +servers pool.ntp.org +constraints from "https://www.google.com/" +.Ed +.El +.Sh FILES +.Bl -tag -width /etc/examples/ntpd.conf -compact +.It Pa /etc/ntpd.conf +Default +.Xr ntpd 8 +configuration file. +.It Pa /etc/examples/ntpd.conf +Example configuration file. +.El +.Sh SEE ALSO +.Xr ntpctl 8 , +.Xr ntpd 8 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 3.6 . diff --git a/ntpd.h b/ntpd.h new file mode 100644 index 0000000..872d028 --- /dev/null +++ b/ntpd.h @@ -0,0 +1,436 @@ +/* $OpenBSD: ntpd.h,v 1.154 2024/05/21 05:00:48 jsg Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * Copyright (c) 2012 Mike Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntp.h" +#include "log.h" + +#define MAXIMUM(a, b) ((a) > (b) ? (a) : (b)) + +#define NTPD_USER "_ntp" +#define CONFFILE "/etc/ntpd.conf" +#define DRIFTFILE "/var/db/ntpd.drift" +#define CTLSOCKET "/var/run/ntpd.sock" + +#define INTERVAL_QUERY_NORMAL 30 /* sync to peers every n secs */ +#define INTERVAL_QUERY_PATHETIC 60 +#define INTERVAL_QUERY_AGGRESSIVE 5 +#define INTERVAL_QUERY_ULTRA_VIOLENCE 1 /* used at startup for auto */ + +#define TRUSTLEVEL_BADPEER 6 +#define TRUSTLEVEL_PATHETIC 2 +#define TRUSTLEVEL_AGGRESSIVE 8 +#define TRUSTLEVEL_MAX 10 + +#define MAX_SERVERS_DNS 8 + +#define QSCALE_OFF_MIN 0.001 +#define QSCALE_OFF_MAX 0.050 + +#define QUERYTIME_MAX 15 /* single query might take n secs max */ +#define OFFSET_ARRAY_SIZE 8 +#define SENSOR_OFFSETS 6 +#define SETTIME_TIMEOUT 15 /* max seconds to wait with -s */ +#define LOG_NEGLIGIBLE_ADJTIME 32 /* negligible drift to not log (ms) */ +#define LOG_NEGLIGIBLE_ADJFREQ 0.05 /* negligible rate to not log (ppm) */ +#define FREQUENCY_SAMPLES 8 /* samples for est. of permanent drift */ +#define MAX_FREQUENCY_ADJUST 128e-5 /* max correction per iteration */ +#define MAX_SEND_ERRORS 3 /* max send errors before reconnect */ +#define MAX_DISPLAY_WIDTH 80 /* max chars in ctl_show report line */ + +#define FILTER_ADJFREQ 0x01 /* set after doing adjfreq */ +#define AUTO_REPLIES 4 /* # of ntp replies we want for auto */ +#define AUTO_THRESHOLD 60 /* dont bother auto setting < this */ +#define INTERVAL_AUIO_DNSFAIL 1 /* DNS tmpfail interval for auto */ +#define TRIES_AUTO_DNSFAIL 4 /* DNS tmpfail quick retries */ + + +#define SENSOR_DATA_MAXAGE (15*60) +#define SENSOR_QUERY_INTERVAL 15 +#define SENSOR_QUERY_INTERVAL_SETTIME (SETTIME_TIMEOUT/3) +#define SENSOR_SCAN_INTERVAL (1*60) +#define SENSOR_DEFAULT_REFID "HARD" + +#define CONSTRAINT_ERROR_MARGIN (4) +#define CONSTRAINT_RETRY_INTERVAL (15) +#define CONSTRAINT_SCAN_INTERVAL (15*60) +#define CONSTRAINT_SCAN_TIMEOUT (10) +#define CONSTRAINT_MARGIN (2.0*60) +#define CONSTRAINT_PORT "443" /* HTTPS port */ +#define CONSTRAINT_MAXHEADERLENGTH 8192 +#define CONSTRAINT_PASSFD (STDERR_FILENO + 1) + +#define PARENT_SOCK_FILENO CONSTRAINT_PASSFD + +#define NTP_PROC_NAME "ntp_main" +#define NTPDNS_PROC_NAME "ntp_dns" +#define CONSTRAINT_PROC_NAME "constraint" + +enum client_state { + STATE_NONE, + STATE_DNS_INPROGRESS, + STATE_DNS_TEMPFAIL, + STATE_DNS_DONE, + STATE_QUERY_SENT, + STATE_REPLY_RECEIVED, + STATE_TIMEOUT, + STATE_INVALID +}; + +struct listen_addr { + TAILQ_ENTRY(listen_addr) entry; + struct sockaddr_storage sa; + int fd; + int rtable; +}; + +struct ntp_addr { + struct ntp_addr *next; + struct sockaddr_storage ss; + int notauth; +}; + +struct ntp_addr_wrap { + char *name; + char *path; + struct ntp_addr *a; + u_int8_t pool; +}; + +struct ntp_addr_msg { + struct ntp_addr a; + size_t namelen; + size_t pathlen; + u_int8_t synced; +}; + +struct ntp_status { + double rootdelay; + double rootdispersion; + double reftime; + u_int32_t refid; + u_int32_t send_refid; + u_int8_t synced; + u_int8_t leap; + int8_t precision; + u_int8_t poll; + u_int8_t stratum; +}; + +struct ntp_offset { + struct ntp_status status; + double offset; + double delay; + double error; + time_t rcvd; + u_int8_t good; +}; + +struct ntp_peer { + TAILQ_ENTRY(ntp_peer) entry; + struct ntp_addr_wrap addr_head; + struct ntp_query query; + struct ntp_addr *addr; + struct ntp_offset reply[OFFSET_ARRAY_SIZE]; + struct ntp_offset update; + struct sockaddr_in query_addr4; + struct sockaddr_in6 query_addr6; + enum client_state state; + time_t next; + time_t deadline; + time_t poll; + u_int32_t id; + u_int8_t shift; + u_int8_t trustlevel; + u_int8_t weight; + u_int8_t trusted; + int lasterror; + int senderrors; +}; + +struct ntp_sensor { + TAILQ_ENTRY(ntp_sensor) entry; + struct ntp_offset offsets[SENSOR_OFFSETS]; + struct ntp_offset update; + time_t next; + time_t last; + char *device; + u_int32_t refid; + int sensordevid; + int correction; + u_int8_t stratum; + u_int8_t weight; + u_int8_t shift; + u_int8_t trusted; +}; + +struct constraint { + TAILQ_ENTRY(constraint) entry; + struct ntp_addr_wrap addr_head; + struct ntp_addr *addr; + int senderrors; + enum client_state state; + u_int32_t id; + int fd; + pid_t pid; + struct imsgbuf ibuf; + time_t last; + time_t constraint; + int dnstries; +}; + +struct ntp_conf_sensor { + TAILQ_ENTRY(ntp_conf_sensor) entry; + char *device; + char *refstr; + int correction; + u_int8_t stratum; + u_int8_t weight; + u_int8_t trusted; +}; + +struct ntp_freq { + double overall_offset; + double x, y; + double xx, xy; + int samples; + u_int num; +}; + +struct ntpd_conf { + TAILQ_HEAD(listen_addrs, listen_addr) listen_addrs; + TAILQ_HEAD(ntp_peers, ntp_peer) ntp_peers; + TAILQ_HEAD(ntp_sensors, ntp_sensor) ntp_sensors; + TAILQ_HEAD(ntp_conf_sensors, ntp_conf_sensor) ntp_conf_sensors; + TAILQ_HEAD(constraints, constraint) constraints; + struct ntp_status status; + struct ntp_freq freq; + struct sockaddr_in query_addr4; + struct sockaddr_in6 query_addr6; + u_int32_t scale; + int debug; + int verbose; + u_int8_t listen_all; + u_int8_t settime; + u_int8_t automatic; + u_int8_t noaction; + u_int8_t filters; + u_int8_t trusted_peers; + u_int8_t trusted_sensors; + time_t constraint_last; + time_t constraint_median; + u_int constraint_errors; + u_int8_t *ca; + size_t ca_len; + int tmpfail; +}; + +struct ctl_show_status { + time_t constraint_median; + time_t constraint_last; + double clock_offset; + u_int peercnt; + u_int sensorcnt; + u_int valid_peers; + u_int valid_sensors; + u_int constraint_errors; + u_int8_t synced; + u_int8_t stratum; + u_int8_t constraints; +}; + +struct ctl_show_peer { + char peer_desc[MAX_DISPLAY_WIDTH]; + u_int8_t syncedto; + u_int8_t weight; + u_int8_t trustlevel; + u_int8_t stratum; + time_t next; + time_t poll; + double offset; + double delay; + double jitter; +}; + +struct ctl_show_sensor { + char sensor_desc[MAX_DISPLAY_WIDTH]; + u_int8_t syncedto; + u_int8_t weight; + u_int8_t good; + u_int8_t stratum; + time_t next; + time_t poll; + double offset; + double correction; +}; + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + struct imsgbuf ibuf; +}; + +TAILQ_HEAD(ctl_conns, ctl_conn) ; + +enum imsg_type { + IMSG_NONE, + IMSG_ADJTIME, + IMSG_ADJFREQ, + IMSG_SETTIME, + IMSG_HOST_DNS, + IMSG_CONSTRAINT_DNS, + IMSG_CONSTRAINT_QUERY, + IMSG_CONSTRAINT_RESULT, + IMSG_CONSTRAINT_CLOSE, + IMSG_CONSTRAINT_KILL, + IMSG_CTL_SHOW_STATUS, + IMSG_CTL_SHOW_PEERS, + IMSG_CTL_SHOW_PEERS_END, + IMSG_CTL_SHOW_SENSORS, + IMSG_CTL_SHOW_SENSORS_END, + IMSG_CTL_SHOW_ALL, + IMSG_CTL_SHOW_ALL_END, + IMSG_SYNCED, + IMSG_UNSYNCED, + IMSG_PROBE_ROOT +}; + +enum ctl_actions { + CTL_SHOW_STATUS, + CTL_SHOW_PEERS, + CTL_SHOW_SENSORS, + CTL_SHOW_ALL +}; + +/* prototypes */ + +/* ntp.c */ +void ntp_main(struct ntpd_conf *, struct passwd *, int, char **); +void peer_addr_head_clear(struct ntp_peer *); +int priv_adjtime(void); +void priv_settime(double, char *); +void priv_dns(int, char *, u_int32_t); +int offset_compare(const void *, const void *); +void update_scale(double); +time_t scale_interval(time_t); +time_t error_interval(void); +extern struct ntpd_conf *conf; +extern struct ctl_conns ctl_conns; + +#define SCALE_INTERVAL(x) MAXIMUM(5, (x) / 10) + +/* parse.y */ +int parse_config(const char *, struct ntpd_conf *); + +/* config.c */ +void host(const char *, struct ntp_addr **); +int host_dns(const char *, int, struct ntp_addr **); +void host_dns_free(struct ntp_addr *); +struct ntp_peer *new_peer(void); +struct ntp_conf_sensor *new_sensor(char *); +struct constraint *new_constraint(void); + +/* ntp_msg.c */ +int ntp_getmsg(struct sockaddr *, char *, ssize_t, struct ntp_msg *); +int ntp_sendmsg(int, struct sockaddr *, struct ntp_msg *); + +/* server.c */ +int setup_listeners(struct servent *, struct ntpd_conf *, u_int *); +int server_dispatch(int, struct ntpd_conf *); + +/* client.c */ +int client_peer_init(struct ntp_peer *); +int client_addr_init(struct ntp_peer *); +int client_nextaddr(struct ntp_peer *); +int client_query(struct ntp_peer *); +int client_dispatch(struct ntp_peer *, u_int8_t, u_int8_t); +void client_log_error(struct ntp_peer *, const char *, int); +void set_next(struct ntp_peer *, time_t); + +/* constraint.c */ +void constraint_add(struct constraint *); +void constraint_remove(struct constraint *); +void constraint_purge(void); +void constraint_reset(void); +int constraint_init(struct constraint *); +int constraint_query(struct constraint *, int); +int constraint_check(double); +void constraint_msg_dns(u_int32_t, u_int8_t *, size_t); +void constraint_msg_result(u_int32_t, u_int8_t *, size_t); +void constraint_msg_close(u_int32_t, u_int8_t *, size_t); +void priv_constraint_msg(u_int32_t, u_int8_t *, size_t, int, char **); +void priv_constraint_child(const char *, uid_t, gid_t); +void priv_constraint_kill(u_int32_t); +int priv_constraint_dispatch(struct pollfd *); +void priv_constraint_check_child(pid_t, int); +char *get_string(u_int8_t *, size_t); + +/* util.c */ +double gettime_corrected(void); +double gettime_from_timeval(struct timeval *); +double getoffset(void); +double gettime(void); +time_t getmonotime(void); +void d_to_tv(double, struct timeval *); +double lfp_to_d(struct l_fixedpt); +struct l_fixedpt d_to_lfp(double); +double sfp_to_d(struct s_fixedpt); +struct s_fixedpt d_to_sfp(double); +char *print_rtable(int); +const char *log_sockaddr(struct sockaddr *); +const char *log_ntp_addr(struct ntp_addr *); +pid_t start_child(char *, int, int, char **); +int sanitize_argv(int *, char ***); + +/* sensors.c */ +void sensor_init(void); +int sensor_scan(void); +void sensor_query(struct ntp_sensor *); + +/* ntp_dns.c */ +void ntp_dns(struct ntpd_conf *, struct passwd *); + +/* control.c */ +int control_check(char *); +int control_init(char *); +int control_listen(int); +void control_shutdown(int); +int control_accept(int); +struct ctl_conn *control_connbyfd(int); +int control_close(int); +int control_dispatch_msg(struct pollfd *, u_int *); +void session_socket_nonblockmode(int); +void build_show_status(struct ctl_show_status *); +void build_show_peer(struct ctl_show_peer *, + struct ntp_peer *); +void build_show_sensor(struct ctl_show_sensor *, + struct ntp_sensor *); + diff --git a/parse.y b/parse.y new file mode 100644 index 0000000..3aa8d95 --- /dev/null +++ b/parse.y @@ -0,0 +1,841 @@ +/* $OpenBSD: parse.y,v 1.78 2021/10/15 15:01:28 naddy Exp $ */ + +/* + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *); +int popfile(void); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +struct sockaddr_in query_addr4; +struct sockaddr_in6 query_addr6; +int poolseqnum; + +struct opts { + int weight; + int correction; + int stratum; + int rtable; + int trusted; + char *refstr; +} opts; +void opts_default(void); + +typedef struct { + union { + int64_t number; + char *string; + struct ntp_addr_wrap *addr; + struct opts opts; + } v; + int lineno; +} YYSTYPE; + +%} + +%token LISTEN ON CONSTRAINT CONSTRAINTS FROM QUERY TRUSTED +%token SERVER SERVERS SENSOR CORRECTION RTABLE REFID STRATUM WEIGHT +%token ERROR +%token STRING +%token NUMBER +%type address url urllist +%type listen_opts listen_opts_l listen_opt +%type server_opts server_opts_l server_opt +%type sensor_opts sensor_opts_l sensor_opt +%type correction +%type rtable +%type refid +%type stratum +%type weight +%type trusted +%% + +grammar : /* empty */ + | grammar '\n' + | grammar main '\n' + | grammar error '\n' { file->errors++; } + ; + +main : LISTEN ON address listen_opts { + struct listen_addr *la; + struct ntp_addr *h, *next; + + if ((h = $3->a) == NULL && + (host_dns($3->name, 0, &h) == -1 || !h)) { + yyerror("could not resolve \"%s\"", $3->name); + free($3->name); + free($3); + YYERROR; + } + + for (; h != NULL; h = next) { + next = h->next; + la = calloc(1, sizeof(struct listen_addr)); + if (la == NULL) + fatal("listen on calloc"); + la->fd = -1; + la->rtable = $4.rtable; + memcpy(&la->sa, &h->ss, + sizeof(struct sockaddr_storage)); + TAILQ_INSERT_TAIL(&conf->listen_addrs, la, + entry); + free(h); + } + free($3->name); + free($3); + } + | QUERY FROM STRING { + struct sockaddr_in sin4; + struct sockaddr_in6 sin6; + + memset(&sin4, 0, sizeof(sin4)); + sin4.sin_family = AF_INET; + sin4.sin_len = sizeof(struct sockaddr_in); + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + + if (inet_pton(AF_INET, $3, &sin4.sin_addr) == 1) + memcpy(&query_addr4, &sin4, sin4.sin_len); + else if (inet_pton(AF_INET6, $3, &sin6.sin6_addr) == 1) + memcpy(&query_addr6, &sin6, sin6.sin6_len); + else { + yyerror("invalid IPv4 or IPv6 address: %s\n", + $3); + free($3); + YYERROR; + } + + free($3); + } + | SERVERS address server_opts { + struct ntp_peer *p; + struct ntp_addr *h, *next; + + h = $2->a; + do { + if (h != NULL) { + next = h->next; + if (h->ss.ss_family != AF_INET && + h->ss.ss_family != AF_INET6) { + yyerror("IPv4 or IPv6 address " + "or hostname expected"); + free(h); + free($2->name); + free($2); + YYERROR; + } + h->next = NULL; + } else + next = NULL; + + p = new_peer(); + p->weight = $3.weight; + p->trusted = $3.trusted; + conf->trusted_peers = conf->trusted_peers || + $3.trusted; + p->query_addr4 = query_addr4; + p->query_addr6 = query_addr6; + p->addr = h; + p->addr_head.a = h; + p->addr_head.pool = ++poolseqnum; + p->addr_head.name = strdup($2->name); + if (p->addr_head.name == NULL) + fatal(NULL); + if (p->addr != NULL) + p->state = STATE_DNS_DONE; + TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry); + h = next; + } while (h != NULL); + + free($2->name); + free($2); + } + | SERVER address server_opts { + struct ntp_peer *p; + struct ntp_addr *h, *next; + + p = new_peer(); + for (h = $2->a; h != NULL; h = next) { + next = h->next; + if (h->ss.ss_family != AF_INET && + h->ss.ss_family != AF_INET6) { + yyerror("IPv4 or IPv6 address " + "or hostname expected"); + free(h); + free(p); + free($2->name); + free($2); + YYERROR; + } + h->next = p->addr; + p->addr = h; + } + + p->weight = $3.weight; + p->trusted = $3.trusted; + conf->trusted_peers = conf->trusted_peers || + $3.trusted; + p->query_addr4 = query_addr4; + p->query_addr6 = query_addr6; + p->addr_head.a = p->addr; + p->addr_head.pool = 0; + p->addr_head.name = strdup($2->name); + if (p->addr_head.name == NULL) + fatal(NULL); + if (p->addr != NULL) + p->state = STATE_DNS_DONE; + TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry); + free($2->name); + free($2); + } + | CONSTRAINTS FROM url { + struct constraint *p; + struct ntp_addr *h, *next; + + h = $3->a; + do { + if (h != NULL) { + next = h->next; + if (h->ss.ss_family != AF_INET && + h->ss.ss_family != AF_INET6) { + yyerror("IPv4 or IPv6 address " + "or hostname expected"); + free(h); + free($3->name); + free($3->path); + free($3); + YYERROR; + } + h->next = NULL; + } else + next = NULL; + + p = new_constraint(); + p->addr = h; + p->addr_head.a = h; + p->addr_head.pool = ++poolseqnum; + p->addr_head.name = strdup($3->name); + p->addr_head.path = strdup($3->path); + if (p->addr_head.name == NULL || + p->addr_head.path == NULL) + fatal(NULL); + if (p->addr != NULL) + p->state = STATE_DNS_DONE; + constraint_add(p); + h = next; + } while (h != NULL); + + free($3->name); + free($3); + } + | CONSTRAINT FROM urllist { + struct constraint *p; + struct ntp_addr *h, *next; + + p = new_constraint(); + for (h = $3->a; h != NULL; h = next) { + next = h->next; + if (h->ss.ss_family != AF_INET && + h->ss.ss_family != AF_INET6) { + yyerror("IPv4 or IPv6 address " + "or hostname expected"); + free(h); + free(p); + free($3->name); + free($3->path); + free($3); + YYERROR; + } + h->next = p->addr; + p->addr = h; + } + + p->addr_head.a = p->addr; + p->addr_head.pool = 0; + p->addr_head.name = strdup($3->name); + p->addr_head.path = strdup($3->path); + if (p->addr_head.name == NULL || + p->addr_head.path == NULL) + fatal(NULL); + if (p->addr != NULL) + p->state = STATE_DNS_DONE; + constraint_add(p); + free($3->name); + free($3); + } + | SENSOR STRING sensor_opts { + struct ntp_conf_sensor *s; + + s = new_sensor($2); + s->weight = $3.weight; + s->correction = $3.correction; + s->refstr = $3.refstr; + s->stratum = $3.stratum; + s->trusted = $3.trusted; + conf->trusted_sensors = conf->trusted_sensors || + $3.trusted; + free($2); + TAILQ_INSERT_TAIL(&conf->ntp_conf_sensors, s, entry); + } + ; + +address : STRING { + if (($$ = calloc(1, sizeof(struct ntp_addr_wrap))) == + NULL) + fatal(NULL); + host($1, &$$->a); + $$->name = $1; + } + ; + +urllist : urllist address { + struct ntp_addr *p, *q = NULL; + struct in_addr ina; + struct in6_addr in6a; + + if (inet_pton(AF_INET, $2->name, &ina) != 1 && + inet_pton(AF_INET6, $2->name, &in6a) != 1) { + yyerror("url can only be followed by IP " + "addresses"); + free($2->name); + free($2); + YYERROR; + } + p = $2->a; + while (p != NULL) { + q = p; + p = p->next; + } + if (q != NULL) { + q->next = $1->a; + $1->a = $2->a; + free($2); + } + $$ = $1; + } + | url { + $$ = $1; + } + ; + +url : STRING { + char *hname, *path; + + if (($$ = calloc(1, sizeof(struct ntp_addr_wrap))) == + NULL) + fatal("calloc"); + + if (strncmp("https://", $1, + strlen("https://")) != 0) { + host($1, &$$->a); + $$->name = $1; + } else { + hname = $1 + strlen("https://"); + + path = hname + strcspn(hname, "/\\"); + if (*path != '\0') { + if (($$->path = strdup(path)) == NULL) + fatal("strdup"); + *path = '\0'; + } + host(hname, &$$->a); + if (($$->name = strdup(hname)) == NULL) + fatal("strdup"); + } + if ($$->path == NULL && + ($$->path = strdup("/")) == NULL) + fatal("strdup"); + } + ; + +listen_opts : { opts_default(); } + listen_opts_l + { $$ = opts; } + | { opts_default(); $$ = opts; } + ; +listen_opts_l : listen_opts_l listen_opt + | listen_opt + ; +listen_opt : rtable + ; + +server_opts : { opts_default(); } + server_opts_l + { $$ = opts; } + | { opts_default(); $$ = opts; } + ; +server_opts_l : server_opts_l server_opt + | server_opt + ; +server_opt : weight + | trusted + ; + +sensor_opts : { opts_default(); } + sensor_opts_l + { $$ = opts; } + | { opts_default(); $$ = opts; } + ; +sensor_opts_l : sensor_opts_l sensor_opt + | sensor_opt + ; +sensor_opt : correction + | refid + | stratum + | weight + | trusted + ; + +correction : CORRECTION NUMBER { + if ($2 < -127000000 || $2 > 127000000) { + yyerror("correction must be between " + "-127000000 and 127000000 microseconds"); + YYERROR; + } + opts.correction = $2; + } + ; + +refid : REFID STRING { + size_t l = strlen($2); + + if (l < 1 || l > 4) { + yyerror("refid must be 1 to 4 characters"); + free($2); + YYERROR; + } + opts.refstr = $2; + } + ; + +stratum : STRATUM NUMBER { + if ($2 < 1 || $2 > 15) { + yyerror("stratum must be between " + "1 and 15"); + YYERROR; + } + opts.stratum = $2; + } + ; + +weight : WEIGHT NUMBER { + if ($2 < 1 || $2 > 10) { + yyerror("weight must be between 1 and 10"); + YYERROR; + } + opts.weight = $2; + } +rtable : RTABLE NUMBER { + if ($2 < 0 || $2 > RT_TABLEID_MAX) { + yyerror("rtable must be between 1" + " and RT_TABLEID_MAX"); + YYERROR; + } + opts.rtable = $2; + } + ; + +trusted : TRUSTED { + opts.trusted = 1; + } + +%% + +void +opts_default(void) +{ + memset(&opts, 0, sizeof opts); + opts.weight = 1; + opts.stratum = 1; +} + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + log_warnx("%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "constraint", CONSTRAINT}, + { "constraints", CONSTRAINTS}, + { "correction", CORRECTION}, + { "from", FROM}, + { "listen", LISTEN}, + { "on", ON}, + { "query", QUERY}, + { "refid", REFID}, + { "rtable", RTABLE}, + { "sensor", SENSOR}, + { "server", SERVER}, + { "servers", SERVERS}, + { "stratum", STRATUM}, + { "trusted", TRUSTED}, + { "weight", WEIGHT} + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = (unsigned char)parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return ((unsigned char)pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index + 1 >= MAXPUSHBACK) + return (EOF); + pushback_buffer[pushback_index++] = c; + return (c); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = (unsigned char)pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p; + int quotec, next, c; + int token; + + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + fatal("yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc((unsigned char)*--p); + c = (unsigned char)*--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '*') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + fatal("yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +struct file * +pushfile(const char *name) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("%s", __func__); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("%s", __func__); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s: %s", __func__, nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +int +parse_config(const char *filename, struct ntpd_conf *xconf) +{ + int errors = 0; + + conf = xconf; + TAILQ_INIT(&conf->listen_addrs); + TAILQ_INIT(&conf->ntp_peers); + TAILQ_INIT(&conf->ntp_conf_sensors); + TAILQ_INIT(&conf->constraints); + + if ((file = pushfile(filename)) == NULL) { + return (-1); + } + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + return (errors ? -1 : 0); +} diff --git a/sensors.c b/sensors.c new file mode 100644 index 0000000..ff0eec0 --- /dev/null +++ b/sensors.c @@ -0,0 +1,265 @@ +/* $OpenBSD: sensors.c,v 1.54 2019/11/11 06:32:52 otto Exp $ */ + +/* + * Copyright (c) 2006 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +#define MAXDEVNAMLEN 16 + +int sensor_probe(int, char *, struct sensor *); +void sensor_add(int, char *); +void sensor_remove(struct ntp_sensor *); +void sensor_update(struct ntp_sensor *); + +void +sensor_init(void) +{ + TAILQ_INIT(&conf->ntp_sensors); +} + +int +sensor_scan(void) +{ + int i, n, err; + char d[MAXDEVNAMLEN]; + struct sensor s; + + n = 0; + for (i = 0; ; i++) + if ((err = sensor_probe(i, d, &s))) { + if (err == 0) + continue; + if (err == -1) /* no further sensors */ + break; + sensor_add(i, d); + n++; + } + + return n; +} + +/* + * 1 = time sensor! + * 0 = sensor exists... but is not a time sensor + * -1: no sensor here, and no further sensors after this + */ +int +sensor_probe(int devid, char *dxname, struct sensor *sensor) +{ + int mib[5]; + size_t slen, sdlen; + struct sensordev sensordev; + + mib[0] = CTL_HW; + mib[1] = HW_SENSORS; + mib[2] = devid; + mib[3] = SENSOR_TIMEDELTA; + mib[4] = 0; + + sdlen = sizeof(sensordev); + if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { + if (errno == ENXIO) + return (0); + if (errno == ENOENT) + return (-1); + log_warn("sensor_probe sysctl"); + } + + if (sensordev.maxnumt[SENSOR_TIMEDELTA] == 0) + return (0); + + strlcpy(dxname, sensordev.xname, MAXDEVNAMLEN); + + slen = sizeof(*sensor); + if (sysctl(mib, 5, sensor, &slen, NULL, 0) == -1) { + if (errno != ENOENT) + log_warn("sensor_probe sysctl"); + return (0); + } + + return (1); +} + +void +sensor_add(int sensordev, char *dxname) +{ + struct ntp_sensor *s; + struct ntp_conf_sensor *cs; + + /* check whether it is already there */ + TAILQ_FOREACH(s, &conf->ntp_sensors, entry) + if (!strcmp(s->device, dxname)) + return; + + /* check whether it is requested in the config file */ + for (cs = TAILQ_FIRST(&conf->ntp_conf_sensors); cs != NULL && + strcmp(cs->device, dxname) && strcmp(cs->device, "*"); + cs = TAILQ_NEXT(cs, entry)) + ; /* nothing */ + if (cs == NULL) + return; + + if ((s = calloc(1, sizeof(*s))) == NULL) + fatal("sensor_add calloc"); + + s->next = getmonotime(); + s->weight = cs->weight; + s->correction = cs->correction; + s->stratum = cs->stratum - 1; + s->trusted = cs->trusted; + if ((s->device = strdup(dxname)) == NULL) + fatal("sensor_add strdup"); + s->sensordevid = sensordev; + + if (cs->refstr == NULL) + memcpy(&s->refid, SENSOR_DEFAULT_REFID, sizeof(s->refid)); + else { + s->refid = 0; + strncpy((char *)&s->refid, cs->refstr, sizeof(s->refid)); + } + + TAILQ_INSERT_TAIL(&conf->ntp_sensors, s, entry); + + log_debug("sensor %s added (weight %d, correction %.6f, refstr %.4u, " + "stratum %d)", s->device, s->weight, s->correction / 1e6, + s->refid, s->stratum); +} + +void +sensor_remove(struct ntp_sensor *s) +{ + TAILQ_REMOVE(&conf->ntp_sensors, s, entry); + free(s->device); + free(s); +} + +void +sensor_query(struct ntp_sensor *s) +{ + char dxname[MAXDEVNAMLEN]; + struct sensor sensor; + double sens_time; + + if (conf->settime) + s->next = getmonotime() + SENSOR_QUERY_INTERVAL_SETTIME; + else + s->next = getmonotime() + SENSOR_QUERY_INTERVAL; + + /* rcvd is walltime here, monotime in client.c. not used elsewhere */ + if (s->update.rcvd < time(NULL) - SENSOR_DATA_MAXAGE) + s->update.good = 0; + + if (!sensor_probe(s->sensordevid, dxname, &sensor)) { + sensor_remove(s); + return; + } + + if (sensor.flags & SENSOR_FINVALID || + sensor.status != SENSOR_S_OK) + return; + + if (strcmp(dxname, s->device)) { + sensor_remove(s); + return; + } + + if (sensor.tv.tv_sec == s->last) /* already seen */ + return; + + s->last = sensor.tv.tv_sec; + + if (!s->trusted && !TAILQ_EMPTY(&conf->constraints)) { + if (conf->constraint_median == 0) { + return; + } + sens_time = gettime() + (sensor.value / -1e9) + + (s->correction / 1e6); + if (constraint_check(sens_time) != 0) { + log_info("sensor %s: constraint check failed", s->device); + return; + } + } + /* + * TD = device time + * TS = system time + * sensor.value = TS - TD in ns + * if value is positive, system time is ahead + */ + s->offsets[s->shift].offset = (sensor.value / -1e9) - getoffset() + + (s->correction / 1e6); + s->offsets[s->shift].rcvd = sensor.tv.tv_sec; + s->offsets[s->shift].good = 1; + + s->offsets[s->shift].status.send_refid = s->refid; + /* stratum increased when sent out */ + s->offsets[s->shift].status.stratum = s->stratum; + s->offsets[s->shift].status.rootdelay = 0; + s->offsets[s->shift].status.rootdispersion = 0; + s->offsets[s->shift].status.reftime = sensor.tv.tv_sec; + s->offsets[s->shift].status.synced = 1; + + log_debug("sensor %s: offset %f", s->device, + s->offsets[s->shift].offset); + + if (++s->shift >= SENSOR_OFFSETS) { + s->shift = 0; + sensor_update(s); + } + +} + +void +sensor_update(struct ntp_sensor *s) +{ + struct ntp_offset **offsets; + int i; + + if ((offsets = calloc(SENSOR_OFFSETS, sizeof(struct ntp_offset *))) == + NULL) + fatal("calloc sensor_update"); + + for (i = 0; i < SENSOR_OFFSETS; i++) + offsets[i] = &s->offsets[i]; + + qsort(offsets, SENSOR_OFFSETS, sizeof(struct ntp_offset *), + offset_compare); + + i = SENSOR_OFFSETS / 2; + memcpy(&s->update, offsets[i], sizeof(s->update)); + if (SENSOR_OFFSETS % 2 == 0) { + s->update.offset = + (offsets[i - 1]->offset + offsets[i]->offset) / 2; + } + free(offsets); + + log_debug("sensor update %s: offset %f", s->device, s->update.offset); + priv_adjtime(); +} diff --git a/server.c b/server.c new file mode 100644 index 0000000..123b693 --- /dev/null +++ b/server.c @@ -0,0 +1,203 @@ +/* $OpenBSD: server.c,v 1.44 2016/09/03 11:52:06 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * Copyright (c) 2004 Alexander Guy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +int +setup_listeners(struct servent *se, struct ntpd_conf *lconf, u_int *cnt) +{ + struct listen_addr *la, *nla, *lap; + struct ifaddrs *ifa, *ifap; + struct sockaddr *sa; + struct if_data *ifd; + u_int8_t *a6; + size_t sa6len = sizeof(struct in6_addr); + u_int new_cnt = 0; + int tos = IPTOS_LOWDELAY, rdomain = 0; + + TAILQ_FOREACH(lap, &lconf->listen_addrs, entry) { + switch (lap->sa.ss_family) { + case AF_UNSPEC: + if (getifaddrs(&ifa) == -1) + fatal("getifaddrs"); + + for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next) { + sa = ifap->ifa_addr; + if (sa == NULL || SA_LEN(sa) == 0) + continue; + if (sa->sa_family == AF_LINK) { + ifd = ifap->ifa_data; + rdomain = ifd->ifi_rdomain; + } + if (sa->sa_family != AF_INET && + sa->sa_family != AF_INET6) + continue; + if (lap->rtable != -1 && rdomain != lap->rtable) + continue; + + if (sa->sa_family == AF_INET && + ((struct sockaddr_in *)sa)->sin_addr.s_addr == + INADDR_ANY) + continue; + + if (sa->sa_family == AF_INET6) { + a6 = ((struct sockaddr_in6 *)sa)-> + sin6_addr.s6_addr; + if (memcmp(a6, &in6addr_any, sa6len) == 0) + continue; + } + + if ((la = calloc(1, sizeof(struct listen_addr))) == + NULL) + fatal("setup_listeners calloc"); + + memcpy(&la->sa, sa, SA_LEN(sa)); + la->rtable = rdomain; + + TAILQ_INSERT_TAIL(&lconf->listen_addrs, la, entry); + } + + freeifaddrs(ifa); + default: + continue; + } + } + + + for (la = TAILQ_FIRST(&lconf->listen_addrs); la; ) { + switch (la->sa.ss_family) { + case AF_INET: + if (((struct sockaddr_in *)&la->sa)->sin_port == 0) + ((struct sockaddr_in *)&la->sa)->sin_port = + se->s_port; + break; + case AF_INET6: + if (((struct sockaddr_in6 *)&la->sa)->sin6_port == 0) + ((struct sockaddr_in6 *)&la->sa)->sin6_port = + se->s_port; + break; + case AF_UNSPEC: + nla = TAILQ_NEXT(la, entry); + TAILQ_REMOVE(&lconf->listen_addrs, la, entry); + free(la); + la = nla; + continue; + default: + fatalx("king bula sez: af borked"); + } + + log_info("listening on %s %s", + log_sockaddr((struct sockaddr *)&la->sa), + print_rtable(la->rtable)); + + if ((la->fd = socket(la->sa.ss_family, SOCK_DGRAM, 0)) == -1) + fatal("socket"); + + if (la->sa.ss_family == AF_INET && setsockopt(la->fd, + IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) + log_warn("setsockopt IPTOS_LOWDELAY"); + + if (la->rtable != -1 && + setsockopt(la->fd, SOL_SOCKET, SO_RTABLE, &la->rtable, + sizeof(la->rtable)) == -1) + fatal("setup_listeners setsockopt SO_RTABLE"); + + if (bind(la->fd, (struct sockaddr *)&la->sa, + SA_LEN((struct sockaddr *)&la->sa)) == -1) { + log_warn("bind on %s failed, skipping", + log_sockaddr((struct sockaddr *)&la->sa)); + close(la->fd); + nla = TAILQ_NEXT(la, entry); + TAILQ_REMOVE(&lconf->listen_addrs, la, entry); + free(la); + la = nla; + continue; + } + new_cnt++; + la = TAILQ_NEXT(la, entry); + } + + *cnt = new_cnt; + + return (0); +} + +int +server_dispatch(int fd, struct ntpd_conf *lconf) +{ + ssize_t size; + double rectime; + struct sockaddr_storage fsa; + socklen_t fsa_len; + struct ntp_msg query, reply; + char buf[NTP_MSGSIZE]; + + fsa_len = sizeof(fsa); + if ((size = recvfrom(fd, &buf, sizeof(buf), 0, + (struct sockaddr *)&fsa, &fsa_len)) == -1) { + if (errno == EHOSTUNREACH || errno == EHOSTDOWN || + errno == ENETUNREACH || errno == ENETDOWN) { + log_warn("recvfrom %s", + log_sockaddr((struct sockaddr *)&fsa)); + return (0); + } else + fatal("recvfrom"); + } + + rectime = gettime_corrected(); + + if (ntp_getmsg((struct sockaddr *)&fsa, buf, size, &query) == -1) + return (0); + + memset(&reply, 0, sizeof(reply)); + if (lconf->status.synced) + reply.status = lconf->status.leap; + else + reply.status = LI_ALARM; + reply.status |= (query.status & VERSIONMASK); + if ((query.status & MODEMASK) == MODE_CLIENT) + reply.status |= MODE_SERVER; + else if ((query.status & MODEMASK) == MODE_SYM_ACT) + reply.status |= MODE_SYM_PAS; + else /* ignore packets of different type (e.g. bcast) */ + return (0); + + reply.stratum = lconf->status.stratum; + reply.ppoll = query.ppoll; + reply.precision = lconf->status.precision; + reply.rectime = d_to_lfp(rectime); + reply.reftime = d_to_lfp(lconf->status.reftime); + reply.xmttime = d_to_lfp(gettime_corrected()); + reply.orgtime = query.xmttime; + reply.rootdelay = d_to_sfp(lconf->status.rootdelay); + reply.refid = lconf->status.refid; + + ntp_sendmsg(fd, (struct sockaddr *)&fsa, &reply); + return (0); +} diff --git a/util.c b/util.c new file mode 100644 index 0000000..d837a9e --- /dev/null +++ b/util.c @@ -0,0 +1,253 @@ +/* $OpenBSD: util.c,v 1.28 2023/12/20 15:36:36 otto Exp $ */ + +/* + * Copyright (c) 2004 Alexander Guy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +double +gettime_corrected(void) +{ + return (gettime() + getoffset()); +} + +double +getoffset(void) +{ + struct timeval tv; + if (adjtime(NULL, &tv) == -1) + return (0.0); + return (tv.tv_sec + 1.0e-6 * tv.tv_usec); +} + +double +gettime(void) +{ + struct timeval tv; + + if (gettimeofday(&tv, NULL) == -1) + fatal("gettimeofday"); + + return (gettime_from_timeval(&tv)); +} + +double +gettime_from_timeval(struct timeval *tv) +{ + /* + * Account for overflow on OSes that have a 32-bit time_t. + */ + return ((uint64_t)tv->tv_sec + JAN_1970 + 1.0e-6 * tv->tv_usec); +} + +time_t +getmonotime(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + fatal("clock_gettime"); + + return (ts.tv_sec); +} + + +void +d_to_tv(double d, struct timeval *tv) +{ + tv->tv_sec = d; + tv->tv_usec = (d - tv->tv_sec) * 1000000; + while (tv->tv_usec < 0) { + tv->tv_usec += 1000000; + tv->tv_sec -= 1; + } +} + +double +lfp_to_d(struct l_fixedpt lfp) +{ + double base, ret; + + lfp.int_partl = ntohl(lfp.int_partl); + lfp.fractionl = ntohl(lfp.fractionl); + + /* see comment in ntp.h */ + base = NTP_ERA; + if (lfp.int_partl <= INT32_MAX) + base++; + ret = base * SECS_IN_ERA; + ret += (double)(lfp.int_partl) + ((double)lfp.fractionl / L_DENOMINATOR); + + return (ret); +} + +struct l_fixedpt +d_to_lfp(double d) +{ + struct l_fixedpt lfp; + + while (d > SECS_IN_ERA) + d -= SECS_IN_ERA; + lfp.int_partl = htonl((u_int32_t)d); + lfp.fractionl = htonl((u_int32_t)((d - (u_int32_t)d) * L_DENOMINATOR)); + + return (lfp); +} + +double +sfp_to_d(struct s_fixedpt sfp) +{ + double ret; + + sfp.int_parts = ntohs(sfp.int_parts); + sfp.fractions = ntohs(sfp.fractions); + + ret = (double)(sfp.int_parts) + ((double)sfp.fractions / S_DENOMINATOR); + + return (ret); +} + +struct s_fixedpt +d_to_sfp(double d) +{ + struct s_fixedpt sfp; + + sfp.int_parts = htons((u_int16_t)d); + sfp.fractions = htons((u_int16_t)((d - (u_int16_t)d) * S_DENOMINATOR)); + + return (sfp); +} + +char * +print_rtable(int r) +{ + static char b[11]; + + b[0] = 0; + if (r > 0) + snprintf(b, sizeof(b), "rtable %d", r); + + return (b); +} + +const char * +log_sockaddr(struct sockaddr *sa) +{ + static char buf[NI_MAXHOST]; + + if (getnameinfo(sa, SA_LEN(sa), buf, sizeof(buf), NULL, 0, + NI_NUMERICHOST)) + return ("(unknown)"); + else + return (buf); +} + +const char * +log_ntp_addr(struct ntp_addr *addr) +{ + if (addr == NULL) + return ("(unknown)"); + return log_sockaddr((struct sockaddr *)&addr->ss); +} + +pid_t +start_child(char *pname, int cfd, int argc, char **argv) +{ + char **nargv; + int nargc, i; + pid_t pid; + + /* Prepare the child process new argv. */ + nargv = calloc(argc + 3, sizeof(char *)); + if (nargv == NULL) + fatal("%s: calloc", __func__); + + /* Copy the program name first. */ + nargc = 0; + nargv[nargc++] = argv[0]; + + /* Set the process name and copy the original args. */ + nargv[nargc++] = "-P"; + nargv[nargc++] = pname; + for (i = 1; i < argc; i++) + nargv[nargc++] = argv[i]; + + nargv[nargc] = NULL; + + switch (pid = fork()) { + case -1: + fatal("%s: fork", __func__); + break; + case 0: + /* Prepare the parent socket and execute. */ + if (cfd != PARENT_SOCK_FILENO) { + if (dup2(cfd, PARENT_SOCK_FILENO) == -1) + fatal("dup2"); + } else if (fcntl(cfd, F_SETFD, 0) == -1) + fatal("fcntl"); + + execvp(argv[0], nargv); + fatal("%s: execvp", __func__); + break; + + default: + /* Close child's socket end. */ + close(cfd); + break; + } + + free(nargv); + return (pid); +} + +int +sanitize_argv(int *argc, char ***argv) +{ + char **nargv; + int nargc; + int i; + + /* + * We need at least three arguments: + * Example: '/usr/sbin/ntpd' '-P' 'foobar'. + */ + if (*argc < 3) + return (-1); + + *argc -= 2; + + /* Allocate new arguments vector and copy pointers. */ + nargv = calloc((*argc) + 1, sizeof(char *)); + if (nargv == NULL) + return (-1); + + nargc = 0; + nargv[nargc++] = (*argv)[0]; + for (i = 1; i < *argc; i++) + nargv[nargc++] = (*argv)[i + 2]; + + nargv[nargc] = NULL; + *argv = nargv; + return (0); +}