2007-01-26 16:26:49 +00:00
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2007-01-05 00:00:42 +00:00
It is considered beerware . Prost . Skol . Cheers or whatever .
Some of the stuff below is stolen from Fefes example libowfat httpd .
2007-12-20 05:59:34 +00:00
$ Id $ */
2007-01-05 00:00:42 +00:00
2007-11-06 17:50:41 +00:00
/* System */
2008-10-04 05:40:51 +00:00
# include <arpa/inet.h>
2024-04-15 00:39:02 +02:00
# include <ctype.h>
2006-12-05 12:56:56 +00:00
# include <errno.h>
2024-04-15 00:39:02 +02:00
# include <pthread.h>
# include <pwd.h>
2006-12-08 22:37:44 +00:00
# include <signal.h>
# include <stdio.h>
2024-04-15 00:39:02 +02:00
# include <stdlib.h>
# include <string.h>
# include <sys/socket.h>
# include <unistd.h>
2010-12-11 15:50:56 +00:00
# ifdef WANT_SYSLOGS
# include <syslog.h>
# endif
2006-12-05 12:56:56 +00:00
2007-11-06 17:50:41 +00:00
/* Libowfat */
2024-04-15 00:39:02 +02:00
# include "byte.h"
2007-11-06 17:50:41 +00:00
# include "io.h"
# include "iob.h"
2009-01-13 22:41:17 +00:00
# include "ip6.h"
2024-04-15 00:39:02 +02:00
# include "scan.h"
# include "socket.h"
2007-11-06 17:50:41 +00:00
/* Opentracker */
2007-12-03 00:58:18 +00:00
# include "ot_accesslist.h"
2024-04-15 00:39:02 +02:00
# include "ot_http.h"
2008-10-04 05:40:51 +00:00
# include "ot_livesync.h"
2024-04-15 00:39:02 +02:00
# include "ot_mutex.h"
# include "ot_stats.h"
# include "ot_udp.h"
# include "trackerlogic.h"
2006-12-08 20:28:17 +00:00
2007-01-26 16:26:49 +00:00
/* Globals */
2024-04-15 00:39:02 +02:00
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 ;
2012-04-25 05:48:16 +00:00
static unsigned int g_udp_workers ;
2007-08-18 09:56:22 +00:00
2024-04-15 00:39:02 +02:00
static void panic ( const char * routine ) __attribute__ ( ( noreturn ) ) ;
static void panic ( const char * routine ) {
fprintf ( stderr , " %s: %s \n " , routine , strerror ( errno ) ) ;
exit ( 111 ) ;
2006-12-05 12:56:56 +00:00
}
2024-04-15 00:39:02 +02:00
static void signal_handler ( int s ) {
if ( s = = SIGINT ) {
2009-01-15 23:01:36 +00:00
/* Any new interrupt signal quits the application */
2024-04-15 00:39:02 +02:00
signal ( SIGINT , SIG_DFL ) ;
2009-03-17 23:57:20 +00:00
2009-01-15 23:01:36 +00:00
/* Tell all other threads to not acquire any new lock on a bucket
but cancel their operations and return */
2008-12-07 03:50:51 +00:00
g_opentracker_running = 0 ;
2007-11-19 21:10:53 +00:00
2007-11-06 01:28:40 +00:00
trackerlogic_deinit ( ) ;
2010-12-11 15:50:56 +00:00
# ifdef WANT_SYSLOGS
closelog ( ) ;
# endif
2024-04-15 00:39:02 +02:00
exit ( 0 ) ;
2006-12-08 22:53:32 +00:00
}
2006-12-08 22:37:44 +00:00
}
2024-04-15 00:39:02 +02:00
static void defaul_signal_handlers ( void ) {
2009-07-17 18:00:26 +00:00
sigset_t signal_mask ;
sigemptyset ( & signal_mask ) ;
2024-04-15 00:39:02 +02:00
sigaddset ( & signal_mask , SIGPIPE ) ;
sigaddset ( & signal_mask , SIGHUP ) ;
sigaddset ( & signal_mask , SIGINT ) ;
sigaddset ( & signal_mask , SIGALRM ) ;
pthread_sigmask ( SIG_BLOCK , & signal_mask , NULL ) ;
2009-07-17 18:00:26 +00:00
}
2024-04-15 00:39:02 +02:00
static void install_signal_handlers ( void ) {
struct sigaction sa ;
sigset_t signal_mask ;
2009-07-17 18:00:26 +00:00
sigemptyset ( & signal_mask ) ;
sa . sa_handler = signal_handler ;
sigemptyset ( & sa . sa_mask ) ;
sa . sa_flags = SA_RESTART ;
2024-04-15 00:39:02 +02:00
if ( ( sigaction ( SIGINT , & sa , NULL ) = = - 1 ) | | ( sigaction ( SIGALRM , & sa , NULL ) = = - 1 ) )
panic ( " install_signal_handlers " ) ;
2009-07-17 18:00:26 +00:00
2024-04-15 00:39:02 +02:00
sigaddset ( & signal_mask , SIGINT ) ;
pthread_sigmask ( SIG_UNBLOCK , & signal_mask , NULL ) ;
2009-07-17 18:00:26 +00:00
}
2024-04-15 00:39:02 +02:00
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] "
2008-10-04 05:40:51 +00:00
# ifdef WANT_ACCESSLIST_BLACK
2024-04-15 00:39:02 +02:00
" [-b blacklistfile] "
# elif defined(WANT_ACCESSLIST_WHITE)
" [-w whitelistfile] "
2007-08-18 09:56:22 +00:00
# endif
2024-04-15 00:39:02 +02:00
" \n " ,
name ) ;
2007-01-05 13:00:06 +00:00
}
2024-04-15 00:39:02 +02:00
# 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) " ) ;
2008-10-04 05:40:51 +00:00
# ifdef WANT_ACCESSLIST_BLACK
2024-04-15 00:39:02 +02:00
HELPLINE ( " -b file " , " specify blacklist file. " ) ;
# elif defined(WANT_ACCESSLIST_WHITE)
HELPLINE ( " -w file " , " specify whitelist file. " ) ;
2007-07-22 00:40:10 +00:00
# endif
2007-04-07 00:24:17 +00:00
2024-04-14 13:12:27 +02:00
fprintf ( stderr , " \n Example: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80 \n " ) ;
fprintf ( stderr , " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969. \n " ) ;
fprintf ( stderr , " If no port is bound from config file or command line, the last address given \n " ) ;
fprintf ( stderr , " (or ::1 if none is set) will be used on port 6969. \n " ) ;
2007-01-05 12:25:44 +00:00
}
2007-04-07 00:24:17 +00:00
# undef HELPLINE
2007-01-05 12:25:44 +00:00
2024-04-15 00:39:02 +02:00
static ssize_t header_complete ( char * request , ssize_t byte_count ) {
2022-11-24 04:20:06 +01:00
ssize_t i = 0 , state = 0 ;
2009-11-18 03:56:26 +00:00
2024-04-15 00:39:02 +02:00
for ( i = 1 ; i < byte_count ; i + = 2 )
if ( request [ i ] < = 13 ) {
2009-11-18 03:56:26 +00:00
i - - ;
2024-04-15 00:39:02 +02:00
for ( state = 0 ; i < byte_count ; + + i ) {
2009-11-18 03:56:26 +00:00
char c = request [ i ] ;
2024-04-15 00:39:02 +02:00
if ( c = = ' \r ' | | c = = ' \n ' )
state = ( state > > 2 ) | ( ( c < < 6 ) & 0xc0 ) ;
2009-11-18 03:56:26 +00:00
else
break ;
2024-04-15 00:39:02 +02:00
if ( state > = 0xa0 | | state = = 0x99 )
return i + 1 ;
2009-11-18 03:56:26 +00:00
}
2024-04-15 00:39:02 +02:00
}
2009-11-18 03:56:26 +00:00
return 0 ;
}
2024-04-15 00:39:02 +02:00
static void handle_dead ( const int64 sock ) {
struct http_data * cookie = io_getcookie ( sock ) ;
if ( cookie ) {
2021-04-24 03:25:30 +02:00
size_t i ;
2024-04-15 00:39:02 +02:00
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 ) ;
2007-11-14 13:06:34 +00:00
}
2024-04-15 00:39:02 +02:00
io_close ( sock ) ;
2007-11-14 13:06:34 +00:00
}
2024-04-15 00:39:02 +02:00
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 ) ;
2007-01-18 02:23:18 +00:00
2024-04-15 00:39:02 +02:00
if ( byte_count = = 0 | | byte_count = = - 3 ) {
handle_dead ( sock ) ;
2009-11-18 04:00:26 +00:00
return ;
2007-12-03 00:58:18 +00:00
}
2007-01-24 20:13:30 +00:00
2024-04-15 00:39:02 +02:00
if ( byte_count = = - 1 )
2024-03-07 04:09:42 +01:00
return ;
2007-01-29 02:02:03 +00:00
/* If we get the whole request in one packet, handle it without copying */
2024-04-15 00:39:02 +02:00
if ( ! array_start ( & cookie - > request ) ) {
if ( ( ws - > header_size = header_complete ( ws - > inbuf , byte_count ) ) ) {
ws - > request = ws - > inbuf ;
2009-01-15 23:01:36 +00:00
ws - > request_size = byte_count ;
2024-04-15 00:39:02 +02:00
http_handle_request ( sock , ws ) ;
2009-11-18 04:00:26 +00:00
} else
2024-04-15 00:39:02 +02:00
array_catb ( & cookie - > request , ws - > inbuf , ( size_t ) byte_count ) ;
2009-11-18 04:00:26 +00:00
return ;
2007-01-29 02:02:03 +00:00
}
2024-04-15 00:39:02 +02:00
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 ) ;
2009-11-18 04:00:26 +00:00
return ;
}
2007-12-03 00:58:18 +00:00
2024-04-15 00:39:02 +02:00
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 ) ;
2010-08-09 14:20:02 +00:00
# ifdef WANT_KEEPALIVE
2024-04-15 00:39:02 +02:00
if ( ! ws - > keep_alive )
2010-08-09 14:20:02 +00:00
# endif
return ;
2009-11-18 04:00:26 +00:00
}
2007-01-18 02:23:18 +00:00
}
2024-04-15 00:39:02 +02:00
static void handle_write ( const int64 sock ) {
struct http_data * cookie = io_getcookie ( sock ) ;
size_t i ;
int chunked = 0 ;
2021-04-24 03:25:30 +02:00
/* Look for the first io_batch still containing bytes to write */
2024-04-15 00:39:02 +02:00
if ( cookie ) {
if ( cookie - > flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER )
2024-04-13 00:47:29 +02:00
chunked = 1 ;
2024-04-15 00:39:02 +02:00
for ( i = 0 ; i < cookie - > batches ; + + i ) {
if ( cookie - > batch [ i ] . bytesleft ) {
int64 res = iob_send ( sock , cookie - > batch + i ) ;
2021-04-24 03:25:30 +02:00
2024-04-15 00:39:02 +02:00
if ( res = = - 3 ) {
handle_dead ( sock ) ;
2024-04-13 00:47:29 +02:00
return ;
}
2021-04-24 03:25:30 +02:00
2024-04-15 00:39:02 +02:00
if ( ! cookie - > batch [ i ] . bytesleft )
2021-04-25 13:30:24 +02:00
continue ;
2024-04-15 00:39:02 +02:00
if ( res = = - 1 | | res > 0 | | i < cookie - > batches - 1 )
2021-04-24 03:25:30 +02:00
return ;
}
2024-04-13 00:47:29 +02:00
}
}
2021-04-24 03:25:30 +02:00
2024-04-13 00:47:29 +02:00
/* In a chunked transfer after all batches accumulated have been sent, wait for the next one */
2024-04-15 00:39:02 +02:00
if ( chunked )
io_dontwantwrite ( sock ) ;
2024-04-14 13:12:11 +02:00
else
2024-04-15 00:39:02 +02:00
handle_dead ( sock ) ;
2007-01-20 01:50:28 +00:00
}
2024-04-15 00:39:02 +02:00
static void handle_accept ( const int64 serversocket ) {
2009-01-15 23:01:36 +00:00
struct http_data * cookie ;
2024-04-15 00:39:02 +02:00
int64 sock ;
ot_ip6 ip ;
uint16 port ;
tai6464 t ;
2007-01-18 12:27:17 +00:00
2024-04-15 00:39:02 +02:00
while ( ( sock = socket_accept6 ( serversocket , ip , & port , NULL ) ) ! = - 1 ) {
2007-01-18 12:27:17 +00:00
2007-11-20 02:21:53 +00:00
/* Put fd into a non-blocking mode */
2024-04-15 00:39:02 +02:00
io_nonblock ( sock ) ;
2007-11-20 02:21:53 +00:00
2024-04-15 00:39:02 +02:00
if ( ! io_fd ( sock ) | | ! ( cookie = ( struct http_data * ) malloc ( sizeof ( struct http_data ) ) ) ) {
io_close ( sock ) ;
2007-01-18 12:27:17 +00:00
continue ;
}
2024-04-15 00:39:02 +02:00
memset ( cookie , 0 , sizeof ( struct http_data ) ) ;
memcpy ( cookie - > ip , ip , sizeof ( ot_ip6 ) ) ;
2007-03-28 23:24:30 +00:00
2024-04-15 00:39:02 +02:00
io_setcookie ( sock , cookie ) ;
io_wantread ( sock ) ;
2009-11-18 04:00:26 +00:00
2024-04-15 00:39:02 +02:00
stats_issue_event ( EVENT_ACCEPT , FLAG_TCP , ( uintptr_t ) ip ) ;
2007-03-16 23:37:04 +00:00
2007-10-19 02:00:53 +00:00
/* 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 */
2024-04-15 00:39:02 +02:00
taia_uint ( & t , 0 ) ; /* Clear t */
tai_unix ( & ( t . sec ) , ( g_now_seconds + OT_CLIENT_TIMEOUT ) ) ;
io_timeout ( sock , t ) ;
2007-01-18 12:27:17 +00:00
}
2018-05-26 00:25:59 +02:00
io_eagain ( serversocket ) ;
2007-01-18 12:27:17 +00:00
}
2024-04-15 00:39:02 +02:00
static void * server_mainloop ( void * args ) {
2009-01-05 18:05:39 +00:00
struct ot_workstruct ws ;
2024-04-15 00:39:02 +02:00
time_t next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL ;
struct iovec * iovector ;
int iovec_entries , is_partial ;
2009-01-05 18:05:39 +00:00
2009-11-18 04:00:26 +00:00
( void ) args ;
2009-01-05 18:05:39 +00:00
/* Initialize our "thread local storage" */
2024-04-15 00:39:02 +02:00
ws . inbuf = malloc ( G_INBUF_SIZE ) ;
ws . outbuf = malloc ( G_OUTBUF_SIZE ) ;
2009-01-05 18:05:39 +00:00
# ifdef _DEBUG_HTTPERROR
2024-04-15 00:39:02 +02:00
ws . debugbuf = malloc ( G_DEBUGBUF_SIZE ) ;
2009-01-05 18:05:39 +00:00
# endif
2021-04-20 04:05:50 +02:00
2024-04-15 00:39:02 +02:00
if ( ! ws . inbuf | | ! ws . outbuf )
panic ( " Initializing worker failed " ) ;
2021-04-20 04:05:50 +02:00
# ifdef WANT_ARC4RANDOM
arc4random_buf ( & ws . rand48_state [ 0 ] , 3 * sizeof ( uint16_t ) ) ;
# else
2021-04-20 03:48:50 +02:00
ws . rand48_state [ 0 ] = ( uint16_t ) random ( ) ;
ws . rand48_state [ 1 ] = ( uint16_t ) random ( ) ;
ws . rand48_state [ 2 ] = ( uint16_t ) random ( ) ;
2021-04-20 04:05:50 +02:00
# endif
2009-01-05 18:05:39 +00:00
2024-04-15 00:39:02 +02:00
for ( ; ; ) {
2009-01-15 23:01:36 +00:00
int64 sock ;
2007-01-18 02:40:18 +00:00
2007-10-13 17:58:20 +00:00
io_wait ( ) ;
2007-01-18 02:23:18 +00:00
2024-04-15 00:39:02 +02:00
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 ) ;
2018-05-26 00:28:10 +02:00
else
2024-04-15 00:39:02 +02:00
handle_read ( sock , & ws ) ;
2007-01-18 12:27:17 +00:00
}
2024-04-15 00:39:02 +02:00
while ( ( sock = mutex_workqueue_popresult ( & iovec_entries , & iovector , & is_partial ) ) ! = - 1 )
http_sendiovecdata ( sock , & ws , iovec_entries , iovector , is_partial ) ;
2007-11-14 13:06:34 +00:00
2024-04-15 00:39:02 +02:00
while ( ( sock = io_canwrite ( ) ) ! = - 1 )
handle_write ( sock ) ;
2007-01-20 01:50:28 +00:00
2024-04-15 00:39:02 +02:00
if ( g_now_seconds > next_timeout_check ) {
while ( ( sock = io_timeouted ( ) ) ! = - 1 )
handle_dead ( sock ) ;
2008-11-28 22:21:10 +00:00
next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL ;
2007-01-18 02:23:18 +00:00
}
2007-03-27 12:07:29 +00:00
2008-10-04 05:40:51 +00:00
livesync_ticker ( ) ;
2007-01-18 02:23:18 +00:00
}
2009-11-18 04:00:26 +00:00
return 0 ;
2007-01-18 02:23:18 +00:00
}
2024-04-15 00:39:02 +02:00
static int64_t ot_try_bind ( ot_ip6 ip , uint16_t port , PROTO_FLAG proto ) {
int64 sock = proto = = FLAG_TCP ? socket_tcp6 ( ) : socket_udp6 ( ) ;
2008-10-05 12:30:06 +00:00
# ifdef _DEBUG
2009-07-17 18:00:26 +00:00
{
2024-04-15 00:39:02 +02:00
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 ) ;
2009-07-17 18:00:26 +00:00
}
2008-10-05 12:30:06 +00:00
# endif
2009-03-17 23:57:20 +00:00
2024-04-15 00:39:02 +02:00
if ( socket_bind6_reuse ( sock , ip , port , 0 ) = = - 1 )
panic ( " socket_bind6_reuse " ) ;
2007-03-05 21:14:50 +00:00
2024-04-15 00:39:02 +02:00
if ( ( proto = = FLAG_TCP ) & & ( socket_listen ( sock , SOMAXCONN ) = = - 1 ) )
panic ( " socket_listen " ) ;
2007-03-05 21:14:50 +00:00
2024-04-15 00:39:02 +02:00
if ( ! io_fd ( sock ) )
panic ( " io_fd " ) ;
2007-03-05 21:14:50 +00:00
2024-04-15 00:39:02 +02:00
io_setcookie ( sock , ( void * ) proto ) ;
2007-03-05 21:14:50 +00:00
2024-04-15 00:39:02 +02:00
if ( ( proto = = FLAG_UDP ) & & g_udp_workers ) {
io_block ( sock ) ;
udp_init ( sock , g_udp_workers ) ;
2012-04-25 05:48:16 +00:00
} else
2024-04-15 00:39:02 +02:00
io_wantread ( sock ) ;
2008-10-05 12:30:06 +00:00
2008-10-28 01:27:22 +00:00
# ifdef _DEBUG
2024-04-15 00:39:02 +02:00
fputs ( " success. \n " , stderr ) ;
2008-10-05 12:30:06 +00:00
# endif
2008-10-28 01:27:22 +00:00
2009-01-15 23:01:36 +00:00
return sock ;
2008-10-04 05:40:51 +00:00
}
2024-04-15 00:39:02 +02:00
char * set_config_option ( char * * option , char * value ) {
2008-10-05 12:30:06 +00:00
# ifdef _DEBUG
2024-04-15 00:39:02 +02:00
fprintf ( stderr , " Setting config option: %s \n " , value ) ;
2008-10-05 12:30:06 +00:00
# endif
2024-04-15 00:39:02 +02:00
while ( isspace ( * value ) )
+ + value ;
free ( * option ) ;
return * option = strdup ( value ) ;
2008-10-04 05:40:51 +00:00
}
2024-04-15 00:39:02 +02:00
static int scan_ip6_port ( const char * src , ot_ip6 ip , uint16 * port ) {
2008-10-05 12:30:06 +00:00
const char * s = src ;
2024-04-15 00:39:02 +02:00
int off , bracket = 0 ;
while ( isspace ( * s ) )
+ + s ;
if ( * s = = ' [ ' )
+ + s , + + bracket ; /* for v6 style notation */
if ( ! ( off = scan_ip6 ( s , ip ) ) )
2008-10-05 12:30:06 +00:00
return 0 ;
s + = off ;
2024-04-15 00:39:02 +02:00
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 ;
2009-01-13 22:41:17 +00:00
s + + ;
} else {
2024-04-15 00:39:02 +02:00
if ( * ( s + + ) ! = ' : ' )
return 0 ;
2009-01-13 22:41:17 +00:00
}
2024-04-15 00:39:02 +02:00
if ( ! ( off = scan_ushort ( s , port ) ) )
return 0 ;
return off + s - src ;
2008-10-04 05:40:51 +00:00
}
2024-04-15 00:39:02 +02:00
static int scan_ip6_net ( const char * src , ot_net * net ) {
2024-03-29 03:30:13 +01:00
const char * s = src ;
2024-04-15 00:39:02 +02:00
int off ;
while ( isspace ( * s ) )
+ + s ;
if ( ! ( off = scan_ip6 ( s , net - > address ) ) )
2024-03-29 03:30:13 +01:00
return 0 ;
s + = off ;
2024-04-15 00:39:02 +02:00
if ( * s ! = ' / ' )
2024-03-29 03:30:13 +01:00
net - > bits = 128 ;
else {
s + + ;
2024-04-15 00:39:02 +02:00
if ( ! ( off = scan_int ( s , & net - > bits ) ) )
2024-03-29 03:30:13 +01:00
return 0 ;
2024-04-15 00:39:02 +02:00
if ( ip6_isv4mapped ( net - > address ) )
2024-03-29 03:30:13 +01:00
net - > bits + = 96 ;
2024-04-15 00:39:02 +02:00
if ( net - > bits > 128 )
2024-03-29 03:30:13 +01:00
return 0 ;
s + = off ;
}
2024-04-15 00:39:02 +02:00
return off + s - src ;
2024-03-29 03:30:13 +01:00
}
2024-04-15 00:39:02 +02:00
int parse_configfile ( char * config_filename ) {
FILE * accesslist_filehandle ;
char inbuf [ 512 ] ;
ot_ip6 tmpip ;
2024-03-29 03:30:13 +01:00
# if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE)
2024-04-15 00:39:02 +02:00
ot_net tmpnet ;
2024-03-29 03:30:13 +01:00
# endif
2024-04-15 00:39:02 +02:00
int bound = 0 ;
2008-10-04 05:40:51 +00:00
2024-04-15 00:39:02 +02:00
accesslist_filehandle = fopen ( config_filename , " r " ) ;
2008-10-28 01:27:22 +00:00
2024-04-15 00:39:02 +02:00
if ( accesslist_filehandle = = NULL ) {
fprintf ( stderr , " Warning: Can't open config file: %s. " , config_filename ) ;
2008-10-04 05:40:51 +00:00
return 0 ;
}
2008-10-28 01:27:22 +00:00
2024-04-15 00:39:02 +02:00
while ( fgets ( inbuf , sizeof ( inbuf ) , accesslist_filehandle ) ) {
char * p = inbuf ;
2010-08-18 00:43:12 +00:00
size_t strl ;
2008-10-04 05:40:51 +00:00
/* Skip white spaces */
2024-04-15 00:39:02 +02:00
while ( isspace ( * p ) )
+ + p ;
2008-10-28 01:27:22 +00:00
2008-10-04 05:40:51 +00:00
/* Ignore comments and empty lines */
2024-04-15 00:39:02 +02:00
if ( ( * p = = ' # ' ) | | ( * p = = ' \n ' ) | | ( * p = = 0 ) )
continue ;
2008-10-04 05:40:51 +00:00
2010-08-18 00:43:12 +00:00
/* consume trailing new lines and spaces */
strl = strlen ( p ) ;
2024-04-15 00:39:02 +02:00
while ( strl & & isspace ( p [ strl - 1 ] ) )
2010-08-18 00:43:12 +00:00
p [ - - strl ] = 0 ;
2008-10-04 05:40:51 +00:00
/* Scan for commands */
2024-04-15 00:39:02 +02:00
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 ] ) ) {
2008-10-06 11:42:03 +00:00
uint16_t tmpport = 6969 ;
2024-04-15 00:39:02 +02:00
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 ] ) ) {
2008-10-04 05:40:51 +00:00
uint16_t tmpport = 6969 ;
2024-04-15 00:39:02 +02:00
if ( ! scan_ip6_port ( p + 11 , tmpip , & tmpport ) )
goto parse_error ;
ot_try_bind ( tmpip , tmpport , FLAG_TCP ) ;
2008-10-04 05:40:51 +00:00
+ + bound ;
2024-04-15 00:39:02 +02:00
} else if ( ! byte_diff ( p , 10 , " listen.udp " ) & & isspace ( p [ 10 ] ) ) {
2008-10-04 05:40:51 +00:00
uint16_t tmpport = 6969 ;
2024-04-15 00:39:02 +02:00
if ( ! scan_ip6_port ( p + 11 , tmpip , & tmpport ) )
goto parse_error ;
ot_try_bind ( tmpip , tmpport , FLAG_UDP ) ;
2008-10-04 05:40:51 +00:00
+ + bound ;
2024-04-15 00:39:02 +02:00
} else if ( ! byte_diff ( p , 18 , " listen.udp.workers " ) & & isspace ( p [ 18 ] ) ) {
2012-04-25 05:48:16 +00:00
char * value = p + 18 ;
2024-04-15 00:39:02 +02:00
while ( isspace ( * value ) )
+ + value ;
scan_uint ( value , & g_udp_workers ) ;
2008-10-26 12:01:58 +00:00
# ifdef WANT_ACCESSLIST_WHITE
2024-04-15 00:39:02 +02:00
} 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 ) ;
2008-10-24 00:04:02 +00:00
# endif
2022-11-24 04:20:06 +01:00
# ifdef WANT_DYNAMIC_ACCESSLIST
2024-04-15 00:39:02 +02:00
} 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 ) ;
2022-11-24 04:20:06 +01:00
# endif
2008-10-24 00:04:02 +00:00
# ifdef WANT_RESTRICT_STATS
2024-04-15 00:39:02 +02:00
} 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 ) ;
2009-03-04 14:35:21 +00:00
# endif
2024-04-15 00:39:02 +02:00
} else if ( ! byte_diff ( p , 17 , " access.stats_path " ) & & isspace ( p [ 17 ] ) ) {
set_config_option ( & g_stats_path , p + 18 ) ;
2009-03-04 14:35:21 +00:00
# ifdef WANT_IP_FROM_PROXY
2024-04-15 00:39:02 +02:00
} 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 ) ;
2008-10-04 05:40:51 +00:00
# endif
2024-04-15 00:39:02 +02:00
} else if ( ! byte_diff ( p , 20 , " tracker.redirect_url " ) & & isspace ( p [ 20 ] ) ) {
set_config_option ( & g_redirecturl , p + 21 ) ;
2008-10-04 05:40:51 +00:00
# ifdef WANT_SYNC_LIVE
2024-04-15 00:39:02 +02:00
} 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 ] ) ) {
2008-10-04 05:40:51 +00:00
uint16_t tmpport = LIVESYNC_PORT ;
2024-04-15 00:39:02 +02:00
if ( ! scan_ip6_port ( p + 24 , tmpip , & tmpport ) )
goto parse_error ;
livesync_bind_mcast ( tmpip , tmpport ) ;
2008-10-04 05:40:51 +00:00
# endif
} else
2024-04-15 00:39:02 +02:00
fprintf ( stderr , " Unhandled line in config file: %s \n " , inbuf ) ;
2008-10-04 05:40:51 +00:00
continue ;
parse_error :
2024-04-15 00:39:02 +02:00
fprintf ( stderr , " Parse error in config file: %s \n " , inbuf ) ;
2008-10-04 05:40:51 +00:00
}
2024-04-15 00:39:02 +02:00
fclose ( accesslist_filehandle ) ;
2008-10-04 05:40:51 +00:00
return bound ;
2007-03-05 21:14:50 +00:00
}
2024-04-15 00:39:02 +02:00
void load_state ( const char * const state_filename ) {
FILE * state_filehandle ;
char inbuf [ 512 ] ;
ot_hash infohash ;
2009-03-17 23:57:20 +00:00
unsigned long long base , downcount ;
2024-04-15 00:39:02 +02:00
int consumed ;
2009-03-17 23:57:20 +00:00
2024-04-15 00:39:02 +02:00
state_filehandle = fopen ( state_filename , " r " ) ;
2009-03-17 23:57:20 +00:00
2024-04-15 00:39:02 +02:00
if ( state_filehandle = = NULL ) {
fprintf ( stderr , " Warning: Can't open config file: %s. " , state_filename ) ;
2009-03-17 23:57:20 +00:00
return ;
}
/* We do ignore anything that is not of the form "^[:xdigit:]:\d+:\d+" */
2024-04-15 00:39:02 +02:00
while ( fgets ( inbuf , sizeof ( inbuf ) , state_filehandle ) ) {
2009-03-17 23:57:20 +00:00
int i ;
2024-04-15 00:39:02 +02:00
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 )
2009-03-17 23:57:20 +00:00
continue ;
infohash [ i ] = eger ;
}
2024-04-15 00:39:02 +02:00
if ( i ! = ( int ) sizeof ( ot_hash ) )
continue ;
2009-03-17 23:57:20 +00:00
i * = 2 ;
2024-04-15 00:39:02 +02:00
if ( inbuf [ i + + ] ! = ' : ' | | ! ( consumed = scan_ulonglong ( inbuf + i , & base ) ) )
continue ;
2009-03-17 23:57:20 +00:00
i + = consumed ;
2024-04-15 00:39:02 +02:00
if ( inbuf [ i + + ] ! = ' : ' | | ! ( consumed = scan_ulonglong ( inbuf + i , & downcount ) ) )
continue ;
add_torrent_from_saved_state ( infohash , base , downcount ) ;
2009-03-17 23:57:20 +00:00
}
2009-11-18 04:00:26 +00:00
2024-04-15 00:39:02 +02:00
fclose ( state_filehandle ) ;
2009-03-17 23:57:20 +00:00
}
2024-04-15 00:39:02 +02:00
int drop_privileges ( const char * const serveruser , const char * const serverdir ) {
2007-04-02 17:26:40 +00:00
struct passwd * pws = NULL ;
2009-01-02 08:57:53 +00:00
2010-04-09 10:15:51 +00:00
# ifdef _DEBUG
2024-04-15 00:39:02 +02:00
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 ) ;
2010-04-09 10:15:51 +00:00
# endif
2009-01-02 08:57:53 +00:00
/* Grab pws entry before chrooting */
2024-04-15 00:39:02 +02:00
pws = getpwnam ( serveruser ) ;
2009-01-02 08:57:53 +00:00
endpwent ( ) ;
2024-04-15 00:39:02 +02:00
if ( geteuid ( ) = = 0 ) {
2009-01-02 08:57:53 +00:00
/* Running as root: chroot and drop privileges */
2024-04-15 00:39:02 +02:00
if ( serverdir & & chroot ( serverdir ) ) {
fprintf ( stderr , " Could not chroot to %s, because: %s \n " , serverdir , strerror ( errno ) ) ;
2009-01-02 08:57:53 +00:00
return - 1 ;
}
2024-04-15 00:39:02 +02:00
if ( chdir ( " / " ) )
2009-01-02 08:57:53 +00:00
panic ( " chdir() failed after chrooting: " ) ;
2010-04-09 10:15:51 +00:00
/* If we can't find server user, revert to nobody's default uid */
2024-04-15 00:39:02 +02:00
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 ) )
2021-08-03 13:53:13 +02:00
panic ( " Could not set uid to value -2 " ) ;
2024-04-15 00:39:02 +02:00
} else {
if ( setegid ( pws - > pw_gid ) | | setgid ( pws - > pw_gid ) | | setuid ( pws - > pw_uid ) | | seteuid ( pws - > pw_uid ) )
2021-08-03 13:53:13 +02:00
panic ( " Could not set uid to specified value " ) ;
2009-01-02 08:57:53 +00:00
}
2024-04-15 00:39:02 +02:00
if ( geteuid ( ) = = 0 | | getegid ( ) = = 0 )
2009-01-02 08:57:53 +00:00
panic ( " Still running with root privileges?! " ) ;
2024-04-15 00:39:02 +02:00
} else {
2009-01-02 08:57:53 +00:00
/* Normal user, just chdir() */
2024-04-15 00:39:02 +02:00
if ( serverdir & & chdir ( serverdir ) ) {
fprintf ( stderr , " Could not chroot to %s, because: %s \n " , serverdir , strerror ( errno ) ) ;
2009-01-02 08:57:53 +00:00
return - 1 ;
}
}
return 0 ;
}
2024-04-12 18:10:31 +02:00
/* Maintain our copy of the clock. time() on BSDs is very expensive. */
2024-04-15 00:39:02 +02:00
static void * time_caching_worker ( void * args ) {
2024-04-12 18:10:31 +02:00
( void ) args ;
while ( 1 ) {
g_now_seconds = time ( NULL ) ;
sleep ( 5 ) ;
}
2024-04-15 17:34:06 +02:00
return NULL ;
2024-04-12 18:10:31 +02:00
}
2024-04-15 00:39:02 +02:00
int main ( int argc , char * * argv ) {
ot_ip6 serverip ;
ot_net tmpnet ;
int bound = 0 , scanon = 1 ;
uint16_t tmpport ;
char * statefile = 0 ;
2024-04-12 18:10:31 +02:00
pthread_t thread_id ; /* time cacher */
2008-10-28 01:27:22 +00:00
2024-04-15 00:39:02 +02:00
memset ( serverip , 0 , sizeof ( ot_ip6 ) ) ;
2024-04-03 23:08:17 +02:00
# ifdef WANT_V4_ONLY
2024-04-15 00:39:02 +02:00
serverip [ 10 ] = serverip [ 11 ] = - 1 ;
2009-02-10 14:49:38 +00:00
# endif
2009-01-13 22:41:17 +00:00
2012-05-28 15:24:33 +00:00
# ifdef WANT_DEV_RANDOM
srandomdev ( ) ;
# else
2024-04-15 00:39:02 +02:00
srandom ( time ( NULL ) ) ;
2012-05-28 15:24:33 +00:00
# endif
2024-04-15 00:39:02 +02:00
while ( scanon ) {
switch ( getopt ( argc , argv ,
" :i:p:A:P:d:u:r:s:f:l:v "
2008-10-04 05:40:51 +00:00
# ifdef WANT_ACCESSLIST_BLACK
2024-04-15 00:39:02 +02:00
" b: "
# elif defined(WANT_ACCESSLIST_WHITE)
" w: "
2007-08-18 09:56:22 +00:00
# endif
2024-04-15 00:39:02 +02:00
" h " ) ) {
case - 1 :
scanon = 0 ;
break ;
case ' i ' :
if ( ! scan_ip6 ( optarg , serverip ) ) {
usage ( argv [ 0 ] ) ;
exit ( 1 ) ;
}
break ;
2008-10-04 05:40:51 +00:00
# ifdef WANT_ACCESSLIST_BLACK
2024-04-15 00:39:02 +02:00
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 ;
2007-07-22 00:40:10 +00:00
# endif
2024-04-15 00:39:02 +02:00
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 ;
2008-10-04 05:40:51 +00:00
# ifdef WANT_SYNC_LIVE
2024-04-15 00:39:02 +02:00
case ' s ' :
if ( ! scan_ushort ( optarg , & tmpport ) ) {
usage ( argv [ 0 ] ) ;
exit ( 1 ) ;
}
livesync_bind_mcast ( serverip , tmpport ) ;
break ;
2008-10-04 05:40:51 +00:00
# endif
2024-04-15 00:39:02 +02:00
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 ) ;
2009-01-05 18:05:39 +00:00
}
2024-04-15 00:39:02 +02:00
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 ) ;
2007-01-05 12:25:44 +00:00
}
2007-01-08 00:57:35 +00:00
}
2006-12-08 20:07:26 +00:00
2007-07-20 10:20:02 +00:00
/* Bind to our default tcp/udp ports */
2024-04-15 00:39:02 +02:00
if ( ! bound ) {
ot_try_bind ( serverip , 6969 , FLAG_TCP ) ;
ot_try_bind ( serverip , 6969 , FLAG_UDP ) ;
2007-03-16 23:37:04 +00:00
}
2007-04-02 17:26:40 +00:00
2024-04-15 23:08:16 +02:00
defaul_signal_handlers ( ) ;
2010-12-11 15:50:56 +00:00
# ifdef WANT_SYSLOGS
2024-04-15 00:39:02 +02:00
openlog ( " opentracker " , 0 , LOG_USER ) ;
2010-12-11 15:50:56 +00:00
setlogmask ( LOG_UPTO ( LOG_INFO ) ) ;
# endif
2024-04-15 00:39:02 +02:00
if ( drop_privileges ( g_serveruser ? g_serveruser : " nobody " , g_serverdir ) = = - 1 )
panic ( " drop_privileges failed, exiting. Last error " ) ;
2007-01-14 20:15:04 +00:00
2024-04-15 00:39:02 +02:00
g_now_seconds = time ( NULL ) ;
pthread_create ( & thread_id , NULL , time_caching_worker , NULL ) ;
2007-12-04 18:29:34 +00:00
2009-01-16 02:26:50 +00:00
/* Create our self pipe which allows us to interrupt mainloops
io_wait in case some data is available to send out */
2024-04-15 00:39:02 +02:00
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 ] ) ;
2009-01-02 08:57:53 +00:00
/* Init all sub systems. This call may fail with an exit() */
2024-04-15 00:39:02 +02:00
trackerlogic_init ( ) ;
2010-08-17 01:06:22 +00:00
2024-04-13 12:26:08 +02:00
# ifdef _DEBUG_RANDOMTORRENTS
2024-04-14 17:34:03 +02:00
fprintf ( stderr , " DEBUG: Generating %d random peers on random torrents. This may take a while. (Setting RANDOMTORRENTS in trackerlogic.h) \n " , RANDOMTORRENTS ) ;
2024-04-14 17:31:16 +02:00
trackerlogic_add_random_torrents ( RANDOMTORRENTS ) ;
2024-04-14 17:32:18 +02:00
fprintf ( stderr , " ... done. \n " ) ;
2024-04-13 12:26:08 +02:00
# endif
2024-04-15 00:39:02 +02:00
if ( statefile )
load_state ( statefile ) ;
2010-08-17 01:06:22 +00:00
2024-04-15 00:39:02 +02:00
install_signal_handlers ( ) ;
2007-01-08 00:57:35 +00:00
2024-04-15 00:39:02 +02:00
if ( ! g_udp_workers )
udp_init ( - 1 , 0 ) ;
2014-10-07 00:32:07 +02:00
2024-04-15 00:39:02 +02:00
server_mainloop ( 0 ) ;
2007-01-08 00:57:35 +00:00
return 0 ;
2006-12-05 12:56:56 +00:00
}