mirror of git://erdgeist.org/opentracker
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
747 lines
22 KiB
C
747 lines
22 KiB
C
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
|
|
It is considered beerware. Prost. Skol. Cheers or whatever.
|
|
Some of the stuff below is stolen from Fefes example libowfat httpd.
|
|
|
|
$Id$ */
|
|
|
|
/* System */
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <pwd.h>
|
|
#include <ctype.h>
|
|
#include <pthread.h>
|
|
#ifdef WANT_SYSLOGS
|
|
#include <syslog.h>
|
|
#endif
|
|
|
|
/* Libowfat */
|
|
#include "socket.h"
|
|
#include "io.h"
|
|
#include "iob.h"
|
|
#include "byte.h"
|
|
#include "scan.h"
|
|
#include "ip6.h"
|
|
|
|
/* Opentracker */
|
|
#include "trackerlogic.h"
|
|
#include "ot_mutex.h"
|
|
#include "ot_http.h"
|
|
#include "ot_udp.h"
|
|
#include "ot_accesslist.h"
|
|
#include "ot_stats.h"
|
|
#include "ot_livesync.h"
|
|
|
|
/* Globals */
|
|
time_t g_now_seconds;
|
|
char * g_redirecturl;
|
|
uint32_t g_tracker_id;
|
|
volatile int g_opentracker_running = 1;
|
|
int g_self_pipe[2];
|
|
|
|
static char * g_serverdir;
|
|
static char * g_serveruser;
|
|
static unsigned int g_udp_workers;
|
|
|
|
static void panic( const char *routine ) __attribute__ ((noreturn));
|
|
static void panic( const char *routine ) {
|
|
fprintf( stderr, "%s: %s\n", routine, strerror(errno) );
|
|
exit( 111 );
|
|
}
|
|
|
|
static void signal_handler( int s ) {
|
|
if( s == SIGINT ) {
|
|
/* Any new interrupt signal quits the application */
|
|
signal( SIGINT, SIG_DFL);
|
|
|
|
/* Tell all other threads to not acquire any new lock on a bucket
|
|
but cancel their operations and return */
|
|
g_opentracker_running = 0;
|
|
|
|
trackerlogic_deinit();
|
|
|
|
#ifdef WANT_SYSLOGS
|
|
closelog();
|
|
#endif
|
|
|
|
exit( 0 );
|
|
}
|
|
}
|
|
|
|
static void defaul_signal_handlers( void ) {
|
|
sigset_t signal_mask;
|
|
sigemptyset(&signal_mask);
|
|
sigaddset (&signal_mask, SIGPIPE);
|
|
sigaddset (&signal_mask, SIGHUP);
|
|
sigaddset (&signal_mask, SIGINT);
|
|
sigaddset (&signal_mask, SIGALRM);
|
|
pthread_sigmask (SIG_BLOCK, &signal_mask, NULL);
|
|
}
|
|
|
|
static void install_signal_handlers( void ) {
|
|
struct sigaction sa;
|
|
sigset_t signal_mask;
|
|
sigemptyset(&signal_mask);
|
|
|
|
sa.sa_handler = signal_handler;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESTART;
|
|
if ((sigaction(SIGINT, &sa, NULL) == -1) || (sigaction(SIGALRM, &sa, NULL) == -1) )
|
|
panic( "install_signal_handlers" );
|
|
|
|
sigaddset (&signal_mask, SIGINT);
|
|
pthread_sigmask (SIG_UNBLOCK, &signal_mask, NULL);
|
|
}
|
|
|
|
static void usage( char *name ) {
|
|
fprintf( stderr, "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]"
|
|
#ifdef WANT_ACCESSLIST_BLACK
|
|
" [-b blacklistfile]"
|
|
#elif defined ( WANT_ACCESSLIST_WHITE )
|
|
" [-w whitelistfile]"
|
|
#endif
|
|
"\n", name );
|
|
}
|
|
|
|
#define HELPLINE(opt,desc) fprintf(stderr, "\t%-10s%s\n",opt,desc)
|
|
static void help( char *name ) {
|
|
usage( name );
|
|
|
|
HELPLINE("-f config","include and execute the config file");
|
|
HELPLINE("-i ip","specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)");
|
|
HELPLINE("-p port","do bind to tcp port (default: 6969, you may specify more than one)");
|
|
HELPLINE("-P port","do bind to udp port (default: 6969, you may specify more than one)");
|
|
HELPLINE("-r redirecturl","specify url where / should be redirected to (default none)");
|
|
HELPLINE("-d dir","specify directory to try to chroot to (default: \".\")");
|
|
HELPLINE("-u user","specify user under whose privileges opentracker should run (default: \"nobody\")");
|
|
HELPLINE("-A ip[/bits]","bless an ip address or net as admin address (e.g. to allow syncs from this address)");
|
|
#ifdef WANT_ACCESSLIST_BLACK
|
|
HELPLINE("-b file","specify blacklist file.");
|
|
#elif defined( WANT_ACCESSLIST_WHITE )
|
|
HELPLINE("-w file","specify whitelist file.");
|
|
#endif
|
|
|
|
HELPLINE("\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80");
|
|
HELPLINE(" Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.");
|
|
HELPLINE(" If no port is bound from config file or command line, the last address given");
|
|
HELPLINE(" (or ::1 if none is set) will be used on port 6969.");
|
|
}
|
|
#undef HELPLINE
|
|
|
|
static ssize_t header_complete( char * request, ssize_t byte_count ) {
|
|
ssize_t i = 0, state = 0;
|
|
|
|
for( i=1; i < byte_count; i+=2 )
|
|
if( request[i] <= 13 ) {
|
|
i--;
|
|
for( state = 0 ; i < byte_count; ++i ) {
|
|
char c = request[i];
|
|
if( c == '\r' || c == '\n' )
|
|
state = ( state >> 2 ) | ( ( c << 6 ) & 0xc0 );
|
|
else
|
|
break;
|
|
if( state >= 0xa0 || state == 0x99 ) return i + 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void handle_dead( const int64 sock ) {
|
|
struct http_data* cookie=io_getcookie( sock );
|
|
if( cookie ) {
|
|
size_t i;
|
|
for ( i = 0; i < cookie->batches; ++i)
|
|
iob_reset( cookie->batch + i );
|
|
free( cookie->batch );
|
|
array_reset( &cookie->request );
|
|
if( cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) )
|
|
mutex_workqueue_canceltask( sock );
|
|
free( cookie );
|
|
}
|
|
io_close( sock );
|
|
}
|
|
|
|
static void handle_read( const int64 sock, struct ot_workstruct *ws ) {
|
|
struct http_data* cookie = io_getcookie( sock );
|
|
ssize_t byte_count = io_tryread( sock, ws->inbuf, G_INBUF_SIZE );
|
|
|
|
if( byte_count == 0 || byte_count == -3 ) {
|
|
handle_dead( sock );
|
|
return;
|
|
}
|
|
|
|
if( byte_count == -1)
|
|
return;
|
|
|
|
/* If we get the whole request in one packet, handle it without copying */
|
|
if( !array_start( &cookie->request ) ) {
|
|
if( ( ws->header_size = header_complete( ws->inbuf, byte_count ) ) ) {
|
|
ws->request = ws->inbuf;
|
|
ws->request_size = byte_count;
|
|
http_handle_request( sock, ws );
|
|
} else
|
|
array_catb( &cookie->request, ws->inbuf, (size_t)byte_count );
|
|
return;
|
|
}
|
|
|
|
array_catb( &cookie->request, ws->inbuf, byte_count );
|
|
if( array_failed( &cookie->request ) || array_bytes( &cookie->request ) > 8192 ) {
|
|
http_issue_error( sock, ws, CODE_HTTPERROR_500 );
|
|
return;
|
|
}
|
|
|
|
while( ( ws->header_size = header_complete( array_start( &cookie->request ), array_bytes( &cookie->request ) ) ) ) {
|
|
ws->request = array_start( &cookie->request );
|
|
ws->request_size = array_bytes( &cookie->request );
|
|
http_handle_request( sock, ws );
|
|
#ifdef WANT_KEEPALIVE
|
|
if( !ws->keep_alive )
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void handle_write( const int64 sock ) {
|
|
struct http_data* cookie=io_getcookie( sock );
|
|
size_t i;
|
|
int chunked = 0;
|
|
|
|
/* Look for the first io_batch still containing bytes to write */
|
|
if( cookie ) {
|
|
if( cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER )
|
|
chunked = 1;
|
|
|
|
for( i = 0; i < cookie->batches; ++i ) {
|
|
fprintf(stderr, "handle_write inspects batch %zu of %zu (bytes left: %llu)\n", i, cookie->batches, cookie->batch[i].bytesleft);
|
|
if( cookie->batch[i].bytesleft ) {
|
|
int64 res = iob_send( sock, cookie->batch + i );
|
|
|
|
fprintf(stderr, "handle_write yields res %lld when trying to iob_send\n", res);
|
|
if( res == -3 ) {
|
|
handle_dead( sock );
|
|
return;
|
|
}
|
|
|
|
if( !cookie->batch[i].bytesleft )
|
|
continue;
|
|
|
|
if( res == -1 || res > 0 || i < cookie->batches - 1 )
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* In a chunked transfer after all batches accumulated have been sent, wait for the next one */
|
|
if( chunked ) {
|
|
fprintf( stderr, "handle_write is STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER => dont want write on sock %lld\n", sock);
|
|
io_dontwantwrite( sock );
|
|
} else {
|
|
fprintf( stderr, "handle_write is STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER => handle dead on sock %lld\n", sock);
|
|
handle_dead( sock );
|
|
}
|
|
}
|
|
|
|
static void handle_accept( const int64 serversocket ) {
|
|
struct http_data *cookie;
|
|
int64 sock;
|
|
ot_ip6 ip;
|
|
uint16 port;
|
|
tai6464 t;
|
|
|
|
while( ( sock = socket_accept6( serversocket, ip, &port, NULL ) ) != -1 ) {
|
|
|
|
/* Put fd into a non-blocking mode */
|
|
io_nonblock( sock );
|
|
|
|
if( !io_fd( sock ) ||
|
|
!( cookie = (struct http_data*)malloc( sizeof(struct http_data) ) ) ) {
|
|
io_close( sock );
|
|
continue;
|
|
}
|
|
memset(cookie, 0, sizeof( struct http_data ) );
|
|
memcpy(cookie->ip,ip,sizeof(ot_ip6));
|
|
|
|
io_setcookie( sock, cookie );
|
|
io_wantread( sock );
|
|
|
|
stats_issue_event( EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
|
|
|
|
/* That breaks taia encapsulation. But there is no way to take system
|
|
time this often in FreeBSD and libowfat does not allow to set unix time */
|
|
taia_uint( &t, 0 ); /* Clear t */
|
|
tai_unix( &(t.sec), (g_now_seconds + OT_CLIENT_TIMEOUT) );
|
|
io_timeout( sock, t );
|
|
}
|
|
io_eagain(serversocket);
|
|
}
|
|
|
|
static void * server_mainloop( void * args ) {
|
|
struct ot_workstruct ws;
|
|
time_t next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL;
|
|
struct iovec *iovector;
|
|
int iovec_entries, is_partial;
|
|
|
|
(void)args;
|
|
|
|
/* Initialize our "thread local storage" */
|
|
ws.inbuf = malloc( G_INBUF_SIZE );
|
|
ws.outbuf = malloc( G_OUTBUF_SIZE );
|
|
#ifdef _DEBUG_HTTPERROR
|
|
ws.debugbuf= malloc( G_DEBUGBUF_SIZE );
|
|
#endif
|
|
|
|
if( !ws.inbuf || !ws.outbuf )
|
|
panic( "Initializing worker failed" );
|
|
|
|
#ifdef WANT_ARC4RANDOM
|
|
arc4random_buf(&ws.rand48_state[0], 3 * sizeof(uint16_t));
|
|
#else
|
|
ws.rand48_state[0] = (uint16_t)random();
|
|
ws.rand48_state[1] = (uint16_t)random();
|
|
ws.rand48_state[2] = (uint16_t)random();
|
|
#endif
|
|
|
|
for( ; ; ) {
|
|
int64 sock;
|
|
|
|
io_wait();
|
|
|
|
while( ( sock = io_canread( ) ) != -1 ) {
|
|
const void *cookie = io_getcookie( sock );
|
|
if( (intptr_t)cookie == FLAG_TCP )
|
|
handle_accept( sock );
|
|
else if( (intptr_t)cookie == FLAG_UDP )
|
|
handle_udp6( sock, &ws );
|
|
else if( (intptr_t)cookie == FLAG_SELFPIPE )
|
|
io_tryread( sock, ws.inbuf, G_INBUF_SIZE );
|
|
else
|
|
handle_read( sock, &ws );
|
|
}
|
|
|
|
while( ( sock = mutex_workqueue_popresult( &iovec_entries, &iovector, &is_partial ) ) != -1 )
|
|
http_sendiovecdata( sock, &ws, iovec_entries, iovector, is_partial );
|
|
|
|
while( ( sock = io_canwrite( ) ) != -1 )
|
|
handle_write( sock );
|
|
|
|
if( g_now_seconds > next_timeout_check ) {
|
|
while( ( sock = io_timeouted() ) != -1 )
|
|
handle_dead( sock );
|
|
next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL;
|
|
}
|
|
|
|
livesync_ticker();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int64_t ot_try_bind( ot_ip6 ip, uint16_t port, PROTO_FLAG proto ) {
|
|
int64 sock = proto == FLAG_TCP ? socket_tcp6( ) : socket_udp6( );
|
|
|
|
#ifdef _DEBUG
|
|
{
|
|
char *protos[] = {"TCP","UDP","UDP mcast"};
|
|
char _debug[512];
|
|
int off = snprintf( _debug, sizeof(_debug), "Binding socket type %s to address [", protos[proto] );
|
|
off += fmt_ip6c( _debug+off, ip);
|
|
snprintf( _debug + off, sizeof(_debug)-off, "]:%d...", port);
|
|
fputs( _debug, stderr );
|
|
}
|
|
#endif
|
|
|
|
if( socket_bind6_reuse( sock, ip, port, 0 ) == -1 )
|
|
panic( "socket_bind6_reuse" );
|
|
|
|
if( ( proto == FLAG_TCP ) && ( socket_listen( sock, SOMAXCONN) == -1 ) )
|
|
panic( "socket_listen" );
|
|
|
|
if( !io_fd( sock ) )
|
|
panic( "io_fd" );
|
|
|
|
io_setcookie( sock, (void*)proto );
|
|
|
|
if( (proto == FLAG_UDP) && g_udp_workers ) {
|
|
io_block( sock );
|
|
udp_init( sock, g_udp_workers );
|
|
} else
|
|
io_wantread( sock );
|
|
|
|
#ifdef _DEBUG
|
|
fputs( " success.\n", stderr);
|
|
#endif
|
|
|
|
return sock;
|
|
}
|
|
|
|
char * set_config_option( char **option, char *value ) {
|
|
#ifdef _DEBUG
|
|
fprintf( stderr, "Setting config option: %s\n", value );
|
|
#endif
|
|
while( isspace(*value) ) ++value;
|
|
free( *option );
|
|
return *option = strdup( value );
|
|
}
|
|
|
|
static int scan_ip6_port( const char *src, ot_ip6 ip, uint16 *port ) {
|
|
const char *s = src;
|
|
int off, bracket = 0;
|
|
while( isspace(*s) ) ++s;
|
|
if( *s == '[' ) ++s, ++bracket; /* for v6 style notation */
|
|
if( !(off = scan_ip6( s, ip ) ) )
|
|
return 0;
|
|
s += off;
|
|
if( bracket && *s == ']' ) ++s;
|
|
if( *s == 0 || isspace(*s)) return s-src;
|
|
if( !ip6_isv4mapped(ip)) {
|
|
if( *s != ':' && *s != '.' ) return 0;
|
|
if( !bracket && *(s) == ':' ) return 0;
|
|
s++;
|
|
} else {
|
|
if( *(s++) != ':' ) return 0;
|
|
}
|
|
if( !(off = scan_ushort (s, port ) ) )
|
|
return 0;
|
|
return off+s-src;
|
|
}
|
|
|
|
static int scan_ip6_net( const char *src, ot_net *net) {
|
|
const char *s = src;
|
|
int off;
|
|
while( isspace(*s) ) ++s;
|
|
if( !(off = scan_ip6( s, net->address ) ) )
|
|
return 0;
|
|
s += off;
|
|
if(*s!='/')
|
|
net->bits = 128;
|
|
else {
|
|
s++;
|
|
if( !(off = scan_int (s, &net->bits ) ) )
|
|
return 0;
|
|
if( ip6_isv4mapped(net->address))
|
|
net->bits += 96;
|
|
if(net->bits > 128)
|
|
return 0;
|
|
s += off;
|
|
}
|
|
return off+s-src;
|
|
}
|
|
|
|
int parse_configfile( char * config_filename ) {
|
|
FILE * accesslist_filehandle;
|
|
char inbuf[512];
|
|
ot_ip6 tmpip;
|
|
#if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE)
|
|
ot_net tmpnet;
|
|
#endif
|
|
int bound = 0;
|
|
|
|
accesslist_filehandle = fopen( config_filename, "r" );
|
|
|
|
if( accesslist_filehandle == NULL ) {
|
|
fprintf( stderr, "Warning: Can't open config file: %s.", config_filename );
|
|
return 0;
|
|
}
|
|
|
|
while( fgets( inbuf, sizeof(inbuf), accesslist_filehandle ) ) {
|
|
char *p = inbuf;
|
|
size_t strl;
|
|
|
|
/* Skip white spaces */
|
|
while(isspace(*p)) ++p;
|
|
|
|
/* Ignore comments and empty lines */
|
|
if((*p=='#')||(*p=='\n')||(*p==0)) continue;
|
|
|
|
/* consume trailing new lines and spaces */
|
|
strl = strlen(p);
|
|
while( strl && isspace(p[strl-1]))
|
|
p[--strl] = 0;
|
|
|
|
/* Scan for commands */
|
|
if(!byte_diff(p,15,"tracker.rootdir" ) && isspace(p[15])) {
|
|
set_config_option( &g_serverdir, p+16 );
|
|
} else if(!byte_diff(p,12,"tracker.user" ) && isspace(p[12])) {
|
|
set_config_option( &g_serveruser, p+13 );
|
|
} else if(!byte_diff(p,14,"listen.tcp_udp" ) && isspace(p[14])) {
|
|
uint16_t tmpport = 6969;
|
|
if( !scan_ip6_port( p+15, tmpip, &tmpport )) goto parse_error;
|
|
ot_try_bind( tmpip, tmpport, FLAG_TCP ); ++bound;
|
|
ot_try_bind( tmpip, tmpport, FLAG_UDP ); ++bound;
|
|
} else if(!byte_diff(p,10,"listen.tcp" ) && isspace(p[10])) {
|
|
uint16_t tmpport = 6969;
|
|
if( !scan_ip6_port( p+11, tmpip, &tmpport )) goto parse_error;
|
|
ot_try_bind( tmpip, tmpport, FLAG_TCP );
|
|
++bound;
|
|
} else if(!byte_diff(p, 10, "listen.udp" ) && isspace(p[10])) {
|
|
uint16_t tmpport = 6969;
|
|
if( !scan_ip6_port( p+11, tmpip, &tmpport )) goto parse_error;
|
|
ot_try_bind( tmpip, tmpport, FLAG_UDP );
|
|
++bound;
|
|
} else if(!byte_diff(p,18,"listen.udp.workers" ) && isspace(p[18])) {
|
|
char *value = p + 18;
|
|
while( isspace(*value) ) ++value;
|
|
scan_uint( value, &g_udp_workers );
|
|
#ifdef WANT_ACCESSLIST_WHITE
|
|
} else if(!byte_diff(p, 16, "access.whitelist" ) && isspace(p[16])) {
|
|
set_config_option( &g_accesslist_filename, p+17 );
|
|
#elif defined( WANT_ACCESSLIST_BLACK )
|
|
} else if(!byte_diff(p, 16, "access.blacklist" ) && isspace(p[16])) {
|
|
set_config_option( &g_accesslist_filename, p+17 );
|
|
#endif
|
|
#ifdef WANT_DYNAMIC_ACCESSLIST
|
|
} else if(!byte_diff(p, 15, "access.fifo_add" ) && isspace(p[15])) {
|
|
set_config_option( &g_accesslist_pipe_add, p+16 );
|
|
} else if(!byte_diff(p, 18, "access.fifo_delete" ) && isspace(p[18])) {
|
|
set_config_option( &g_accesslist_pipe_delete, p+19 );
|
|
#endif
|
|
#ifdef WANT_RESTRICT_STATS
|
|
} else if(!byte_diff(p, 12, "access.stats" ) && isspace(p[12])) {
|
|
if( !scan_ip6_net( p+13, &tmpnet )) goto parse_error;
|
|
accesslist_bless_net( &tmpnet, OT_PERMISSION_MAY_STAT );
|
|
#endif
|
|
} else if(!byte_diff(p, 17, "access.stats_path" ) && isspace(p[17])) {
|
|
set_config_option( &g_stats_path, p+18 );
|
|
#ifdef WANT_IP_FROM_PROXY
|
|
} else if(!byte_diff(p, 12, "access.proxy" ) && isspace(p[12])) {
|
|
if( !scan_ip6_net( p+13, &tmpnet )) goto parse_error;
|
|
accesslist_bless_net( &tmpnet, OT_PERMISSION_MAY_PROXY );
|
|
#endif
|
|
} else if(!byte_diff(p, 20, "tracker.redirect_url" ) && isspace(p[20])) {
|
|
set_config_option( &g_redirecturl, p+21 );
|
|
#ifdef WANT_SYNC_LIVE
|
|
} else if(!byte_diff(p, 24, "livesync.cluster.node_ip" ) && isspace(p[24])) {
|
|
if( !scan_ip6_net( p+25, &tmpnet )) goto parse_error;
|
|
accesslist_bless_net( &tmpnet, OT_PERMISSION_MAY_LIVESYNC );
|
|
} else if(!byte_diff(p, 23, "livesync.cluster.listen" ) && isspace(p[23])) {
|
|
uint16_t tmpport = LIVESYNC_PORT;
|
|
if( !scan_ip6_port( p+24, tmpip, &tmpport )) goto parse_error;
|
|
livesync_bind_mcast( tmpip, tmpport );
|
|
#endif
|
|
} else
|
|
fprintf( stderr, "Unhandled line in config file: %s\n", inbuf );
|
|
continue;
|
|
parse_error:
|
|
fprintf( stderr, "Parse error in config file: %s\n", inbuf);
|
|
}
|
|
fclose( accesslist_filehandle );
|
|
return bound;
|
|
}
|
|
|
|
void load_state(const char * const state_filename ) {
|
|
FILE * state_filehandle;
|
|
char inbuf[512];
|
|
ot_hash infohash;
|
|
unsigned long long base, downcount;
|
|
int consumed;
|
|
|
|
state_filehandle = fopen( state_filename, "r" );
|
|
|
|
if( state_filehandle == NULL ) {
|
|
fprintf( stderr, "Warning: Can't open config file: %s.", state_filename );
|
|
return;
|
|
}
|
|
|
|
/* We do ignore anything that is not of the form "^[:xdigit:]:\d+:\d+" */
|
|
while( fgets( inbuf, sizeof(inbuf), state_filehandle ) ) {
|
|
int i;
|
|
for( i=0; i<(int)sizeof(ot_hash); ++i ) {
|
|
int eger = 16 * scan_fromhex( inbuf[ 2*i ] ) + scan_fromhex( inbuf[ 1 + 2*i ] );
|
|
if( eger < 0 )
|
|
continue;
|
|
infohash[i] = eger;
|
|
}
|
|
|
|
if( i != (int)sizeof(ot_hash) ) continue;
|
|
i *= 2;
|
|
|
|
if( inbuf[ i++ ] != ':' || !( consumed = scan_ulonglong( inbuf+i, &base ) ) ) continue;
|
|
i += consumed;
|
|
if( inbuf[ i++ ] != ':' || !( consumed = scan_ulonglong( inbuf+i, &downcount ) ) ) continue;
|
|
add_torrent_from_saved_state( infohash, base, downcount );
|
|
}
|
|
|
|
fclose( state_filehandle );
|
|
}
|
|
|
|
int drop_privileges ( const char * const serveruser, const char * const serverdir ) {
|
|
struct passwd *pws = NULL;
|
|
|
|
#ifdef _DEBUG
|
|
if( !geteuid() )
|
|
fprintf( stderr, "Dropping to user %s.\n", serveruser );
|
|
if( serverdir )
|
|
fprintf( stderr, "ch%s'ing to directory %s.\n", geteuid() ? "dir" : "root", serverdir );
|
|
#endif
|
|
|
|
/* Grab pws entry before chrooting */
|
|
pws = getpwnam( serveruser );
|
|
endpwent();
|
|
|
|
if( geteuid() == 0 ) {
|
|
/* Running as root: chroot and drop privileges */
|
|
if( serverdir && chroot( serverdir ) ) {
|
|
fprintf( stderr, "Could not chroot to %s, because: %s\n", serverdir, strerror(errno) );
|
|
return -1;
|
|
}
|
|
|
|
if(chdir("/"))
|
|
panic("chdir() failed after chrooting: ");
|
|
|
|
/* If we can't find server user, revert to nobody's default uid */
|
|
if( !pws ) {
|
|
fprintf( stderr, "Warning: Could not get password entry for %s. Reverting to uid -2.\n", serveruser );
|
|
if (setegid( (gid_t)-2 ) || setgid( (gid_t)-2 ) || setuid( (uid_t)-2 ) || seteuid( (uid_t)-2 ))
|
|
panic("Could not set uid to value -2");
|
|
}
|
|
else {
|
|
if (setegid( pws->pw_gid ) || setgid( pws->pw_gid ) || setuid( pws->pw_uid ) || seteuid( pws->pw_uid ))
|
|
panic("Could not set uid to specified value");
|
|
}
|
|
|
|
if( geteuid() == 0 || getegid() == 0 )
|
|
panic("Still running with root privileges?!");
|
|
}
|
|
else {
|
|
/* Normal user, just chdir() */
|
|
if( serverdir && chdir( serverdir ) ) {
|
|
fprintf( stderr, "Could not chroot to %s, because: %s\n", serverdir, strerror(errno) );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Maintain our copy of the clock. time() on BSDs is very expensive. */
|
|
static void *time_caching_worker(void*args) {
|
|
(void)args;
|
|
while (1) {
|
|
g_now_seconds = time(NULL);
|
|
sleep(5);
|
|
}
|
|
}
|
|
|
|
int main( int argc, char **argv ) {
|
|
ot_ip6 serverip;
|
|
ot_net tmpnet;
|
|
int bound = 0, scanon = 1;
|
|
uint16_t tmpport;
|
|
char * statefile = 0;
|
|
pthread_t thread_id; /* time cacher */
|
|
|
|
memset( serverip, 0, sizeof(ot_ip6) );
|
|
#ifdef WANT_V4_ONLY
|
|
serverip[10]=serverip[11]=-1;
|
|
#endif
|
|
|
|
#ifdef WANT_DEV_RANDOM
|
|
srandomdev();
|
|
#else
|
|
srandom( time(NULL) );
|
|
#endif
|
|
|
|
while( scanon ) {
|
|
switch( getopt( argc, argv, ":i:p:A:P:d:u:r:s:f:l:v"
|
|
#ifdef WANT_ACCESSLIST_BLACK
|
|
"b:"
|
|
#elif defined( WANT_ACCESSLIST_WHITE )
|
|
"w:"
|
|
#endif
|
|
"h" ) ) {
|
|
case -1 : scanon = 0; break;
|
|
case 'i':
|
|
if( !scan_ip6( optarg, serverip )) { usage( argv[0] ); exit( 1 ); }
|
|
break;
|
|
#ifdef WANT_ACCESSLIST_BLACK
|
|
case 'b': set_config_option( &g_accesslist_filename, optarg); break;
|
|
#elif defined( WANT_ACCESSLIST_WHITE )
|
|
case 'w': set_config_option( &g_accesslist_filename, optarg); break;
|
|
#endif
|
|
case 'p':
|
|
if( !scan_ushort( optarg, &tmpport)) { usage( argv[0] ); exit( 1 ); }
|
|
ot_try_bind( serverip, tmpport, FLAG_TCP ); bound++; break;
|
|
case 'P':
|
|
if( !scan_ushort( optarg, &tmpport)) { usage( argv[0] ); exit( 1 ); }
|
|
ot_try_bind( serverip, tmpport, FLAG_UDP ); bound++; break;
|
|
#ifdef WANT_SYNC_LIVE
|
|
case 's':
|
|
if( !scan_ushort( optarg, &tmpport)) { usage( argv[0] ); exit( 1 ); }
|
|
livesync_bind_mcast( serverip, tmpport); break;
|
|
#endif
|
|
case 'd': set_config_option( &g_serverdir, optarg ); break;
|
|
case 'u': set_config_option( &g_serveruser, optarg ); break;
|
|
case 'r': set_config_option( &g_redirecturl, optarg ); break;
|
|
case 'l': statefile = optarg; break;
|
|
case 'A':
|
|
if( !scan_ip6_net( optarg, &tmpnet )) { usage( argv[0] ); exit( 1 ); }
|
|
accesslist_bless_net( &tmpnet, 0xffff ); /* Allow everything for now */
|
|
break;
|
|
case 'f': bound += parse_configfile( optarg ); break;
|
|
case 'h': help( argv[0] ); exit( 0 );
|
|
case 'v': {
|
|
char buffer[8192];
|
|
stats_return_tracker_version( buffer );
|
|
fputs( buffer, stderr );
|
|
exit( 0 );
|
|
}
|
|
default:
|
|
case '?': usage( argv[0] ); exit( 1 );
|
|
}
|
|
}
|
|
|
|
/* Bind to our default tcp/udp ports */
|
|
if( !bound) {
|
|
ot_try_bind( serverip, 6969, FLAG_TCP );
|
|
ot_try_bind( serverip, 6969, FLAG_UDP );
|
|
}
|
|
|
|
#ifdef WANT_SYSLOGS
|
|
openlog( "opentracker", 0, LOG_USER );
|
|
setlogmask(LOG_UPTO(LOG_INFO));
|
|
#endif
|
|
|
|
if( drop_privileges( g_serveruser ? g_serveruser : "nobody", g_serverdir ) == -1 )
|
|
panic( "drop_privileges failed, exiting. Last error");
|
|
|
|
g_now_seconds = time( NULL );
|
|
pthread_create( &thread_id, NULL, time_caching_worker, NULL);
|
|
|
|
/* Create our self pipe which allows us to interrupt mainloops
|
|
io_wait in case some data is available to send out */
|
|
if( pipe( g_self_pipe ) == -1 )
|
|
panic( "selfpipe failed: " );
|
|
if( !io_fd( g_self_pipe[0] ) )
|
|
panic( "selfpipe io_fd failed: " );
|
|
if( !io_fd( g_self_pipe[1] ) )
|
|
panic( "selfpipe io_fd failed: " );
|
|
io_setcookie( g_self_pipe[0], (void*)FLAG_SELFPIPE );
|
|
io_wantread( g_self_pipe[0] );
|
|
|
|
defaul_signal_handlers( );
|
|
/* Init all sub systems. This call may fail with an exit() */
|
|
trackerlogic_init( );
|
|
|
|
#ifdef _DEBUG_RANDOMTORRENTS
|
|
trackerlogic_add_random_torrents(1024*1024*1);
|
|
#endif
|
|
|
|
if( statefile )
|
|
load_state( statefile );
|
|
|
|
install_signal_handlers( );
|
|
|
|
if( !g_udp_workers )
|
|
udp_init( -1, 0 );
|
|
|
|
server_mainloop( 0 );
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *g_version_opentracker_c = "$Source$: $Revision$\n";
|