461 lines
11 KiB
C
461 lines
11 KiB
C
/*
|
|
* 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! */
|
|
}
|