summaryrefslogtreecommitdiff
path: root/ot_accesslist.c
diff options
context:
space:
mode:
Diffstat (limited to 'ot_accesslist.c')
-rw-r--r--ot_accesslist.c595
1 files changed, 0 insertions, 595 deletions
diff --git a/ot_accesslist.c b/ot_accesslist.c
deleted file mode 100644
index e8e0476..0000000
--- a/ot_accesslist.c
+++ /dev/null
@@ -1,595 +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 void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) {
278 struct stat st;
279
280 if (!stat(fifoname, &st)) {
281 if (!S_ISFIFO(st.st_mode)) {
282 fprintf(stderr, "Error when starting dynamic accesslists: Found Non-FIFO file at %s.\nPlease remove it and restart opentracker.\n", fifoname);
283 return NULL;
284 }
285 } else {
286 int error = mkfifo(fifoname, 0755);
287 if (error && error != EEXIST) {
288 fprintf(stderr, "Error when starting dynamic accesslists: Couldn't create FIFO at %s, error: %s\n", fifoname, strerror(errno));
289 return NULL;
290 }
291 }
292
293 while (g_opentracker_running) {
294 FILE *fifo = fopen(fifoname, "r");
295 char *line = NULL;
296 size_t linecap = 0;
297 ssize_t linelen;
298
299 if (!fifo) {
300 fprintf(stderr, "Error when reading dynamic accesslists: Couldn't open FIFO at %s, error: %s\n", fifoname, strerror(errno));
301 return NULL;
302 }
303
304 while ((linelen = getline(&line, &linecap, fifo)) > 0) {
305 ot_hash info_hash;
306
307 printf("Got line %*s", (int)linelen, line);
308 /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*"
309 If there's not enough characters for an info_hash in the line, skip it. */
310 if (linelen < 41)
311 continue;
312
313 if (!hex_to_hash(info_hash, line))
314 continue;
315
316 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the
317 other worker threads from doing the same */
318 pthread_mutex_lock(&g_accesslist_mutex);
319
320 /* If the info hash is in the removing_from list, create a new head without that entry */
321 if (*removing_from && (*removing_from)->list) {
322 ot_hash *exactmatch = bsearch(info_hash, (*removing_from)->list, (*removing_from)->size, OT_HASH_COMPARE_SIZE, vector_compare_hash);
323 if (exactmatch) {
324 ptrdiff_t off = exactmatch - (*removing_from)->list;
325 ot_accesslist *accesslist_new = accesslist_make(*removing_from, (*removing_from)->size - 1);
326 if (accesslist_new) {
327 memcpy(accesslist_new->list, (*removing_from)->list, sizeof(ot_hash) * off);
328 memcpy(accesslist_new->list + off, (*removing_from)->list + off + 1, sizeof(ot_hash) * ((*removing_from)->size - off - 1));
329 (*removing_from)->base = time_now_minutes();
330 *removing_from = accesslist_new;
331 }
332 }
333 }
334
335 /* Simple case: there's no adding_to list yet, create one with one member */
336 if (!*adding_to) {
337 ot_accesslist *accesslist_new = accesslist_make(NULL, 1);
338 if (accesslist_new) {
339 memcpy(accesslist_new->list, info_hash, sizeof(ot_hash));
340 *adding_to = accesslist_new;
341 }
342 } else {
343 int exactmatch = 0;
344 ot_hash *insert_point = vector_binary_search(info_hash, (*adding_to)->list, (*adding_to)->size, sizeof(ot_hash), OT_HASH_COMPARE_SIZE, &exactmatch);
345
346 /* Only if the info hash is not in the adding_to list, create a new head with that entry */
347 if (!exactmatch) {
348 ot_accesslist *accesslist_new = accesslist_make(*adding_to, (*adding_to)->size + 1);
349 ptrdiff_t off = insert_point - (*adding_to)->list;
350 if (accesslist_new) {
351 memcpy(accesslist_new->list, (*adding_to)->list, sizeof(ot_hash) * off);
352 memcpy(accesslist_new->list + off, info_hash, sizeof(info_hash));
353 memcpy(accesslist_new->list + off + 1, (*adding_to)->list + off, sizeof(ot_hash) * ((*adding_to)->size - off));
354 (*adding_to)->base = time_now_minutes();
355 *adding_to = accesslist_new;
356 }
357 }
358 }
359
360 pthread_mutex_unlock(&g_accesslist_mutex);
361 }
362
363 fclose(fifo);
364 }
365 return NULL;
366}
367
368static void *accesslist_adder_worker(void *args) {
369 (void)args;
370 return accesslist_adddel_worker(g_accesslist_pipe_add, &g_accesslist_add, &g_accesslist_delete);
371}
372static void *accesslist_deleter_worker(void *args) {
373 (void)args;
374 return accesslist_adddel_worker(g_accesslist_pipe_delete, &g_accesslist_delete, &g_accesslist_add);
375}
376#endif
377
378static pthread_t thread_id;
379void accesslist_init(void) {
380 pthread_mutex_init(&g_accesslist_mutex, NULL);
381 pthread_create(&thread_id, NULL, accesslist_worker, NULL);
382#ifdef WANT_DYNAMIC_ACCESSLIST
383 if (g_accesslist_pipe_add)
384 pthread_create(&thread_adder_id, NULL, accesslist_adder_worker, NULL);
385 if (g_accesslist_pipe_delete)
386 pthread_create(&thread_deleter_id, NULL, accesslist_deleter_worker, NULL);
387#endif
388}
389
390void accesslist_deinit(void) {
391 /* Wake the worker and wait for it to notice that the tracker is stopping. */
392 pthread_kill(thread_id, SIGHUP);
393 pthread_join(thread_id, NULL);
394
395 pthread_mutex_lock(&g_accesslist_mutex);
396
397 g_accesslist = accesslist_free(g_accesslist);
398
399#ifdef WANT_DYNAMIC_ACCESSLIST
400 g_accesslist_add = accesslist_free(g_accesslist_add);
401 g_accesslist_delete = accesslist_free(g_accesslist_delete);
402#endif
403
404 pthread_mutex_unlock(&g_accesslist_mutex);
405 pthread_mutex_destroy(&g_accesslist_mutex);
406}
407
408void accesslist_cleanup(void) {
409 pthread_mutex_lock(&g_accesslist_mutex);
410
411 accesslist_clean(g_accesslist);
412#if WANT_DYNAMIC_ACCESSLIST
413 accesslist_clean(g_accesslist_add);
414 accesslist_clean(g_accesslist_delete);
415#endif
416
417 pthread_mutex_unlock(&g_accesslist_mutex);
418}
419#endif
420
421int accesslist_address_in_net(const ot_ip6 address, const ot_net *net) {
422 int bits = net->bits, checkbits = (0x7f00 >> (bits & 7));
423 int result = memcmp(address, &net->address, bits >> 3);
424 if (!result && (bits & 7))
425 result = (checkbits & address[bits >> 3]) - (checkbits & net->address[bits >> 3]);
426 return result == 0;
427}
428
429void *accesslist_set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size) {
430 size_t i;
431 int exactmatch;
432
433 /* Caller must have a concept of ot_net in it's member */
434 if (member_size < sizeof(ot_net))
435 return 0;
436
437 /* Check each net in vector for overlap */
438 uint8_t *member = ((uint8_t *)vector->data);
439 for (i = 0; i < vector->size; ++i) {
440 if (accesslist_address_in_net(*(ot_ip6 *)member, net) || accesslist_address_in_net(net->address, (ot_net *)member))
441 return 0;
442 member += member_size;
443 }
444
445 member = vector_find_or_insert(vector, (void *)net, member_size, sizeof(ot_net), &exactmatch);
446 if (member) {
447 memcpy(member, net, sizeof(ot_net));
448 memcpy(member + sizeof(ot_net), value, member_size - sizeof(ot_net));
449 }
450
451 return member;
452}
453
454/* Takes a vector filled with { ot_net net, uint8_t[x] value };
455 Returns value associated with the net, or NULL if not found */
456void *accesslist_get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size) {
457 int exactmatch;
458 /* This binary search will return a pointer to the first non-containing network... */
459 ot_net *net = vector_binary_search(address, vector->data, vector->size, member_size, sizeof(ot_ip6), &exactmatch);
460 if (!net)
461 return NULL;
462 /* ... so we'll need to move back one step unless we've exactly hit the first address in network */
463 if (!exactmatch && ((void *)net > vector->data))
464 --net;
465 if (!accesslist_address_in_net(address, net))
466 return NULL;
467 return (void *)net;
468}
469
470#ifdef WANT_FULLLOG_NETWORKS
471static ot_vector g_lognets_list;
472ot_log *g_logchain_first, *g_logchain_last;
473static pthread_mutex_t g_lognets_list_mutex = PTHREAD_MUTEX_INITIALIZER;
474
475void loglist_add_network(const ot_net *net) {
476 pthread_mutex_lock(&g_lognets_list_mutex);
477 accesslist_set_value_for_net(net, &g_lognets_list, NULL, sizeof(ot_net));
478 pthread_mutex_unlock(&g_lognets_list_mutex);
479}
480
481void loglist_reset(void) {
482 pthread_mutex_lock(&g_lognets_list_mutex);
483 free(g_lognets_list.data);
484 g_lognets_list.data = 0;
485 g_lognets_list.size = g_lognets_list.space = 0;
486 pthread_mutex_unlock(&g_lognets_list_mutex);
487}
488
489int loglist_check_address(const ot_ip6 address) {
490 int result;
491 pthread_mutex_lock(&g_lognets_list_mutex);
492 result = (NULL != accesslist_get_value_for_net(address, &g_lognets_list, sizeof(ot_net)));
493 pthread_mutex_unlock(&g_lognets_list_mutex);
494 return result;
495}
496#endif
497
498#ifdef WANT_IP_FROM_PROXY
499typedef struct {
500 ot_net *proxy;
501 ot_vector networks;
502} ot_proxymap;
503
504static ot_vector g_proxies_list;
505static pthread_mutex_t g_proxies_list_mutex = PTHREAD_MUTEX_INITIALIZER;
506
507int proxylist_add_network(const ot_net *proxy, const ot_net *net) {
508 ot_proxymap *map;
509 int exactmatch, result = 1;
510 pthread_mutex_lock(&g_proxies_list_mutex);
511
512 /* If we have a direct hit, use and extend the vector there */
513 map = vector_binary_search(proxy, g_proxies_list.data, g_proxies_list.size, sizeof(ot_proxymap), sizeof(ot_net), &exactmatch);
514
515 if (!map || !exactmatch) {
516 /* else see, if we've got overlapping networks
517 and get a new empty vector if not */
518 ot_vector empty;
519 memset(&empty, 0, sizeof(ot_vector));
520 map = accesslist_set_value_for_net(proxy, &g_proxies_list, &empty, sizeof(ot_proxymap));
521 }
522
523 if (map && accesslist_set_value_for_net(net, &map->networks, NULL, sizeof(ot_net)))
524 result = 1;
525
526 pthread_mutex_unlock(&g_proxies_list_mutex);
527 return result;
528}
529
530int proxylist_check_proxy(const ot_ip6 proxy, const ot_ip6 address) {
531 int result = 0;
532 ot_proxymap *map;
533
534 pthread_mutex_lock(&g_proxies_list_mutex);
535
536 if ((map = accesslist_get_value_for_net(proxy, &g_proxies_list, sizeof(ot_proxymap))))
537 if (!address || accesslist_get_value_for_net(address, &map->networks, sizeof(ot_net)))
538 result = 1;
539
540 pthread_mutex_unlock(&g_proxies_list_mutex);
541 return result;
542}
543
544#endif
545
546static ot_net g_admin_nets[OT_ADMINIP_MAX];
547static ot_permissions g_admin_nets_permissions[OT_ADMINIP_MAX];
548static size_t g_admin_nets_count = 0;
549
550int accesslist_bless_net(ot_net *net, ot_permissions permissions) {
551 if (g_admin_nets_count >= OT_ADMINIP_MAX)
552 return -1;
553
554 memcpy(g_admin_nets + g_admin_nets_count, net, sizeof(ot_net));
555 g_admin_nets_permissions[g_admin_nets_count++] = permissions;
556
557#ifdef _DEBUG
558 {
559 char _debug[512];
560 int off = snprintf(_debug, sizeof(_debug), "Blessing ip net ");
561 off += ip_format(_debug + off, net->address);
562 if (net->bits < 128) {
563 _debug[off++] = '/';
564 if (ip_is_v4mapped(net->address))
565 off += snprintf(_debug + off, sizeof(_debug) - off, "%d", net->bits - 96);
566 else
567 off += snprintf(_debug + off, sizeof(_debug) - off, "%d", net->bits);
568 }
569
570 if (permissions & OT_PERMISSION_MAY_STAT)
571 off += snprintf(_debug + off, 512 - off, " may_fetch_stats");
572 if (permissions & OT_PERMISSION_MAY_LIVESYNC)
573 off += snprintf(_debug + off, 512 - off, " may_sync_live");
574 if (permissions & OT_PERMISSION_MAY_FULLSCRAPE)
575 off += snprintf(_debug + off, 512 - off, " may_fetch_fullscrapes");
576 if (permissions & OT_PERMISSION_MAY_PROXY)
577 off += snprintf(_debug + off, 512 - off, " may_proxy");
578 if (!permissions)
579 off += snprintf(_debug + off, sizeof(_debug) - off, " nothing");
580 _debug[off++] = '.';
581 _debug[off++] = '\n';
582 (void)write(2, _debug, off);
583 }
584#endif
585
586 return 0;
587}
588
589int accesslist_is_blessed(ot_ip6 ip, ot_permissions permissions) {
590 size_t i;
591 for (i = 0; i < g_admin_nets_count; ++i)
592 if (accesslist_address_in_net(ip, g_admin_nets + i) && (g_admin_nets_permissions[i] & permissions))
593 return 1;
594 return 0;
595}