root/usr.sbin/ntpd/constraint.c
/*      $OpenBSD: constraint.c,v 1.60 2024/11/21 13:38:14 claudio 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];

        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];
        if (imsgbuf_init(&cstr->ibuf, cstr->fd) == -1)
                fatal("imsgbuf_init");
        if (imsg_compose(&cstr->ibuf, IMSG_CONSTRAINT_QUERY, id, 0, -1,
            data, len) == -1)
                fatal("%s: imsg_compose", __func__);
        /*
         * 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);

        if (imsgbuf_flush(&cstr->ibuf) == -1)
                fatal("imsgbuf_flush");
}

static int
imsgbuf_read_one(struct imsgbuf *imsgbuf, struct imsg *imsg)
{
        while (1) {
                switch (imsg_get(imsgbuf, imsg)) {
                case -1:
                        return (-1);
                case 0:
                        break;
                default:
                        return (1);
                }

                switch (imsgbuf_read(imsgbuf)) {
                case -1:
                        return (-1);
                case 0:
                        return (0);
                }
        }
}

void
priv_constraint_readquery(struct constraint *cstr, struct ntp_addr_msg *am,
    uint8_t **data)
{
        struct ntp_addr         *h;
        uint8_t                 *dptr;
        struct imsg              imsg;
        size_t                   mlen;

        /* Read the message our parent left us. */
        switch (imsgbuf_read_one(&cstr->ibuf, &imsg)) {
        case -1:
                fatal("%s: imsgbuf_read_one", __func__);
        case 0:
                fatalx("%s: imsgbuf_read_one: connection closed", __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;

        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;
        if (imsgbuf_init(&cstr.ibuf, cstr.fd) == -1)
                fatal("imsgbuf_init");
        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);
        imsgbuf_flush(&cstr.ibuf);

        /* 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);

        imsgbuf_clear(&cstr->ibuf);
        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 (imsgbuf_read(&cstr->ibuf) != 1) {
                /* 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);
}