mirror of git://erdgeist.org/opentracker
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
808 lines
23 KiB
808 lines
23 KiB
/* 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); |
|
} |
|
} |
|
|
|
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";
|
|
|