summaryrefslogtreecommitdiff
path: root/ot_accesslist.c
diff options
context:
space:
mode:
Diffstat (limited to 'ot_accesslist.c')
-rw-r--r--ot_accesslist.c631
1 files changed, 0 insertions, 631 deletions
diff --git a/ot_accesslist.c b/ot_accesslist.c
deleted file mode 100644
index df084b8..0000000
--- a/ot_accesslist.c
+++ /dev/null
@@ -1,631 +0,0 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <pthread.h>
8#include <signal.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13#if defined(WANT_ACCESSLIST_BLACK) || defined(WANT_ACCESSLIST_WHITE)
14#include <fcntl.h>
15#include <stdint.h>
16#include <sys/mman.h>
17#include <sys/stat.h>
18#include <sys/types.h>
19#endif
20#ifdef WANT_DYNAMIC_ACCESSLIST
21#include <errno.h>
22#endif
23
24/* Opentracker */
25#include "ot_accesslist.h"
26#include "ot_hex.h"
27#include "ot_ip.h"
28#include "ot_vector.h"
29#include "trackerlogic.h"
30
31/* GLOBAL VARIABLES */
32#ifdef WANT_ACCESSLIST
33const char *g_accesslist_filename = NULL;
34#ifdef WANT_DYNAMIC_ACCESSLIST
35const char *g_accesslist_pipe_add = NULL;
36const char *g_accesslist_pipe_delete = NULL;
37#endif
38static pthread_mutex_t g_accesslist_mutex;
39
40/* Accesslists are lock free linked lists. We can not make them locking, because every announce
41 would try to acquire the mutex, making it the most contested mutex in the whole of opentracker,
42 basically creating a central performance choke point.
43
44 The idea is that updating the list heads happens under the g_accesslist_mutex guard and is
45 done atomically, while consumers might potentially still hold pointers deeper inside the list.
46
47 Consumers (for now only via accesslist_hashisvalid) will always fetch the list head pointer
48 that is guaranteed to live for at least five minutes. This should be many orders of magnitudes
49 more than how long it will be needed by the bsearch done on the list. */
50struct ot_accesslist;
51typedef struct ot_accesslist ot_accesslist;
52struct ot_accesslist {
53 ot_hash *list;
54 size_t size;
55 ot_time base;
56 ot_accesslist *next;
57};
58static ot_accesslist *_Atomic g_accesslist = NULL;
59#ifdef WANT_DYNAMIC_ACCESSLIST
60static ot_accesslist *_Atomic g_accesslist_add = NULL;
61static ot_accesslist *_Atomic g_accesslist_delete = NULL;
62#endif
63
64/* Helpers to work on access lists */
65static int vector_compare_hash(const void *hash1, const void *hash2) { return memcmp(hash1, hash2, OT_HASH_COMPARE_SIZE); }
66
67static ot_accesslist *accesslist_free(ot_accesslist *accesslist) {
68 while (accesslist) {
69 ot_accesslist *this_accesslist = accesslist;
70 accesslist = this_accesslist->next;
71 free(this_accesslist->list);
72 free(this_accesslist);
73 }
74 return NULL;
75}
76
77static ot_accesslist *accesslist_make(ot_accesslist *next, size_t size) {
78 ot_accesslist *accesslist_new = malloc(sizeof(ot_accesslist));
79 if (accesslist_new) {
80 accesslist_new->list = size ? malloc(sizeof(ot_hash) * size) : NULL;
81 accesslist_new->size = size;
82 accesslist_new->base = 0;
83 accesslist_new->next = next;
84 if (size && !accesslist_new->list) {
85 free(accesslist_new);
86 accesslist_new = NULL;
87 }
88 }
89 return accesslist_new;
90}
91
92/* This must be called with g_accesslist_mutex held.
93 This will never delete head, because that might still be in use. */
94static void accesslist_clean(ot_accesslist *accesslist) {
95 while (accesslist && accesslist->next) {
96 if (accesslist->next->base + 5 < time_now_minutes())
97 accesslist->next = accesslist_free(accesslist->next);
98 accesslist = accesslist->next;
99 }
100}
101
102/* A hash needs forty bytes, and all but a final one are followed by at least
103 one separator, so n hashes need at least 41n - 1 bytes. (len + 1) / 41 is
104 therefore an exact upper bound on what a buffer of len bytes can hold. */
105size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len) {
106 const char *end = buf + len;
107 const char *offs = buf;
108 size_t count = 0;
109
110 while (count < capacity && end - offs >= 40) {
111 if (hex_to_hash(dest[count], offs)) {
112 offs += 40;
113
114 if (offs == end || !hex_isdigit((unsigned char)*offs))
115 ++count;
116 }
117
118 /* Find start of next line */
119 while (offs < end && *(offs++) != '\n') {}
120 }
121
122 return count;
123}
124
125/* Map a file read only. Returns 0 and sets map and maplen on success, -1 if
126 the file cannot be opened or mapped. */
127static int accesslist_map(const char *filename, const char **map, size_t *maplen) {
128 struct stat st;
129 void *mapped;
130 int fd = open(filename, O_RDONLY);
131
132 if (fd < 0)
133 return -1;
134
135 if (fstat(fd, &st) < 0 || st.st_size < 0 || (uintmax_t)st.st_size > (uintmax_t)SIZE_MAX) {
136 close(fd);
137 return -1;
138 }
139
140 if (st.st_size == 0) {
141 close(fd);
142 *map = NULL;
143 *maplen = 0;
144 return 0;
145 }
146
147 mapped = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, 0);
148 close(fd);
149
150 if (mapped == MAP_FAILED)
151 return -1;
152
153 *map = mapped;
154 *maplen = (size_t)st.st_size;
155 return 0;
156}
157
158static void accesslist_readfile(void) {
159 ot_accesslist *accesslist_new;
160 const char *map;
161 size_t maplen, capacity, found;
162
163 if (!g_accesslist_filename)
164 return;
165
166 if (accesslist_map(g_accesslist_filename, &map, &maplen) < 0) {
167 char *wd = getcwd(NULL, 0);
168 fprintf(stderr, "Warning: Can't open accesslist file: %s (but will try to create it later, if necessary and possible).\nPWD: %s\n", g_accesslist_filename, wd);
169 free(wd);
170 return;
171 }
172
173 /* Make room for the most info_hashes this file could possibly hold */
174 capacity = (maplen + 1) / 41;
175 accesslist_new = accesslist_make(g_accesslist, capacity);
176 if (!accesslist_new) {
177 fprintf(stderr, "Warning: Not enough memory to allocate %zd bytes for accesslist buffer. May succeed later.\n", capacity * sizeof(ot_hash));
178 if (map)
179 munmap((void *)map, maplen);
180 return;
181 }
182
183 found = maplen ? accesslist_parse_hashes(accesslist_new->list, capacity, map, maplen) : 0;
184#ifdef _DEBUG
185 fprintf(stderr, "Added %zd info_hashes to accesslist\n", found);
186#endif
187
188 if (map)
189 munmap((void *)map, maplen);
190
191 if (found)
192 qsort(accesslist_new->list, found, sizeof(ot_hash), vector_compare_hash);
193 accesslist_new->size = found;
194
195 /* Now exchange the accesslist vector in the least race condition prone way */
196 pthread_mutex_lock(&g_accesslist_mutex);
197
198 /* Mark old accesslist as "needs to live another five minutes" */
199 if (g_accesslist)
200 g_accesslist->base = time_now_minutes();
201 accesslist_new->next = g_accesslist;
202 g_accesslist = accesslist_new; /* Only now set a new list */
203
204#ifdef WANT_DYNAMIC_ACCESSLIST
205 /* If we have dynamic accesslists, reloading a new one will always void the add/delete lists.
206 Insert empty ones at the list head */
207 if (g_accesslist_add && (accesslist_new = accesslist_make(g_accesslist_add, 0)) != NULL) {
208 g_accesslist_add->base = time_now_minutes();
209 g_accesslist_add = accesslist_new;
210 }
211 if (g_accesslist_delete && (accesslist_new = accesslist_make(g_accesslist_delete, 0)) != NULL) {
212 g_accesslist_delete->base = time_now_minutes();
213 g_accesslist_delete = accesslist_new;
214 }
215#endif
216
217 accesslist_clean(g_accesslist);
218
219 pthread_mutex_unlock(&g_accesslist_mutex);
220}
221
222int accesslist_hashisvalid(const ot_hash hash) {
223 /* Get working copy of current access list */
224 ot_accesslist *accesslist = g_accesslist;
225#ifdef WANT_DYNAMIC_ACCESSLIST
226 ot_accesslist *accesslist_add, *accesslist_delete;
227#endif
228 void *exactmatch = NULL;
229
230 if (accesslist && accesslist->size)
231 exactmatch = bsearch(hash, accesslist->list, accesslist->size, OT_HASH_COMPARE_SIZE, vector_compare_hash);
232
233#ifdef WANT_DYNAMIC_ACCESSLIST
234 /* If we had no match on the main list, scan the list of dynamically added hashes */
235 accesslist_add = g_accesslist_add;
236 if ((exactmatch == NULL) && accesslist_add && accesslist_add->size)
237 exactmatch = bsearch(hash, accesslist_add->list, accesslist_add->size, OT_HASH_COMPARE_SIZE, vector_compare_hash);
238
239 /* If we found a matching hash on the main list, scan the list of dynamically deleted hashes */
240 accesslist_delete = g_accesslist_delete;
241 if ((exactmatch != NULL) && accesslist_delete && accesslist_delete->size && bsearch(hash, accesslist_delete->list, accesslist_delete->size, OT_HASH_COMPARE_SIZE, vector_compare_hash))
242 exactmatch = NULL;
243#endif
244
245#ifdef WANT_ACCESSLIST_BLACK
246 return exactmatch == NULL;
247#else
248 return exactmatch != NULL;
249#endif
250}
251
252static void *accesslist_worker(void *args) {
253 int sig;
254 sigset_t signal_mask;
255
256 sigemptyset(&signal_mask);
257 sigaddset(&signal_mask, SIGHUP);
258
259 (void)args;
260
261 while (1) {
262 if (!g_opentracker_running)
263 return NULL;
264
265 /* Initial attempt to read accesslist */
266 accesslist_readfile();
267
268 /* Wait for signals */
269 while (sigwait(&signal_mask, &sig) != 0 && sig != SIGHUP)
270 ;
271 }
272 return NULL;
273}
274
275#ifdef WANT_DYNAMIC_ACCESSLIST
276static pthread_t thread_adder_id, thread_deleter_id;
277static _Atomic int adder_left, deleter_left;
278static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) {
279 struct stat st;
280
281 if (!stat(fifoname, &st)) {
282 if (!S_ISFIFO(st.st_mode)) {
283 fprintf(stderr, "Error when starting dynamic accesslists: Found Non-FIFO file at %s.\nPlease remove it and restart opentracker.\n", fifoname);
284 return NULL;
285 }
286 } else {
287 int error = mkfifo(fifoname, 0755);
288 if (error && error != EEXIST) {
289 fprintf(stderr, "Error when starting dynamic accesslists: Couldn't create FIFO at %s, error: %s\n", fifoname, strerror(errno));
290 return NULL;
291 }
292 }
293
294 while (g_opentracker_running) {
295 FILE *fifo = fopen(fifoname, "r");
296 char *line = NULL;
297 size_t linecap = 0;
298 ssize_t linelen;
299
300 if (!fifo) {
301 /* Woken on purpose: the loop condition above decides what happens next */
302 if (errno == EINTR)
303 continue;
304 fprintf(stderr, "Error when reading dynamic accesslists: Couldn't open FIFO at %s, error: %s\n", fifoname, strerror(errno));
305 return NULL;
306 }
307
308 while ((linelen = getline(&line, &linecap, fifo)) > 0) {
309 ot_hash info_hash;
310
311 printf("Got line %*s", (int)linelen, line);
312 /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*"
313 If there's not enough characters for an info_hash in the line, skip it. */
314 if (linelen < 41)
315 continue;
316
317 if (!hex_to_hash(info_hash, line))
318 continue;
319
320 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the
321 other worker threads from doing the same */
322 pthread_mutex_lock(&g_accesslist_mutex);
323
324 /* If the info hash is in the removing_from list, create a new head without that entry */
325 if (*removing_from && (*removing_from)->list) {
326 ot_hash *exactmatch = bsearch(info_hash, (*removing_from)->list, (*removing_from)->size, OT_HASH_COMPARE_SIZE, vector_compare_hash);
327 if (exactmatch) {
328 ptrdiff_t off = exactmatch - (*removing_from)->list;
329 ot_accesslist *accesslist_new = accesslist_make(*removing_from, (*removing_from)->size - 1);
330 if (accesslist_new) {
331 size_t after = (*removing_from)->size - off - 1;
332
333 /* Removing the only entry leaves a list with no room at all, and
334 memcpy may not be handed that even for nothing */
335 if (off)
336 memcpy(accesslist_new->list, (*removing_from)->list, sizeof(ot_hash) * off);
337 if (after)
338 memcpy(accesslist_new->list + off, (*removing_from)->list + off + 1, sizeof(ot_hash) * after);
339 (*removing_from)->base = time_now_minutes();
340 *removing_from = accesslist_new;
341 }
342 }
343 }
344
345 /* Simple case: there's no adding_to list yet, create one with one member */
346 if (!*adding_to) {
347 ot_accesslist *accesslist_new = accesslist_make(NULL, 1);
348 if (accesslist_new) {
349 memcpy(accesslist_new->list, info_hash, sizeof(ot_hash));
350 *adding_to = accesslist_new;
351 }
352 } else {
353 int exactmatch = 0;
354 ot_hash *insert_point = vector_binary_search(info_hash, (*adding_to)->list, (*adding_to)->size, sizeof(ot_hash), OT_HASH_COMPARE_SIZE, &exactmatch);
355
356 /* Only if the info hash is not in the adding_to list, create a new head with that entry */
357 if (!exactmatch) {
358 ot_accesslist *accesslist_new = accesslist_make(*adding_to, (*adding_to)->size + 1);
359 ptrdiff_t off = insert_point - (*adding_to)->list;
360 if (accesslist_new) {
361 memcpy(accesslist_new->list, (*adding_to)->list, sizeof(ot_hash) * off);
362 memcpy(accesslist_new->list + off, info_hash, sizeof(info_hash));
363 memcpy(accesslist_new->list + off + 1, (*adding_to)->list + off, sizeof(ot_hash) * ((*adding_to)->size - off));
364 (*adding_to)->base = time_now_minutes();
365 *adding_to = accesslist_new;
366 }
367 }
368 }
369
370 pthread_mutex_unlock(&g_accesslist_mutex);
371 }
372
373 free(line);
374 fclose(fifo);
375 }
376 return NULL;
377}
378
379static void *accesslist_adder_worker(void *args) {
380 (void)args;
381 accesslist_adddel_worker(g_accesslist_pipe_add, &g_accesslist_add, &g_accesslist_delete);
382 atomic_store(&adder_left, 1);
383 return NULL;
384}
385static void *accesslist_deleter_worker(void *args) {
386 (void)args;
387 accesslist_adddel_worker(g_accesslist_pipe_delete, &g_accesslist_delete, &g_accesslist_add);
388 atomic_store(&deleter_left, 1);
389 return NULL;
390}
391
392/* These sit in a blocking open or read on a FIFO, so they are woken the way
393 the other workers are, and the signal is repeated because it only
394 interrupts a call already under way. The pipe name is what says whether
395 the thread was ever started. */
396static void accesslist_stop_fifo_worker(pthread_t thread, _Atomic int *left, const char *pipe_name) {
397 if (!pipe_name)
398 return;
399
400 while (!atomic_load(left)) {
401 pthread_kill(thread, OT_SIGNAL_WAKE);
402 usleep(1000);
403 }
404 pthread_join(thread, NULL);
405}
406#endif
407
408static pthread_t thread_id;
409void accesslist_init(void) {
410 pthread_mutex_init(&g_accesslist_mutex, NULL);
411 pthread_create(&thread_id, NULL, accesslist_worker, NULL);
412#ifdef WANT_DYNAMIC_ACCESSLIST
413 if (g_accesslist_pipe_add)
414 pthread_create(&thread_adder_id, NULL, accesslist_adder_worker, NULL);
415 if (g_accesslist_pipe_delete)
416 pthread_create(&thread_deleter_id, NULL, accesslist_deleter_worker, NULL);
417#endif
418}
419
420void accesslist_deinit(void) {
421 /* Wake the worker and wait for it to notice that the tracker is stopping. */
422 pthread_kill(thread_id, SIGHUP);
423 pthread_join(thread_id, NULL);
424
425#ifdef WANT_DYNAMIC_ACCESSLIST
426 /* Before the lists they are reading are freed below */
427 accesslist_stop_fifo_worker(thread_adder_id, &adder_left, g_accesslist_pipe_add);
428 accesslist_stop_fifo_worker(thread_deleter_id, &deleter_left, g_accesslist_pipe_delete);
429#endif
430
431 pthread_mutex_lock(&g_accesslist_mutex);
432
433 g_accesslist = accesslist_free(g_accesslist);
434
435#ifdef WANT_DYNAMIC_ACCESSLIST
436 g_accesslist_add = accesslist_free(g_accesslist_add);
437 g_accesslist_delete = accesslist_free(g_accesslist_delete);
438#endif
439
440 pthread_mutex_unlock(&g_accesslist_mutex);
441 pthread_mutex_destroy(&g_accesslist_mutex);
442}
443
444void accesslist_cleanup(void) {
445 pthread_mutex_lock(&g_accesslist_mutex);
446
447 accesslist_clean(g_accesslist);
448#if WANT_DYNAMIC_ACCESSLIST
449 accesslist_clean(g_accesslist_add);
450 accesslist_clean(g_accesslist_delete);
451#endif
452
453 pthread_mutex_unlock(&g_accesslist_mutex);
454}
455#endif
456
457int accesslist_address_in_net(const ot_ip6 address, const ot_net *net) {
458 int bits = net->bits, checkbits = (0x7f00 >> (bits & 7));
459 int result = memcmp(address, &net->address, bits >> 3);
460 if (!result && (bits & 7))
461 result = (checkbits & address[bits >> 3]) - (checkbits & net->address[bits >> 3]);
462 return result == 0;
463}
464
465void *accesslist_set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size) {
466 size_t i;
467 int exactmatch;
468
469 /* Caller must have a concept of ot_net in it's member */
470 if (member_size < sizeof(ot_net))
471 return 0;
472
473 /* Check each net in vector for overlap */
474 uint8_t *member = ((uint8_t *)vector->data);
475 for (i = 0; i < vector->size; ++i) {
476 if (accesslist_address_in_net(*(ot_ip6 *)member, net) || accesslist_address_in_net(net->address, (ot_net *)member))
477 return 0;
478 member += member_size;
479 }
480
481 member = vector_find_or_insert(vector, (void *)net, member_size, sizeof(ot_net), &exactmatch);
482 if (member) {
483 memcpy(member, net, sizeof(ot_net));
484 memcpy(member + sizeof(ot_net), value, member_size - sizeof(ot_net));
485 }
486
487 return member;
488}
489
490/* Takes a vector filled with { ot_net net, uint8_t[x] value };
491 Returns value associated with the net, or NULL if not found */
492void *accesslist_get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size) {
493 int exactmatch;
494 /* This binary search will return a pointer to the first non-containing network... */
495 ot_net *net = vector_binary_search(address, vector->data, vector->size, member_size, sizeof(ot_ip6), &exactmatch);
496 if (!net)
497 return NULL;
498 /* ... so we'll need to move back one step unless we've exactly hit the first address in network */
499 if (!exactmatch && ((void *)net > vector->data))
500 --net;
501 if (!accesslist_address_in_net(address, net))
502 return NULL;
503 return (void *)net;
504}
505
506#ifdef WANT_FULLLOG_NETWORKS
507static ot_vector g_lognets_list;
508ot_log *g_logchain_first, *g_logchain_last;
509static pthread_mutex_t g_lognets_list_mutex = PTHREAD_MUTEX_INITIALIZER;
510
511void loglist_add_network(const ot_net *net) {
512 pthread_mutex_lock(&g_lognets_list_mutex);
513 accesslist_set_value_for_net(net, &g_lognets_list, NULL, sizeof(ot_net));
514 pthread_mutex_unlock(&g_lognets_list_mutex);
515}
516
517void loglist_reset(void) {
518 pthread_mutex_lock(&g_lognets_list_mutex);
519 free(g_lognets_list.data);
520 g_lognets_list.data = 0;
521 g_lognets_list.size = g_lognets_list.space = 0;
522 pthread_mutex_unlock(&g_lognets_list_mutex);
523}
524
525int loglist_check_address(const ot_ip6 address) {
526 int result;
527 pthread_mutex_lock(&g_lognets_list_mutex);
528 result = (NULL != accesslist_get_value_for_net(address, &g_lognets_list, sizeof(ot_net)));
529 pthread_mutex_unlock(&g_lognets_list_mutex);
530 return result;
531}
532#endif
533
534#ifdef WANT_IP_FROM_PROXY
535typedef struct {
536 ot_net *proxy;
537 ot_vector networks;
538} ot_proxymap;
539
540static ot_vector g_proxies_list;
541static pthread_mutex_t g_proxies_list_mutex = PTHREAD_MUTEX_INITIALIZER;
542
543int proxylist_add_network(const ot_net *proxy, const ot_net *net) {
544 ot_proxymap *map;
545 int exactmatch, result = 1;
546 pthread_mutex_lock(&g_proxies_list_mutex);
547
548 /* If we have a direct hit, use and extend the vector there */
549 map = vector_binary_search(proxy, g_proxies_list.data, g_proxies_list.size, sizeof(ot_proxymap), sizeof(ot_net), &exactmatch);
550
551 if (!map || !exactmatch) {
552 /* else see, if we've got overlapping networks
553 and get a new empty vector if not */
554 ot_vector empty;
555 memset(&empty, 0, sizeof(ot_vector));
556 map = accesslist_set_value_for_net(proxy, &g_proxies_list, &empty, sizeof(ot_proxymap));
557 }
558
559 if (map && accesslist_set_value_for_net(net, &map->networks, NULL, sizeof(ot_net)))
560 result = 1;
561
562 pthread_mutex_unlock(&g_proxies_list_mutex);
563 return result;
564}
565
566int proxylist_check_proxy(const ot_ip6 proxy, const ot_ip6 address) {
567 int result = 0;
568 ot_proxymap *map;
569
570 pthread_mutex_lock(&g_proxies_list_mutex);
571
572 if ((map = accesslist_get_value_for_net(proxy, &g_proxies_list, sizeof(ot_proxymap))))
573 if (!address || accesslist_get_value_for_net(address, &map->networks, sizeof(ot_net)))
574 result = 1;
575
576 pthread_mutex_unlock(&g_proxies_list_mutex);
577 return result;
578}
579
580#endif
581
582static ot_net g_admin_nets[OT_ADMINIP_MAX];
583static ot_permissions g_admin_nets_permissions[OT_ADMINIP_MAX];
584static size_t g_admin_nets_count = 0;
585
586int accesslist_bless_net(ot_net *net, ot_permissions permissions) {
587 if (g_admin_nets_count >= OT_ADMINIP_MAX)
588 return -1;
589
590 memcpy(g_admin_nets + g_admin_nets_count, net, sizeof(ot_net));
591 g_admin_nets_permissions[g_admin_nets_count++] = permissions;
592
593#ifdef _DEBUG
594 {
595 char _debug[512];
596 int off = snprintf(_debug, sizeof(_debug), "Blessing ip net ");
597 off += ip_format(_debug + off, net->address);
598 if (net->bits < 128) {
599 _debug[off++] = '/';
600 if (ip_is_v4mapped(net->address))
601 off += snprintf(_debug + off, sizeof(_debug) - off, "%d", net->bits - 96);
602 else
603 off += snprintf(_debug + off, sizeof(_debug) - off, "%d", net->bits);
604 }
605
606 if (permissions & OT_PERMISSION_MAY_STAT)
607 off += snprintf(_debug + off, 512 - off, " may_fetch_stats");
608 if (permissions & OT_PERMISSION_MAY_LIVESYNC)
609 off += snprintf(_debug + off, 512 - off, " may_sync_live");
610 if (permissions & OT_PERMISSION_MAY_FULLSCRAPE)
611 off += snprintf(_debug + off, 512 - off, " may_fetch_fullscrapes");
612 if (permissions & OT_PERMISSION_MAY_PROXY)
613 off += snprintf(_debug + off, 512 - off, " may_proxy");
614 if (!permissions)
615 off += snprintf(_debug + off, sizeof(_debug) - off, " nothing");
616 _debug[off++] = '.';
617 _debug[off++] = '\n';
618 (void)write(2, _debug, off);
619 }
620#endif
621
622 return 0;
623}
624
625int accesslist_is_blessed(ot_ip6 ip, ot_permissions permissions) {
626 size_t i;
627 for (i = 0; i < g_admin_nets_count; ++i)
628 if (accesslist_address_in_net(ip, g_admin_nets + i) && (g_admin_nets_permissions[i] & permissions))
629 return 1;
630 return 0;
631}