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 <stdlib.h>
8 #include <string.h>
9 #include <stdio.h>
10 #include <arpa/inet.h>
11 #include <unistd.h>
12 #include <errno.h>
13 #include <stdint.h>
14 
15 /* Libowfat */
16 #include "byte.h"
17 #include "io.h"
18 #include "iob.h"
19 #include "array.h"
20 
21 /* Opentracker */
22 #include "trackerlogic.h"
23 #include "ot_mutex.h"
24 #include "ot_stats.h"
25 #include "ot_clean.h"
26 #include "ot_http.h"
27 #include "ot_accesslist.h"
28 #include "ot_fullscrape.h"
29 #include "ot_livesync.h"
30 
31 /* Forward declaration */
32 size_t return_peers_for_torrent( ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto );
33 
free_peerlist(ot_peerlist * peer_list)34 void free_peerlist( ot_peerlist *peer_list ) {
35   if( peer_list->peers.data ) {
36     if( OT_PEERLIST_HASBUCKETS( peer_list ) ) {
37       ot_vector *bucket_list = (ot_vector*)(peer_list->peers.data);
38 
39       while( peer_list->peers.size-- )
40         free( bucket_list++->data );
41     }
42     free( peer_list->peers.data );
43   }
44   free( peer_list );
45 }
46 
add_torrent_from_saved_state(ot_hash hash,ot_time base,size_t down_count)47 void add_torrent_from_saved_state( ot_hash hash, ot_time base, size_t down_count ) {
48   int         exactmatch;
49   ot_torrent *torrent;
50   ot_vector  *torrents_list = mutex_bucket_lock_by_hash( hash );
51 
52   if( !accesslist_hashisvalid( hash ) )
53     return mutex_bucket_unlock_by_hash( hash, 0 );
54 
55   torrent = vector_find_or_insert( torrents_list, (void*)hash, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
56   if( !torrent || exactmatch )
57     return mutex_bucket_unlock_by_hash( hash, 0 );
58 
59   /* Create a new torrent entry, then */
60   memcpy( torrent->hash, hash, sizeof(ot_hash) );
61 
62   if( !( torrent->peer_list = malloc( sizeof (ot_peerlist) ) ) ) {
63     vector_remove_torrent( torrents_list, torrent );
64     return mutex_bucket_unlock_by_hash( hash, 0 );
65   }
66 
67   byte_zero( torrent->peer_list, sizeof( ot_peerlist ) );
68   torrent->peer_list->base = base;
69   torrent->peer_list->down_count = down_count;
70 
71   return mutex_bucket_unlock_by_hash( hash, 1 );
72 }
73 
add_peer_to_torrent_and_return_peers(PROTO_FLAG proto,struct ot_workstruct * ws,size_t amount)74 size_t add_peer_to_torrent_and_return_peers( PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount ) {
75   int         exactmatch, delta_torrentcount = 0;
76   ot_torrent *torrent;
77   ot_peer    *peer_dest;
78   ot_vector  *torrents_list = mutex_bucket_lock_by_hash( *ws->hash );
79 
80   if( !accesslist_hashisvalid( *ws->hash ) ) {
81     mutex_bucket_unlock_by_hash( *ws->hash, 0 );
82     if( proto == FLAG_TCP ) {
83       const char invalid_hash[] = "d14:failure reason63:Requested download is not authorized for use with this tracker.e";
84       memcpy( ws->reply, invalid_hash, strlen( invalid_hash ) );
85       return strlen( invalid_hash );
86     }
87     return 0;
88   }
89 
90   torrent = vector_find_or_insert( torrents_list, (void*)ws->hash, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
91   if( !torrent ) {
92     mutex_bucket_unlock_by_hash( *ws->hash, 0 );
93     return 0;
94   }
95 
96   if( !exactmatch ) {
97     /* Create a new torrent entry, then */
98     memcpy( torrent->hash, *ws->hash, sizeof(ot_hash) );
99 
100     if( !( torrent->peer_list = malloc( sizeof (ot_peerlist) ) ) ) {
101       vector_remove_torrent( torrents_list, torrent );
102       mutex_bucket_unlock_by_hash( *ws->hash, 0 );
103       return 0;
104     }
105 
106     byte_zero( torrent->peer_list, sizeof( ot_peerlist ) );
107     delta_torrentcount = 1;
108   } else
109     clean_single_torrent( torrent );
110 
111   torrent->peer_list->base = g_now_minutes;
112 
113   /* Check for peer in torrent */
114   peer_dest = vector_find_or_insert_peer( &(torrent->peer_list->peers), &ws->peer, &exactmatch );
115   if( !peer_dest ) {
116     mutex_bucket_unlock_by_hash( *ws->hash, delta_torrentcount );
117     return 0;
118   }
119 
120   /* Tell peer that it's fresh */
121   OT_PEERTIME( &ws->peer ) = 0;
122 
123   /* Sanitize flags: Whoever claims to have completed download, must be a seeder */
124   if( ( OT_PEERFLAG( &ws->peer ) & ( PEER_FLAG_COMPLETED | PEER_FLAG_SEEDING ) ) == PEER_FLAG_COMPLETED )
125     OT_PEERFLAG( &ws->peer ) ^= PEER_FLAG_COMPLETED;
126 
127   /* If we hadn't had a match create peer there */
128   if( !exactmatch ) {
129 
130 #ifdef WANT_SYNC_LIVE
131     if( proto == FLAG_MCA )
132       OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_FROM_SYNC;
133     else
134       livesync_tell( ws );
135 #endif
136 
137     torrent->peer_list->peer_count++;
138     if( OT_PEERFLAG(&ws->peer) & PEER_FLAG_COMPLETED ) {
139       torrent->peer_list->down_count++;
140       stats_issue_event( EVENT_COMPLETED, 0, (uintptr_t)ws );
141     }
142     if( OT_PEERFLAG(&ws->peer) & PEER_FLAG_SEEDING )
143       torrent->peer_list->seed_count++;
144 
145   } else {
146     stats_issue_event( EVENT_RENEW, 0, OT_PEERTIME( peer_dest ) );
147 #ifdef WANT_SPOT_WOODPECKER
148     if( ( OT_PEERTIME(peer_dest) > 0 ) && ( OT_PEERTIME(peer_dest) < 20 ) )
149       stats_issue_event( EVENT_WOODPECKER, 0, (uintptr_t)&ws->peer );
150 #endif
151 #ifdef WANT_SYNC_LIVE
152     /* Won't live sync peers that come back too fast. Only exception:
153        fresh "completed" reports */
154     if( proto != FLAG_MCA ) {
155       if( OT_PEERTIME( peer_dest ) > OT_CLIENT_SYNC_RENEW_BOUNDARY ||
156          ( !(OT_PEERFLAG(peer_dest) & PEER_FLAG_COMPLETED ) && (OT_PEERFLAG(&ws->peer) & PEER_FLAG_COMPLETED ) ) )
157         livesync_tell( ws );
158     }
159 #endif
160 
161     if(  (OT_PEERFLAG(peer_dest) & PEER_FLAG_SEEDING )   && !(OT_PEERFLAG(&ws->peer) & PEER_FLAG_SEEDING ) )
162       torrent->peer_list->seed_count--;
163     if( !(OT_PEERFLAG(peer_dest) & PEER_FLAG_SEEDING )   &&  (OT_PEERFLAG(&ws->peer) & PEER_FLAG_SEEDING ) )
164       torrent->peer_list->seed_count++;
165     if( !(OT_PEERFLAG(peer_dest) & PEER_FLAG_COMPLETED ) &&  (OT_PEERFLAG(&ws->peer) & PEER_FLAG_COMPLETED ) ) {
166       torrent->peer_list->down_count++;
167       stats_issue_event( EVENT_COMPLETED, 0, (uintptr_t)ws );
168     }
169     if(   OT_PEERFLAG(peer_dest) & PEER_FLAG_COMPLETED )
170       OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_COMPLETED;
171   }
172 
173   memcpy( peer_dest, &ws->peer, sizeof(ot_peer) );
174 #ifdef WANT_SYNC
175   if( proto == FLAG_MCA ) {
176     mutex_bucket_unlock_by_hash( *ws->hash, delta_torrentcount );
177     return 0;
178   }
179 #endif
180 
181   ws->reply_size = return_peers_for_torrent( torrent, amount, ws->reply, proto );
182   mutex_bucket_unlock_by_hash( *ws->hash, delta_torrentcount );
183   return ws->reply_size;
184 }
185 
return_peers_all(ot_peerlist * peer_list,char * reply)186 static size_t return_peers_all( ot_peerlist *peer_list, char *reply ) {
187   unsigned int bucket, num_buckets = 1;
188   ot_vector  * bucket_list = &peer_list->peers;
189   size_t       result = OT_PEER_COMPARE_SIZE * peer_list->peer_count;
190   char       * r_end = reply + result;
191 
192   if( OT_PEERLIST_HASBUCKETS(peer_list) ) {
193     num_buckets = bucket_list->size;
194     bucket_list = (ot_vector *)bucket_list->data;
195   }
196 
197   for( bucket = 0; bucket<num_buckets; ++bucket ) {
198     ot_peer * peers = (ot_peer*)bucket_list[bucket].data;
199     size_t    peer_count = bucket_list[bucket].size;
200     while( peer_count-- ) {
201       if( OT_PEERFLAG(peers) & PEER_FLAG_SEEDING ) {
202         r_end-=OT_PEER_COMPARE_SIZE;
203         memcpy(r_end,peers++,OT_PEER_COMPARE_SIZE);
204       } else {
205         memcpy(reply,peers++,OT_PEER_COMPARE_SIZE);
206         reply+=OT_PEER_COMPARE_SIZE;
207       }
208     }
209   }
210   return result;
211 }
212 
return_peers_selection(ot_peerlist * peer_list,size_t amount,char * reply)213 static size_t return_peers_selection( ot_peerlist *peer_list, size_t amount, char *reply ) {
214   unsigned int bucket_offset, bucket_index = 0, num_buckets = 1;
215   ot_vector  * bucket_list = &peer_list->peers;
216   unsigned int shifted_pc = peer_list->peer_count;
217   unsigned int shifted_step = 0;
218   unsigned int shift = 0;
219   size_t       result = OT_PEER_COMPARE_SIZE * amount;
220   char       * r_end = reply + result;
221 
222   if( OT_PEERLIST_HASBUCKETS(peer_list) ) {
223     num_buckets = bucket_list->size;
224     bucket_list = (ot_vector *)bucket_list->data;
225   }
226 
227   /* Make fixpoint arithmetic as exact as possible */
228 #define MAXPRECBIT (1<<(8*sizeof(int)-3))
229   while( !(shifted_pc & MAXPRECBIT ) ) { shifted_pc <<= 1; shift++; }
230   shifted_step = shifted_pc/amount;
231 #undef MAXPRECBIT
232 
233   /* Initialize somewhere in the middle of peers so that
234    fixpoint's aliasing doesn't alway miss the same peers */
235   bucket_offset = random() % peer_list->peer_count;
236 
237   while( amount-- ) {
238     ot_peer * peer;
239 
240     /* This is the aliased, non shifted range, next value may fall into */
241     unsigned int diff = ( ( ( amount + 1 ) * shifted_step ) >> shift ) -
242                         ( (   amount       * shifted_step ) >> shift );
243     bucket_offset += 1 + random() % diff;
244 
245     while( bucket_offset >= bucket_list[bucket_index].size ) {
246       bucket_offset -= bucket_list[bucket_index].size;
247       bucket_index = ( bucket_index + 1 ) % num_buckets;
248     }
249     peer = ((ot_peer*)bucket_list[bucket_index].data) + bucket_offset;
250     if( OT_PEERFLAG(peer) & PEER_FLAG_SEEDING ) {
251       r_end-=OT_PEER_COMPARE_SIZE;
252       memcpy(r_end,peer,OT_PEER_COMPARE_SIZE);
253     } else {
254       memcpy(reply,peer,OT_PEER_COMPARE_SIZE);
255       reply+=OT_PEER_COMPARE_SIZE;
256     }
257   }
258   return result;
259 }
260 
261 /* Compiles a list of random peers for a torrent
262    * reply must have enough space to hold 92+6*amount bytes
263    * does not yet check not to return self
264 */
return_peers_for_torrent(ot_torrent * torrent,size_t amount,char * reply,PROTO_FLAG proto)265 size_t return_peers_for_torrent( ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto ) {
266   ot_peerlist *peer_list = torrent->peer_list;
267   char        *r = reply;
268 
269   if( amount > peer_list->peer_count )
270     amount = peer_list->peer_count;
271 
272   if( proto == FLAG_TCP ) {
273     int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
274     r += sprintf( r, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie" PEERS_BENCODED "%zd:", peer_list->seed_count, peer_list->down_count, peer_list->peer_count-peer_list->seed_count, erval, erval/2, OT_PEER_COMPARE_SIZE*amount );
275   } else {
276     *(uint32_t*)(r+0) = htonl( OT_CLIENT_REQUEST_INTERVAL_RANDOM );
277     *(uint32_t*)(r+4) = htonl( peer_list->peer_count - peer_list->seed_count );
278     *(uint32_t*)(r+8) = htonl( peer_list->seed_count );
279     r += 12;
280   }
281 
282   if( amount ) {
283     if( amount == peer_list->peer_count )
284       r += return_peers_all( peer_list, r );
285     else
286       r += return_peers_selection( peer_list, amount, r );
287   }
288 
289   if( proto == FLAG_TCP )
290     *r++ = 'e';
291 
292   return r - reply;
293 }
294 
295 /* Fetches scrape info for a specific torrent */
return_udp_scrape_for_torrent(ot_hash hash,char * reply)296 size_t return_udp_scrape_for_torrent( ot_hash hash, char *reply ) {
297   int          exactmatch, delta_torrentcount = 0;
298   ot_vector   *torrents_list = mutex_bucket_lock_by_hash( hash );
299   ot_torrent  *torrent = binary_search( hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
300 
301   if( !exactmatch ) {
302     memset( reply, 0, 12);
303   } else {
304     uint32_t *r = (uint32_t*) reply;
305 
306     if( clean_single_torrent( torrent ) ) {
307       vector_remove_torrent( torrents_list, torrent );
308       memset( reply, 0, 12);
309       delta_torrentcount = -1;
310     } else {
311       r[0] = htonl( torrent->peer_list->seed_count );
312       r[1] = htonl( torrent->peer_list->down_count );
313       r[2] = htonl( torrent->peer_list->peer_count-torrent->peer_list->seed_count );
314     }
315   }
316   mutex_bucket_unlock_by_hash( hash, delta_torrentcount );
317   return 12;
318 }
319 
320 /* Fetches scrape info for a specific torrent */
return_tcp_scrape_for_torrent(ot_hash * hash_list,int amount,char * reply)321 size_t return_tcp_scrape_for_torrent( ot_hash *hash_list, int amount, char *reply ) {
322   char *r = reply;
323   int   exactmatch, i;
324 
325   r += sprintf( r, "d5:filesd" );
326 
327   for( i=0; i<amount; ++i ) {
328     int          delta_torrentcount = 0;
329     ot_hash     *hash = hash_list + i;
330     ot_vector   *torrents_list = mutex_bucket_lock_by_hash( *hash );
331     ot_torrent  *torrent = binary_search( hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
332 
333     if( exactmatch ) {
334       if( clean_single_torrent( torrent ) ) {
335         vector_remove_torrent( torrents_list, torrent );
336         delta_torrentcount = -1;
337       } else {
338         *r++='2';*r++='0';*r++=':';
339         memcpy( r, hash, sizeof(ot_hash) ); r+=sizeof(ot_hash);
340         r += sprintf( r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee",
341           torrent->peer_list->seed_count, torrent->peer_list->down_count, torrent->peer_list->peer_count-torrent->peer_list->seed_count );
342       }
343     }
344     mutex_bucket_unlock_by_hash( *hash, delta_torrentcount );
345   }
346 
347   *r++ = 'e'; *r++ = 'e';
348   return r - reply;
349 }
350 
351 static ot_peerlist dummy_list;
remove_peer_from_torrent(PROTO_FLAG proto,struct ot_workstruct * ws)352 size_t remove_peer_from_torrent( PROTO_FLAG proto, struct ot_workstruct *ws ) {
353   int          exactmatch;
354   ot_vector   *torrents_list = mutex_bucket_lock_by_hash( *ws->hash );
355   ot_torrent  *torrent = binary_search( ws->hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
356   ot_peerlist *peer_list = &dummy_list;
357 
358 #ifdef WANT_SYNC_LIVE
359   if( proto != FLAG_MCA ) {
360     OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_STOPPED;
361     livesync_tell( ws );
362   }
363 #endif
364 
365   if( exactmatch ) {
366     peer_list = torrent->peer_list;
367     switch( vector_remove_peer( &peer_list->peers, &ws->peer ) ) {
368       case 2:  peer_list->seed_count--; /* Fall throughs intended */
369       case 1:  peer_list->peer_count--; /* Fall throughs intended */
370       default: break;
371     }
372   }
373 
374   if( proto == FLAG_TCP ) {
375     int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
376     ws->reply_size = sprintf( ws->reply, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie" PEERS_BENCODED "0:e", peer_list->seed_count, peer_list->peer_count - peer_list->seed_count, erval, erval / 2 );
377   }
378 
379   /* Handle UDP reply */
380   if( proto == FLAG_UDP ) {
381     ((uint32_t*)ws->reply)[2] = htonl( OT_CLIENT_REQUEST_INTERVAL_RANDOM );
382     ((uint32_t*)ws->reply)[3] = htonl( peer_list->peer_count - peer_list->seed_count );
383     ((uint32_t*)ws->reply)[4] = htonl( peer_list->seed_count);
384     ws->reply_size = 20;
385   }
386 
387   mutex_bucket_unlock_by_hash( *ws->hash, 0 );
388   return ws->reply_size;
389 }
390 
iterate_all_torrents(int (* for_each)(ot_torrent * torrent,uintptr_t data),uintptr_t data)391 void iterate_all_torrents( int (*for_each)( ot_torrent* torrent, uintptr_t data ), uintptr_t data ) {
392   int bucket;
393   size_t j;
394 
395   for( bucket=0; bucket<OT_BUCKET_COUNT; ++bucket ) {
396     ot_vector  *torrents_list = mutex_bucket_lock( bucket );
397     ot_torrent *torrents = (ot_torrent*)(torrents_list->data);
398 
399     for( j=0; j<torrents_list->size; ++j )
400       if( for_each( torrents + j, data ) )
401         break;
402 
403     mutex_bucket_unlock( bucket, 0 );
404     if( !g_opentracker_running ) return;
405   }
406 }
407 
exerr(char * message)408 void exerr( char * message ) {
409   fprintf( stderr, "%s\n", message );
410   exit( 111 );
411 }
412 
trackerlogic_init()413 void trackerlogic_init( ) {
414   g_tracker_id = random();
415 
416   if( !g_stats_path )
417     g_stats_path = "stats";
418   g_stats_path_len = strlen( g_stats_path );
419 
420   /* Initialise background worker threads */
421   mutex_init( );
422   clean_init( );
423   fullscrape_init( );
424   accesslist_init( );
425   livesync_init( );
426   stats_init( );
427 }
428 
trackerlogic_deinit(void)429 void trackerlogic_deinit( void ) {
430   int bucket, delta_torrentcount = 0;
431   size_t j;
432 
433   /* Free all torrents... */
434   for(bucket=0; bucket<OT_BUCKET_COUNT; ++bucket ) {
435     ot_vector *torrents_list = mutex_bucket_lock( bucket );
436     if( torrents_list->size ) {
437       for( j=0; j<torrents_list->size; ++j ) {
438         ot_torrent *torrent = ((ot_torrent*)(torrents_list->data)) + j;
439         free_peerlist( torrent->peer_list );
440         delta_torrentcount -= 1;
441       }
442       free( torrents_list->data );
443     }
444     mutex_bucket_unlock( bucket, delta_torrentcount );
445   }
446 
447   /* Deinitialise background worker threads */
448   stats_deinit( );
449   livesync_deinit( );
450   accesslist_deinit( );
451   fullscrape_deinit( );
452   clean_deinit( );
453   /* Release mutexes */
454   mutex_deinit( );
455 }
456 
457 const char *g_version_trackerlogic_c = "$Source$: $Revision$\n";
458