commit 2f4aa1c4125a9208b7543016eeecd9469943163f Author: Wolfgang Hottgenroth Date: Fri Feb 14 12:20:20 2025 +0100 initial, unchanged sources from openbsd 7.6 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); +}