2bddeb8538
for resolving by a child process that, upon success, will add the entry to the config of the running running parent process. Unfortunately there are a couple of bugs with this, fixed in various later versions of upstream in potentially different ways due to other code changes: 1) Upon server [-46] <FQDN> the [-46] are used as FQDN for later resolving which does not work. Make sure we always pass the name (or IP there). 2) The intermediate file to carry the information to the child process does not know about -4/-6 restrictions, so that a dual-stacked host could resolve to an IPv6 address but that might be unreachable (see r223626) leading to no working synchronization ignoring a IPv4 record. Thus alter the intermediate format to also pass the address family (AF_UNSPEC (default), AF_INET or AF_INET6) to the child process depending on -4 or -6. 3) Make the child process to parse the new intermediate file format and save the address family for getaddrinfo() hints flags. 4) Change child to always reload resolv.conf calling res_init() before trying to resolve names. This will pick up resolv.conf changes or new resolv.confs should they have not existed or been empty or unusable on ntp startup. This fix is more conditional in upstream versions but given FreeBSD has res_init there is no need for the configure logic as well. Approved by: roberto Sponsored by: Sandvine Incorporated MFC after: 9 days
1186 lines
28 KiB
C
1186 lines
28 KiB
C
/*
|
|
* ripped off from ../ntpres/ntpres.c by Greg Troxel 4/2/92
|
|
* routine callable from ntpd, rather than separate program
|
|
* also, key info passed in via a global, so no key file needed.
|
|
*/
|
|
|
|
/*
|
|
* ntpres - process configuration entries which require use of the resolver
|
|
*
|
|
* This is meant to be run by ntpd on the fly. It is not guaranteed
|
|
* to work properly if run by hand. This is actually a quick hack to
|
|
* stave off violence from people who hate using numbers in the
|
|
* configuration file (at least I hope the rest of the daemon is
|
|
* better than this). Also might provide some ideas about how one
|
|
* might go about autoconfiguring an NTP distribution network.
|
|
*
|
|
*/
|
|
|
|
#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 <resolv.h>
|
|
#include <signal.h>
|
|
|
|
/**/
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
/**/
|
|
#ifdef HAVE_SYS_PARAM_H
|
|
# include <sys/param.h> /* MAXHOSTNAMELEN (often) */
|
|
#endif
|
|
|
|
#include <isc/net.h>
|
|
#include <isc/result.h>
|
|
|
|
#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0)
|
|
|
|
/*
|
|
* Each item we are to resolve and configure gets one of these
|
|
* structures defined for it.
|
|
*/
|
|
struct conf_entry {
|
|
struct conf_entry *ce_next;
|
|
char *ce_name; /* name we are trying to resolve */
|
|
struct conf_peer ce_config; /* configuration info for peer */
|
|
struct sockaddr_storage peer_store; /* address info for both fams */
|
|
};
|
|
#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
|
|
|
|
/*
|
|
* confentries is a pointer to the list of configuration entries
|
|
* we have left to do.
|
|
*/
|
|
static struct conf_entry *confentries = NULL;
|
|
|
|
/*
|
|
* We take an interrupt every thirty seconds, at which time we decrement
|
|
* config_timer and resolve_timer. The former is set to 2, so we retry
|
|
* unsucessful reconfigurations every minute. The latter is set to
|
|
* an exponentially increasing value which starts at 2 and increases to
|
|
* 32. When this expires we retry failed name resolutions.
|
|
*
|
|
* We sleep SLEEPTIME seconds before doing anything, to give the server
|
|
* time to arrange itself.
|
|
*/
|
|
#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; /* next value of resolve timer */
|
|
|
|
/*
|
|
* Big hack attack
|
|
*/
|
|
#define LOCALHOST 0x7f000001 /* 127.0.0.1, in hex, of course */
|
|
#define SKEWTIME 0x08000000 /* 0.03125 seconds as a l_fp fraction */
|
|
|
|
/*
|
|
* Select time out. Set to 2 seconds. The server is on the local machine,
|
|
* after all.
|
|
*/
|
|
#define TIMEOUT_SEC 2
|
|
#define TIMEOUT_USEC 0
|
|
|
|
|
|
/*
|
|
* Input processing. The data on each line in the configuration file
|
|
* is supposed to consist of entries in the following order
|
|
*/
|
|
#define TOK_HOSTNAME 0
|
|
#define TOK_PEERAF 1
|
|
#define TOK_HMODE 2
|
|
#define TOK_VERSION 3
|
|
#define TOK_MINPOLL 4
|
|
#define TOK_MAXPOLL 5
|
|
#define TOK_FLAGS 6
|
|
#define TOK_TTL 7
|
|
#define TOK_KEYID 8
|
|
#define TOK_KEYSTR 9
|
|
#define NUMTOK 10
|
|
|
|
#define MAXLINESIZE 512
|
|
|
|
|
|
/*
|
|
* File descriptor for ntp request code.
|
|
*/
|
|
static SOCKET sockfd = INVALID_SOCKET; /* NT uses SOCKET */
|
|
|
|
/* stuff to be filled in by caller */
|
|
|
|
keyid_t req_keyid; /* request keyid */
|
|
char *req_file; /* name of the file with configuration info */
|
|
|
|
/* end stuff to be filled in */
|
|
|
|
|
|
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 *, u_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 { /* Tagged packet: */
|
|
void *tag; /* For the caller */
|
|
u_int32 paddr; /* IP to look up, or 0 */
|
|
char name[MAXHOSTNAMELEN]; /* Name to look up (if 1st byte is not 0) */
|
|
};
|
|
|
|
struct ntp_res_c_pkt { /* Control packet: */
|
|
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];
|
|
};
|
|
|
|
|
|
static void resolver_exit P((int));
|
|
|
|
/*
|
|
* Call here instead of just exiting
|
|
*/
|
|
|
|
static void resolver_exit (int code)
|
|
{
|
|
#ifdef SYS_WINNT
|
|
CloseHandle(ResolverEventHandle);
|
|
ResolverEventHandle = NULL;
|
|
ExitThread(code); /* Just to kill the thread not the process */
|
|
#else
|
|
exit(code); /* kill the forked process */
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* ntp_res_recv: Process an answer from the resolver
|
|
*/
|
|
|
|
void
|
|
ntp_res_recv(void)
|
|
{
|
|
/*
|
|
We have data ready on our descriptor.
|
|
It may be an EOF, meaning the resolver process went away.
|
|
Otherwise, it will be an "answer".
|
|
*/
|
|
}
|
|
|
|
|
|
/*
|
|
* ntp_intres needs;
|
|
*
|
|
* req_key(???), req_keyid, req_file valid
|
|
* syslog still open
|
|
*/
|
|
|
|
void
|
|
ntp_intres(void)
|
|
{
|
|
FILE *in;
|
|
struct timeval tv;
|
|
fd_set fdset;
|
|
#ifdef SYS_WINNT
|
|
DWORD rc;
|
|
#else
|
|
int rc;
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
if (debug > 1) {
|
|
msyslog(LOG_INFO, "NTP_INTRES running");
|
|
}
|
|
#endif
|
|
|
|
/* check out auth stuff */
|
|
if (sys_authenticate) {
|
|
if (!authistrusted(req_keyid)) {
|
|
msyslog(LOG_ERR, "invalid request keyid %08x",
|
|
req_keyid );
|
|
resolver_exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read the configuration info
|
|
* {this is bogus, since we are forked, but it is easier
|
|
* to keep this code - gdt}
|
|
*/
|
|
if ((in = fopen(req_file, "r")) == NULL) {
|
|
msyslog(LOG_ERR, "can't open configuration file %s: %m",
|
|
req_file);
|
|
resolver_exit(1);
|
|
}
|
|
readconf(in, req_file);
|
|
(void) fclose(in);
|
|
|
|
#ifdef DEBUG
|
|
if (!debug )
|
|
#endif
|
|
(void) unlink(req_file);
|
|
|
|
/*
|
|
* Set up the timers to do first shot immediately.
|
|
*/
|
|
resolve_timer = 0;
|
|
resolve_value = MINRESOLVE;
|
|
config_timer = CONFIG_TIME;
|
|
|
|
for (;;) {
|
|
checkparent();
|
|
|
|
if (resolve_timer == 0) {
|
|
/*
|
|
* Sleep a little to make sure the network is completely up
|
|
*/
|
|
sleep(SLEEPTIME);
|
|
doconfigure(1);
|
|
|
|
/* prepare retry, in case there's more work to do */
|
|
resolve_timer = resolve_value;
|
|
#ifdef DEBUG
|
|
if (debug > 2)
|
|
msyslog(LOG_INFO, "resolve_timer: 0->%d", resolve_timer);
|
|
#endif
|
|
if (resolve_value < MAXRESOLVE)
|
|
resolve_value <<= 1;
|
|
|
|
config_timer = CONFIG_TIME;
|
|
} else if (config_timer == 0) { /* MB: in which case would this be required ? */
|
|
doconfigure(0);
|
|
/* MB: should we check now if we could exit, similar to the code above? */
|
|
config_timer = CONFIG_TIME;
|
|
#ifdef DEBUG
|
|
if (debug > 2)
|
|
msyslog(LOG_INFO, "config_timer: 0->%d", config_timer);
|
|
#endif
|
|
}
|
|
|
|
if (confentries == NULL)
|
|
resolver_exit(0); /* done */
|
|
|
|
#ifdef SYS_WINNT
|
|
rc = WaitForSingleObject(ResolverEventHandle, 1000 * ALARM_TIME); /* in milliseconds */
|
|
|
|
if ( rc == WAIT_OBJECT_0 ) { /* signaled by the main thread */
|
|
resolve_timer = 0; /* retry resolving immediately */
|
|
continue;
|
|
}
|
|
|
|
if ( rc != WAIT_TIMEOUT ) /* not timeout: error */
|
|
resolver_exit(1);
|
|
|
|
#else /* not SYS_WINNT */
|
|
tv.tv_sec = ALARM_TIME;
|
|
tv.tv_usec = 0;
|
|
FD_ZERO(&fdset);
|
|
FD_SET(resolver_pipe_fd[0], &fdset);
|
|
rc = select(resolver_pipe_fd[0] + 1, &fdset, (fd_set *)0, (fd_set *)0, &tv);
|
|
|
|
if (rc > 0) { /* parent process has written to the pipe */
|
|
read(resolver_pipe_fd[0], (char *)&rc, sizeof(rc)); /* make pipe empty */
|
|
resolve_timer = 0; /* retry resolving immediately */
|
|
continue;
|
|
}
|
|
|
|
if ( rc < 0 ) /* select() returned error */
|
|
resolver_exit(1);
|
|
#endif
|
|
|
|
/* normal timeout, keep on waiting */
|
|
if (config_timer > 0)
|
|
config_timer--;
|
|
if (resolve_timer > 0)
|
|
resolve_timer--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* checkparent - see if our parent process is still running
|
|
*
|
|
* No need to worry in the Windows NT environment whether the
|
|
* main thread is still running, because if it goes
|
|
* down it takes the whole process down with it (in
|
|
* which case we won't be running this thread either)
|
|
* Turn function into NOP;
|
|
*/
|
|
|
|
static void
|
|
checkparent(void)
|
|
{
|
|
#if !defined (SYS_WINNT) && !defined (SYS_VXWORKS)
|
|
|
|
/*
|
|
* If our parent (the server) has died we will have been
|
|
* inherited by init. If so, exit.
|
|
*/
|
|
if (getppid() == 1) {
|
|
msyslog(LOG_INFO, "parent died before we finished, exiting");
|
|
resolver_exit(0);
|
|
}
|
|
#endif /* SYS_WINNT && SYS_VXWORKS*/
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* removeentry - we are done with an entry, remove it from the list
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* addentry - add an entry to the configuration list
|
|
*/
|
|
static void
|
|
addentry(
|
|
char *name,
|
|
int mode,
|
|
int version,
|
|
int minpoll,
|
|
int maxpoll,
|
|
u_int flags,
|
|
int ttl,
|
|
keyid_t keyid,
|
|
char *keystr,
|
|
u_char peeraf
|
|
)
|
|
{
|
|
register char *cp;
|
|
register struct conf_entry *ce;
|
|
unsigned int len;
|
|
|
|
#ifdef DEBUG
|
|
if (debug > 1)
|
|
msyslog(LOG_INFO,
|
|
"intres: <%s> %u %d %d %d %d %x %d %x %s\n", name, peeraf,
|
|
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;
|
|
#ifdef ISC_PLATFORM_HAVEIPV6
|
|
ce->ce_peeraddr6 = in6addr_any;
|
|
#endif
|
|
ANYSOCK(&ce->peer_store);
|
|
ce->peer_store.ss_family = peeraf; /* Save AF for getaddrinfo hints. */
|
|
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)
|
|
/* nothing */;
|
|
cep->ce_next = ce;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* findhostaddr - resolve a host name into an address (Or vice-versa)
|
|
*
|
|
* Given one of {ce_peeraddr,ce_name}, find the other one.
|
|
* It returns 1 for "success" and 0 for an uncorrectable failure.
|
|
* Note that "success" includes try again errors. You can tell that you
|
|
* got a "try again" since {ce_peeraddr,ce_name} will still be zero.
|
|
*/
|
|
static int
|
|
findhostaddr(
|
|
struct conf_entry *entry
|
|
)
|
|
{
|
|
static int eai_again_seen = 0;
|
|
struct addrinfo *addr;
|
|
struct addrinfo hints;
|
|
int again;
|
|
int error;
|
|
|
|
checkparent(); /* make sure our guy is still running */
|
|
|
|
if (entry->ce_name != NULL && !SOCKNUL(&entry->peer_store)) {
|
|
/* HMS: Squawk? */
|
|
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) {
|
|
DPRINTF(2, ("findhostaddr: Resolving <%s>\n",
|
|
entry->ce_name));
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = entry->peer_store.ss_family;
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
/*
|
|
* If the IPv6 stack is not available look only for IPv4 addresses
|
|
*/
|
|
if (isc_net_probeipv6() != ISC_R_SUCCESS)
|
|
hints.ai_family = AF_INET;
|
|
|
|
error = getaddrinfo(entry->ce_name, NULL, &hints, &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 {
|
|
DPRINTF(2, ("findhostaddr: Resolving <%s>\n",
|
|
stoa(&entry->peer_store)));
|
|
|
|
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 (0 == error) {
|
|
|
|
/* again is our return value, for success it is 1 */
|
|
again = 1;
|
|
|
|
DPRINTF(2, ("findhostaddr: %s resolved.\n",
|
|
(entry->ce_name) ? "name" : "address"));
|
|
} else {
|
|
/*
|
|
* If the resolver failed, see if the failure is
|
|
* temporary. If so, return success.
|
|
*/
|
|
again = 0;
|
|
|
|
switch (error) {
|
|
|
|
case EAI_FAIL:
|
|
again = 1;
|
|
break;
|
|
|
|
case EAI_AGAIN:
|
|
again = 1;
|
|
eai_again_seen = 1;
|
|
break;
|
|
|
|
case EAI_NONAME:
|
|
#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
|
|
case EAI_NODATA:
|
|
#endif
|
|
msyslog(LOG_ERR, "host name not found%s%s: %s",
|
|
(EAI_NONAME == error) ? "" : " EAI_NODATA",
|
|
(eai_again_seen) ? " (permanent)" : "",
|
|
entry->ce_name);
|
|
again = !eai_again_seen;
|
|
break;
|
|
|
|
#ifdef EAI_SYSTEM
|
|
case EAI_SYSTEM:
|
|
/*
|
|
* EAI_SYSTEM means the real error is in errno. We should be more
|
|
* discriminating about which errno values require retrying, but
|
|
* this matches existing behavior.
|
|
*/
|
|
again = 1;
|
|
DPRINTF(1, ("intres: EAI_SYSTEM errno %d (%s) means try again, right?\n",
|
|
errno, strerror(errno)));
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
/* do this here to avoid perturbing errno earlier */
|
|
DPRINTF(2, ("intres: got error status of: %d\n", error));
|
|
}
|
|
|
|
return again;
|
|
}
|
|
|
|
|
|
/*
|
|
* openntp - open a socket to the ntp server
|
|
*/
|
|
static void
|
|
openntp(void)
|
|
{
|
|
const char *localhost = "127.0.0.1"; /* Use IPv4 loopback */
|
|
struct addrinfo hints;
|
|
struct addrinfo *addr;
|
|
u_long on;
|
|
int err;
|
|
|
|
if (sockfd != INVALID_SOCKET)
|
|
return;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
/*
|
|
* For now only bother with IPv4
|
|
*/
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
|
|
err = getaddrinfo(localhost, "ntp", &hints, &addr);
|
|
|
|
if (err) {
|
|
#ifdef EAI_SYSTEM
|
|
if (EAI_SYSTEM == err)
|
|
msyslog(LOG_ERR, "getaddrinfo(%s) failed: %m",
|
|
localhost);
|
|
else
|
|
#endif
|
|
msyslog(LOG_ERR, "getaddrinfo(%s) failed: %s",
|
|
localhost, gai_strerror(err));
|
|
resolver_exit(1);
|
|
}
|
|
|
|
sockfd = socket(addr->ai_family, addr->ai_socktype, 0);
|
|
|
|
if (INVALID_SOCKET == sockfd) {
|
|
msyslog(LOG_ERR, "socket() failed: %m");
|
|
resolver_exit(1);
|
|
}
|
|
|
|
#ifndef SYS_WINNT
|
|
/*
|
|
* On Windows only the count of sockets must be less than
|
|
* FD_SETSIZE. On Unix each descriptor's value must be less
|
|
* than FD_SETSIZE, as fd_set is a bit array.
|
|
*/
|
|
if (sockfd >= FD_SETSIZE) {
|
|
msyslog(LOG_ERR, "socket fd %d too large, FD_SETSIZE %d",
|
|
(int)sockfd, FD_SETSIZE);
|
|
resolver_exit(1);
|
|
}
|
|
|
|
/*
|
|
* Make the socket non-blocking. We'll wait with select()
|
|
* Unix: fcntl(O_NONBLOCK) or fcntl(FNDELAY)
|
|
*/
|
|
# ifdef O_NONBLOCK
|
|
if (fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1) {
|
|
msyslog(LOG_ERR, "fcntl(O_NONBLOCK) failed: %m");
|
|
resolver_exit(1);
|
|
}
|
|
# else
|
|
# ifdef FNDELAY
|
|
if (fcntl(sockfd, F_SETFL, FNDELAY) == -1) {
|
|
msyslog(LOG_ERR, "fcntl(FNDELAY) failed: %m");
|
|
resolver_exit(1);
|
|
}
|
|
# else
|
|
# include "Bletch: NEED NON BLOCKING IO"
|
|
# endif /* FNDDELAY */
|
|
# endif /* O_NONBLOCK */
|
|
(void)on; /* quiet unused warning */
|
|
#else /* !SYS_WINNT above */
|
|
/*
|
|
* Make the socket non-blocking. We'll wait with select()
|
|
* Windows: ioctlsocket(FIONBIO)
|
|
*/
|
|
on = 1;
|
|
err = ioctlsocket(sockfd, FIONBIO, &on);
|
|
if (SOCKET_ERROR == err) {
|
|
msyslog(LOG_ERR, "ioctlsocket(FIONBIO) fails: %m");
|
|
resolver_exit(1);
|
|
}
|
|
#endif /* SYS_WINNT */
|
|
|
|
err = connect(sockfd, addr->ai_addr, addr->ai_addrlen);
|
|
if (SOCKET_ERROR == err) {
|
|
msyslog(LOG_ERR, "openntp: connect() failed: %m");
|
|
resolver_exit(1);
|
|
}
|
|
|
|
freeaddrinfo(addr);
|
|
}
|
|
|
|
|
|
/*
|
|
* request - send a configuration request to the server, wait for a response
|
|
*/
|
|
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 /* SYS_WINNT */
|
|
|
|
checkparent(); /* make sure our guy is still running */
|
|
|
|
if (sockfd == INVALID_SOCKET)
|
|
openntp();
|
|
|
|
#ifdef SYS_WINNT
|
|
hReadWriteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
#endif /* SYS_WINNT */
|
|
|
|
/*
|
|
* Try to clear out any previously received traffic so it
|
|
* doesn't fool us. Note the socket is nonblocking.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* Make up a request packet with the configuration info
|
|
*/
|
|
memset((char *)&reqpkt, 0, sizeof(reqpkt));
|
|
|
|
reqpkt.rm_vn_mode = RM_VN_MODE(0, 0, 0);
|
|
reqpkt.auth_seq = AUTH_SEQ(1, 0); /* authenticated, no seq */
|
|
reqpkt.implementation = IMPL_XNTPD; /* local implementation */
|
|
reqpkt.request = REQ_CONFIG; /* configure a new peer */
|
|
reqpkt.err_nitems = ERR_NITEMS(0, 1); /* one item */
|
|
reqpkt.mbz_itemsize = MBZ_ITEMSIZE(sizeof(struct conf_peer));
|
|
/* Make sure mbz_itemsize <= sizeof reqpkt.data */
|
|
if (sizeof(struct conf_peer) > sizeof (reqpkt.data)) {
|
|
msyslog(LOG_ERR, "Bletch: conf_peer is too big for reqpkt.data!");
|
|
resolver_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);
|
|
|
|
/*
|
|
* Done. Send it.
|
|
*/
|
|
#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; /* maybe should exit */
|
|
}
|
|
#else
|
|
/* In the NT world, documentation seems to indicate that there
|
|
* exist _write and _read routines that can be used to do blocking
|
|
* I/O on sockets. Problem is these routines require a socket
|
|
* handle obtained through the _open_osf_handle C run-time API
|
|
* of which there is no explanation in the documentation. We need
|
|
* nonblocking write's and read's anyway for our purpose here.
|
|
* We're therefore forced to deviate a little bit from the Unix
|
|
* model here and use the ReadFile and WriteFile Win32 I/O API's
|
|
* on the socket
|
|
*/
|
|
overlap.Offset = overlap.OffsetHigh = (DWORD)0;
|
|
overlap.hEvent = hReadWriteEvent;
|
|
ret = WriteFile((HANDLE)sockfd, (char *)&reqpkt, REQ_LEN_NOMAC + n,
|
|
NULL, (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;
|
|
}
|
|
if (!GetOverlappedResult((HANDLE)sockfd, (LPOVERLAPPED)&overlap,
|
|
(LPDWORD)&NumberOfBytesWritten, FALSE)) {
|
|
msyslog(LOG_ERR, "GetOverlappedResult for WriteFile fails: %m");
|
|
return 0;
|
|
}
|
|
#endif /* SYS_WINNT */
|
|
|
|
|
|
/*
|
|
* Wait for a response. A weakness of the mode 7 protocol used
|
|
* is that there is no way to associate a response with a
|
|
* particular request, i.e. the response to this configuration
|
|
* request is indistinguishable from that to any other. I should
|
|
* fix this some day. In any event, the time out is fairly
|
|
* pessimistic to make sure that if an answer is coming back
|
|
* at all, we get it.
|
|
*/
|
|
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)
|
|
{
|
|
#ifdef DEBUG
|
|
if (debug)
|
|
msyslog(LOG_INFO, "select() returned 0.");
|
|
#endif
|
|
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 /* Overlapped I/O used on non-blocking sockets on Windows NT */
|
|
ret = ReadFile((HANDLE)sockfd, (char *)&reqpkt, (DWORD)REQ_LEN_MAC,
|
|
NULL, (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 for ReadFile fails: %m");
|
|
return 0;
|
|
}
|
|
continue;
|
|
}
|
|
if (!GetOverlappedResult((HANDLE)sockfd, (LPOVERLAPPED)&overlap,
|
|
(LPDWORD)&NumberOfBytesRead, FALSE)) {
|
|
msyslog(LOG_ERR, "GetOverlappedResult fails: %m");
|
|
return 0;
|
|
}
|
|
n = NumberOfBytesRead;
|
|
#endif /* SYS_WINNT */
|
|
|
|
/*
|
|
* Got one. Check through to make sure it is what
|
|
* we expect.
|
|
*/
|
|
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:
|
|
/* success */
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* nexttoken - return the next token from a line
|
|
*/
|
|
static char *
|
|
nexttoken(
|
|
char **lptr
|
|
)
|
|
{
|
|
register char *cp;
|
|
register char *tstart;
|
|
|
|
cp = *lptr;
|
|
|
|
/*
|
|
* Skip leading white space
|
|
*/
|
|
while (*cp == ' ' || *cp == '\t')
|
|
cp++;
|
|
|
|
/*
|
|
* If this is the end of the line, return nothing.
|
|
*/
|
|
if (*cp == '\n' || *cp == '\0') {
|
|
*lptr = cp;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Must be the start of a token. Record the pointer and look
|
|
* for the end.
|
|
*/
|
|
tstart = cp++;
|
|
while (*cp != ' ' && *cp != '\t' && *cp != '\n' && *cp != '\0')
|
|
cp++;
|
|
|
|
/*
|
|
* Terminate the token with a \0. If this isn't the end of the
|
|
* line, space to the next character.
|
|
*/
|
|
if (*cp == '\n' || *cp == '\0')
|
|
*cp = '\0';
|
|
else
|
|
*cp++ = '\0';
|
|
|
|
*lptr = cp;
|
|
return tstart;
|
|
}
|
|
|
|
|
|
/*
|
|
* readconf - read the configuration information out of the file we
|
|
* were passed. Note that since the file is supposed to be
|
|
* machine generated, we bail out at the first sign of trouble.
|
|
*/
|
|
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);
|
|
resolver_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);
|
|
resolver_exit(1);
|
|
}
|
|
}
|
|
|
|
if (intval[TOK_PEERAF] != AF_UNSPEC && intval[TOK_PEERAF] !=
|
|
AF_INET && intval[TOK_PEERAF] != AF_INET6) {
|
|
msyslog(LOG_ERR, "invalid peer address family (%u) in "
|
|
"file %s", intval[TOK_PEERAF], 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);
|
|
resolver_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);
|
|
resolver_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);
|
|
resolver_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);
|
|
resolver_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);
|
|
resolver_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;
|
|
|
|
/*
|
|
* This is as good as we can check it. Add it in.
|
|
*/
|
|
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], (u_char)intval[TOK_PEERAF]);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* doconfigure - attempt to resolve names and configure the server
|
|
*/
|
|
static void
|
|
doconfigure(
|
|
int dores
|
|
)
|
|
{
|
|
register struct conf_entry *ce;
|
|
register struct conf_entry *ceremove;
|
|
|
|
#ifdef DEBUG
|
|
if (debug > 1)
|
|
msyslog(LOG_INFO, "Running doconfigure %s DNS",
|
|
dores ? "with" : "without" );
|
|
#endif
|
|
|
|
if (dores) /* Reload /etc/resolv.conf - bug 1226 */
|
|
res_init();
|
|
|
|
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)) {
|
|
#ifndef IGNORE_DNS_ERRORS
|
|
msyslog(LOG_ERR,
|
|
"couldn't resolve `%s', giving up on it",
|
|
ce->ce_name);
|
|
ceremove = ce;
|
|
ce = ceremove->ce_next;
|
|
removeentry(ceremove);
|
|
continue;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|