/*
 * Copyright (c) 2005
 *	Hartmut Brandt.
 *	All rights reserved.
 *
 * Author: Harti Brandt <harti@freebsd.org>
 *
 * Redistribution of this software and documentation and use in source and
 * binary forms, with or without modification, are permitted provided that
 * the following conditions are met:
 *
 * 1. Redistributions of source code or documentation must retain the above
 *    copyright notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
 * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Begemot: bsnmp/snmp_ntp/snmp_ntp.c,v 1.9 2005/10/06 07:15:01 brandt_h Exp $
 *
 * NTP interface for SNMPd.
 */

#include <sys/queue.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#ifdef HAVE_STDINT_H
#include <stdint.h>
#elif defined(HAVE_INTTYPES_H)
#include <inttypes.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "support.h"
#include "snmpmod.h"
#include "ntp_tree.h"
#include "ntp_oid.h"

#define	NTPC_MAX	576
#define	NTPC_VERSION	3
#define	NTPC_MODE	6
#define	NTPC_DMAX	468

#define	NTPC_BIT_RESP	0x80
#define	NTPC_BIT_ERROR	0x40
#define	NTPC_BIT_MORE	0x20

#define	NTPC_OPMASK	0x1f
#define	NTPC_OP_READSTAT	1
#define	NTPC_OP_READVAR		2

/* our module handle */
static struct lmodule *module;

/* debug flag */
static uint32_t ntp_debug;
#define DBG_DUMP_PKTS	0x01
#define	DBG_DUMP_VARS	0x02

/* OIDs */
static const struct asn_oid oid_ntpMIB = OIDX_ntpMIB;

/* the Object Resource registration index */
static u_int reg_index;

/* last time we've fetch the system variables */
static uint64_t sysinfo_tick;

/* cached system variables */
static int32_t	sys_leap;
static int	sysb_leap;
static int32_t	sys_stratum;
static int	sysb_stratum;
static int32_t	sys_precision;
static int	sysb_precision;
static char	*sys_rootdelay;
static char	*sys_rootdispersion;
static char	*sys_refid;
static char	sys_reftime[8];
static int	sysb_reftime;
static int32_t	sys_poll;
static int	sysb_poll;
static uint32_t	sys_peer;
static int	sysb_peer;
static u_char	sys_clock[8];
static int	sysb_clock;
static char	*sys_system;
static char	*sys_processor;
static int	sysb_jitter;
static double	sys_jitter;
static int	sysb_stability;
static double	sys_stability;

/* last time we've fetch the peer list */
static uint64_t peers_tick;

/* request sequence number generator */
static uint16_t	seqno;

/* NTPD socket */
static int ntpd_sock;
static void *ntpd_fd;

struct peer {
	/* required entries for macros */
	uint32_t	index;
	TAILQ_ENTRY(peer) link;

	int32_t		config;		/* config bit */
	u_char		srcadr[4];	/* PeerAddress */
	uint32_t	srcport;	/* PeerPort */
	u_char		dstadr[4];	/* HostAddress */
	uint32_t	dstport;	/* HostPort */
	int32_t		leap;		/* Leap */
	int32_t		hmode;		/* Mode */
	int32_t		stratum;	/* Stratum */
	int32_t		ppoll;		/* PeerPoll */
	int32_t		hpoll;		/* HostPoll */
	int32_t		precision;	/* Precision */
	char		*rootdelay;	/* RootDelay */
	char		*rootdispersion;/* RootDispersion */
	char		*refid;		/* RefId */
	u_char		reftime[8];	/* RefTime */
	u_char		orgtime[8];	/* OrgTime */
	u_char		rcvtime[8];	/* ReceiveTime */
	u_char		xmttime[8];	/* TransmitTime */
	u_int32_t	reach;		/* Reach */
	int32_t		timer;		/* Timer */
	char		*offset;	/* Offset */
	char		*delay;		/* Delay */
	char		*dispersion;	/* Dispersion */
	int32_t		filt_entries;
};
TAILQ_HEAD(peer_list, peer);

/* list of peers */
static struct peer_list peers = TAILQ_HEAD_INITIALIZER(peers);

struct filt {
	/* required fields */
	struct asn_oid	index;
	TAILQ_ENTRY(filt) link;

	char		*offset;
	char		*delay;
	char		*dispersion;
};
TAILQ_HEAD(filt_list, filt);

/* list of filters */
static struct filt_list filts = TAILQ_HEAD_INITIALIZER(filts);

/* configuration */
static u_char *ntp_host;
static u_char *ntp_port;
static uint32_t ntp_timeout;

static void ntpd_input(int, void *);
static int open_socket(void);

/* the initialization function */
static int
ntp_init(struct lmodule *mod, int argc, char *argv[] __unused)
{

	module = mod;

	if (argc != 0) {
		syslog(LOG_ERR, "bad number of arguments for %s", __func__);
		return (EINVAL);
	}

	ntp_host = strdup("localhost");
	ntp_port = strdup("ntp");
	ntp_timeout = 50;		/* 0.5sec */

	return (0);
}

/*
 * Module is started
 */
static void
ntp_start(void)
{

	if (open_socket() != -1) {
		ntpd_fd = fd_select(ntpd_sock, ntpd_input, NULL, module);
		if (ntpd_fd == NULL) {
			syslog(LOG_ERR, "fd_select failed on ntpd socket: %m");
			return;
		}
	}
	reg_index = or_register(&oid_ntpMIB, "The MIB for NTP.", module);
}

/*
 * Called, when the module is to be unloaded after it was successfully loaded
 */
static int
ntp_fini(void)
{

	or_unregister(reg_index);
	fd_deselect(ntpd_fd);

	return (0);
}

