/*      $NetBSD: ntp_restrict.c,v 1.13 2024/10/01 20:59:51 christos Exp $       */

/*
* 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 addressses we want
* to place restrictions on (or remove them from). The restrictions are
* implemented as a set of flags which tell you what matching addresses
* can't do.  The list is sorted retrieve the restrictions most specific
*  to the address.
*
* 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.
*/
#define MASK_IPV6_ADDR(dst, src, msk)                                   \
       do {                                                            \
               int x;                                                  \
                                                                       \
               for (x = 0; x < (int)COUNTOF((dst)->s6_addr); x++) {    \
                       (dst)->s6_addr[x] =   (src)->s6_addr[x]         \
                                           & (msk)->s6_addr[x];        \
               }                                                       \
       } while (FALSE)

/*
* 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 32 bytes
* for allocator overhead).
*/
#define INC_RESLIST4    ((1024 - 32) / sizeof(struct restrict_4))
#define INC_RESLIST6    ((1024 - 32) / sizeof(struct restrict_6))

/*
* The restriction list
*/
struct restrict_4 *restrictlist4;
struct restrict_6 *restrictlist6;
static size_t restrictcount;    /* count in the restrict lists */

/*
* The free list and associated counters.  Also some uninteresting
* stat counters.
*/
static struct restrict_4 *resfree4;     /* available entries (free list) */
static struct restrict_6 *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.
*
* We can make this cleaner with c99 support: see init_restrict().
*/
static  struct restrict_4       restrict_def4;
static  struct restrict_6       restrict_def6;

/*
* "restrict source ..." enabled knob and restriction bits.
*/
static  int             restrict_source_enabled;
static  u_int32         restrict_source_rflags;
static  u_short         restrict_source_mflags;
static  short           restrict_source_ippeerlimit;

/*
* private functions
*/
static  struct restrict_4 *     alloc_res4(void);
static  struct restrict_6 *     alloc_res6(void);
static  void            free_res4(struct restrict_4 *);
static  void            free_res6(struct restrict_6 *);
static  inline void     inc_res_limited(void);
static  inline void     dec_res_limited(void);
static  struct restrict_4 *     match_restrict4_addr(u_int32, u_short);
static  struct restrict_6 *     match_restrict6_addr(const struct in6_addr *,
                                            u_short);
static inline int/*BOOL*/       mflags_sorts_before(u_short, u_short);
static  int/*BOOL*/     res_sorts_before4(struct restrict_4 *,
                                       struct restrict_4 *);
static  int/*BOOL*/     res_sorts_before6(struct restrict_6 *,
                                       struct restrict_6 *);

#ifdef DEBUG
/* dump_restrict() & dump_restricts() are DEBUG-only */

static void
dump_restrict(const struct restrict_info *ri, const char *as, const char *ms)
{
       printf("%s/%s: hits %u ippeerlimit %hd mflags %s rflags %s",
               as, ms, ri->count, ri->ippeerlimit,
               mflags_str(ri->mflags),
               rflags_str(ri->rflags));
       if (ri->expire > 0) {
               printf(" expire %u\n", ri->expire);
       } else {
               printf("\n");
       }
}

/*
* dump_restrict - spit out a single restriction entry
*/
static void
dump_restrict4(
       struct restrict_4 *     res)
{
       char as[INET6_ADDRSTRLEN];
       char ms[INET6_ADDRSTRLEN];

       struct in_addr  sia, sim;

       sia.s_addr = htonl(res->v4.addr);
       sim.s_addr = htonl(res->v4.addr);
       inet_ntop(AF_INET, &sia, as, sizeof as);
       inet_ntop(AF_INET, &sim, ms, sizeof ms);

       dump_restrict(&res->ri, as, ms);
}

static void
dump_restrict6(
       struct restrict_6 *     res)
{
       char as[INET6_ADDRSTRLEN];
       char ms[INET6_ADDRSTRLEN];

       inet_ntop(AF_INET6, &res->v6.addr, as, sizeof as);
       inet_ntop(AF_INET6, &res->v6.mask, ms, sizeof ms);

       dump_restrict(&res->ri, as, ms);
}


