/* $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 #endif #include #include #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 */