const struct snmp_module config = {
	.comment =	"This module implements the NTP MIB",
	.init =		ntp_init,
	.start =	ntp_start,
	.fini =		ntp_fini,
	.tree =		ntp_ctree,
	.tree_size =	ntp_CTREE_SIZE,
};

/*
 * Open the NTPD socket
 */
static int
open_socket(void)
{
	struct addrinfo hints, *res, *res0;
	int	error;
	const char *cause;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_DGRAM;

	error = getaddrinfo(ntp_host, ntp_port, &hints, &res0);
	if (error) {
		syslog(LOG_ERR, "%s(%s): %s", ntp_host, ntp_port,
		    gai_strerror(error));
		return (-1);
	}

	ntpd_sock = -1;
	cause = "no address";
	errno = EADDRNOTAVAIL;
	for (res = res0; res != NULL; res = res->ai_next) {
		ntpd_sock = socket(res->ai_family, res->ai_socktype,
		    res->ai_protocol);
		if (ntpd_sock == -1) {
			cause = "socket";
			continue;
		}
		if (connect(ntpd_sock, res->ai_addr, res->ai_addrlen) == -1) {
			cause = "connect";
			(void)close(ntpd_sock);
			ntpd_sock = -1;
			continue;
		}
		break;
	}
	if (ntpd_sock == -1) {
		syslog(LOG_ERR, "%s: %m", cause);
		return (-1);
	}
	freeaddrinfo(res0);
	return (0);
}

/*
 * Dump a packet
 */
static void
dump_packet(const u_char *pkt, size_t ret)
{
	char buf[8 * 3 + 1];
	size_t i, j;

	for (i = 0; i < ret; i += 8) {
		buf[0] = '\0';
		for (j = 0; i + j < (size_t)ret && j < 8; j++)
			sprintf(buf + strlen(buf), " %02x", pkt[i + j]);
		syslog(LOG_INFO, "%04zu:%s", i, buf);
	}
}

/*
 * Execute an NTP request.
 */
static int
ntpd_request(u_int op, u_int associd, const char *vars)
{
	u_char	*rpkt;
	u_char	*ptr;
	size_t	vlen;
	ssize_t	ret;

	if ((rpkt = malloc(NTPC_MAX)) == NULL) {
		syslog(LOG_ERR, "%m");
		return (-1);
	}
	memset(rpkt, 0, NTPC_MAX);

	ptr = rpkt;
	*ptr++ = (NTPC_VERSION << 3) | NTPC_MODE;
	*ptr++ = op;

	if (++seqno == 0)
		seqno++;
	*ptr++ = seqno >> 8;
	*ptr++ = seqno;

	/* skip status */
	ptr += 2;

	*ptr++ = associd >> 8;
	*ptr++ = associd;

	/* skip offset */
	ptr += 2;

	if (vars != NULL) {
		vlen = strlen(vars);
		if (vlen > NTPC_DMAX) {
			syslog(LOG_ERR, "NTP request too long (%zu)", vlen);
			free(rpkt);
			return (-1);
		}
		*ptr++ = vlen >> 8;
		*ptr++ = vlen;

		memcpy(ptr, vars, vlen);
		ptr += vlen;
	} else
		/* skip data length (is already zero) */
		ptr += 2;

	while ((ptr - rpkt) % 4 != 0)
		*ptr++ = 0;

	if (ntp_debug & DBG_DUMP_PKTS) {
		syslog(LOG_INFO, "sending %zd bytes", ptr - rpkt);
		dump_packet(rpkt, ptr - rpkt);
	}

	ret = send(ntpd_sock, rpkt, ptr - rpkt, 0);
	if (ret == -1) {
		syslog(LOG_ERR, "cannot send to ntpd: %m");
		free(rpkt);
		return (-1);
	}
	return (0);
}

/*
 * Callback if packet arrived from NTPD
 */
