|
@ -0,0 +1,659 @@ |
|
|
|
|
|
/* $OpenBSD: constraint.c,v 1.1 2015/02/10 06:40:08 reyk 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/uio.h> |
|
|
|
|
|
|
|
|
|
|
|
#include <netinet/in.h> |
|
|
|
|
|
#include <arpa/inet.h> |
|
|
|
|
|
|
|
|
|
|
|
#include <stdio.h> |
|
|
|
|
|
#include <stdlib.h> |
|
|
|
|
|
#include <errno.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 <tls.h> |
|
|
|
|
|
|
|
|
|
|
|
#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); |
|
|
|
|
|
} |