/*
 * ntp_restrict.c - determine host restrictions
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <sys/types.h>

#include "ntpd.h"
#include "ntp_if.h"
#include "ntp_stdlib.h"

/*
 * This code keeps a simple address-and-mask list of hosts we want
 * to place restrictions on (or remove them from). The restrictions
 * are implemented as a set of flags which tell you what the host
 * can't do. There is a subroutine entry to return the flags. The
 * list is kept sorted to reduce the average number of comparisons
 * and make sure you get the set of restrictions most specific to
 * the address.
 *
 * The algorithm is that, when looking up a host, it is first assumed
 * that the default set of restrictions will apply. It then searches
 * down through the list. Whenever it finds a match it adopts the
 * match's flags instead. When you hit the point where the sorted
 * address is greater than the target, you return with the last set of
 * flags you found. Because of the ordering of the list, the most
 * specific match will provide the final set of flags.
 *
 * This was originally intended to restrict you from sync'ing to your
 * own broadcasts when you are doing that, by restricting yourself from
 * your own interfaces. It was also thought it would sometimes be useful
 * to keep a misbehaving host or two from abusing your primary clock. It
 * has been expanded, however, to suit the needs of those with more
 * restrictive access policies.
 */
/*
 * We will use two lists, one for IPv4 addresses and one for IPv6
 * addresses. This is not protocol-independant but for now I can't
 * find a way to respect this. We'll check this later... JFB 07/2001
 */
#define SET_IPV6_ADDR_MASK(dst, src, msk) \
	do { \
		int idx; \
		for (idx = 0; idx < 16; idx++) { \
			(dst)->s6_addr[idx] = \
			    (u_char) ((src)->s6_addr[idx] & (msk)->s6_addr[idx]); \
		} \
	} while (0)

/*
 * Memory allocation parameters.  We allocate INITRESLIST entries
 * initially, and add INCRESLIST entries to the free list whenever
 * we run out.
 */
#define	INITRESLIST	10
#define	INCRESLIST	5

#define RES_AVG		8.	/* interpacket averaging factor */

/*
 * The restriction list
 */
struct restrictlist *restrictlist;
struct restrictlist6 *restrictlist6;
static	int restrictcount;	/* count of entries in the res list */
static	int restrictcount6;	/* count of entries in the res list 2*/

/*
 * The free list and associated counters.  Also some uninteresting
 * stat counters.
 */
static	struct restrictlist *resfree;
static	struct restrictlist6 *resfree6;
static	int numresfree;		/* number of structures on free list */
static	int numresfree6;	/* number of structures on free list 2 */

static	u_long res_calls;
static	u_long res_found;
static	u_long res_not_found;

/*
 * Parameters of the RES_LIMITED restriction option.
 */
u_long res_avg_interval = 5;	/* min average interpacket interval */
u_long res_min_interval = 1;	/* min interpacket interval */

/*
 * Count number of restriction entries referring to RES_LIMITED controls
 * activation/deactivation of monitoring (with respect to RES_LIMITED
 * control)
 */
static	u_long res_limited_refcnt;
static	u_long res_limited_refcnt6;

/*
 * Our initial allocation of lists entries.
 */
static	struct restrictlist resinit[INITRESLIST];
static	struct restrictlist6 resinit6[INITRESLIST];

/*
 * init_restrict - initialize the restriction data structures
 */
void
init_restrict(void)
{
	register int i;

	/*
	 * Zero the list and put all but one on the free list
	 */
	resfree = 0;
	memset((char *)resinit, 0, sizeof resinit);
	resfree6 = 0;
	memset((char *)resinit6, 0, sizeof resinit6);
	for (i = 1; i < INITRESLIST; i++) {
		resinit[i].next = resfree;
		resinit6[i].next = resfree6;
		resfree = &resinit[i];
		resfree6 = &resinit6[i];
	}
	numresfree = INITRESLIST-1;
	numresfree6 = INITRESLIST-1;

	/*
	 * Put the remaining item at the head of the list as our default
	 * entry. Everything in here should be zero for now.
	 */
	resinit[0].addr = htonl(INADDR_ANY);
	resinit[0].mask = 0;
	memset(&resinit6[0].addr6, 0, sizeof(struct in6_addr)); 
	memset(&resinit6[0].mask6, 0, sizeof(struct in6_addr)); 
	restrictlist = &resinit[0];
	restrictlist6 = &resinit6[0];
	restrictcount = 1;
	restrictcount = 2;

	/*
	 * fix up stat counters
	 */
	res_calls = 0;
	res_found = 0;
	res_not_found = 0;

	/*
	 * set default values for RES_LIMIT functionality
	 */
	res_limited_refcnt = 0;
	res_limited_refcnt6 = 0;
}