static int
ntpd_read(uint16_t *op, uint16_t *associd, u_char **data, size_t *datalen)
{
	u_char	pkt[NTPC_MAX + 1];
	u_char	*ptr, *nptr;
	u_int	n;
	ssize_t	ret;
	size_t	z;
	u_int	offset;		/* current offset */
	int	more;		/* more flag */
	int	sel;
	struct timeval inc, end, rem;
	fd_set	iset;

	*datalen = 0;
	*data = NULL;
	offset = 0;

	inc.tv_sec = ntp_timeout / 100;
	inc.tv_usec = (ntp_timeout % 100) * 1000;

	(void)gettimeofday(&end, NULL);
	timeradd(&end, &inc, &end);

  next:
	/* compute remaining time */
	(void)gettimeofday(&rem, NULL);
	if (timercmp(&rem, &end, >=)) {
		/* do a poll */
		rem.tv_sec = 0;
		rem.tv_usec = 0;
	} else {
		timersub(&end, &rem, &rem);
	}

	/* select */
	FD_ZERO(&iset);
	FD_SET(ntpd_sock, &iset);
	sel = select(ntpd_sock + 1, &iset, NULL, NULL, &rem);
	if (sel == -1) {
		if (errno == EINTR)
			goto next;
		syslog(LOG_ERR, "select ntpd_sock: %m");
		free(*data);
		return (-1);
	}
	if (sel == 0) {
		syslog(LOG_ERR, "timeout on NTP connection");
		free(*data);
		return (-1);
	}

	/* now read it */
	ret = recv(ntpd_sock, pkt, sizeof(pkt), 0);
	if (ret == -1) {
		syslog(LOG_ERR, "error reading from ntpd: %m");
		free(*data);
		return (-1);
	}

	if (ntp_debug & DBG_DUMP_PKTS) {
		syslog(LOG_INFO, "got %zd bytes", ret);
		dump_packet(pkt, (size_t)ret);
	}

	ptr = pkt;
	if ((*ptr & 0x3f) != ((NTPC_VERSION << 3) | NTPC_MODE)) {
		syslog(LOG_ERR, "unexpected packet version 0x%x", *ptr);
		free(*data);
		return (-1);
	}
	ptr++;

	if (!(*ptr & NTPC_BIT_RESP)) {
		syslog(LOG_ERR, "not a response packet");
		return (-1);
	}
	if (*ptr & NTPC_BIT_ERROR) {
		z = *datalen - 12;
		if (z > NTPC_DMAX)
			z = NTPC_DMAX;
		syslog(LOG_ERR, "error response: %.*s", (int)z, pkt + 12);
		free(*data);
		return (-1);
	}
	more = (*ptr & NTPC_BIT_MORE);

	*op = *ptr++ & NTPC_OPMASK;

	/* seqno */
	n = *ptr++ << 8;
	n |= *ptr++;

	if (n != seqno) {
		syslog(LOG_ERR, "expecting seqno %u, got %u", seqno, n);
		free(*data);
		return (-1);
	}

	/* status */
	n = *ptr++ << 8;
	n |= *ptr++;

	/* associd */
	*associd = *ptr++ << 8;
	*associd |= *ptr++;

	/* offset */
	n = *ptr++ << 8;
	n |= *ptr++;

	if (n != offset) {
		syslog(LOG_ERR, "offset: expecting %u, got %u", offset, n);
		free(*data);
		return (-1);
	}

	/* count */
	n = *ptr++ << 8;
	n |= *ptr++;

	if ((size_t)ret < 12 + n) {
		syslog(LOG_ERR, "packet too short");
		return (-1);
	}

	nptr = realloc(*data, *datalen + n);
	if (nptr == NULL) {
		syslog(LOG_ERR, "cannot allocate memory: %m");
		free(*data);
		return (-1);
	}
	*data = nptr;

	memcpy(*data + offset, ptr, n);
	*datalen += n;

	if (!more)
		return (0);

	offset += n;
	goto next;
}

/*
 * Send a request and wait for the response
 */
static int
ntpd_dialog(u_int op, u_int associd, const char *vars, u_char **data,
    size_t *datalen)
{
	uint16_t rassocid;
	uint16_t rop;

	if (ntpd_request(op, associd, vars) == -1)
		return (-1);
	if (ntpd_read(&rop, &rassocid, data, datalen) == -1)
		return (-1);

	if (rop != op) {
		syslog(LOG_ERR, "bad response op 0x%x", rop);
		free(data);
		return (-1);
	}

	if (associd != rassocid) {
		syslog(LOG_ERR, "response for wrong associd");
		free(data);
		return (-1);
	}
	return (0);
}

/*
 * Callback if packet arrived from NTPD
 */
static void
ntpd_input(int fd __unused, void *arg __unused)
{
	uint16_t associd;
	uint16_t op;
	u_char	*data;
	size_t	datalen;

	if (ntpd_read(&op, &associd, &data, &datalen) == -1)
		return;

	free(data);
}

/*
 * Find the value of a variable
 */
static int
ntpd_parse(u_char **data, size_t *datalen, char **namep, char **valp)
{
	u_char *ptr = *data;
	u_char *end = ptr + *datalen;
	char *ptr1;
	char endc;

	/* skip leading spaces */
	while (ptr < end && isspace((int)*ptr))
		ptr++;

	if (ptr == end)
		return (0);

	*namep = ptr;

	/* skip to space or '=' or ','*/
	while (ptr < end && !isspace((int)*ptr) && *ptr != '=' && *ptr != ',')
		ptr++;
	endc = *ptr;
	*ptr++ = '\0';

	/* skip space */
	while (ptr < end && isspace((int)*ptr))
		ptr++;

	if (ptr == end || endc == ',') {
		/* no value */
		*valp = NULL;
		*datalen -= ptr - *data;
		*data = ptr;
		return (1);
	}

	if (*ptr == '"') {
		/* quoted */
		ptr++;
		*valp = ptr;
		while (ptr < end && *ptr != '"')
			ptr++;
		if (ptr == end)
			return (0);

		*ptr++ = '\0';

		/* find comma */
		while (ptr < end && isspace((int)*ptr) && *ptr == ',')
			ptr++;
	} else {
		*valp = ptr;

		/* skip to end of value */
		while (ptr < end && *ptr != ',')
			ptr++;

		/* remove trailing blanks */
		for (ptr1 = ptr; ptr1 > *valp; ptr1--)
			if (!isspace((int)ptr1[-1]))
				break;
		*ptr1 = '\0';

		if (ptr < end)
			ptr++;
	}

	*datalen -= ptr - *data;
	*data = ptr;

	return (1);
}

/*
 * Parse an int32 value
 */
static int
val_parse_int32(const char *val, int32_t *p, int32_t min, int32_t max, int base)
{
	long n;
	char *end;

	errno = 0;
	n = strtol(val, &end, base);
	if (errno != 0 || *end != '\0')
		return (0);
	if (n < min || n > max)
		return (0);
	*p = (int32_t)n;
	return (1);
}

/*
 * Parse an uint32 value
 */
static int
val_parse_uint32(const char *val, uint32_t *p, uint32_t min, uint32_t max,
    int base)
{
	u_long n;
	char *end;

	errno = 0;
	n = strtoul(val, &end, base);
	if (errno != 0 || *end != '\0')
		return (0);
	if (n < min || n > max)
		return (0);
	*p = (uint32_t)n;
	return (1);
}

/*
 * Parse a double
 */