/*
* dump_restricts - spit out the 'restrict' entries
*/
void
dump_restricts(void)
{
       struct restrict_4 *     res4;
       struct restrict_6 *     res6;

       /* Spit out the IPv4 list */
       printf("dump_restricts: restrictlist4: %p\n", restrictlist4);
       for (res4 = restrictlist4; res4 != NULL; res4 = res4->link) {
               dump_restrict4(res4);
       }

       /* Spit out the IPv6 list */
       printf("dump_restricts: restrictlist6: %p\n", restrictlist6);
       for (res6 = restrictlist6; res6 != NULL; res6 = res6->link) {
               dump_restrict6(res6);
       }
}
#endif /* DEBUG - dump_restrict() / dump_restricts() */


/*
* init_restrict - initialize the restriction data structures
*/
void
init_restrict(void)
{
       /*
        * The restriction lists end 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).
        * We want to never talk to ourself, so RES_IGNORE entries for
        * each local address are added by ntp_io.c with a host mask and
        * both RESM_INTERFACE and RESM_NTPONLY set.  We sort those
        * entries before entries without those flags to achieve this.
        * The remaining match flag is RESM_SOURCE, used to dynamically
        * set restrictions for each peer based on the prototype set by
        * "restrict source" in the configuration.  We want those entries
        * to be considered only when there is not a static host
        * restriction for the address in the configuration, to allow
        * operators to blacklist pool and manycast servers at runtime as
        * desired using ntpq runtime configuration.  Such static entries
        * have no RESM_ bits set, so the sort order for mflags is first
        * RESM_INTERFACE, then entries without RESM_SOURCE, finally the
        * remaining.
        */

       restrict_def4.ri.ippeerlimit = -1;      /* Cleaner if we have C99 */
       restrict_def6.ri.ippeerlimit = -1;      /* Cleaner if we have C99 */

       LINK_SLIST(restrictlist4, &restrict_def4, link);
       LINK_SLIST(restrictlist6, &restrict_def6, link);
       restrictcount = 2;
}


static struct restrict_4 *
alloc_res4(void)
{
       const size_t    count = INC_RESLIST4;
       struct restrict_4*      rl;
       struct restrict_4*      res;
       const size_t    cb = sizeof(*rl);
       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);
       }
       DEBUG_INSIST(rl == res);
       /* allocate the first */
       return res;
}


static struct restrict_6 *
alloc_res6(void)
{
       const size_t    count = INC_RESLIST6;
       struct restrict_6 *     rl;
       struct restrict_6 *     res;
       const size_t    cb = sizeof(*rl);
       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);
       }
       DEBUG_INSIST(rl == res);
       /* allocate the first */
       return res;
}


static void
free_res6(struct restrict_6 *   res)
{
       struct restrict_6 *     unlinked;

       restrictcount--;
       if (RES_LIMITED & res->ri.rflags) {
               dec_res_limited();
       }
       UNLINK_SLIST(unlinked, restrictlist6, res, link, struct restrict_6);
       INSIST(unlinked == res);
       zero_mem(res, sizeof(*res));
       LINK_SLIST(resfree6, res, link);
}

static void
free_res4(struct restrict_4 *   res)
{
       struct restrict_4 *     unlinked;

       restrictcount--;
       if (RES_LIMITED & res->ri.rflags) {
               dec_res_limited();
       }
       UNLINK_SLIST(unlinked, restrictlist4, res, link, struct restrict_4);
       INSIST(unlinked == res);
       zero_mem(res, sizeof(*res));
       LINK_SLIST(resfree4, res, link);
}

static inline void
inc_res_limited(void)
{
       if (0 == res_limited_refcnt) {
               mon_start(MON_RES);
       }
       res_limited_refcnt++;
}


static inline void
dec_res_limited(void)
{
       res_limited_refcnt--;
       if (0 == res_limited_refcnt) {
               mon_stop(MON_RES);
       }
}


static struct restrict_4 *
match_restrict4_addr(
       u_int32 addr,
       u_short port
       )
{
       struct restrict_4 *     res;
       struct restrict_4 *     next;

       for (res = restrictlist4; res != NULL; res = next) {
               next = res->link;
               if (res->ri.expire && res->ri.expire <= current_time) {
                       free_res4(res); /* zeroes the contents */
               }
               if (   res->v4.addr == (addr & res->v4.mask)
                   && (   !(RESM_NTPONLY & res->ri.mflags)
                       || NTP_PORT == port)) {

                       break;
               }
       }
       return res;
}


