diff options
Diffstat (limited to 'opentracker.c')
-rw-r--r-- | opentracker.c | 122 |
1 files changed, 58 insertions, 64 deletions
diff --git a/opentracker.c b/opentracker.c index 00b73a3..54fb80f 100644 --- a/opentracker.c +++ b/opentracker.c | |||
@@ -33,7 +33,8 @@ static time_t ot_start_time; | |||
33 | static const size_t SUCCESS_HTTP_HEADER_LENGTH = 80; | 33 | static const size_t SUCCESS_HTTP_HEADER_LENGTH = 80; |
34 | static const size_t SUCCESS_HTTP_SIZE_OFF = 17; | 34 | static const size_t SUCCESS_HTTP_SIZE_OFF = 17; |
35 | /* To always have space for error messages ;) */ | 35 | /* To always have space for error messages ;) */ |
36 | static char static_scratch[8192]; | 36 | static char static_inbuf[8192]; |
37 | static char static_outbuf[8192*4]; | ||
37 | 38 | ||
38 | #ifdef _DEBUG_HTTPERROR | 39 | #ifdef _DEBUG_HTTPERROR |
39 | static char debug_request[8192]; | 40 | static char debug_request[8192]; |
@@ -51,12 +52,11 @@ struct http_data { | |||
51 | 52 | ||
52 | int main( int argc, char **argv ); | 53 | int main( int argc, char **argv ); |
53 | 54 | ||
54 | static int httpheader_complete( struct http_data *h ); | 55 | static void httperror( const int64 s, const char *title, const char *message ); |
55 | static void httperror( const int64 s, struct http_data *h, const char *title, const char *message ); | 56 | static void httpresponse( const int64 s, char *data ); |
56 | static void httpresponse( const int64 s, struct http_data *h); | ||
57 | 57 | ||
58 | static void sendmallocdata( const int64 s, struct http_data *h, char *buffer, const size_t size ); | 58 | static void sendmallocdata( const int64 s, char *buffer, const size_t size ); |
59 | static void senddata( const int64 s, struct http_data *h, char *buffer, const size_t size ); | 59 | static void senddata( const int64 s, char *buffer, const size_t size ); |
60 | 60 | ||
61 | static void server_mainloop( const int64 serversocket ); | 61 | static void server_mainloop( const int64 serversocket ); |
62 | static void handle_timeouted( void ); | 62 | static void handle_timeouted( void ); |
@@ -71,11 +71,11 @@ static void carp( const char *routine ); | |||
71 | static void panic( const char *routine ); | 71 | static void panic( const char *routine ); |
72 | static void graceful( int s ); | 72 | static void graceful( int s ); |
73 | 73 | ||
74 | #define HTTPERROR_400 return httperror( s, h, "400 Invalid Request", "This server only understands GET." ) | 74 | #define HTTPERROR_400 return httperror( s, "400 Invalid Request", "This server only understands GET." ) |
75 | #define HTTPERROR_400_PARAM return httperror( s, h, "400 Invalid Request", "Invalid parameter" ) | 75 | #define HTTPERROR_400_PARAM return httperror( s, "400 Invalid Request", "Invalid parameter" ) |
76 | #define HTTPERROR_400_COMPACT return httperror( s, h, "400 Invalid Request", "This server only delivers compact results." ) | 76 | #define HTTPERROR_400_COMPACT return httperror( s, "400 Invalid Request", "This server only delivers compact results." ) |
77 | #define HTTPERROR_404 return httperror( s, h, "404 Not Found", "No such file or directory." ) | 77 | #define HTTPERROR_404 return httperror( s, "404 Not Found", "No such file or directory." ) |
78 | #define HTTPERROR_500 return httperror( s, h, "500 Internal Server Error", "A server error has occured. Please retry later." ) | 78 | #define HTTPERROR_500 return httperror( s, "500 Internal Server Error", "A server error has occured. Please retry later." ) |
79 | 79 | ||
80 | /* End of prototypes */ | 80 | /* End of prototypes */ |
81 | 81 | ||
@@ -91,33 +91,23 @@ static void panic( const char *routine ) { | |||
91 | exit( 111 ); | 91 | exit( 111 ); |
92 | } | 92 | } |
93 | 93 | ||
94 | static int httpheader_complete( struct http_data *h ) { | 94 | static void httperror( const int64 s, const char *title, const char *message ) { |
95 | size_t l = array_bytes( &h->request ), i; | 95 | 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", |
96 | const char* c = array_start( &h->request ); | ||
97 | |||
98 | for( i=0; i+1<l; ++i) { | ||
99 | if( c[i]=='\n' && c[i+1]=='\n') return i+2; | ||
100 | if( i+3<l && c[i]=='\r' && c[i+1]=='\n' && c[i+2]=='\r' && c[i+3]=='\n' ) return i+4; | ||
101 | } | ||
102 | return 0; | ||
103 | } | ||
104 | |||
105 | static void httperror( const int64 s, struct http_data *h, const char *title, const char *message ) { | ||
106 | size_t reply_size = sprintf( static_scratch, "HTTP/1.0 %s\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n", | ||
107 | title, strlen(message)+strlen(title)+16-4,title+4); | 96 | title, strlen(message)+strlen(title)+16-4,title+4); |
108 | #ifdef _DEBUG_HTTPERROR | 97 | #ifdef _DEBUG_HTTPERROR |
109 | fprintf( stderr, "DEBUG: invalid request was: %s\n", debug_request ); | 98 | fprintf( stderr, "DEBUG: invalid request was: %s\n", debug_request ); |
110 | #endif | 99 | #endif |
111 | senddata(s,h,static_scratch,reply_size); | 100 | senddata(s,static_outbuf,reply_size); |
112 | } | 101 | } |
113 | 102 | ||
114 | static void sendmallocdata( const int64 s, struct http_data *h, char *buffer, size_t size ) { | 103 | static void sendmallocdata( const int64 s, char *buffer, size_t size ) { |
115 | tai6464 t; | 104 | struct http_data *h = io_getcookie( s ); |
116 | char *header; | 105 | char *header; |
117 | size_t header_size; | 106 | size_t header_size; |
107 | tai6464 t; | ||
118 | 108 | ||
119 | if( !h ) | 109 | if( !h ) |
120 | return free( buffer); | 110 | return free( buffer ); |
121 | array_reset( &h->request ); | 111 | array_reset( &h->request ); |
122 | 112 | ||
123 | header = malloc( SUCCESS_HTTP_HEADER_LENGTH ); | 113 | header = malloc( SUCCESS_HTTP_HEADER_LENGTH ); |
@@ -133,12 +123,13 @@ static void sendmallocdata( const int64 s, struct http_data *h, char *buffer, si | |||
133 | iob_addbuf_free( &h->batch, buffer, size ); | 123 | iob_addbuf_free( &h->batch, buffer, size ); |
134 | 124 | ||
135 | /* writeable sockets just have a tcp timeout */ | 125 | /* writeable sockets just have a tcp timeout */ |
136 | taia_uint(&t,0); io_timeout( s, t ); | 126 | taia_uint( &t, 0 ); io_timeout( s, t ); |
137 | io_dontwantread( s ); | 127 | io_dontwantread( s ); |
138 | io_wantwrite( s ); | 128 | io_wantwrite( s ); |
139 | } | 129 | } |
140 | 130 | ||
141 | static void senddata( const int64 s, struct http_data *h, char *buffer, size_t size ) { | 131 | static void senddata( const int64 s, char *buffer, size_t size ) { |
132 | struct http_data *h = io_getcookie( s ); | ||
142 | size_t written_size; | 133 | size_t written_size; |
143 | 134 | ||
144 | /* whoever sends data is not interested in its input-array */ | 135 | /* whoever sends data is not interested in its input-array */ |
@@ -168,8 +159,8 @@ static void senddata( const int64 s, struct http_data *h, char *buffer, size_t s | |||
168 | } | 159 | } |
169 | } | 160 | } |
170 | 161 | ||
171 | static void httpresponse( const int64 s, struct http_data *h) { | 162 | static void httpresponse( const int64 s, char *data ) { |
172 | char *c, *data, *reply; | 163 | char *c, *reply; |
173 | ot_peer peer; | 164 | ot_peer peer; |
174 | ot_torrent *torrent; | 165 | ot_torrent *torrent; |
175 | ot_hash *hash = NULL; | 166 | ot_hash *hash = NULL; |
@@ -178,22 +169,19 @@ static void httpresponse( const int64 s, struct http_data *h) { | |||
178 | time_t t; | 169 | time_t t; |
179 | size_t reply_size = 0, reply_off; | 170 | size_t reply_size = 0, reply_off; |
180 | 171 | ||
181 | array_cat0( &h->request ); | ||
182 | c = array_start( &h->request ); | ||
183 | |||
184 | #ifdef _DEBUG_HTTPERROR | 172 | #ifdef _DEBUG_HTTPERROR |
185 | memcpy( debug_request, array_start( &h->request ), array_bytes( &h->request ) ); | 173 | memcpy( debug_request, data, sizeof( debug_request ) ); |
186 | #endif | 174 | #endif |
187 | 175 | ||
188 | if( byte_diff( c, 4, "GET ") ) HTTPERROR_400; | 176 | /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */ |
177 | if( byte_diff( data, 5, "GET /") ) HTTPERROR_400; | ||
189 | 178 | ||
190 | c+=4; | 179 | /* Query string MUST terminate with SP -- we know that theres at least a '\n' where this search terminates */ |
191 | for( data = c; *data!=' ' && *data != '\t' && *data != '\n' && *data != '\r'; ++data ) ; | 180 | for( c = data + 5; *c!=' ' && *c != '\t' && *c != '\n' && *c != '\r'; ++c ) ; |
181 | if( *c != ' ' ) HTTPERROR_400; | ||
192 | 182 | ||
193 | if( *data != ' ' ) HTTPERROR_400; | 183 | /* Skip leading '/' */ |
194 | *data = 0; | 184 | for( c = data+4; *c == '/'; ++c); |
195 | if( c[0] != '/' ) HTTPERROR_404; | ||
196 | while( *c == '/' ) ++c; | ||
197 | 185 | ||
198 | switch( scan_urlencoded_query( &c, data = c, SCAN_PATH ) ) { | 186 | switch( scan_urlencoded_query( &c, data = c, SCAN_PATH ) ) { |
199 | case 4: /* sync ? */ | 187 | case 4: /* sync ? */ |
@@ -222,7 +210,7 @@ static void httpresponse( const int64 s, struct http_data *h) { | |||
222 | if( !hash ) HTTPERROR_400_PARAM; | 210 | if( !hash ) HTTPERROR_400_PARAM; |
223 | if( ( reply_size = return_sync_for_torrent( hash, &reply ) ) <= 0 ) HTTPERROR_500; | 211 | if( ( reply_size = return_sync_for_torrent( hash, &reply ) ) <= 0 ) HTTPERROR_500; |
224 | 212 | ||
225 | return sendmallocdata( s, h, reply, reply_size ); | 213 | return sendmallocdata( s, reply, reply_size ); |
226 | case 5: /* stats ? */ | 214 | case 5: /* stats ? */ |
227 | if( byte_diff(data,5,"stats")) HTTPERROR_404; | 215 | if( byte_diff(data,5,"stats")) HTTPERROR_404; |
228 | scanon = 1; | 216 | scanon = 1; |
@@ -250,7 +238,7 @@ static void httpresponse( const int64 s, struct http_data *h) { | |||
250 | } | 238 | } |
251 | 239 | ||
252 | /* Enough for http header + whole scrape string */ | 240 | /* Enough for http header + whole scrape string */ |
253 | if( ( reply_size = return_stats_for_tracker( SUCCESS_HTTP_HEADER_LENGTH + static_scratch, mode ) ) <= 0 ) HTTPERROR_500; | 241 | if( ( reply_size = return_stats_for_tracker( SUCCESS_HTTP_HEADER_LENGTH + static_outbuf, mode ) ) <= 0 ) HTTPERROR_500; |
254 | 242 | ||
255 | break; | 243 | break; |
256 | case 6: /* scrape ? */ | 244 | case 6: /* scrape ? */ |
@@ -279,18 +267,18 @@ SCRAPE_WORKAROUND: | |||
279 | /* Scanned whole query string, no hash means full scrape... you might want to limit that */ | 267 | /* Scanned whole query string, no hash means full scrape... you might want to limit that */ |
280 | if( !hash ) { | 268 | if( !hash ) { |
281 | if( ( reply_size = return_fullscrape_for_tracker( &reply ) ) <= 0 ) HTTPERROR_500; | 269 | if( ( reply_size = return_fullscrape_for_tracker( &reply ) ) <= 0 ) HTTPERROR_500; |
282 | return sendmallocdata( s, h, reply, reply_size ); | 270 | return sendmallocdata( s, reply, reply_size ); |
283 | } | 271 | } |
284 | 272 | ||
285 | /* Enough for http header + whole scrape string */ | 273 | /* Enough for http header + whole scrape string */ |
286 | if( ( reply_size = return_scrape_for_torrent( hash, SUCCESS_HTTP_HEADER_LENGTH + static_scratch ) ) <= 0 ) HTTPERROR_500; | 274 | if( ( reply_size = return_scrape_for_torrent( hash, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf ) ) <= 0 ) HTTPERROR_500; |
287 | break; | 275 | break; |
288 | case 8: | 276 | case 8: |
289 | if( byte_diff(data,8,"announce")) HTTPERROR_404; | 277 | if( byte_diff(data,8,"announce")) HTTPERROR_404; |
290 | 278 | ||
291 | ANNOUNCE_WORKAROUND: | 279 | ANNOUNCE_WORKAROUND: |
292 | 280 | ||
293 | OT_SETIP( &peer, h->ip); | 281 | OT_SETIP( &peer, ((struct http_data*)io_getcookie( s ))->ip); |
294 | OT_SETPORT( &peer, &port ); | 282 | OT_SETPORT( &peer, &port ); |
295 | OT_FLAG( &peer ) = 0; | 283 | OT_FLAG( &peer ) = 0; |
296 | numwant = 50; | 284 | numwant = 50; |
@@ -369,10 +357,10 @@ ANNOUNCE_WORKAROUND: | |||
369 | 357 | ||
370 | if( OT_FLAG( &peer ) & PEER_FLAG_STOPPED ) { | 358 | if( OT_FLAG( &peer ) & PEER_FLAG_STOPPED ) { |
371 | remove_peer_from_torrent( hash, &peer ); | 359 | remove_peer_from_torrent( hash, &peer ); |
372 | reply_size = sprintf( static_scratch + SUCCESS_HTTP_HEADER_LENGTH, "d8:completei0e10:incompletei0e8:intervali%ie5:peers0:e", OT_CLIENT_REQUEST_INTERVAL_RANDOM ); | 360 | reply_size = sprintf( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, "d8:completei0e10:incompletei0e8:intervali%ie5:peers0:e", OT_CLIENT_REQUEST_INTERVAL_RANDOM ); |
373 | } else { | 361 | } else { |
374 | torrent = add_peer_to_torrent( hash, &peer ); | 362 | torrent = add_peer_to_torrent( hash, &peer ); |
375 | if( !torrent || ( reply_size = return_peers_for_torrent( torrent, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_scratch ) ) <= 0 ) HTTPERROR_500; | 363 | if( !torrent || ( reply_size = return_peers_for_torrent( torrent, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf ) ) <= 0 ) HTTPERROR_500; |
376 | } | 364 | } |
377 | ot_overall_successfulannounces++; | 365 | ot_overall_successfulannounces++; |
378 | break; | 366 | break; |
@@ -383,7 +371,7 @@ ANNOUNCE_WORKAROUND: | |||
383 | if( byte_diff(data,11,"mrtg_scrape")) HTTPERROR_404; | 371 | if( byte_diff(data,11,"mrtg_scrape")) HTTPERROR_404; |
384 | 372 | ||
385 | t = time( NULL ) - ot_start_time; | 373 | t = time( NULL ) - ot_start_time; |
386 | reply_size = sprintf( static_scratch + SUCCESS_HTTP_HEADER_LENGTH, | 374 | reply_size = sprintf( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, |
387 | "%i\n%i\nUp: %i seconds (%i hours)\nPretuned by german engineers, currently handling %i connections per second.", | 375 | "%i\n%i\nUp: %i seconds (%i hours)\nPretuned by german engineers, currently handling %i connections per second.", |
388 | ot_overall_connections, ot_overall_successfulannounces, (int)t, (int)(t / 3600), (int)ot_overall_connections / ( (int)t ? (int)t : 1 ) ); | 376 | ot_overall_connections, ot_overall_successfulannounces, (int)t, (int)(t / 3600), (int)ot_overall_connections / ( (int)t ? (int)t : 1 ) ); |
389 | break; | 377 | break; |
@@ -403,16 +391,16 @@ ANNOUNCE_WORKAROUND: | |||
403 | plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for it expansion and calculate | 391 | plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for it expansion and calculate |
404 | the space NOT needed to expand in reply_off | 392 | the space NOT needed to expand in reply_off |
405 | */ | 393 | */ |
406 | reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( static_scratch, 0, "%zd", reply_size ); | 394 | reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( static_outbuf, 0, "%zd", reply_size ); |
407 | 395 | ||
408 | /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete | 396 | /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete |
409 | packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */ | 397 | packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */ |
410 | reply_size += 1 + sprintf( static_scratch + reply_off, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", reply_size ); | 398 | 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 ); |
411 | 399 | ||
412 | /* 3. Finally we join both blocks neatly */ | 400 | /* 3. Finally we join both blocks neatly */ |
413 | static_scratch[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n'; | 401 | static_outbuf[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n'; |
414 | 402 | ||
415 | senddata( s, h, static_scratch + reply_off, reply_size ); | 403 | senddata( s, static_outbuf + reply_off, reply_size ); |
416 | } | 404 | } |
417 | 405 | ||
418 | static void graceful( int s ) { | 406 | static void graceful( int s ) { |
@@ -463,7 +451,7 @@ static void handle_read( const int64 clientsocket ) { | |||
463 | struct http_data* h = io_getcookie( clientsocket ); | 451 | struct http_data* h = io_getcookie( clientsocket ); |
464 | size_t l; | 452 | size_t l; |
465 | 453 | ||
466 | if( ( l = io_tryread( clientsocket, static_scratch, sizeof static_scratch ) ) <= 0 ) { | 454 | if( ( l = io_tryread( clientsocket, static_inbuf, sizeof static_inbuf ) ) <= 0 ) { |
467 | if( h ) { | 455 | if( h ) { |
468 | array_reset( &h->request ); | 456 | array_reset( &h->request ); |
469 | free( h ); | 457 | free( h ); |
@@ -472,24 +460,30 @@ static void handle_read( const int64 clientsocket ) { | |||
472 | return; | 460 | return; |
473 | } | 461 | } |
474 | 462 | ||
475 | array_catb( &h->request, static_scratch, l ); | ||
476 | |||
477 | #ifdef _DEBUG_HTTPERROR | 463 | #ifdef _DEBUG_HTTPERROR |
478 | memcpy( debug_request, "500!\0", 5 ); | 464 | memcpy( debug_request, "500!\0", 5 ); |
479 | #endif | 465 | #endif |
480 | 466 | ||
467 | /* If we get the whole request in one packet, handle it without copying */ | ||
468 | if( !array_start( &h->request ) ) { | ||
469 | if( memchr( static_inbuf, '\n', l ) ) | ||
470 | return httpresponse( clientsocket, static_inbuf ); | ||
471 | return array_catb( &h->request, static_inbuf, l ); | ||
472 | } | ||
473 | |||
474 | array_catb( &h->request, static_inbuf, l ); | ||
475 | |||
481 | if( array_failed( &h->request ) ) | 476 | if( array_failed( &h->request ) ) |
482 | httperror( clientsocket, h, "500 Server Error", "Request too long."); | 477 | httperror( clientsocket, "500 Server Error", "Request too long."); |
483 | else if( array_bytes( &h->request ) > 8192 ) | 478 | else if( array_bytes( &h->request ) > 8192 ) |
484 | httperror( clientsocket, h, "500 request too long", "You sent too much headers"); | 479 | httperror( clientsocket, "500 request too long", "You sent too much headers"); |
485 | else if( ( l = httpheader_complete( h ) ) ) | 480 | else if( memchr( array_start( &h->request ), '\n', array_length( &h->request, 1 ) ) ) |
486 | httpresponse( clientsocket, h); | 481 | httpresponse( clientsocket, array_start( &h->request ) ); |
487 | } | 482 | } |
488 | 483 | ||
489 | static void handle_write( const int64 clientsocket ) { | 484 | static void handle_write( const int64 clientsocket ) { |
490 | struct http_data* h=io_getcookie( clientsocket ); | 485 | struct http_data* h=io_getcookie( clientsocket ); |
491 | if( !h ) return; | 486 | if( !h || ( iob_send( clientsocket, &h->batch ) <= 0 ) ) { |
492 | if( iob_send( clientsocket, &h->batch ) <= 0 ) { | ||
493 | iob_reset( &h->batch ); | 487 | iob_reset( &h->batch ); |
494 | io_close( clientsocket ); | 488 | io_close( clientsocket ); |
495 | free( h ); | 489 | free( h ); |