1168 lines
27 KiB
C
Raw Normal View History

/* $OpenBSD: constraint.c,v 1.56 2023/12/20 15:36:36 otto Exp $ */
/*
* Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
*
* 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 <sys/queue.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <imsg.h>
#include <netdb.h>
#include <poll.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <ctype.h>
#include <tls.h>
#include <pwd.h>
#include <math.h>
#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(&notbefore)) == 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(&notafter)) == 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);
}