freebsd-skq/contrib/ntp/ntpd/ntp_restrict.c
2017-03-23 22:06:06 +00:00

680 lines
16 KiB
C

/*
* 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_lists.h"
#include "ntp_stdlib.h"
#include "ntp_assert.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 MASK_IPV6_ADDR(dst, src, msk) \
do { \
int idx; \
for (idx = 0; idx < (int)COUNTOF((dst)->s6_addr); idx++) { \
(dst)->s6_addr[idx] = (src)->s6_addr[idx] \
& (msk)->s6_addr[idx]; \
} \
} while (0)
/*
* We allocate INC_RESLIST{4|6} entries to the free list whenever empty.
* Auto-tune these to be just less than 1KB (leaving at least 16 bytes
* for allocator overhead).
*/
#define INC_RESLIST4 ((1024 - 16) / V4_SIZEOF_RESTRICT_U)
#define INC_RESLIST6 ((1024 - 16) / V6_SIZEOF_RESTRICT_U)
/*
* The restriction list
*/
restrict_u *restrictlist4;
restrict_u *restrictlist6;
static int restrictcount; /* count in the restrict lists */
/*
* The free list and associated counters. Also some uninteresting
* stat counters.
*/
static restrict_u *resfree4; /* available entries (free list) */
static restrict_u *resfree6;
static u_long res_calls;
static u_long res_found;
static u_long res_not_found;
/*
* Count number of restriction entries referring to RES_LIMITED, to
* control implicit activation/deactivation of the MRU monlist.
*/
static u_long res_limited_refcnt;
/*
* Our default entries.
*/
static restrict_u restrict_def4;
static restrict_u restrict_def6;
/*
* "restrict source ..." enabled knob and restriction bits.
*/
static int restrict_source_enabled;
static u_short restrict_source_flags;
static u_short restrict_source_mflags;
/*
* private functions
*/
static restrict_u * alloc_res4(void);
static restrict_u * alloc_res6(void);
static void free_res(restrict_u *, int);
static void inc_res_limited(void);
static void dec_res_limited(void);
static restrict_u * match_restrict4_addr(u_int32, u_short);
static restrict_u * match_restrict6_addr(const struct in6_addr *,
u_short);
static restrict_u * match_restrict_entry(const restrict_u *, int);
static int res_sorts_before4(restrict_u *, restrict_u *);
static int res_sorts_before6(restrict_u *, restrict_u *);
/*
* init_restrict - initialize the restriction data structures
*/
void
init_restrict(void)
{
/*
* The restriction lists begin with a default entry with address
* and mask 0, which will match any entry. The lists are kept
* sorted by descending address followed by descending mask:
*
* address mask
* 192.168.0.0 255.255.255.0 kod limited noquery nopeer
* 192.168.0.0 255.255.0.0 kod limited
* 0.0.0.0 0.0.0.0 kod limited noquery
*
* The first entry which matches an address is used. With the
* example restrictions above, 192.168.0.0/24 matches the first
* entry, the rest of 192.168.0.0/16 matches the second, and
* everything else matches the third (default).
*
* Note this achieves the same result a little more efficiently
* than the documented behavior, which is to keep the lists
* sorted by ascending address followed by ascending mask, with
* the _last_ matching entry used.
*
* An additional wrinkle is we may have multiple entries with
* the same address and mask but differing match flags (mflags).
* At present there is only one, RESM_NTPONLY. Entries with
* RESM_NTPONLY are sorted earlier so they take precedence over
* any otherwise similar entry without. Again, this is the same
* behavior as but reversed implementation compared to the docs.
*
*/
LINK_SLIST(restrictlist4, &restrict_def4, link);
LINK_SLIST(restrictlist6, &restrict_def6, link);
restrictcount = 2;
}
static restrict_u *
alloc_res4(void)
{
const size_t cb = V4_SIZEOF_RESTRICT_U;
const size_t count = INC_RESLIST4;
restrict_u * rl;
restrict_u * res;
size_t i;
UNLINK_HEAD_SLIST(res, resfree4, link);
if (res != NULL)
return res;
rl = eallocarray(count, cb);
/* link all but the first onto free list */
res = (void *)((char *)rl + (count - 1) * cb);
for (i = count - 1; i > 0; i--) {
LINK_SLIST(resfree4, res, link);
res = (void *)((char *)res - cb);
}
INSIST(rl == res);
/* allocate the first */
return res;
}
static restrict_u *
alloc_res6(void)
{
const size_t cb = V6_SIZEOF_RESTRICT_U;
const size_t count = INC_RESLIST6;
restrict_u * rl;
restrict_u * res;
size_t i;
UNLINK_HEAD_SLIST(res, resfree6, link);
if (res != NULL)
return res;
rl = eallocarray(count, cb);
/* link all but the first onto free list */
res = (void *)((char *)rl + (count - 1) * cb);
for (i = count - 1; i > 0; i--) {
LINK_SLIST(resfree6, res, link);
res = (void *)((char *)res - cb);
}
INSIST(rl == res);
/* allocate the first */
return res;
}
static void
free_res(
restrict_u * res,
int v6
)
{
restrict_u ** plisthead;
restrict_u * unlinked;
restrictcount--;
if (RES_LIMITED & res->flags)
dec_res_limited();
if (v6)
plisthead = &restrictlist6;
else
plisthead = &restrictlist4;
UNLINK_SLIST(unlinked, *plisthead, res, link, restrict_u);
INSIST(unlinked == res);
if (v6) {
zero_mem(res, V6_SIZEOF_RESTRICT_U);
plisthead = &resfree6;
} else {
zero_mem(res, V4_SIZEOF_RESTRICT_U);
plisthead = &resfree4;
}
LINK_SLIST(*plisthead, res, link);
}
static void
inc_res_limited(void)
{
if (!res_limited_refcnt)
mon_start(MON_RES);
res_limited_refcnt++;
}
static void
dec_res_limited(void)
{
res_limited_refcnt--;
if (!res_limited_refcnt)
mon_stop(MON_RES);
}
static restrict_u *
match_restrict4_addr(
u_int32 addr,
u_short port
)
{
const int v6 = 0;
restrict_u * res;
restrict_u * next;
for (res = restrictlist4; res != NULL; res = next) {
next = res->link;
if (res->expire &&
res->expire <= current_time)
free_res(res, v6);
if (res->u.v4.addr == (addr & res->u.v4.mask)
&& (!(RESM_NTPONLY & res->mflags)
|| NTP_PORT == port))
break;
}
return res;
}
static restrict_u *
match_restrict6_addr(
const struct in6_addr * addr,
u_short port
)
{
const int v6 = 1;
restrict_u * res;
restrict_u * next;
struct in6_addr masked;
for (res = restrictlist6; res != NULL; res = next) {
next = res->link;
INSIST(next != res);
if (res->expire &&
res->expire <= current_time)
free_res(res, v6);
MASK_IPV6_ADDR(&masked, addr, &res->u.v6.mask);
if (ADDR6_EQ(&masked, &res->u.v6.addr)
&& (!(RESM_NTPONLY & res->mflags)
|| NTP_PORT == (int)port))
break;
}
return res;
}
/*
* match_restrict_entry - find an exact match on a restrict list.
*
* Exact match is addr, mask, and mflags all equal.
* In order to use more common code for IPv4 and IPv6, this routine
* requires the caller to populate a restrict_u with mflags and either
* the v4 or v6 address and mask as appropriate. Other fields in the
* input restrict_u are ignored.
*/
static restrict_u *
match_restrict_entry(
const restrict_u * pmatch,
int v6
)
{
restrict_u *res;
restrict_u *rlist;
size_t cb;
if (v6) {
rlist = restrictlist6;
cb = sizeof(pmatch->u.v6);
} else {
rlist = restrictlist4;
cb = sizeof(pmatch->u.v4);
}
for (res = rlist; res != NULL; res = res->link)
if (res->mflags == pmatch->mflags &&
!memcmp(&res->u, &pmatch->u, cb))
break;
return res;
}
/*
* res_sorts_before4 - compare two restrict4 entries
*
* Returns nonzero if r1 sorts before r2. We sort by descending
* address, then descending mask, then descending mflags, so sorting
* before means having a higher value.
*/
static int
res_sorts_before4(
restrict_u *r1,
restrict_u *r2
)
{
int r1_before_r2;
if (r1->u.v4.addr > r2->u.v4.addr)
r1_before_r2 = 1;
else if (r1->u.v4.addr < r2->u.v4.addr)
r1_before_r2 = 0;
else if (r1->u.v4.mask > r2->u.v4.mask)
r1_before_r2 = 1;
else if (r1->u.v4.mask < r2->u.v4.mask)
r1_before_r2 = 0;
else if (r1->mflags > r2->mflags)
r1_before_r2 = 1;
else
r1_before_r2 = 0;
return r1_before_r2;
}
/*
* res_sorts_before6 - compare two restrict6 entries
*
* Returns nonzero if r1 sorts before r2. We sort by descending
* address, then descending mask, then descending mflags, so sorting
* before means having a higher value.
*/
static int
res_sorts_before6(
restrict_u *r1,
restrict_u *r2
)
{
int r1_before_r2;
int cmp;
cmp = ADDR6_CMP(&r1->u.v6.addr, &r2->u.v6.addr);
if (cmp > 0) /* r1->addr > r2->addr */
r1_before_r2 = 1;
else if (cmp < 0) /* r2->addr > r1->addr */
r1_before_r2 = 0;
else {
cmp = ADDR6_CMP(&r1->u.v6.mask, &r2->u.v6.mask);
if (cmp > 0) /* r1->mask > r2->mask*/
r1_before_r2 = 1;
else if (cmp < 0) /* r2->mask > r1->mask */
r1_before_r2 = 0;
else if (r1->mflags > r2->mflags)
r1_before_r2 = 1;
else
r1_before_r2 = 0;
}
return r1_before_r2;
}
/*
* restrictions - return restrictions for this host
*/
u_short
restrictions(
sockaddr_u *srcadr
)
{
restrict_u *match;
struct in6_addr *pin6;
u_short flags;
res_calls++;
flags = 0;
/* IPv4 source address */
if (IS_IPV4(srcadr)) {
/*
* Ignore any packets with a multicast source address
* (this should be done early in the receive process,
* not later!)
*/
if (IN_CLASSD(SRCADR(srcadr)))
return (int)RES_IGNORE;
match = match_restrict4_addr(SRCADR(srcadr),
SRCPORT(srcadr));
INSIST(match != NULL);
match->count++;
/*
* res_not_found counts only use of the final default
* entry, not any "restrict default ntpport ...", which
* would be just before the final default.
*/
if (&restrict_def4 == match)
res_not_found++;
else
res_found++;
flags = match->flags;
}
/* IPv6 source address */
if (IS_IPV6(srcadr)) {
pin6 = PSOCK_ADDR6(srcadr);
/*
* Ignore any packets with a multicast source address
* (this should be done early in the receive process,
* not later!)
*/
if (IN6_IS_ADDR_MULTICAST(pin6))
return (int)RES_IGNORE;
match = match_restrict6_addr(pin6, SRCPORT(srcadr));
INSIST(match != NULL);
match->count++;
if (&restrict_def6 == match)
res_not_found++;
else
res_found++;
flags = match->flags;
}
return (flags);
}
/*
* hack_restrict - add/subtract/manipulate entries on the restrict list
*/
void
hack_restrict(
int op,
sockaddr_u * resaddr,
sockaddr_u * resmask,
u_short mflags,
u_short flags,
u_long expire
)
{
int v6;
restrict_u match;
restrict_u * res;
restrict_u ** plisthead;
DPRINTF(1, ("restrict: op %d addr %s mask %s mflags %08x flags %08x\n",
op, stoa(resaddr), stoa(resmask), mflags, flags));
if (NULL == resaddr) {
REQUIRE(NULL == resmask);
REQUIRE(RESTRICT_FLAGS == op);
restrict_source_flags = flags;
restrict_source_mflags = mflags;
restrict_source_enabled = 1;
return;
}
ZERO(match);
#if 0
/* silence VC9 potentially uninit warnings */
// HMS: let's use a compiler-specific "enable" for this.
res = NULL;
v6 = 0;
#endif
if (IS_IPV4(resaddr)) {
v6 = 0;
/*
* Get address and mask in host byte order for easy
* comparison as u_int32
*/
match.u.v4.addr = SRCADR(resaddr);
match.u.v4.mask = SRCADR(resmask);
match.u.v4.addr &= match.u.v4.mask;
} else if (IS_IPV6(resaddr)) {
v6 = 1;
/*
* Get address and mask in network byte order for easy
* comparison as byte sequences (e.g. memcmp())
*/
match.u.v6.mask = SOCK_ADDR6(resmask);
MASK_IPV6_ADDR(&match.u.v6.addr, PSOCK_ADDR6(resaddr),
&match.u.v6.mask);
} else /* not IPv4 nor IPv6 */
REQUIRE(0);
match.flags = flags;
match.mflags = mflags;
match.expire = expire;
res = match_restrict_entry(&match, v6);
switch (op) {
case RESTRICT_FLAGS:
/*
* Here we add bits to the flags. If this is a
* new restriction add it.
*/
if (NULL == res) {
if (v6) {
res = alloc_res6();
memcpy(res, &match,
V6_SIZEOF_RESTRICT_U);
plisthead = &restrictlist6;
} else {
res = alloc_res4();
memcpy(res, &match,
V4_SIZEOF_RESTRICT_U);
plisthead = &restrictlist4;
}
LINK_SORT_SLIST(
*plisthead, res,
(v6)
? res_sorts_before6(res, L_S_S_CUR())
: res_sorts_before4(res, L_S_S_CUR()),
link, restrict_u);
restrictcount++;
if (RES_LIMITED & flags)
inc_res_limited();
} else {
if ((RES_LIMITED & flags) &&
!(RES_LIMITED & res->flags))
inc_res_limited();
res->flags |= flags;
}
break;
case RESTRICT_UNFLAG:
/*
* Remove some bits from the flags. If we didn't
* find this one, just return.
*/
if (res != NULL) {
if ((RES_LIMITED & res->flags)
&& (RES_LIMITED & flags))
dec_res_limited();
res->flags &= ~flags;
}
break;
case RESTRICT_REMOVE:
case RESTRICT_REMOVEIF:
/*
* 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 (res != NULL
&& (RESTRICT_REMOVEIF == op
|| !(RESM_INTERFACE & res->mflags))
&& res != &restrict_def4
&& res != &restrict_def6)
free_res(res, v6);
break;
default: /* unknown op */
INSIST(0);
break;
}
}
/*
* restrict_source - maintains dynamic "restrict source ..." entries as
* peers come and go.
*/
void
restrict_source(
sockaddr_u * addr,
int farewell, /* 0 to add, 1 to remove */
u_long expire /* 0 is infinite, valid until */
)
{
sockaddr_u onesmask;
restrict_u * res;
int found_specific;
if (!restrict_source_enabled || SOCK_UNSPEC(addr) ||
IS_MCAST(addr) || ISREFCLOCKADR(addr))
return;
REQUIRE(AF_INET == AF(addr) || AF_INET6 == AF(addr));
SET_HOSTMASK(&onesmask, AF(addr));
if (farewell) {
hack_restrict(RESTRICT_REMOVE, addr, &onesmask,
0, 0, 0);
DPRINTF(1, ("restrict_source: %s removed", stoa(addr)));
return;
}
/*
* If there is a specific entry for this address, hands
* off, as it is condidered more specific than "restrict
* server ...".
* However, if the specific entry found is a fleeting one
* added by pool_xmit() before soliciting, replace it
* immediately regardless of the expire value to make way
* for the more persistent entry.
*/
if (IS_IPV4(addr)) {
res = match_restrict4_addr(SRCADR(addr), SRCPORT(addr));
INSIST(res != NULL);
found_specific = (SRCADR(&onesmask) == res->u.v4.mask);
} else {
res = match_restrict6_addr(&SOCK_ADDR6(addr),
SRCPORT(addr));
INSIST(res != NULL);
found_specific = ADDR6_EQ(&res->u.v6.mask,
&SOCK_ADDR6(&onesmask));
}
if (!expire && found_specific && res->expire) {
found_specific = 0;
free_res(res, IS_IPV6(addr));
}
if (found_specific)
return;
hack_restrict(RESTRICT_FLAGS, addr, &onesmask,
restrict_source_mflags, restrict_source_flags,
expire);
DPRINTF(1, ("restrict_source: %s host restriction added\n",
stoa(addr)));
}