#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "ntp_machine.h"
#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_request.h"
#include "ntp_stdlib.h"
#include "ntp_syslog.h"
#include <stdio.h>
#include <ctype.h>
#include <netdb.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0)
struct conf_entry {
struct conf_entry *ce_next;
char *ce_name;
struct conf_peer ce_config;
struct sockaddr_storage peer_store;
};
#define ce_peeraddr ce_config.peeraddr
#define ce_peeraddr6 ce_config.peeraddr6
#define ce_hmode ce_config.hmode
#define ce_version ce_config.version
#define ce_minpoll ce_config.minpoll
#define ce_maxpoll ce_config.maxpoll
#define ce_flags ce_config.flags
#define ce_ttl ce_config.ttl
#define ce_keyid ce_config.keyid
#define ce_keystr ce_config.keystr
static struct conf_entry *confentries = NULL;
#define MINRESOLVE 2
#define MAXRESOLVE 32
#define CONFIG_TIME 2
#define ALARM_TIME 30
#define SLEEPTIME 2
static volatile int config_timer = 0;
static volatile int resolve_timer = 0;
static int resolve_value;
#define LOCALHOST 0x7f000001
#define SKEWTIME 0x08000000
#define TIMEOUT_SEC 2
#define TIMEOUT_USEC 0
#define TOK_HOSTNAME 0
#define TOK_HMODE 1
#define TOK_VERSION 2
#define TOK_MINPOLL 3
#define TOK_MAXPOLL 4
#define TOK_FLAGS 5
#define TOK_TTL 6
#define TOK_KEYID 7
#define TOK_KEYSTR 8
#define NUMTOK 9
#define MAXLINESIZE 512
static SOCKET sockfd = INVALID_SOCKET;
keyid_t req_keyid;
char *req_file;
static RETSIGTYPE bong P((int));
static void checkparent P((void));
static void removeentry P((struct conf_entry *));
static void addentry P((char *, int, int, int, int, u_int,
int, keyid_t, char *));
static int findhostaddr P((struct conf_entry *));
static void openntp P((void));
static int request P((struct conf_peer *));
static char * nexttoken P((char **));
static void readconf P((FILE *, char *));
static void doconfigure P((int));
struct ntp_res_t_pkt {
void *tag;
u_int32 paddr;
char name[MAXHOSTNAMELEN];
};
struct ntp_res_c_pkt {
char name[MAXHOSTNAMELEN];
u_int32 paddr;
int mode;
int version;
int minpoll;
int maxpoll;
u_int flags;
int ttl;
keyid_t keyid;
u_char keystr[MAXFILENAME];
};
void
ntp_res_recv(void)
{
}
void
ntp_intres(void)
{
FILE *in;
#ifdef HAVE_SIGSUSPEND
sigset_t set;
sigemptyset(&set);
#endif
#ifdef DEBUG
if (debug > 1) {
msyslog(LOG_INFO, "NTP_INTRES running");
}
#endif
if (sys_authenticate) {
if (!authistrusted(req_keyid)) {
msyslog(LOG_ERR, "invalid request keyid %08x",
req_keyid );
exit(1);
}
}
if ((in = fopen(req_file, "r")) == NULL) {
msyslog(LOG_ERR, "can't open configuration file %s: %m",
req_file);
exit(1);
}
readconf(in, req_file);
(void) fclose(in);
if (!debug )
(void) unlink(req_file);
sleep(SLEEPTIME);
doconfigure(1);
if (confentries == NULL) {
#if defined SYS_WINNT
ExitThread(0);
#else
exit(0);
#endif
}
resolve_value = resolve_timer = MINRESOLVE;
config_timer = CONFIG_TIME;
#ifndef SYS_WINNT
(void) signal_no_reset(SIGALRM, bong);
alarm(ALARM_TIME);
#endif
for (;;) {
if (confentries == NULL)
exit(0);
checkparent();
if (resolve_timer == 0) {
if (resolve_value < MAXRESOLVE)
resolve_value <<= 1;
resolve_timer = resolve_value;
#ifdef DEBUG
if (debug > 2)
msyslog(LOG_INFO, "resolve_timer: 0->%d", resolve_timer);
#endif
config_timer = CONFIG_TIME;
doconfigure(1);
continue;
} else if (config_timer == 0) {
config_timer = CONFIG_TIME;
#ifdef DEBUG
if (debug > 2)
msyslog(LOG_INFO, "config_timer: 0->%d", config_timer);
#endif
doconfigure(0);
continue;
}
#ifndef SYS_WINNT
# ifdef HAVE_SIGSUSPEND
sigsuspend(&set);
# else
sigpause(0);
# endif
#else
if (config_timer > 0)
config_timer--;
if (resolve_timer > 0)
resolve_timer--;
sleep(ALARM_TIME);
#endif
}
}
#ifndef SYS_WINNT
static RETSIGTYPE
bong(
int sig
)
{
if (config_timer > 0)
config_timer--;
if (resolve_timer > 0)
resolve_timer--;
alarm(ALARM_TIME);
}
#endif
static void
checkparent(void)
{
#if !defined (SYS_WINNT) && !defined (SYS_VXWORKS)
if (getppid() == 1) {
msyslog(LOG_INFO, "parent died before we finished, exiting");
exit(0);
}
#endif
}
static void
removeentry(
struct conf_entry *entry
)
{
register struct conf_entry *ce;
ce = confentries;
if (ce == entry) {
confentries = ce->ce_next;
return;
}
while (ce != NULL) {
if (ce->ce_next == entry) {
ce->ce_next = entry->ce_next;
return;
}
ce = ce->ce_next;
}
}
static void
addentry(
char *name,
int mode,
int version,
int minpoll,
int maxpoll,
u_int flags,
int ttl,
keyid_t keyid,
char *keystr
)
{
register char *cp;
register struct conf_entry *ce;
unsigned int len;
#ifdef DEBUG
if (debug > 1)
msyslog(LOG_INFO,
"intres: <%s> %d %d %d %d %x %d %x %s\n", name,
mode, version, minpoll, maxpoll, flags, ttl, keyid,
keystr);
#endif
len = strlen(name) + 1;
cp = (char *)emalloc(len);
memmove(cp, name, len);
ce = (struct conf_entry *)emalloc(sizeof(struct conf_entry));
ce->ce_name = cp;
ce->ce_peeraddr = 0;
ce->ce_peeraddr6 = in6addr_any;
ANYSOCK(&ce->peer_store);
ce->ce_hmode = (u_char)mode;
ce->ce_version = (u_char)version;
ce->ce_minpoll = (u_char)minpoll;
ce->ce_maxpoll = (u_char)maxpoll;
ce->ce_flags = (u_char)flags;
ce->ce_ttl = (u_char)ttl;
ce->ce_keyid = keyid;
strncpy((char *)ce->ce_keystr, keystr, MAXFILENAME);
ce->ce_next = NULL;
if (confentries == NULL) {
confentries = ce;
} else {
register struct conf_entry *cep;
for (cep = confentries; cep->ce_next != NULL;
cep = cep->ce_next)
;
cep->ce_next = ce;
}
}
static int
findhostaddr(
struct conf_entry *entry
)
{
struct addrinfo *addr;
int error;
checkparent();
if (entry->ce_name != NULL && SOCKNUL(&entry->peer_store)) {
msyslog(LOG_ERR, "findhostaddr: both ce_name and ce_peeraddr are defined...");
return 1;
}
if (entry->ce_name == NULL && !SOCKNUL(&entry->peer_store)) {
msyslog(LOG_ERR, "findhostaddr: both ce_name and ce_peeraddr are undefined!");
return 0;
}
if (entry->ce_name) {
#ifdef DEBUG
if (debug > 2)
msyslog(LOG_INFO, "findhostaddr: Resolving <%s>",
entry->ce_name);
#endif
error = getaddrinfo(entry->ce_name, NULL, NULL, &addr);
if (error == 0) {
entry->peer_store = *((struct sockaddr_storage*)(addr->ai_addr));
if (entry->peer_store.ss_family == AF_INET) {
entry->ce_peeraddr =
GET_INADDR(entry->peer_store);
entry->ce_config.v6_flag = 0;
} else {
entry->ce_peeraddr6 =
GET_INADDR6(entry->peer_store);
entry->ce_config.v6_flag = 1;
}
}
} else {
#ifdef DEBUG
if (debug > 2)
msyslog(LOG_INFO, "findhostaddr: Resolving %s>",
stoa(&entry->peer_store));
#endif
entry->ce_name = emalloc(MAXHOSTNAMELEN);
error = getnameinfo((const struct sockaddr *)&entry->peer_store,
SOCKLEN(&entry->peer_store),
(char *)&entry->ce_name, MAXHOSTNAMELEN,
NULL, 0, 0);
}
if (error != 0) {
if (h_errno == TRY_AGAIN)
return (1);
return (0);
}
if (entry->ce_name) {
#ifdef DEBUG
if (debug > 2)
msyslog(LOG_INFO, "findhostaddr: name resolved.");
#endif
#ifdef DEBUG
if (debug > 2)
msyslog(LOG_INFO, "findhostaddr: address resolved.");
#endif
}
return (1);
}
static void
openntp(void)
{
struct addrinfo hints;
struct addrinfo *addrResult;
if (sockfd >= 0)
return;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if (getaddrinfo(NULL, "ntp", &hints, &addrResult)!=0) {
msyslog(LOG_ERR, "getaddrinfo failed: %m");
exit(1);
}
sockfd = socket(addrResult->ai_family, addrResult->ai_socktype, 0);
if (sockfd == -1) {
msyslog(LOG_ERR, "socket() failed: %m");
exit(1);
}
#ifndef SYS_WINNT
#if defined(O_NONBLOCK)
if (fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1) {
msyslog(LOG_ERR, "fcntl(O_NONBLOCK) failed: %m");
exit(1);
}
#else
#if defined(FNDELAY)
if (fcntl(sockfd, F_SETFL, FNDELAY) == -1) {
msyslog(LOG_ERR, "fcntl(FNDELAY) failed: %m");
exit(1);
}
#else
# include "Bletch: NEED NON BLOCKING IO"
#endif
#endif
#else
{
int on = 1;
if (ioctlsocket(sockfd,FIONBIO,(u_long *) &on) == SOCKET_ERROR) {
msyslog(LOG_ERR, "ioctlsocket(FIONBIO) fails: %m");
exit(1);
}
}
#endif
if (connect(sockfd, addrResult->ai_addr, addrResult->ai_addrlen) == -1) {
msyslog(LOG_ERR, "openntp: connect() failed: %m");
exit(1);
}
freeaddrinfo(addrResult);
}
static int
request(
struct conf_peer *conf
)
{
fd_set fdset;
struct timeval tvout;
struct req_pkt reqpkt;
l_fp ts;
int n;
#ifdef SYS_WINNT
HANDLE hReadWriteEvent = NULL;
BOOL ret;
DWORD NumberOfBytesWritten, NumberOfBytesRead, dwWait;
OVERLAPPED overlap;
#endif
checkparent();
if (sockfd < 0)
openntp();
#ifdef SYS_WINNT
hReadWriteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
#endif
tvout.tv_sec = 0;
tvout.tv_usec = 0;
FD_ZERO(&fdset);
FD_SET(sockfd, &fdset);
while (select(sockfd + 1, &fdset, (fd_set *)0, (fd_set *)0, &tvout) >
0) {
recv(sockfd, (char *)&reqpkt, REQ_LEN_MAC, 0);
FD_ZERO(&fdset);
FD_SET(sockfd, &fdset);
}
memset((char *)&reqpkt, 0, sizeof(reqpkt));
reqpkt.rm_vn_mode = RM_VN_MODE(0, 0, 0);
reqpkt.auth_seq = AUTH_SEQ(1, 0);
reqpkt.implementation = IMPL_XNTPD;
reqpkt.request = REQ_CONFIG;
reqpkt.err_nitems = ERR_NITEMS(0, 1);
reqpkt.mbz_itemsize = MBZ_ITEMSIZE(sizeof(struct conf_peer));
if (sizeof(struct conf_peer) > sizeof (reqpkt.data)) {
msyslog(LOG_ERR, "Bletch: conf_peer is too big for reqpkt.data!");
exit(1);
}
memmove(reqpkt.data, (char *)conf, sizeof(struct conf_peer));
reqpkt.keyid = htonl(req_keyid);
get_systime(&ts);
L_ADDUF(&ts, SKEWTIME);
HTONL_FP(&ts, &reqpkt.tstamp);
n = 0;
if (sys_authenticate)
n = authencrypt(req_keyid, (u_int32 *)&reqpkt, REQ_LEN_NOMAC);
#ifndef SYS_WINNT
n = send(sockfd, (char *)&reqpkt, (unsigned)(REQ_LEN_NOMAC + n), 0);
if (n < 0) {
msyslog(LOG_ERR, "send to NTP server failed: %m");
return 0;
}
#else
overlap.Offset = overlap.OffsetHigh = (DWORD)0;
overlap.hEvent = hReadWriteEvent;
ret = WriteFile((HANDLE)sockfd, (char *)&reqpkt, REQ_LEN_NOMAC + n,
(LPDWORD)&NumberOfBytesWritten, (LPOVERLAPPED)&overlap);
if ((ret == FALSE) && (GetLastError() != ERROR_IO_PENDING)) {
msyslog(LOG_ERR, "send to NTP server failed: %m");
return 0;
}
dwWait = WaitForSingleObject(hReadWriteEvent, (DWORD) TIMEOUT_SEC * 1000);
if ((dwWait == WAIT_FAILED) || (dwWait == WAIT_TIMEOUT)) {
if (dwWait == WAIT_FAILED)
msyslog(LOG_ERR, "WaitForSingleObject failed: %m");
return 0;
}
#endif
for (;;) {
FD_ZERO(&fdset);
FD_SET(sockfd, &fdset);
tvout.tv_sec = TIMEOUT_SEC;
tvout.tv_usec = TIMEOUT_USEC;
n = select(sockfd + 1, &fdset, (fd_set *)0,
(fd_set *)0, &tvout);
if (n < 0)
{
if (errno != EINTR)
msyslog(LOG_ERR, "select() fails: %m");
return 0;
}
else if (n == 0)
{
if (debug)
msyslog(LOG_INFO, "select() returned 0.");
return 0;
}
#ifndef SYS_WINNT
n = recv(sockfd, (char *)&reqpkt, REQ_LEN_MAC, 0);
if (n <= 0) {
if (n < 0) {
msyslog(LOG_ERR, "recv() fails: %m");
return 0;
}
continue;
}
#else
ret = ReadFile((HANDLE)sockfd, (char *)&reqpkt, (DWORD)REQ_LEN_MAC,
(LPDWORD)&NumberOfBytesRead, (LPOVERLAPPED)&overlap);
if ((ret == FALSE) && (GetLastError() != ERROR_IO_PENDING)) {
msyslog(LOG_ERR, "ReadFile() fails: %m");
return 0;
}
dwWait = WaitForSingleObject(hReadWriteEvent, (DWORD) TIMEOUT_SEC * 1000);
if ((dwWait == WAIT_FAILED) || (dwWait == WAIT_TIMEOUT)) {
if (dwWait == WAIT_FAILED) {
msyslog(LOG_ERR, "WaitForSingleObject fails: %m");
return 0;
}
continue;
}
n = NumberOfBytesRead;
#endif
if (n < RESP_HEADER_SIZE) {
msyslog(LOG_ERR, "received runt response (%d octets)",
n);
continue;
}
if (!ISRESPONSE(reqpkt.rm_vn_mode)) {
#ifdef DEBUG
if (debug > 1)
msyslog(LOG_INFO, "received non-response packet");
#endif
continue;
}
if (ISMORE(reqpkt.rm_vn_mode)) {
#ifdef DEBUG
if (debug > 1)
msyslog(LOG_INFO, "received fragmented packet");
#endif
continue;
}
if ( ( (INFO_VERSION(reqpkt.rm_vn_mode) < 2)
|| (INFO_VERSION(reqpkt.rm_vn_mode) > NTP_VERSION))
|| INFO_MODE(reqpkt.rm_vn_mode) != MODE_PRIVATE) {
#ifdef DEBUG
if (debug > 1)
msyslog(LOG_INFO,
"version (%d/%d) or mode (%d/%d) incorrect",
INFO_VERSION(reqpkt.rm_vn_mode),
NTP_VERSION,
INFO_MODE(reqpkt.rm_vn_mode),
MODE_PRIVATE);
#endif
continue;
}
if (INFO_SEQ(reqpkt.auth_seq) != 0) {
#ifdef DEBUG
if (debug > 1)
msyslog(LOG_INFO,
"nonzero sequence number (%d)",
INFO_SEQ(reqpkt.auth_seq));
#endif
continue;
}
if (reqpkt.implementation != IMPL_XNTPD ||
reqpkt.request != REQ_CONFIG) {
#ifdef DEBUG
if (debug > 1)
msyslog(LOG_INFO,
"implementation (%d) or request (%d) incorrect",
reqpkt.implementation, reqpkt.request);
#endif
continue;
}
if (INFO_NITEMS(reqpkt.err_nitems) != 0 ||
INFO_MBZ(reqpkt.mbz_itemsize) != 0 ||
INFO_ITEMSIZE(reqpkt.mbz_itemsize) != 0) {
#ifdef DEBUG
if (debug > 1)
msyslog(LOG_INFO,
"nitems (%d) mbz (%d) or itemsize (%d) nonzero",
INFO_NITEMS(reqpkt.err_nitems),
INFO_MBZ(reqpkt.mbz_itemsize),
INFO_ITEMSIZE(reqpkt.mbz_itemsize));
#endif
continue;
}
n = INFO_ERR(reqpkt.err_nitems);
switch (n) {
case INFO_OKAY:
return 1;
case INFO_ERR_IMPL:
msyslog(LOG_ERR,
"ntpd reports implementation mismatch!");
return 0;
case INFO_ERR_REQ:
msyslog(LOG_ERR,
"ntpd says configuration request is unknown!");
return 0;
case INFO_ERR_FMT:
msyslog(LOG_ERR,
"ntpd indicates a format error occurred!");
return 0;
case INFO_ERR_NODATA:
msyslog(LOG_ERR,
"ntpd indicates no data available!");
return 0;
case INFO_ERR_AUTH:
msyslog(LOG_ERR,
"ntpd returns a permission denied error!");
return 0;
default:
msyslog(LOG_ERR,
"ntpd returns unknown error code %d!", n);
return 0;
}
}
}
static char *
nexttoken(
char **lptr
)
{
register char *cp;
register char *tstart;
cp = *lptr;
while (*cp == ' ' || *cp == '\t')
cp++;
if (*cp == '\n' || *cp == '\0') {
*lptr = cp;
return NULL;
}
tstart = cp++;
while (*cp != ' ' && *cp != '\t' && *cp != '\n' && *cp != '\0')
cp++;
if (*cp == '\n' || *cp == '\0')
*cp = '\0';
else
*cp++ = '\0';
*lptr = cp;
return tstart;
}
static void
readconf(
FILE *fp,
char *name
)
{
register int i;
char *token[NUMTOK];
u_long intval[NUMTOK];
u_int flags;
char buf[MAXLINESIZE];
char *bp;
while (fgets(buf, MAXLINESIZE, fp) != NULL) {
bp = buf;
for (i = 0; i < NUMTOK; i++) {
if ((token[i] = nexttoken(&bp)) == NULL) {
msyslog(LOG_ERR,
"tokenizing error in file `%s', quitting",
name);
exit(1);
}
}
for (i = 1; i < NUMTOK - 1; i++) {
if (!atouint(token[i], &intval[i])) {
msyslog(LOG_ERR,
"format error for integer token `%s', file `%s', quitting",
token[i], name);
exit(1);
}
}
if (intval[TOK_HMODE] != MODE_ACTIVE &&
intval[TOK_HMODE] != MODE_CLIENT &&
intval[TOK_HMODE] != MODE_BROADCAST) {
msyslog(LOG_ERR, "invalid mode (%ld) in file %s",
intval[TOK_HMODE], name);
exit(1);
}
if (intval[TOK_VERSION] > NTP_VERSION ||
intval[TOK_VERSION] < NTP_OLDVERSION) {
msyslog(LOG_ERR, "invalid version (%ld) in file %s",
intval[TOK_VERSION], name);
exit(1);
}
if (intval[TOK_MINPOLL] < NTP_MINPOLL ||
intval[TOK_MINPOLL] > NTP_MAXPOLL) {
msyslog(LOG_ERR, "invalid MINPOLL value (%ld) in file %s",
intval[TOK_MINPOLL], name);
exit(1);
}
if (intval[TOK_MAXPOLL] < NTP_MINPOLL ||
intval[TOK_MAXPOLL] > NTP_MAXPOLL) {
msyslog(LOG_ERR, "invalid MAXPOLL value (%ld) in file %s",
intval[TOK_MAXPOLL], name);
exit(1);
}
if ((intval[TOK_FLAGS] & ~(FLAG_AUTHENABLE | FLAG_PREFER |
FLAG_NOSELECT | FLAG_BURST | FLAG_IBURST | FLAG_SKEY))
!= 0) {
msyslog(LOG_ERR, "invalid flags (%ld) in file %s",
intval[TOK_FLAGS], name);
exit(1);
}
flags = 0;
if (intval[TOK_FLAGS] & FLAG_AUTHENABLE)
flags |= CONF_FLAG_AUTHENABLE;
if (intval[TOK_FLAGS] & FLAG_PREFER)
flags |= CONF_FLAG_PREFER;
if (intval[TOK_FLAGS] & FLAG_NOSELECT)
flags |= CONF_FLAG_NOSELECT;
if (intval[TOK_FLAGS] & FLAG_BURST)
flags |= CONF_FLAG_BURST;
if (intval[TOK_FLAGS] & FLAG_IBURST)
flags |= CONF_FLAG_IBURST;
if (intval[TOK_FLAGS] & FLAG_SKEY)
flags |= CONF_FLAG_SKEY;
addentry(token[TOK_HOSTNAME], (int)intval[TOK_HMODE],
(int)intval[TOK_VERSION], (int)intval[TOK_MINPOLL],
(int)intval[TOK_MAXPOLL], flags, (int)intval[TOK_TTL],
intval[TOK_KEYID], token[TOK_KEYSTR]);
}
}
static void
doconfigure(
int dores
)
{
register struct conf_entry *ce;
register struct conf_entry *ceremove;
ce = confentries;
while (ce != NULL) {
#ifdef DEBUG
if (debug > 1)
msyslog(LOG_INFO,
"doconfigure: <%s> has peeraddr %s",
ce->ce_name, stoa(&ce->peer_store));
#endif
if (dores && !SOCKNUL(&(ce->peer_store))) {
if (!findhostaddr(ce)) {
msyslog(LOG_ERR,
"couldn't resolve `%s', giving up on it",
ce->ce_name);
ceremove = ce;
ce = ceremove->ce_next;
removeentry(ceremove);
continue;
}
}
if (!SOCKNUL(&ce->peer_store)) {
if (request(&ce->ce_config)) {
ceremove = ce;
ce = ceremove->ce_next;
removeentry(ceremove);
continue;
}
#ifdef DEBUG
if (debug > 1) {
msyslog(LOG_INFO,
"doconfigure: request() FAILED, maybe next time.");
}
#endif
}
ce = ce->ce_next;
}
}