/*
 * ntp_restrict.c - find out what restrictions this host is running under
 */
#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.
 */

/*
 * 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

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

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

static	u_long res_calls;
static	u_long res_found;
static	u_long res_not_found;
/* static	u_long res_timereset; */

/*
 * Parameters of the RES_LIMITED restriction option.
 * client_limit is the number of hosts allowed per source net
 * client_limit_period is the number of seconds after which an entry
 * is no longer considered for client limit determination
 */
u_long client_limit;
u_long client_limit_period;
/*
 * 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;

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

/*
 * init_restrict - initialize the restriction data structures
 */
void
init_restrict(void)
{
	register int i;
	char bp[80];

	/*
	 * Zero the list and put all but one on the free list
	 */
	resfree = 0;
	memset((char *)resinit, 0, sizeof resinit);

	for (i = 1; i < INITRESLIST; i++) {
		resinit[i].next = resfree;
		resfree = &resinit[i];
	}

	numresfree = 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;
	restrictlist = &resinit[0];
	restrictcount = 1;


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

	/*
	 * set default values for RES_LIMIT functionality
	 */
	client_limit = 3;
	client_limit_period = 3600;
	res_limited_refcnt = 0;

	sprintf(bp, "client_limit=%ld", client_limit);
	set_sys_var(bp, strlen(bp)+1, RO);
	sprintf(bp, "client_limit_period=%ld", client_limit_period);
	set_sys_var(bp, strlen(bp)+1, RO);
}


/*
 * restrictions - return restrictions for this host
 */
int
restrictions(
	struct sockaddr_in *srcadr
	)
{
	register struct restrictlist *rl;
	register struct restrictlist *match;
	register u_int32 hostaddr;
	register int isntpport;

	res_calls++;
	/*
	 * 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(ntohl(srcadr->sin_addr.s_addr)))
	    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++;
	
	/*
	 * The following implements limiting the number of clients
	 * accepted from a given network. The notion of "same network"
	 * is determined by the mask and addr fields of the restrict
	 * list entry. The monitor mechanism has to be enabled for
	 * collecting info on current clients.
	 *
	 * The policy is as follows:
	 *	- take the list of clients recorded
	 *        from the given "network" seen within the last
	 *        client_limit_period seconds
	 *      - if there are at most client_limit entries: 
	 *        --> access allowed
	 *      - otherwise sort by time first seen
	 *      - current client among the first client_limit seen
	 *        hosts?
	 *        if yes: access allowed
	 *        else:   eccess denied
	 */
	if (match->flags & RES_LIMITED) {
		int lcnt;
		struct mon_data *md, *this_client;

#ifdef DEBUG
		if (debug > 2)
		    printf("limited clients check: %ld clients, period %ld seconds, net is 0x%lX\n",
			   client_limit, client_limit_period,
			   (u_long)netof(hostaddr));
#endif /*DEBUG*/
		if (mon_enabled == MON_OFF) {
#ifdef DEBUG
			if (debug > 4)
			    printf("no limit - monitoring is off\n");
#endif
			return (int)(match->flags & ~RES_LIMITED);
		}

		/*
		 * How nice, MRU list provides our current client as the
		 * first entry in the list.
		 * Monitoring was verified to be active above, thus we
		 * know an entry for our client must exist, or some 
		 * brain dead set the memory limit for mon entries to ZERO!!!
		 */
		this_client = mon_mru_list.mru_next;

		for (md = mon_fifo_list.fifo_next,lcnt = 0;
		     md != &mon_fifo_list;
		     md = md->fifo_next) {
			if ((current_time - md->lasttime)
			    > client_limit_period) {
#ifdef DEBUG
				if (debug > 5)
				    printf("checking: %s: ignore: too old: %ld\n",
					   numtoa(md->rmtadr),
					   current_time - md->lasttime);
#endif
				continue;
			}
			if (md->mode == MODE_BROADCAST ||
			    md->mode == MODE_CONTROL ||
			    md->mode == MODE_PRIVATE) {
#ifdef DEBUG
				if (debug > 5)
				    printf("checking: %s: ignore mode %d\n",
					   numtoa(md->rmtadr),
					   md->mode);
#endif
				continue;
			}
			if (netof(md->rmtadr) !=
			    netof(hostaddr)) {
#ifdef DEBUG
				if (debug > 5)
				    printf("checking: %s: different net 0x%lX\n",
					   numtoa(md->rmtadr),
					   (u_long)netof(md->rmtadr));
#endif
				continue;
			}
			lcnt++;
			if (lcnt >  (int) client_limit ||
			    md->rmtadr == hostaddr) {
#ifdef DEBUG
				if (debug > 5)
				    printf("considering %s: found host\n",
					   numtoa(md->rmtadr));
#endif
				break;
			}
#ifdef DEBUG
			else {
				if (debug > 5)
				    printf("considering %s: same net\n",
					   numtoa(md->rmtadr));
			}
#endif

		}
#ifdef DEBUG
		if (debug > 4)
		    printf("this one is rank %d in list, limit is %lu: %s\n",
			   lcnt, client_limit,
			   (lcnt <= (int) client_limit) ? "ALLOW" : "REJECT");
#endif
		if (lcnt <= (int) client_limit) {
			this_client->lastdrop = 0;
			return (int)(match->flags & ~RES_LIMITED);
		} else {
			this_client->lastdrop = current_time;
		}
	}
	return (int)match->flags;
}


/*
 * hack_restrict - add/subtract/manipulate entries on the restrict list
 */
void
hack_restrict(
	int op,
	struct sockaddr_in *resaddr,
	struct sockaddr_in *resmask,
	int mflags,
	int flags
	)
{
	register u_int32 addr;
	register u_int32 mask;
	register struct restrictlist *rl;
	register struct restrictlist *rlprev;
	int i;

	/*
	 * Get address and mask in host byte order
	 */
	addr = SRCADR(resaddr);
	mask = SRCADR(resmask);
	addr &= mask;		/* make sure low bits are zero */

	/*
	 * If this is the default address, point at first on list.  Else
	 * go searching for it.
	 */
	if (addr == htonl(INADDR_ANY)) {
		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;	/* exact match */
					if (!(mflags & RESM_NTPONLY)) {
						/*
						 * No flag fits before flag
						 */
						rl = 0;
						break;
					}
					/* continue on */
				} else if (rl->mask > mask) {
					rl = 0;
					break;
				}
			}
			rlprev = rl;
			rl = rl->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
	 */
	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); /* ensure data gets collected */
		}
		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:
		/* Oh, well */
		break;
	}

	/* done! */
}