static struct restrict_6 *
match_restrict6_addr(
       const struct in6_addr * addr,
       u_short                 port
       )
{
       struct restrict_6 *     res;
       struct restrict_6 *     next;
       struct in6_addr masked;

       for (res = restrictlist6; res != NULL; res = next) {
               next = res->link;
               if (res->ri.expire && res->ri.expire <= current_time) {
                       free_res6(res);
               }
               MASK_IPV6_ADDR(&masked, addr, &res->v6.mask);
               if (ADDR6_EQ(&masked, &res->v6.addr)
                   && (   !(RESM_NTPONLY & res->ri.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_[46] with mflags and either
* the v4 or v6 address and mask as appropriate.  Other fields in the
* input restrict_u are ignored.
*/
static struct restrict_4 *
match_restrict4_entry(
       const struct restrict_4 *       pmatch)
{
       struct restrict_4 *res;

       for (res = restrictlist4; res != NULL; res = res->link) {
               if (res->ri.mflags == pmatch->ri.mflags &&
                   !memcmp(&res->v4, &pmatch->v4, sizeof(res->v4))) {
                       break;
               }
       }
       return res;
}

static struct restrict_6 *
match_restrict6_entry(
       const struct restrict_6 *       pmatch)
{
       struct restrict_6 *res;

       for (res = restrictlist6; res != NULL; res = res->link) {
               if (res->ri.mflags == pmatch->ri.mflags &&
                   !memcmp(&res->v6, &pmatch->v6, sizeof(res->v6))) {
                       break;
               }
       }
       return res;
}

/*
* mflags_sorts_before - common mflags sorting code
*
* See block comment in init_restrict() above for rationale.
*/
static inline int/*BOOL*/
mflags_sorts_before(
       u_short m1,
       u_short m2
       )
{
       if (    (RESM_INTERFACE & m1)
           && !(RESM_INTERFACE & m2)) {
               return TRUE;
       } else if (   !(RESM_SOURCE & m1)
                  &&  (RESM_SOURCE & m2)) {
               return TRUE;
       } else {
               return FALSE;
       }
}


/*
* res_sorts_before4 - compare IPv4 restriction entries
*
* Returns nonzero if r1 sorts before r2.  We sort by descending
* address, then descending mask, then an intricate mflags sort
* order explained in a block comment near the top of this file.
*/
static int/*BOOL*/
res_sorts_before4(
       struct restrict_4 *r1,
       struct restrict_4 *r2
       )
{
       int r1_before_r2;

       if (r1->v4.addr > r2->v4.addr) {
               r1_before_r2 = TRUE;
       } else if (r1->v4.addr < r2->v4.addr) {
               r1_before_r2 = FALSE;
       } else if (r1->v4.mask > r2->v4.mask) {
               r1_before_r2 = TRUE;
       } else if (r1->v4.mask < r2->v4.mask) {
               r1_before_r2 = FALSE;
       } else {
               r1_before_r2 = mflags_sorts_before(r1->ri.mflags, r2->ri.mflags);
       }

       return r1_before_r2;
}


/*
* res_sorts_before6 - compare IPv6 restriction entries
*
* Returns nonzero if r1 sorts before r2.  We sort by descending
* address, then descending mask, then an intricate mflags sort
* order explained in a block comment near the top of this file.
*/
static int/*BOOL*/
res_sorts_before6(
       struct restrict_6* r1,
       struct restrict_6* r2
)
{
       int r1_before_r2;
       int cmp;

       cmp = ADDR6_CMP(&r1->v6.addr, &r2->v6.addr);
       if (cmp > 0) {          /* r1->addr > r2->addr */
               r1_before_r2 = TRUE;
       } else if (cmp < 0) {   /* r2->addr > r1->addr */
               r1_before_r2 = FALSE;
       } else {
               cmp = ADDR6_CMP(&r1->v6.mask, &r2->v6.mask);
               if (cmp > 0) {          /* r1->mask > r2->mask*/
                       r1_before_r2 = TRUE;
               } else if (cmp < 0) {   /* r2->mask > r1->mask */
                       r1_before_r2 = FALSE;
               } else {
                       r1_before_r2 = mflags_sorts_before(r1->ri.mflags,
                                                          r2->ri.mflags);
               }
       }

       return r1_before_r2;
}


/*
* restrictions - return restrictions for this host in *r4a
*/
void
restrictions(
       sockaddr_u *srcadr,
       r4addr *r4a
       )
{
       struct in6_addr *pin6;

       DEBUG_REQUIRE(NULL != r4a);

       res_calls++;

       if (IS_IPV4(srcadr)) {
               struct restrict_4 *match;
               /*
                * Ignore any packets with a multicast source address
                * (this should be done early in the receive process,
                * not later!)
                */
               if (IN_CLASSD(SRCADR(srcadr))) {
                       goto multicast;
               }

               match = match_restrict4_addr(SRCADR(srcadr),
                                            SRCPORT(srcadr));
               DEBUG_INSIST(match != NULL);
               match->ri.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++;
               r4a->rflags = match->ri.rflags;
               r4a->ippeerlimit = match->ri.ippeerlimit;
       } else {
               struct restrict_6 *match;
               DEBUG_REQUIRE(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)) {
                       goto multicast;
               }
               match = match_restrict6_addr(pin6, SRCPORT(srcadr));
               DEBUG_INSIST(match != NULL);
               match->ri.count++;
               if (&restrict_def6 == match)
                       res_not_found++;
               else
                       res_found++;
               r4a->rflags = match->ri.rflags;
               r4a->ippeerlimit = match->ri.ippeerlimit;
       }

       return;

   multicast:
       r4a->rflags = RES_IGNORE;
       r4a->ippeerlimit = 0;
}


#ifdef DEBUG
/* display string for restrict_op */
const char *
resop_str(restrict_op op)
{
       switch (op) {
           case RESTRICT_FLAGS:        return "RESTRICT_FLAGS";
           case RESTRICT_UNFLAG:       return "RESTRICT_UNFLAG";
           case RESTRICT_REMOVE:       return "RESTRICT_REMOVE";
           case RESTRICT_REMOVEIF:     return "RESTRICT_REMOVEIF";
       }
       DEBUG_INVARIANT(!"bad restrict_op in resop_str");
       return "";      /* silence not all paths return value warning */
}
#endif  /* DEBUG */


/*
* hack_restrict - add/subtract/manipulate entries on the restrict list
*/
int/*BOOL*/
hack_restrict(
       restrict_op     op,
       sockaddr_u *    resaddr,
       sockaddr_u *    resmask,
       short           ippeerlimit,
       u_short         mflags,
       u_short         rflags,
       u_int32         expire
       )
{
       int             bump_res_limited = FALSE;
       struct restrict_4       match4, *res4 = NULL;
       struct restrict_6       match6, *res6 = NULL;
       struct restrict_info *ri;

#ifdef DEBUG
       if (debug > 0) {
               printf("hack_restrict: op %s addr %s mask %s",
                       resop_str(op), stoa(resaddr), stoa(resmask));
               if (ippeerlimit >= 0) {
                       printf(" ippeerlimit %d", ippeerlimit);
               }
               printf(" mflags %s rflags %s", mflags_str(mflags),
                      rflags_str(rflags));
               if (expire) {
                       printf("lifetime %u\n",
                              expire - (u_int32)current_time);
               } else {
                       printf("\n");
               }
       }
#endif

       if (NULL == resaddr) {
               DEBUG_REQUIRE(NULL == resmask);
               DEBUG_REQUIRE(RESTRICT_FLAGS == op);
               DEBUG_REQUIRE(RESM_SOURCE & mflags);
               restrict_source_rflags = rflags;
               restrict_source_mflags = mflags;
               restrict_source_ippeerlimit = ippeerlimit;
               restrict_source_enabled = TRUE;
               DPRINTF(1, ("restrict source template saved\n"));
               return TRUE;
       }


       if (IS_IPV4(resaddr)) {
               DEBUG_INVARIANT(IS_IPV4(resmask));
               /*
                * Get address and mask in host byte order for easy
                * comparison as u_int32
                */
               ZERO(match4);
               match4.v4.addr = SRCADR(resaddr);
               match4.v4.mask = SRCADR(resmask);
               match4.v4.addr &= match4.v4.mask;
               match4.ri.mflags = mflags;
               res4 = match_restrict4_entry(&match4);
               ri = res4 ? &res4->ri : NULL;
       } else {
               DEBUG_INVARIANT(IS_IPV6(resaddr));
               DEBUG_INVARIANT(IS_IPV6(resmask));
               /*
                * Get address and mask in network byte order for easy
                * comparison as byte sequences (e.g. memcmp())
                */
               ZERO(match6);
               match6.v6.mask = SOCK_ADDR6(resmask);
               MASK_IPV6_ADDR(&match6.v6.addr, PSOCK_ADDR6(resaddr),
                              &match6.v6.mask);
               match6.ri.mflags = mflags;
               res6 = match_restrict6_entry(&match6);
               ri = res6 ? &res6->ri : NULL;
       }


       switch (op) {

       case RESTRICT_FLAGS:
               /*
                * Here we add bits to the rflags. If we already have
                * this restriction modify it.
                */
               if (NULL != ri) {
                       if (    (RES_LIMITED & rflags)
                           && !(RES_LIMITED & ri->rflags)) {

                               bump_res_limited = TRUE;
                       }
                       ri->rflags |= rflags;
                       ri->expire = expire;
               } else {
                       if (IS_IPV4(resaddr)) {
                               match4.ri.rflags = rflags;
                               match4.ri.expire = expire;
                               match4.ri.ippeerlimit = ippeerlimit;
                               res4 = alloc_res4();
                               memcpy(res4, &match4, sizeof(*res4));
                               LINK_SORT_SLIST(
                                   restrictlist4, res4,
                                   res_sorts_before4(res4, L_S_S_CUR()),
                                   link, struct restrict_4);
                       } else {
                               match6.ri.rflags = rflags;
                               match6.ri.expire = expire;
                               match6.ri.ippeerlimit = ippeerlimit;
                               res6 = alloc_res6();
                               memcpy(res6, &match6, sizeof(*res6));
                               LINK_SORT_SLIST(
                                   restrictlist6, res6,
                                   res_sorts_before6(res6, L_S_S_CUR()),
                                   link, struct restrict_6);
                       }
                       restrictcount++;
                       if (RES_LIMITED & rflags) {
                               bump_res_limited = TRUE;
                       }
               }
               if (bump_res_limited) {
                       inc_res_limited();
               }
               return TRUE;

       case RESTRICT_UNFLAG:
               /*
                * Remove some bits from the rflags. If we didn't
                * find this one, just return.
                */
               if (NULL == ri) {
                       DPRINTF(1, ("No match for %s %s removing rflags %s\n",
                                   stoa(resaddr), stoa(resmask),
                                   rflags_str(rflags)));
                       return FALSE;
               }
               if (   (RES_LIMITED & ri->rflags)
                   && (RES_LIMITED & rflags)) {
                       dec_res_limited();
               }
               ri->rflags &= ~rflags;
               return TRUE;

       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 unless asked.
                */
               if (   ri != NULL
                   && (   RESTRICT_REMOVEIF == op
                       || !(RESM_INTERFACE & ri->mflags))) {
                       if (res4 && res4 != &restrict_def4) {
                               free_res4(res4);
                               return TRUE;
                       }
                       if (res6 && res6 != &restrict_def6) {
                               free_res6(res6);
                               return TRUE;
                       }
               }
               DPRINTF(1, ("No match removing %s %s restriction\n",
                           stoa(resaddr), stoa(resmask)));
               return FALSE;
       }
       /* notreached */
       return FALSE;
}


/*
* restrict_source - maintains dynamic "restrict source ..." entries as
*                   peers come and go.
*/
void
restrict_source(
       sockaddr_u *    addr,
       int             farewell,       /* TRUE to remove */
       u_int32         lifetime        /* seconds, 0 forever */
       )
{
       sockaddr_u      onesmask;
       int/*BOOL*/     success;

       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) {
               success = hack_restrict(RESTRICT_REMOVE, addr, &onesmask,
                                       0, RESM_SOURCE, 0, 0);
               if (success) {
                       DPRINTF(1, ("%s %s removed", __func__,
                                   stoa(addr)));
               } else {
                       msyslog(LOG_ERR, "%s remove %s failed",
                                        __func__, stoa(addr));
               }
               return;
       }

       success = hack_restrict(RESTRICT_FLAGS, addr, &onesmask,
                               restrict_source_ippeerlimit,
                               restrict_source_mflags,
                               restrict_source_rflags,
                               lifetime > 0
                                   ? lifetime + current_time
                                   : 0);
       if (success) {
               DPRINTF(1, ("%s %s add/upd\n", __func__,
                           stoa(addr)));
       } else {
               msyslog(LOG_ERR, "%s %s failed", __func__, stoa(addr));
       }
}


#ifdef DEBUG
/* Convert restriction RES_ flag bits into a display string */
const char *
rflags_str(
       u_short rflags
       )
{
       const size_t    sz = LIB_BUFLENGTH;
       char *          rfs;

       LIB_GETBUF(rfs);
       rfs[0] = '\0';

       if (rflags & RES_FLAKE) {
               CLEAR_BIT_IF_DEBUG(RES_FLAKE, rflags);
               append_flagstr(rfs, sz, "flake");
       }

       if (rflags & RES_IGNORE) {
               CLEAR_BIT_IF_DEBUG(RES_IGNORE, rflags);
               append_flagstr(rfs, sz, "ignore");
       }

       if (rflags & RES_KOD) {
               CLEAR_BIT_IF_DEBUG(RES_KOD, rflags);
               append_flagstr(rfs, sz, "kod");
       }

       if (rflags & RES_MSSNTP) {
               CLEAR_BIT_IF_DEBUG(RES_MSSNTP, rflags);
               append_flagstr(rfs, sz, "mssntp");
       }

       if (rflags & RES_LIMITED) {
               CLEAR_BIT_IF_DEBUG(RES_LIMITED, rflags);
               append_flagstr(rfs, sz, "limited");
       }

       if (rflags & RES_LPTRAP) {
               CLEAR_BIT_IF_DEBUG(RES_LPTRAP, rflags);
               append_flagstr(rfs, sz, "lptrap");
       }

       if (rflags & RES_NOMODIFY) {
               CLEAR_BIT_IF_DEBUG(RES_NOMODIFY, rflags);
               append_flagstr(rfs, sz, "nomodify");
       }

       if (rflags & RES_NOMRULIST) {
               CLEAR_BIT_IF_DEBUG(RES_NOMRULIST, rflags);
               append_flagstr(rfs, sz, "nomrulist");
       }

       if (rflags & RES_NOEPEER) {
               CLEAR_BIT_IF_DEBUG(RES_NOEPEER, rflags);
               append_flagstr(rfs, sz, "noepeer");
       }

       if (rflags & RES_NOPEER) {
               CLEAR_BIT_IF_DEBUG(RES_NOPEER, rflags);
               append_flagstr(rfs, sz, "nopeer");
       }

       if (rflags & RES_NOQUERY) {
               CLEAR_BIT_IF_DEBUG(RES_NOQUERY, rflags);
               append_flagstr(rfs, sz, "noquery");
       }

       if (rflags & RES_DONTSERVE) {
               CLEAR_BIT_IF_DEBUG(RES_DONTSERVE, rflags);
               append_flagstr(rfs, sz, "dontserve");
       }

       if (rflags & RES_NOTRAP) {
               CLEAR_BIT_IF_DEBUG(RES_NOTRAP, rflags);
               append_flagstr(rfs, sz, "notrap");
       }

       if (rflags & RES_DONTTRUST) {
               CLEAR_BIT_IF_DEBUG(RES_DONTTRUST, rflags);
               append_flagstr(rfs, sz, "notrust");
       }

       if (rflags & RES_SRVRSPFUZ) {
               CLEAR_BIT_IF_DEBUG(RES_SRVRSPFUZ, rflags);
               append_flagstr(rfs, sz, "srvrspfuz");
       }

       if (rflags & RES_VERSION) {
               CLEAR_BIT_IF_DEBUG(RES_VERSION, rflags);
               append_flagstr(rfs, sz, "version");
       }

       DEBUG_INVARIANT(!rflags);

       if ('\0' == rfs[0]) {
               append_flagstr(rfs, sz, "(none)");
       }

       return rfs;
}


/* Convert restriction match RESM_ flag bits into a display string */
const char *
mflags_str(
       u_short mflags
       )
{
       const size_t    sz = LIB_BUFLENGTH;
       char *          mfs;

       LIB_GETBUF(mfs);
       mfs[0] = '\0';

       if (mflags & RESM_NTPONLY) {
               CLEAR_BIT_IF_DEBUG(RESM_NTPONLY, mflags);
               append_flagstr(mfs, sz, "ntponly");
       }

       if (mflags & RESM_SOURCE) {
               CLEAR_BIT_IF_DEBUG(RESM_SOURCE, mflags);
               append_flagstr(mfs, sz, "source");
       }

       if (mflags & RESM_INTERFACE) {
               CLEAR_BIT_IF_DEBUG(RESM_INTERFACE, mflags);
               append_flagstr(mfs, sz, "interface");
       }

       DEBUG_INVARIANT(!mflags);

       return mfs;
}
#endif  /* DEBUG */