static int
val_parse_double(const char *val, double *p)
{
	char *end;

	errno = 0;
	*p = strtod(val, &end);
	if (errno != 0 || *end != '\0')
		return (0);
	return (1);
}

static int
val_parse_ts(const char *val, char *buf)
{
	int r, n;
	u_int i, f;

	if (strlen(val) > 2 && val[0] == '0' && val[1] == 'x') {
		/* hex format */
		r = sscanf(val + 2, "%x.%x%n", &i, &f, &n);
		if (r != 2 || (size_t)n != strlen(val + 2))
			return (0);
	} else {
		/* probably decimal */
		r = sscanf(val, "%d.%d%n", &i, &f, &n);
		if (r != 2 || (size_t)n != strlen(val))
			return (0);
	}
	buf[0] = i >> 24;
	buf[1] = i >> 16;
	buf[2] = i >>  8;
	buf[3] = i >>  0;
	buf[4] = f >> 24;
	buf[5] = f >> 16;
	buf[6] = f >>  8;
	buf[7] = f >>  0;
	return (1);
}

/*
 * Parse an IP address. This resolves non-numeric names.
 */
static int
val_parse_ip(const char *val, u_char ip[4])
{
	int r, n, error;
	struct addrinfo hints, *res0;
	struct sockaddr_in *sin_local;

	r = sscanf(val, "%hhd.%hhd.%hhd.%hhd%n",
	    &ip[0], &ip[1], &ip[2], &ip[3], &n);
	if (n == 4 && (size_t)n == strlen(val))
		return (0);

	memset(ip, 0, 4);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_DGRAM;

	error = getaddrinfo(val, NULL, &hints, &res0);
	if (error) {
		syslog(LOG_ERR, "%s: %s", val, gai_strerror(error));
		return (-1);
	}
	if (res0 == NULL) {
		syslog(LOG_ERR, "%s: no address", val);
		return (-1);
	}

	sin_local = (struct sockaddr_in *)(void *)res0->ai_addr;
	ip[3] = sin_local->sin_addr.s_addr >> 24;
	ip[2] = sin_local->sin_addr.s_addr >> 16;
	ip[1] = sin_local->sin_addr.s_addr >>  8;
	ip[0] = sin_local->sin_addr.s_addr >>  0;

	freeaddrinfo(res0);
	return (0);
}

/*
 * Fetch system info
 */
static int
fetch_sysinfo(void)
{
	u_char *data;
	u_char *ptr;
	size_t datalen;
	char *name;
	char *val;

	if (ntpd_dialog(NTPC_OP_READVAR, 0,
	    "leap,stratum,precision,rootdelay,rootdispersion,refid,reftime,"
	    "poll,peer,clock,system,processor,jitter,stability",
	    &data, &datalen))
		return (-1);

	/* clear info */
	sysb_leap = 0;
	sysb_stratum = 0;
	sysb_precision = 0;
	free(sys_rootdelay);
	sys_rootdelay = NULL;
	free(sys_rootdispersion);
	sys_rootdispersion = NULL;
	free(sys_refid);
	sys_refid = NULL;
	sysb_reftime = 0;
	sysb_poll = 0;
	sysb_peer = 0;
	sysb_clock = 0;
	free(sys_system);
	sys_system = NULL;
	free(sys_processor);
	sys_processor = NULL;
	sysb_jitter = 0;
	sysb_stability = 0;

	ptr = data;
	while (ntpd_parse(&ptr, &datalen, &name, &val)) {
		if (ntp_debug & DBG_DUMP_VARS)
			syslog(LOG_DEBUG, "%s: '%s'='%s'", __func__, name, val);
		if (strcmp(name, "leap") == 0 ||
		    strcmp(name, "sys.leap") == 0) {
			sysb_leap = val_parse_int32(val, &sys_leap,
			    0, 3, 2);

		} else if (strcmp(name, "stratum") == 0 ||
		    strcmp(name, "sys.stratum") == 0) {
			sysb_stratum = val_parse_int32(val, &sys_stratum,
			    0, 255, 0);

		} else if (strcmp(name, "precision") == 0 ||
		    strcmp(name, "sys.precision") == 0) {
			sysb_precision = val_parse_int32(val, &sys_precision,
			    INT32_MIN, INT32_MAX, 0);

		} else if (strcmp(name, "rootdelay") == 0 ||
		    strcmp(name, "sys.rootdelay") == 0) {
			sys_rootdelay = strdup(val);

		} else if (strcmp(name, "rootdispersion") == 0 ||
		    strcmp(name, "sys.rootdispersion") == 0) {
			sys_rootdispersion = strdup(val);

		} else if (strcmp(name, "refid") == 0 ||
		    strcmp(name, "sys.refid") == 0) {
			sys_refid = strdup(val);

		} else if (strcmp(name, "reftime") == 0 ||
		    strcmp(name, "sys.reftime") == 0) {
			sysb_reftime = val_parse_ts(val, sys_reftime);

		} else if (strcmp(name, "poll") == 0 ||
		    strcmp(name, "sys.poll") == 0) {
			sysb_poll = val_parse_int32(val, &sys_poll,
			    INT32_MIN, INT32_MAX, 0);

		} else if (strcmp(name, "peer") == 0 ||
		    strcmp(name, "sys.peer") == 0) {
			sysb_peer = val_parse_uint32(val, &sys_peer,
			    0, UINT32_MAX, 0);

		} else if (strcmp(name, "clock") == 0 ||
		    strcmp(name, "sys.clock") == 0) {
			sysb_clock = val_parse_ts(val, sys_clock);

		} else if (strcmp(name, "system") == 0 ||
		    strcmp(name, "sys.system") == 0) {
			sys_system = strdup(val);

		} else if (strcmp(name, "processor") == 0 ||
		    strcmp(name, "sys.processor") == 0) {
			sys_processor = strdup(val);

		} else if (strcmp(name, "jitter") == 0 ||
		    strcmp(name, "sys.jitter") == 0) {
			sysb_jitter = val_parse_double(val, &sys_jitter);

		} else if (strcmp(name, "stability") == 0 ||
		    strcmp(name, "sys.stability") == 0) {
			sysb_stability = val_parse_double(val, &sys_stability);
		}
	}

	free(data);
	return (0);
}

