/* This software was written by Dirk Engling It is considered beerware. Prost. Skol. Cheers or whatever. Some of the stuff below is stolen from Fefes example libowfat httpd. $Id$ */ /* System */ #include #include #include #include #include #include #include #include #include #include #include #include /* Libowfat */ #include "socket.h" #include "io.h" #include "iob.h" #include "array.h" #include "byte.h" #include "fmt.h" #include "scan.h" #include "ip4.h" /* Opentracker */ #include "trackerlogic.h" #include "ot_iovec.h" #include "ot_mutex.h" #include "ot_http.h" #include "ot_udp.h" #include "ot_clean.h" #include "ot_accesslist.h" #include "ot_stats.h" #include "ot_livesync.h" /* Globals */ time_t g_now; char * g_redirecturl = NULL; uint32_t g_tracker_id; static char * g_serverdir = NULL; /* To always have space for error messages ;) */ static char static_inbuf[8192]; 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 ) { signal( SIGINT, SIG_IGN); trackerlogic_deinit(); exit( 0 ); } else if( s == SIGALRM ) { g_now = time(NULL); alarm(5); } } static void usage( char *name ) { fprintf( stderr, "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-A ip] [-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 (default: *, you may specify more than one)"); HELPLINE("-p port","specify tcp port to bind to (default: 6969, you may specify more than one)"); HELPLINE("-P port","specify udp port to bind to (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("-A ip","bless an ip address 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 fprintf( stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n" ); } #undef HELPLINE static void handle_dead( const int64 socket ) { struct http_data* h=io_getcookie( socket ); if( h ) { if( h->flag & STRUCT_HTTP_FLAG_IOB_USED ) iob_reset( &h->batch ); if( h->flag & STRUCT_HTTP_FLAG_ARRAY_USED ) array_reset( &h->request ); if( h->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK ) mutex_workqueue_canceltask( socket ); free( h ); } io_close( socket ); } static ssize_t handle_read( const int64 clientsocket ) { struct http_data* h = io_getcookie( clientsocket ); ssize_t l; if( ( l = io_tryread( clientsocket, static_inbuf, sizeof static_inbuf ) ) <= 0 ) { handle_dead( clientsocket ); return 0; } /* If we get the whole request in one packet, handle it without copying */ if( !array_start( &h->request ) ) { if( memchr( static_inbuf, '\n', l ) ) return http_handle_request( clientsocket, static_inbuf, l ); h->flag |= STRUCT_HTTP_FLAG_ARRAY_USED; array_catb( &h->request, static_inbuf, l ); return 0; } h->flag |= STRUCT_HTTP_FLAG_ARRAY_USED; array_catb( &h->request, static_inbuf, l ); if( array_failed( &h->request ) ) return http_issue_error( clientsocket, CODE_HTTPERROR_500 ); if( ( array_bytes( &h->request ) > 8192 ) && !accesslist_isblessed( (char*)&h->ip, OT_PERMISSION_MAY_SYNC ) ) return http_issue_error( clientsocket, CODE_HTTPERROR_500 ); if( memchr( array_start( &h->request ), '\n', array_bytes( &h->request ) ) ) return http_handle_request( clientsocket, array_start( &h->request ), array_bytes( &h->request ) ); return 0; } static void handle_write( const int64 clientsocket ) { struct http_data* h=io_getcookie( clientsocket ); if( !h || ( iob_send( clientsocket, &h->batch ) <= 0 ) ) handle_dead( clientsocket ); } static void handle_accept( const int64 serversocket ) { struct http_data *h; unsigned char ip[4]; uint16 port; tai6464 t; int64 i; while( ( i = socket_accept4( serversocket, (char*)ip, &port) ) != -1 ) { /* Put fd into a non-blocking mode */ io_nonblock( i ); if( !io_fd( i ) || !( h = (struct http_data*)malloc( sizeof( struct http_data ) ) ) ) { io_close( i ); continue; } io_setcookie( i, h ); io_wantread( i ); memset( h, 0, sizeof( struct http_data ) ); memmove( h->ip, ip, sizeof( ip ) ); stats_issue_event( EVENT_ACCEPT, FLAG_TCP, ntohl(*(uint32_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 + OT_CLIENT_TIMEOUT) ); io_timeout( i, t ); } if( errno == EAGAIN ) io_eagain( serversocket ); } static void server_mainloop( ) { static time_t ot_last_clean_time; time_t next_timeout_check = g_now + OT_CLIENT_TIMEOUT_CHECKINTERVAL; struct iovec *iovector; int iovec_entries; for( ; ; ) { int64 i; io_wait(); while( ( i = io_canread( ) ) != -1 ) { const void *cookie = io_getcookie( i ); if( (int)cookie == FLAG_TCP ) handle_accept( i ); else if( (int)cookie == FLAG_UDP ) handle_udp4( i ); else handle_read( i ); } while( ( i = mutex_workqueue_popresult( &iovec_entries, &iovector ) ) != -1 ) http_sendiovecdata( i, iovec_entries, iovector ); while( ( i = io_canwrite( ) ) != -1 ) handle_write( i ); if( g_now > next_timeout_check ) { while( ( i = io_timeouted() ) != -1 ) handle_dead( i ); next_timeout_check = g_now + OT_CLIENT_TIMEOUT_CHECKINTERVAL; } livesync_ticker(); /* See if we need to move our pools */ if( NOW != ot_last_clean_time ) { ot_last_clean_time = NOW; clean_all_torrents(); } /* Enforce setting the clock */ signal_handler( SIGALRM ); } } static int64_t ot_try_bind( char ip[4], uint16_t port, PROTO_FLAG proto ) { int64 s = proto == FLAG_TCP ? socket_tcp4( ) : socket_udp4(); #ifdef _DEBUG char *protos[] = {"TCP","UDP","UDP mcast"}; uint8_t *_ip = (uint8_t *)ip; fprintf( stderr, "Binding socket type %s to address %d.%d.%d.%d:%d...", protos[proto],_ip[0],_ip[1],_ip[2],_ip[3],port); #endif if( socket_bind4_reuse( s, ip, port ) == -1 ) panic( "socket_bind4_reuse" ); if( ( proto == FLAG_TCP ) && ( socket_listen( s, SOMAXCONN) == -1 ) ) panic( "socket_listen" ); if( !io_fd( s ) ) panic( "io_fd" ); io_setcookie( s, (void*)proto ); io_wantread( s ); #ifdef _DEBUG fputs( " success.\n", stderr); #endif return s; } char * set_config_option( char **option, char *value ) { #ifdef _DEBUG fprintf( stderr, "Setting config option: %s\n", value ); #endif while( isspace(*value) ) ++value; if( *option ) free( *option ); return *option = strdup( value ); } static int scan_ip4_port( const char *src, char *ip, uint16 *port ) { const char *s = src; int off; while( isspace(*s) ) ++s; if( !(off = scan_ip4( s, ip ) ) ) return 0; s += off; if( *s == 0 || isspace(*s)) return s-src; if( *(s++) != ':' ) return 0; if( !(off = scan_ushort (s, port ) ) ) return 0; return off+s-src; } int parse_configfile( char * config_filename ) { FILE * accesslist_filehandle; char inbuf[512], tmpip[4]; 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 *newl; char *p = inbuf; /* Skip white spaces */ while(isspace(*p)) ++p; /* Ignore comments and empty lines */ if((*p=='#')||(*p=='\n')||(*p==0)) continue; /* chomp */ if(( newl = strchr(p, '\n' ))) *newl = 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,14,"listen.tcp_udp" ) && isspace(p[14])) { uint16_t tmpport = 6969; if( !scan_ip4_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_ip4_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_ip4_port( p+11, tmpip, &tmpport )) goto parse_error; ot_try_bind( tmpip, tmpport, FLAG_UDP ); ++bound; #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_RESTRICT_STATS } else if(!byte_diff(p, 12, "access.stats" ) && isspace(p[12])) { if( !scan_ip4( p+13, tmpip )) goto parse_error; accesslist_blessip( tmpip, OT_PERMISSION_MAY_STAT ); #endif } else if(!byte_diff(p, 20, "tracker.redirect_url" ) && isspace(p[20])) { set_config_option( &g_redirecturl, p+21 ); #ifdef WANT_SYNC_BATCH } else if(!byte_diff(p, 26, "batchsync.cluster.admin_ip" ) && isspace(p[26])) { if(!scan_ip4( p+27, tmpip )) goto parse_error; accesslist_blessip( tmpip, OT_PERMISSION_MAY_SYNC ); #endif #ifdef WANT_SYNC_LIVE } else if(!byte_diff(p, 24, "livesync.cluster.node_ip" ) && isspace(p[24])) { if( !scan_ip4( p+25, tmpip )) goto parse_error; accesslist_blessip( tmpip, OT_PERMISSION_MAY_LIVESYNC ); } else if(!byte_diff(p, 23, "livesync.cluster.listen" ) && isspace(p[23])) { uint16_t tmpport = LIVESYNC_PORT; if( !scan_ip4_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; } int main( int argc, char **argv ) { struct passwd *pws = NULL; char serverip[4] = {0,0,0,0}, tmpip[4]; int bound = 0, scanon = 1; uint16_t tmpport; while( scanon ) { switch( getopt( argc, argv, ":i:p:A:P:d:r:s:f:v" #ifdef WANT_ACCESSLIST_BLACK "b:" #elif defined( WANT_ACCESSLIST_WHITE ) "w:" #endif "h" ) ) { case -1 : scanon = 0; break; case 'i': if( !scan_ip4( 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 'r': set_config_option( &g_redirecturl, optarg ); break; case 'A': if( !scan_ip4( optarg, tmpip )) { usage( argv[0] ); exit( 1 ); } accesslist_blessip( tmpip, 0xffff ); /* Allow everything for now */ break; case 'f': bound += parse_configfile( optarg ); break; case 'h': help( argv[0] ); exit( 0 ); case 'v': write( 2, static_inbuf, stats_return_tracker_version( static_inbuf )); 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 ); } /* Drop permissions */ pws = getpwnam( "nobody" ); if( !pws ) { setegid( (gid_t)-2 ); setuid( (uid_t)-2 ); setgid( (gid_t)-2 ); seteuid( (uid_t)-2 ); } else { setegid( pws->pw_gid ); setuid( pws->pw_uid ); setgid( pws->pw_gid ); seteuid( pws->pw_uid ); } endpwent(); signal( SIGPIPE, SIG_IGN ); signal( SIGINT, signal_handler ); signal( SIGALRM, signal_handler ); g_now = time( NULL ); if( trackerlogic_init( g_serverdir ? g_serverdir : "." ) == -1 ) panic( "Logic not started" ); alarm(5); server_mainloop( ); return 0; } const char *g_version_opentracker_c = "$Source$: $Revision$\n";