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) {