From 7433fa0bcef27966a04cc38d63fcc709090fc9f5 Mon Sep 17 00:00:00 2001 From: reyk <> Date: Tue, 10 Feb 2015 06:40:08 +0000 Subject: [PATCH] Add support for "constraints": when configured, ntpd(8) will query the time from HTTPS servers, by parsing the Date: header, and use the median constraint time as a boundary to verify NTP responses. This adds some level of authentication and protection against MITM attacks while preserving the accuracy of the NTP protocol; without relying on authentication options for NTP that are basically unavailable at present. This is an initial implementation and the semantics will be improved once it is in the tree. Discussed with deraadt@ and henning@ OK henning@ --- src/etc/examples/ntpd.conf | 5 +- src/usr.sbin/ntpd/Makefile | 8 +- src/usr.sbin/ntpd/client.c | 11 +- src/usr.sbin/ntpd/config.c | 16 +- src/usr.sbin/ntpd/constraint.c | 659 +++++++++++++++++++++++++++++++++ src/usr.sbin/ntpd/control.c | 5 +- src/usr.sbin/ntpd/ntp.c | 63 +++- src/usr.sbin/ntpd/ntpctl.8 | 5 +- src/usr.sbin/ntpd/ntpd.c | 15 +- src/usr.sbin/ntpd/ntpd.conf.5 | 38 +- src/usr.sbin/ntpd/ntpd.h | 50 ++- src/usr.sbin/ntpd/parse.y | 125 ++++++- src/usr.sbin/ntpd/util.c | 8 +- 13 files changed, 981 insertions(+), 27 deletions(-) create mode 100644 src/usr.sbin/ntpd/constraint.c diff --git a/src/etc/examples/ntpd.conf b/src/etc/examples/ntpd.conf index 7fcbbed0..2446fc75 100644 --- a/src/etc/examples/ntpd.conf +++ b/src/etc/examples/ntpd.conf @@ -1,4 +1,4 @@ -# $OpenBSD: ntpd.conf,v 1.1 2014/07/13 13:53:36 rpe Exp $ +# $OpenBSD: ntpd.conf,v 1.2 2015/02/10 06:40:08 reyk Exp $ # sample ntpd configuration file, see ntpd.conf(5) # Addresses to listen on (ntpd does not listen by default) @@ -16,3 +16,6 @@ servers pool.ntp.org # use all detected timedelta sensors #sensor * + +# get the time constraint from a well-known HTTPS site +#constraints from "https://www.google.com/search?q=openntpd" diff --git a/src/usr.sbin/ntpd/Makefile b/src/usr.sbin/ntpd/Makefile index e77935f8..0025aae4 100644 --- a/src/usr.sbin/ntpd/Makefile +++ b/src/usr.sbin/ntpd/Makefile @@ -1,17 +1,17 @@ -# $OpenBSD: Makefile,v 1.12 2013/10/04 14:28:15 phessler Exp $ +# $OpenBSD: Makefile,v 1.13 2015/02/10 06:40:08 reyk 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 + control.c constraint.c CFLAGS+= -Wall -I${.CURDIR} CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes CFLAGS+= -Wmissing-declarations CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual CFLAGS+= -Wsign-compare YFLAGS= -LDADD+= -lutil -DPADD+= ${LIBUTIL} +LDADD+= -lutil -lcrypto -lssl -ltls +DPADD+= ${LIBUTIL} ${LIBCRYPTO} ${LIBSSL} ${LIBTLS} LINKS= ${BINDIR}/ntpd ${BINDIR}/ntpctl MAN= ntpd.8 ntpd.conf.5 ntpctl.8 diff --git a/src/usr.sbin/ntpd/client.c b/src/usr.sbin/ntpd/client.c index 06c41fda..06c5e924 100644 --- a/src/usr.sbin/ntpd/client.c +++ b/src/usr.sbin/ntpd/client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: client.c,v 1.98 2015/01/19 20:47:03 bcook Exp $ */ +/* $OpenBSD: client.c,v 1.99 2015/02/10 06:40:08 reyk Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -324,6 +324,15 @@ client_dispatch(struct ntp_peer *p, u_int8_t settime) return (0); } + /* Detect liars */ + if (conf->constraint_median != 0 && + (constraint_check(T2) != 0 || constraint_check(T3) != 0)) { + log_info("reply from %s: constraint check failed", + log_sockaddr((struct sockaddr *)&p->addr->ss)); + set_next(p, error_interval()); + return (0); + } + p->reply[p->shift].offset = ((T2 - T1) + (T3 - T4)) / 2; p->reply[p->shift].delay = (T4 - T1) - (T3 - T2); p->reply[p->shift].status.stratum = msg.stratum; diff --git a/src/usr.sbin/ntpd/config.c b/src/usr.sbin/ntpd/config.c index e2443e65..35d7b31f 100644 --- a/src/usr.sbin/ntpd/config.c +++ b/src/usr.sbin/ntpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.25 2015/02/08 04:54:15 reyk Exp $ */ +/* $OpenBSD: config.c,v 1.26 2015/02/10 06:40:08 reyk Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -33,6 +33,7 @@ struct ntp_addr *host_v4(const char *); struct ntp_addr *host_v6(const char *); static u_int32_t maxid = 0; +static u_int32_t constraint_maxid = 0; void host(const char *s, struct ntp_addr **hn) @@ -193,3 +194,16 @@ new_sensor(char *device) 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; + + return (p); +} + diff --git a/src/usr.sbin/ntpd/constraint.c b/src/usr.sbin/ntpd/constraint.c new file mode 100644 index 00000000..4a6265cf --- /dev/null +++ b/src/usr.sbin/ntpd/constraint.c @@ -0,0 +1,659 @@ +/* $OpenBSD: constraint.c,v 1.1 2015/02/10 06:40:08 reyk 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 "log.h" +#include "ntpd.h" + +int constraint_addr_init(struct constraint *); +struct constraint * + constraint_byfd(int); +struct constraint * + constraint_bypid(pid_t); +int constraint_close(int); +void constraint_update(void); +void constraint_reset(void); +int constraint_cmp(const void *, const void *); + +struct httpsdate * + httpsdate_init(const char *, const char *, const char *, + const char *, const u_int8_t *, size_t); +void httpsdate_free(void *); +int httpsdate_request(struct httpsdate *, struct timeval *); +void *httpsdate_query(const char *, const char *, const char *, + const char *, const u_int8_t *, size_t, + struct timeval *, struct timeval *); + +char *tls_readline(struct tls *, size_t *, size_t *, struct timeval *); + +struct httpsdate { + char *tls_host; + char *tls_port; + char *tls_name; + 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; + + 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; + int cnt = 0; + + for (h = cstr->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(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 */ + } + cnt++; + } + + return (cnt); +} + +int +constraint_query(struct constraint *cstr) +{ + int pipes[2]; + struct timeval rectv, xmttv; + void *ctx; + static char hname[NI_MAXHOST]; + time_t now; + struct iovec iov[2]; + + now = getmonotime(); + if (cstr->state >= STATE_REPLY_RECEIVED) { + if (cstr->last + CONSTRAINT_SCAN_INTERVAL > now) { + /* Nothing to do */ + return (-1); + } + + /* Reset */ + cstr->senderrors = 0; + constraint_close(cstr->fd); + } else if (cstr->state == 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 */ + kill(cstr->pid, SIGTERM); + return (-1); + } + + cstr->last = now; + if (getnameinfo((struct sockaddr *)&cstr->addr->ss, + SA_LEN((struct sockaddr *)&cstr->addr->ss), + hname, sizeof(hname), NULL, 0, + NI_NUMERICHOST) != 0) + fatalx("%s getnameinfo %s", __func__, cstr->addr_head.name); + + log_debug("constraint request to %s", hname); + + if (socketpair(AF_UNIX, SOCK_DGRAM, AF_UNSPEC, pipes) == -1) + fatal("%s pipes", __func__); + + /* Fork child handlers */ + switch (cstr->pid = fork()) { + case -1: + cstr->senderrors++; + close(pipes[0]); + close(pipes[1]); + return (-1); + case 0: + tzset(); + log_init(conf->debug); + + setproctitle("constraint from %s", hname); + + /* Child process */ + if (dup2(pipes[1], CONSTRAINT_PASSFD) == -1) + fatal("%s dup2 CONSTRAINT_PASSFD", __func__); + if (pipes[0] != CONSTRAINT_PASSFD) + close(pipes[0]); + if (pipes[1] != CONSTRAINT_PASSFD) + close(pipes[1]); + (void)closefrom(CONSTRAINT_PASSFD + 1); + + if (fcntl(CONSTRAINT_PASSFD, F_SETFD, FD_CLOEXEC) == -1) + fatal("%s fcntl F_SETFD", __func__); + + cstr->fd = CONSTRAINT_PASSFD; + imsg_init(&cstr->ibuf, cstr->fd); + + if ((ctx = httpsdate_query(hname, + CONSTRAINT_PORT, cstr->addr_head.name, cstr->addr_head.path, + conf->ca, conf->ca_len, &rectv, &xmttv)) == 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, 0, 0, -1, iov, 2); + imsg_flush(&cstr->ibuf); + + /* Tear down the TLS connection after sending the result */ + httpsdate_free(ctx); + + _exit(0); + /* NOTREACHED */ + default: + /* Parent */ + close(pipes[1]); + cstr->fd = pipes[0]; + cstr->state = STATE_QUERY_SENT; + + imsg_init(&cstr->ibuf, cstr->fd); + break; + } + + + return (0); +} + +void +constraint_check_child(void) +{ + struct constraint *cstr; + int status; + int fail; + char *cause; + pid_t pid; + + do { + pid = waitpid(WAIT_ANY, &status, WNOHANG); + if (pid <= 0) + continue; + + fail = 0; + if (WIFSIGNALED(status)) { + fail = 1; + asprintf(&cause, "terminated; signal %d", + WTERMSIG(status)); + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + fail = 1; + asprintf(&cause, "exited abnormally"); + } else + asprintf(&cause, "exited okay"); + } else + fatalx("unexpected cause of SIGCHLD"); + + if ((cstr = constraint_bypid(pid)) != NULL) { + if (fail) { + log_info("no constraint reply from %s" + " received in time, next query %ds", + log_sockaddr((struct sockaddr *) + &cstr->addr->ss), CONSTRAINT_SCAN_INTERVAL); + } + + if (fail || cstr->state < STATE_REPLY_RECEIVED) { + cstr->senderrors++; + constraint_close(cstr->fd); + } + } + free(cause); + } while (pid > 0 || (pid == -1 && errno == EINTR)); +} + +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(int fd) +{ + struct constraint *cstr; + + if ((cstr = constraint_byfd(fd)) == NULL) { + log_warn("%s: fd %d: not found", __func__, fd); + return (0); + } + + msgbuf_clear(&cstr->ibuf.w); + close(cstr->fd); + cstr->fd = -1; + if (cstr->senderrors) + cstr->state = STATE_INVALID; + else if (cstr->state >= STATE_QUERY_SENT) + cstr->state = STATE_DNS_DONE; + + cstr->last = getmonotime(); + + return (1); +} + +int +constraint_dispatch_msg(struct pollfd *pfd) +{ + struct imsg imsg; + struct constraint *cstr; + ssize_t n; + struct timeval tv[2]; + double offset; + + if ((cstr = constraint_byfd(pfd->fd)) == NULL) + return (0); + + if (!(pfd->revents & POLLIN)) + return (0); + + if ((n = imsg_read(&cstr->ibuf)) == -1 || n == 0) { + constraint_close(pfd->fd); + return (1); + } + + for (;;) { + if ((n = imsg_get(&cstr->ibuf, &imsg)) == -1) { + constraint_close(pfd->fd); + return (1); + } + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CONSTRAINT: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(tv)) + fatalx("invalid IMSG_CONSTRAINT received"); + + memcpy(tv, imsg.data, sizeof(tv)); + + offset = gettime_from_timeval(&tv[0]) - + gettime_from_timeval(&tv[1]); + + log_info("constraint reply from %s: offset %f", + log_sockaddr((struct sockaddr *)&cstr->addr->ss), + offset); + + cstr->state = STATE_REPLY_RECEIVED; + cstr->last = getmonotime(); + cstr->constraint = tv[0].tv_sec; + + constraint_update(); + break; + default: + break; + } + imsg_free(&imsg); + } + + return (0); +} + +int +constraint_cmp(const void *a, const void *b) +{ + return (*(const time_t *)a - *(const time_t *)b); +} + +void +constraint_update(void) +{ + struct constraint *cstr; + int cnt, i; + time_t *sum; + time_t now; + + now = getmonotime(); + + cnt = 0; + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (cstr->state != STATE_REPLY_RECEIVED) + continue; + cnt++; + } + + if ((sum = calloc(cnt, sizeof(time_t))) == NULL) + fatal("calloc"); + + i = 0; + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (cstr->state != STATE_REPLY_RECEIVED) + continue; + sum[i++] = cstr->constraint + (now - cstr->last); + } + + qsort(sum, cnt, sizeof(time_t), constraint_cmp); + + /* calculate median */ + i = cnt / 2; + if (cnt % 2 == 0) + if (sum[i - 1] < sum[i]) + i -= 1; + + conf->constraint_last = now; + conf->constraint_median = sum[i]; + + free(sum); +} + +void +constraint_reset(void) +{ + struct constraint *cstr; + + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (cstr->state == STATE_QUERY_SENT) + continue; + constraint_close(cstr->fd); + } + conf->constraint_errors = 0; +} + +int +constraint_check(double val) +{ + struct timeval tv; + double constraint; + 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; + constraint = gettime_from_timeval(&tv); + + if (((val - constraint) > CONSTRAINT_MARGIN) || + ((constraint - val) > CONSTRAINT_MARGIN)) { + /* XXX get new constraint if too many errors happened */ + if (conf->constraint_errors++ > CONSTRAINT_ERROR_MARGIN) { + constraint_reset(); + } + + return (-1); + } + + return (0); +} + +struct httpsdate * +httpsdate_init(const char *hname, const char *port, const char *name, + const char *path, const u_int8_t *ca, size_t ca_len) +{ + struct httpsdate *httpsdate = NULL; + + if (tls_init() == -1) + return (NULL); + + if ((httpsdate = calloc(1, sizeof(*httpsdate))) == NULL) + goto fail; + + if (name == NULL) + name = hname; + + if ((httpsdate->tls_host = strdup(hname)) == NULL || + (httpsdate->tls_port = strdup(port)) == NULL || + (httpsdate->tls_name = strdup(name)) == 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_name) == -1) + goto fail; + + if ((httpsdate->tls_config = tls_config_new()) == NULL) + goto fail; + + /* XXX we have to pre-resolve, so name and host are not equal */ + tls_config_insecure_noverifyhost(httpsdate->tls_config); + + if (ca == NULL || ca_len == 0) + tls_config_insecure_noverifycert(httpsdate->tls_config); + else + tls_config_set_ca_mem(httpsdate->tls_config, ca, ca_len); + + 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_host); + free(httpsdate->tls_port); + free(httpsdate->tls_name); + free(httpsdate->tls_path); + free(httpsdate->tls_request); + free(httpsdate); +} + +int +httpsdate_request(struct httpsdate *httpsdate, struct timeval *when) +{ + size_t outlen = 0, maxlength = CONSTRAINT_MAXHEADERLENGTH; + char *line, *p; + + if ((httpsdate->tls_ctx = tls_client()) == NULL) + goto fail; + + if (tls_configure(httpsdate->tls_ctx, httpsdate->tls_config) == -1) + goto fail; + + if (tls_connect(httpsdate->tls_ctx, + httpsdate->tls_host, httpsdate->tls_port) == -1) { + log_warnx("tls failed: %s: %s", httpsdate->tls_host, + tls_error(httpsdate->tls_ctx)); + goto fail; + } + + if (tls_write(httpsdate->tls_ctx, + httpsdate->tls_request, strlen(httpsdate->tls_request), + &outlen) == -1) + goto fail; + + 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, "%a, %d %h %Y %T %Z", + &httpsdate->tls_tm) == NULL) { + log_warnx("unsupported date format"); + free(line); + return (-1); + } + + free(line); + break; + next: + free(line); + } + + + return (0); + fail: + httpsdate_free(httpsdate); + return (-1); +} + +void * +httpsdate_query(const char *hname, const char *port, const char *name, + const char *path, const u_int8_t *ca, size_t ca_len, + struct timeval *rectv, struct timeval *xmttv) +{ + struct httpsdate *httpsdate; + struct timeval when; + time_t t; + + if ((httpsdate = httpsdate_init(hname, port, name, path, + ca, ca_len)) == NULL) + return (NULL); + + if (httpsdate_request(httpsdate, &when) == -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, nr; + char *buf, *q, c; + int 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, &nr); + if (ret == TLS_READ_AGAIN) + goto again; + if (ret != 0) { + /* SSL read error, ignore */ + return (NULL); + } + + if (maxlength != NULL && (*maxlength)-- == 0) { + log_warnx("maximum length exceeded"); + return (NULL); + } + + buf[i] = c; + if (c == '\n') + break; + } + *lenp = i; + if (gettimeofday(when, NULL) == -1) + fatal("gettimeofday"); + return (buf); +} diff --git a/src/usr.sbin/ntpd/control.c b/src/usr.sbin/ntpd/control.c index e6d91bf3..867f9112 100644 --- a/src/usr.sbin/ntpd/control.c +++ b/src/usr.sbin/ntpd/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.4 2015/01/09 07:35:37 deraadt Exp $ */ +/* $OpenBSD: control.c,v 1.5 2015/02/10 06:40:08 reyk Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -309,6 +309,9 @@ build_show_status(struct ctl_show_status *cs) cs->synced = conf->status.synced; cs->stratum = conf->status.stratum; cs->clock_offset = getoffset() * 1000.0; + cs->constraint_median = conf->constraint_median; + cs->constraint_last = conf->constraint_last; + cs->constraint_errors = conf->constraint_errors; } void diff --git a/src/usr.sbin/ntpd/ntp.c b/src/usr.sbin/ntpd/ntp.c index 5a4e336a..76629af5 100644 --- a/src/usr.sbin/ntpd/ntp.c +++ b/src/usr.sbin/ntpd/ntp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ntp.c,v 1.127 2015/02/10 06:03:43 bcook Exp $ */ +/* $OpenBSD: ntp.c,v 1.128 2015/02/10 06:40:08 reyk Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -30,6 +30,7 @@ #include #include #include +#include #include "ntpd.h" @@ -41,6 +42,7 @@ volatile sig_atomic_t ntp_quit = 0; volatile sig_atomic_t ntp_report = 0; +volatile sig_atomic_t ntp_sigchld = 0; struct imsgbuf *ibuf_main; struct imsgbuf *ibuf_dns; struct ntpd_conf *conf; @@ -67,6 +69,9 @@ ntp_sighdlr(int sig) case SIGINFO: ntp_report = 1; break; + case SIGCHLD: + ntp_sigchld = 1; + break; } } @@ -76,9 +81,10 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, { int a, b, nfds, i, j, idx_peers, timeout; int hotplugfd, nullfd, pipe_dns[2], idx_clients; + int ctls, boundaries; u_int pfd_elms = 0, idx2peer_elms = 0; u_int listener_cnt, new_cnt, sent_cnt, trial_cnt; - u_int ctl_cnt; + u_int ctl_cnt, constraint_cnt; pid_t pid; struct pollfd *pfd = NULL; struct servent *se; @@ -86,10 +92,11 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, 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; + time_t nextaction, last_sensor_scan = 0, now; void *newp; switch (pid = fork()) { @@ -102,6 +109,13 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, return (pid); } + tls_init(); + + /* Verification will be turned off if CA is not found */ + if ((conf->ca = tls_load_file(CONSTRAINT_CA, + &conf->ca_len, NULL)) == NULL) + log_warnx("constraint certificate verification turned off"); + /* in this case the parent didn't init logging and didn't daemonize */ if (nconf->settime && !nconf->debug) { log_init(nconf->debug); @@ -157,7 +171,7 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, signal(SIGINFO, ntp_sighdlr); signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); - signal(SIGCHLD, SIG_DFL); + signal(SIGCHLD, ntp_sighdlr); if ((ibuf_main = malloc(sizeof(struct imsgbuf))) == NULL) fatal(NULL); @@ -166,6 +180,12 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, 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); @@ -213,7 +233,8 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, idx2peer_elms = peer_cnt; } - new_cnt = PFD_MAX + peer_cnt + listener_cnt + ctl_cnt; + new_cnt = PFD_MAX + + peer_cnt + listener_cnt + ctl_cnt + constraint_cnt; if (new_cnt > pfd_elms) { if ((newp = reallocarray(pfd, new_cnt, sizeof(*pfd))) == NULL) { @@ -248,6 +269,9 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, idx_peers = i; sent_cnt = trial_cnt = 0; TAILQ_FOREACH(p, &conf->ntp_peers, entry) { + if (constraint_cnt && conf->constraint_median == 0) + continue; + if (p->next > 0 && p->next <= getmonotime()) { if (p->state > STATE_DNS_INPROGRESS) trial_cnt++; @@ -326,8 +350,23 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, pfd[i].events |= POLLOUT; i++; } + ctls = i; - timeout = nextaction - getmonotime(); + boundaries = 0; + TAILQ_FOREACH(cstr, &conf->constraints, entry) { + if (constraint_query(cstr) == -1) + continue; + pfd[i].fd = cstr->fd; + pfd[i].events = POLLIN; + i++; + } + boundaries = i; + + now = getmonotime(); + if (constraint_cnt) + nextaction = now + 1; + + timeout = nextaction - now; if (timeout < 0) timeout = 0; @@ -389,8 +428,13 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, } } - for (; nfds > 0 && j < i; j++) + for (; nfds > 0 && j < ctls; j++) { nfds -= control_dispatch_msg(&pfd[j], &ctl_cnt); + } + + for (; nfds > 0 && j < i; j++) { + nfds -= constraint_dispatch_msg(&pfd[j]); + } for (s = TAILQ_FIRST(&conf->ntp_sensors); s != NULL; s = next_s) { @@ -400,6 +444,11 @@ ntp_main(int pipe_prnt[2], int fd_ctl, struct ntpd_conf *nconf, } report_peers(ntp_report); ntp_report = 0; + + if (ntp_sigchld) { + constraint_check_child(); + ntp_sigchld = 0; + } } msgbuf_write(&ibuf_main->w); diff --git a/src/usr.sbin/ntpd/ntpctl.8 b/src/usr.sbin/ntpd/ntpctl.8 index dcf0088f..b09c3310 100644 --- a/src/usr.sbin/ntpd/ntpctl.8 +++ b/src/usr.sbin/ntpd/ntpctl.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ntpctl.8,v 1.6 2014/01/22 02:55:15 benno Exp $ +.\" $OpenBSD: ntpctl.8,v 1.7 2015/02/10 06:40:08 reyk Exp $ .\" .\" Copyright (c) 2012 Mike Miller .\" @@ -14,7 +14,7 @@ .\" 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: January 22 2014 $ +.Dd $Mdocdate: February 10 2015 $ .Dt NTPCTL 8 .Os .Sh NAME @@ -53,6 +53,7 @@ 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 diff --git a/src/usr.sbin/ntpd/ntpd.c b/src/usr.sbin/ntpd/ntpd.c index c22e5b73..622fd223 100644 --- a/src/usr.sbin/ntpd/ntpd.c +++ b/src/usr.sbin/ntpd/ntpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ntpd.c,v 1.88 2015/01/21 03:14:10 bcook Exp $ */ +/* $OpenBSD: ntpd.c,v 1.89 2015/02/10 06:40:08 reyk Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -709,6 +709,7 @@ 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"); @@ -723,6 +724,18 @@ show_status_msg(struct imsg *imsg) 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; + printf("constraint offset %f", + gettime_from_timeval(&tv) - gettime()); + if (cstatus->constraint_errors) + printf(" (%d errors)", + cstatus->constraint_errors); + printf(", "); + } + if (cstatus->peercnt + cstatus->sensorcnt == 0) printf("no peers and no sensors configured\n"); diff --git a/src/usr.sbin/ntpd/ntpd.conf.5 b/src/usr.sbin/ntpd/ntpd.conf.5 index 3e8020ba..8466076f 100644 --- a/src/usr.sbin/ntpd/ntpd.conf.5 +++ b/src/usr.sbin/ntpd/ntpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ntpd.conf.5,v 1.24 2012/09/20 12:43:16 patrick Exp $ +.\" $OpenBSD: ntpd.conf.5,v 1.25 2015/02/10 06:40:08 reyk Exp $ .\" .\" Copyright (c) 2003, 2004 Henning Brauer .\" @@ -14,7 +14,7 @@ .\" 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: September 20 2012 $ +.Dd $Mdocdate: February 10 2015 $ .Dt NTPD.CONF 5 .Os .Sh NAME @@ -35,6 +35,40 @@ character are ignored. Keywords may be specified multiple times within the configuration file. They are as follows: .Bl -tag -width Ds +.It Ic constraint from Ar url +Specify the URL, IP address or the hostname of a HTTPS server to +provide a constraint. +.Xr ntpd 8 +will connect to the server and retrieve the remote time from the +.Eq Date +header. +This time will be used as a constraint on time synchronization; +received NTP packets with time information that is more than a few +minutes off will be discarded and the NTP +.Ic server +will be marked as invalid. +If multiple +.Ic constraint +keywords are used, +.Xr ntpd 8 +will calculate a median constraint from all the servers specified. +.Bd -literal -offset indent +server ntp.example.org +constraint www.example.com +.Ed +.It Ic constraints from Ar url +As with +.Ic constraint , +specify the URL, IP address or the hostname of a 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/search?q=openntpd" +.Ed .It Xo Ic listen on Ar address .Op Ic rtable Ar table-id .Xc diff --git a/src/usr.sbin/ntpd/ntpd.h b/src/usr.sbin/ntpd/ntpd.h index 44f91780..9c65ee2d 100644 --- a/src/usr.sbin/ntpd/ntpd.h +++ b/src/usr.sbin/ntpd/ntpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ntpd.h,v 1.117 2015/01/13 02:28:56 bcook Exp $ */ +/* $OpenBSD: ntpd.h,v 1.118 2015/02/10 06:40:08 reyk Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -72,16 +72,26 @@ #define SENSOR_DATA_MAXAGE (15*60) #define SENSOR_QUERY_INTERVAL 15 #define SENSOR_QUERY_INTERVAL_SETTIME (SETTIME_TIMEOUT/3) -#define SENSOR_SCAN_INTERVAL (5*60) +#define SENSOR_SCAN_INTERVAL (1*60) #define SENSOR_DEFAULT_REFID "HARD" +#define CONSTRAINT_ERROR_MARGIN (4) +#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 CONSTRAINT_CA "/etc/ssl/cert.pem" + enum client_state { STATE_NONE, STATE_DNS_INPROGRESS, STATE_DNS_TEMPFAIL, STATE_DNS_DONE, STATE_QUERY_SENT, - STATE_REPLY_RECEIVED + STATE_REPLY_RECEIVED, + STATE_INVALID }; struct listen_addr { @@ -99,6 +109,7 @@ struct ntp_addr { struct ntp_addr_wrap { char *name; + char *path; struct ntp_addr *a; u_int8_t pool; }; @@ -160,6 +171,20 @@ struct ntp_sensor { u_int8_t shift; }; +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; +}; + struct ntp_conf_sensor { TAILQ_ENTRY(ntp_conf_sensor) entry; char *device; @@ -182,6 +207,7 @@ struct ntpd_conf { 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; u_int32_t scale; @@ -190,6 +216,11 @@ struct ntpd_conf { u_int8_t debug; u_int8_t noaction; u_int8_t filters; + time_t constraint_last; + time_t constraint_median; + u_int constraint_errors; + u_int8_t *ca; + size_t ca_len; }; struct ctl_show_status { @@ -200,6 +231,9 @@ struct ctl_show_status { u_int8_t synced; u_int8_t stratum; double clock_offset; + time_t constraint_median; + time_t constraint_last; + u_int constraint_errors; }; struct ctl_show_peer { @@ -245,6 +279,7 @@ enum imsg_type { IMSG_ADJFREQ, IMSG_SETTIME, IMSG_HOST_DNS, + IMSG_CONSTRAINT, IMSG_CTL_SHOW_STATUS, IMSG_CTL_SHOW_PEERS, IMSG_CTL_SHOW_PEERS_END, @@ -284,6 +319,7 @@ int host_dns(const char *, 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 *); @@ -303,8 +339,16 @@ int client_dispatch(struct ntp_peer *, u_int8_t); void client_log_error(struct ntp_peer *, const char *, int); void set_next(struct ntp_peer *, time_t); +/* constraint.c */ +int constraint_init(struct constraint *); +int constraint_query(struct constraint *); +int constraint_dispatch_msg(struct pollfd *); +void constraint_check_child(void); +int constraint_check(double); + /* util.c */ double gettime_corrected(void); +double gettime_from_timeval(struct timeval *); double getoffset(void); double gettime(void); time_t getmonotime(void); diff --git a/src/usr.sbin/ntpd/parse.y b/src/usr.sbin/ntpd/parse.y index 1d459580..49432fc3 100644 --- a/src/usr.sbin/ntpd/parse.y +++ b/src/usr.sbin/ntpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.57 2015/01/10 13:47:05 tedu Exp $ */ +/* $OpenBSD: parse.y,v 1.58 2015/02/10 06:40:08 reyk Exp $ */ /* * Copyright (c) 2002, 2003, 2004 Henning Brauer @@ -80,12 +80,12 @@ typedef struct { %} -%token LISTEN ON +%token LISTEN ON CONSTRAINT CONSTRAINTS FROM %token SERVER SERVERS SENSOR CORRECTION RTABLE REFID STRATUM WEIGHT %token ERROR %token STRING %token NUMBER -%type address +%type address url %type listen_opts listen_opts_l listen_opt %type server_opts server_opts_l server_opt %type sensor_opts sensor_opts_l sensor_opt @@ -208,6 +208,82 @@ main : LISTEN ON address listen_opts { 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 = 1; + 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; + TAILQ_INSERT_TAIL(&conf->constraints, + p, entry); + h = next; + } while (h != NULL); + + free($3->name); + free($3); + } + | CONSTRAINT FROM url { + 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; + TAILQ_INSERT_TAIL(&conf->constraints, p, entry); + free($3->name); + free($3); + } | SENSOR STRING sensor_opts { struct ntp_conf_sensor *s; @@ -230,6 +306,45 @@ address : STRING { } ; +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; + if (($$->path = strdup("/")) == NULL) + fatal("strdup"); + } else { + hname = $1 + strlen("https://"); + + path = hname + strcspn(hname, "/\\"); + if (*path == '\0') + path = "/"; + if (($$->path = strdup(path)) == NULL) + fatal("strdup"); + *path = '\0'; + host(hname, &$$->a); + if (($$->name = strdup(hname)) == NULL) + fatal("strdup"); + } + + if ($$->a == NULL && + (host_dns($$->name, &$$->a) == -1 || + $$->a == NULL)) { + yyerror("could not resolve \"%s\"", $$->name); + free($$->name); + free($$->path); + free($$); + YYERROR; + } + } + ; + listen_opts : { opts_default(); } listen_opts_l { $$ = opts; } @@ -358,7 +473,10 @@ 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}, { "refid", REFID}, @@ -646,6 +764,7 @@ parse_config(const char *filename, struct ntpd_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); diff --git a/src/usr.sbin/ntpd/util.c b/src/usr.sbin/ntpd/util.c index 88f9a517..79b1bb25 100644 --- a/src/usr.sbin/ntpd/util.c +++ b/src/usr.sbin/ntpd/util.c @@ -1,4 +1,4 @@ -/* $OpenBSD: util.c,v 1.16 2015/01/04 01:11:24 bcook Exp $ */ +/* $OpenBSD: util.c,v 1.17 2015/02/10 06:40:08 reyk Exp $ */ /* * Copyright (c) 2004 Alexander Guy @@ -48,6 +48,12 @@ gettime(void) return (tv.tv_sec + JAN_1970 + 1.0e-6 * tv.tv_usec); } +double +gettime_from_timeval(struct timeval *tv) +{ + return (tv->tv_sec + JAN_1970 + 1.0e-6 * tv->tv_usec); +} + time_t getmonotime(void) {