|
/* $OpenBSD: ntp.c,v 1.45 2004/12/22 05:34:52 henning Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
|
|
* Copyright (c) 2004 Alexander Guy <alexander.guy@andern.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 MIND, 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/param.h>
|
|
#include <sys/time.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <paths.h>
|
|
#include <poll.h>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "ntpd.h"
|
|
#include "ntp.h"
|
|
|
|
#define PFD_PIPE_MAIN 0
|
|
#define PFD_MAX 1
|
|
|
|
volatile sig_atomic_t ntp_quit = 0;
|
|
struct imsgbuf *ibuf_main;
|
|
struct ntpd_conf *conf;
|
|
u_int peer_cnt;
|
|
|
|
void ntp_sighdlr(int);
|
|
int ntp_dispatch_imsg(void);
|
|
void peer_add(struct ntp_peer *);
|
|
void peer_remove(struct ntp_peer *);
|
|
int offset_compare(const void *, const void *);
|
|
|
|
void
|
|
ntp_sighdlr(int sig)
|
|
{
|
|
switch (sig) {
|
|
case SIGINT:
|
|
case SIGTERM:
|
|
ntp_quit = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pid_t
|
|
ntp_main(int pipe_prnt[2], struct ntpd_conf *nconf)
|
|
{
|
|
int a, b, nfds, i, j, idx_peers, timeout, nullfd;
|
|
u_int pfd_elms = 0, idx2peer_elms = 0;
|
|
u_int listener_cnt, new_cnt;
|
|
pid_t pid;
|
|
struct pollfd *pfd = NULL;
|
|
struct passwd *pw;
|
|
struct servent *se;
|
|
struct listen_addr *la;
|
|
struct ntp_peer *p;
|
|
struct ntp_peer **idx2peer = NULL;
|
|
struct timespec tp;
|
|
struct stat stb;
|
|
time_t nextaction;
|
|
void *newp;
|
|
|
|
switch (pid = fork()) {
|
|
case -1:
|
|
fatal("cannot fork");
|
|
case 0:
|
|
break;
|
|
default:
|
|
return (pid);
|
|
}
|
|
|
|
if ((se = getservbyname("ntp", "udp")) == NULL)
|
|
fatal("getservbyname");
|
|
|
|
if ((pw = getpwnam(NTPD_USER)) == NULL)
|
|
fatal("getpwnam");
|
|
|
|
if ((nullfd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1)
|
|
fatal(NULL);
|
|
|
|
if (stat(pw->pw_dir, &stb) == -1)
|
|
fatal("stat");
|
|
if (stb.st_uid != 0 || (stb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
|
|
fatal("bad privsep dir permissions");
|
|
if (chroot(pw->pw_dir) == -1)
|
|
fatal("chroot");
|
|
if (chdir("/") == -1)
|
|
fatal("chdir(\"/\")");
|
|
|
|
if (!nconf->debug) {
|
|
dup2(nullfd, STDIN_FILENO);
|
|
dup2(nullfd, STDOUT_FILENO);
|
|
dup2(nullfd, STDERR_FILENO);
|
|
}
|
|
close(nullfd);
|
|
|
|
setproctitle("ntp engine");
|
|
|
|
conf = nconf;
|
|
setup_listeners(se, conf, &listener_cnt);
|
|
|
|
if (setgroups(1, &pw->pw_gid) ||
|
|
setegid(pw->pw_gid) || setgid(pw->pw_gid) ||
|
|
seteuid(pw->pw_uid) || setuid(pw->pw_uid))
|
|
fatal("can't drop privileges");
|
|
|
|
endpwent();
|
|
endservent();
|
|
|
|
signal(SIGTERM, ntp_sighdlr);
|
|
signal(SIGINT, ntp_sighdlr);
|
|
signal(SIGPIPE, SIG_IGN);
|
|
signal(SIGHUP, SIG_IGN);
|
|
|
|
close(pipe_prnt[0]);
|
|
if ((ibuf_main = malloc(sizeof(struct imsgbuf))) == NULL)
|
|
fatal(NULL);
|
|
imsg_init(ibuf_main, pipe_prnt[1]);
|
|
|
|
TAILQ_FOREACH(p, &conf->ntp_peers, entry)
|
|
client_peer_init(p);
|
|
|
|
bzero(&conf->status, sizeof(conf->status));
|
|
conf->status.leap = LI_ALARM;
|
|
clock_getres(CLOCK_REALTIME, &tp);
|
|
b = 1000000000 / tp.tv_nsec; /* convert to Hz */
|
|
for (a = 0; b > 1; a--, b >>= 1);
|
|
conf->status.precision = a;
|
|
|
|
|
|
log_info("ntp engine ready");
|
|
|
|
peer_cnt = 0;
|
|
TAILQ_FOREACH(p, &conf->ntp_peers, entry)
|
|
peer_cnt++;
|
|
|
|
while (ntp_quit == 0) {
|
|
if (peer_cnt > idx2peer_elms) {
|
|
if ((newp = realloc(idx2peer, sizeof(void *) *
|
|
peer_cnt)) == NULL) {
|
|
/* panic for now */
|
|
log_warn("could not resize idx2peer from %u -> "
|
|
"%u entries", idx2peer_elms, peer_cnt);
|
|
fatalx("exiting");
|
|
}
|
|
idx2peer = newp;
|
|
idx2peer_elms = peer_cnt;
|
|
}
|
|
|
|
new_cnt = PFD_MAX + peer_cnt + listener_cnt;
|
|
if (new_cnt > pfd_elms) {
|
|
if ((newp = realloc(pfd, sizeof(struct pollfd) *
|
|
new_cnt)) == NULL) {
|
|
/* panic for now */
|
|
log_warn("could not resize pfd from %u -> "
|
|
"%u entries", pfd_elms, new_cnt);
|
|
fatalx("exiting");
|
|
}
|
|
pfd = newp;
|
|
pfd_elms = new_cnt;
|
|
}
|
|
|
|
bzero(pfd, sizeof(struct pollfd) * pfd_elms);
|
|
bzero(idx2peer, sizeof(void *) * idx2peer_elms);
|
|
nextaction = time(NULL) + 3600;
|
|
pfd[PFD_PIPE_MAIN].fd = ibuf_main->fd;
|
|
pfd[PFD_PIPE_MAIN].events = POLLIN;
|
|
|
|
i = 1;
|
|
TAILQ_FOREACH(la, &conf->listen_addrs, entry) {
|
|
pfd[i].fd = la->fd;
|
|
pfd[i].events = POLLIN;
|
|
i++;
|
|
}
|
|
|
|
idx_peers = i;
|
|
TAILQ_FOREACH(p, &conf->ntp_peers, entry) {
|
|
if (p->next > 0 && p->next < nextaction)
|
|
nextaction = p->next;
|
|
if (p->next > 0 && p->next <= time(NULL))
|
|
client_query(p);
|
|
|
|
if (p->deadline > 0 && p->deadline < nextaction)
|
|
nextaction = p->deadline;
|
|
if (p->deadline > 0 && p->deadline <= time(NULL)) {
|
|
log_debug("no reply from %s received in time",
|
|
log_sockaddr(
|
|
(struct sockaddr *)&p->addr->ss));
|
|
if (p->trustlevel >= TRUSTLEVEL_BADPEER &&
|
|
(p->trustlevel /= 2) < TRUSTLEVEL_BADPEER)
|
|
log_info("peer %s now invalid",
|
|
log_sockaddr(
|
|
(struct sockaddr *)&p->addr->ss));
|
|
client_nextaddr(p);
|
|
client_query(p);
|
|
}
|
|
|
|
if (p->state == STATE_QUERY_SENT) {
|
|
pfd[i].fd = p->query->fd;
|
|
pfd[i].events = POLLIN;
|
|
idx2peer[i - idx_peers] = p;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (ibuf_main->w.queued > 0)
|
|
pfd[PFD_PIPE_MAIN].events |= POLLOUT;
|
|
|
|
timeout = nextaction - time(NULL);
|
|
if (timeout < 0)
|
|
timeout = 0;
|
|
|
|
if ((nfds = poll(pfd, i, timeout * 1000)) == -1)
|
|
if (errno != EINTR) {
|
|
log_warn("poll error");
|
|
ntp_quit = 1;
|
|
}
|
|
|
|
if (nfds > 0 && (pfd[PFD_PIPE_MAIN].revents & POLLOUT))
|
|
if (msgbuf_write(&ibuf_main->w) < 0) {
|
|
log_warn("pipe write error (to parent)");
|
|
ntp_quit = 1;
|
|
}
|
|
|
|
if (nfds > 0 && pfd[PFD_PIPE_MAIN].revents & (POLLIN|POLLERR)) {
|
|
nfds--;
|
|
if (ntp_dispatch_imsg() == -1)
|
|
ntp_quit = 1;
|
|
}
|
|
|
|
for (j = 1; nfds > 0 && j < idx_peers; j++)
|
|
if (pfd[j].revents & (POLLIN|POLLERR)) {
|
|
nfds--;
|
|
if (server_dispatch(pfd[j].fd, conf) == -1)
|
|
ntp_quit = 1;
|
|
}
|
|
|
|
for (; nfds > 0 && j < i; j++)
|
|
if (pfd[j].revents & (POLLIN|POLLERR)) {
|
|
nfds--;
|
|
if (client_dispatch(idx2peer[j - idx_peers],
|
|
conf->settime) == -1)
|
|
ntp_quit = 1;
|
|
}
|
|
}
|
|
|
|
msgbuf_write(&ibuf_main->w);
|
|
msgbuf_clear(&ibuf_main->w);
|
|
free(ibuf_main);
|
|
|
|
log_info("ntp engine exiting");
|
|
_exit(0);
|
|
}
|
|
|
|
int
|
|
ntp_dispatch_imsg(void)
|
|
{
|
|
struct imsg imsg;
|
|
int n;
|
|
struct ntp_peer *peer, *npeer;
|
|
u_int16_t dlen;
|
|
u_char *p;
|
|
struct ntp_addr *h;
|
|
|
|
if ((n = imsg_read(ibuf_main)) == -1)
|
|
return (-1);
|
|
|
|
if (n == 0) { /* connection closed */
|
|
log_warnx("ntp_dispatch_imsg in ntp engine: pipe closed");
|
|
return (-1);
|
|
}
|
|
|
|
for (;;) {
|
|
if ((n = imsg_get(ibuf_main, &imsg)) == -1)
|
|
return (-1);
|
|
|
|
if (n == 0)
|
|
break;
|
|
|
|
switch (imsg.hdr.type) {
|
|
case IMSG_HOST_DNS:
|
|
TAILQ_FOREACH(peer, &conf->ntp_peers, entry)
|
|
if (peer->id == imsg.hdr.peerid)
|
|
break;
|
|
if (peer == NULL) {
|
|
log_warnx("IMSG_HOST_DNS with invalid peerID");
|
|
break;
|
|
}
|
|
if (peer->addr != NULL) {
|
|
log_warnx("IMSG_HOST_DNS but addr != NULL!");
|
|
break;
|
|
}
|
|
dlen = imsg.hdr.len - IMSG_HEADER_SIZE;
|
|
p = (u_char *)imsg.data;
|
|
while (dlen >= sizeof(struct sockaddr_storage)) {
|
|
if ((h = calloc(1, sizeof(struct ntp_addr))) ==
|
|
NULL)
|
|
fatal(NULL);
|
|
memcpy(&h->ss, p, sizeof(h->ss));
|
|
p += sizeof(h->ss);
|
|
dlen -= sizeof(h->ss);
|
|
if (peer->addr_head.pool) {
|
|
npeer = new_peer();
|
|
h->next = NULL;
|
|
npeer->addr = h;
|
|
npeer->addr_head.a = h;
|
|
client_peer_init(npeer);
|
|
peer_add(npeer);
|
|
} else {
|
|
h->next = peer->addr;
|
|
peer->addr = h;
|
|
peer->addr_head.a = peer->addr;
|
|
}
|
|
}
|
|
if (dlen != 0)
|
|
fatal("IMSG_HOST_DNS: dlen != 0");
|
|
if (peer->addr_head.pool)
|
|
peer_remove(peer);
|
|
else
|
|
client_addr_init(peer);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
imsg_free(&imsg);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
peer_add(struct ntp_peer *p)
|
|
{
|
|
TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry);
|
|
peer_cnt++;
|
|
}
|
|
|
|
void
|
|
peer_remove(struct ntp_peer *p)
|
|
{
|
|
TAILQ_REMOVE(&conf->ntp_peers, p, entry);
|
|
free(p);
|
|
peer_cnt--;
|
|
}
|
|
|
|
void
|
|
priv_adjtime(void)
|
|
{
|
|
struct ntp_peer *p;
|
|
int offset_cnt = 0, i = 0;
|
|
struct ntp_peer **peers;
|
|
double offset_median;
|
|
|
|
TAILQ_FOREACH(p, &conf->ntp_peers, entry) {
|
|
if (p->trustlevel < TRUSTLEVEL_BADPEER)
|
|
continue;
|
|
if (!p->update.good)
|
|
return;
|
|
offset_cnt++;
|
|
}
|
|
|
|
if ((peers = calloc(offset_cnt, sizeof(struct ntp_peer *))) == NULL)
|
|
fatal("calloc ntp_adjtime");
|
|
|
|
TAILQ_FOREACH(p, &conf->ntp_peers, entry) {
|
|
if (p->trustlevel < TRUSTLEVEL_BADPEER)
|
|
continue;
|
|
peers[i++] = p;
|
|
}
|
|
|
|
qsort(peers, offset_cnt, sizeof(struct ntp_peer *), offset_compare);
|
|
|
|
if (offset_cnt > 0) {
|
|
if (offset_cnt > 1 && offset_cnt % 2 == 0) {
|
|
offset_median =
|
|
(peers[offset_cnt / 2 - 1]->update.offset +
|
|
peers[offset_cnt / 2]->update.offset) / 2;
|
|
conf->status.rootdelay =
|
|
(peers[offset_cnt / 2 - 1]->update.delay +
|
|
peers[offset_cnt / 2]->update.delay) / 2;
|
|
conf->status.stratum = MAX(
|
|
peers[offset_cnt / 2 - 1]->update.status.stratum,
|
|
peers[offset_cnt / 2]->update.status.stratum);
|
|
} else {
|
|
offset_median = peers[offset_cnt / 2]->update.offset;
|
|
conf->status.rootdelay =
|
|
peers[offset_cnt / 2]->update.delay;
|
|
conf->status.stratum =
|
|
peers[offset_cnt / 2]->update.status.stratum;
|
|
}
|
|
|
|
imsg_compose(ibuf_main, IMSG_ADJTIME, 0, 0,
|
|
&offset_median, sizeof(offset_median));
|
|
|
|
conf->status.reftime = gettime();
|
|
conf->status.leap = LI_NOWARNING;
|
|
conf->status.stratum++; /* one more than selected peer */
|
|
|
|
if (peers[offset_cnt / 2]->addr->ss.ss_family == AF_INET)
|
|
conf->status.refid = ((struct sockaddr_in *)
|
|
&peers[offset_cnt / 2]->addr->ss)->sin_addr.s_addr;
|
|
}
|
|
|
|
free(peers);
|
|
|
|
TAILQ_FOREACH(p, &conf->ntp_peers, entry)
|
|
p->update.good = 0;
|
|
}
|
|
|
|
int
|
|
offset_compare(const void *aa, const void *bb)
|
|
{
|
|
const struct ntp_peer * const *a;
|
|
const struct ntp_peer * const *b;
|
|
|
|
a = aa;
|
|
b = bb;
|
|
|
|
if ((*a)->update.offset < (*b)->update.offset)
|
|
return (-1);
|
|
else if ((*a)->update.offset > (*b)->update.offset)
|
|
return (1);
|
|
else
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
priv_settime(double offset)
|
|
{
|
|
imsg_compose(ibuf_main, IMSG_SETTIME, 0, 0, &offset, sizeof(offset));
|
|
conf->settime = 0;
|
|
}
|
|
|
|
void
|
|
priv_host_dns(char *name, u_int32_t peerid)
|
|
{
|
|
u_int16_t dlen;
|
|
|
|
dlen = strlen(name) + 1;
|
|
imsg_compose(ibuf_main, IMSG_HOST_DNS, peerid, 0, name, dlen);
|
|
}
|