/*
 * restrictions - return restrictions for this host
 */
int
restrictions(
	struct sockaddr_storage *srcadr
	)
{
	struct restrictlist *rl;
	struct restrictlist *match = NULL;
	struct restrictlist6 *rl6;
	struct restrictlist6 *match6 = NULL;
	struct in6_addr hostaddr6;
	struct in6_addr hostservaddr6;
	u_int32	hostaddr;
	int	flags = 0;
	int	isntpport;

	res_calls++;
	if (srcadr->ss_family == AF_INET) {
		/*
		 * We need the host address in host order.  Also need to
		 * know whether this is from the ntp port or not.
		 */
		hostaddr = SRCADR(srcadr);
		isntpport = (SRCPORT(srcadr) == NTP_PORT);

		/*
		 * Ignore any packets with a multicast source address
		 * (this should be done early in the receive process,
		 * later!)
		 */
		if (IN_CLASSD(SRCADR(srcadr)))
			return (int)RES_IGNORE;

		/*
		 * Set match to first entry, which is default entry.
		 * Work our way down from there.
		 */
		match = restrictlist;
		for (rl = match->next; rl != 0 && rl->addr <= hostaddr;
		    rl = rl->next)
			if ((hostaddr & rl->mask) == rl->addr) {
				if ((rl->mflags & RESM_NTPONLY) &&
				    !isntpport)
					continue;
				match = rl;
			}
		match->count++;
		if (match == restrictlist)
			res_not_found++;
		else
			res_found++;
		flags = match->flags;
	}

	/* IPv6 source address */
	if (srcadr->ss_family == AF_INET6) {
		/*
		 * Need to know whether this is from the ntp port or
		 * not.
		 */
		hostaddr6 = GET_INADDR6(*srcadr);
		isntpport = (ntohs((
		    (struct sockaddr_in6 *)srcadr)->sin6_port) ==
		    NTP_PORT);

		/*
		 * Ignore any packets with a multicast source address
		 * (this should be done early in the receive process,
		 * later!)
		 */
		if (IN6_IS_ADDR_MULTICAST(&hostaddr6))
			return (int)RES_IGNORE;

		/*
		 * Set match to first entry, which is default entry.
		 *  Work our way down from there.
		 */
		match6 = restrictlist6;
		for (rl6 = match6->next; rl6 != 0 &&
		    (memcmp(&(rl6->addr6), &hostaddr6,
		    sizeof(hostaddr6)) <= 0); rl6 = rl6->next) {
			SET_IPV6_ADDR_MASK(&hostservaddr6, &hostaddr6,
			    &rl6->mask6);
			if (memcmp(&hostservaddr6, &(rl6->addr6),
			    sizeof(hostservaddr6)) == 0) {
				if ((rl6->mflags & RESM_NTPONLY) &&
				    !isntpport)
					continue;
				match6 = rl6;
			}
		}
		match6->count++;
		if (match6 == restrictlist6)
			res_not_found++;
		else
			res_found++;
		flags = match6->flags;
	}

	/*
	 * The following implements a generalized call gap facility.
	 * Douse the RES_LIMITED bit only if the interval since the last
	 * packet is greater than res_min_interval and the average is
	 * greater thatn res_avg_interval.
	 */
	if (mon_enabled == MON_OFF) {
		flags &= ~RES_LIMITED;
	} else {
		struct mon_data *md;

		/*
		 * At this poin the most recent arrival is first in the
		 * MRU list. Let the first 10 packets in for free until
		 * the average stabilizes.
		 */
		md = mon_mru_list.mru_next;
		if (md->avg_interval == 0)
			md->avg_interval = md->drop_count;
		else
			md->avg_interval += (md->drop_count -
			    md->avg_interval) / RES_AVG;
		if (md->count < 10 || (md->drop_count >
		    res_min_interval && md->avg_interval >
		    res_avg_interval))
			flags &= ~RES_LIMITED;
		md->drop_count = flags;
	}
	return (flags);
}


