diff options
Diffstat (limited to 'opentracker.c')
-rw-r--r-- | opentracker.c | 656 |
1 files changed, 38 insertions, 618 deletions
diff --git a/opentracker.c b/opentracker.c index debb868..cd6205c 100644 --- a/opentracker.c +++ b/opentracker.c | |||
@@ -6,9 +6,8 @@ | |||
6 | /* System */ | 6 | /* System */ |
7 | #include <string.h> | 7 | #include <string.h> |
8 | #include <sys/types.h> | 8 | #include <sys/types.h> |
9 | #include <sys/stat.h> | ||
10 | #include <sys/socket.h> | 9 | #include <sys/socket.h> |
11 | #include <arpa/inet.h> | 10 | //#include <arpa/inet.h> |
12 | #include <unistd.h> | 11 | #include <unistd.h> |
13 | #include <stdlib.h> | 12 | #include <stdlib.h> |
14 | #include <errno.h> | 13 | #include <errno.h> |
@@ -20,605 +19,35 @@ | |||
20 | #include "socket.h" | 19 | #include "socket.h" |
21 | #include "io.h" | 20 | #include "io.h" |
22 | #include "iob.h" | 21 | #include "iob.h" |
23 | #include "buffer.h" | ||
24 | #include "array.h" | 22 | #include "array.h" |
25 | #include "byte.h" | ||
26 | #include "case.h" | ||
27 | #include "fmt.h" | 23 | #include "fmt.h" |
28 | #include "str.h" | ||
29 | #include "scan.h" | 24 | #include "scan.h" |
30 | #include "ip4.h" | 25 | #include "ip4.h" |
31 | 26 | ||
32 | /* Opentracker */ | 27 | /* Opentracker */ |
33 | #include "trackerlogic.h" | 28 | #include "trackerlogic.h" |
34 | #include "scan_urlencoded_query.h" | ||
35 | #include "ot_stats.h" | ||
36 | #include "ot_sync.h" | ||
37 | #include "ot_udp.h" | ||
38 | #include "ot_fullscrape.h" | ||
39 | #include "ot_iovec.h" | 29 | #include "ot_iovec.h" |
40 | #include "ot_accesslist.h" | ||
41 | #include "ot_mutex.h" | 30 | #include "ot_mutex.h" |
31 | #include "ot_http.h" | ||
32 | #include "ot_udp.h" | ||
42 | #include "ot_clean.h" | 33 | #include "ot_clean.h" |
34 | #include "ot_accesslist.h" | ||
35 | #include "ot_stats.h" | ||
43 | 36 | ||
44 | /* Globals */ | 37 | /* Globals */ |
45 | static const size_t SUCCESS_HTTP_HEADER_LENGTH = 80; | ||
46 | static const size_t SUCCESS_HTTP_HEADER_LENGHT_CONTENT_ENCODING = 32; | ||
47 | static const size_t SUCCESS_HTTP_SIZE_OFF = 17; | ||
48 | static uint32_t g_adminip_addresses[OT_ADMINIP_MAX]; | ||
49 | static unsigned int g_adminip_count = 0; | ||
50 | static time_t ot_last_clean_time; | ||
51 | time_t ot_start_time; | ||
52 | time_t g_now; | 38 | time_t g_now; |
53 | 39 | ||
54 | #ifndef WANT_TRACKER_SYNC | ||
55 | #define add_peer_to_torrent(A,B,C) add_peer_to_torrent(A,B) | ||
56 | #endif | ||
57 | |||
58 | #ifndef NO_FULLSCRAPE_LOGGING | ||
59 | #define LOG_TO_STDERR( ... ) fprintf( stderr, __VA_ARGS__ ) | ||
60 | #else | ||
61 | #define LOG_TO_STDERR( ... ) | ||
62 | #endif | ||
63 | |||
64 | /* To always have space for error messages ;) */ | 40 | /* To always have space for error messages ;) */ |
65 | static char static_inbuf[8192]; | 41 | static char static_inbuf[8192]; |
66 | static char static_outbuf[8192]; | ||
67 | |||
68 | #define OT_MAXMULTISCRAPE_COUNT 64 | ||
69 | static ot_hash multiscrape_buf[OT_MAXMULTISCRAPE_COUNT]; | ||
70 | |||
71 | static char *FLAG_TCP = "TCP"; | ||
72 | static char *FLAG_UDP = "UDP"; | ||
73 | static size_t ot_sockets_count = 0; | ||
74 | |||
75 | #ifdef _DEBUG_HTTPERROR | ||
76 | static char debug_request[8192]; | ||
77 | #endif | ||
78 | 42 | ||
79 | typedef enum { | 43 | static char *FLAG_TCP = "T"; |
80 | STRUCT_HTTP_FLAG_ARRAY_USED = 1, | 44 | static char *FLAG_UDP = "U"; |
81 | STRUCT_HTTP_FLAG_IOB_USED = 2, | ||
82 | STRUCT_HTTP_FLAG_WAITINGFORTASK = 4, | ||
83 | STRUCT_HTTP_FLAG_GZIP = 8, | ||
84 | STRUCT_HTTP_FLAG_BZIP2 = 16 | ||
85 | } STRUCT_HTTP_FLAG; | ||
86 | |||
87 | struct http_data { | ||
88 | union { | ||
89 | array request; | ||
90 | io_batch batch; | ||
91 | }; | ||
92 | unsigned char ip[4]; | ||
93 | STRUCT_HTTP_FLAG flag; | ||
94 | }; | ||
95 | #define NOTBLESSED( h ) (!bsearch( &h->ip, g_adminip_addresses, g_adminip_count, 4, ot_ip_compare )) | ||
96 | static int ot_ip_compare( const void *a, const void *b ) { return memcmp( a,b,4 ); } | ||
97 | |||
98 | /* Prototypes */ | ||
99 | |||
100 | int main( int argc, char **argv ); | ||
101 | |||
102 | static void httperror( const int64 s, const char *title, const char *message ); | ||
103 | static void httpresponse( const int64 s, char *data, size_t l ); | ||
104 | |||
105 | static void sendiovecdata( const int64 s, int iovec_entries, struct iovec *iovector ); | ||
106 | static void senddata( const int64 s, char *buffer, const size_t size ); | ||
107 | |||
108 | static void server_mainloop( ); | ||
109 | static void handle_timeouted( void ); | ||
110 | static void handle_accept( const int64 serversocket ); | ||
111 | static void handle_read( const int64 clientsocket ); | ||
112 | static void handle_write( const int64 clientsocket ); | ||
113 | |||
114 | static void ot_try_bind( char ip[4], uint16 port, int is_tcp ); | ||
115 | |||
116 | static void usage( char *name ); | ||
117 | static void help( char *name ); | ||
118 | |||
119 | static void carp( const char *routine ); | ||
120 | static void panic( const char *routine ); | ||
121 | static void signal_handler( int s ); | ||
122 | |||
123 | #define HTTPERROR_400 return httperror( s, "400 Invalid Request", "This server only understands GET." ) | ||
124 | #define HTTPERROR_400_PARAM return httperror( s, "400 Invalid Request", "Invalid parameter" ) | ||
125 | #define HTTPERROR_400_COMPACT return httperror( s, "400 Invalid Request", "This server only delivers compact results." ) | ||
126 | #define HTTPERROR_403_IP return httperror( s, "403 Access Denied", "Your ip address is not allowed to administrate this server." ) | ||
127 | #define HTTPERROR_404 return httperror( s, "404 Not Found", "No such file or directory." ) | ||
128 | #define HTTPERROR_500 return httperror( s, "500 Internal Server Error", "A server error has occured. Please retry later." ) | ||
129 | |||
130 | /* End of prototypes */ | ||
131 | |||
132 | static void carp( const char *routine ) { | ||
133 | buffer_puts( buffer_2, routine ); | ||
134 | buffer_puts( buffer_2, ": " ); | ||
135 | buffer_puterror( buffer_2 ); | ||
136 | buffer_putnlflush( buffer_2 ); | ||
137 | } | ||
138 | 45 | ||
139 | static void panic( const char *routine ) { | 46 | static void panic( const char *routine ) { |
140 | carp( routine ); | 47 | fprintf( stderr, "%s: %s\n", routine, strerror(errno) ); |
141 | exit( 111 ); | 48 | exit( 111 ); |
142 | } | 49 | } |
143 | 50 | ||
144 | static void httperror( const int64 s, const char *title, const char *message ) { | ||
145 | size_t reply_size = sprintf( static_outbuf, "HTTP/1.0 %s\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n", | ||
146 | title, strlen(message)+strlen(title)+16-4,title+4); | ||
147 | #ifdef _DEBUG_HTTPERROR | ||
148 | fprintf( stderr, "DEBUG: invalid request was: %s\n", debug_request ); | ||
149 | #endif | ||
150 | senddata(s,static_outbuf,reply_size); | ||
151 | } | ||
152 | |||
153 | static void sendiovecdata( const int64 s, int iovec_entries, struct iovec *iovector ) { | ||
154 | struct http_data *h = io_getcookie( s ); | ||
155 | char *header; | ||
156 | int i; | ||
157 | size_t header_size, size = iovec_length( &iovec_entries, &iovector ); | ||
158 | tai6464 t; | ||
159 | |||
160 | /* No cookie? Bad socket. Leave. */ | ||
161 | if( !h ) { | ||
162 | iovec_free( &iovec_entries, &iovector ); | ||
163 | HTTPERROR_500; | ||
164 | } | ||
165 | |||
166 | /* If this socket collected request in a buffer, | ||
167 | free it now */ | ||
168 | if( h->flag & STRUCT_HTTP_FLAG_ARRAY_USED ) { | ||
169 | h->flag &= ~STRUCT_HTTP_FLAG_ARRAY_USED; | ||
170 | array_reset( &h->request ); | ||
171 | } | ||
172 | |||
173 | /* If we came here, wait for the answer is over */ | ||
174 | h->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
175 | |||
176 | /* Our answers never are 0 vectors. Return an error. */ | ||
177 | if( !iovec_entries ) { | ||
178 | HTTPERROR_500; | ||
179 | } | ||
180 | |||
181 | /* Prepare space for http header */ | ||
182 | header = malloc( SUCCESS_HTTP_HEADER_LENGTH + SUCCESS_HTTP_HEADER_LENGHT_CONTENT_ENCODING ); | ||
183 | if( !header ) { | ||
184 | iovec_free( &iovec_entries, &iovector ); | ||
185 | HTTPERROR_500; | ||
186 | } | ||
187 | |||
188 | if( h->flag & STRUCT_HTTP_FLAG_GZIP ) | ||
189 | header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\nContent-Length: %zd\r\n\r\n", size ); | ||
190 | else if( h->flag & STRUCT_HTTP_FLAG_BZIP2 ) | ||
191 | header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: bzip2\r\nContent-Length: %zd\r\n\r\n", size ); | ||
192 | else | ||
193 | header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r\n", size ); | ||
194 | |||
195 | iob_reset( &h->batch ); | ||
196 | iob_addbuf_free( &h->batch, header, header_size ); | ||
197 | |||
198 | /* Will move to ot_iovec.c */ | ||
199 | for( i=0; i<iovec_entries; ++i ) | ||
200 | iob_addbuf_munmap( &h->batch, iovector[i].iov_base, iovector[i].iov_len ); | ||
201 | free( iovector ); | ||
202 | |||
203 | h->flag |= STRUCT_HTTP_FLAG_IOB_USED; | ||
204 | |||
205 | /* writeable sockets timeout after twice the pool timeout | ||
206 | which defaults to 5 minutes (e.g. after 10 minutes) */ | ||
207 | taia_now( &t ); taia_addsec( &t, &t, OT_CLIENT_TIMEOUT_SEND ); | ||
208 | io_timeout( s, t ); | ||
209 | io_dontwantread( s ); | ||
210 | io_wantwrite( s ); | ||
211 | } | ||
212 | |||
213 | static void senddata( const int64 s, char *buffer, size_t size ) { | ||
214 | struct http_data *h = io_getcookie( s ); | ||
215 | ssize_t written_size; | ||
216 | |||
217 | /* whoever sends data is not interested in its input-array */ | ||
218 | if( h && ( h->flag & STRUCT_HTTP_FLAG_ARRAY_USED ) ) { | ||
219 | h->flag &= ~STRUCT_HTTP_FLAG_ARRAY_USED; | ||
220 | array_reset( &h->request ); | ||
221 | } | ||
222 | |||
223 | written_size = write( s, buffer, size ); | ||
224 | if( ( written_size < 0 ) || ( (size_t)written_size == size ) ) { | ||
225 | free( h ); io_close( s ); | ||
226 | } else { | ||
227 | char * outbuf; | ||
228 | tai6464 t; | ||
229 | |||
230 | if( !h ) return; | ||
231 | if( !( outbuf = malloc( size - written_size ) ) ) { | ||
232 | free(h); io_close( s ); | ||
233 | return; | ||
234 | } | ||
235 | |||
236 | iob_reset( &h->batch ); | ||
237 | memmove( outbuf, buffer + written_size, size - written_size ); | ||
238 | iob_addbuf_free( &h->batch, outbuf, size - written_size ); | ||
239 | h->flag |= STRUCT_HTTP_FLAG_IOB_USED; | ||
240 | |||
241 | /* writeable short data sockets just have a tcp timeout */ | ||
242 | taia_uint( &t, 0 ); io_timeout( s, t ); | ||
243 | io_dontwantread( s ); | ||
244 | io_wantwrite( s ); | ||
245 | } | ||
246 | } | ||
247 | |||
248 | static void httpresponse( const int64 s, char *data, size_t l ) { | ||
249 | struct http_data* h = io_getcookie( s ); | ||
250 | char *c, *d=data; | ||
251 | ot_peer peer; | ||
252 | ot_torrent *torrent; | ||
253 | ot_hash *hash = NULL; | ||
254 | int numwant, tmp, scanon, mode; | ||
255 | ot_tasktype format = TASK_FULLSCRAPE; | ||
256 | unsigned short port = htons(6881); | ||
257 | ssize_t len; | ||
258 | size_t reply_size = 0, reply_off; | ||
259 | tai6464 t; | ||
260 | |||
261 | /* Touch l and d in case it is unused */ | ||
262 | l = l; d = d; | ||
263 | |||
264 | #ifdef _DEBUG_HTTPERROR | ||
265 | if( l >= sizeof( debug_request ) ) | ||
266 | l = sizeof( debug_request) - 1; | ||
267 | memcpy( debug_request, data, l ); | ||
268 | debug_request[ l ] = 0; | ||
269 | #endif | ||
270 | |||
271 | /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */ | ||
272 | if( byte_diff( data, 5, "GET /") ) HTTPERROR_400; | ||
273 | |||
274 | /* Query string MUST terminate with SP -- we know that theres at least a '\n' where this search terminates */ | ||
275 | for( c = data + 5; *c!=' ' && *c != '\t' && *c != '\n' && *c != '\r'; ++c ) ; | ||
276 | if( *c != ' ' ) HTTPERROR_400; | ||
277 | |||
278 | /* Skip leading '/' */ | ||
279 | for( c = data+4; *c == '/'; ++c); | ||
280 | |||
281 | switch( scan_urlencoded_query( &c, data = c, SCAN_PATH ) ) { | ||
282 | #ifdef WANT_TRACKER_SYNC | ||
283 | /****************************** | ||
284 | * S Y N C * | ||
285 | ******************************/ | ||
286 | case 4: /* sync ? */ | ||
287 | if( *data == 'a' ) goto ANNOUNCE_WORKAROUND; | ||
288 | if( !byte_diff( data, 2, "sc" ) ) goto SCRAPE_WORKAROUND; | ||
289 | if( byte_diff( data, 4, "sync") ) HTTPERROR_404; | ||
290 | if( NOTBLESSED( h ) ) HTTPERROR_403_IP; | ||
291 | |||
292 | LOG_TO_STDERR( "sync: %d.%d.%d.%d\n", h->ip[0], h->ip[1], h->ip[2], h->ip[3] ); | ||
293 | |||
294 | mode = SYNC_OUT; | ||
295 | scanon = 1; | ||
296 | |||
297 | while( scanon ) { | ||
298 | switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { | ||
299 | case -2: scanon = 0; break; /* TERMINATOR */ | ||
300 | case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
301 | default: scan_urlencoded_skipvalue( &c ); break; | ||
302 | case 9: | ||
303 | if(byte_diff(data,9,"changeset")) { | ||
304 | scan_urlencoded_skipvalue( &c ); | ||
305 | continue; | ||
306 | } | ||
307 | /* ignore this, when we dont at least see "d4:syncdee" */ | ||
308 | if( ( len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) < 10 ) HTTPERROR_400_PARAM; | ||
309 | if( add_changeset_to_tracker( (ot_byte*)data, len ) ) HTTPERROR_400_PARAM; | ||
310 | mode = SYNC_IN; | ||
311 | break; | ||
312 | } | ||
313 | } | ||
314 | |||
315 | if( mode == SYNC_OUT ) { | ||
316 | /* Pass this task to the worker thread */ | ||
317 | h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
318 | sync_deliver( s ); | ||
319 | io_dontwantread( s ); | ||
320 | return; | ||
321 | } | ||
322 | |||
323 | /* Simple but proof for now */ | ||
324 | memmove( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, "OK", 2); | ||
325 | reply_size = 2; | ||
326 | |||
327 | break; | ||
328 | #endif | ||
329 | /****************************** | ||
330 | * S T A T S * | ||
331 | ******************************/ | ||
332 | case 5: /* stats ? */ | ||
333 | if( *data == 'a' ) goto ANNOUNCE_WORKAROUND; | ||
334 | if( !byte_diff( data, 2, "sc" ) ) goto SCRAPE_WORKAROUND; | ||
335 | if( byte_diff(data,5,"stats")) HTTPERROR_404; | ||
336 | scanon = 1; | ||
337 | mode = TASK_STATS_PEERS; | ||
338 | |||
339 | while( scanon ) { | ||
340 | switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { | ||
341 | case -2: scanon = 0; break; /* TERMINATOR */ | ||
342 | case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
343 | default: scan_urlencoded_skipvalue( &c ); break; | ||
344 | case 4: | ||
345 | if( byte_diff(data,4,"mode")) { | ||
346 | scan_urlencoded_skipvalue( &c ); | ||
347 | continue; | ||
348 | } | ||
349 | if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 4 ) HTTPERROR_400_PARAM; | ||
350 | if( !byte_diff(data,4,"peer")) | ||
351 | mode = TASK_STATS_PEERS; | ||
352 | else if( !byte_diff(data,4,"conn")) | ||
353 | mode = TASK_STATS_CONNS; | ||
354 | else if( !byte_diff(data,4,"scrp")) | ||
355 | mode = TASK_STATS_SCRAPE; | ||
356 | else if( !byte_diff(data,4,"top5")) | ||
357 | mode = TASK_STATS_TOP5; | ||
358 | else if( !byte_diff(data,4,"fscr")) | ||
359 | mode = TASK_STATS_FULLSCRAPE; | ||
360 | else if( !byte_diff(data,4,"tcp4")) | ||
361 | mode = TASK_STATS_TCP; | ||
362 | else if( !byte_diff(data,4,"udp4")) | ||
363 | mode = TASK_STATS_UDP; | ||
364 | else if( !byte_diff(data,4,"s24s")) | ||
365 | mode = TASK_STATS_SLASH24S; | ||
366 | else if( !byte_diff(data,4,"tpbs")) | ||
367 | mode = TASK_STATS_TPB; | ||
368 | else | ||
369 | HTTPERROR_400_PARAM; | ||
370 | break; | ||
371 | case 6: | ||
372 | if( byte_diff(data,6,"format")) { | ||
373 | scan_urlencoded_skipvalue( &c ); | ||
374 | continue; | ||
375 | } | ||
376 | if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 3 ) HTTPERROR_400_PARAM; | ||
377 | if( !byte_diff(data,3,"bin")) | ||
378 | format = TASK_FULLSCRAPE_TPB_BINARY; | ||
379 | else if( !byte_diff(data,3,"ben")) | ||
380 | format = TASK_FULLSCRAPE; | ||
381 | else if( !byte_diff(data,3,"url")) | ||
382 | format = TASK_FULLSCRAPE_TPB_URLENCODED; | ||
383 | else if( !byte_diff(data,3,"txt")) | ||
384 | format = TASK_FULLSCRAPE_TPB_ASCII; | ||
385 | else | ||
386 | HTTPERROR_400_PARAM; | ||
387 | break; | ||
388 | } | ||
389 | } | ||
390 | |||
391 | if( mode == TASK_STATS_TPB ) { | ||
392 | #ifdef WANT_COMPRESSION_GZIP | ||
393 | d[l-1] = 0; | ||
394 | if( strstr( d, "gzip" ) ) { | ||
395 | h->flag |= STRUCT_HTTP_FLAG_GZIP; | ||
396 | format |= TASK_FLAG_GZIP; | ||
397 | } | ||
398 | #endif | ||
399 | /* Pass this task to the worker thread */ | ||
400 | h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
401 | |||
402 | /* Clients waiting for us should not easily timeout */ | ||
403 | taia_uint( &t, 0 ); io_timeout( s, t ); | ||
404 | fullscrape_deliver( s, format ); | ||
405 | io_dontwantread( s ); | ||
406 | return; | ||
407 | } | ||
408 | |||
409 | // default format for now | ||
410 | if( !( reply_size = return_stats_for_tracker( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, mode, 0 ) ) ) HTTPERROR_500; | ||
411 | break; | ||
412 | |||
413 | /****************************** | ||
414 | * S C R A P E * | ||
415 | ******************************/ | ||
416 | case 6: /* scrape ? */ | ||
417 | if( *data == 'a' ) goto ANNOUNCE_WORKAROUND; | ||
418 | if( byte_diff( data, 6, "scrape") ) HTTPERROR_404; | ||
419 | |||
420 | /* Full scrape... you might want to limit that */ | ||
421 | if( !byte_diff( data, 12, "scrape HTTP/" ) ) { | ||
422 | format = 0; | ||
423 | #ifdef WANT_COMPRESSION_GZIP | ||
424 | d[l-1] = 0; | ||
425 | if( strstr( d, "gzip" ) ) { | ||
426 | h->flag |= STRUCT_HTTP_FLAG_GZIP; | ||
427 | format = TASK_FLAG_GZIP; | ||
428 | LOG_TO_STDERR( "[%08d] scrp: %d.%d.%d.%d - FULL SCRAPE GZIP\n", (unsigned int)(g_now - ot_start_time), h->ip[0], h->ip[1], h->ip[2], h->ip[3] ); | ||
429 | } else | ||
430 | #endif | ||
431 | LOG_TO_STDERR( "[%08d] scrp: %d.%d.%d.%d - FULL SCRAPE\n", (unsigned int)(g_now - ot_start_time), h->ip[0], h->ip[1], h->ip[2], h->ip[3] ); | ||
432 | |||
433 | #ifdef _DEBUG_HTTPERROR | ||
434 | write( 2, debug_request, l ); | ||
435 | #endif | ||
436 | |||
437 | /* Pass this task to the worker thread */ | ||
438 | h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
439 | /* Clients waiting for us should not easily timeout */ | ||
440 | taia_uint( &t, 0 ); io_timeout( s, t ); | ||
441 | fullscrape_deliver( s, TASK_FULLSCRAPE | format ); | ||
442 | io_dontwantread( s ); | ||
443 | return; | ||
444 | } | ||
445 | |||
446 | SCRAPE_WORKAROUND: | ||
447 | |||
448 | /* This is to hack around stupid clients that send "scrape ?info_hash" */ | ||
449 | if( c[-1] != '?' ) { | ||
450 | while( ( *c != '?' ) && ( *c != '\n' ) ) ++c; | ||
451 | if( *c == '\n' ) HTTPERROR_400_PARAM; | ||
452 | ++c; | ||
453 | } | ||
454 | |||
455 | scanon = 1; | ||
456 | numwant = 0; | ||
457 | while( scanon ) { | ||
458 | switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { | ||
459 | case -2: scanon = 0; break; /* TERMINATOR */ | ||
460 | case -1: | ||
461 | if( numwant ) | ||
462 | goto UTORRENT1600_WORKAROUND; | ||
463 | HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
464 | default: scan_urlencoded_skipvalue( &c ); break; | ||
465 | case 9: | ||
466 | if(byte_diff(data,9,"info_hash")) { | ||
467 | scan_urlencoded_skipvalue( &c ); | ||
468 | continue; | ||
469 | } | ||
470 | /* ignore this, when we have less than 20 bytes */ | ||
471 | if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != (ssize_t)sizeof(ot_hash) ) { | ||
472 | #ifdef WANT_UTORRENT1600_WORKAROUND | ||
473 | if( data[20] != '?' ) | ||
474 | #endif | ||
475 | HTTPERROR_400_PARAM; | ||
476 | } | ||
477 | if( numwant < OT_MAXMULTISCRAPE_COUNT ) | ||
478 | memmove( multiscrape_buf + numwant++, data, sizeof(ot_hash) ); | ||
479 | break; | ||
480 | } | ||
481 | } | ||
482 | |||
483 | UTORRENT1600_WORKAROUND: | ||
484 | |||
485 | /* No info_hash found? Inform user */ | ||
486 | if( !numwant ) HTTPERROR_400_PARAM; | ||
487 | |||
488 | /* Enough for http header + whole scrape string */ | ||
489 | if( !( reply_size = return_tcp_scrape_for_torrent( multiscrape_buf, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf ) ) ) HTTPERROR_500; | ||
490 | stats_issue_event( EVENT_SCRAPE, 1, reply_size ); | ||
491 | break; | ||
492 | /****************************** | ||
493 | * A N N O U N C E * | ||
494 | ******************************/ | ||
495 | case 8: | ||
496 | if( !byte_diff( data, 2, "sc" ) ) goto SCRAPE_WORKAROUND; | ||
497 | if( *data != 'a' ) HTTPERROR_404; | ||
498 | |||
499 | ANNOUNCE_WORKAROUND: | ||
500 | |||
501 | /* This is to hack around stupid clients that send "announce ?info_hash" */ | ||
502 | if( c[-1] != '?' ) { | ||
503 | while( ( *c != '?' ) && ( *c != '\n' ) ) ++c; | ||
504 | if( *c == '\n' ) HTTPERROR_400_PARAM; | ||
505 | ++c; | ||
506 | } | ||
507 | |||
508 | OT_SETIP( &peer, ((struct http_data*)io_getcookie( s ) )->ip ); | ||
509 | OT_SETPORT( &peer, &port ); | ||
510 | OT_FLAG( &peer ) = 0; | ||
511 | numwant = 50; | ||
512 | scanon = 1; | ||
513 | |||
514 | while( scanon ) { | ||
515 | switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { | ||
516 | case -2: scanon = 0; break; /* TERMINATOR */ | ||
517 | case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
518 | default: scan_urlencoded_skipvalue( &c ); break; | ||
519 | #ifdef WANT_IP_FROM_QUERY_STRING | ||
520 | case 2: | ||
521 | if(!byte_diff(data,2,"ip")) { | ||
522 | unsigned char ip[4]; | ||
523 | len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); | ||
524 | if( ( len <= 0 ) || scan_fixed_ip( data, len, ip ) ) HTTPERROR_400_PARAM; | ||
525 | OT_SETIP( &peer, ip ); | ||
526 | } else | ||
527 | scan_urlencoded_skipvalue( &c ); | ||
528 | break; | ||
529 | #endif | ||
530 | case 4: | ||
531 | if( !byte_diff( data, 4, "port" ) ) { | ||
532 | len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); | ||
533 | if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) || ( tmp > 0xffff ) ) HTTPERROR_400_PARAM; | ||
534 | port = htons( tmp ); OT_SETPORT( &peer, &port ); | ||
535 | } else if( !byte_diff( data, 4, "left" ) ) { | ||
536 | if( ( len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM; | ||
537 | if( scan_fixed_int( data, len, &tmp ) ) tmp = 0; | ||
538 | if( !tmp ) OT_FLAG( &peer ) |= PEER_FLAG_SEEDING; | ||
539 | } else | ||
540 | scan_urlencoded_skipvalue( &c ); | ||
541 | break; | ||
542 | case 5: | ||
543 | if( byte_diff( data, 5, "event" ) ) | ||
544 | scan_urlencoded_skipvalue( &c ); | ||
545 | else switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) { | ||
546 | case -1: | ||
547 | HTTPERROR_400_PARAM; | ||
548 | case 7: | ||
549 | if( !byte_diff( data, 7, "stopped" ) ) OT_FLAG( &peer ) |= PEER_FLAG_STOPPED; | ||
550 | break; | ||
551 | case 9: | ||
552 | if( !byte_diff( data, 9, "completed" ) ) OT_FLAG( &peer ) |= PEER_FLAG_COMPLETED; | ||
553 | default: /* Fall through intended */ | ||
554 | break; | ||
555 | } | ||
556 | break; | ||
557 | case 7: | ||
558 | if(!byte_diff(data,7,"numwant")) { | ||
559 | len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); | ||
560 | if( ( len <= 0 ) || scan_fixed_int( data, len, &numwant ) ) HTTPERROR_400_PARAM; | ||
561 | if( numwant < 0 ) numwant = 50; | ||
562 | if( numwant > 200 ) numwant = 200; | ||
563 | } else if(!byte_diff(data,7,"compact")) { | ||
564 | len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); | ||
565 | if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) ) HTTPERROR_400_PARAM; | ||
566 | if( !tmp ) HTTPERROR_400_COMPACT; | ||
567 | } else | ||
568 | scan_urlencoded_skipvalue( &c ); | ||
569 | break; | ||
570 | case 9: | ||
571 | if(byte_diff(data,9,"info_hash")) { | ||
572 | scan_urlencoded_skipvalue( &c ); | ||
573 | continue; | ||
574 | } | ||
575 | /* ignore this, when we have less than 20 bytes */ | ||
576 | if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM; | ||
577 | hash = (ot_hash*)data; | ||
578 | break; | ||
579 | } | ||
580 | } | ||
581 | |||
582 | /* Scanned whole query string */ | ||
583 | if( !hash ) { | ||
584 | reply_size = sprintf( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, "d14:failure reason81:Your client forgot to send your torrent's info_hash. Please upgrade your client.e" ); | ||
585 | break; | ||
586 | } | ||
587 | if( OT_FLAG( &peer ) & PEER_FLAG_STOPPED ) | ||
588 | reply_size = remove_peer_from_torrent( hash, &peer, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf, 1 ); | ||
589 | else { | ||
590 | torrent = add_peer_to_torrent( hash, &peer, 0 ); | ||
591 | if( !torrent || !( reply_size = return_peers_for_torrent( hash, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf, 1 ) ) ) HTTPERROR_500; | ||
592 | } | ||
593 | stats_issue_event( EVENT_ANNOUNCE, 1, reply_size); | ||
594 | break; | ||
595 | default: | ||
596 | if( ( *data == 'a' ) || ( *data == '?' ) ) goto ANNOUNCE_WORKAROUND; | ||
597 | if( !byte_diff( data, 2, "sc" ) ) goto SCRAPE_WORKAROUND; | ||
598 | HTTPERROR_404; | ||
599 | } | ||
600 | |||
601 | if( !reply_size ) HTTPERROR_500; | ||
602 | |||
603 | /* This one is rather ugly, so I take you step by step through it. | ||
604 | |||
605 | 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to | ||
606 | write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our static buffer, which is enough for the static string | ||
607 | plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate | ||
608 | the space NOT needed to expand in reply_off | ||
609 | */ | ||
610 | reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( static_outbuf, 0, "%zd", reply_size ); | ||
611 | |||
612 | /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete | ||
613 | packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */ | ||
614 | reply_size += 1 + sprintf( static_outbuf + reply_off, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", reply_size ); | ||
615 | |||
616 | /* 3. Finally we join both blocks neatly */ | ||
617 | static_outbuf[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n'; | ||
618 | |||
619 | senddata( s, static_outbuf + reply_off, reply_size ); | ||
620 | } | ||
621 | |||
622 | static void signal_handler( int s ) { | 51 | static void signal_handler( int s ) { |
623 | if( s == SIGINT ) { | 52 | if( s == SIGINT ) { |
624 | signal( SIGINT, SIG_IGN); | 53 | signal( SIGINT, SIG_IGN); |
@@ -674,36 +103,37 @@ static void handle_dead( const int64 socket ) { | |||
674 | io_close( socket ); | 103 | io_close( socket ); |
675 | } | 104 | } |
676 | 105 | ||
677 | static void handle_read( const int64 clientsocket ) { | 106 | static ssize_t handle_read( const int64 clientsocket ) { |
678 | struct http_data* h = io_getcookie( clientsocket ); | 107 | struct http_data* h = io_getcookie( clientsocket ); |
679 | ssize_t l; | 108 | ssize_t l; |
680 | 109 | ||
681 | if( ( l = io_tryread( clientsocket, static_inbuf, sizeof static_inbuf ) ) <= 0 ) | 110 | if( ( l = io_tryread( clientsocket, static_inbuf, sizeof static_inbuf ) ) <= 0 ) { |
682 | return handle_dead( clientsocket ); | 111 | handle_dead( clientsocket ); |
683 | 112 | return 0; | |
684 | #ifdef _DEBUG_HTTPERROR | 113 | } |
685 | memcpy( debug_request, "500!\0", 5 ); | ||
686 | #endif | ||
687 | 114 | ||
688 | /* If we get the whole request in one packet, handle it without copying */ | 115 | /* If we get the whole request in one packet, handle it without copying */ |
689 | if( !array_start( &h->request ) ) { | 116 | if( !array_start( &h->request ) ) { |
690 | if( memchr( static_inbuf, '\n', l ) ) | 117 | if( memchr( static_inbuf, '\n', l ) ) |
691 | return httpresponse( clientsocket, static_inbuf, l ); | 118 | return http_handle_request( clientsocket, static_inbuf, l ); |
692 | h->flag |= STRUCT_HTTP_FLAG_ARRAY_USED; | 119 | h->flag |= STRUCT_HTTP_FLAG_ARRAY_USED; |
693 | return array_catb( &h->request, static_inbuf, l ); | 120 | array_catb( &h->request, static_inbuf, l ); |
121 | return 0; | ||
694 | } | 122 | } |
695 | 123 | ||
696 | h->flag |= STRUCT_HTTP_FLAG_ARRAY_USED; | 124 | h->flag |= STRUCT_HTTP_FLAG_ARRAY_USED; |
697 | array_catb( &h->request, static_inbuf, l ); | 125 | array_catb( &h->request, static_inbuf, l ); |
698 | 126 | ||
699 | if( array_failed( &h->request ) ) | 127 | if( array_failed( &h->request ) ) |
700 | return httperror( clientsocket, "500 Server Error", "Request too long."); | 128 | return http_issue_error( clientsocket, "500 Server Error", "Request too long."); |
701 | 129 | ||
702 | if( ( array_bytes( &h->request ) > 8192 ) && NOTBLESSED( h ) ) | 130 | if( ( array_bytes( &h->request ) > 8192 ) && !accesslist_isblessed( (char*)&h->ip, OT_PERMISSION_MAY_SYNC ) ) |
703 | return httperror( clientsocket, "500 request too long", "You sent too much headers"); | 131 | return http_issue_error( clientsocket, "500 request too long", "You sent too much headers"); |
704 | 132 | ||
705 | if( memchr( array_start( &h->request ), '\n', array_bytes( &h->request ) ) ) | 133 | if( memchr( array_start( &h->request ), '\n', array_bytes( &h->request ) ) ) |
706 | return httpresponse( clientsocket, array_start( &h->request ), array_bytes( &h->request ) ); | 134 | return http_handle_request( clientsocket, array_start( &h->request ), array_bytes( &h->request ) ); |
135 | |||
136 | return 0; | ||
707 | } | 137 | } |
708 | 138 | ||
709 | static void handle_write( const int64 clientsocket ) { | 139 | static void handle_write( const int64 clientsocket ) { |
@@ -732,7 +162,7 @@ static void handle_accept( const int64 serversocket ) { | |||
732 | io_setcookie( i, h ); | 162 | io_setcookie( i, h ); |
733 | io_wantread( i ); | 163 | io_wantread( i ); |
734 | 164 | ||
735 | byte_zero( h, sizeof( struct http_data ) ); | 165 | memset( h, 0, sizeof( struct http_data ) ); |
736 | memmove( h->ip, ip, sizeof( ip ) ); | 166 | memmove( h->ip, ip, sizeof( ip ) ); |
737 | 167 | ||
738 | stats_issue_event( EVENT_ACCEPT, 1, 0); | 168 | stats_issue_event( EVENT_ACCEPT, 1, 0); |
@@ -748,16 +178,11 @@ static void handle_accept( const int64 serversocket ) { | |||
748 | io_eagain( serversocket ); | 178 | io_eagain( serversocket ); |
749 | } | 179 | } |
750 | 180 | ||
751 | static void handle_timeouted( void ) { | ||
752 | int64 i; | ||
753 | while( ( i = io_timeouted() ) != -1 ) | ||
754 | handle_dead( i ); | ||
755 | } | ||
756 | |||
757 | static void server_mainloop( ) { | 181 | static void server_mainloop( ) { |
758 | time_t next_timeout_check = g_now + OT_CLIENT_TIMEOUT_CHECKINTERVAL; | 182 | static time_t ot_last_clean_time; |
759 | struct iovec *iovector; | 183 | time_t next_timeout_check = g_now + OT_CLIENT_TIMEOUT_CHECKINTERVAL; |
760 | int iovec_entries; | 184 | struct iovec *iovector; |
185 | int iovec_entries; | ||
761 | 186 | ||
762 | for( ; ; ) { | 187 | for( ; ; ) { |
763 | int64 i; | 188 | int64 i; |
@@ -775,13 +200,14 @@ static void server_mainloop( ) { | |||
775 | } | 200 | } |
776 | 201 | ||
777 | while( ( i = mutex_workqueue_popresult( &iovec_entries, &iovector ) ) != -1 ) | 202 | while( ( i = mutex_workqueue_popresult( &iovec_entries, &iovector ) ) != -1 ) |
778 | sendiovecdata( i, iovec_entries, iovector ); | 203 | http_sendiovecdata( i, iovec_entries, iovector ); |
779 | 204 | ||
780 | while( ( i = io_canwrite( ) ) != -1 ) | 205 | while( ( i = io_canwrite( ) ) != -1 ) |
781 | handle_write( i ); | 206 | handle_write( i ); |
782 | 207 | ||
783 | if( g_now > next_timeout_check ) { | 208 | if( g_now > next_timeout_check ) { |
784 | handle_timeouted( ); | 209 | while( ( i = io_timeouted() ) != -1 ) |
210 | handle_dead( i ); | ||
785 | next_timeout_check = g_now + OT_CLIENT_TIMEOUT_CHECKINTERVAL; | 211 | next_timeout_check = g_now + OT_CLIENT_TIMEOUT_CHECKINTERVAL; |
786 | } | 212 | } |
787 | 213 | ||
@@ -811,15 +237,13 @@ static void ot_try_bind( char ip[4], uint16 port, int is_tcp ) { | |||
811 | io_setcookie( s, is_tcp ? FLAG_TCP : FLAG_UDP ); | 237 | io_setcookie( s, is_tcp ? FLAG_TCP : FLAG_UDP ); |
812 | 238 | ||
813 | io_wantread( s ); | 239 | io_wantread( s ); |
814 | |||
815 | ++ot_sockets_count; | ||
816 | } | 240 | } |
817 | 241 | ||
818 | int main( int argc, char **argv ) { | 242 | int main( int argc, char **argv ) { |
819 | struct passwd *pws = NULL; | 243 | struct passwd *pws = NULL; |
820 | char serverip[4] = {0,0,0,0}; | 244 | char serverip[4] = {0,0,0,0}, tmpip[4]; |
821 | char *serverdir = "."; | 245 | char *serverdir = "."; |
822 | int scanon = 1; | 246 | int bound = 0, scanon = 1; |
823 | #ifdef WANT_ACCESS_CONTROL | 247 | #ifdef WANT_ACCESS_CONTROL |
824 | char *accesslist_filename = NULL; | 248 | char *accesslist_filename = NULL; |
825 | #endif | 249 | #endif |
@@ -839,12 +263,12 @@ int main( int argc, char **argv ) { | |||
839 | #elif defined( WANT_CLOSED_TRACKER ) | 263 | #elif defined( WANT_CLOSED_TRACKER ) |
840 | case 'w': accesslist_filename = optarg; break; | 264 | case 'w': accesslist_filename = optarg; break; |
841 | #endif | 265 | #endif |
842 | case 'p': ot_try_bind( serverip, (uint16)atol( optarg ), 1 ); break; | 266 | case 'p': ot_try_bind( serverip, (uint16)atol( optarg ), 1 ); bound++; break; |
843 | case 'P': ot_try_bind( serverip, (uint16)atol( optarg ), 0 ); break; | 267 | case 'P': ot_try_bind( serverip, (uint16)atol( optarg ), 0 ); bound++; break; |
844 | case 'd': serverdir = optarg; break; | 268 | case 'd': serverdir = optarg; break; |
845 | case 'A': | 269 | case 'A': |
846 | if( g_adminip_count < OT_ADMINIP_MAX ) | 270 | scan_ip4( optarg, tmpip ); |
847 | scan_ip4( optarg, (char*)(g_adminip_addresses + g_adminip_count++) ); | 271 | accesslist_blessip( tmpip, 0xffff ); /* Allow everything for now */ |
848 | break; | 272 | break; |
849 | case 'h': help( argv[0] ); exit( 0 ); | 273 | case 'h': help( argv[0] ); exit( 0 ); |
850 | default: | 274 | default: |
@@ -852,11 +276,8 @@ int main( int argc, char **argv ) { | |||
852 | } | 276 | } |
853 | } | 277 | } |
854 | 278 | ||
855 | /* Sort our admin ips for quick lookup */ | ||
856 | qsort( g_adminip_addresses, g_adminip_count, 4, ot_ip_compare ); | ||
857 | |||
858 | /* Bind to our default tcp/udp ports */ | 279 | /* Bind to our default tcp/udp ports */ |
859 | if( !ot_sockets_count ) { | 280 | if( !bound) { |
860 | ot_try_bind( serverip, 6969, 1 ); | 281 | ot_try_bind( serverip, 6969, 1 ); |
861 | ot_try_bind( serverip, 6969, 0 ); | 282 | ot_try_bind( serverip, 6969, 0 ); |
862 | } | 283 | } |
@@ -881,8 +302,7 @@ int main( int argc, char **argv ) { | |||
881 | if( trackerlogic_init( serverdir ) == -1 ) | 302 | if( trackerlogic_init( serverdir ) == -1 ) |
882 | panic( "Logic not started" ); | 303 | panic( "Logic not started" ); |
883 | 304 | ||
884 | g_now = ot_start_time = time( NULL ); | 305 | g_now = time( NULL ); |
885 | ot_last_clean_time = NOW; | ||
886 | alarm(5); | 306 | alarm(5); |
887 | 307 | ||
888 | server_mainloop( ); | 308 | server_mainloop( ); |