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.
809 lines
22 KiB
C
809 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 <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);
|
|
}
|
|
|
|
defaul_signal_handlers();
|
|
|
|
#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]);
|
|
|
|
/* 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;
|
|
}
|