Make chunked transfers use gzip also
[opentracker.git] / ot_accesslist.c
blob1badc254175d0827dfbd29e4b6fd00d30463d243
1 /* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
4 $id$ */
6 /* System */
7 #include <pthread.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <signal.h>
12 #include <unistd.h>
13 #ifdef WANT_DYNAMIC_ACCESSLIST
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <errno.h>
17 #endif
19 /* Libowfat */
20 #include "byte.h"
21 #include "scan.h"
22 #include "ip6.h"
23 #include "mmap.h"
24 #include "fmt.h"
26 /* Opentracker */
27 #include "trackerlogic.h"
28 #include "ot_accesslist.h"
29 #include "ot_vector.h"
31 /* GLOBAL VARIABLES */
32 #ifdef WANT_ACCESSLIST
33 char *g_accesslist_filename = NULL;
34 #ifdef WANT_DYNAMIC_ACCESSLIST
35 char *g_accesslist_pipe_add = NULL;
36 char *g_accesslist_pipe_delete = NULL;
37 #endif
38 static pthread_mutex_t g_accesslist_mutex;
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.
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.
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. */
50 struct ot_accesslist;
51 typedef struct ot_accesslist ot_accesslist;
52 struct ot_accesslist {
53 ot_hash *list;
54 size_t size;
55 ot_time base;
56 ot_accesslist *next;
58 static ot_accesslist * _Atomic g_accesslist = NULL;
59 #ifdef WANT_DYNAMIC_ACCESSLIST
60 static ot_accesslist * _Atomic g_accesslist_add = NULL;
61 static ot_accesslist * _Atomic g_accesslist_delete = NULL;
62 #endif
64 /* Helpers to work on access lists */
65 static int vector_compare_hash(const void *hash1, const void *hash2 ) {
66 return memcmp( hash1, hash2, OT_HASH_COMPARE_SIZE );
69 static ot_accesslist * accesslist_free(ot_accesslist *accesslist) {
70 while (accesslist) {
71 ot_accesslist * this_accesslist = accesslist;
72 accesslist = this_accesslist->next;
73 free(this_accesslist->list);
74 free(this_accesslist);
76 return NULL;
79 static ot_accesslist * accesslist_make(ot_accesslist *next, size_t size) {
80 ot_accesslist * accesslist_new = malloc(sizeof(ot_accesslist));
81 if (accesslist_new) {
82 accesslist_new->list = size ? malloc(sizeof(ot_hash) * size) : NULL;
83 accesslist_new->size = size;
84 accesslist_new->base = g_now_minutes;
85 accesslist_new->next = next;
86 if (size && !accesslist_new->list) {
87 free(accesslist_new);
88 accesslist_new = NULL;
91 return accesslist_new;
94 /* This must be called with g_accesslist_mutex held.
95 This will never delete head, because that might still be in use. */
96 static void accesslist_clean(ot_accesslist *accesslist) {
97 while (accesslist && accesslist->next) {
98 if (accesslist->next->base + 5 < g_now_minutes)
99 accesslist->next = accesslist_free(accesslist->next);
100 accesslist = accesslist->next;
104 /* Read initial access list */
105 static void accesslist_readfile( void ) {
106 ot_accesslist * accesslist_new;
107 ot_hash *info_hash;
108 const char *map, *map_end, *read_offs;
109 size_t maplen;
111 if( ( map = mmap_read( g_accesslist_filename, &maplen ) ) == NULL ) {
112 char *wd = getcwd( NULL, 0 );
113 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 );
114 free( wd );
115 return;
118 /* You need at least 41 bytes to pass an info_hash, make enough room
119 for the maximum amount of them */
120 accesslist_new = accesslist_make(g_accesslist, maplen / 41);
121 if( !accesslist_new ) {
122 fprintf( stderr, "Warning: Not enough memory to allocate %zd bytes for accesslist buffer. May succeed later.\n", ( maplen / 41 ) * 20 );
123 mmap_unmap( map, maplen);
124 return;
126 info_hash = accesslist_new->list;
128 /* No use to scan if there's not enough room for another full info_hash */
129 map_end = map + maplen - 40;
130 read_offs = map;
132 /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*" */
133 while( read_offs <= map_end ) {
134 int i;
135 for( i=0; i<(int)sizeof(ot_hash); ++i ) {
136 int eger1 = scan_fromhex( (unsigned char)read_offs[ 2*i ] );
137 int eger2 = scan_fromhex( (unsigned char)read_offs[ 1 + 2*i ] );
138 if( eger1 < 0 || eger2 < 0 )
139 break;
140 (*info_hash)[i] = (uint8_t)(eger1 * 16 + eger2);
143 if( i == sizeof(ot_hash) ) {
144 read_offs += 40;
146 /* Append accesslist to accesslist vector */
147 if( read_offs == map_end || scan_fromhex( (unsigned char)*read_offs ) < 0 )
148 ++info_hash;
151 /* Find start of next line */
152 while( read_offs <= map_end && *(read_offs++) != '\n' );
154 #ifdef _DEBUG
155 fprintf( stderr, "Added %zd info_hashes to accesslist\n", (size_t)(info_hash - accesslist_new->list) );
156 #endif
158 mmap_unmap( map, maplen);
160 qsort( accesslist_new->list, info_hash - accesslist_new->list, sizeof( *info_hash ), vector_compare_hash );
161 accesslist_new->size = info_hash - accesslist_new->list;
163 /* Now exchange the accesslist vector in the least race condition prone way */
164 pthread_mutex_lock(&g_accesslist_mutex);
165 accesslist_new->next = g_accesslist;
166 g_accesslist = accesslist_new; /* Only now set a new list */
168 #ifdef WANT_DYNAMIC_ACCESSLIST
169 /* If we have dynamic accesslists, reloading a new one will always void the add/delete lists.
170 Insert empty ones at the list head */
171 if (g_accesslist_add && (accesslist_new = accesslist_make(g_accesslist_add, 0)) != NULL)
172 g_accesslist_add = accesslist_new;
173 if (g_accesslist_delete && (accesslist_new = accesslist_make(g_accesslist_delete, 0)) != NULL)
174 g_accesslist_delete = accesslist_new;
175 #endif
177 accesslist_clean(g_accesslist);
179 pthread_mutex_unlock(&g_accesslist_mutex);
182 int accesslist_hashisvalid( ot_hash hash ) {
183 /* Get working copy of current access list */
184 ot_accesslist * accesslist = g_accesslist;
185 #ifdef WANT_DYNAMIC_ACCESSLIST
186 ot_accesslist * accesslist_add, * accesslist_delete;
187 #endif
188 void * exactmatch = NULL;
190 if (accesslist)
191 exactmatch = bsearch( hash, accesslist->list, accesslist->size, OT_HASH_COMPARE_SIZE, vector_compare_hash );
193 #ifdef WANT_DYNAMIC_ACCESSLIST
194 /* If we had no match on the main list, scan the list of dynamically added hashes */
195 accesslist_add = g_accesslist_add;
196 if ((exactmatch == NULL) && accesslist_add)
197 exactmatch = bsearch( hash, accesslist_add->list, accesslist_add->size, OT_HASH_COMPARE_SIZE, vector_compare_hash );
199 /* If we found a matching hash on the main list, scan the list of dynamically deleted hashes */
200 accesslist_delete = g_accesslist_delete;
201 if ((exactmatch != NULL) && accesslist_delete && bsearch( hash, accesslist_add->list, accesslist_add->size, OT_HASH_COMPARE_SIZE, vector_compare_hash ))
202 exactmatch = NULL;
203 #endif
205 #ifdef WANT_ACCESSLIST_BLACK
206 return exactmatch == NULL;
207 #else
208 return exactmatch != NULL;
209 #endif
212 static void * accesslist_worker( void * args ) {
213 int sig;
214 sigset_t signal_mask;
216 sigemptyset(&signal_mask);
217 sigaddset(&signal_mask, SIGHUP);
219 (void)args;
221 while( 1 ) {
222 if (!g_opentracker_running)
223 return NULL;
225 /* Initial attempt to read accesslist */
226 accesslist_readfile( );
228 /* Wait for signals */
229 while( sigwait (&signal_mask, &sig) != 0 && sig != SIGHUP );
231 return NULL;
234 #ifdef WANT_DYNAMIC_ACCESSLIST
235 static pthread_t thread_adder_id, thread_deleter_id;
236 static void * accesslist_adddel_worker(char * fifoname, ot_accesslist * _Atomic * adding_to, ot_accesslist * _Atomic * removing_from) {
237 struct stat st;
239 if (!stat(fifoname, &st)) {
240 if (!S_ISFIFO(st.st_mode)) {
241 fprintf(stderr, "Error when starting dynamic accesslists: Found Non-FIFO file at %s.\nPlease remove it and restart opentracker.\n", fifoname);
242 return NULL;
244 } else {
245 int error = mkfifo(fifoname, 0755);
246 if (error && error != EEXIST) {
247 fprintf(stderr, "Error when starting dynamic accesslists: Couldn't create FIFO at %s, error: %s\n", fifoname, strerror(errno));
248 return NULL;
252 while (g_opentracker_running) {
253 FILE * fifo = fopen(fifoname, "r");
254 char *line = NULL;
255 size_t linecap = 0;
256 ssize_t linelen;
258 if (!fifo) {
259 fprintf(stderr, "Error when reading dynamic accesslists: Couldn't open FIFO at %s, error: %s\n", fifoname, strerror(errno));
260 return NULL;
263 while ((linelen = getline(&line, &linecap, fifo)) > 0) {
264 ot_hash info_hash;
265 int i;
267 printf("Got line %*s", (int)linelen, line);
268 /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*"
269 If there's not enough characters for an info_hash in the line, skip it. */
270 if (linelen < 41)
271 continue;
273 for( i=0; i<(int)sizeof(ot_hash); ++i ) {
274 int eger1 = scan_fromhex( (unsigned char)line[ 2*i ] );
275 int eger2 = scan_fromhex( (unsigned char)line[ 1 + 2*i ] );
276 if( eger1 < 0 || eger2 < 0 )
277 break;
278 ((uint8_t*)info_hash)[i] = (uint8_t)(eger1 * 16 + eger2);
280 printf("parsed info_hash %20s\n", info_hash);
281 if( i != sizeof(ot_hash) )
282 continue;
284 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the
285 other worker threads from doing the same */
286 pthread_mutex_lock(&g_accesslist_mutex);
288 /* If the info hash is in the removing_from list, create a new head without that entry */
289 if (*removing_from && (*removing_from)->list) {
290 ot_hash * exactmatch = bsearch( info_hash, (*removing_from)->list, (*removing_from)->size, OT_HASH_COMPARE_SIZE, vector_compare_hash );
291 if (exactmatch) {
292 ptrdiff_t off = exactmatch - (*removing_from)->list;
293 ot_accesslist * accesslist_new = accesslist_make(*removing_from, (*removing_from)->size - 1);
294 if (accesslist_new) {
295 memcpy(accesslist_new->list, (*removing_from)->list, sizeof(ot_hash) * off);
296 memcpy(accesslist_new->list + off, (*removing_from)->list + off + 1, (*removing_from)->size - off - 1);
297 *removing_from = accesslist_new;
302 /* Simple case: there's no adding_to list yet, create one with one member */
303 if (!*adding_to) {
304 ot_accesslist * accesslist_new = accesslist_make(NULL, 1);
305 if (accesslist_new) {
306 memcpy(accesslist_new->list, info_hash, sizeof(ot_hash));
307 *adding_to = accesslist_new;
309 } else {
310 int exactmatch = 0;
311 ot_hash * insert_point = binary_search( info_hash, (*adding_to)->list, (*adding_to)->size, OT_HASH_COMPARE_SIZE, sizeof(ot_hash), &exactmatch );
313 /* Only if the info hash is not in the adding_to list, create a new head with that entry */
314 if (!exactmatch) {
315 ot_accesslist * accesslist_new = accesslist_make(*adding_to, (*adding_to)->size + 1);
316 ptrdiff_t off = insert_point - (*adding_to)->list;
317 if (accesslist_new) {
318 memcpy(accesslist_new->list, (*adding_to)->list, sizeof(ot_hash) * off);
319 memcpy(accesslist_new->list + off, info_hash, sizeof(info_hash));
320 memcpy(accesslist_new->list + off + 1, (*adding_to)->list + off, (*adding_to)->size - off);
321 *adding_to = accesslist_new;
326 pthread_mutex_unlock(&g_accesslist_mutex);
329 fclose(fifo);
331 return NULL;
334 static void * accesslist_adder_worker( void * args ) {
335 (void)args;
336 return accesslist_adddel_worker(g_accesslist_pipe_add, &g_accesslist_add, &g_accesslist_delete);
338 static void * accesslist_deleter_worker( void * args ) {
339 (void)args;
340 return accesslist_adddel_worker(g_accesslist_pipe_delete, &g_accesslist_delete, &g_accesslist_add);
342 #endif
344 static pthread_t thread_id;
345 void accesslist_init( ) {
346 pthread_mutex_init(&g_accesslist_mutex, NULL);
347 pthread_create( &thread_id, NULL, accesslist_worker, NULL );
348 #ifdef WANT_DYNAMIC_ACCESSLIST
349 if (g_accesslist_pipe_add)
350 pthread_create( &thread_adder_id, NULL, accesslist_adder_worker, NULL );
351 if (g_accesslist_pipe_delete)
352 pthread_create( &thread_deleter_id, NULL, accesslist_deleter_worker, NULL );
353 #endif
356 void accesslist_deinit( void ) {
357 /* Wake up sleeping worker */
358 pthread_kill(thread_id, SIGHUP);
360 pthread_mutex_lock(&g_accesslist_mutex);
362 g_accesslist = accesslist_free(g_accesslist);
364 #ifdef WANT_DYNAMIC_ACCESSLIST
365 g_accesslist_add = accesslist_free(g_accesslist_add);
366 g_accesslist_delete = accesslist_free(g_accesslist_delete);
367 #endif
369 pthread_mutex_unlock(&g_accesslist_mutex);
370 pthread_cancel( thread_id );
371 pthread_mutex_destroy(&g_accesslist_mutex);
374 void accesslist_cleanup( void ) {
375 pthread_mutex_lock(&g_accesslist_mutex);
377 accesslist_clean(g_accesslist);
378 #if WANT_DYNAMIC_ACCESSLIST
379 accesslist_clean(g_accesslist_add);
380 accesslist_clean(g_accesslist_delete);
381 #endif
383 pthread_mutex_unlock(&g_accesslist_mutex);
385 #endif
387 int address_in_net( const ot_ip6 address, const ot_net *net ) {
388 int bits = net->bits, checkbits = ( 0x7f00 >> ( bits & 7 ));
389 int result = memcmp( address, &net->address, bits >> 3 );
390 if( !result && ( bits & 7 ) )
391 result = ( checkbits & address[bits>>3] ) - ( checkbits & net->address[bits>>3]);
392 return result == 0;
395 void *set_value_for_net( const ot_net *net, ot_vector *vector, const void *value, const size_t member_size ) {
396 size_t i;
397 int exactmatch;
399 /* Caller must have a concept of ot_net in it's member */
400 if( member_size < sizeof(ot_net) )
401 return 0;
403 /* Check each net in vector for overlap */
404 uint8_t *member = ((uint8_t*)vector->data);
405 for( i=0; i<vector->size; ++i ) {
406 if( address_in_net( *(ot_ip6*)member, net ) ||
407 address_in_net( net->address, (ot_net*)member ) )
408 return 0;
409 member += member_size;
412 member = vector_find_or_insert( vector, (void*)net, member_size, sizeof(ot_net), &exactmatch );
413 if( member ) {
414 memcpy( member, net, sizeof(ot_net));
415 memcpy( member + sizeof(ot_net), value, member_size - sizeof(ot_net));
418 return member;
421 /* Takes a vector filled with { ot_net net, uint8_t[x] value };
422 Returns value associated with the net, or NULL if not found */
423 void *get_value_for_net( const ot_ip6 address, const ot_vector *vector, const size_t member_size ) {
424 int exactmatch;
425 /* This binary search will return a pointer to the first non-containing network... */
426 ot_net *net = binary_search( address, vector->data, vector->size, member_size, sizeof(ot_ip6), &exactmatch );
427 if( !net )
428 return NULL;
429 /* ... so we'll need to move back one step unless we've exactly hit the first address in network */
430 if( !exactmatch && ( (void*)net > vector->data ) )
431 --net;
432 if( !address_in_net( address, net ) )
433 return NULL;
434 return (void*)net;
437 #ifdef WANT_FULLLOG_NETWORKS
438 static ot_vector g_lognets_list;
439 ot_log *g_logchain_first, *g_logchain_last;
441 static pthread_mutex_t g_lognets_list_mutex = PTHREAD_MUTEX_INITIALIZER;
442 void loglist_add_network( const ot_net *net ) {
443 pthread_mutex_lock(&g_lognets_list_mutex);
444 set_value_for_net( net, &g_lognets_list, NULL, sizeof(ot_net));
445 pthread_mutex_unlock(&g_lognets_list_mutex);
448 void loglist_reset( ) {
449 pthread_mutex_lock(&g_lognets_list_mutex);
450 free( g_lognets_list.data );
451 g_lognets_list.data = 0;
452 g_lognets_list.size = g_lognets_list.space = 0;
453 pthread_mutex_unlock(&g_lognets_list_mutex);
456 int loglist_check_address( const ot_ip6 address ) {
457 int result;
458 pthread_mutex_lock(&g_lognets_list_mutex);
459 result = ( NULL != get_value_for_net( address, &g_lognets_list, sizeof(ot_net)) );
460 pthread_mutex_unlock(&g_lognets_list_mutex);
461 return result;
463 #endif
465 #ifdef WANT_IP_FROM_PROXY
466 typedef struct {
467 ot_net *proxy;
468 ot_vector networks;
469 } ot_proxymap;
471 static ot_vector g_proxies_list;
472 static pthread_mutex_t g_proxies_list_mutex = PTHREAD_MUTEX_INITIALIZER;
474 int proxylist_add_network( const ot_net *proxy, const ot_net *net ) {
475 ot_proxymap *map;
476 int exactmatch, result = 1;
477 pthread_mutex_lock(&g_proxies_list_mutex);
479 /* If we have a direct hit, use and extend the vector there */
480 map = binary_search( proxy, g_proxies_list.data, g_proxies_list.size, sizeof(ot_proxymap), sizeof(ot_net), &exactmatch );
482 if( !map || !exactmatch ) {
483 /* else see, if we've got overlapping networks
484 and get a new empty vector if not */
485 ot_vector empty;
486 memset( &empty, 0, sizeof( ot_vector ) );
487 map = set_value_for_net( proxy, &g_proxies_list, &empty, sizeof(ot_proxymap));
490 if( map && set_value_for_net( net, &map->networks, NULL, sizeof(ot_net) ) )
491 result = 1;
493 pthread_mutex_unlock(&g_proxies_list_mutex);
494 return result;
497 int proxylist_check_proxy( const ot_ip6 proxy, const ot_ip6 address ) {
498 int result = 0;
499 ot_proxymap *map;
501 pthread_mutex_lock(&g_proxies_list_mutex);
503 if( ( map = get_value_for_net( proxy, &g_proxies_list, sizeof(ot_proxymap) ) ) )
504 if( !address || get_value_for_net( address, &map->networks, sizeof(ot_net) ) )
505 result = 1;
507 pthread_mutex_unlock(&g_proxies_list_mutex);
508 return result;
511 #endif
513 static ot_net g_admin_nets[OT_ADMINIP_MAX];
514 static ot_permissions g_admin_nets_permissions[OT_ADMINIP_MAX];
515 static unsigned int g_admin_nets_count = 0;
517 int accesslist_bless_net( ot_net *net, ot_permissions permissions ) {
518 if( g_admin_nets_count >= OT_ADMINIP_MAX )
519 return -1;
521 memcpy(g_admin_nets + g_admin_nets_count, net, sizeof(ot_net));
522 g_admin_nets_permissions[ g_admin_nets_count++ ] = permissions;
524 #ifdef _DEBUG
526 char _debug[512];
527 int off = snprintf( _debug, sizeof(_debug), "Blessing ip net " );
528 off += fmt_ip6c(_debug+off, net->address );
529 if( net->bits < 128) {
530 _debug[off++] = '/';
531 if( ip6_isv4mapped(net->address) )
532 off += fmt_long(_debug+off, net->bits-96);
533 else
534 off += fmt_long(_debug+off, net->bits);
537 if( permissions & OT_PERMISSION_MAY_STAT ) off += snprintf( _debug+off, 512-off, " may_fetch_stats" );
538 if( permissions & OT_PERMISSION_MAY_LIVESYNC ) off += snprintf( _debug+off, 512-off, " may_sync_live" );
539 if( permissions & OT_PERMISSION_MAY_FULLSCRAPE ) off += snprintf( _debug+off, 512-off, " may_fetch_fullscrapes" );
540 if( permissions & OT_PERMISSION_MAY_PROXY ) off += snprintf( _debug+off, 512-off, " may_proxy" );
541 if( !permissions ) off += snprintf( _debug+off, sizeof(_debug)-off, " nothing" );
542 _debug[off++] = '.';
543 _debug[off++] = '\n';
544 (void)write( 2, _debug, off );
546 #endif
548 return 0;
551 int accesslist_is_blessed( ot_ip6 ip, ot_permissions permissions ) {
552 unsigned int i;
553 for( i=0; i<g_admin_nets_count; ++i )
554 if( address_in_net(ip, g_admin_nets + i) && (g_admin_nets_permissions[ i ] & permissions ))
555 return 1;
556 return 0;
559 const char *g_version_accesslist_c = "$Source$: $Revision$\n";