static int
parse_filt(char *val, uint16_t associd, int which)
{
	char *w;
	int cnt;
	struct filt *f;

	cnt = 0;
	for (w = strtok(val, " \t"); w != NULL; w = strtok(NULL, " \t")) {
		TAILQ_FOREACH(f, &filts, link)
			if (f->index.subs[0] == associd &&
			    f->index.subs[1] == (asn_subid_t)(cnt + 1))
				break;
		if (f == NULL) {
			f = malloc(sizeof(*f));
			memset(f, 0, sizeof(*f));
			f->index.len = 2;
			f->index.subs[0] = associd;
			f->index.subs[1] = cnt + 1;

			INSERT_OBJECT_OID(f, &filts);
		}

		switch (which) {

		  case 0:
			f->offset = strdup(w);
			break;

		  case 1:
			f->delay = strdup(w);
			break;

		  case 2:
			f->dispersion = strdup(w);
			break;

		  default:
			abort();
		}
		cnt++;
	}
	return (cnt);
}

/*
 * Fetch the complete peer list
 */
static int
fetch_peers(void)
{
	u_char *data, *pdata, *ptr;
	size_t datalen, pdatalen;
	int i;
	struct peer *p;
	struct filt *f;
	uint16_t associd;
	char *name, *val;

	/* free the old list */
	while ((p = TAILQ_FIRST(&peers)) != NULL) {
		TAILQ_REMOVE(&peers, p, link);
		free(p->rootdelay);
		free(p->rootdispersion);
		free(p->refid);
		free(p->offset);
		free(p->delay);
		free(p->dispersion);
		free(p);
	}
	while ((f = TAILQ_FIRST(&filts)) != NULL) {
		TAILQ_REMOVE(&filts, f, link);
		free(f->offset);
		free(f->delay);
		free(f->dispersion);
		free(f);
	}

	/* fetch the list of associations */
	if (ntpd_dialog(NTPC_OP_READSTAT, 0, NULL, &data, &datalen))
		return (-1);

	for (i = 0; i < (int)(datalen / 4); i++) {
		associd  = data[4 * i + 0] << 8;
		associd |= data[4 * i + 1] << 0;

		/* ask for the association variables */
		if (ntpd_dialog(NTPC_OP_READVAR, associd,
		    "config,srcadr,srcport,dstadr,dstport,leap,hmode,stratum,"
		    "hpoll,ppoll,precision,rootdelay,rootdispersion,refid,"
		    "reftime,org,rec,xmt,reach,timer,offset,delay,dispersion,"
		    "filtdelay,filtoffset,filtdisp",
		    &pdata, &pdatalen)) {
			free(data);
			return (-1);
		}

		/* now save and parse the data */
		p = malloc(sizeof(*p));
		if (p == NULL) {
			free(data);
			syslog(LOG_ERR, "%m");
			return (-1);
		}
		memset(p, 0, sizeof(*p));
		p->index = associd;
		INSERT_OBJECT_INT(p, &peers);

		ptr = pdata;
		while (ntpd_parse(&ptr, &pdatalen, &name, &val)) {
			if (ntp_debug & DBG_DUMP_VARS)
				syslog(LOG_DEBUG, "%s: '%s'='%s'",
				    __func__, name, val);
			if (strcmp(name, "config") == 0 ||
			    strcmp(name, "peer.config") == 0) {
				val_parse_int32(val, &p->config, 0, 1, 0);

			} else if (strcmp(name, "srcadr") == 0 ||
			    strcmp(name, "peer.srcadr") == 0) {
				val_parse_ip(val, p->srcadr);

			} else if (strcmp(name, "srcport") == 0 ||
			    strcmp(name, "peer.srcport") == 0) {
				val_parse_uint32(val, &p->srcport,
				    1, 65535, 0);

			} else if (strcmp(name, "dstadr") == 0 ||
			    strcmp(name, "peer.dstadr") == 0) {
				val_parse_ip(val, p->dstadr);

			} else if (strcmp(name, "dstport") == 0 ||
			    strcmp(name, "peer.dstport") == 0) {
				val_parse_uint32(val, &p->dstport,
				    1, 65535, 0);

			} else if (strcmp(name, "leap") == 0 ||
			    strcmp(name, "peer.leap") == 0) {
				val_parse_int32(val, &p->leap, 0, 3, 2);

			} else if (strcmp(name, "hmode") == 0 ||
			    strcmp(name, "peer.hmode") == 0) {
				val_parse_int32(val, &p->hmode, 0, 7, 0);

			} else if (strcmp(name, "stratum") == 0 ||
			    strcmp(name, "peer.stratum") == 0) {
				val_parse_int32(val, &p->stratum, 0, 255, 0);

			} else if (strcmp(name, "ppoll") == 0 ||
			    strcmp(name, "peer.ppoll") == 0) {
				val_parse_int32(val, &p->ppoll,
				    INT32_MIN, INT32_MAX, 0);

			} else if (strcmp(name, "hpoll") == 0 ||
			    strcmp(name, "peer.hpoll") == 0) {
				val_parse_int32(val, &p->hpoll,
				    INT32_MIN, INT32_MAX, 0);

			} else if (strcmp(name, "precision") == 0 ||
			    strcmp(name, "peer.precision") == 0) {
				val_parse_int32(val, &p->hpoll,
				    INT32_MIN, INT32_MAX, 0);

			} else if (strcmp(name, "rootdelay") == 0 ||
			    strcmp(name, "peer.rootdelay") == 0) {
				p->rootdelay = strdup(val);

			} else if (strcmp(name, "rootdispersion") == 0 ||
			    strcmp(name, "peer.rootdispersion") == 0) {
				p->rootdispersion = strdup(val);

			} else if (strcmp(name, "refid") == 0 ||
			    strcmp(name, "peer.refid") == 0) {
				p->refid = strdup(val);

			} else if (strcmp(name, "reftime") == 0 ||
			    strcmp(name, "sys.reftime") == 0) {
				val_parse_ts(val, p->reftime);

			} else if (strcmp(name, "org") == 0 ||
			    strcmp(name, "sys.org") == 0) {
				val_parse_ts(val, p->orgtime);

			} else if (strcmp(name, "rec") == 0 ||
			    strcmp(name, "sys.rec") == 0) {
				val_parse_ts(val, p->rcvtime);

			} else if (strcmp(name, "xmt") == 0 ||
			    strcmp(name, "sys.xmt") == 0) {
				val_parse_ts(val, p->xmttime);

			} else if (strcmp(name, "reach") == 0 ||
			    strcmp(name, "peer.reach") == 0) {
				val_parse_uint32(val, &p->reach,
				    0, 65535, 0);

			} else if (strcmp(name, "timer") == 0 ||
			    strcmp(name, "peer.timer") == 0) {
				val_parse_int32(val, &p->timer,
				    INT32_MIN, INT32_MAX, 0);

			} else if (strcmp(name, "offset") == 0 ||
			    strcmp(name, "peer.offset") == 0) {
				p->offset = strdup(val);

			} else if (strcmp(name, "delay") == 0 ||
			    strcmp(name, "peer.delay") == 0) {
				p->delay = strdup(val);

			} else if (strcmp(name, "dispersion") == 0 ||
			    strcmp(name, "peer.dispersion") == 0) {
				p->dispersion = strdup(val);

			} else if (strcmp(name, "filtdelay") == 0 ||
			    strcmp(name, "peer.filtdelay") == 0) {
				p->filt_entries = parse_filt(val, associd, 0);

			} else if (strcmp(name, "filtoffset") == 0 ||
			    strcmp(name, "peer.filtoffset") == 0) {
				p->filt_entries = parse_filt(val, associd, 1);

			} else if (strcmp(name, "filtdisp") == 0 ||
			    strcmp(name, "peer.filtdisp") == 0) {
				p->filt_entries = parse_filt(val, associd, 2);
			}
		}
		free(pdata);
	}

	free(data);
	return (0);
}

