/* 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;
}
