Browse Source

Introduce another unspec'd command: sync which syncs the most recent peer pool for a torrent out. Do some variable type clean up. Do some code structure clean up.

dynamic-accesslists
erdgeist 18 years ago
parent
commit
05420c40e5
  1. 356
      opentracker.c
  2. 77
      trackerlogic.c
  3. 9
      trackerlogic.h

356
opentracker.c

@ -1,4 +1,4 @@
/* This software was written by Dirk Engling <erdgeist@erdgeist.org> /* This software was written by Dirk Engling <erdgeist@erdgeist.org>
It is considered beerware. Prost. Skol. Cheers or whatever. It is considered beerware. Prost. Skol. Cheers or whatever.
Some of the stuff below is stolen from Fefes example libowfat httpd. Some of the stuff below is stolen from Fefes example libowfat httpd.
*/ */
@ -26,6 +26,7 @@
#include "trackerlogic.h" #include "trackerlogic.h"
#include "scan_urlencoded_query.h" #include "scan_urlencoded_query.h"
/* Globals */
static unsigned int ot_overall_connections = 0; static unsigned int ot_overall_connections = 0;
static unsigned int ot_overall_successfulannounces = 0; static unsigned int ot_overall_successfulannounces = 0;
static time_t ot_start_time; static time_t ot_start_time;
@ -34,35 +35,64 @@ static const size_t SUCCESS_HTTP_SIZE_OFF = 17;
/* To always have space for error messages ;) */ /* To always have space for error messages ;) */
static char static_scratch[8192]; static char static_scratch[8192];
#ifdef _DEBUG_FDS
static char fd_debug_space[0x10000];
#endif
#ifdef _DEBUG_HTTPERROR #ifdef _DEBUG_HTTPERROR
static char debug_request[8192]; static char debug_request[8192];
#endif #endif
static void carp(const char* routine ) { struct http_data {
union {
array request;
io_batch batch;
};
unsigned char ip[4];
};
/* Prototypes */
int main( int argc, char **argv );
static int httpheader_complete( struct http_data *h );
static void httperror( const int64 s, struct http_data *h, const char *title, const char *message );
static void httpresponse( const int64 s, struct http_data *h);
static void sendmallocdata( const int64 s, struct http_data *h, char *buffer, const size_t size );
static void senddata( const int64 s, struct http_data *h, char *buffer, const size_t size );
static void server_mainloop( const int64 serversocket );
static void handle_timeouted( void );
static void handle_accept( const int64 serversocket );
static void handle_read( const int64 clientsocket );
static void handle_write( const int64 clientsocket );
static void usage( char *name );
static void help( char *name );
static void carp( const char *routine );
static void panic( const char *routine );
static void graceful( int s );
#define HTTPERROR_400 return httperror( s, h, "400 Invalid Request", "This server only understands GET." )
#define HTTPERROR_400_PARAM return httperror( s, h, "400 Invalid Request", "Invalid parameter" )
#define HTTPERROR_400_COMPACT return httperror( s, h, "400 Invalid Request", "This server only delivers compact results." )
#define HTTPERROR_404 return httperror( s, h, "404 Not Found", "No such file or directory." )
#define HTTPERROR_500 return httperror( s, h, "500 Internal Server Error", "A server error has occured. Please retry later." )
/* End of prototypes */
static void carp( const char *routine ) {
buffer_puts( buffer_2, routine ); buffer_puts( buffer_2, routine );
buffer_puts( buffer_2, ": " ); buffer_puts( buffer_2, ": " );
buffer_puterror( buffer_2 ); buffer_puterror( buffer_2 );
buffer_putnlflush( buffer_2 ); buffer_putnlflush( buffer_2 );
} }
static void panic( const char* routine ) { static void panic( const char *routine ) {
carp( routine ); carp( routine );
exit( 111 ); exit( 111 );
} }
struct http_data { static int httpheader_complete( struct http_data *h ) {
union { size_t l = array_bytes( &h->request ), i;
array request;
io_batch batch;
};
unsigned char ip[4];
};
int header_complete( struct http_data* h ) {
int l = array_bytes( &h->request ), i;
const char* c = array_start( &h->request ); const char* c = array_start( &h->request );
for( i=0; i+1<l; ++i) { for( i=0; i+1<l; ++i) {
@ -72,16 +102,29 @@ int header_complete( struct http_data* h ) {
return 0; return 0;
} }
void sendmallocdata( int64 s, struct http_data *h, char * buffer, size_t size ) { static void httperror( const int64 s, struct http_data *h, const char *title, const char *message ) {
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",
title, strlen(message)+strlen(title)+16-4,title+4);
#ifdef _DEBUG_HTTPERROR
fprintf( stderr, "DEBUG: invalid request was: %s\n", debug_request );
#endif
senddata(s,h,static_scratch,reply_size);
}
static void sendmallocdata( const int64 s, struct http_data *h, char *buffer, size_t size ) {
tai6464 t; tai6464 t;
char *header; char *header;
size_t header_size; size_t header_size;
if( !h ) { free( buffer); return; } if( !h )
return free( buffer);
array_reset( &h->request ); array_reset( &h->request );
header = malloc( SUCCESS_HTTP_HEADER_LENGTH ); header = malloc( SUCCESS_HTTP_HEADER_LENGTH );
if( !header ) { free( buffer ); return; } if( !header ) {
free( buffer );
HTTPERROR_500;
}
header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r\n", size ); header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r\n", size );
@ -89,13 +132,13 @@ void sendmallocdata( int64 s, struct http_data *h, char * buffer, size_t size )
iob_addbuf_free( &h->batch, header, header_size ); iob_addbuf_free( &h->batch, header, header_size );
iob_addbuf_free( &h->batch, buffer, size ); iob_addbuf_free( &h->batch, buffer, size );
// writeable sockets just have a tcp timeout /* writeable sockets just have a tcp timeout */
taia_uint(&t,0); io_timeout( s, t ); taia_uint(&t,0); io_timeout( s, t );
io_dontwantread( s ); io_dontwantread( s );
io_wantwrite( s ); io_wantwrite( s );
} }
void senddata(int64 s, struct http_data* h, char *buffer, size_t size ) { static void senddata( const int64 s, struct http_data *h, char *buffer, size_t size ) {
size_t written_size; size_t written_size;
/* whoever sends data is not interested in its input-array */ /* whoever sends data is not interested in its input-array */
@ -104,20 +147,12 @@ void senddata(int64 s, struct http_data* h, char *buffer, size_t size ) {
written_size = write( s, buffer, size ); written_size = write( s, buffer, size );
if( ( written_size < 0 ) || ( written_size == size ) ) { if( ( written_size < 0 ) || ( written_size == size ) ) {
#ifdef _DEBUG_FDS
if( !fd_debug_space[s] ) fprintf( stderr, "close on non-open fd\n" );
fd_debug_space[s] = 0;
#endif
free( h ); io_close( s ); free( h ); io_close( s );
} else { } else {
char * outbuf = malloc( size - written_size ); char * outbuf = malloc( size - written_size );
tai6464 t; tai6464 t;
if( !outbuf ) { if( !outbuf ) {
#ifdef _DEBUG_FDS
if( !fd_debug_space[s] ) fprintf( stderr, "close on non-open fd\n" );
fd_debug_space[s] = 0;
#endif
free(h); io_close( s ); free(h); io_close( s );
return; return;
} }
@ -126,29 +161,21 @@ void senddata(int64 s, struct http_data* h, char *buffer, size_t size ) {
memmove( outbuf, buffer + written_size, size - written_size ); memmove( outbuf, buffer + written_size, size - written_size );
iob_addbuf_free( &h->batch, outbuf, size - written_size ); iob_addbuf_free( &h->batch, outbuf, size - written_size );
// writeable sockets just have a tcp timeout /* writeable sockets just have a tcp timeout */
taia_uint( &t, 0 ); io_timeout( s, t ); taia_uint( &t, 0 ); io_timeout( s, t );
io_dontwantread( s ); io_dontwantread( s );
io_wantwrite( s ); io_wantwrite( s );
} }
} }
void httperror(int64 s,struct http_data* h,const char* title,const char* message) { static void httpresponse( const int64 s, struct http_data *h) {
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", char *c, *data, *reply;
title, strlen(message)+strlen(title)+16-4,title+4);
#ifdef _DEBUG_HTTPERROR
fprintf( stderr, "DEBUG: invalid request was: %s\n", debug_request );
#endif
senddata(s,h,static_scratch,reply_size);
}
void httpresponse( int64 s, struct http_data* h) {
char *c, *data;
ot_peer peer; ot_peer peer;
ot_torrent *torrent; ot_torrent *torrent;
ot_hash *hash = NULL; ot_hash *hash = NULL;
int numwant, tmp, scanon, mode; int numwant, tmp, scanon, mode;
unsigned short port = htons(6881); unsigned short port = htons(6881);
time_t t;
size_t reply_size = 0; size_t reply_size = 0;
array_cat0( &h->request ); array_cat0( &h->request );
@ -158,104 +185,106 @@ void httpresponse( int64 s, struct http_data* h) {
memcpy( debug_request, array_start( &h->request ), array_bytes( &h->request ) ); memcpy( debug_request, array_start( &h->request ), array_bytes( &h->request ) );
#endif #endif
if( byte_diff( c, 4, "GET ") ) { if( byte_diff( c, 4, "GET ") ) HTTPERROR_400;
e400:
return httperror( s, h, "400 Invalid Request", "This server only understands GET." );
}
c+=4; c+=4;
for( data = c; *data!=' ' && *data != '\t' && *data != '\n' && *data != '\r'; ++data ) ; for( data = c; *data!=' ' && *data != '\t' && *data != '\n' && *data != '\r'; ++data ) ;
if( *data != ' ' ) goto e400; if( *data != ' ' ) HTTPERROR_400;
*data = 0; *data = 0;
if( c[0] != '/' ) goto e404; if( c[0] != '/' ) HTTPERROR_404;
while( *c == '/' ) ++c; while( *c == '/' ) ++c;
switch( scan_urlencoded_query( &c, data = c, SCAN_PATH ) ) { switch( scan_urlencoded_query( &c, data = c, SCAN_PATH ) ) {
case 4: /* sync ? */
if( byte_diff( data, 4, "sync") ) HTTPERROR_404;
scanon = 1;
while( scanon ) {
switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
case -2: scanon = 0; break; /* TERMINATOR */
case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
case 9:
if(byte_diff(data,9,"info_hash")) {
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
continue;
}
/* ignore this, when we have less than 20 bytes */
if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM;
hash = (ot_hash*)data; /* Fall through intended */
break;
default:
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
break;
}
}
if( !hash ) HTTPERROR_400_PARAM;
if( ( reply_size = return_sync_for_torrent( hash, &reply ) ) <= 0 ) HTTPERROR_500;
return sendmallocdata( s, h, reply, reply_size );
case 5: /* stats ? */ case 5: /* stats ? */
if( byte_diff(data,5,"stats")) if( byte_diff(data,5,"stats")) HTTPERROR_404;
goto e404;
scanon = 1; scanon = 1;
mode = STATS_MRTG; mode = STATS_MRTG;
while( scanon ) { while( scanon ) {
switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
case -2: /* terminator */ case -2: scanon = 0; break; /* TERMINATOR */
scanon = 0; case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
break; default: scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE ); break;
case -1: /* error */
goto e404;
case 4: case 4:
if( byte_diff(data,4,"mode")) { if( byte_diff(data,4,"mode")) {
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE ); scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
continue; continue;
} }
size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
if( len <= 0 ) goto e400_param; if( len <= 0 ) HTTPERROR_400_PARAM;
if( !byte_diff(data,4,"mrtg")) if( !byte_diff(data,4,"mrtg"))
mode = STATS_MRTG; mode = STATS_MRTG;
else if( !byte_diff(data,4,"top5")) else if( !byte_diff(data,4,"top5"))
mode = STATS_TOP5; mode = STATS_TOP5;
else else
goto e400_param; HTTPERROR_400_PARAM;
default:
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
break;
} }
} }
/* Enough for http header + whole scrape string */ /* Enough for http header + whole scrape string */
if( ( reply_size = return_stats_for_tracker( SUCCESS_HTTP_HEADER_LENGTH + static_scratch, mode ) ) <= 0 ) if( ( reply_size = return_stats_for_tracker( SUCCESS_HTTP_HEADER_LENGTH + static_scratch, mode ) ) <= 0 ) HTTPERROR_500;
goto e500;
break; break;
case 6: /* scrape ? */ case 6: /* scrape ? */
if( byte_diff( data, 6, "scrape") ) if( byte_diff( data, 6, "scrape") ) HTTPERROR_404;
goto e404;
scanon = 1; scanon = 1;
while( scanon ) { while( scanon ) {
switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
case -2: /* terminator */ case -2: scanon = 0; break; /* TERMINATOR */
scanon = 0; case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
break; default: scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE ); break;
case -1: /* error */
goto e404;
case 9: case 9:
if(byte_diff(data,9,"info_hash")) { if(byte_diff(data,9,"info_hash")) {
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE ); scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
continue; continue;
} }
/* ignore this, when we have less than 20 bytes */ /* ignore this, when we have less than 20 bytes */
if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 20 ) { if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM;
e400_param:
return httperror(s,h,"400 Invalid Request","Invalid parameter");
}
hash = (ot_hash*)data; /* Fall through intended */ hash = (ot_hash*)data; /* Fall through intended */
break; break;
default:
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
break;
} }
} }
/* Scanned whole query string, no hash means full scrape... you might want to limit that */ /* Scanned whole query string, no hash means full scrape... you might want to limit that */
if( !hash ) { if( !hash ) {
char * reply; if( ( reply_size = return_fullscrape_for_tracker( &reply ) ) <= 0 ) HTTPERROR_500;
return sendmallocdata( s, h, reply, reply_size );
reply_size = return_fullscrape_for_tracker( &reply );
if( reply_size )
return sendmallocdata( s, h, reply, reply_size );
goto e500;
} else {
/* Enough for http header + whole scrape string */
if( ( reply_size = return_scrape_for_torrent( hash, SUCCESS_HTTP_HEADER_LENGTH + static_scratch ) ) <= 0 )
goto e500;
} }
/* Enough for http header + whole scrape string */
if( ( reply_size = return_scrape_for_torrent( hash, SUCCESS_HTTP_HEADER_LENGTH + static_scratch ) ) <= 0 ) HTTPERROR_500;
break; break;
case 8: case 8:
if( byte_diff(data,8,"announce")) if( byte_diff(data,8,"announce")) HTTPERROR_404;
goto e404;
OT_SETIP( &peer, h->ip); OT_SETIP( &peer, h->ip);
OT_SETPORT( &peer, &port ); OT_SETPORT( &peer, &port );
@ -265,17 +294,15 @@ e400_param:
while( scanon ) { while( scanon ) {
switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
case -2: /* terminator */ case -2: scanon = 0; break; /* TERMINATOR */
scanon = 0; case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
break; default: scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE ); break;
case -1: /* error */
goto e404;
#ifdef WANT_IP_FROM_QUERY_STRING #ifdef WANT_IP_FROM_QUERY_STRING
case 2: case 2:
if(!byte_diff(data,2,"ip")) { if(!byte_diff(data,2,"ip")) {
size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
unsigned char ip[4]; unsigned char ip[4];
if( ( len <= 0 ) || scan_fixed_ip( data, len, ip ) ) goto e400_param; if( ( len <= 0 ) || scan_fixed_ip( data, len, ip ) ) HTTPERROR_400_PARAM;
OT_SETIP( &peer, ip ); OT_SETIP( &peer, ip );
} else } else
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE ); scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
@ -284,11 +311,11 @@ e400_param:
case 4: case 4:
if(!byte_diff(data,4,"port")) { if(!byte_diff(data,4,"port")) {
size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) || ( tmp > 0xffff ) ) goto e400_param; if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) || ( tmp > 0xffff ) ) HTTPERROR_400_PARAM;
port = htons( tmp ); OT_SETPORT( &peer, &port ); port = htons( tmp ); OT_SETPORT( &peer, &port );
} else if(!byte_diff(data,4,"left")) { } else if(!byte_diff(data,4,"left")) {
size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
if( len <= 0 ) goto e400_param; if( len <= 0 ) HTTPERROR_400_PARAM;
if( scan_fixed_int( data, len, &tmp ) ) tmp = 0; if( scan_fixed_int( data, len, &tmp ) ) tmp = 0;
if( !tmp ) OT_FLAG( &peer ) |= PEER_FLAG_SEEDING; if( !tmp ) OT_FLAG( &peer ) |= PEER_FLAG_SEEDING;
} else } else
@ -299,7 +326,7 @@ e400_param:
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE ); scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
else switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) { else switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) {
case -1: case -1:
goto e400_param; HTTPERROR_400_PARAM;
case 7: case 7:
if(!byte_diff(data,7,"stopped")) OT_FLAG( &peer ) |= PEER_FLAG_STOPPED; if(!byte_diff(data,7,"stopped")) OT_FLAG( &peer ) |= PEER_FLAG_STOPPED;
break; break;
@ -312,13 +339,12 @@ e400_param:
case 7: case 7:
if(!byte_diff(data,7,"numwant")) { if(!byte_diff(data,7,"numwant")) {
size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
if( ( len <= 0 ) || scan_fixed_int( data, len, &numwant ) ) goto e400_param; if( ( len <= 0 ) || scan_fixed_int( data, len, &numwant ) ) HTTPERROR_400_PARAM;
if( numwant > 200 ) numwant = 200; if( numwant > 200 ) numwant = 200;
} else if(!byte_diff(data,7,"compact")) { } else if(!byte_diff(data,7,"compact")) {
size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); size_t len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) ) goto e400_param; if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) ) HTTPERROR_400_PARAM;
if( !tmp ) if( !tmp ) HTTPERROR_400_COMPACT;
return httperror(s,h,"400 Invalid Request","This server only delivers compact results.");
} else } else
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE ); scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
break; break;
@ -328,79 +354,58 @@ e400_param:
continue; continue;
} }
/* ignore this, when we have less than 20 bytes */ /* ignore this, when we have less than 20 bytes */
if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 20 ) if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM;
goto e400;
hash = (ot_hash*)data; hash = (ot_hash*)data;
break; break;
default:
scan_urlencoded_query( &c, NULL, SCAN_SEARCHPATH_VALUE );
break;
} }
} }
/* Scanned whole query string */ /* Scanned whole query string */
if( !hash ) goto e400; if( !hash ) HTTPERROR_400_PARAM;
if( OT_FLAG( &peer ) & PEER_FLAG_STOPPED ) { if( OT_FLAG( &peer ) & PEER_FLAG_STOPPED ) {
remove_peer_from_torrent( hash, &peer ); remove_peer_from_torrent( hash, &peer );
reply_size = sprintf( static_scratch + SUCCESS_HTTP_HEADER_LENGTH, "d8:completei0e10:incompletei0e8:intervali%ie5:peers0:e", OT_CLIENT_REQUEST_INTERVAL_RANDOM ); reply_size = sprintf( static_scratch + SUCCESS_HTTP_HEADER_LENGTH, "d8:completei0e10:incompletei0e8:intervali%ie5:peers0:e", OT_CLIENT_REQUEST_INTERVAL_RANDOM );
} else { } else {
torrent = add_peer_to_torrent( hash, &peer ); torrent = add_peer_to_torrent( hash, &peer );
if( !torrent ) { if( !torrent || ( reply_size = return_peers_for_torrent( torrent, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_scratch ) ) <= 0 ) HTTPERROR_500;
e500:
return httperror(s,h,"500 Internal Server Error","A server error has occured. Please retry later.");
}
if( ( reply_size = return_peers_for_torrent( torrent, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_scratch ) ) <= 0 )
goto e500;
} }
ot_overall_successfulannounces++; ot_overall_successfulannounces++;
break; break;
case 11: case 11:
if( byte_diff(data,11,"mrtg_scrape")) if( byte_diff(data,11,"mrtg_scrape")) HTTPERROR_404;
goto e404;
{ t = time( NULL ) - ot_start_time;
time_t seconds_elapsed = time( NULL ) - ot_start_time; reply_size = sprintf( static_scratch + SUCCESS_HTTP_HEADER_LENGTH,
reply_size = sprintf( static_scratch + SUCCESS_HTTP_HEADER_LENGTH, "%i\n%i\nUp: %i seconds (%i hours)\nPretuned by german engineers, currently handling %i connections per second.",
"%i\n%i\nUp: %i seconds (%i hours)\nPretuned by german engineers, currently handling %i connections per second.", ot_overall_connections, ot_overall_successfulannounces, (int)t, (int)(t / 3600), (int)ot_overall_connections / ( (int)t ? (int)t : 1 ) );
ot_overall_connections, ot_overall_successfulannounces, (int)seconds_elapsed,
(int)(seconds_elapsed / 3600), (int)ot_overall_connections / ( (int)seconds_elapsed ? (int)seconds_elapsed : 1 ) );
}
break; break;
default: /* neither *scrape nor announce */ default: /* neither *scrape nor announce */
e404: HTTPERROR_404;
return httperror( s, h, "404 Not Found", "No such file or directory." );
} }
if( reply_size ) { if( reply_size <= 0 ) HTTPERROR_500;
/* This one is rather ugly, so I take you step by step through it.
1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to /* This one is rather ugly, so I take you step by step through it.
write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our static buffer, which is enough for the static string
plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for it expansion and calculate
the space NOT needed to expand in reply_off
*/
size_t reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( static_scratch, 0, "%zd", reply_size );
/* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to
packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */ write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our static buffer, which is enough for the static string
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 ); plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for it expansion and calculate
the space NOT needed to expand in reply_off
*/
size_t reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( static_scratch, 0, "%zd", reply_size );
/* 3. Finally we join both blocks neatly */ /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete
static_scratch[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n'; packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */
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 );
senddata( s, h, static_scratch + reply_off, reply_size ); /* 3. Finally we join both blocks neatly */
} else { static_scratch[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n';
if( h )
array_reset( &h->request ); senddata( s, h, static_scratch + reply_off, reply_size );
#ifdef _DEBUG_FDS
if( !fd_debug_space[s] ) fprintf( stderr, "close on non-open fd\n" );
fd_debug_space[s] = 0;
#endif
free( h ); io_close( s );
}
} }
void graceful( int s ) { static void graceful( int s ) {
if( s == SIGINT ) { if( s == SIGINT ) {
signal( SIGINT, SIG_IGN); signal( SIGINT, SIG_IGN);
deinit_logic(); deinit_logic();
@ -408,16 +413,7 @@ void graceful( int s ) {
} }
} }
#ifdef _DEBUG_FDS static void usage( char *name ) {
void count_fds( int s ) {
int i, count = 0;
for( i=0; i<sizeof(fd_debug_space); ++i )
if( fd_debug_space[i] ) ++count;
fprintf( stderr, "Open fds here: %i\n", count );
}
#endif
void usage( char *name ) {
fprintf( stderr, "Usage: %s [-i serverip] [-p serverport] [-d serverdirectory]" fprintf( stderr, "Usage: %s [-i serverip] [-p serverport] [-d serverdirectory]"
#ifdef WANT_CLOSED_TRACKER #ifdef WANT_CLOSED_TRACKER
" [-oc]" " [-oc]"
@ -428,7 +424,7 @@ void usage( char *name ) {
"\n", name ); "\n", name );
} }
void help( char *name ) { static void help( char *name ) {
usage( name ); usage( name );
fprintf( stderr, "\t-i serverip\tspecify ip to bind to (default: *)\n" fprintf( stderr, "\t-i serverip\tspecify ip to bind to (default: *)\n"
"\t-p serverport\tspecify port to bind to (default: 6969)\n" "\t-p serverport\tspecify port to bind to (default: 6969)\n"
@ -453,19 +449,15 @@ void help( char *name ) {
); );
} }
void handle_read( int64 clientsocket ) { static void handle_read( const int64 clientsocket ) {
struct http_data* h = io_getcookie( clientsocket ); struct http_data* h = io_getcookie( clientsocket );
int l = io_tryread( clientsocket, static_scratch, sizeof static_scratch ); size_t l;
if( l <= 0 ) { if( ( l = io_tryread( clientsocket, static_scratch, sizeof static_scratch ) ) <= 0 ) {
if( h ) { if( h ) {
array_reset( &h->request ); array_reset( &h->request );
free( h ); free( h );
} }
#ifdef _DEBUG_FDS
if( !fd_debug_space[clientsocket] ) fprintf( stderr, "close on non-open fd\n" );
fd_debug_space[clientsocket] = 0;
#endif
io_close( clientsocket ); io_close( clientsocket );
return; return;
} }
@ -480,26 +472,22 @@ void handle_read( int64 clientsocket ) {
httperror( clientsocket, h, "500 Server Error", "Request too long."); httperror( clientsocket, h, "500 Server Error", "Request too long.");
else if( array_bytes( &h->request ) > 8192 ) else if( array_bytes( &h->request ) > 8192 )
httperror( clientsocket, h, "500 request too long", "You sent too much headers"); httperror( clientsocket, h, "500 request too long", "You sent too much headers");
else if( ( l = header_complete( h ) ) ) else if( ( l = httpheader_complete( h ) ) )
httpresponse( clientsocket, h); httpresponse( clientsocket, h);
} }
void handle_write( int64 clientsocket ) { static void handle_write( const int64 clientsocket ) {
struct http_data* h=io_getcookie( clientsocket ); struct http_data* h=io_getcookie( clientsocket );
if( !h ) return; if( !h ) return;
if( iob_send( clientsocket, &h->batch ) <= 0 ) { if( iob_send( clientsocket, &h->batch ) <= 0 ) {
iob_reset( &h->batch ); iob_reset( &h->batch );
#ifdef _DEBUG_FDS
if( !fd_debug_space[clientsocket] ) fprintf( stderr, "close on non-open fd\n" );
fd_debug_space[clientsocket] = 0;
#endif
io_close( clientsocket ); io_close( clientsocket );
free( h ); free( h );
} }
} }
void handle_accept( int64 serversocket ) { static void handle_accept( const int64 serversocket ) {
struct http_data* h; struct http_data *h;
unsigned char ip[4]; unsigned char ip[4];
uint16 port; uint16 port;
tai6464 t; tai6464 t;
@ -513,11 +501,6 @@ void handle_accept( int64 serversocket ) {
continue; continue;
} }
#ifdef _DEBUG_FDS
if( fd_debug_space[i] ) fprintf( stderr, "double use of fd: %i\n", (int)i );
fd_debug_space[i] = 1;
#endif
io_wantread( i ); io_wantread( i );
byte_zero(h,sizeof(struct http_data)); byte_zero(h,sizeof(struct http_data));
@ -531,13 +514,9 @@ void handle_accept( int64 serversocket ) {
if( errno==EAGAIN ) if( errno==EAGAIN )
io_eagain( serversocket ); io_eagain( serversocket );
/*
else
carp( "socket_accept4" );
*/
} }
void handle_timeouted( ) { static void handle_timeouted( void ) {
int64 i; int64 i;
while( ( i = io_timeouted() ) != -1 ) { while( ( i = io_timeouted() ) != -1 ) {
struct http_data* h=io_getcookie( i ); struct http_data* h=io_getcookie( i );
@ -545,15 +524,11 @@ void handle_timeouted( ) {
array_reset( &h->request ); array_reset( &h->request );
free( h ); free( h );
} }
#ifdef _DEBUG_FDS
if( !fd_debug_space[i] ) fprintf( stderr, "close on non-open fd\n" );
fd_debug_space[i] = 0;
#endif
io_close(i); io_close(i);
} }
} }
void server_mainloop( int64 serversocket ) { static void server_mainloop( const int64 serversocket ) {
tai6464 t, next_timeout_check; tai6464 t, next_timeout_check;
io_wantread( serversocket ); io_wantread( serversocket );
@ -626,9 +601,6 @@ int main( int argc, char **argv ) {
signal( SIGPIPE, SIG_IGN ); signal( SIGPIPE, SIG_IGN );
signal( SIGINT, graceful ); signal( SIGINT, graceful );
#ifdef _DEBUG_FDS
signal( SIGINFO, count_fds );
#endif
if( init_logic( serverdir ) == -1 ) if( init_logic( serverdir ) == -1 )
panic( "Logic not started" ); panic( "Logic not started" );

77
trackerlogic.c

@ -64,7 +64,7 @@ static void *binary_search( const void * const key, const void * base, const siz
*/ */
char ths[2+2*20]="-";char*to_hex(ot_byte*s){char*m="0123456789ABCDEF";char*e=ths+41;char*t=ths+1;while(t<e){*t++=m[*s>>4];*t++=m[*s++&15];}*t=0;return ths+1;} char ths[2+2*20]="-";char*to_hex(ot_byte*s){char*m="0123456789ABCDEF";char*e=ths+41;char*t=ths+1;while(t<e){*t++=m[*s>>4];*t++=m[*s++&15];}*t=0;return ths+1;}
static void *vector_find_or_insert( ot_vector *vector, void *key, size_t member_size, int compare_size, int *exactmatch ) { static void *vector_find_or_insert( ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch ) {
ot_byte *match = binary_search( key, vector->data, vector->size, member_size, compare_size, exactmatch ); ot_byte *match = binary_search( key, vector->data, vector->size, member_size, compare_size, exactmatch );
if( *exactmatch ) return match; if( *exactmatch ) return match;
@ -104,7 +104,7 @@ static int vector_remove_peer( ot_vector *vector, ot_peer *peer ) {
} }
static void free_peerlist( ot_peerlist *peer_list ) { static void free_peerlist( ot_peerlist *peer_list ) {
int i; size_t i;
for( i=0; i<OT_POOLS_COUNT; ++i ) for( i=0; i<OT_POOLS_COUNT; ++i )
if( peer_list->peers[i].data ) if( peer_list->peers[i].data )
free( peer_list->peers[i].data ); free( peer_list->peers[i].data );
@ -227,9 +227,9 @@ ot_torrent *add_peer_to_torrent( ot_hash *hash, ot_peer *peer ) {
* RANDOM may return huge values * RANDOM may return huge values
* does not yet check not to return self * does not yet check not to return self
*/ */
size_t return_peers_for_torrent( ot_torrent *torrent, unsigned int amount, char *reply ) { size_t return_peers_for_torrent( ot_torrent *torrent, size_t amount, char *reply ) {
char *r = reply; char *r = reply;
unsigned int peer_count, seed_count, index; size_t peer_count, seed_count, index;
#ifdef WANT_CLOSED_TRACKER #ifdef WANT_CLOSED_TRACKER
if( torrent == OT_TORRENT_NOT_ON_WHITELIST ) { if( torrent == OT_TORRENT_NOT_ON_WHITELIST ) {
@ -247,13 +247,13 @@ size_t return_peers_for_torrent( ot_torrent *torrent, unsigned int amount, char
} }
#endif #endif
for( peer_count=seed_count=index=0; index<OT_POOLS_COUNT; ++index) { for( peer_count = seed_count = index = 0; index < OT_POOLS_COUNT; ++index ) {
peer_count += torrent->peer_list->peers[index].size; peer_count += torrent->peer_list->peers[index].size;
seed_count += torrent->peer_list->seed_count[index]; seed_count += torrent->peer_list->seed_count[index];
} }
if( peer_count < amount ) amount = peer_count; if( peer_count < amount ) amount = peer_count;
r += sprintf( r, "d8:completei%ie10:incompletei%ie8:intervali%ie5:peers%i:", seed_count, peer_count-seed_count, OT_CLIENT_REQUEST_INTERVAL_RANDOM, 6*amount ); r += sprintf( r, "d8:completei%zde10:incompletei%zde8:intervali%ie5:peers%zd:", seed_count, peer_count-seed_count, OT_CLIENT_REQUEST_INTERVAL_RANDOM, 6*amount );
if( amount ) { if( amount ) {
unsigned int pool_offset, pool_index = 0;; unsigned int pool_offset, pool_index = 0;;
unsigned int shifted_pc = peer_count; unsigned int shifted_pc = peer_count;
@ -292,8 +292,9 @@ size_t return_peers_for_torrent( ot_torrent *torrent, unsigned int amount, char
/* Fetch full scrape info for all torrents */ /* Fetch full scrape info for all torrents */
size_t return_fullscrape_for_tracker( char **reply ) { size_t return_fullscrape_for_tracker( char **reply ) {
int torrent_count = 0, i, j, k; size_t torrent_count = 0, j;
char* r; int i, k;
char *r;
time_t time_now = NOW; time_t time_now = NOW;
for( i=0; i<256; ++i ) { for( i=0; i<256; ++i ) {
@ -301,16 +302,15 @@ size_t return_fullscrape_for_tracker( char **reply ) {
torrent_count += torrents_list->size; torrent_count += torrents_list->size;
} }
r = *reply = malloc( 128*torrent_count ); if( !( r = *reply = malloc( 128*torrent_count ) ) ) return 0;
if( !reply ) return 0;
memmove( r, "d5:filesd", 9 ); r += 9; memmove( r, "d5:filesd", 9 ); r += 9;
for( i=0; i<256; ++i ) { for( i=0; i<256; ++i ) {
ot_vector *torrents_list = &all_torrents[i]; ot_vector *torrents_list = &all_torrents[i];
for( j=0; j<torrents_list->size; ++j ) { for( j=0; j<torrents_list->size; ++j ) {
ot_peerlist *peer_list = ( ((ot_torrent*)(torrents_list->data))[j] ).peer_list; ot_peerlist *peer_list = ( ((ot_torrent*)(torrents_list->data))[j] ).peer_list;
ot_hash *hash =&( ((ot_torrent*)(torrents_list->data))[j] ).hash; ot_hash *hash =&( ((ot_torrent*)(torrents_list->data))[j] ).hash;
int peers = 0, seeds = 0; size_t peers = 0, seeds = 0;
clean_peerlist( time_now, peer_list ); clean_peerlist( time_now, peer_list );
for( k=0; k<OT_POOLS_COUNT; ++k ) { for( k=0; k<OT_POOLS_COUNT; ++k ) {
peers += peer_list->peers[k].size; peers += peer_list->peers[k].size;
@ -318,7 +318,7 @@ size_t return_fullscrape_for_tracker( char **reply ) {
} }
memmove( r, "20:", 3 ); r+=3; memmove( r, "20:", 3 ); r+=3;
memmove( r, hash, 20 ); r+=20; memmove( r, hash, 20 ); r+=20;
r += sprintf( r, "d8:completei%de10:downloadedi%de10:incompletei%de", seeds, peer_list->downloaded, peers-seeds ); r += sprintf( r, "d8:completei%zde10:downloadedi%zde10:incompletei%zde", seeds, peer_list->downloaded, peers-seeds );
} }
} }
@ -329,7 +329,8 @@ size_t return_fullscrape_for_tracker( char **reply ) {
/* Fetches scrape info for a specific torrent */ /* Fetches scrape info for a specific torrent */
size_t return_scrape_for_torrent( ot_hash *hash, char *reply ) { size_t return_scrape_for_torrent( ot_hash *hash, char *reply ) {
char *r = reply; char *r = reply;
int exactmatch, peers = 0, seeds = 0, i; int exactmatch, i;
size_t peers = 0, seeds = 0;
ot_vector *torrents_list = &all_torrents[*hash[0]]; ot_vector *torrents_list = &all_torrents[*hash[0]];
ot_torrent *torrent = binary_search( hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch ); ot_torrent *torrent = binary_search( hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
@ -342,20 +343,45 @@ size_t return_scrape_for_torrent( ot_hash *hash, char *reply ) {
} }
memmove( r, "d5:filesd20:", 12 ); memmove( r+12, hash, 20 ); memmove( r, "d5:filesd20:", 12 ); memmove( r+12, hash, 20 );
r += sprintf( r+32, "d8:completei%de10:downloadedi%de10:incompletei%deeee", seeds, torrent->peer_list->downloaded, peers-seeds ) + 32; r += sprintf( r+32, "d8:completei%zde10:downloadedi%zde10:incompletei%zdeeee", seeds, torrent->peer_list->downloaded, peers-seeds ) + 32;
return r - reply; return r - reply;
} }
size_t return_sync_for_torrent( ot_hash *hash, char **reply ) {
int exactmatch;
size_t peers = 0;
char *r;
ot_vector *torrents_list = &all_torrents[*hash[0]];
ot_torrent *torrent = binary_search( hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
if( exactmatch ) {
clean_peerlist( NOW, torrent->peer_list );
peers = torrent->peer_list->peers[0].size;
}
if( !( r = *reply = malloc( 10 + peers * sizeof( ot_peer ) ) ) ) return 0;
memmove( r, "d4:sync", 7 );
r += 7;
r += sprintf( r, "%zd:", peers * sizeof( ot_peer ) );
if( peers ) {
memmove( r, torrent->peer_list->peers[0].data, peers * sizeof( ot_peer ) );
r += peers * sizeof( ot_peer );
}
*r++ = 'e';
return r - *reply;
}
typedef struct { int val; ot_torrent * torrent; } ot_record; typedef struct { int val; ot_torrent * torrent; } ot_record;
/* Fetches stats from tracker */ /* Fetches stats from tracker */
size_t return_stats_for_tracker( char *reply, int mode ) { size_t return_stats_for_tracker( char *reply, int mode ) {
time_t time_now = NOW; time_t time_now = NOW;
int torrent_count = 0, peer_count = 0, seed_count = 0; size_t torrent_count = 0, peer_count = 0, seed_count = 0, j;
ot_record top5s[5], top5c[5]; ot_record top5s[5], top5c[5];
char *r = reply; char *r = reply;
int i,j,k; int i,k;
byte_zero( top5s, sizeof( top5s ) ); byte_zero( top5s, sizeof( top5s ) );
byte_zero( top5c, sizeof( top5c ) ); byte_zero( top5c, sizeof( top5c ) );
@ -365,7 +391,7 @@ size_t return_stats_for_tracker( char *reply, int mode ) {
torrent_count += torrents_list->size; torrent_count += torrents_list->size;
for( j=0; j<torrents_list->size; ++j ) { for( j=0; j<torrents_list->size; ++j ) {
ot_peerlist *peer_list = ( ((ot_torrent*)(torrents_list->data))[j] ).peer_list; ot_peerlist *peer_list = ( ((ot_torrent*)(torrents_list->data))[j] ).peer_list;
int local_peers = 0, local_seeds = 0; size_t local_peers = 0, local_seeds = 0;
clean_peerlist( time_now, peer_list ); clean_peerlist( time_now, peer_list );
for( k=0; k<OT_POOLS_COUNT; ++k ) { for( k=0; k<OT_POOLS_COUNT; ++k ) {
local_peers += peer_list->peers[k].size; local_peers += peer_list->peers[k].size;
@ -399,7 +425,7 @@ size_t return_stats_for_tracker( char *reply, int mode ) {
if( top5s[idx].torrent ) if( top5s[idx].torrent )
r += sprintf( r, "\t%i\t%s\n", top5s[idx].val, to_hex(top5s[idx].torrent->hash) ); r += sprintf( r, "\t%i\t%s\n", top5s[idx].val, to_hex(top5s[idx].torrent->hash) );
} else { } else {
r += sprintf( r, "%i\n%i\nopentracker serving %i torrents\nSomething else.", peer_count, seed_count, torrent_count ); r += sprintf( r, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", peer_count, seed_count, torrent_count );
} }
return r - reply; return r - reply;
@ -429,7 +455,7 @@ void remove_peer_from_torrent( ot_hash *hash, ot_peer *peer ) {
} }
} }
int init_logic( char *serverdir ) { int init_logic( const char * const serverdir ) {
if( serverdir && chdir( serverdir ) ) { if( serverdir && chdir( serverdir ) ) {
fprintf( stderr, "Could not chdir() to %s\n", serverdir ); fprintf( stderr, "Could not chdir() to %s\n", serverdir );
return -1; return -1;
@ -443,8 +469,9 @@ int init_logic( char *serverdir ) {
return 0; return 0;
} }
void deinit_logic( ) { void deinit_logic( void ) {
int i, j; int i;
size_t j;
/* Free all torrents... */ /* Free all torrents... */
for(i=0; i<256; ++i ) { for(i=0; i<256; ++i ) {

9
trackerlogic.h

@ -64,7 +64,7 @@ static const ot_byte PEER_FLAG_STOPPED = 0x20;
typedef struct { typedef struct {
ot_time base; ot_time base;
size_t seed_count[ OT_POOLS_COUNT ]; size_t seed_count[ OT_POOLS_COUNT ];
unsigned int downloaded; size_t downloaded;
ot_vector peers[ OT_POOLS_COUNT ]; ot_vector peers[ OT_POOLS_COUNT ];
} ot_peerlist; } ot_peerlist;
@ -77,8 +77,8 @@ typedef struct {
Exported functions Exported functions
*/ */
int init_logic( char *serverdir ); int init_logic( const char * const serverdir );
void deinit_logic( ); void deinit_logic( void );
#ifdef WANT_CLOSED_TRACKER #ifdef WANT_CLOSED_TRACKER
extern int g_closedtracker; extern int g_closedtracker;
@ -90,9 +90,10 @@ extern int g_check_blacklist;
enum { STATS_MRTG, STATS_TOP5 }; enum { STATS_MRTG, STATS_TOP5 };
ot_torrent *add_peer_to_torrent( ot_hash *hash, ot_peer *peer ); ot_torrent *add_peer_to_torrent( ot_hash *hash, ot_peer *peer );
size_t return_peers_for_torrent( ot_torrent *torrent, unsigned int amount, char *reply ); size_t return_peers_for_torrent( ot_torrent *torrent, size_t amount, char *reply );
size_t return_fullscrape_for_tracker( char **reply ); size_t return_fullscrape_for_tracker( char **reply );
size_t return_scrape_for_torrent( ot_hash *hash, char *reply ); size_t return_scrape_for_torrent( ot_hash *hash, char *reply );
size_t return_sync_for_torrent( ot_hash *hash, char **reply );
size_t return_stats_for_tracker( char *reply, int mode ); size_t return_stats_for_tracker( char *reply, int mode );
void remove_peer_from_torrent( ot_hash *hash, ot_peer *peer ); void remove_peer_from_torrent( ot_hash *hash, ot_peer *peer );

Loading…
Cancel
Save