/*
 * System variables - read-only scalars only.
 */
int
op_ntpSystem(struct snmp_context *ctx __unused, struct snmp_value *value,
    u_int sub, u_int iidx __unused, enum snmp_op op)
{
	asn_subid_t which = value->var.subs[sub - 1];

	switch (op) {

	  case SNMP_OP_GETNEXT:
		abort();

	  case SNMP_OP_GET:
		if (this_tick > sysinfo_tick) {
			if (fetch_sysinfo() == -1)
				return (SNMP_ERR_GENERR);
			sysinfo_tick = this_tick;
		}

		switch (which) {

		  case LEAF_ntpSysLeap:
			if (!sysb_leap)
				return (SNMP_ERR_NOSUCHNAME);
			value->v.integer = sys_leap;
			break;

		  case LEAF_ntpSysStratum:
			if (!sysb_stratum)
				return (SNMP_ERR_NOSUCHNAME);
			value->v.integer = sys_stratum;
			break;

		  case LEAF_ntpSysPrecision:
			if (!sysb_precision)
				return (SNMP_ERR_NOSUCHNAME);
			value->v.integer = sys_precision;
			break;

		  case LEAF_ntpSysRootDelay:
			if (sys_rootdelay == NULL)
				return (SNMP_ERR_NOSUCHNAME);
			return (string_get(value, sys_rootdelay, -1));

		  case LEAF_ntpSysRootDispersion:
			if (sys_rootdispersion == NULL)
				return (SNMP_ERR_NOSUCHNAME);
			return (string_get(value, sys_rootdispersion, -1));

		  case LEAF_ntpSysRefId:
			if (sys_refid == NULL)
				return (SNMP_ERR_NOSUCHNAME);
			return (string_get(value, sys_refid, -1));

		  case LEAF_ntpSysRefTime:
			if (sysb_reftime == 0)
				return (SNMP_ERR_NOSUCHNAME);
			return (string_get(value, sys_reftime, 8));

		  case LEAF_ntpSysPoll:
			if (sysb_poll == 0)
				return (SNMP_ERR_NOSUCHNAME);
			value->v.integer = sys_poll;
			break;

		  case LEAF_ntpSysPeer:
			if (sysb_peer == 0)
				return (SNMP_ERR_NOSUCHNAME);
			value->v.uint32 = sys_peer;
			break;

		  case LEAF_ntpSysClock:
			if (sysb_clock == 0)
				return (SNMP_ERR_NOSUCHNAME);
			return (string_get(value, sys_clock, 8));

		  case LEAF_ntpSysSystem:
			if (sys_system == NULL)
				return (SNMP_ERR_NOSUCHNAME);
			return (string_get(value, sys_system, -1));

		  case LEAF_ntpSysProcessor:
			if (sys_processor == NULL)
				return (SNMP_ERR_NOSUCHNAME);
			return (string_get(value, sys_processor, -1));

		  default:
			abort();
		}
		return (SNMP_ERR_NOERROR);

	  case SNMP_OP_SET:
		return (SNMP_ERR_NOT_WRITEABLE);

	  case SNMP_OP_COMMIT:
	  case SNMP_OP_ROLLBACK:
		abort();
	}
	abort();
}