/*
 * hack_restrict - add/subtract/manipulate entries on the restrict list
 */
void
hack_restrict(
	int op,
	struct sockaddr_storage *resaddr,
	struct sockaddr_storage *resmask,
	int mflags,
	int flags
	)
{
	register u_int32 addr = 0;
	register u_int32 mask = 0;
	struct in6_addr addr6;
	struct in6_addr mask6;
	register struct restrictlist *rl = NULL;
	register struct restrictlist *rlprev = NULL;
	register struct restrictlist6 *rl6 = NULL;
	register struct restrictlist6 *rlprev6 = NULL;
	int i, addr_cmp, mask_cmp;
	memset(&addr6, 0, sizeof(struct in6_addr)); 
	memset(&mask6, 0, sizeof(struct in6_addr)); 

	if (resaddr->ss_family == AF_INET) {
		/*
		 * Get address and mask in host byte order
		 */
		addr = SRCADR(resaddr);
		mask = SRCADR(resmask);
		addr &= mask;		/* make sure low bits zero */

		/*
		 * If this is the default address, point at first on
		 * list. Else go searching for it.
		 */
		if (addr == 0) {
			rlprev = 0;
			rl = restrictlist;
		} else {
			rlprev = restrictlist;
			rl = rlprev->next;
			while (rl != 0) {
				if (rl->addr > addr) {
					rl = 0;
					break;
				} else if (rl->addr == addr) {
					if (rl->mask == mask) {
						if ((mflags &
						    RESM_NTPONLY) ==
						    (rl->mflags &
						    RESM_NTPONLY))
							break;

						if (!(mflags &
						    RESM_NTPONLY)) {
							rl = 0;
							break;
						}
					} else if (rl->mask > mask) {
						rl = 0;
						break;
					}
				}
				rlprev = rl;
				rl = rl->next;
			}
		}
	}

	if (resaddr->ss_family == AF_INET6) {
		mask6 = GET_INADDR6(*resmask);
		SET_IPV6_ADDR_MASK(&addr6,
		    &GET_INADDR6(*resaddr), &mask6);
		if (IN6_IS_ADDR_UNSPECIFIED(&addr6)) {
			rlprev6 = 0;
			rl6 = restrictlist6;
		} else {
			rlprev6 = restrictlist6;
			rl6 = rlprev6->next;
			while (rl6 != 0) {
				addr_cmp = memcmp(&rl6->addr6, &addr6,
				    sizeof(addr6));
				if (addr_cmp > 0) {
					rl6 = 0;
					break;
				} else if (addr_cmp == 0) {
					mask_cmp = memcmp(&rl6->mask6,
					    &mask6, sizeof(mask6));
					if (mask_cmp == 0) {
						if ((mflags &
						    RESM_NTPONLY) ==
						    (rl6->mflags &
						    RESM_NTPONLY))
							break;

						if (!(mflags &
						    RESM_NTPONLY)) {
							rl6 = 0;
							break;
						}
					} else if (mask_cmp > 0) {
						rl6 = 0;
						break;
					}
				}
				rlprev6 = rl6;
				rl6 = rl6->next;
			}
		}
	}

	/*
	 * In case the above wasn't clear :-), either rl now points
	 * at the entry this call refers to, or rl is zero and rlprev
	 * points to the entry prior to where this one should go in
	 * the sort.
	 */

	/*
	 * Switch based on operation
	 */
	if (resaddr->ss_family == AF_INET) {
		switch (op) {
		case RESTRICT_FLAGS:
			/*
			 * Here we add bits to the flags. If this is a
			 * new restriction add it.
			 */
			if (rl == 0) {
				if (numresfree == 0) {
					rl = (struct restrictlist *)
					    emalloc(INCRESLIST *
					    sizeof(struct
					    restrictlist));
					memset((char *)rl, 0,
					    INCRESLIST * sizeof(struct
					    restrictlist));
					for (i = 0; i < INCRESLIST; i++) {
						rl->next = resfree;
						resfree = rl;
						rl++;
					}
					numresfree = INCRESLIST;
				}

				rl = resfree;
				resfree = rl->next;
				numresfree--;

				rl->addr = addr;
				rl->mask = mask;
				rl->mflags = (u_short)mflags;

				rl->next = rlprev->next;
				rlprev->next = rl;
				restrictcount++;
			}
			if ((rl->flags ^ (u_short)flags) &
			    RES_LIMITED) {
				res_limited_refcnt++;
				mon_start(MON_RES);
			}
			rl->flags |= (u_short)flags;
			break;

		case RESTRICT_UNFLAG:
			/*
			 * Remove some bits from the flags. If we didn't
			 * find this one, just return.
			 */
			if (rl != 0) {
				if ((rl->flags ^ (u_short)flags) &
				    RES_LIMITED) {
					res_limited_refcnt--;
					if (res_limited_refcnt == 0)
						mon_stop(MON_RES);
				}
				rl->flags &= (u_short)~flags;
			}
			break;
	
		case RESTRICT_REMOVE:
			/*
			 * Remove an entry from the table entirely if we
			 * found one. Don't remove the default entry and
			 * don't remove an interface entry.
			 */
			if (rl != 0
			    && rl->addr != htonl(INADDR_ANY)
			    && !(rl->mflags & RESM_INTERFACE)) {
				rlprev->next = rl->next;
				restrictcount--;
				if (rl->flags & RES_LIMITED) {
					res_limited_refcnt--;
					if (res_limited_refcnt == 0)
						mon_stop(MON_RES);
				}
				memset((char *)rl, 0,
				    sizeof(struct restrictlist));

				rl->next = resfree;
				resfree = rl;
				numresfree++;
			}
			break;

		default:
			break;
		}
	} else if (resaddr->ss_family == AF_INET6) {
		switch (op) {
		case RESTRICT_FLAGS:
			/*
			 * Here we add bits to the flags. If this is a
			 * new restriction add it.
			 */
			if (rl6 == 0) {
				if (numresfree6 == 0) {
					rl6 = (struct
					    restrictlist6 *)emalloc(
					    INCRESLIST * sizeof(struct
					    restrictlist6));
					memset((char *)rl6, 0,
					    INCRESLIST * sizeof(struct
					    restrictlist6));

					for (i = 0; i < INCRESLIST;
					    i++) {
						rl6->next = resfree6;
						resfree6 = rl6;
						rl6++;
					}
					numresfree6 = INCRESLIST;
				}
				rl6 = resfree6;
				resfree6 = rl6->next;
				numresfree6--;
				rl6->addr6 = addr6;
				rl6->mask6 = mask6;
				rl6->mflags = (u_short)mflags;
				rl6->next = rlprev6->next;
				rlprev6->next = rl6;
				restrictcount6++;
			}
			if ((rl6->flags ^ (u_short)flags) &
			    RES_LIMITED) {
				res_limited_refcnt6++;
				mon_start(MON_RES);
			}
			rl6->flags |= (u_short)flags;
			break;

		case RESTRICT_UNFLAG:
			/*
			 * Remove some bits from the flags. If we didn't
			 * find this one, just return.
			 */
			if (rl6 != 0) {
				if ((rl6->flags ^ (u_short)flags) &
				    RES_LIMITED) {
					res_limited_refcnt6--;
					if (res_limited_refcnt6 == 0)
						mon_stop(MON_RES);
				}
				rl6->flags &= (u_short)~flags;
			}
			break;

		case RESTRICT_REMOVE:
			/*
			 * Remove an entry from the table entirely if we
			 * found one. Don't remove the default entry and
			 * don't remove an interface entry.
			 */
			if (rl6 != 0 &&
			    !IN6_IS_ADDR_UNSPECIFIED(&rl6->addr6)
			    && !(rl6->mflags & RESM_INTERFACE)) {
				rlprev6->next = rl6->next;
				restrictcount6--;
				if (rl6->flags & RES_LIMITED) {
					res_limited_refcnt6--;
					if (res_limited_refcnt6 == 0)
						mon_stop(MON_RES);
				}
				memset((char *)rl6, 0,
				    sizeof(struct restrictlist6));
				rl6->next = resfree6;
				resfree6 = rl6;
				numresfree6++;
			}
			break;

		default:
			break;
		}
	}
}
