1 /* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
24 #include "trackerlogic.h"
28 #include "scan_urlencoded_query.h"
29 #include "ot_fullscrape.h"
31 #include "ot_accesslist.h"
33 #define OT_MAXMULTISCRAPE_COUNT 64
34 #define OT_BATCH_LIMIT (1024*1024*16)
35 extern char *g_redirecturl
;
38 ssize_t g_stats_path_len
;
41 SUCCESS_HTTP_HEADER_LENGTH
= 80,
42 SUCCESS_HTTP_HEADER_LENGTH_CONTENT_ENCODING
= 32,
43 SUCCESS_HTTP_SIZE_OFF
= 17 };
45 static void http_senddata( const int64 sock
, struct ot_workstruct
*ws
) {
46 struct http_data
*cookie
= io_getcookie( sock
);
49 if( !cookie
) { io_close(sock
); return; }
51 /* whoever sends data is not interested in its input-array */
52 if( ws
->keep_alive
&& ws
->header_size
!= ws
->request_size
) {
53 size_t rest
= ws
->request_size
- ws
->header_size
;
54 if( array_start(&cookie
->request
) ) {
55 memmove( array_start(&cookie
->request
), ws
->request
+ ws
->header_size
, rest
);
56 array_truncate( &cookie
->request
, 1, rest
);
58 array_catb(&cookie
->request
, ws
->request
+ ws
->header_size
, rest
);
60 array_reset( &cookie
->request
);
62 written_size
= write( sock
, ws
->reply
, ws
->reply_size
);
63 if( ( written_size
< 0 ) || ( ( written_size
== ws
->reply_size
) && !ws
->keep_alive
) ) {
64 array_reset( &cookie
->request
);
65 free( cookie
); io_close( sock
); return;
68 if( written_size
< ws
->reply_size
) {
72 if( !( outbuf
= malloc( ws
->reply_size
- written_size
) ) ) {
73 array_reset( &cookie
->request
);
74 free(cookie
); io_close( sock
);
78 memcpy( outbuf
, ws
->reply
+ written_size
, ws
->reply_size
- written_size
);
79 if ( !cookie
->batch
) {
80 cookie
->batch
= malloc( sizeof(io_batch
) );
81 memset( cookie
->batch
, 0, sizeof(io_batch
) );
85 iob_addbuf_free( cookie
->batch
, outbuf
, ws
->reply_size
- written_size
);
87 /* writeable short data sockets just have a tcp timeout */
88 if( !ws
->keep_alive
) {
89 taia_uint( &t
, 0 ); io_timeout( sock
, t
);
90 io_dontwantread( sock
);
96 #define HTTPERROR_302 return http_issue_error( sock, ws, CODE_HTTPERROR_302 )
97 #define HTTPERROR_400 return http_issue_error( sock, ws, CODE_HTTPERROR_400 )
98 #define HTTPERROR_400_PARAM return http_issue_error( sock, ws, CODE_HTTPERROR_400_PARAM )
99 #define HTTPERROR_400_COMPACT return http_issue_error( sock, ws, CODE_HTTPERROR_400_COMPACT )
100 #define HTTPERROR_400_DOUBLEHASH return http_issue_error( sock, ws, CODE_HTTPERROR_400_PARAM )
101 #define HTTPERROR_402_NOTMODEST return http_issue_error( sock, ws, CODE_HTTPERROR_402_NOTMODEST )
102 #define HTTPERROR_403_IP return http_issue_error( sock, ws, CODE_HTTPERROR_403_IP )
103 #define HTTPERROR_404 return http_issue_error( sock, ws, CODE_HTTPERROR_404 )
104 #define HTTPERROR_500 return http_issue_error( sock, ws, CODE_HTTPERROR_500 )
105 ssize_t
http_issue_error( const int64 sock
, struct ot_workstruct
*ws
, int code
) {
106 char *error_code
[] = { "302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required",
107 "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error" };
108 char *title
= error_code
[code
];
110 ws
->reply
= ws
->outbuf
;
111 if( code
== CODE_HTTPERROR_302
)
112 ws
->reply_size
= snprintf( ws
->reply
, G_OUTBUF_SIZE
, "HTTP/1.0 302 Found\r\nContent-Length: 0\r\nLocation: %s\r\n\r\n", g_redirecturl
);
114 ws
->reply_size
= snprintf( ws
->reply
, G_OUTBUF_SIZE
, "HTTP/1.0 %s\r\nContent-Type: text/html\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n", title
, strlen(title
)+16-4,title
+4);
116 #ifdef _DEBUG_HTTPERROR
117 fprintf( stderr
, "DEBUG: invalid request was: %s\n", ws
->debugbuf
);
119 stats_issue_event( EVENT_FAILED
, FLAG_TCP
, code
);
120 http_senddata( sock
, ws
);
121 return ws
->reply_size
= -2;
124 ssize_t
http_sendiovecdata( const int64 sock
, struct ot_workstruct
*ws
, int iovec_entries
, struct iovec
*iovector
, int is_partial
) {
125 struct http_data
*cookie
= io_getcookie( sock
);
128 const char *encoding
= "";
130 size_t header_size
, size
= iovec_length( &iovec_entries
, (const struct iovec
**)&iovector
);
133 /* No cookie? Bad socket. Leave. */
135 iovec_free( &iovec_entries
, &iovector
);
139 /* If this socket collected request in a buffer, free it now */
140 array_reset( &cookie
->request
);
142 /* If we came here, wait for the answer is over */
143 cookie
->flag
&= ~STRUCT_HTTP_FLAG_WAITINGFORTASK
;
145 fprintf(stderr
, "http_sendiovecdata sending %d iovec entries found cookie->batch == %p\n", iovec_entries
, cookie
->batch
);
147 if( iovec_entries
) {
149 /* Prepare space for http header */
150 header
= malloc( SUCCESS_HTTP_HEADER_LENGTH
+ SUCCESS_HTTP_HEADER_LENGTH_CONTENT_ENCODING
);
152 iovec_free( &iovec_entries
, &iovector
);
156 if( cookie
->flag
& STRUCT_HTTP_FLAG_GZIP
)
157 encoding
= "Content-Encoding: gzip\r\n";
158 else if( cookie
->flag
& STRUCT_HTTP_FLAG_BZIP2
)
159 encoding
= "Content-Encoding: bzip2\r\n";
161 if( !(cookie
->flag
& STRUCT_HTTP_FLAG_CHUNKED
) )
162 header_size
= sprintf( header
, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n%sContent-Length: %zd\r\n\r\n", encoding
, size
);
164 if ( !(cookie
->flag
& STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER
)) {
165 header_size
= sprintf( header
, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n%sTransfer-Encoding: chunked\r\n\r\n%zx\r\n", encoding
, size
);
166 cookie
->flag
|= STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER
;
168 header_size
= sprintf( header
, "%zx\r\n", size
);
171 if (!cookie
->batch
) {
172 cookie
->batch
= malloc( sizeof(io_batch
) );
173 if (!cookie
->batch
) {
175 iovec_free( &iovec_entries
, &iovector
);
178 memset( cookie
->batch
, 0, sizeof(io_batch
) );
181 current
= cookie
->batch
+ cookie
->batches
- 1;
182 iob_addbuf_free( current
, header
, header_size
);
184 /* Split huge iovectors into separate io_batches */
185 for( i
=0; i
<iovec_entries
; ++i
) {
186 /* If the current batch's limit is reached, try to reallocate a new batch to work on */
187 if( current
->bytesleft
> OT_BATCH_LIMIT
) {
188 fprintf(stderr
, "http_sendiovecdata found batch above limit: %zd\n", current
->bytesleft
);
189 io_batch
* new_batch
= realloc( cookie
->batch
, (cookie
->batches
+ 1) * sizeof(io_batch
) );
191 cookie
->batch
= new_batch
;
192 current
= cookie
->batch
+ cookie
->batches
++;
193 memset( current
, 0, sizeof(io_batch
) );
196 fprintf(stderr
, "http_sendiovecdata calling iob_addbuf_free with %zd\n", iovector
[i
].iov_len
);
197 iob_addbuf_free( current
, iovector
[i
].iov_base
, iovector
[i
].iov_len
);
200 if ( cookie
->flag
& STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER
)
201 iob_addbuf(current
, "\r\n", 2);
204 if ((cookie
->flag
& STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER
) && cookie
->batch
&& !is_partial
) {
205 fprintf(stderr
, "http_sendiovecdata adds a terminating 0 size buffer to batch\n");
206 current
= cookie
->batch
+ cookie
->batches
- 1;
207 iob_addbuf(current
, "0\r\n\r\n", 5);
208 cookie
->flag
&= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER
;
211 /* writeable sockets timeout after 10 minutes */
212 taia_now( &t
); taia_addsec( &t
, &t
, OT_CLIENT_TIMEOUT_SEND
);
213 io_timeout( sock
, t
);
214 io_dontwantread( sock
);
215 fprintf (stderr
, "http_sendiovecdata marks socket %lld as wantwrite\n", sock
);
216 io_wantwrite( sock
);
220 static ssize_t
http_handle_stats( const int64 sock
, struct ot_workstruct
*ws
, char *read_ptr
) {
221 static const ot_keywords keywords_main
[] =
222 { { "mode", 1 }, {"format", 2 }, {"info_hash", 3}, { NULL
, -3 } };
223 static const ot_keywords keywords_mode
[] =
224 { { "peer", TASK_STATS_PEERS
}, { "conn", TASK_STATS_CONNS
}, { "scrp", TASK_STATS_SCRAPE
}, { "udp4", TASK_STATS_UDP
}, { "tcp4", TASK_STATS_TCP
},
225 { "busy", TASK_STATS_BUSY_NETWORKS
}, { "torr", TASK_STATS_TORRENTS
}, { "fscr", TASK_STATS_FULLSCRAPE
},
226 { "s24s", TASK_STATS_SLASH24S
}, { "tpbs", TASK_STATS_TPB
}, { "herr", TASK_STATS_HTTPERRORS
}, { "completed", TASK_STATS_COMPLETED
},
227 { "top100", TASK_STATS_TOP100
}, { "top10", TASK_STATS_TOP10
}, { "renew", TASK_STATS_RENEW
}, { "syncs", TASK_STATS_SYNCS
}, { "version", TASK_STATS_VERSION
},
228 { "everything", TASK_STATS_EVERYTHING
}, { "statedump", TASK_FULLSCRAPE_TRACKERSTATE
}, { "fulllog", TASK_STATS_FULLLOG
},
229 { "woodpeckers", TASK_STATS_WOODPECKERS
},
230 #ifdef WANT_LOG_NUMWANT
231 { "numwants", TASK_STATS_NUMWANTS
},
234 static const ot_keywords keywords_format
[] =
235 { { "bin", TASK_FULLSCRAPE_TPB_BINARY
}, { "ben", TASK_FULLSCRAPE
}, { "url", TASK_FULLSCRAPE_TPB_URLENCODED
},
236 { "txt", TASK_FULLSCRAPE_TPB_ASCII
}, { "txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS
}, { NULL
, -3 } };
238 int mode
= TASK_STATS_PEERS
, scanon
= 1, format
= 0;
240 #ifdef WANT_RESTRICT_STATS
241 struct http_data
*cookie
= io_getcookie( sock
);
243 if( !cookie
|| !accesslist_is_blessed( cookie
->ip
, OT_PERMISSION_MAY_STAT
) )
248 switch( scan_find_keywords( keywords_main
, &read_ptr
, SCAN_SEARCHPATH_PARAM
) ) {
249 case -2: scanon
= 0; break; /* TERMINATOR */
250 case -1: HTTPERROR_400_PARAM
; /* PARSE ERROR */
251 case -3: scan_urlencoded_skipvalue( &read_ptr
); break;
252 case 1: /* matched "mode" */
253 if( ( mode
= scan_find_keywords( keywords_mode
, &read_ptr
, SCAN_SEARCHPATH_VALUE
) ) <= 0 ) HTTPERROR_400_PARAM
;
255 case 2: /* matched "format" */
256 if( ( format
= scan_find_keywords( keywords_format
, &read_ptr
, SCAN_SEARCHPATH_VALUE
) ) <= 0 ) HTTPERROR_400_PARAM
;
258 case 3: HTTPERROR_400_PARAM
; /* If the stats URL was mistakenly added as announce URL, return a 400 */
262 #ifdef WANT_FULLSCRAPE
263 if( mode
== TASK_FULLSCRAPE_TRACKERSTATE
) {
264 format
= mode
; mode
= TASK_STATS_TPB
;
267 if( mode
== TASK_STATS_TPB
) {
268 struct http_data
* cookie
= io_getcookie( sock
);
270 #ifdef WANT_COMPRESSION_GZIP
271 ws
->request
[ws
->request_size
] = 0;
272 #ifdef WANT_COMPRESSION_GZIP_ALWAYS
273 if( strstr( read_ptr
- 1, "gzip" ) ) {
275 cookie
->flag
|= STRUCT_HTTP_FLAG_GZIP
;
276 format
|= TASK_FLAG_GZIP
;
277 #ifdef WANT_COMPRESSION_GZIP_ALWAYS
281 /* Pass this task to the worker thread */
282 cookie
->flag
|= STRUCT_HTTP_FLAG_WAITINGFORTASK
| STRUCT_HTTP_FLAG_CHUNKED
;
284 /* Clients waiting for us should not easily timeout */
285 taia_uint( &t
, 0 ); io_timeout( sock
, t
);
286 fullscrape_deliver( sock
, format
);
287 io_dontwantread( sock
);
288 return ws
->reply_size
= -2;
292 /* default format for now */
293 if( ( mode
& TASK_CLASS_MASK
) == TASK_STATS
) {
295 /* Complex stats also include expensive memory debugging tools */
296 taia_uint( &t
, 0 ); io_timeout( sock
, t
);
297 stats_deliver( sock
, mode
);
298 return ws
->reply_size
= -2;
301 /* Simple stats can be answerred immediately */
302 return ws
->reply_size
= return_stats_for_tracker( ws
->reply
, mode
, 0 );
305 #ifdef WANT_MODEST_FULLSCRAPES
306 static pthread_mutex_t g_modest_fullscrape_mutex
= PTHREAD_MUTEX_INITIALIZER
;
307 static ot_vector g_modest_fullscrape_timeouts
;
308 typedef struct { ot_ip6 ip
; ot_time last_fullscrape
; } ot_scrape_log
;
311 #ifdef WANT_FULLSCRAPE
312 static ssize_t
http_handle_fullscrape( const int64 sock
, struct ot_workstruct
*ws
) {
313 struct http_data
* cookie
= io_getcookie( sock
);
317 #ifdef WANT_MODEST_FULLSCRAPES
319 ot_scrape_log this_peer
, *new_peer
;
321 memcpy( this_peer
.ip
, cookie
->ip
, sizeof(ot_ip6
));
322 this_peer
.last_fullscrape
= g_now_seconds
;
323 pthread_mutex_lock(&g_modest_fullscrape_mutex
);
324 new_peer
= vector_find_or_insert( &g_modest_fullscrape_timeouts
, &this_peer
, sizeof(ot_scrape_log
), sizeof(ot_ip6
), &exactmatch
);
326 pthread_mutex_unlock(&g_modest_fullscrape_mutex
);
329 if( exactmatch
&& ( this_peer
.last_fullscrape
- new_peer
->last_fullscrape
) < OT_MODEST_PEER_TIMEOUT
) {
330 pthread_mutex_unlock(&g_modest_fullscrape_mutex
);
331 HTTPERROR_402_NOTMODEST
;
333 memcpy( new_peer
, &this_peer
, sizeof(ot_scrape_log
));
334 pthread_mutex_unlock(&g_modest_fullscrape_mutex
);
338 #ifdef WANT_COMPRESSION_GZIP
339 ws
->request
[ws
->request_size
-1] = 0;
340 if( strstr( ws
->request
, "gzip" ) ) {
341 cookie
->flag
|= STRUCT_HTTP_FLAG_GZIP
;
342 format
= TASK_FLAG_GZIP
;
343 stats_issue_event( EVENT_FULLSCRAPE_REQUEST_GZIP
, 0, (uintptr_t)cookie
->ip
);
346 stats_issue_event( EVENT_FULLSCRAPE_REQUEST
, 0, (uintptr_t)cookie
->ip
);
348 #ifdef _DEBUG_HTTPERROR
349 fprintf( stderr
, "%s", ws
->debugbuf
);
352 /* Pass this task to the worker thread */
353 cookie
->flag
|= STRUCT_HTTP_FLAG_WAITINGFORTASK
| STRUCT_HTTP_FLAG_CHUNKED
;
354 /* Clients waiting for us should not easily timeout */
355 taia_uint( &t
, 0 ); io_timeout( sock
, t
);
356 fullscrape_deliver( sock
, TASK_FULLSCRAPE
| format
);
357 io_dontwantread( sock
);
358 return ws
->reply_size
= -2;
362 static ssize_t
http_handle_scrape( const int64 sock
, struct ot_workstruct
*ws
, char *read_ptr
) {
363 static const ot_keywords keywords_scrape
[] = { { "info_hash", 1 }, { NULL
, -3 } };
365 ot_hash
* multiscrape_buf
= (ot_hash
*)ws
->request
;
366 int scanon
= 1, numwant
= 0;
368 /* This is to hack around stupid clients that send "scrape ?info_hash" */
369 if( read_ptr
[-1] != '?' ) {
370 while( ( *read_ptr
!= '?' ) && ( *read_ptr
!= '\n' ) ) ++read_ptr
;
371 if( *read_ptr
== '\n' ) HTTPERROR_400_PARAM
;
376 switch( scan_find_keywords( keywords_scrape
, &read_ptr
, SCAN_SEARCHPATH_PARAM
) ) {
377 case -2: scanon
= 0; break; /* TERMINATOR */
378 default: HTTPERROR_400_PARAM
; /* PARSE ERROR */
379 case -3: scan_urlencoded_skipvalue( &read_ptr
); break;
380 case 1: /* matched "info_hash" */
381 /* ignore this, when we have less than 20 bytes */
382 if( scan_urlencoded_query( &read_ptr
, (char*)(multiscrape_buf
+ numwant
++), SCAN_SEARCHPATH_VALUE
) != (ssize_t
)sizeof(ot_hash
) )
388 /* No info_hash found? Inform user */
389 if( !numwant
) HTTPERROR_400_PARAM
;
391 /* Limit number of hashes to process */
392 if( numwant
> OT_MAXMULTISCRAPE_COUNT
)
393 numwant
= OT_MAXMULTISCRAPE_COUNT
;
395 /* Enough for http header + whole scrape string */
396 ws
->reply_size
= return_tcp_scrape_for_torrent( (const ot_hash
*)multiscrape_buf
, numwant
, ws
->reply
);
397 stats_issue_event( EVENT_SCRAPE
, FLAG_TCP
, ws
->reply_size
);
398 return ws
->reply_size
;
401 #ifdef WANT_LOG_NUMWANT
402 unsigned long long numwants
[201];
405 #if defined( WANT_KEEPALIVE ) || defined( WANT_IP_FROM_PROXY )
406 static char* http_header( char *data
, size_t byte_count
, char *header
) {
408 long sl
= strlen( header
);
409 for( i
= 0; i
+ sl
+ 2 < byte_count
; ++i
) {
410 if( data
[i
] != '\n' || data
[ i
+ sl
+ 1] != ':' ) continue;
411 if( !case_equalb( data
+ i
+ 1, sl
, header
) ) continue;
413 while( *data
== ' ' || *data
== '\t' ) ++data
;
420 static ot_keywords keywords_announce
[] = { { "port", 1 }, { "left", 2 }, { "event", 3 }, { "numwant", 4 }, { "compact", 5 }, { "compact6", 5 }, { "info_hash", 6 },
421 #ifdef WANT_IP_FROM_QUERY_STRING
424 #ifdef WANT_FULLLOG_NETWORKS
429 static ot_keywords keywords_announce_event
[] = { { "completed", 1 }, { "stopped", 2 }, { NULL
, -3 } };
430 static ssize_t
http_handle_announce( const int64 sock
, struct ot_workstruct
*ws
, char *read_ptr
) {
431 int numwant
, tmp
, scanon
;
432 unsigned short port
= 0;
435 struct http_data
*cookie
= io_getcookie( sock
);
437 /* This is to hack around stupid clients that send "announce ?info_hash" */
438 if( read_ptr
[-1] != '?' ) {
439 while( ( *read_ptr
!= '?' ) && ( *read_ptr
!= '\n' ) ) ++read_ptr
;
440 if( *read_ptr
== '\n' ) HTTPERROR_400_PARAM
;
444 #ifdef WANT_IP_FROM_PROXY
445 if( accesslist_is_blessed( cookie
->ip
, OT_PERMISSION_MAY_PROXY
) ) {
447 char *fwd
= http_header( ws
->request
, ws
->header_size
, "x-forwarded-for" );
448 if( fwd
&& scan_ip6( fwd
, proxied_ip
) ) {
449 OT_SETIP( ws
->peer
, proxied_ip
);
451 OT_SETIP( ws
->peer
, cookie
->ip
);
454 OT_SETIP( ws
->peer
, cookie
->ip
);
459 OT_SETPORT( ws
->peer
, &port
);
460 OT_PEERFLAG( ws
->peer
) = 0;
465 switch( scan_find_keywords(keywords_announce
, &read_ptr
, SCAN_SEARCHPATH_PARAM
) ) {
466 case -2: scanon
= 0; break; /* TERMINATOR */
467 case -1: HTTPERROR_400_PARAM
; /* PARSE ERROR */
468 case -3: scan_urlencoded_skipvalue( &read_ptr
); break;
469 case 1: /* matched "port" */
470 len
= scan_urlencoded_query( &read_ptr
, write_ptr
= read_ptr
, SCAN_SEARCHPATH_VALUE
);
471 if( ( len
<= 0 ) || scan_fixed_int( write_ptr
, len
, &tmp
) || ( tmp
> 0xffff ) ) HTTPERROR_400_PARAM
;
472 port
= htons( tmp
); OT_SETPORT( &ws
->peer
, &port
);
474 case 2: /* matched "left" */
475 if( ( len
= scan_urlencoded_query( &read_ptr
, write_ptr
= read_ptr
, SCAN_SEARCHPATH_VALUE
) ) <= 0 ) HTTPERROR_400_PARAM
;
476 if( scan_fixed_int( write_ptr
, len
, &tmp
) ) tmp
= 0;
477 if( !tmp
) OT_PEERFLAG( &ws
->peer
) |= PEER_FLAG_SEEDING
;
479 case 3: /* matched "event" */
480 switch( scan_find_keywords( keywords_announce_event
, &read_ptr
, SCAN_SEARCHPATH_VALUE
) ) {
481 case -1: HTTPERROR_400_PARAM
;
482 case 1: /* matched "completed" */
483 OT_PEERFLAG( &ws
->peer
) |= PEER_FLAG_COMPLETED
;
485 case 2: /* matched "stopped" */
486 OT_PEERFLAG( &ws
->peer
) |= PEER_FLAG_STOPPED
;
492 case 4: /* matched "numwant" */
493 len
= scan_urlencoded_query( &read_ptr
, write_ptr
= read_ptr
, SCAN_SEARCHPATH_VALUE
);
494 if( ( len
<= 0 ) || scan_fixed_int( write_ptr
, len
, &numwant
) ) HTTPERROR_400_PARAM
;
495 if( numwant
< 0 ) numwant
= 50;
496 if( numwant
> 200 ) numwant
= 200;
498 case 5: /* matched "compact" */
499 len
= scan_urlencoded_query( &read_ptr
, write_ptr
= read_ptr
, SCAN_SEARCHPATH_VALUE
);
500 if( ( len
<= 0 ) || scan_fixed_int( write_ptr
, len
, &tmp
) ) HTTPERROR_400_PARAM
;
501 if( !tmp
) HTTPERROR_400_COMPACT
;
503 case 6: /* matched "info_hash" */
504 if( ws
->hash
) HTTPERROR_400_DOUBLEHASH
;
505 /* ignore this, when we have less than 20 bytes */
506 if( scan_urlencoded_query( &read_ptr
, write_ptr
= read_ptr
, SCAN_SEARCHPATH_VALUE
) != 20 ) HTTPERROR_400_PARAM
;
507 ws
->hash
= (ot_hash
*)write_ptr
;
509 #ifdef WANT_IP_FROM_QUERY_STRING
510 case 7: /* matched "ip" */
512 char *tmp_buf1
= ws
->reply
, *tmp_buf2
= ws
->reply
+16;
513 len
= scan_urlencoded_query( &read_ptr
, tmp_buf2
, SCAN_SEARCHPATH_VALUE
);
515 if( ( len
<= 0 ) || !scan_ip6( tmp_buf2
, tmp_buf1
) ) HTTPERROR_400_PARAM
;
516 OT_SETIP( &ws
->peer
, tmp_buf1
);
520 #ifdef WANT_FULLLOG_NETWORKS
521 case 8: /* matched "lognet" */
523 //if( accesslist_is_blessed( cookie->ip, OT_PERMISSION_MAY_STAT ) ) {
524 char *tmp_buf
= ws
->reply
;
526 signed short parsed
, bits
;
528 len
= scan_urlencoded_query( &read_ptr
, tmp_buf
, SCAN_SEARCHPATH_VALUE
);
530 if( len
<= 0 ) HTTPERROR_400_PARAM
;
531 if( *tmp_buf
== '-' ) {
533 return ws
->reply_size
= sprintf( ws
->reply
, "Successfully removed.\n" );
535 parsed
= scan_ip6( tmp_buf
, net
.address
);
536 if( !parsed
) HTTPERROR_400_PARAM
;
537 if( tmp_buf
[parsed
++] != '/' )
540 parsed
= scan_short( tmp_buf
+ parsed
, &bits
);
541 if( !parsed
) HTTPERROR_400_PARAM
;
542 if( ip6_isv4mapped( net
.address
) )
546 loglist_add_network( &net
);
547 return ws
->reply_size
= sprintf( ws
->reply
, "Successfully added.\n" );
552 case 9: /* matched "peer_id" */
553 /* ignore this, when we have less than 20 bytes */
554 if( scan_urlencoded_query( &read_ptr
, write_ptr
= read_ptr
, SCAN_SEARCHPATH_VALUE
) != 20 ) HTTPERROR_400_PARAM
;
555 ws
->peer_id
= write_ptr
;
560 #ifdef WANT_LOG_NUMWANT
565 stats_issue_event( EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ws->reply );
568 /* Scanned whole query string */
570 return ws
->reply_size
= sprintf( ws
->reply
, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e" );
572 if( OT_PEERFLAG( &ws
->peer
) & PEER_FLAG_STOPPED
)
573 ws
->reply_size
= remove_peer_from_torrent( FLAG_TCP
, ws
);
575 ws
->reply_size
= add_peer_to_torrent_and_return_peers( FLAG_TCP
, ws
, numwant
);
577 stats_issue_event( EVENT_ANNOUNCE
, FLAG_TCP
, ws
->reply_size
);
578 return ws
->reply_size
;
581 ssize_t
http_handle_request( const int64 sock
, struct ot_workstruct
*ws
) {
582 ssize_t reply_off
, len
;
583 char *read_ptr
= ws
->request
, *write_ptr
;
585 #ifdef WANT_FULLLOG_NETWORKS
586 struct http_data
*cookie
= io_getcookie( sock
);
587 if( loglist_check_address( cookie
->ip
) ) {
588 ot_log
*log
= malloc( sizeof( ot_log
) );
590 log
->size
= ws
->request_size
;
591 log
->data
= malloc( ws
->request_size
);
593 log
->time
= g_now_seconds
;
594 memcpy( log
->ip
, cookie
->ip
, sizeof(ot_ip6
));
596 memcpy( log
->data
, ws
->request
, ws
->request_size
);
597 if( !g_logchain_first
)
598 g_logchain_first
= g_logchain_last
= log
;
600 g_logchain_last
->next
= log
;
601 g_logchain_last
= log
;
609 #ifdef _DEBUG_HTTPERROR
610 reply_off
= ws
->request_size
;
611 if( ws
->request_size
>= G_DEBUGBUF_SIZE
)
612 reply_off
= G_DEBUGBUF_SIZE
- 1;
613 memcpy( ws
->debugbuf
, ws
->request
, reply_off
);
614 ws
->debugbuf
[ reply_off
] = 0;
617 /* Tell subroutines where to put reply data */
618 ws
->reply
= ws
->outbuf
+ SUCCESS_HTTP_HEADER_LENGTH
;
620 /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */
621 if( memcmp( read_ptr
, "GET /", 5) ) HTTPERROR_400
;
623 /* Skip leading '/' */
624 for( read_ptr
+=4; *read_ptr
== '/'; ++read_ptr
);
626 /* Try to parse the request.
627 In reality we abandoned requiring the url to be correct. This now
628 only decodes url encoded characters, we check for announces and
629 scrapes by looking for "a*" or "sc" */
630 len
= scan_urlencoded_query( &read_ptr
, write_ptr
= read_ptr
, SCAN_PATH
);
632 /* If parsing returned an error, leave with not found */
633 if( g_redirecturl
&& ( len
== -2 ) ) HTTPERROR_302
;
634 if( len
<= 0 ) HTTPERROR_404
;
636 /* This is the hardcore match for announce*/
637 if( ( *write_ptr
== 'a' ) || ( *write_ptr
== '?' ) )
638 http_handle_announce( sock
, ws
, read_ptr
);
639 #ifdef WANT_FULLSCRAPE
640 else if( !memcmp( write_ptr
, "scrape HTTP/", 12 ) )
641 http_handle_fullscrape( sock
, ws
);
643 /* This is the hardcore match for scrape */
644 else if( !memcmp( write_ptr
, "sc", 2 ) )
645 http_handle_scrape( sock
, ws
, read_ptr
);
646 /* All the rest is matched the standard way */
647 else if( len
== g_stats_path_len
&& !memcmp( write_ptr
, g_stats_path
, len
) )
648 http_handle_stats( sock
, ws
, read_ptr
);
652 /* Find out if the client wants to keep this connection alive */
654 #ifdef WANT_KEEPALIVE
655 read_ptr
=http_header( ws
->request
, ws
->header_size
, "connection");
656 if( read_ptr
&& ( *read_ptr
== 'K' || *read_ptr
== 'k' ) ) ws
->keep_alive
= 1;
659 /* If routines handled sending themselves, just return */
660 if( ws
->reply_size
== -2 ) return 0;
661 /* If routine failed, let http error take over */
662 if( ws
->reply_size
<= 0 ) HTTPERROR_500
;
664 /* This one is rather ugly, so I take you step by step through it.
666 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to
667 write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our work buffer, which is enough for the static string
668 plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate
669 the space NOT needed to expand in reply_off
671 reply_off
= SUCCESS_HTTP_SIZE_OFF
- snprintf( ws
->outbuf
, 0, "%zd", ws
->reply_size
);
672 ws
->reply
= ws
->outbuf
+ reply_off
;
674 /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete
675 packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */
676 ws
->reply_size
+= 1 + sprintf( ws
->reply
, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws
->reply_size
);
678 /* 3. Finally we join both blocks neatly */
679 ws
->outbuf
[ SUCCESS_HTTP_HEADER_LENGTH
- 1 ] = '\n';
681 http_senddata( sock
, ws
);
682 return ws
->reply_size
;
685 const char *g_version_http_c
= "$Source$: $Revision$\n";