int
op_ntpPeersVarTable(struct snmp_context *ctx __unused, struct snmp_value *value,
    u_int sub, u_int iidx, enum snmp_op op)
{
	asn_subid_t which = value->var.subs[sub - 1];
	uint32_t peer;
	struct peer *t;

	if (this_tick > peers_tick) {
		if (fetch_peers() == -1)
			return (SNMP_ERR_GENERR);
		peers_tick = this_tick;
	}

	switch (op) {

	  case SNMP_OP_GETNEXT:
		t = NEXT_OBJECT_INT(&peers, &value->var, sub);
		if (t == NULL)
			return (SNMP_ERR_NOSUCHNAME);
		value->var.len = sub + 1;
		value->var.subs[sub] = t->index;
		break;

	  case SNMP_OP_GET:
		t = FIND_OBJECT_INT(&peers, &value->var, sub);
		if (t == NULL)
			return (SNMP_ERR_NOSUCHNAME);
		break;

	  case SNMP_OP_SET:
		if (index_decode(&value->var, sub, iidx, &peer))
			return (SNMP_ERR_NO_CREATION);
		t = FIND_OBJECT_INT(&peers, &value->var, sub);
		if (t != NULL)
			return (SNMP_ERR_NOT_WRITEABLE);
		return (SNMP_ERR_NO_CREATION);

	  case SNMP_OP_COMMIT:
	  case SNMP_OP_ROLLBACK:
	  default:
		abort();
	}

	/*
	 * Come here for GET and COMMIT
	 */
	switch (which) {

	  case LEAF_ntpPeersConfigured:
		value->v.integer = t->config;
		break;

	  case LEAF_ntpPeersPeerAddress:
		return (ip_get(value, t->srcadr));

	  case LEAF_ntpPeersPeerPort:
		value->v.uint32 = t->srcport;
		break;

	  case LEAF_ntpPeersHostAddress:
		return (ip_get(value, t->dstadr));

	  case LEAF_ntpPeersHostPort:
		value->v.uint32 = t->dstport;
		break;

	  case LEAF_ntpPeersLeap:
		value->v.integer = t->leap;
		break;

	  case LEAF_ntpPeersMode:
		value->v.integer = t->hmode;
		break;

	  case LEAF_ntpPeersStratum:
		value->v.integer = t->stratum;
		break;

	  case LEAF_ntpPeersPeerPoll:
		value->v.integer = t->ppoll;
		break;

	  case LEAF_ntpPeersHostPoll:
		value->v.integer = t->hpoll;
		break;

	  case LEAF_ntpPeersPrecision:
		value->v.integer = t->precision;
		break;

	  case LEAF_ntpPeersRootDelay:
		return (string_get(value, t->rootdelay, -1));

	  case LEAF_ntpPeersRootDispersion:
		return (string_get(value, t->rootdispersion, -1));

	  case LEAF_ntpPeersRefId:
		return (string_get(value, t->refid, -1));

	  case LEAF_ntpPeersRefTime:
		return (string_get(value, t->reftime, 8));

	  case LEAF_ntpPeersOrgTime:
		return (string_get(value, t->orgtime, 8));

	  case LEAF_ntpPeersReceiveTime:
		return (string_get(value, t->rcvtime, 8));

	  case LEAF_ntpPeersTransmitTime:
		return (string_get(value, t->xmttime, 8));

	  case LEAF_ntpPeersReach:
		value->v.uint32 = t->reach;
		break;

	  case LEAF_ntpPeersTimer:
		value->v.uint32 = t->timer;
		break;

	  case LEAF_ntpPeersOffset:
		return (string_get(value, t->offset, -1));

	  case LEAF_ntpPeersDelay:
		return (string_get(value, t->delay, -1));

	  case LEAF_ntpPeersDispersion:
		return (string_get(value, t->dispersion, -1));

	  default:
		abort();
	}
	return (SNMP_ERR_NOERROR);
}


int
op_ntpFilterPeersVarTable(struct snmp_context *ctx __unused,
    struct snmp_value *value, u_int sub, u_int iidx, enum snmp_op op)
{
	asn_subid_t which = value->var.subs[sub - 1];
	uint32_t peer;
	struct peer *t;

	if (this_tick > peers_tick) {
		if (fetch_peers() == -1)
			return (SNMP_ERR_GENERR);
		peers_tick = this_tick;
	}

	switch (op) {

	  case SNMP_OP_GETNEXT:
		t = NEXT_OBJECT_INT(&peers, &value->var, sub);
		if (t == NULL)
			return (SNMP_ERR_NOSUCHNAME);
		value->var.len = sub + 1;
		value->var.subs[sub] = t->index;
		break;

	  case SNMP_OP_GET:
		t = FIND_OBJECT_INT(&peers, &value->var, sub);
		if (t == NULL)
			return (SNMP_ERR_NOSUCHNAME);
		break;

	  case SNMP_OP_SET:
		if (index_decode(&value->var, sub, iidx, &peer))
			return (SNMP_ERR_NO_CREATION);
		t = FIND_OBJECT_INT(&peers, &value->var, sub);
		if (t != NULL)
			return (SNMP_ERR_NOT_WRITEABLE);
		return (SNMP_ERR_NO_CREATION);

	  case SNMP_OP_COMMIT:
	  case SNMP_OP_ROLLBACK:
	  default:
		abort();
	}

