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.
opentracker/opentracker.c

810 lines
23 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 <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <pthread.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#ifdef WANT_SYSLOGS
#include <syslog.h>
#endif
/* Libowfat */
#include "byte.h"
#include "io.h"
#include "iob.h"
#include "ip6.h"
#include "scan.h"
#include "socket.h"
/* Opentracker */
#include "ot_accesslist.h"
#include "ot_http.h"
#include "ot_livesync.h"
#include "ot_mutex.h"
#include "ot_stats.h"
#include "ot_udp.h"
#include "trackerlogic.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
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");
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");
}
#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) {
if (cookie->batch[i].bytesleft) {
int64 res = iob_send(sock, cookie->batch + i);
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)
io_dontwantwrite(sock);
else
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);
}
return NULL;
}
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
fprintf(stderr, "DEBUG: Generating %d random peers on random torrents. This may take a while. (Setting RANDOMTORRENTS in trackerlogic.h)\n", RANDOMTORRENTS);
trackerlogic_add_random_torrents(RANDOMTORRENTS);
fprintf(stderr, "... done.\n");
#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";