	/*
	 * Come here for GET and COMMIT
	 */
	switch (which) {

	  case LEAF_ntpFilterValidEntries:
		value->v.integer = t->filt_entries;
		break;

	  default:
		abort();
	}
	return (SNMP_ERR_NOERROR);
}

int
op_ntpFilterRegisterTable(struct snmp_context *ctx __unused, struct snmp_value *value __unused,
    u_int sub __unused, u_int iidx __unused, enum snmp_op op __unused)
{
	asn_subid_t which = value->var.subs[sub - 1];
	uint32_t peer;
	uint32_t filt;
	struct filt *t;

	if (this_tick > peers_tick) {
		if (fetch_peers() == -1)
			return (SNMP_ERR_GENERR);
		peers_tick = this_tick;
	}

	switch (op) {

	  case SNMP_OP_GETNEXT:
		t = NEXT_OBJECT_OID(&filts, &value->var, sub);
		if (t == NULL)
			return (SNMP_ERR_NOSUCHNAME);
		index_append(&value->var, sub, &t->index);
		break;

	  case SNMP_OP_GET:
		t = FIND_OBJECT_OID(&filts, &value->var, sub);
		if (t == NULL)
			return (SNMP_ERR_NOSUCHNAME);
		break;

	  case SNMP_OP_SET:
		if (index_decode(&value->var, sub, iidx, &peer, &filt))
			return (SNMP_ERR_NO_CREATION);
		t = FIND_OBJECT_OID(&filts, &value->var, sub);
		if (t != NULL)
			return (SNMP_ERR_NOT_WRITEABLE);
		return (SNMP_ERR_NO_CREATION);

	  case SNMP_OP_COMMIT:
	  case SNMP_OP_ROLLBACK:
	  default:
		abort();
	}

	/*
	 * Come here for GET and COMMIT
	 */
	switch (which) {

	  case LEAF_ntpFilterPeersOffset:
		return (string_get(value, t->offset, -1));

	  case LEAF_ntpFilterPeersDelay:
		return (string_get(value, t->delay, -1));

	  case LEAF_ntpFilterPeersDispersion:
		return (string_get(value, t->dispersion, -1));

	  default:
		abort();
	}
	return (SNMP_ERR_NOERROR);
}

/*
 * System variables - read-only scalars only.
 */
int
op_begemot_ntp(struct snmp_context *ctx __unused, struct snmp_value *value,
    u_int sub, u_int iidx __unused, enum snmp_op op)
{
	asn_subid_t which = value->var.subs[sub - 1];
	int ret;

	switch (op) {

	  case SNMP_OP_GETNEXT:
		abort();

	  case SNMP_OP_GET:
		switch (which) {

		  case LEAF_begemotNtpHost:
			return (string_get(value, ntp_host, -1));

		  case LEAF_begemotNtpPort:
			return (string_get(value, ntp_port, -1));

		  case LEAF_begemotNtpTimeout:
			value->v.uint32 = ntp_timeout;
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpDebug:
			value->v.uint32 = ntp_debug;
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpJitter:
			if (this_tick > sysinfo_tick) {
				if (fetch_sysinfo() == -1)
					return (SNMP_ERR_GENERR);
				sysinfo_tick = this_tick;
			}
			if (!sysb_jitter)
				return (SNMP_ERR_NOSUCHNAME);
			value->v.counter64 = sys_jitter / 1000 * (1ULL << 32);
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpStability:
			if (this_tick > sysinfo_tick) {
				if (fetch_sysinfo() == -1)
					return (SNMP_ERR_GENERR);
				sysinfo_tick = this_tick;
			}
			if (!sysb_stability)
				return (SNMP_ERR_NOSUCHNAME);
			value->v.counter64 = sys_stability * (1ULL << 32);
			return (SNMP_ERR_NOERROR);
		}
		abort();

	  case SNMP_OP_SET:
		switch (which) {

		  case LEAF_begemotNtpHost:
			/* only at initialization */
			if (community != COMM_INITIALIZE)
				return (SNMP_ERR_NOT_WRITEABLE);

			if ((ret = string_save(value, ctx, -1, &ntp_host))
			    != SNMP_ERR_NOERROR)
				return (ret);
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpPort:
			/* only at initialization */
			if (community != COMM_INITIALIZE)
				return (SNMP_ERR_NOT_WRITEABLE);

			if ((ret = string_save(value, ctx, -1, &ntp_port))
			    != SNMP_ERR_NOERROR)
				return (ret);
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpTimeout:
			ctx->scratch->int1 = ntp_timeout;
			if (value->v.uint32 < 1)
				return (SNMP_ERR_WRONG_VALUE);
			ntp_timeout = value->v.integer;
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpDebug:
			ctx->scratch->int1 = ntp_debug;
			ntp_debug = value->v.integer;
			return (SNMP_ERR_NOERROR);
		}
		abort();

	  case SNMP_OP_ROLLBACK:
		switch (which) {

		  case LEAF_begemotNtpHost:
			string_rollback(ctx, &ntp_host);
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpPort:
			string_rollback(ctx, &ntp_port);
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpTimeout:
			ntp_timeout = ctx->scratch->int1;
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpDebug:
			ntp_debug = ctx->scratch->int1;
			return (SNMP_ERR_NOERROR);
		}
		abort();

	  case SNMP_OP_COMMIT:
		switch (which) {

		  case LEAF_begemotNtpHost:
			string_commit(ctx);
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpPort:
			string_commit(ctx);
			return (SNMP_ERR_NOERROR);

		  case LEAF_begemotNtpTimeout:
		  case LEAF_begemotNtpDebug:
			return (SNMP_ERR_NOERROR);
		}
		abort();
	}
	abort();
}
