Imported Upstream version 20141007 upstream
authorkaliko <kaliko@azylum.org>
Thu, 5 Feb 2015 17:14:08 +0000 (18:14 +0100)
committerkaliko <kaliko@azylum.org>
Thu, 5 Feb 2015 17:14:08 +0000 (18:14 +0100)
38 files changed:
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
README_v6 [new file with mode: 0644]
opentracker.c [new file with mode: 0644]
opentracker.conf.sample [new file with mode: 0644]
opentracker.xcodeproj/project.pbxproj [new file with mode: 0644]
ot_accesslist.c [new file with mode: 0644]
ot_accesslist.h [new file with mode: 0644]
ot_clean.c [new file with mode: 0644]
ot_clean.h [new file with mode: 0644]
ot_fullscrape.c [new file with mode: 0644]
ot_fullscrape.h [new file with mode: 0644]
ot_http.c [new file with mode: 0644]
ot_http.h [new file with mode: 0644]
ot_iovec.c [new file with mode: 0644]
ot_iovec.h [new file with mode: 0644]
ot_livesync.c [new file with mode: 0644]
ot_livesync.h [new file with mode: 0644]
ot_mutex.c [new file with mode: 0644]
ot_mutex.h [new file with mode: 0644]
ot_rijndael.c [new file with mode: 0644]
ot_rijndael.h [new file with mode: 0644]
ot_stats.c [new file with mode: 0644]
ot_stats.h [new file with mode: 0644]
ot_sync.c [new file with mode: 0644]
ot_sync.h [new file with mode: 0644]
ot_udp.c [new file with mode: 0644]
ot_udp.h [new file with mode: 0644]
ot_vector.c [new file with mode: 0644]
ot_vector.h [new file with mode: 0644]
proxy.c [new file with mode: 0644]
scan_urlencoded_query.c [new file with mode: 0644]
scan_urlencoded_query.h [new file with mode: 0644]
sync_daemon.pl [new file with mode: 0644]
tests/testsuite.sh [new file with mode: 0644]
tests/testsuite2.sh [new file with mode: 0644]
trackerlogic.c [new file with mode: 0644]
trackerlogic.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..da2c8f1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,87 @@
+# $Id$
+
+CC?=gcc
+
+# Linux flavour
+# PREFIX?=/opt/diet
+# LIBOWFAT_HEADERS=$(PREFIX)/include
+# LIBOWFAT_LIBRARY=$(PREFIX)/lib
+
+# BSD flavour
+# PREFIX?=/usr/local
+# LIBOWFAT_HEADERS=$(PREFIX)/include/libowfat
+# LIBOWFAT_LIBRARY=$(PREFIX)/lib
+
+# Debug flavour
+PREFIX?=..
+LIBOWFAT_HEADERS=$(PREFIX)/libowfat
+LIBOWFAT_LIBRARY=$(PREFIX)/libowfat
+
+BINDIR?=$(PREFIX)/bin
+
+#FEATURES+=-DWANT_V6
+
+#FEATURES+=-DWANT_ACCESSLIST_BLACK
+#FEATURES+=-DWANT_ACCESSLIST_WHITE
+
+#FEATURES+=-DWANT_SYNC_LIVE
+#FEATURES+=-DWANT_IP_FROM_QUERY_STRING
+#FEATURES+=-DWANT_COMPRESSION_GZIP
+#FEATURES+=-DWANT_COMPRESSION_GZIP_ALWAYS
+#FEATURES+=-DWANT_LOG_NETWORKS
+#FEATURES+=-DWANT_RESTRICT_STATS
+#FEATURES+=-DWANT_IP_FROM_PROXY
+#FEATURES+=-DWANT_FULLLOG_NETWORKS
+#FEATURES+=-DWANT_LOG_NUMWANT
+#FEATURES+=-DWANT_MODEST_FULLSCRAPES
+#FEATURES+=-DWANT_SPOT_WOODPECKER
+#FEATURES+=-DWANT_SYSLOGS
+#FEATURES+=-DWANT_DEV_RANDOM
+FEATURES+=-DWANT_FULLSCRAPE
+
+#FEATURES+=-D_DEBUG_HTTPERROR
+
+OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage
+OPTS_production=-O3
+
+CFLAGS+=-I$(LIBOWFAT_HEADERS) -Wall -pipe -Wextra #-ansi -pedantic
+LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread -lpthread -lz
+
+BINARY =opentracker
+HEADERS=trackerlogic.h scan_urlencoded_query.h ot_mutex.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h
+SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_mutex.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c
+SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c
+
+OBJECTS = $(SOURCES:%.c=%.o)
+OBJECTS_debug = $(SOURCES:%.c=%.debug.o)
+OBJECTS_proxy = $(SOURCES_proxy:%.c=%.o)
+OBJECTS_proxy_debug = $(SOURCES_proxy:%.c=%.debug.o)
+
+.SUFFIXES: .debug.o .o .c
+
+all: $(BINARY) $(BINARY).debug
+
+CFLAGS_production = $(CFLAGS) $(OPTS_production) $(FEATURES)
+CFLAGS_debug = $(CFLAGS) $(OPTS_debug) $(FEATURES)
+
+$(BINARY): $(OBJECTS) $(HEADERS)
+       $(CC) -o $@ $(OBJECTS) $(LDFLAGS)
+       strip $@
+$(BINARY).debug: $(OBJECTS_debug) $(HEADERS)
+       $(CC) -o $@ $(OBJECTS_debug) $(LDFLAGS)
+proxy: $(OBJECTS_proxy) $(HEADERS)
+       $(CC) -o $@ $(OBJECTS_proxy) $(CFLAGS_production) $(LDFLAGS)
+proxy.debug: $(OBJECTS_proxy_debug) $(HEADERS)
+       $(CC) -o $@ $(OBJECTS_proxy_debug) $(LDFLAGS)
+
+.c.debug.o : $(HEADERS)
+       $(CC) -c -o $@ $(CFLAGS_debug) $(<:.debug.o=.c)
+
+.c.o : $(HEADERS)
+       $(CC) -c -o $@ $(CFLAGS_production) $<
+
+clean:
+       rm -rf opentracker opentracker.debug *.o *~
+
+install:
+       install -m 755 opentracker $(BINDIR)
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..bb8a06e
--- /dev/null
+++ b/README
@@ -0,0 +1,30 @@
+This is opentracker. An open bittorrent tracker.
+
+You need libowfat (http://www.fefe.de/libowfat/).
+
+Steps to go:
+
+cvs -d :pserver:cvs@cvs.fefe.de:/cvs -z9 co libowfat
+cd libowfat
+make
+cd ..
+cvs -d:pserver:anoncvs@cvs.erdgeist.org:/home/cvsroot co opentracker
+cd opentracker
+make
+./opentracker
+
+This tracker is open in a sense that everyone announcing a torrent is welcome to do so and will be informed about anyone else announcing the same torrent. Unless
+-DWANT_IP_FROM_QUERY_STRING is enabled (which is meant for debugging purposes only), only source IPs are accepted. The tracker implements a minimal set of
+essential features only but was able respond to far more than 10000 requests per second on a Sun Fire 2200 M2 (thats where we found no more clients able to fire
+more of our testsuite.sh script).
+
+Some tweaks you may want to try under FreeBSD:
+
+sysctl kern.ipc.somaxconn=1024
+sysctl kern.ipc.nmbclusters=32768
+sysctl net.inet.tcp.msl=10000
+sysctl kern.maxfiles=10240
+
+License information:
+
+Although the libowfat library is under GPL, Felix von Leitner agreed that the compiled binary may be distributed under the same beer ware license as the source code for opentracker. However, we like to hear from happy customers.
diff --git a/README_v6 b/README_v6
new file mode 100644 (file)
index 0000000..7fac79a
--- /dev/null
+++ b/README_v6
@@ -0,0 +1 @@
+IPv6 is implemented in opentracker now. You can chose whether your tracker runs in v4 or v6 mode in Makefile. YMMV.
diff --git a/opentracker.c b/opentracker.c
new file mode 100644 (file)
index 0000000..40ed78e
--- /dev/null
@@ -0,0 +1,665 @@
+/* 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 <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <pwd.h>
+#include <ctype.h>
+#include <pthread.h>
+#ifdef WANT_SYSLOGS
+#include <syslog.h>
+#endif
+
+/* Libowfat */
+#include "socket.h"
+#include "io.h"
+#include "iob.h"
+#include "byte.h"
+#include "scan.h"
+#include "ip6.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_mutex.h"
+#include "ot_http.h"
+#include "ot_udp.h"
+#include "ot_accesslist.h"
+#include "ot_stats.h"
+#include "ot_livesync.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 ) {
+  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 );
+  } else if( s == SIGALRM ) {
+    /* Maintain our copy of the clock. time() on BSDs is very expensive. */
+    g_now_seconds = time(NULL);
+    alarm(5);
+  }
+}
+
+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);
+  sigaddset (&signal_mask, SIGALRM);
+  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] [-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 (default: *, you may specify more than one)");
+  HELPLINE("-p port","specify tcp port to bind to (default: 6969, you may specify more than one)");
+  HELPLINE("-P port","specify udp port to bind to (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 priviliges opentracker should run (default: \"nobody\")");
+  HELPLINE("-A ip","bless an ip address 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" );
+}
+#undef HELPLINE
+
+static size_t header_complete( char * request, ssize_t byte_count ) {
+  int 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 ) {
+    iob_reset( &cookie->batch );
+    array_reset( &cookie->request );
+    if( cookie->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK )
+      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;
+
+  if( ( byte_count = io_tryread( sock, ws->inbuf, G_INBUF_SIZE ) ) <= 0 ) {
+    handle_dead( sock );
+    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, 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 );
+  if( !cookie || ( iob_send( sock, &cookie->batch ) <= 0 ) )
+    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 );
+  }
+}
+
+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;
+
+  (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" );
+
+  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 ) ) != -1 )
+      http_sendiovecdata( sock, &ws, iovec_entries, iovector );
+
+    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();
+
+    /* Enforce setting the clock */
+    signal_handler( SIGALRM );
+  }
+  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( );
+
+#ifndef WANT_V6
+  if( !ip6_isv4mapped(ip) ) {
+    exerr( "V4 Tracker is V4 only!" );
+  }
+#else
+  if( ip6_isv4mapped(ip) ) {
+    exerr( "V6 Tracker is V6 only!" );
+  }
+#endif
+
+#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;
+}
+
+int parse_configfile( char * config_filename ) {
+  FILE *  accesslist_filehandle;
+  char    inbuf[512];
+  ot_ip6  tmpip;
+  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_RESTRICT_STATS
+    } else if(!byte_diff(p, 12, "access.stats" ) && isspace(p[12])) {
+      if( !scan_ip6( p+13, tmpip )) goto parse_error;
+      accesslist_blessip( tmpip, 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( p+13, tmpip )) goto parse_error;
+      accesslist_blessip( tmpip, 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( p+25, tmpip )) goto parse_error;
+      accesslist_blessip( tmpip, 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 );
+      setegid( (gid_t)-2 ); setgid( (gid_t)-2 );
+      setuid( (uid_t)-2 );  seteuid( (uid_t)-2 );
+    }
+    else {
+      setegid( pws->pw_gid ); setgid( pws->pw_gid );
+      setuid( pws->pw_uid );  seteuid( pws->pw_uid );
+    }
+
+    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;
+}
+
+int main( int argc, char **argv ) {
+  ot_ip6 serverip, tmpip;
+  int bound = 0, scanon = 1;
+  uint16_t tmpport;
+  char * statefile = 0;
+
+  memset( serverip, 0, sizeof(ot_ip6) );
+#ifndef WANT_V6
+  serverip[10]=serverip[11]=-1;
+  noipv6=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( optarg, tmpip )) { usage( argv[0] ); exit( 1 ); }
+        accesslist_blessip( tmpip, 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 );
+
+  /* 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: " );
+  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( );
+
+  if( statefile )
+    load_state( statefile );
+
+  install_signal_handlers( );
+
+  if( !g_udp_workers )
+    udp_init( -1, 0 );
+
+  /* Kick off our initial clock setting alarm */
+  alarm(5);
+
+  server_mainloop( 0 );
+
+  return 0;
+}
+
+const char *g_version_opentracker_c = "$Source$: $Revision$\n";
diff --git a/opentracker.conf.sample b/opentracker.conf.sample
new file mode 100644 (file)
index 0000000..db45122
--- /dev/null
@@ -0,0 +1,106 @@
+# opentracker config file
+#
+
+# I)   Address opentracker will listen on, using both, tcp AND udp family
+#      (note, that port 6969 is implicite if ommitted).
+#
+#      If no listen option is given (here or on the command line), opentracker
+#      listens on 0.0.0.0:6969 tcp and udp.
+#
+#      The next variable determines if udp sockets are handled in the event
+#      loop (set it to 0, the default) or are handled in blocking reads in
+#      dedicated worker threads. You have to set this value before the
+#      listen.tcp_udp or listen.udp statements before it takes effect, but you
+#      can re-set it for each listen statement. Normally you should keep it at
+#      the top of the config file.
+#
+# listen.udp.workers 4
+#
+# listen.tcp_udp 0.0.0.0
+# listen.tcp_udp 192.168.0.1:80
+# listen.tcp_udp 10.0.0.5:6969
+#
+#      To only listen on tcp or udp family ports, list them this way:
+#
+# listen.tcp 0.0.0.0
+# listen.udp 192.168.0.1:6969
+#
+#      Note, that using 0.0.0.0 for udp sockets may yield surprising results.
+#      An answer packet sent on that socket will not necessarily have the
+#      source address that the requesting client may expect, but any address
+#      on that interface.
+#
+
+# II)  If opentracker runs in a non-open mode, point it to files containing
+#      all torrent hashes that it will serve (shell option -w)
+#
+# access.whitelist /path/to/whitelist
+#
+#      or, if opentracker was compiled to allow blacklisting (shell option -b)
+#
+# access.blacklist ./blacklist
+#
+#      It is pointless and hence not possible to compile black AND white
+#      listing, so choose one of those options at compile time. File format
+#      is straight forward: "<hex info hash>\n<hex info hash>\n..."
+#
+#      If you do not want to grant anyone access to your stats, enable the
+#      WANT_RESTRICT_STATS option in Makefile and bless the ip addresses
+#      allowed to fetch stats here.
+#
+# access.stats 192.168.0.23
+#
+#      There is another way of hiding your stats. You can obfuscate the path
+#      to them. Normally it is located at /stats but you can configure it to
+#      appear anywhere on your tracker.
+#
+# access.stats_path stats
+
+# III) Live sync uses udp multicast packets to keep a cluster of opentrackers
+#      synchronized. This option tells opentracker which port to listen for
+#      incoming live sync packets. The ip address tells opentracker, on which
+#      interface to join the multicast group, those packets will arrive.
+#      (shell option -i 192.168.0.1 -s 9696), port 9696 is default.
+#
+# livesync.cluster.listen 192.168.0.1:9696
+#
+#      Note that two udp sockets will be opened. One on ip address 0.0.0.0
+#      port 9696, that will join the multicast group 224.0.42.23 for incoming
+#      udp packets and one on ip address 192.168.0.1 port 9696 for outgoing
+#      udp packets.
+#
+#      As of now one and only one ip address must be given, if opentracker
+#      was built with the WANT_SYNC_LIVE feature.
+#
+
+# IV)  Sync between trackers running in a cluster is restricted to packets
+#      coming from trusted ip addresses. While source ip verification is far
+#      from perfect, the authors of opentracker trust in the correct
+#      application of tunnels, filters and LAN setups (shell option -A).
+#
+# livesync.cluster.node_ip 192.168.0.4
+# livesync.cluster.node_ip 192.168.0.5
+# livesync.cluster.node_ip 192.168.0.6
+#
+#      This is the admin ip address for old style (HTTP based) asynchronus
+#      tracker syncing.
+#
+# batchsync.cluster.admin_ip 10.1.1.1
+#
+
+# V)   Control privilege drop behaviour.
+#      Put in the directory opentracker will chroot/chdir to. All black/white
+#      list files must be put in that directory (shell option -d).
+#
+#
+# tracker.rootdir /usr/local/etc/opentracker
+#
+#      Tell opentracker which user to setuid to.
+#
+# tracker.user    nobody
+#
+
+# VI)  opentracker can be told to answer to a "GET / HTTP"-request with a
+#      redirect to another location (shell option -r).
+#
+# tracker.redirect_url https://your.tracker.local/
diff --git a/opentracker.xcodeproj/project.pbxproj b/opentracker.xcodeproj/project.pbxproj
new file mode 100644 (file)
index 0000000..3706a3f
--- /dev/null
@@ -0,0 +1,320 @@
+// !$*UTF8*$!
+{
+       archiveVersion = 1;
+       classes = {
+       };
+       objectVersion = 44;
+       objects = {
+
+/* Begin PBXBuildFile section */
+               6520B7530D036AAF00A43B1F /* libowfat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6520B7520D036AAF00A43B1F /* libowfat.a */; };
+               653A320C0CE7F475007F0D03 /* ot_accesslist.c in Sources */ = {isa = PBXBuildFile; fileRef = 653A320B0CE7F475007F0D03 /* ot_accesslist.c */; };
+               653A56B50CE28EC5000CF140 /* ot_iovec.c in Sources */ = {isa = PBXBuildFile; fileRef = 653A56B40CE28EC5000CF140 /* ot_iovec.c */; };
+               654A80890CD832FD009035DE /* opentracker.c in Sources */ = {isa = PBXBuildFile; fileRef = 654A80840CD832FC009035DE /* opentracker.c */; };
+               654A808A0CD832FD009035DE /* scan_urlencoded_query.c in Sources */ = {isa = PBXBuildFile; fileRef = 654A80850CD832FC009035DE /* scan_urlencoded_query.c */; };
+               654A808B0CD832FD009035DE /* trackerlogic.c in Sources */ = {isa = PBXBuildFile; fileRef = 654A80870CD832FC009035DE /* trackerlogic.c */; };
+               65542D8B0CE078E800469330 /* ot_vector.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542D8A0CE078E800469330 /* ot_vector.c */; };
+               65542D930CE07CED00469330 /* ot_mutex.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542D8F0CE07CED00469330 /* ot_mutex.c */; };
+               65542D940CE07CED00469330 /* ot_stats.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542D910CE07CED00469330 /* ot_stats.c */; };
+               65542E750CE08B9100469330 /* ot_clean.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542E740CE08B9100469330 /* ot_clean.c */; };
+               65542EE80CE0CA6B00469330 /* ot_udp.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542EE70CE0CA6B00469330 /* ot_udp.c */; };
+               65542F920CE17CA900469330 /* ot_fullscrape.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542F910CE17CA900469330 /* ot_fullscrape.c */; };
+               65B8DF3C0D0310D20017149E /* ot_http.c in Sources */ = {isa = PBXBuildFile; fileRef = 65B8DF3B0D0310D20017149E /* ot_http.c */; };
+               65FA33990E7EF09200F7D5A5 /* ot_livesync.c in Sources */ = {isa = PBXBuildFile; fileRef = 65FA33980E7EF09200F7D5A5 /* ot_livesync.c */; };
+               8DD76FB00486AB0100D96B5E /* opentracker.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6A0FF2C0290799A04C91782 /* opentracker.1 */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+               8DD76FAF0486AB0100D96B5E /* CopyFiles */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 8;
+                       dstPath = /usr/share/man/man1/;
+                       dstSubfolderSpec = 0;
+                       files = (
+                               8DD76FB00486AB0100D96B5E /* opentracker.1 in CopyFiles */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+               6520B7520D036AAF00A43B1F /* libowfat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libowfat.a; path = ../libowfat/libowfat.a; sourceTree = SOURCE_ROOT; };
+               653A320A0CE7F475007F0D03 /* ot_accesslist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_accesslist.h; sourceTree = "<group>"; };
+               653A320B0CE7F475007F0D03 /* ot_accesslist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_accesslist.c; sourceTree = "<group>"; };
+               653A56AC0CE201FF000CF140 /* opentracker */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = opentracker; sourceTree = BUILT_PRODUCTS_DIR; };
+               653A56B30CE28EC5000CF140 /* ot_iovec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_iovec.h; sourceTree = "<group>"; };
+               653A56B40CE28EC5000CF140 /* ot_iovec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_iovec.c; sourceTree = "<group>"; };
+               654A80840CD832FC009035DE /* opentracker.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = opentracker.c; sourceTree = "<group>"; };
+               654A80850CD832FC009035DE /* scan_urlencoded_query.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = scan_urlencoded_query.c; sourceTree = "<group>"; };
+               654A80860CD832FC009035DE /* scan_urlencoded_query.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scan_urlencoded_query.h; sourceTree = "<group>"; };
+               654A80870CD832FC009035DE /* trackerlogic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = trackerlogic.c; sourceTree = "<group>"; };
+               654A80880CD832FC009035DE /* trackerlogic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = trackerlogic.h; sourceTree = "<group>"; };
+               65542D890CE078E800469330 /* ot_vector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_vector.h; sourceTree = "<group>"; };
+               65542D8A0CE078E800469330 /* ot_vector.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_vector.c; sourceTree = "<group>"; };
+               65542D8F0CE07CED00469330 /* ot_mutex.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_mutex.c; sourceTree = "<group>"; };
+               65542D900CE07CED00469330 /* ot_mutex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_mutex.h; sourceTree = "<group>"; };
+               65542D910CE07CED00469330 /* ot_stats.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_stats.c; sourceTree = "<group>"; };
+               65542D920CE07CED00469330 /* ot_stats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_stats.h; sourceTree = "<group>"; };
+               65542E730CE08B9100469330 /* ot_clean.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_clean.h; sourceTree = "<group>"; };
+               65542E740CE08B9100469330 /* ot_clean.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_clean.c; sourceTree = "<group>"; };
+               65542EE60CE0CA6B00469330 /* ot_udp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_udp.h; sourceTree = "<group>"; };
+               65542EE70CE0CA6B00469330 /* ot_udp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_udp.c; sourceTree = "<group>"; };
+               65542F900CE17CA900469330 /* ot_fullscrape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_fullscrape.h; sourceTree = "<group>"; };
+               65542F910CE17CA900469330 /* ot_fullscrape.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_fullscrape.c; sourceTree = "<group>"; };
+               65B8DF3A0D0310D20017149E /* ot_http.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_http.h; sourceTree = "<group>"; };
+               65B8DF3B0D0310D20017149E /* ot_http.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_http.c; sourceTree = "<group>"; };
+               65FA33970E7EF09200F7D5A5 /* ot_livesync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_livesync.h; sourceTree = "<group>"; };
+               65FA33980E7EF09200F7D5A5 /* ot_livesync.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_livesync.c; sourceTree = "<group>"; };
+               C6A0FF2C0290799A04C91782 /* opentracker.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = opentracker.1; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+               8DD76FAD0486AB0100D96B5E /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               6520B7530D036AAF00A43B1F /* libowfat.a in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+               08FB7794FE84155DC02AAC07 /* opentracker */ = {
+                       isa = PBXGroup;
+                       children = (
+                               65542D810CE0786F00469330 /* Headers */,
+                               08FB7795FE84155DC02AAC07 /* Source */,
+                               92762AC9104EDED700FDCB60 /* Libraries */,
+                               C6A0FF2B0290797F04C91782 /* Documentation */,
+                               653A56AD0CE201FF000CF140 /* Products */,
+                       );
+                       name = opentracker;
+                       sourceTree = "<group>";
+               };
+               08FB7795FE84155DC02AAC07 /* Source */ = {
+                       isa = PBXGroup;
+                       children = (
+                               654A80840CD832FC009035DE /* opentracker.c */,
+                               653A320B0CE7F475007F0D03 /* ot_accesslist.c */,
+                               65542E740CE08B9100469330 /* ot_clean.c */,
+                               65542F910CE17CA900469330 /* ot_fullscrape.c */,
+                               65B8DF3B0D0310D20017149E /* ot_http.c */,
+                               65FA33980E7EF09200F7D5A5 /* ot_livesync.c */,
+                               653A56B40CE28EC5000CF140 /* ot_iovec.c */,
+                               65542D8F0CE07CED00469330 /* ot_mutex.c */,
+                               65542D910CE07CED00469330 /* ot_stats.c */,
+                               65542EE70CE0CA6B00469330 /* ot_udp.c */,
+                               65542D8A0CE078E800469330 /* ot_vector.c */,
+                               654A80850CD832FC009035DE /* scan_urlencoded_query.c */,
+                               654A80870CD832FC009035DE /* trackerlogic.c */,
+                       );
+                       name = Source;
+                       sourceTree = "<group>";
+               };
+               653A56AD0CE201FF000CF140 /* Products */ = {
+                       isa = PBXGroup;
+                       children = (
+                               653A56AC0CE201FF000CF140 /* opentracker */,
+                       );
+                       name = Products;
+                       sourceTree = "<group>";
+               };
+               65542D810CE0786F00469330 /* Headers */ = {
+                       isa = PBXGroup;
+                       children = (
+                               653A320A0CE7F475007F0D03 /* ot_accesslist.h */,
+                               65542E730CE08B9100469330 /* ot_clean.h */,
+                               65542F900CE17CA900469330 /* ot_fullscrape.h */,
+                               65B8DF3A0D0310D20017149E /* ot_http.h */,
+                               653A56B30CE28EC5000CF140 /* ot_iovec.h */,
+                               65FA33970E7EF09200F7D5A5 /* ot_livesync.h */,
+                               65542D900CE07CED00469330 /* ot_mutex.h */,
+                               65542D920CE07CED00469330 /* ot_stats.h */,
+                               65542EE60CE0CA6B00469330 /* ot_udp.h */,
+                               65542D890CE078E800469330 /* ot_vector.h */,
+                               654A80860CD832FC009035DE /* scan_urlencoded_query.h */,
+                               654A80880CD832FC009035DE /* trackerlogic.h */,
+                       );
+                       name = Headers;
+                       sourceTree = "<group>";
+               };
+               92762AC9104EDED700FDCB60 /* Libraries */ = {
+                       isa = PBXGroup;
+                       children = (
+                               6520B7520D036AAF00A43B1F /* libowfat.a */,
+                       );
+                       name = Libraries;
+                       sourceTree = "<group>";
+               };
+               C6A0FF2B0290797F04C91782 /* Documentation */ = {
+                       isa = PBXGroup;
+                       children = (
+                               C6A0FF2C0290799A04C91782 /* opentracker.1 */,
+                       );
+                       name = Documentation;
+                       sourceTree = "<group>";
+               };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+               8DD76FA90486AB0100D96B5E /* opentracker */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "opentracker" */;
+                       buildPhases = (
+                               8DD76FAB0486AB0100D96B5E /* Sources */,
+                               8DD76FAD0486AB0100D96B5E /* Frameworks */,
+                               8DD76FAF0486AB0100D96B5E /* CopyFiles */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = opentracker;
+                       productInstallPath = "$(HOME)/bin";
+                       productName = opentracker;
+                       productReference = 653A56AC0CE201FF000CF140 /* opentracker */;
+                       productType = "com.apple.product-type.tool";
+               };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+               08FB7793FE84155DC02AAC07 /* Project object */ = {
+                       isa = PBXProject;
+                       buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "opentracker" */;
+                       compatibilityVersion = "Xcode 3.0";
+                       hasScannedForEncodings = 1;
+                       mainGroup = 08FB7794FE84155DC02AAC07 /* opentracker */;
+                       productRefGroup = 653A56AD0CE201FF000CF140 /* Products */;
+                       projectDirPath = "";
+                       projectRoot = "";
+                       targets = (
+                               8DD76FA90486AB0100D96B5E /* opentracker */,
+                       );
+               };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+               8DD76FAB0486AB0100D96B5E /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               654A80890CD832FD009035DE /* opentracker.c in Sources */,
+                               654A808A0CD832FD009035DE /* scan_urlencoded_query.c in Sources */,
+                               654A808B0CD832FD009035DE /* trackerlogic.c in Sources */,
+                               65542D8B0CE078E800469330 /* ot_vector.c in Sources */,
+                               65542D930CE07CED00469330 /* ot_mutex.c in Sources */,
+                               65542D940CE07CED00469330 /* ot_stats.c in Sources */,
+                               65542E750CE08B9100469330 /* ot_clean.c in Sources */,
+                               65542EE80CE0CA6B00469330 /* ot_udp.c in Sources */,
+                               65542F920CE17CA900469330 /* ot_fullscrape.c in Sources */,
+                               653A56B50CE28EC5000CF140 /* ot_iovec.c in Sources */,
+                               653A320C0CE7F475007F0D03 /* ot_accesslist.c in Sources */,
+                               65B8DF3C0D0310D20017149E /* ot_http.c in Sources */,
+                               65FA33990E7EF09200F7D5A5 /* ot_livesync.c in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+               1DEB928608733DD80010E9CD /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               COPY_PHASE_STRIP = NO;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_ENABLE_FIX_AND_CONTINUE = YES;
+                               GCC_MODEL_TUNING = G5;
+                               GCC_OPTIMIZATION_LEVEL = 0;
+                               INSTALL_PATH = /usr/local/bin;
+                               LIBRARY_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "\"$(SRCROOT)/../libowfat\"",
+                               );
+                               PRODUCT_NAME = opentracker;
+                               ZERO_LINK = YES;
+                       };
+                       name = Debug;
+               };
+               1DEB928708733DD80010E9CD /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               GCC_ENABLE_FIX_AND_CONTINUE = YES;
+                               GCC_MODEL_TUNING = G5;
+                               INSTALL_PATH = /usr/local/bin;
+                               LIBRARY_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "\"$(SRCROOT)/../libowfat\"",
+                               );
+                               PRODUCT_NAME = opentracker;
+                       };
+                       name = Release;
+               };
+               1DEB928A08733DD80010E9CD /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ARCHS = "$(ONLY_ACTIVE_ARCH_PRE_XCODE_3_1)";
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       WANT_ACCESSLIST_WHITE,
+                                       WANT_IP_FROM_QUERY_STRING,
+                                       WANT_FULLSCRAPE,
+                               );
+                               GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               HEADER_SEARCH_PATHS = ../libowfat/;
+                               LIBRARY_SEARCH_PATHS = ../libowfat/;
+                               ONLY_ACTIVE_ARCH_PRE_XCODE_3_1 = "$(NATIVE_ARCH_ACTUAL)";
+                               PREBINDING = NO;
+                               SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.6.sdk";
+                       };
+                       name = Debug;
+               };
+               1DEB928B08733DD80010E9CD /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ARCHS = "$(ONLY_ACTIVE_ARCH_PRE_XCODE_3_1)";
+                               DEAD_CODE_STRIPPING = NO;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       WANT_ACCESSLIST_WHITE,
+                                       WANT_IP_FROM_QUERY_STRING,
+                                       WANT_FULLSCRAPE,
+                               );
+                               GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               HEADER_SEARCH_PATHS = ../libowfat/;
+                               LIBRARY_SEARCH_PATHS = ../libowfat/;
+                               ONLY_ACTIVE_ARCH_PRE_XCODE_3_1 = "$(NATIVE_ARCH_ACTUAL)";
+                               PREBINDING = NO;
+                               SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.6.sdk";
+                               ZERO_LINK = NO;
+                       };
+                       name = Release;
+               };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+               1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "opentracker" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               1DEB928608733DD80010E9CD /* Debug */,
+                               1DEB928708733DD80010E9CD /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "opentracker" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               1DEB928A08733DD80010E9CD /* Debug */,
+                               1DEB928B08733DD80010E9CD /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+/* End XCConfigurationList section */
+       };
+       rootObject = 08FB7793FE84155DC02AAC07 /* Project object */;
+}
diff --git a/ot_accesslist.c b/ot_accesslist.c
new file mode 100644 (file)
index 0000000..cdb964d
--- /dev/null
@@ -0,0 +1,313 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* System */
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+
+/* Libowfat */
+#include "byte.h"
+#include "scan.h"
+#include "ip6.h"
+#include "mmap.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_accesslist.h"
+#include "ot_vector.h"
+
+/* GLOBAL VARIABLES */
+#ifdef WANT_ACCESSLIST
+       char    *g_accesslist_filename;
+static ot_hash *g_accesslist;
+static size_t   g_accesslist_size;
+static pthread_mutex_t g_accesslist_mutex;
+
+static int vector_compare_hash(const void *hash1, const void *hash2 ) {
+  return memcmp( hash1, hash2, OT_HASH_COMPARE_SIZE );
+}
+
+/* Read initial access list */
+static void accesslist_readfile( void ) {
+  ot_hash *info_hash, *accesslist_new = NULL;
+  char    *map, *map_end, *read_offs;
+  size_t   maplen;
+
+  if( ( map = mmap_read( g_accesslist_filename, &maplen ) ) == NULL ) {
+    char *wd = getcwd( NULL, 0 );
+    fprintf( stderr, "Warning: Can't open accesslist file: %s (but will try to create it later, if necessary and possible).\nPWD: %s\n", g_accesslist_filename, wd );
+    free( wd );
+    return;
+  }
+
+  /* You need at least 41 bytes to pass an info_hash, make enough room
+     for the maximum amount of them */
+  info_hash = accesslist_new = malloc( ( maplen / 41 ) * 20 );
+  if( !accesslist_new ) {
+    fprintf( stderr, "Warning: Not enough memory to allocate %zd bytes for accesslist buffer. May succeed later.\n", ( maplen / 41 ) * 20 );
+    return;
+  }
+
+  /* No use to scan if there's not enough room for another full info_hash */
+  map_end = map + maplen - 40;
+  read_offs = map;
+
+  /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*" */
+  while( read_offs <= map_end ) {
+    int i;
+    for( i=0; i<(int)sizeof(ot_hash); ++i ) {
+      int eger1 = scan_fromhex( read_offs[ 2*i ] );
+      int eger2 = scan_fromhex( read_offs[ 1 + 2*i ] );
+      if( eger1 < 0 || eger2 < 0 )
+        break;
+      (*info_hash)[i] = eger1 * 16 + eger2;
+    }
+
+    if( i == sizeof(ot_hash) ) {
+      read_offs += 40;
+
+      /* Append accesslist to accesslist vector */
+      if( read_offs == map_end || scan_fromhex( *read_offs ) < 0 )
+        ++info_hash;
+    }
+
+    /* Find start of next line */
+    while( read_offs <= map_end && *(read_offs++) != '\n' );
+  }
+#ifdef _DEBUG
+  fprintf( stderr, "Added %zd info_hashes to accesslist\n", (size_t)(info_hash - accesslist_new) );
+#endif
+
+  mmap_unmap( map, maplen);
+
+  qsort( accesslist_new, info_hash - accesslist_new, sizeof( *info_hash ), vector_compare_hash );
+
+  /* Now exchange the accesslist vector in the least race condition prone way */
+  pthread_mutex_lock(&g_accesslist_mutex);
+  free( g_accesslist );
+  g_accesslist      = accesslist_new;
+  g_accesslist_size = info_hash - accesslist_new;
+  pthread_mutex_unlock(&g_accesslist_mutex);
+}
+
+int accesslist_hashisvalid( ot_hash hash ) {
+  void *exactmatch;
+
+  /* Lock should hardly ever be contended */
+  pthread_mutex_lock(&g_accesslist_mutex);
+  exactmatch = bsearch( hash, g_accesslist, g_accesslist_size, OT_HASH_COMPARE_SIZE, vector_compare_hash );
+  pthread_mutex_unlock(&g_accesslist_mutex);
+
+#ifdef WANT_ACCESSLIST_BLACK
+  return exactmatch == NULL;
+#else
+  return exactmatch != NULL;
+#endif
+}
+
+static void * accesslist_worker( void * args ) {
+  int sig;
+  sigset_t   signal_mask;
+
+  sigemptyset(&signal_mask);
+  sigaddset(&signal_mask, SIGHUP);
+
+  (void)args;
+
+  while( 1 ) {
+
+    /* Initial attempt to read accesslist */
+    accesslist_readfile( );
+
+    /* Wait for signals */
+    while( sigwait (&signal_mask, &sig) != 0 && sig != SIGHUP );
+  }
+  return NULL;
+}
+
+static pthread_t thread_id;
+void accesslist_init( ) {
+  pthread_mutex_init(&g_accesslist_mutex, NULL);
+  pthread_create( &thread_id, NULL, accesslist_worker, NULL );
+}
+
+void accesslist_deinit( void ) {
+  pthread_cancel( thread_id );
+  pthread_mutex_destroy(&g_accesslist_mutex);
+  free( g_accesslist );
+  g_accesslist = 0;
+  g_accesslist_size = 0;
+}
+#endif
+
+int address_in_net( const ot_ip6 address, const ot_net *net ) {
+  int bits = net->bits;
+  int result = memcmp( address, &net->address, bits >> 3 );
+  if( !result && ( bits & 7 ) )
+    result = ( ( 0x7f00 >> ( bits & 7 ) ) & address[bits>>3] ) - net->address[bits>>3];
+  return result == 0;
+}
+
+void *set_value_for_net( const ot_net *net, ot_vector *vector, const void *value, const size_t member_size ) {
+  size_t i;
+  int exactmatch;
+
+  /* Caller must have a concept of ot_net in it's member */
+  if( member_size < sizeof(ot_net) )
+    return 0;
+
+  /* Check each net in vector for overlap */
+  uint8_t *member = ((uint8_t*)vector->data);
+  for( i=0; i<vector->size; ++i ) {
+    if( address_in_net( *(ot_ip6*)member, net ) ||
+        address_in_net( net->address, (ot_net*)member ) )
+      return 0;
+    member += member_size;
+  }
+
+  member = vector_find_or_insert( vector, (void*)net, member_size, sizeof(ot_net), &exactmatch );
+  if( member ) {
+    memcpy( member, net, sizeof(ot_net));
+    memcpy( member + sizeof(ot_net), value, member_size - sizeof(ot_net));
+  }
+
+  return member;
+}
+
+/* Takes a vector filled with { ot_net net, uint8_t[x] value };
+   Returns value associated with the net, or NULL if not found */
+void *get_value_for_net( const ot_ip6 address, const ot_vector *vector, const size_t member_size ) {
+  int exactmatch;
+  /* This binary search will return a pointer to the first non-containing network... */
+  ot_net *net = binary_search( address, vector->data, vector->size, member_size, sizeof(ot_ip6), &exactmatch );
+  if( !net )
+    return NULL;
+  /* ... so we'll need to move back one step unless we've exactly hit the first address in network */
+  if( !exactmatch && ( (void*)net > vector->data ) )
+    --net;
+  if( !address_in_net( address, net ) )
+    return NULL;
+  return (void*)net;
+}
+
+#ifdef WANT_FULLLOG_NETWORKS
+static ot_vector g_lognets_list;
+ot_log *g_logchain_first, *g_logchain_last;
+
+static pthread_mutex_t g_lognets_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+void loglist_add_network( const ot_net *net ) {
+  pthread_mutex_lock(&g_lognets_list_mutex);
+  set_value_for_net( net, &g_lognets_list, NULL, sizeof(ot_net));
+  pthread_mutex_unlock(&g_lognets_list_mutex);
+}
+
+void loglist_reset( ) {
+  pthread_mutex_lock(&g_lognets_list_mutex);
+  free( g_lognets_list.data );
+  g_lognets_list.data = 0;
+  g_lognets_list.size = g_lognets_list.space = 0;
+  pthread_mutex_unlock(&g_lognets_list_mutex);    
+}
+
+int loglist_check_address( const ot_ip6 address ) {
+  int result;
+  pthread_mutex_lock(&g_lognets_list_mutex);
+  result = ( NULL != get_value_for_net( address, &g_lognets_list, sizeof(ot_net)) );
+  pthread_mutex_unlock(&g_lognets_list_mutex);
+  return result;
+}
+#endif
+
+#ifdef WANT_IP_FROM_PROXY
+typedef struct {
+  ot_net    *proxy;
+  ot_vector  networks;
+} ot_proxymap;
+
+static ot_vector g_proxies_list;
+static pthread_mutex_t g_proxies_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+int proxylist_add_network( const ot_net *proxy, const ot_net *net ) {
+  ot_proxymap *map;
+  int exactmatch, result = 1;
+  pthread_mutex_lock(&g_proxies_list_mutex);
+
+  /* If we have a direct hit, use and extend the vector there */
+  map = binary_search( proxy, g_proxies_list.data, g_proxies_list.size, sizeof(ot_proxymap), sizeof(ot_net), &exactmatch );
+
+  if( !map || !exactmatch ) {
+    /* else see, if we've got overlapping networks
+       and get a new empty vector if not */
+    ot_vector empty;
+    memset( &empty, 0, sizeof( ot_vector ) );
+    map = set_value_for_net( proxy, &g_proxies_list, &empty, sizeof(ot_proxymap));
+  }
+
+  if( map && set_value_for_net( net, &map->networks, NULL, sizeof(ot_net) ) )
+       result = 1;
+
+  pthread_mutex_unlock(&g_proxies_list_mutex);
+  return result;
+}
+
+int proxylist_check_proxy( const ot_ip6 proxy, const ot_ip6 address ) {
+  int result = 0;
+  ot_proxymap *map;
+
+  pthread_mutex_lock(&g_proxies_list_mutex);
+
+  if( ( map = get_value_for_net( proxy, &g_proxies_list, sizeof(ot_proxymap) ) ) )
+    if( !address || get_value_for_net( address, &map->networks, sizeof(ot_net) ) ) 
+      result = 1;
+
+  pthread_mutex_unlock(&g_proxies_list_mutex);
+  return result;
+}
+
+#endif
+
+static ot_ip6         g_adminip_addresses[OT_ADMINIP_MAX];
+static ot_permissions g_adminip_permissions[OT_ADMINIP_MAX];
+static unsigned int   g_adminip_count = 0;
+
+int accesslist_blessip( ot_ip6 ip, ot_permissions permissions ) {
+  if( g_adminip_count >= OT_ADMINIP_MAX )
+    return -1;
+
+  memcpy(g_adminip_addresses + g_adminip_count,ip,sizeof(ot_ip6));
+  g_adminip_permissions[ g_adminip_count++ ] = permissions;
+
+#ifdef _DEBUG
+  {
+    char _debug[512];
+    int off = snprintf( _debug, sizeof(_debug), "Blessing ip address " );
+    off += fmt_ip6c(_debug+off, ip );
+
+    if( permissions & OT_PERMISSION_MAY_STAT       ) off += snprintf( _debug+off, 512-off, " may_fetch_stats" );
+    if( permissions & OT_PERMISSION_MAY_LIVESYNC   ) off += snprintf( _debug+off, 512-off, " may_sync_live" );
+    if( permissions & OT_PERMISSION_MAY_FULLSCRAPE ) off += snprintf( _debug+off, 512-off, " may_fetch_fullscrapes" );
+    if( permissions & OT_PERMISSION_MAY_PROXY      ) off += snprintf( _debug+off, 512-off, " may_proxy" );
+    if( !permissions ) off += snprintf( _debug+off, sizeof(_debug)-off, " nothing\n" );
+    _debug[off++] = '.';
+    write( 2, _debug, off );
+  }
+#endif
+
+  return 0;
+}
+
+int accesslist_isblessed( ot_ip6 ip, ot_permissions permissions ) {
+  unsigned int i;
+  for( i=0; i<g_adminip_count; ++i )
+    if( !memcmp( g_adminip_addresses + i, ip, sizeof(ot_ip6)) && ( g_adminip_permissions[ i ] & permissions ) )
+      return 1;
+  return 0;
+}
+
+const char *g_version_accesslist_c = "$Source$: $Revision$\n";
diff --git a/ot_accesslist.h b/ot_accesslist.h
new file mode 100644 (file)
index 0000000..a1e4ad2
--- /dev/null
@@ -0,0 +1,79 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_ACCESSLIST_H__
+#define __OT_ACCESSLIST_H__
+
+#if defined ( WANT_ACCESSLIST_BLACK ) && defined (WANT_ACCESSLIST_WHITE )
+#  error WANT_ACCESSLIST_BLACK and WANT_ACCESSLIST_WHITE are exclusive.
+#endif
+
+#if defined ( WANT_ACCESSLIST_BLACK ) || defined (WANT_ACCESSLIST_WHITE )
+#define WANT_ACCESSLIST
+void accesslist_init( );
+void accesslist_deinit( );
+int  accesslist_hashisvalid( ot_hash hash );
+
+extern char *g_accesslist_filename;
+
+#else
+#define accesslist_init( accesslist_filename )
+#define accesslist_deinit( )
+#define accesslist_hashisvalid( hash ) 1
+#endif
+
+/* Test if an address is subset of an ot_net, return value is considered a bool */
+int address_in_net( const ot_ip6 address, const ot_net *net );
+
+/* Store a value into a vector of struct { ot_net net, uint8_t[x] value } member;
+   returns NULL
+     if member_size is too small, or
+     if one of the nets inside the vector are a subnet of _net_, or
+     if _net_ is a subnet of one of the nets inside the vector, or
+     if the vector could not be resized
+   returns pointer to new member in vector for success
+   member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping
+*/
+void *set_value_for_net( const ot_net *net, ot_vector *vector, const void *value, const size_t member_size );
+
+/* Takes a vector filled with struct { ot_net net, uint8_t[x] value } member;
+   Returns pointer to _member_ associated with the net, or NULL if not found
+   member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping
+*/
+void *get_value_for_net( const ot_ip6 address, const ot_vector *vector, const size_t member_size );
+
+
+#ifdef WANT_IP_FROM_PROXY
+int proxylist_add_network( const ot_net *proxy, const ot_net *net );
+int proxylist_check_network( const ot_ip6 *proxy, const ot_ip6 address /* can be NULL to only check proxy */ );
+#endif
+
+#ifdef WANT_FULLLOG_NETWORKS
+typedef struct ot_log ot_log;
+struct ot_log {
+  ot_ip6   ip;
+  uint8_t *data;
+  size_t   size;
+  ot_time  time;
+  ot_log  *next;
+};
+extern ot_log *g_logchain_first, *g_logchain_last;
+
+void loglist_add_network( const ot_net *net );
+void loglist_reset( );
+int  loglist_check_address( const ot_ip6 address );
+#endif  
+
+typedef enum {
+  OT_PERMISSION_MAY_FULLSCRAPE = 0x1,
+  OT_PERMISSION_MAY_STAT       = 0x2,
+  OT_PERMISSION_MAY_LIVESYNC   = 0x4,
+  OT_PERMISSION_MAY_PROXY      = 0x8
+} ot_permissions;
+
+int  accesslist_blessip( ot_ip6 ip, ot_permissions permissions );
+int  accesslist_isblessed( ot_ip6 ip, ot_permissions permissions );
+
+#endif
diff --git a/ot_clean.c b/ot_clean.c
new file mode 100644 (file)
index 0000000..4c03416
--- /dev/null
@@ -0,0 +1,139 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* System */
+#include <pthread.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Libowfat */
+#include "io.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_mutex.h"
+#include "ot_vector.h"
+#include "ot_clean.h"
+#include "ot_stats.h"
+
+/* Returns amount of removed peers */
+static ssize_t clean_single_bucket( ot_peer *peers, size_t peer_count, time_t timedout, int *removed_seeders ) {
+  ot_peer *last_peer = peers + peer_count, *insert_point;
+  time_t timediff;
+
+  /* Two scan modes: unless there is one peer removed, just increase ot_peertime */
+  while( peers < last_peer ) {
+    if( ( timediff = timedout + OT_PEERTIME( peers ) ) >= OT_PEER_TIMEOUT )
+      break;
+    OT_PEERTIME( peers++ ) = timediff;
+  }
+
+  /* If we at least remove one peer, we have to copy  */
+  insert_point = peers;
+  while( peers < last_peer )
+    if( ( timediff = timedout + OT_PEERTIME( peers ) ) < OT_PEER_TIMEOUT ) {
+      OT_PEERTIME( peers ) = timediff;
+      memcpy( insert_point++, peers++, sizeof(ot_peer));
+    } else
+      if( OT_PEERFLAG( peers++ ) & PEER_FLAG_SEEDING )
+        (*removed_seeders)++;
+
+  return peers - insert_point;
+}
+
+/* Clean a single torrent
+   return 1 if torrent timed out
+*/
+int clean_single_torrent( ot_torrent *torrent ) {
+  ot_peerlist *peer_list = torrent->peer_list;
+  ot_vector *bucket_list = &peer_list->peers;
+  time_t timedout = (time_t)( g_now_minutes - peer_list->base );
+  int num_buckets = 1, removed_seeders = 0;
+
+  /* No need to clean empty torrent */
+  if( !timedout )
+    return 0;
+
+  /* Torrent has idled out */
+  if( timedout > OT_TORRENT_TIMEOUT )
+    return 1;
+
+  /* Nothing to be cleaned here? Test if torrent is worth keeping */
+  if( timedout > OT_PEER_TIMEOUT ) {
+    if( !peer_list->peer_count )
+      return peer_list->down_count ? 0 : 1;
+    timedout = OT_PEER_TIMEOUT;
+  }
+
+  if( OT_PEERLIST_HASBUCKETS( peer_list ) ) {
+    num_buckets = bucket_list->size;
+    bucket_list = (ot_vector *)bucket_list->data;
+  }
+
+  while( num_buckets-- ) {
+    size_t removed_peers = clean_single_bucket( bucket_list->data, bucket_list->size, timedout, &removed_seeders );
+    peer_list->peer_count -= removed_peers;
+    bucket_list->size     -= removed_peers;
+    if( bucket_list->size < removed_peers )
+      vector_fixup_peers( bucket_list );
+    ++bucket_list;
+  }
+
+  peer_list->seed_count -= removed_seeders;
+
+  /* See, if we need to convert a torrent from simple vector to bucket list */
+  if( ( peer_list->peer_count > OT_PEER_BUCKET_MINCOUNT ) || OT_PEERLIST_HASBUCKETS(peer_list) )
+    vector_redistribute_buckets( peer_list );
+
+  if( peer_list->peer_count )
+    peer_list->base = g_now_minutes;
+  else {
+    /* When we got here, the last time that torrent
+     has been touched is OT_PEER_TIMEOUT Minutes before */
+    peer_list->base = g_now_minutes - OT_PEER_TIMEOUT;
+  }
+  return 0;
+
+}
+
+/* Clean up all peers in current bucket, remove timedout pools and
+ torrents */
+static void * clean_worker( void * args ) {
+  (void) args;
+  while( 1 ) {
+    int bucket = OT_BUCKET_COUNT;
+    while( bucket-- ) {
+      ot_vector *torrents_list = mutex_bucket_lock( bucket );
+      size_t     toffs;
+      int        delta_torrentcount = 0;
+
+      for( toffs=0; toffs<torrents_list->size; ++toffs ) {
+        ot_torrent *torrent = ((ot_torrent*)(torrents_list->data)) + toffs;
+        if( clean_single_torrent( torrent ) ) {
+          vector_remove_torrent( torrents_list, torrent );
+          --delta_torrentcount;
+          --toffs;
+        }
+      }
+      mutex_bucket_unlock( bucket, delta_torrentcount );
+      if( !g_opentracker_running )
+        return NULL;
+      usleep( OT_CLEAN_SLEEP );
+    }
+    stats_cleanup();
+  }
+  return NULL;
+}
+
+static pthread_t thread_id;
+void clean_init( void ) {
+  pthread_create( &thread_id, NULL, clean_worker, NULL );
+}
+
+void clean_deinit( void ) {
+  pthread_cancel( thread_id );
+}
+
+const char *g_version_clean_c = "$Source$: $Revision$\n";
diff --git a/ot_clean.h b/ot_clean.h
new file mode 100644 (file)
index 0000000..cb450c4
--- /dev/null
@@ -0,0 +1,19 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_CLEAN_H__
+#define __OT_CLEAN_H__
+
+/* The amount of time a clean cycle should take */
+#define OT_CLEAN_INTERVAL_MINUTES       2
+
+/* So after each bucket wait 1 / OT_BUCKET_COUNT intervals */
+#define OT_CLEAN_SLEEP ( ( ( OT_CLEAN_INTERVAL_MINUTES ) * 60 * 1000000 ) / ( OT_BUCKET_COUNT ) )
+
+void clean_init( void );
+void clean_deinit( void );
+int  clean_single_torrent( ot_torrent *torrent );
+
+#endif
diff --git a/ot_fullscrape.c b/ot_fullscrape.c
new file mode 100644 (file)
index 0000000..89db2f1
--- /dev/null
@@ -0,0 +1,245 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifdef WANT_FULLSCRAPE
+
+/* System */
+#include <sys/param.h>
+#include <stdio.h>
+#include <string.h>
+#include <pthread.h>
+#include <arpa/inet.h>
+#ifdef WANT_COMPRESSION_GZIP
+#include <zlib.h>
+#endif
+
+/* Libowfat */
+#include "byte.h"
+#include "io.h"
+#include "textcode.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_mutex.h"
+#include "ot_iovec.h"
+#include "ot_fullscrape.h"
+
+/* Fetch full scrape info for all torrents
+   Full scrapes usually are huge and one does not want to
+   allocate more memory. So lets get them in 512k units
+*/
+#define OT_SCRAPE_CHUNK_SIZE (512*1024)
+
+/* "d8:completei%zde10:downloadedi%zde10:incompletei%zdee" */
+#define OT_SCRAPE_MAXENTRYLEN 256
+
+#ifdef WANT_COMPRESSION_GZIP
+#define IF_COMPRESSION( TASK ) if( mode & TASK_FLAG_GZIP ) TASK
+#define WANT_COMPRESSION_GZIP_PARAM( param1, param2, param3 ) , param1, param2, param3
+#else
+#define IF_COMPRESSION( TASK )
+#define WANT_COMPRESSION_GZIP_PARAM( param1, param2, param3 )
+#endif
+
+/* Forward declaration */
+static void fullscrape_make( int *iovec_entries, struct iovec **iovector, ot_tasktype mode );
+
+/* Converter function from memory to human readable hex strings
+   XXX - Duplicated from ot_stats. Needs fix. */
+static char*to_hex(char*d,uint8_t*s){char*m="0123456789ABCDEF";char *t=d;char*e=d+40;while(d<e){*d++=m[*s>>4];*d++=m[*s++&15];}*d=0;return t;}
+
+/* This is the entry point into this worker thread
+   It grabs tasks from mutex_tasklist and delivers results back
+*/
+static void * fullscrape_worker( void * args ) {
+  int iovec_entries;
+  struct iovec *iovector;
+
+  (void) args;
+
+  while( 1 ) {
+    ot_tasktype tasktype = TASK_FULLSCRAPE;
+    ot_taskid   taskid   = mutex_workqueue_poptask( &tasktype );
+    fullscrape_make( &iovec_entries, &iovector, tasktype );
+    if( mutex_workqueue_pushresult( taskid, iovec_entries, iovector ) )
+      iovec_free( &iovec_entries, &iovector );
+    if( !g_opentracker_running )
+      return NULL;
+  }
+  return NULL;
+}
+
+static pthread_t thread_id;
+void fullscrape_init( ) {
+  pthread_create( &thread_id, NULL, fullscrape_worker, NULL );
+}
+
+void fullscrape_deinit( ) {
+  pthread_cancel( thread_id );
+}
+
+void fullscrape_deliver( int64 sock, ot_tasktype tasktype ) {
+  mutex_workqueue_pushtask( sock, tasktype );
+}
+
+static int fullscrape_increase( int *iovec_entries, struct iovec **iovector,
+                         char **r, char **re  WANT_COMPRESSION_GZIP_PARAM( z_stream *strm, ot_tasktype mode, int zaction ) ) {
+  /* Allocate a fresh output buffer at the end of our buffers list */
+  if( !( *r = iovec_fix_increase_or_free( iovec_entries, iovector, *r, OT_SCRAPE_CHUNK_SIZE ) ) ) {
+
+    /* Deallocate gzip buffers */
+    IF_COMPRESSION( deflateEnd(strm); )
+
+    /* Release lock on current bucket and return */
+    return -1;
+  }
+
+  /* Adjust new end of output buffer */
+  *re = *r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN;
+
+  /* When compressing, we have all the bytes in output buffer */
+#ifdef WANT_COMPRESSION_GZIP
+  if( mode & TASK_FLAG_GZIP ) {
+    int zres;
+    *re -= OT_SCRAPE_MAXENTRYLEN;
+    strm->next_out  = (uint8_t*)*r;
+    strm->avail_out = OT_SCRAPE_CHUNK_SIZE;
+    zres = deflate( strm, zaction );
+    if( ( zres < Z_OK ) && ( zres != Z_BUF_ERROR ) )
+      fprintf( stderr, "deflate() failed while in fullscrape_increase(%d).\n", zaction );
+    *r = (char*)strm->next_out;
+  }
+#endif
+
+  return 0;
+}
+
+static void fullscrape_make( int *iovec_entries, struct iovec **iovector, ot_tasktype mode ) {
+  int      bucket;
+  char    *r, *re;
+#ifdef WANT_COMPRESSION_GZIP
+  char     compress_buffer[OT_SCRAPE_MAXENTRYLEN];
+  z_stream strm;
+#endif
+
+  /* Setup return vector... */
+  *iovec_entries = 0;
+  *iovector = NULL;
+  if( !( r = iovec_increase( iovec_entries, iovector, OT_SCRAPE_CHUNK_SIZE ) ) )
+    return;
+
+  /* re points to low watermark */
+  re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN;
+
+#ifdef WANT_COMPRESSION_GZIP
+  if( mode & TASK_FLAG_GZIP ) {
+    re += OT_SCRAPE_MAXENTRYLEN;
+    byte_zero( &strm, sizeof(strm) );
+    strm.next_in   = (uint8_t*)compress_buffer;
+    strm.next_out  = (uint8_t*)r;
+    strm.avail_out = OT_SCRAPE_CHUNK_SIZE;
+    if( deflateInit2(&strm,7,Z_DEFLATED,31,8,Z_DEFAULT_STRATEGY) != Z_OK )
+      fprintf( stderr, "not ok.\n" );
+    r = compress_buffer;
+  }
+#endif
+
+  if( ( mode & TASK_TASK_MASK ) == TASK_FULLSCRAPE )
+    r += sprintf( r, "d5:filesd" );
+
+  /* For each bucket... */
+  for( bucket=0; bucket<OT_BUCKET_COUNT; ++bucket ) {
+    /* Get exclusive access to that bucket */
+    ot_vector *torrents_list = mutex_bucket_lock( bucket );
+    size_t tor_offset;
+
+    /* For each torrent in this bucket.. */
+    for( tor_offset=0; tor_offset<torrents_list->size; ++tor_offset ) {
+      /* Address torrents members */
+      ot_peerlist *peer_list = ( ((ot_torrent*)(torrents_list->data))[tor_offset] ).peer_list;
+      ot_hash     *hash      =&( ((ot_torrent*)(torrents_list->data))[tor_offset] ).hash;
+
+      switch( mode & TASK_TASK_MASK ) {
+      case TASK_FULLSCRAPE:
+      default:
+        /* push hash as bencoded string */
+        *r++='2'; *r++='0'; *r++=':';
+        memcpy( r, hash, sizeof(ot_hash) ); r += sizeof(ot_hash);
+        /* push rest of the scrape string */
+        r += sprintf( r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", peer_list->seed_count, peer_list->down_count, peer_list->peer_count-peer_list->seed_count );
+
+        break;
+      case TASK_FULLSCRAPE_TPB_ASCII:
+        to_hex( r, *hash ); r+= 2 * sizeof(ot_hash);
+        r += sprintf( r, ":%zd:%zd\n", peer_list->seed_count, peer_list->peer_count-peer_list->seed_count );
+        break;
+      case TASK_FULLSCRAPE_TPB_BINARY:
+        memcpy( r, *hash, sizeof(ot_hash) ); r += sizeof(ot_hash);
+        *(uint32_t*)(r+0) = htonl( (uint32_t)  peer_list->seed_count );
+        *(uint32_t*)(r+4) = htonl( (uint32_t)( peer_list->peer_count-peer_list->seed_count) );
+        r+=8;
+        break;
+      case TASK_FULLSCRAPE_TPB_URLENCODED:
+        r += fmt_urlencoded( r, (char *)*hash, 20 );
+        r += sprintf( r, ":%zd:%zd\n", peer_list->seed_count, peer_list->peer_count-peer_list->seed_count );
+        break;
+      case TASK_FULLSCRAPE_TRACKERSTATE:
+        to_hex( r, *hash ); r+= 2 * sizeof(ot_hash);
+        r += sprintf( r, ":%zd:%zd\n", peer_list->base, peer_list->down_count );
+        break;
+      }
+
+#ifdef WANT_COMPRESSION_GZIP
+     if( mode & TASK_FLAG_GZIP ) {
+        int zres;
+        strm.next_in  = (uint8_t*)compress_buffer;
+        strm.avail_in = r - compress_buffer;
+        zres = deflate( &strm, Z_NO_FLUSH );
+        if( ( zres < Z_OK ) && ( zres != Z_BUF_ERROR ) )
+          fprintf( stderr, "deflate() failed while in fullscrape_make().\n" );
+        r = (char*)strm.next_out;
+      }
+#endif
+
+      /* Check if there still is enough buffer left */
+      while( r >= re )
+       if( fullscrape_increase( iovec_entries, iovector, &r, &re WANT_COMPRESSION_GZIP_PARAM( &strm, mode, Z_NO_FLUSH ) ) )
+         return mutex_bucket_unlock( bucket, 0 );
+
+      IF_COMPRESSION( r = compress_buffer; )
+    }
+
+    /* All torrents done: release lock on current bucket */
+    mutex_bucket_unlock( bucket, 0 );
+
+    /* Parent thread died? */
+    if( !g_opentracker_running )
+      return;
+  }
+
+  if( ( mode & TASK_TASK_MASK ) == TASK_FULLSCRAPE )
+    r += sprintf( r, "ee" );
+
+#ifdef WANT_COMPRESSION_GZIP
+  if( mode & TASK_FLAG_GZIP ) {
+    strm.next_in  = (uint8_t*)compress_buffer;
+    strm.avail_in = r - compress_buffer;
+    if( deflate( &strm, Z_FINISH ) < Z_OK )
+      fprintf( stderr, "deflate() failed while in fullscrape_make()'s endgame.\n" );
+    r = (char*)strm.next_out;
+
+    while( r >= re )
+      if( fullscrape_increase( iovec_entries, iovector, &r, &re WANT_COMPRESSION_GZIP_PARAM( &strm, mode, Z_FINISH ) ) )
+        return mutex_bucket_unlock( bucket, 0 );
+    deflateEnd(&strm);
+  }
+#endif
+
+  /* Release unused memory in current output buffer */
+  iovec_fixlast( iovec_entries, iovector, r );
+}
+#endif
+
+const char *g_version_fullscrape_c = "$Source$: $Revision$\n";
diff --git a/ot_fullscrape.h b/ot_fullscrape.h
new file mode 100644 (file)
index 0000000..b86f8ea
--- /dev/null
@@ -0,0 +1,22 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_FULLSCRAPE_H__
+#define __OT_FULLSCRAPE_H__
+
+#ifdef WANT_FULLSCRAPE
+
+void fullscrape_init( );
+void fullscrape_deinit( );
+void fullscrape_deliver( int64 sock, ot_tasktype tasktype );
+
+#else
+
+#define fullscrape_init()
+#define fullscrape_deinit()
+
+#endif
+
+#endif
diff --git a/ot_http.c b/ot_http.c
new file mode 100644 (file)
index 0000000..dc8bd4e
--- /dev/null
+++ b/ot_http.c
@@ -0,0 +1,634 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* System */
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+
+/* Libowfat */
+#include "byte.h"
+#include "array.h"
+#include "iob.h"
+#include "ip6.h"
+#include "scan.h"
+#include "case.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_mutex.h"
+#include "ot_http.h"
+#include "ot_iovec.h"
+#include "scan_urlencoded_query.h"
+#include "ot_fullscrape.h"
+#include "ot_stats.h"
+#include "ot_accesslist.h"
+
+#define OT_MAXMULTISCRAPE_COUNT 64
+extern char *g_redirecturl;
+
+char   *g_stats_path;
+ssize_t g_stats_path_len;
+
+enum {
+  SUCCESS_HTTP_HEADER_LENGTH = 80,
+  SUCCESS_HTTP_HEADER_LENGTH_CONTENT_ENCODING = 32,
+  SUCCESS_HTTP_SIZE_OFF = 17 };
+
+static void http_senddata( const int64 sock, struct ot_workstruct *ws ) {
+  struct http_data *cookie = io_getcookie( sock );
+  ssize_t written_size;
+
+  if( !cookie ) { io_close(sock); return; }
+
+  /* whoever sends data is not interested in its input-array */
+  if( ws->keep_alive && ws->header_size != ws->request_size ) {
+    size_t rest = ws->request_size - ws->header_size;
+    if( array_start(&cookie->request) ) {
+      memmove( array_start(&cookie->request), ws->request + ws->header_size, rest );
+      array_truncate( &cookie->request, 1, rest );
+    } else
+      array_catb(&cookie->request, ws->request + ws->header_size, rest );    
+  } else
+    array_reset( &cookie->request );
+
+  written_size = write( sock, ws->reply, ws->reply_size );
+  if( ( written_size < 0 ) || ( ( written_size == ws->reply_size ) && !ws->keep_alive ) ) {
+    array_reset( &cookie->request );
+    free( cookie ); io_close( sock ); return;
+  }
+
+  if( written_size < ws->reply_size ) {
+    char * outbuf;
+    tai6464 t;
+
+    if( !( outbuf = malloc( ws->reply_size - written_size ) ) ) {
+      array_reset( &cookie->request );
+      free(cookie); io_close( sock );
+      return;
+    }
+
+    memcpy( outbuf, ws->reply + written_size, ws->reply_size - written_size );
+    iob_addbuf_free( &cookie->batch, outbuf, ws->reply_size - written_size );
+
+    /* writeable short data sockets just have a tcp timeout */
+    if( !ws->keep_alive ) {
+      taia_uint( &t, 0 ); io_timeout( sock, t );
+      io_dontwantread( sock );
+    }
+    io_wantwrite( sock );
+  }
+}
+
+#define HTTPERROR_302            return http_issue_error( sock, ws, CODE_HTTPERROR_302 )
+#define HTTPERROR_400            return http_issue_error( sock, ws, CODE_HTTPERROR_400 )
+#define HTTPERROR_400_PARAM      return http_issue_error( sock, ws, CODE_HTTPERROR_400_PARAM )
+#define HTTPERROR_400_COMPACT    return http_issue_error( sock, ws, CODE_HTTPERROR_400_COMPACT )
+#define HTTPERROR_400_DOUBLEHASH return http_issue_error( sock, ws, CODE_HTTPERROR_400_PARAM )
+#define HTTPERROR_402_NOTMODEST  return http_issue_error( sock, ws, CODE_HTTPERROR_402_NOTMODEST )
+#define HTTPERROR_403_IP         return http_issue_error( sock, ws, CODE_HTTPERROR_403_IP )
+#define HTTPERROR_404            return http_issue_error( sock, ws, CODE_HTTPERROR_404 )
+#define HTTPERROR_500            return http_issue_error( sock, ws, CODE_HTTPERROR_500 )
+ssize_t http_issue_error( const int64 sock, struct ot_workstruct *ws, int code ) {
+  char *error_code[] = { "302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required",
+                         "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error" };
+  char *title = error_code[code];
+
+  ws->reply = ws->outbuf;
+  if( code == CODE_HTTPERROR_302 )
+    ws->reply_size = snprintf( ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 302 Found\r\nContent-Length: 0\r\nLocation: %s\r\n\r\n", g_redirecturl );
+  else
+    ws->reply_size = snprintf( ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 %s\r\nContent-Type: text/html\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n", title, strlen(title)+16-4,title+4);
+
+#ifdef _DEBUG_HTTPERROR
+  fprintf( stderr, "DEBUG: invalid request was: %s\n", ws->debugbuf );
+#endif
+  stats_issue_event( EVENT_FAILED, FLAG_TCP, code );
+  http_senddata( sock, ws );
+  return ws->reply_size = -2;
+}
+
+ssize_t http_sendiovecdata( const int64 sock, struct ot_workstruct *ws, int iovec_entries, struct iovec *iovector ) {
+  struct http_data *cookie = io_getcookie( sock );
+  char *header;
+  int i;
+  size_t header_size, size = iovec_length( &iovec_entries, &iovector );
+  tai6464 t;
+
+  /* No cookie? Bad socket. Leave. */
+  if( !cookie ) {
+    iovec_free( &iovec_entries, &iovector );
+    HTTPERROR_500;
+  }
+
+  /* If this socket collected request in a buffer, free it now */
+  array_reset( &cookie->request );
+
+  /* If we came here, wait for the answer is over */
+  cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK;
+
+  /* Our answers never are 0 vectors. Return an error. */
+  if( !iovec_entries ) {
+    HTTPERROR_500;
+  }
+
+  /* Prepare space for http header */
+  header = malloc( SUCCESS_HTTP_HEADER_LENGTH + SUCCESS_HTTP_HEADER_LENGTH_CONTENT_ENCODING );
+  if( !header ) {
+    iovec_free( &iovec_entries, &iovector );
+    HTTPERROR_500;
+  }
+
+  if( cookie->flag & STRUCT_HTTP_FLAG_GZIP )
+    header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\nContent-Length: %zd\r\n\r\n", size );
+  else if( cookie->flag & STRUCT_HTTP_FLAG_BZIP2 )
+    header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: bzip2\r\nContent-Length: %zd\r\n\r\n", size );
+  else
+    header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r\n", size );
+
+  iob_reset( &cookie->batch );
+  iob_addbuf_free( &cookie->batch, header, header_size );
+
+  /* Will move to ot_iovec.c */
+  for( i=0; i<iovec_entries; ++i )
+    iob_addbuf_munmap( &cookie->batch, iovector[i].iov_base, iovector[i].iov_len );
+  free( iovector );
+
+  /* writeable sockets timeout after 10 minutes */
+  taia_now( &t ); taia_addsec( &t, &t, OT_CLIENT_TIMEOUT_SEND );
+  io_timeout( sock, t );
+  io_dontwantread( sock );
+  io_wantwrite( sock );
+  return 0;
+}
+
+static ssize_t http_handle_stats( const int64 sock, struct ot_workstruct *ws, char *read_ptr ) {
+static const ot_keywords keywords_main[] =
+  { { "mode", 1 }, {"format", 2 }, { NULL, -3 } };
+static const ot_keywords keywords_mode[] =
+  { { "peer", TASK_STATS_PEERS }, { "conn", TASK_STATS_CONNS }, { "scrp", TASK_STATS_SCRAPE }, { "udp4", TASK_STATS_UDP }, { "tcp4", TASK_STATS_TCP },
+    { "busy", TASK_STATS_BUSY_NETWORKS }, { "torr", TASK_STATS_TORRENTS }, { "fscr", TASK_STATS_FULLSCRAPE },
+    { "s24s", TASK_STATS_SLASH24S }, { "tpbs", TASK_STATS_TPB }, { "herr", TASK_STATS_HTTPERRORS }, { "completed", TASK_STATS_COMPLETED },
+    { "top100", TASK_STATS_TOP100 }, { "top10", TASK_STATS_TOP10 }, { "renew", TASK_STATS_RENEW }, { "syncs", TASK_STATS_SYNCS }, { "version", TASK_STATS_VERSION },
+    { "everything", TASK_STATS_EVERYTHING }, { "statedump", TASK_FULLSCRAPE_TRACKERSTATE }, { "fulllog", TASK_STATS_FULLLOG },
+    { "woodpeckers", TASK_STATS_WOODPECKERS},
+#ifdef WANT_LOG_NUMWANT
+    { "numwants", TASK_STATS_NUMWANTS},
+#endif
+    { NULL, -3 } };
+static const ot_keywords keywords_format[] =
+  { { "bin", TASK_FULLSCRAPE_TPB_BINARY }, { "ben", TASK_FULLSCRAPE }, { "url", TASK_FULLSCRAPE_TPB_URLENCODED },
+    { "txt", TASK_FULLSCRAPE_TPB_ASCII }, { NULL, -3 } };
+
+  int mode = TASK_STATS_PEERS, scanon = 1, format = 0;
+
+#ifdef WANT_RESTRICT_STATS
+  struct http_data *cookie = io_getcookie( sock );
+
+  if( !cookie || !accesslist_isblessed( cookie->ip, OT_PERMISSION_MAY_STAT ) )
+    HTTPERROR_403_IP;
+#endif
+
+  while( scanon ) {
+    switch( scan_find_keywords( keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM ) ) {
+    case -2: scanon = 0; break;   /* TERMINATOR */
+    case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
+    case -3: scan_urlencoded_skipvalue( &read_ptr ); break;
+    case  1: /* matched "mode" */
+      if( ( mode = scan_find_keywords( keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM;
+      break;
+    case  2: /* matched "format" */
+      if( ( format = scan_find_keywords( keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM;
+      break;
+    }
+  }
+
+#ifdef WANT_FULLSCRAPE
+  if( mode == TASK_FULLSCRAPE_TRACKERSTATE ) {
+    format = mode; mode = TASK_STATS_TPB;
+  }
+
+  if( mode == TASK_STATS_TPB ) {
+    struct http_data* cookie = io_getcookie( sock );
+    tai6464 t;
+#ifdef WANT_COMPRESSION_GZIP
+    ws->request[ws->request_size] = 0;
+#ifdef WANT_COMPRESSION_GZIP_ALWAYS
+    if( strstr( read_ptr - 1, "gzip" ) ) {
+#endif
+      cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
+      format |= TASK_FLAG_GZIP;
+#ifdef WANT_COMPRESSION_GZIP_ALWAYS
+    }
+#endif
+#endif
+    /* Pass this task to the worker thread */
+    cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
+
+    /* Clients waiting for us should not easily timeout */
+    taia_uint( &t, 0 ); io_timeout( sock, t );
+    fullscrape_deliver( sock, format );
+    io_dontwantread( sock );
+    return ws->reply_size = -2;
+  }
+#endif
+
+  /* default format for now */
+  if( ( mode & TASK_CLASS_MASK ) == TASK_STATS ) {
+    tai6464 t;
+    /* Complex stats also include expensive memory debugging tools */
+    taia_uint( &t, 0 ); io_timeout( sock, t );
+    stats_deliver( sock, mode );
+    return ws->reply_size = -2;
+  }
+
+  /* Simple stats can be answerred immediately */
+  return ws->reply_size = return_stats_for_tracker( ws->reply, mode, 0 );
+}
+
+#ifdef WANT_MODEST_FULLSCRAPES
+static pthread_mutex_t g_modest_fullscrape_mutex = PTHREAD_MUTEX_INITIALIZER; 
+static ot_vector g_modest_fullscrape_timeouts;
+typedef struct { ot_ip6 ip; ot_time last_fullscrape; } ot_scrape_log;
+#endif
+
+#ifdef WANT_FULLSCRAPE
+static ssize_t http_handle_fullscrape( const int64 sock, struct ot_workstruct *ws ) {
+  struct http_data* cookie = io_getcookie( sock );
+  int format = 0;
+  tai6464 t;
+
+#ifdef WANT_MODEST_FULLSCRAPES
+  {
+    ot_scrape_log this_peer, *new_peer;
+    int exactmatch;
+    memcpy( this_peer.ip, cookie->ip, sizeof(ot_ip6));
+    this_peer.last_fullscrape = g_now_seconds;
+    pthread_mutex_lock(&g_modest_fullscrape_mutex);
+    new_peer = vector_find_or_insert( &g_modest_fullscrape_timeouts, &this_peer, sizeof(ot_scrape_log), sizeof(ot_ip6), &exactmatch );
+    if( !new_peer ) {
+      pthread_mutex_unlock(&g_modest_fullscrape_mutex);
+      HTTPERROR_500;
+    }
+    if( exactmatch && ( this_peer.last_fullscrape - new_peer->last_fullscrape ) < OT_MODEST_PEER_TIMEOUT ) {
+      pthread_mutex_unlock(&g_modest_fullscrape_mutex);
+      HTTPERROR_402_NOTMODEST;
+    }
+    memcpy( new_peer, &this_peer, sizeof(ot_scrape_log));
+    pthread_mutex_unlock(&g_modest_fullscrape_mutex);
+  }
+#endif
+
+#ifdef WANT_COMPRESSION_GZIP
+  ws->request[ws->request_size-1] = 0;
+  if( strstr( ws->request, "gzip" ) ) {
+    cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
+    format = TASK_FLAG_GZIP;
+    stats_issue_event( EVENT_FULLSCRAPE_REQUEST_GZIP, 0, (uintptr_t)cookie->ip );
+  } else
+#endif
+    stats_issue_event( EVENT_FULLSCRAPE_REQUEST, 0, (uintptr_t)cookie->ip );
+
+#ifdef _DEBUG_HTTPERROR
+  fprintf( stderr, "%s", ws->debugbuf );
+#endif
+
+  /* Pass this task to the worker thread */
+  cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
+  /* Clients waiting for us should not easily timeout */
+  taia_uint( &t, 0 ); io_timeout( sock, t );
+  fullscrape_deliver( sock, TASK_FULLSCRAPE | format );
+  io_dontwantread( sock );
+  return ws->reply_size = -2;
+}
+#endif
+
+static ssize_t http_handle_scrape( const int64 sock, struct ot_workstruct *ws, char *read_ptr ) {
+  static const ot_keywords keywords_scrape[] = { { "info_hash", 1 }, { NULL, -3 } };
+
+  ot_hash * multiscrape_buf = (ot_hash*)ws->request;
+  int scanon = 1, numwant = 0;
+
+  /* This is to hack around stupid clients that send "scrape ?info_hash" */
+  if( read_ptr[-1] != '?' ) {
+    while( ( *read_ptr != '?' ) && ( *read_ptr != '\n' ) ) ++read_ptr;
+    if( *read_ptr == '\n' ) HTTPERROR_400_PARAM;
+    ++read_ptr;
+  }
+
+  while( scanon ) {
+    switch( scan_find_keywords( keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM ) ) {
+    case -2: scanon = 0; break;   /* TERMINATOR */
+    default: HTTPERROR_400_PARAM; /* PARSE ERROR */
+    case -3: scan_urlencoded_skipvalue( &read_ptr ); break;
+    case  1: /* matched "info_hash" */
+      /* ignore this, when we have less than 20 bytes */
+      if( scan_urlencoded_query( &read_ptr, (char*)(multiscrape_buf + numwant++), SCAN_SEARCHPATH_VALUE ) != (ssize_t)sizeof(ot_hash) )
+        HTTPERROR_400_PARAM;
+      break;
+    }
+  }
+
+  /* No info_hash found? Inform user */
+  if( !numwant ) HTTPERROR_400_PARAM;
+
+  /* Limit number of hashes to process */
+  if( numwant > OT_MAXMULTISCRAPE_COUNT )
+    numwant = OT_MAXMULTISCRAPE_COUNT;
+
+  /* Enough for http header + whole scrape string */
+  ws->reply_size = return_tcp_scrape_for_torrent( multiscrape_buf, numwant, ws->reply );
+  stats_issue_event( EVENT_SCRAPE, FLAG_TCP, ws->reply_size );
+  return ws->reply_size;
+}
+
+#ifdef WANT_LOG_NUMWANT
+  unsigned long long numwants[201];
+#endif
+
+#if defined( WANT_KEEPALIVE ) || defined( WANT_IP_FROM_PROXY )
+static char* http_header( char *data, size_t byte_count, char *header ) {
+  size_t i;
+  long sl = strlen( header );
+  for( i = 0; i + sl + 2 < byte_count; ++i ) {
+    if( data[i] != '\n' || data[ i + sl + 1] != ':' ) continue;
+    if( !case_equalb( data + i + 1, sl, header ) ) continue;
+    data += i + sl + 2;
+    while( *data == ' ' || *data == '\t' ) ++data;
+    return data;
+  }
+  return 0;
+}
+#endif
+
+static ot_keywords keywords_announce[] = { { "port", 1 }, { "left", 2 }, { "event", 3 }, { "numwant", 4 }, { "compact", 5 }, { "compact6", 5 }, { "info_hash", 6 },
+#ifdef WANT_IP_FROM_QUERY_STRING
+{ "ip", 7 },
+#endif
+#ifdef WANT_FULLLOG_NETWORKS
+{ "lognet", 8 },
+#endif
+{ "peer_id", 9 },
+{ NULL, -3 } };
+static ot_keywords keywords_announce_event[] = { { "completed", 1 }, { "stopped", 2 }, { NULL, -3 } };
+static ssize_t http_handle_announce( const int64 sock, struct ot_workstruct *ws, char *read_ptr ) {
+  int               numwant, tmp, scanon;
+  unsigned short    port = 0;
+  char             *write_ptr;
+  ssize_t           len;
+  struct http_data *cookie = io_getcookie( sock );
+
+  /* This is to hack around stupid clients that send "announce ?info_hash" */
+  if( read_ptr[-1] != '?' ) {
+    while( ( *read_ptr != '?' ) && ( *read_ptr != '\n' ) ) ++read_ptr;
+    if( *read_ptr == '\n' ) HTTPERROR_400_PARAM;
+    ++read_ptr;
+  }
+
+#ifdef WANT_IP_FROM_PROXY
+  if( accesslist_isblessed( cookie->ip, OT_PERMISSION_MAY_PROXY ) ) {
+    ot_ip6 proxied_ip;
+    char *fwd = http_header( ws->request, ws->header_size, "x-forwarded-for" );
+    if( fwd && scan_ip6( fwd, proxied_ip ) )
+      OT_SETIP( &ws->peer, proxied_ip );
+    else
+      OT_SETIP( &ws->peer, cookie->ip );
+  } else
+#endif
+  OT_SETIP( &ws->peer, cookie->ip );
+
+  ws->peer_id = NULL;
+  ws->hash = NULL;
+
+  OT_SETPORT( &ws->peer, &port );
+  OT_PEERFLAG( &ws->peer ) = 0;
+  numwant = 50;
+  scanon = 1;
+
+  while( scanon ) {
+    switch( scan_find_keywords(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM ) ) {
+    case -2: scanon = 0; break;   /* TERMINATOR */
+    case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
+    case -3: scan_urlencoded_skipvalue( &read_ptr ); break;
+    case 1: /* matched "port" */
+      len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE );
+      if( ( len <= 0 ) || scan_fixed_int( write_ptr, len, &tmp ) || ( tmp > 0xffff ) ) HTTPERROR_400_PARAM;
+      port = htons( tmp ); OT_SETPORT( &ws->peer, &port );
+      break;
+    case 2: /* matched "left" */
+      if( ( len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM;
+      if( scan_fixed_int( write_ptr, len, &tmp ) ) tmp = 0;
+      if( !tmp ) OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_SEEDING;
+      break;
+    case 3: /* matched "event" */
+      switch( scan_find_keywords( keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE ) ) {
+        case -1: HTTPERROR_400_PARAM;
+        case  1: /* matched "completed" */
+          OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_COMPLETED;
+          break;
+        case  2: /* matched "stopped" */
+          OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_STOPPED;
+          break;
+        default:
+          break;
+      }
+      break;
+    case 4: /* matched "numwant" */
+      len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE );
+      if( ( len <= 0 ) || scan_fixed_int( write_ptr, len, &numwant ) ) HTTPERROR_400_PARAM;
+      if( numwant < 0 ) numwant = 50;
+      if( numwant > 200 ) numwant = 200;
+      break;
+    case 5: /* matched "compact" */
+      len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE );
+      if( ( len <= 0 ) || scan_fixed_int( write_ptr, len, &tmp ) ) HTTPERROR_400_PARAM;
+      if( !tmp ) HTTPERROR_400_COMPACT;
+      break;
+    case 6: /* matched "info_hash" */
+      if( ws->hash ) HTTPERROR_400_DOUBLEHASH;
+      /* ignore this, when we have less than 20 bytes */
+      if( scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM;
+        ws->hash = (ot_hash*)write_ptr;
+      break;
+#ifdef WANT_IP_FROM_QUERY_STRING
+    case  7: /* matched "ip" */
+      {
+        char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply+16;
+        len = scan_urlencoded_query( &read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE );
+        tmp_buf2[len] = 0;
+        if( ( len <= 0 ) || !scan_ip6( tmp_buf2, tmp_buf1 ) ) HTTPERROR_400_PARAM;
+        OT_SETIP( &ws->peer, tmp_buf1 );
+      }
+      break;
+#endif
+#ifdef WANT_FULLLOG_NETWORKS
+      case 8: /* matched "lognet" */
+      {
+        //if( accesslist_isblessed( cookie->ip, OT_PERMISSION_MAY_STAT ) ) {
+          char *tmp_buf = ws->reply;
+          ot_net net;
+          signed short parsed, bits;
+
+          len = scan_urlencoded_query( &read_ptr, tmp_buf, SCAN_SEARCHPATH_VALUE );
+          tmp_buf[len] = 0;
+          if( len <= 0 ) HTTPERROR_400_PARAM;
+          if( *tmp_buf == '-' ) {
+            loglist_reset( );
+            return ws->reply_size = sprintf( ws->reply, "Successfully removed.\n" );
+          }
+          parsed = scan_ip6( tmp_buf, net.address );
+          if( !parsed ) HTTPERROR_400_PARAM;
+          if( tmp_buf[parsed++] != '/' )
+            bits = 128;
+          else {
+            parsed = scan_short( tmp_buf + parsed, &bits );
+            if( !parsed ) HTTPERROR_400_PARAM; 
+            if( ip6_isv4mapped( net.address ) )
+              bits += 96;
+          }
+          net.bits = bits;
+          loglist_add_network( &net );
+          return ws->reply_size = sprintf( ws->reply, "Successfully added.\n" );
+        //}
+      }
+#endif
+        break;
+      case 9: /* matched "peer_id" */
+        /* ignore this, when we have less than 20 bytes */
+        if( scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM;
+        ws->peer_id = write_ptr;
+        break;
+    }
+  }
+
+#ifdef WANT_LOG_NUMWANT
+  numwants[numwant]++;
+#endif
+
+  /* XXX DEBUG */
+  stats_issue_event( EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ws->reply );
+
+  /* Scanned whole query string */
+  if( !ws->hash )
+    return ws->reply_size = sprintf( ws->reply, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e" );
+
+  if( OT_PEERFLAG( &ws->peer ) & PEER_FLAG_STOPPED )
+    ws->reply_size = remove_peer_from_torrent( FLAG_TCP, ws );
+  else
+    ws->reply_size = add_peer_to_torrent_and_return_peers( FLAG_TCP, ws, numwant );
+
+  stats_issue_event( EVENT_ANNOUNCE, FLAG_TCP, ws->reply_size);
+  return ws->reply_size;
+}
+
+ssize_t http_handle_request( const int64 sock, struct ot_workstruct *ws ) {
+  ssize_t reply_off, len;
+  char   *read_ptr = ws->request, *write_ptr;
+
+#ifdef WANT_FULLLOG_NETWORKS
+  struct http_data *cookie = io_getcookie( sock );
+  if( loglist_check_address( cookie->ip ) ) {
+    ot_log *log = malloc( sizeof( ot_log ) );
+    if( log ) {
+      log->size = ws->request_size;
+      log->data = malloc( ws->request_size );
+      log->next = 0;
+      log->time = g_now_seconds;
+      memcpy( log->ip, cookie->ip, sizeof(ot_ip6));
+      if( log->data ) {
+        memcpy( log->data, ws->request, ws->request_size );
+        if( !g_logchain_first )
+          g_logchain_first = g_logchain_last = log;
+        else {
+          g_logchain_last->next = log;
+          g_logchain_last = log;  
+        }        
+      } else
+        free( log );
+    }
+  }
+#endif
+
+#ifdef _DEBUG_HTTPERROR
+  reply_off = ws->request_size;
+  if( ws->request_size >= G_DEBUGBUF_SIZE )
+    reply_off = G_DEBUGBUF_SIZE - 1;
+  memcpy( ws->debugbuf, ws->request, reply_off );
+  ws->debugbuf[ reply_off ] = 0;
+#endif
+  
+  /* Tell subroutines where to put reply data */
+  ws->reply = ws->outbuf + SUCCESS_HTTP_HEADER_LENGTH;
+
+  /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */
+  if( memcmp( read_ptr, "GET /", 5) ) HTTPERROR_400;
+
+  /* Skip leading '/' */
+  for( read_ptr+=4; *read_ptr == '/'; ++read_ptr);
+
+  /* Try to parse the request.
+     In reality we abandoned requiring the url to be correct. This now
+     only decodes url encoded characters, we check for announces and
+     scrapes by looking for "a*" or "sc" */
+  len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_PATH );
+
+  /* If parsing returned an error, leave with not found */
+  if( g_redirecturl && ( len == -2 ) ) HTTPERROR_302;
+  if( len <= 0 ) HTTPERROR_404;
+
+  /* This is the hardcore match for announce*/
+  if( ( *write_ptr == 'a' ) || ( *write_ptr == '?' ) )
+    http_handle_announce( sock, ws, read_ptr );
+#ifdef WANT_FULLSCRAPE
+  else if( !memcmp( write_ptr, "scrape HTTP/", 12 ) )
+    http_handle_fullscrape( sock, ws );
+#endif
+  /* This is the hardcore match for scrape */
+  else if( !memcmp( write_ptr, "sc", 2 ) )
+    http_handle_scrape( sock, ws, read_ptr );
+  /* All the rest is matched the standard way */
+  else if( len == g_stats_path_len && !memcmp( write_ptr, g_stats_path, len ) )
+    http_handle_stats( sock, ws, read_ptr );
+  else
+    HTTPERROR_404;
+
+  /* Find out if the client wants to keep this connection alive */
+  ws->keep_alive = 0;
+#ifdef WANT_KEEPALIVE
+  read_ptr=http_header( ws->request, ws->header_size, "connection");
+  if( read_ptr && ( *read_ptr == 'K' || *read_ptr == 'k' ) ) ws->keep_alive = 1;
+#endif
+
+  /* If routines handled sending themselves, just return */
+  if( ws->reply_size == -2 ) return 0;
+  /* If routine failed, let http error take over */
+  if( ws->reply_size <= 0 ) HTTPERROR_500;
+
+  /* This one is rather ugly, so I take you step by step through it.
+
+     1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to
+     write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our work buffer, which is enough for the static string
+     plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate
+     the space NOT needed to expand in reply_off
+  */
+  reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( ws->outbuf, 0, "%zd", ws->reply_size );
+  ws->reply = ws->outbuf + reply_off;
+
+  /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete
+     packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */
+  ws->reply_size += 1 + sprintf( ws->reply, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size );
+
+  /* 3. Finally we join both blocks neatly */
+  ws->outbuf[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n';
+
+  http_senddata( sock, ws );
+  return ws->reply_size;
+}
+
+const char *g_version_http_c = "$Source$: $Revision$\n";
diff --git a/ot_http.h b/ot_http.h
new file mode 100644 (file)
index 0000000..7e367ed
--- /dev/null
+++ b/ot_http.h
@@ -0,0 +1,29 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_HTTP_H__
+#define __OT_HTTP_H__
+
+typedef enum {
+  STRUCT_HTTP_FLAG_WAITINGFORTASK = 1,
+  STRUCT_HTTP_FLAG_GZIP           = 2,
+  STRUCT_HTTP_FLAG_BZIP2          = 4
+} STRUCT_HTTP_FLAG;
+
+struct http_data {
+  array            request;
+  io_batch         batch;
+  ot_ip6           ip;
+  STRUCT_HTTP_FLAG flag;
+};
+
+ssize_t http_handle_request( const int64 s, struct ot_workstruct *ws );
+ssize_t http_sendiovecdata( const int64 s, struct ot_workstruct *ws, int iovec_entries, struct iovec *iovector );
+ssize_t http_issue_error( const int64 s, struct ot_workstruct *ws, int code );
+
+extern char   *g_stats_path;
+extern ssize_t g_stats_path_len;
+
+#endif
diff --git a/ot_iovec.c b/ot_iovec.c
new file mode 100644 (file)
index 0000000..ec0bd12
--- /dev/null
@@ -0,0 +1,76 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* System */
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/uio.h>
+
+/* Libowfat */
+
+/* Opentracker */
+#include "ot_iovec.h"
+
+void *iovec_increase( int *iovec_entries, struct iovec **iovector, size_t new_alloc ) {
+  void *new_ptr = realloc( *iovector, (1 + *iovec_entries ) * sizeof( struct iovec ) );
+  if( !new_ptr )
+    return NULL;
+  *iovector = new_ptr;
+  new_ptr = mmap( NULL, new_alloc, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 );
+  if( !new_ptr )
+    return NULL;
+  ((*iovector)[*iovec_entries]).iov_base = new_ptr;
+  ((*iovector)[*iovec_entries]).iov_len  = new_alloc;
+  ++*iovec_entries;
+  return new_ptr;
+}
+
+void iovec_free( int *iovec_entries, struct iovec **iovector ) {
+  int i;
+  for( i=0; i<*iovec_entries; ++i )
+    munmap( ((*iovector)[i]).iov_base, ((*iovector)[i]).iov_len );
+  *iovec_entries = 0;
+}
+
+void  iovec_fixlast( int *iovec_entries, struct iovec **iovector, void *last_ptr ) {
+  int page_size = getpagesize();
+  size_t old_alloc, new_alloc, old_pages, new_pages;
+  char * base = (char*)((*iovector)[ *iovec_entries - 1 ]).iov_base;
+
+  if( !*iovec_entries ) return;
+
+  old_alloc = ((*iovector)[ *iovec_entries - 1 ]).iov_len;
+  new_alloc = ((char*)last_ptr) - base;
+  old_pages = 1 + old_alloc / page_size;
+  new_pages = 1 + new_alloc / page_size;
+
+  if( old_pages != new_pages )
+    munmap( base + new_pages * page_size, old_alloc - new_pages * page_size );
+  ((*iovector)[*iovec_entries - 1 ]).iov_len = new_alloc;
+}
+
+void  *iovec_fix_increase_or_free( int *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc ) {
+  void *new_ptr;
+
+  iovec_fixlast( iovec_entries, iovector, last_ptr );
+
+  if( !( new_ptr = iovec_increase( iovec_entries, iovector, new_alloc ) ) )
+    iovec_free( iovec_entries, iovector );
+
+  return new_ptr;
+}
+
+
+size_t iovec_length( int *iovec_entries, struct iovec **iovector ) {
+  size_t length = 0;
+  int i;
+  for( i=0; i<*iovec_entries; ++i )
+    length += ((*iovector)[i]).iov_len;
+  return length;
+}
+
+const char *g_version_iovec_c = "$Source$: $Revision$\n";
diff --git a/ot_iovec.h b/ot_iovec.h
new file mode 100644 (file)
index 0000000..83a1a36
--- /dev/null
@@ -0,0 +1,19 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_IOVEC_H__
+#define __OT_IOVEC_H__
+
+#include <sys/uio.h>
+
+void  *iovec_increase( int *iovec_entries, struct iovec **iovector, size_t new_alloc );
+void   iovec_fixlast( int *iovec_entries, struct iovec **iovector, void *last_ptr );
+void   iovec_free( int *iovec_entries, struct iovec **iovector );
+
+size_t iovec_length( int *iovec_entries, struct iovec **iovector );
+
+void  *iovec_fix_increase_or_free( int *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc );
+
+#endif
diff --git a/ot_livesync.c b/ot_livesync.c
new file mode 100644 (file)
index 0000000..cded0f7
--- /dev/null
@@ -0,0 +1,216 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+ It is considered beerware. Prost. Skol. Cheers or whatever.
+
+ $id$ */
+
+/* System */
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+/* Libowfat */
+#include "socket.h"
+#include "ndelay.h"
+#include "byte.h"
+#include "ip6.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_livesync.h"
+#include "ot_accesslist.h"
+#include "ot_stats.h"
+#include "ot_mutex.h"
+
+#ifdef WANT_SYNC_LIVE
+
+char groupip_1[4] = { 224,0,23,5 };
+
+#define LIVESYNC_INCOMING_BUFFSIZE          (256*256)
+
+#define LIVESYNC_OUTGOING_BUFFSIZE_PEERS     1480
+#define LIVESYNC_OUTGOING_WATERMARK_PEERS   (sizeof(ot_peer)+sizeof(ot_hash))
+
+#define LIVESYNC_MAXDELAY                    15      /* seconds */
+
+enum { OT_SYNC_PEER };
+
+/* Forward declaration */
+static void * livesync_worker( void * args );
+
+/* For outgoing packets */
+static int64    g_socket_in = -1;
+
+/* For incoming packets */
+static int64    g_socket_out = -1;
+
+static pthread_mutex_t g_outbuf_mutex = PTHREAD_MUTEX_INITIALIZER;
+char            g_outbuf[LIVESYNC_OUTGOING_BUFFSIZE_PEERS];
+static size_t   g_outbuf_data;
+static ot_time  g_next_packet_time;
+
+static pthread_t thread_id;
+void livesync_init( ) {
+
+  if( g_socket_in == -1 )
+    exerr( "No socket address for live sync specified." );
+
+  /* Prepare outgoing peers buffer */
+  memcpy( g_outbuf, &g_tracker_id, sizeof( g_tracker_id ) );
+  uint32_pack_big( g_outbuf + sizeof( g_tracker_id ), OT_SYNC_PEER);
+  g_outbuf_data = sizeof( g_tracker_id ) + sizeof( uint32_t );
+
+  g_next_packet_time = g_now_seconds + LIVESYNC_MAXDELAY;
+
+  pthread_create( &thread_id, NULL, livesync_worker, NULL );
+}
+
+void livesync_deinit() {
+  if( g_socket_in != -1 )
+    close( g_socket_in );
+  if( g_socket_out != -1 )
+    close( g_socket_out );
+
+  pthread_cancel( thread_id );
+}
+
+void livesync_bind_mcast( ot_ip6 ip, uint16_t port) {
+  char tmpip[4] = {0,0,0,0};
+  char *v4ip;
+
+  if( !ip6_isv4mapped(ip))
+    exerr("v6 mcast support not yet available.");
+  v4ip = ip+12;
+
+  if( g_socket_in != -1 )
+    exerr("Error: Livesync listen ip specified twice.");
+
+  if( ( g_socket_in = socket_udp4( )) < 0)
+    exerr("Error: Cant create live sync incoming socket." );
+  ndelay_off(g_socket_in);
+
+  if( socket_bind4_reuse( g_socket_in, tmpip, port ) == -1 )
+    exerr("Error: Cant bind live sync incoming socket." );
+
+  if( socket_mcjoin4( g_socket_in, groupip_1, v4ip ) )
+    exerr("Error: Cant make live sync incoming socket join mcast group.");
+
+  if( ( g_socket_out = socket_udp4()) < 0)
+    exerr("Error: Cant create live sync outgoing socket." );
+  if( socket_bind4_reuse( g_socket_out, v4ip, port ) == -1 )
+    exerr("Error: Cant bind live sync outgoing socket." );
+
+  socket_mcttl4(g_socket_out, 1);
+  socket_mcloop4(g_socket_out, 0);
+}
+
+/* Caller MUST hold g_outbuf_mutex. Returns with g_outbuf_mutex unlocked */
+static void livesync_issue_peersync( ) {
+  char   mycopy[LIVESYNC_OUTGOING_BUFFSIZE_PEERS];
+  size_t data = g_outbuf_data;
+
+  memcpy( mycopy, g_outbuf, data );
+  g_outbuf_data = sizeof( g_tracker_id ) + sizeof( uint32_t );
+  g_next_packet_time = g_now_seconds + LIVESYNC_MAXDELAY;
+
+  /* From now this thread has a local copy of the buffer and
+     has modified the protected element */
+  pthread_mutex_unlock(&g_outbuf_mutex);
+
+  socket_send4(g_socket_out, mycopy, data, groupip_1, LIVESYNC_PORT);
+}
+
+static void livesync_handle_peersync( struct ot_workstruct *ws ) {
+  int off = sizeof( g_tracker_id ) + sizeof( uint32_t );
+
+  /* Now basic sanity checks have been done on the live sync packet
+     We might add more testing and logging. */
+  while( off + (ssize_t)sizeof( ot_hash ) + (ssize_t)sizeof( ot_peer ) <= ws->request_size ) {
+    memcpy( &ws->peer, ws->request + off + sizeof(ot_hash), sizeof( ot_peer ) );
+    ws->hash = (ot_hash*)(ws->request + off);
+
+    if( !g_opentracker_running ) return;
+
+    if( OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED )
+      remove_peer_from_torrent( FLAG_MCA, ws );
+    else
+      add_peer_to_torrent_and_return_peers( FLAG_MCA, ws, /* amount = */ 0 );
+
+    off += sizeof( ot_hash ) + sizeof( ot_peer );
+  }
+
+  stats_issue_event(EVENT_SYNC, 0,
+                    (ws->request_size - sizeof( g_tracker_id ) - sizeof( uint32_t ) ) /
+                    ((ssize_t)sizeof( ot_hash ) + (ssize_t)sizeof( ot_peer )));
+}
+
+/* Tickle the live sync module from time to time, so no events get
+   stuck when there's not enough traffic to fill udp packets fast
+   enough */
+void livesync_ticker( ) {
+  /* livesync_issue_peersync sets g_next_packet_time */
+  pthread_mutex_lock(&g_outbuf_mutex);
+  if( g_now_seconds > g_next_packet_time &&
+     g_outbuf_data > sizeof( g_tracker_id ) + sizeof( uint32_t ) )
+    livesync_issue_peersync();
+  else
+    pthread_mutex_unlock(&g_outbuf_mutex);
+}
+
+/* Inform live sync about whats going on. */
+void livesync_tell( struct ot_workstruct *ws ) {
+  pthread_mutex_lock(&g_outbuf_mutex);
+
+  memcpy( g_outbuf + g_outbuf_data, ws->hash, sizeof(ot_hash) );
+  memcpy( g_outbuf + g_outbuf_data + sizeof(ot_hash), &ws->peer, sizeof(ot_peer) );
+
+  g_outbuf_data += sizeof(ot_hash) + sizeof(ot_peer);
+
+  if( g_outbuf_data >= LIVESYNC_OUTGOING_BUFFSIZE_PEERS - LIVESYNC_OUTGOING_WATERMARK_PEERS )
+    livesync_issue_peersync();
+  else
+    pthread_mutex_unlock(&g_outbuf_mutex);
+}
+
+static void * livesync_worker( void * args ) {
+  struct ot_workstruct ws;
+  ot_ip6 in_ip; uint16_t in_port;
+
+  (void)args;
+
+  /* Initialize our "thread local storage" */
+  ws.inbuf   = ws.request = malloc( LIVESYNC_INCOMING_BUFFSIZE );
+  ws.outbuf  = ws.reply   = 0;
+
+  memcpy( in_ip, V4mappedprefix, sizeof( V4mappedprefix ) );
+
+  while( 1 ) {
+    ws.request_size = socket_recv4(g_socket_in, (char*)ws.inbuf, LIVESYNC_INCOMING_BUFFSIZE, 12+(char*)in_ip, &in_port);
+
+    /* Expect at least tracker id and packet type */
+    if( ws.request_size <= (ssize_t)(sizeof( g_tracker_id ) + sizeof( uint32_t )) )
+      continue;
+    if( !accesslist_isblessed(in_ip, OT_PERMISSION_MAY_LIVESYNC))
+      continue;
+    if( !memcmp( ws.inbuf, &g_tracker_id, sizeof( g_tracker_id ) ) ) {
+      /* TODO: log packet coming from ourselves */
+      continue;
+    }
+
+    switch( uint32_read_big( sizeof( g_tracker_id ) + (char *)ws.inbuf ) ) {
+    case OT_SYNC_PEER:
+      livesync_handle_peersync( &ws );
+      break;
+    default:
+      break;
+    }
+  }
+
+  /* Never returns. */
+  return NULL;
+}
+
+#endif
+const char *g_version_livesync_c = "$Source$: $Revision$\n";
diff --git a/ot_livesync.h b/ot_livesync.h
new file mode 100644 (file)
index 0000000..1a3ed45
--- /dev/null
@@ -0,0 +1,72 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+ It is considered beerware. Prost. Skol. Cheers or whatever.
+
+ $id$ */
+
+#ifndef __OT_LIVESYNC_H__
+#define __OT_LIVESYNC_H__
+
+#include "io.h"
+#include "trackerlogic.h"
+
+/*
+  Syncing is done as udp packets in the multicast domain 224.0.42.5 port 9696
+
+  Each tracker should join the multicast group and send its live sync packets
+  to that group, using a ttl of 1
+
+  Format of all sync packets is straight forward, packet type determines
+  which kind of packet this is:
+
+    0x0000 0x04 id of tracker instance
+    0x0004 0x04 packet type
+
+                             ########
+ ######## PEER SYNC PROTOCOL ########
+ ########
+
+  Each tracker instance accumulates announce requests until its buffer is
+  full or a timeout is reached. Then it broadcasts its live sync packer:
+
+  packet type SYNC_LIVE
+  [ 0x0008 0x14 info_hash
+    0x001c 0x04 peer's ipv4 address
+    0x0020 0x02 peer's port
+    0x0024 0x02 peer flags v1 ( SEEDING = 0x80, COMPLETE = 0x40, STOPPED = 0x20 )
+  ]*
+
+*/
+
+#ifdef WANT_SYNC_LIVE
+
+#define LIVESYNC_PORT 9696
+
+void livesync_init();
+void livesync_deinit();
+
+/* Join multicast group for listening and create sending socket */
+void livesync_bind_mcast( char *ip, uint16_t port );
+
+/* Inform live sync about whats going on. */
+void livesync_tell( struct ot_workstruct *ws );
+
+/* Tickle the live sync module from time to time, so no events get
+   stuck when there's not enough traffic to fill udp packets fast
+   enough */
+void livesync_ticker( );
+
+/* Handle an incoming live sync packet */
+void handle_livesync( const int64 sock );
+
+#else
+
+/* If no syncing is required, save calling code from #ifdef
+   constructions */
+#define livesync_deinit()
+#define livesync_init()
+#define livesync_ticker()
+#define handle_livesync(a)
+
+#endif
+
+#endif
diff --git a/ot_mutex.c b/ot_mutex.c
new file mode 100644 (file)
index 0000000..772d936
--- /dev/null
@@ -0,0 +1,336 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* System */
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+
+/* Libowfat */
+#include "byte.h"
+#include "io.h"
+#include "uint32.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_mutex.h"
+#include "ot_stats.h"
+
+/* #define MTX_DBG( STRING ) fprintf( stderr, STRING ) */
+#define MTX_DBG( STRING )
+
+/* Our global all torrents list */
+static ot_vector all_torrents[OT_BUCKET_COUNT];
+static size_t    g_torrent_count;
+
+/* Bucket Magic */
+static int bucket_locklist[ OT_MAX_THREADS ];
+static int bucket_locklist_count = 0;
+static pthread_mutex_t bucket_mutex;
+static pthread_cond_t bucket_being_unlocked;
+
+/* Self pipe from opentracker.c */
+extern int g_self_pipe[2];
+
+static int bucket_check( int bucket ) {
+  /* C should come with auto-i ;) */
+  int i;
+
+  /* No more space to acquire lock to bucket -- should not happen */
+  if( bucket_locklist_count == OT_MAX_THREADS ) {
+    fprintf( stderr, "More lock requests than mutexes. Consult source code.\n" );
+    return -1;
+  }
+
+  /* See, if bucket is already locked */
+  for( i=0; i<bucket_locklist_count; ++i )
+    if( bucket_locklist[ i ] == bucket ) {
+      stats_issue_event( EVENT_BUCKET_LOCKED, 0, 0 );
+      return -1;
+    }
+
+  return 0;
+}
+
+static void bucket_push( int bucket ) {
+  bucket_locklist[ bucket_locklist_count++ ] = bucket;
+}
+
+static void bucket_remove( int bucket ) {
+  int i = 0;
+
+  while( ( i < bucket_locklist_count ) && ( bucket_locklist[ i ] != bucket ) )
+    ++i;
+
+  if( i == bucket_locklist_count ) {
+    fprintf( stderr, "Request to unlock bucket that was never locked. Consult source code.\n" );
+    return;
+  }
+
+  for( ; i < bucket_locklist_count - 1; ++i )
+    bucket_locklist[ i ] = bucket_locklist[ i + 1 ];
+
+  --bucket_locklist_count;
+}
+
+/* Can block */
+ot_vector *mutex_bucket_lock( int bucket ) {
+  pthread_mutex_lock( &bucket_mutex );
+  while( bucket_check( bucket ) )
+    pthread_cond_wait( &bucket_being_unlocked, &bucket_mutex );
+  bucket_push( bucket );
+  pthread_mutex_unlock( &bucket_mutex );
+  return all_torrents + bucket;
+}
+
+ot_vector *mutex_bucket_lock_by_hash( ot_hash hash ) {
+  return mutex_bucket_lock( uint32_read_big( (char*)hash ) >> OT_BUCKET_COUNT_SHIFT );
+}
+
+void mutex_bucket_unlock( int bucket, int delta_torrentcount ) {
+  pthread_mutex_lock( &bucket_mutex );
+  bucket_remove( bucket );
+  g_torrent_count += delta_torrentcount;
+  pthread_cond_broadcast( &bucket_being_unlocked );
+  pthread_mutex_unlock( &bucket_mutex );
+}
+
+void mutex_bucket_unlock_by_hash( ot_hash hash, int delta_torrentcount ) {
+  mutex_bucket_unlock( uint32_read_big( (char*)hash ) >> OT_BUCKET_COUNT_SHIFT, delta_torrentcount );
+}
+
+size_t mutex_get_torrent_count( ) {
+  size_t torrent_count;
+  pthread_mutex_lock( &bucket_mutex );
+  torrent_count = g_torrent_count;
+  pthread_mutex_unlock( &bucket_mutex );
+  return torrent_count;
+}
+
+/* TaskQueue Magic */
+
+struct ot_task {
+  ot_taskid       taskid;
+  ot_tasktype     tasktype;
+  int64           sock;
+  int             iovec_entries;
+  struct iovec   *iovec;
+  struct ot_task *next;
+};
+
+static ot_taskid next_free_taskid = 1;
+static struct ot_task *tasklist;
+static pthread_mutex_t tasklist_mutex;
+static pthread_cond_t tasklist_being_filled;
+
+int mutex_workqueue_pushtask( int64 sock, ot_tasktype tasktype ) {
+  struct ot_task ** tmptask, * task;
+
+  /* Want exclusive access to tasklist */
+  MTX_DBG( "pushtask locks.\n" );
+  pthread_mutex_lock( &tasklist_mutex );
+  MTX_DBG( "pushtask locked.\n" );
+
+  task = malloc(sizeof( struct ot_task));
+  if( !task ) {
+    MTX_DBG( "pushtask fail unlocks.\n" );
+    pthread_mutex_unlock( &tasklist_mutex );
+    MTX_DBG( "pushtask fail unlocked.\n" );
+    return -1;
+  }
+
+  /* Skip to end of list */
+  tmptask = &tasklist;
+  while( *tmptask )
+    tmptask = &(*tmptask)->next;
+  *tmptask = task;
+
+  task->taskid        = 0;
+  task->tasktype      = tasktype;
+  task->sock          = sock;
+  task->iovec_entries = 0;
+  task->iovec         = NULL;
+  task->next          = 0;
+
+  /* Inform waiting workers and release lock */
+  MTX_DBG( "pushtask broadcasts.\n" );
+  pthread_cond_broadcast( &tasklist_being_filled );
+  MTX_DBG( "pushtask broadcasted, mutex unlocks.\n" );
+  pthread_mutex_unlock( &tasklist_mutex );
+  MTX_DBG( "pushtask end mutex unlocked.\n" );
+  return 0;
+}
+
+void mutex_workqueue_canceltask( int64 sock ) {
+  struct ot_task ** task;
+
+  /* Want exclusive access to tasklist */
+  MTX_DBG( "canceltask locks.\n" );
+  pthread_mutex_lock( &tasklist_mutex );
+  MTX_DBG( "canceltask locked.\n" );
+
+  task = &tasklist;
+  while( *task && ( (*task)->sock != sock ) )
+    *task = (*task)->next;
+
+  if( *task && ( (*task)->sock == sock ) ) {
+    struct iovec *iovec = (*task)->iovec;
+    struct ot_task *ptask = *task;
+    int i;
+
+    /* Free task's iovec */
+    for( i=0; i<(*task)->iovec_entries; ++i )
+      munmap( iovec[i].iov_base, iovec[i].iov_len );
+
+    *task = (*task)->next;
+    free( ptask );
+  }
+
+  /* Release lock */
+  MTX_DBG( "canceltask unlocks.\n" );
+  pthread_mutex_unlock( &tasklist_mutex );
+  MTX_DBG( "canceltask unlocked.\n" );
+}
+
+ot_taskid mutex_workqueue_poptask( ot_tasktype *tasktype ) {
+  struct ot_task * task;
+  ot_taskid taskid = 0;
+
+  /* Want exclusive access to tasklist */
+  MTX_DBG( "poptask mutex locks.\n" );
+  pthread_mutex_lock( &tasklist_mutex );
+  MTX_DBG( "poptask mutex locked.\n" );
+
+  while( !taskid ) {
+    /* Skip to the first unassigned task this worker wants to do */
+    task = tasklist;
+    while( task && ( ( ( TASK_CLASS_MASK & task->tasktype ) != *tasktype ) || task->taskid ) )
+      task = task->next;
+
+    /* If we found an outstanding task, assign a taskid to it
+       and leave the loop */
+    if( task ) {
+      task->taskid = taskid = ++next_free_taskid;
+      *tasktype = task->tasktype;
+    } else {
+      /* Wait until the next task is being fed */
+      MTX_DBG( "poptask cond waits.\n" );
+      pthread_cond_wait( &tasklist_being_filled, &tasklist_mutex );
+      MTX_DBG( "poptask cond waited.\n" );
+    }
+  }
+
+  /* Release lock */
+  MTX_DBG( "poptask end mutex unlocks.\n" );
+  pthread_mutex_unlock( &tasklist_mutex );
+  MTX_DBG( "poptask end mutex unlocked.\n" );
+
+  return taskid;
+}
+
+void mutex_workqueue_pushsuccess( ot_taskid taskid ) {
+  struct ot_task ** task;
+
+  /* Want exclusive access to tasklist */
+  MTX_DBG( "pushsuccess locks.\n" );
+  pthread_mutex_lock( &tasklist_mutex );
+  MTX_DBG( "pushsuccess locked.\n" );
+
+  task = &tasklist;
+  while( *task && ( (*task)->taskid != taskid ) )
+    *task = (*task)->next;
+
+  if( *task && ( (*task)->taskid == taskid ) ) {
+    struct ot_task *ptask = *task;
+    *task = (*task)->next;
+    free( ptask );
+  }
+
+  /* Release lock */
+  MTX_DBG( "pushsuccess unlocks.\n" );
+  pthread_mutex_unlock( &tasklist_mutex );
+  MTX_DBG( "pushsuccess unlocked.\n" );
+}
+
+int mutex_workqueue_pushresult( ot_taskid taskid, int iovec_entries, struct iovec *iovec ) {
+  struct ot_task * task;
+  const char byte = 'o';
+
+  /* Want exclusive access to tasklist */
+  MTX_DBG( "pushresult locks.\n" );
+  pthread_mutex_lock( &tasklist_mutex );
+  MTX_DBG( "pushresult locked.\n" );
+
+  task = tasklist;
+  while( task && ( task->taskid != taskid ) )
+    task = task->next;
+
+  if( task ) {
+    task->iovec_entries = iovec_entries;
+    task->iovec         = iovec;
+    task->tasktype      = TASK_DONE;
+  }
+
+  /* Release lock */
+  MTX_DBG( "pushresult unlocks.\n" );
+  pthread_mutex_unlock( &tasklist_mutex );
+  MTX_DBG( "pushresult unlocked.\n" );
+
+  io_trywrite( g_self_pipe[1], &byte, 1 );
+
+  /* Indicate whether the worker has to throw away results */
+  return task ? 0 : -1;
+}
+
+int64 mutex_workqueue_popresult( int *iovec_entries, struct iovec ** iovec ) {
+  struct ot_task ** task;
+  int64 sock = -1;
+
+  /* Want exclusive access to tasklist */
+  MTX_DBG( "popresult locks.\n" );
+  pthread_mutex_lock( &tasklist_mutex );
+  MTX_DBG( "popresult locked.\n" );
+
+  task = &tasklist;
+  while( *task && ( (*task)->tasktype != TASK_DONE ) )
+    task = &(*task)->next;
+
+  if( *task && ( (*task)->tasktype == TASK_DONE ) ) {
+    struct ot_task *ptask = *task;
+
+    *iovec_entries = (*task)->iovec_entries;
+    *iovec         = (*task)->iovec;
+    sock           = (*task)->sock;
+
+    *task = (*task)->next;
+    free( ptask );
+  }
+
+  /* Release lock */
+  MTX_DBG( "popresult unlocks.\n" );
+  pthread_mutex_unlock( &tasklist_mutex );
+  MTX_DBG( "popresult unlocked.\n" );
+  return sock;
+}
+
+void mutex_init( ) {
+  pthread_mutex_init(&tasklist_mutex, NULL);
+  pthread_cond_init (&tasklist_being_filled, NULL);
+  pthread_mutex_init(&bucket_mutex, NULL);
+  pthread_cond_init (&bucket_being_unlocked, NULL);
+  byte_zero( all_torrents, sizeof( all_torrents ) );
+}
+
+void mutex_deinit( ) {
+  pthread_mutex_destroy(&bucket_mutex);
+  pthread_cond_destroy(&bucket_being_unlocked);
+  pthread_mutex_destroy(&tasklist_mutex);
+  pthread_cond_destroy(&tasklist_being_filled);
+  byte_zero( all_torrents, sizeof( all_torrents ) );
+}
+
+const char *g_version_mutex_c = "$Source$: $Revision$\n";
diff --git a/ot_mutex.h b/ot_mutex.h
new file mode 100644 (file)
index 0000000..b0f0f0b
--- /dev/null
@@ -0,0 +1,74 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_MUTEX_H__
+#define __OT_MUTEX_H__
+
+#include <sys/uio.h>
+
+void mutex_init( );
+void mutex_deinit( );
+
+ot_vector *mutex_bucket_lock( int bucket );
+ot_vector *mutex_bucket_lock_by_hash( ot_hash hash );
+
+void mutex_bucket_unlock( int bucket, int delta_torrentcount );
+void mutex_bucket_unlock_by_hash( ot_hash hash, int delta_torrentcount );
+
+size_t mutex_get_torrent_count();
+
+typedef enum {
+  TASK_STATS_CONNS                 = 0x0001,
+  TASK_STATS_TCP                   = 0x0002,
+  TASK_STATS_UDP                   = 0x0003,
+  TASK_STATS_SCRAPE                = 0x0004,
+  TASK_STATS_FULLSCRAPE            = 0x0005,
+  TASK_STATS_TPB                   = 0x0006,
+  TASK_STATS_HTTPERRORS            = 0x0007,
+  TASK_STATS_VERSION               = 0x0008,
+  TASK_STATS_BUSY_NETWORKS         = 0x0009,
+  TASK_STATS_RENEW                 = 0x000a,
+  TASK_STATS_SYNCS                 = 0x000b,
+  TASK_STATS_COMPLETED             = 0x000c,
+  TASK_STATS_NUMWANTS              = 0x000d,
+
+  TASK_STATS                       = 0x0100, /* Mask */
+  TASK_STATS_TORRENTS              = 0x0101,
+  TASK_STATS_PEERS                 = 0x0102,
+  TASK_STATS_SLASH24S              = 0x0103,
+  TASK_STATS_TOP10                 = 0x0104,
+  TASK_STATS_TOP100                = 0x0105,
+  TASK_STATS_EVERYTHING            = 0x0106,
+  TASK_STATS_FULLLOG               = 0x0107,
+  TASK_STATS_WOODPECKERS           = 0x0108,
+  
+  TASK_FULLSCRAPE                  = 0x0200, /* Default mode */
+  TASK_FULLSCRAPE_TPB_BINARY       = 0x0201,
+  TASK_FULLSCRAPE_TPB_ASCII        = 0x0202,
+  TASK_FULLSCRAPE_TPB_URLENCODED   = 0x0203,
+  TASK_FULLSCRAPE_TRACKERSTATE     = 0x0204,
+
+  TASK_DMEM                        = 0x0300,
+
+  TASK_DONE                        = 0x0f00,
+
+  TASK_FLAG_GZIP                   = 0x1000,
+  TASK_FLAG_BZIP2                  = 0x2000,
+
+  TASK_TASK_MASK                   = 0x0fff,
+  TASK_CLASS_MASK                  = 0x0f00,
+  TASK_FLAGS_MASK                  = 0xf000
+} ot_tasktype;
+
+typedef unsigned long ot_taskid;
+
+int       mutex_workqueue_pushtask( int64 sock, ot_tasktype tasktype );
+void      mutex_workqueue_canceltask( int64 sock );
+void      mutex_workqueue_pushsuccess( ot_taskid taskid );
+ot_taskid mutex_workqueue_poptask( ot_tasktype *tasktype );
+int       mutex_workqueue_pushresult( ot_taskid taskid, int iovec_entries, struct iovec *iovector );
+int64     mutex_workqueue_popresult( int *iovec_entries, struct iovec ** iovector );
+
+#endif
diff --git a/ot_rijndael.c b/ot_rijndael.c
new file mode 100644 (file)
index 0000000..f468e2f
--- /dev/null
@@ -0,0 +1,490 @@
+/**
+ * rijndael-alg-fst.c
+ *
+ * @version 3.0 (December 2000)
+ *
+ * Optimised ANSI C code for the Rijndael cipher (now AES)
+ *
+ * @author Vincent Rijmen <vincent.rijmen@esat.kuleuven.ac.be>
+ * @author Antoon Bosselaers <antoon.bosselaers@esat.kuleuven.ac.be>
+ * @author Paulo Barreto <paulo.barreto@terra.com.br>
+ *
+ * This code is hereby placed in the public domain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "ot_rijndael.h"
+
+static const uint32_t Te0[256] = {
+    0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU,
+    0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U,
+    0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU,
+    0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU,
+    0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U,
+    0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU,
+    0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU,
+    0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU,
+    0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU,
+    0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU,
+    0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U,
+    0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU,
+    0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU,
+    0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U,
+    0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU,
+    0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU,
+    0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU,
+    0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU,
+    0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU,
+    0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U,
+    0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU,
+    0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU,
+    0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU,
+    0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU,
+    0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U,
+    0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U,
+    0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U,
+    0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U,
+    0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU,
+    0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U,
+    0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U,
+    0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU,
+    0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU,
+    0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U,
+    0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U,
+    0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U,
+    0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU,
+    0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U,
+    0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU,
+    0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U,
+    0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU,
+    0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U,
+    0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U,
+    0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU,
+    0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U,
+    0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U,
+    0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U,
+    0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U,
+    0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U,
+    0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U,
+    0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U,
+    0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U,
+    0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU,
+    0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U,
+    0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U,
+    0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U,
+    0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U,
+    0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U,
+    0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U,
+    0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU,
+    0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U,
+    0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U,
+    0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U,
+    0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU,
+};
+static const uint32_t Te1[256] = {
+    0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU,
+    0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U,
+    0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU,
+    0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U,
+    0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU,
+    0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U,
+    0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU,
+    0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U,
+    0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U,
+    0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU,
+    0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U,
+    0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U,
+    0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U,
+    0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU,
+    0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U,
+    0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U,
+    0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU,
+    0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U,
+    0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U,
+    0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U,
+    0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU,
+    0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU,
+    0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U,
+    0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU,
+    0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU,
+    0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U,
+    0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU,
+    0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U,
+    0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU,
+    0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U,
+    0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U,
+    0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U,
+    0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU,
+    0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U,
+    0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU,
+    0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U,
+    0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU,
+    0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U,
+    0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U,
+    0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU,
+    0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU,
+    0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU,
+    0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U,
+    0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U,
+    0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU,
+    0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U,
+    0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU,
+    0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U,
+    0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU,
+    0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U,
+    0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU,
+    0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU,
+    0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U,
+    0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU,
+    0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U,
+    0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU,
+    0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U,
+    0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U,
+    0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U,
+    0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU,
+    0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU,
+    0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U,
+    0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU,
+    0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U,
+};
+static const uint32_t Te2[256] = {
+    0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU,
+    0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U,
+    0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU,
+    0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U,
+    0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU,
+    0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U,
+    0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU,
+    0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U,
+    0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U,
+    0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU,
+    0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U,
+    0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U,
+    0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U,
+    0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU,
+    0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U,
+    0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U,
+    0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU,
+    0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U,
+    0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U,
+    0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U,
+    0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU,
+    0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU,
+    0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U,
+    0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU,
+    0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU,
+    0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U,
+    0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU,
+    0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U,
+    0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU,
+    0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U,
+    0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U,
+    0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U,
+    0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU,
+    0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U,
+    0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU,
+    0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U,
+    0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU,
+    0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U,
+    0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U,
+    0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU,
+    0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU,
+    0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU,
+    0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U,
+    0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U,
+    0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU,
+    0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U,
+    0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU,
+    0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U,
+    0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU,
+    0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U,
+    0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU,
+    0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU,
+    0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U,
+    0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU,
+    0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U,
+    0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU,
+    0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U,
+    0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U,
+    0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U,
+    0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU,
+    0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU,
+    0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U,
+    0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU,
+    0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U,
+};
+static const uint32_t Te3[256] = {
+
+    0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U,
+    0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U,
+    0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U,
+    0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU,
+    0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU,
+    0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU,
+    0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U,
+    0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU,
+    0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU,
+    0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U,
+    0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U,
+    0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU,
+    0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU,
+    0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU,
+    0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU,
+    0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU,
+    0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U,
+    0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU,
+    0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU,
+    0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U,
+    0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U,
+    0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U,
+    0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U,
+    0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U,
+    0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU,
+    0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U,
+    0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU,
+    0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU,
+    0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U,
+    0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U,
+    0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U,
+    0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU,
+    0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U,
+    0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU,
+    0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU,
+    0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U,
+    0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U,
+    0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU,
+    0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U,
+    0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU,
+    0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U,
+    0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U,
+    0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U,
+    0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U,
+    0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU,
+    0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U,
+    0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU,
+    0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U,
+    0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU,
+    0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U,
+    0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU,
+    0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU,
+    0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU,
+    0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU,
+    0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U,
+    0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U,
+    0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U,
+    0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U,
+    0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U,
+    0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U,
+    0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU,
+    0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U,
+    0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU,
+    0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU,
+};
+static const uint32_t Te4[256] = {
+    0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU,
+    0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U,
+    0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU,
+    0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U,
+    0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU,
+    0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U,
+    0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU,
+    0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U,
+    0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U,
+    0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU,
+    0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U,
+    0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U,
+    0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U,
+    0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU,
+    0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U,
+    0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U,
+    0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU,
+    0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U,
+    0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U,
+    0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U,
+    0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU,
+    0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU,
+    0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U,
+    0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU,
+    0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU,
+    0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U,
+    0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU,
+    0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U,
+    0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU,
+    0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U,
+    0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U,
+    0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U,
+    0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU,
+    0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U,
+    0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU,
+    0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U,
+    0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU,
+    0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U,
+    0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U,
+    0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU,
+    0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU,
+    0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU,
+    0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U,
+    0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U,
+    0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU,
+    0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U,
+    0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU,
+    0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U,
+    0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU,
+    0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U,
+    0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU,
+    0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU,
+    0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U,
+    0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU,
+    0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U,
+    0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU,
+    0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U,
+    0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U,
+    0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U,
+    0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU,
+    0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU,
+    0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U,
+    0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU,
+    0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U,
+};
+static const uint32_t rcon[] = {
+    0x01000000, 0x02000000, 0x04000000, 0x08000000,
+    0x10000000, 0x20000000, 0x40000000, 0x80000000,
+    0x1B000000, 0x36000000, /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */
+};
+
+#define GETU32(pt) (((uint32_t)(pt)[0] << 24) ^ ((uint32_t)(pt)[1] << 16) ^ ((uint32_t)(pt)[2] <<  8) ^ ((uint32_t)(pt)[3]))
+#define PUTU32(ct, st) { (ct)[0] = (uint8_t)((st) >> 24); (ct)[1] = (uint8_t)((st) >> 16); (ct)[2] = (uint8_t)((st) >>  8); (ct)[3] = (uint8_t)(st); }
+
+/**
+ * Expand the cipher key into the encryption key schedule.
+ *
+ * @return  the number of rounds for the given cipher key size.
+ */
+int rijndaelKeySetupEnc128(uint32_t rk[44], const uint8_t cipherKey[] ) {
+    int i = 0;
+    uint32_t temp;
+
+    rk[0] = GETU32(cipherKey     );
+    rk[1] = GETU32(cipherKey +  4);
+    rk[2] = GETU32(cipherKey +  8);
+    rk[3] = GETU32(cipherKey + 12);
+    for (;;) {
+        temp  = rk[3];
+        rk[4] = rk[0] ^
+            (Te4[(temp >> 16) & 0xff] & 0xff000000) ^
+            (Te4[(temp >>  8) & 0xff] & 0x00ff0000) ^
+            (Te4[(temp      ) & 0xff] & 0x0000ff00) ^
+            (Te4[(temp >> 24)       ] & 0x000000ff) ^
+            rcon[i];
+        rk[5] = rk[1] ^ rk[4];
+        rk[6] = rk[2] ^ rk[5];
+        rk[7] = rk[3] ^ rk[6];
+        if (++i == 10) {
+            return 10;
+        }
+        rk += 4;
+    }
+}
+
+void rijndaelEncrypt128(const uint32_t rk[44], const uint8_t pt[16], uint8_t ct[16]) {
+    uint32_t s0, s1, s2, s3, t0, t1, t2, t3;
+
+    /*
+     * map byte array block to cipher state
+     * and add initial round key:
+     */
+    s0 = GETU32(pt     ) ^ rk[0];
+    s1 = GETU32(pt +  4) ^ rk[1];
+    s2 = GETU32(pt +  8) ^ rk[2];
+    s3 = GETU32(pt + 12) ^ rk[3];
+    /* round 1: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[ 4];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[ 5];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[ 6];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[ 7];
+    /* round 2: */
+    s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[ 8];
+    s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[ 9];
+    s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[10];
+    s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[11];
+    /* round 3: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[12];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[13];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[14];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[15];
+    /* round 4: */
+    s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[16];
+    s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[17];
+    s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[18];
+    s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[19];
+    /* round 5: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[20];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[21];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[22];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[23];
+    /* round 6: */
+    s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[24];
+    s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[25];
+    s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[26];
+    s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[27];
+    /* round 7: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[28];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[29];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[30];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[31];
+    /* round 8: */
+    s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[32];
+    s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[33];
+    s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[34];
+    s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[35];
+    /* round 9: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[36];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[37];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[38];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[39];
+
+    /*
+     * apply last round and
+     * map cipher state to byte array block:
+     */
+    s0 =
+        (Te4[(t0 >> 24)       ] & 0xff000000) ^
+        (Te4[(t1 >> 16) & 0xff] & 0x00ff0000) ^
+        (Te4[(t2 >>  8) & 0xff] & 0x0000ff00) ^
+        (Te4[(t3      ) & 0xff] & 0x000000ff) ^
+        rk[40];
+    PUTU32(ct     , s0);
+    s1 =
+        (Te4[(t1 >> 24)       ] & 0xff000000) ^
+        (Te4[(t2 >> 16) & 0xff] & 0x00ff0000) ^
+        (Te4[(t3 >>  8) & 0xff] & 0x0000ff00) ^
+        (Te4[(t0      ) & 0xff] & 0x000000ff) ^
+        rk[41];
+    PUTU32(ct +  4, s1);
+    s2 =
+        (Te4[(t2 >> 24)       ] & 0xff000000) ^
+        (Te4[(t3 >> 16) & 0xff] & 0x00ff0000) ^
+        (Te4[(t0 >>  8) & 0xff] & 0x0000ff00) ^
+        (Te4[(t1      ) & 0xff] & 0x000000ff) ^
+        rk[42];
+    PUTU32(ct +  8, s2);
+    s3 =
+        (Te4[(t3 >> 24)       ] & 0xff000000) ^
+        (Te4[(t0 >> 16) & 0xff] & 0x00ff0000) ^
+        (Te4[(t1 >>  8) & 0xff] & 0x0000ff00) ^
+        (Te4[(t2      ) & 0xff] & 0x000000ff) ^
+        rk[43];
+    PUTU32(ct + 12, s3);
+}
+
+const char *g_version_rijndael_c = "$Source$: $Revision$\n";
diff --git a/ot_rijndael.h b/ot_rijndael.h
new file mode 100644 (file)
index 0000000..7f57b3f
--- /dev/null
@@ -0,0 +1,20 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   The rijndael implementation was taken from
+
+     http://www.cs.ucdavis.edu/~rogaway/ocb/ocb-ref/rijndael-alg-fst.c
+
+   and modified to work with 128 bits (this is 10 rounds) only.
+
+   $id$ */
+
+#ifndef __OT_RIJNDAEL_H__
+#define __OT_RIJNDAEL_H__
+
+#include <stdint.h>
+
+int rijndaelKeySetupEnc128(uint32_t rk[44], const uint8_t cipherKey[] );
+void rijndaelEncrypt128(const uint32_t rk[44], const uint8_t pt[16], uint8_t ct[16]);
+
+#endif
diff --git a/ot_stats.c b/ot_stats.c
new file mode 100644 (file)
index 0000000..4edfad7
--- /dev/null
@@ -0,0 +1,772 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+ It is considered beerware. Prost. Skol. Cheers or whatever.
+
+ $id$ */
+
+/* System */
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <inttypes.h>
+#ifdef WANT_SYSLOGS
+#include <syslog.h>
+#endif
+
+/* Libowfat */
+#include "byte.h"
+#include "io.h"
+#include "ip4.h"
+#include "ip6.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_mutex.h"
+#include "ot_iovec.h"
+#include "ot_stats.h"
+#include "ot_accesslist.h"
+
+#ifndef NO_FULLSCRAPE_LOGGING
+#define LOG_TO_STDERR( ... ) fprintf( stderr, __VA_ARGS__ )
+#else
+#define LOG_TO_STDERR( ... )
+#endif
+
+/* Forward declaration */
+static void stats_make( int *iovec_entries, struct iovec **iovector, ot_tasktype mode );
+#define OT_STATS_TMPSIZE 8192
+
+/* Clumsy counters... to be rethought */
+static unsigned long long ot_overall_tcp_connections = 0;
+static unsigned long long ot_overall_udp_connections = 0;
+static unsigned long long ot_overall_tcp_successfulannounces = 0;
+static unsigned long long ot_overall_udp_successfulannounces = 0;
+static unsigned long long ot_overall_tcp_successfulscrapes = 0;
+static unsigned long long ot_overall_udp_successfulscrapes = 0;
+static unsigned long long ot_overall_udp_connectionidmissmatches = 0;
+static unsigned long long ot_overall_tcp_connects = 0;
+static unsigned long long ot_overall_udp_connects = 0;
+static unsigned long long ot_overall_completed = 0;
+static unsigned long long ot_full_scrape_count = 0;
+static unsigned long long ot_full_scrape_request_count = 0;
+static unsigned long long ot_full_scrape_size = 0;
+static unsigned long long ot_failed_request_counts[CODE_HTTPERROR_COUNT];
+static char *             ot_failed_request_names[] = { "302 Redirect", "400 Parse Error", "400 Invalid Parameter", "400 Invalid Parameter (compact=0)", "400 Not Modest", "403 Access Denied", "404 Not found", "500 Internal Server Error" };
+static unsigned long long ot_renewed[OT_PEER_TIMEOUT];
+static unsigned long long ot_overall_sync_count;
+static unsigned long long ot_overall_stall_count;
+
+static time_t ot_start_time;
+
+#define STATS_NETWORK_NODE_BITWIDTH       4
+#define STATS_NETWORK_NODE_COUNT         (1<<STATS_NETWORK_NODE_BITWIDTH)
+
+#define __BYTE(P,D)  (((uint8_t*)P)[D/8])
+#define __MSK        (STATS_NETWORK_NODE_COUNT-1)
+#define __SHFT(D)    ((D^STATS_NETWORK_NODE_BITWIDTH)&STATS_NETWORK_NODE_BITWIDTH)
+
+#define __LDR(P,D)   ((__BYTE((P),(D))>>__SHFT((D)))&__MSK)
+#define __STR(P,D,V)   __BYTE((P),(D))=(__BYTE((P),(D))&~(__MSK<<__SHFT((D))))|((V)<<__SHFT((D)))
+
+#ifdef WANT_V6
+#define STATS_NETWORK_NODE_MAXDEPTH  (68-STATS_NETWORK_NODE_BITWIDTH)
+#define STATS_NETWORK_NODE_LIMIT     (48-STATS_NETWORK_NODE_BITWIDTH)
+#else
+#define STATS_NETWORK_NODE_MAXDEPTH  (28-STATS_NETWORK_NODE_BITWIDTH)
+#define STATS_NETWORK_NODE_LIMIT     (24-STATS_NETWORK_NODE_BITWIDTH)
+#endif
+
+typedef union stats_network_node stats_network_node;
+union stats_network_node {
+  size_t              counters[STATS_NETWORK_NODE_COUNT];
+  stats_network_node *children[STATS_NETWORK_NODE_COUNT];
+};
+
+#ifdef WANT_LOG_NETWORKS
+static stats_network_node *stats_network_counters_root;
+#endif
+
+static int stat_increase_network_count( stats_network_node **pnode, int depth, uintptr_t ip ) {
+  int foo = __LDR(ip,depth);
+  stats_network_node *node;
+
+  if( !*pnode ) {
+    *pnode = malloc( sizeof( stats_network_node ) );
+    if( !*pnode )
+      return -1;
+    memset( *pnode, 0, sizeof( stats_network_node ) );
+  }
+  node = *pnode;
+
+  if( depth < STATS_NETWORK_NODE_MAXDEPTH )
+    return stat_increase_network_count( node->children + foo, depth+STATS_NETWORK_NODE_BITWIDTH, ip );
+
+  node->counters[ foo ]++;
+  return 0;
+}
+
+static int stats_shift_down_network_count( stats_network_node **node, int depth, int shift ) {
+  int i, rest = 0;
+
+  if( !*node )
+    return 0;
+
+  for( i=0; i<STATS_NETWORK_NODE_COUNT; ++i )
+    if( depth < STATS_NETWORK_NODE_MAXDEPTH )
+      rest += stats_shift_down_network_count( (*node)->children + i, depth+STATS_NETWORK_NODE_BITWIDTH, shift );
+    else
+      rest += (*node)->counters[i] >>= shift;
+
+  if( !rest ) {
+    free( *node );
+    *node = NULL;
+  }
+
+  return rest;
+}
+
+static size_t stats_get_highscore_networks( stats_network_node *node, int depth, ot_ip6 node_value, size_t *scores, ot_ip6 *networks, int network_count, int limit ) {
+  size_t score = 0;
+  int i;
+
+  if( !node ) return 0;
+
+  if( depth < limit ) {
+    for( i=0; i<STATS_NETWORK_NODE_COUNT; ++i )
+      if( node->children[i] ) {
+        __STR(node_value,depth,i);
+        score += stats_get_highscore_networks( node->children[i], depth+STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit );
+      }
+    return score;
+  }
+
+  if( depth > limit && depth < STATS_NETWORK_NODE_MAXDEPTH ) {
+    for( i=0; i<STATS_NETWORK_NODE_COUNT; ++i )
+      if( node->children[i] )
+        score += stats_get_highscore_networks( node->children[i], depth+STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit );
+    return score;
+  }
+
+  if( depth > limit && depth == STATS_NETWORK_NODE_MAXDEPTH ) {
+    for( i=0; i<STATS_NETWORK_NODE_COUNT; ++i )
+      score += node->counters[i];
+    return score;
+  }
+
+  /* if( depth == limit ) */
+  for( i=0; i<STATS_NETWORK_NODE_COUNT; ++i ) {
+    int j=1;
+    size_t node_score;
+
+    if( depth == STATS_NETWORK_NODE_MAXDEPTH )
+      node_score = node->counters[i];
+    else
+      node_score = stats_get_highscore_networks( node->children[i], depth+STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit );
+
+    score += node_score;
+
+    if( node_score <= scores[0] ) continue;
+
+    __STR(node_value,depth,i);
+    while( j < network_count && node_score > scores[j] ) ++j;
+    --j;
+
+    memcpy( scores, scores + 1, j * sizeof( *scores ) );
+    memcpy( networks, networks + 1, j * sizeof( *networks ) );
+    scores[ j ] = node_score;
+    memcpy( networks + j, node_value, sizeof( *networks ) );
+  }
+
+  return score;
+}
+
+static size_t stats_return_busy_networks( char * reply, stats_network_node *tree, int amount, int limit ) {
+  ot_ip6   networks[amount];
+  ot_ip6   node_value;
+  size_t   scores[amount];
+  int      i;
+  char   * r = reply;
+
+  memset( scores, 0, sizeof( scores ) );
+  memset( networks, 0, sizeof( networks ) );
+  memset( node_value, 0, sizeof( node_value ) );
+
+  stats_get_highscore_networks( tree, 0, node_value, scores, networks, amount, limit );
+
+  r += sprintf( r, "Networks, limit /%d:\n", limit+STATS_NETWORK_NODE_BITWIDTH );
+  for( i=amount-1; i>=0; --i) {
+    if( scores[i] ) {
+      r += sprintf( r, "%08zd: ", scores[i] );
+#ifdef WANT_V6
+      r += fmt_ip6c( r, networks[i] );
+#else
+      r += fmt_ip4( r, networks[i]);
+#endif
+      *r++ = '\n';
+    }
+  }
+  *r++ = '\n';
+
+  return r - reply;
+}
+
+static size_t stats_slash24s_txt( char *reply, size_t amount ) {
+  stats_network_node *slash24s_network_counters_root = NULL;
+  char *r=reply;
+  int bucket;
+  size_t i;
+
+  for( bucket=0; bucket<OT_BUCKET_COUNT; ++bucket ) {
+    ot_vector *torrents_list = mutex_bucket_lock( bucket );
+    for( i=0; i<torrents_list->size; ++i ) {
+      ot_peerlist *peer_list = ( ((ot_torrent*)(torrents_list->data))[i] ).peer_list;
+      ot_vector   *bucket_list = &peer_list->peers;
+      int          num_buckets = 1;
+
+      if( OT_PEERLIST_HASBUCKETS( peer_list ) ) {
+        num_buckets = bucket_list->size;
+        bucket_list = (ot_vector *)bucket_list->data;
+      }
+
+      while( num_buckets-- ) {
+        ot_peer *peers = (ot_peer*)bucket_list->data;
+        size_t   numpeers = bucket_list->size;
+        while( numpeers-- )
+          if( stat_increase_network_count( &slash24s_network_counters_root, 0, (uintptr_t)(peers++) ) )
+            goto bailout_unlock;
+        ++bucket_list;
+      }
+    }
+    mutex_bucket_unlock( bucket, 0 );
+    if( !g_opentracker_running )
+      goto bailout_error;
+  }
+
+  /* The tree is built. Now analyze */
+  r += stats_return_busy_networks( r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH );
+  r += stats_return_busy_networks( r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT );
+  goto success;
+
+bailout_unlock:
+  mutex_bucket_unlock( bucket, 0 );
+bailout_error:
+  r = reply;
+success:
+  stats_shift_down_network_count( &slash24s_network_counters_root, 0, sizeof(int)*8-1 );
+
+  return r-reply;
+}
+
+#ifdef WANT_SPOT_WOODPECKER
+static stats_network_node *stats_woodpeckers_tree;
+static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static size_t stats_return_woodpeckers( char * reply, int amount ) {
+  char * r = reply;
+
+  pthread_mutex_lock( &g_woodpeckers_mutex );
+  r += stats_return_busy_networks( r, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH );
+  pthread_mutex_unlock( &g_woodpeckers_mutex );
+  return r-reply;
+}
+#endif
+
+typedef struct {
+  unsigned long long torrent_count;
+  unsigned long long peer_count;
+  unsigned long long seed_count;
+} torrent_stats;
+
+static int torrent_statter( ot_torrent *torrent, uintptr_t data ) {
+  torrent_stats *stats = (torrent_stats*)data;
+  stats->torrent_count++;
+  stats->peer_count += torrent->peer_list->peer_count;
+  stats->seed_count += torrent->peer_list->seed_count;
+  return 0;
+}
+
+/* Converter function from memory to human readable hex strings */
+static char*to_hex(char*d,uint8_t*s){char*m="0123456789ABCDEF";char *t=d;char*e=d+40;while(d<e){*d++=m[*s>>4];*d++=m[*s++&15];}*d=0;return t;}
+
+typedef struct { size_t val; ot_torrent * torrent; } ot_record;
+
+/* Fetches stats from tracker */
+size_t stats_top_txt( char * reply, int amount ) {
+  size_t    j;
+  ot_record top100s[100], top100c[100];
+  char     *r  = reply, hex_out[42];
+  int       idx, bucket;
+
+  if( amount > 100 )
+    amount = 100;
+
+  byte_zero( top100s, sizeof( top100s ) );
+  byte_zero( top100c, sizeof( top100c ) );
+
+  for( bucket=0; bucket<OT_BUCKET_COUNT; ++bucket ) {
+    ot_vector *torrents_list = mutex_bucket_lock( bucket );
+    for( j=0; j<torrents_list->size; ++j ) {
+      ot_peerlist *peer_list = ( ((ot_torrent*)(torrents_list->data))[j] ).peer_list;
+      int idx = amount - 1; while( (idx >= 0) && ( peer_list->peer_count > top100c[idx].val ) ) --idx;
+      if ( idx++ != amount - 1 ) {
+        memmove( top100c + idx + 1, top100c + idx, ( amount - 1 - idx ) * sizeof( ot_record ) );
+        top100c[idx].val = peer_list->peer_count;
+        top100c[idx].torrent = (ot_torrent*)(torrents_list->data) + j;
+      }
+      idx = amount - 1; while( (idx >= 0) && ( peer_list->seed_count > top100s[idx].val ) ) --idx;
+      if ( idx++ != amount - 1 ) {
+        memmove( top100s + idx + 1, top100s + idx, ( amount - 1 - idx ) * sizeof( ot_record ) );
+        top100s[idx].val = peer_list->seed_count;
+        top100s[idx].torrent = (ot_torrent*)(torrents_list->data) + j;
+      }
+    }
+    mutex_bucket_unlock( bucket, 0 );
+    if( !g_opentracker_running )
+      return 0;
+  }
+
+  r += sprintf( r, "Top %d torrents by peers:\n", amount );
+  for( idx=0; idx<amount; ++idx )
+    if( top100c[idx].torrent )
+      r += sprintf( r, "\t%zd\t%s\n", top100c[idx].val, to_hex( hex_out, top100c[idx].torrent->hash) );
+  r += sprintf( r, "Top %d torrents by seeds:\n", amount );
+  for( idx=0; idx<amount; ++idx )
+    if( top100s[idx].torrent )
+      r += sprintf( r, "\t%zd\t%s\n", top100s[idx].val, to_hex( hex_out, top100s[idx].torrent->hash) );
+
+  return r - reply;
+}
+
+static unsigned long events_per_time( unsigned long long events, time_t t ) {
+  return events / ( (unsigned int)t ? (unsigned int)t : 1 );
+}
+
+static size_t stats_connections_mrtg( char * reply ) {
+  ot_time t = time( NULL ) - ot_start_time;
+  return sprintf( reply,
+                 "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.",
+                 ot_overall_tcp_connections+ot_overall_udp_connections,
+                 ot_overall_tcp_successfulannounces+ot_overall_udp_successfulannounces+ot_overall_udp_connects,
+                 (int)t,
+                 (int)(t / 3600),
+                 events_per_time( ot_overall_tcp_connections+ot_overall_udp_connections, t ),
+                 events_per_time( ot_overall_tcp_successfulannounces+ot_overall_udp_successfulannounces+ot_overall_udp_connects, t )
+                 );
+}
+
+static size_t stats_udpconnections_mrtg( char * reply ) {
+  ot_time t = time( NULL ) - ot_start_time;
+  return sprintf( reply,
+                 "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.",
+                 ot_overall_udp_connections,
+                 ot_overall_udp_successfulannounces+ot_overall_udp_connects,
+                 (int)t,
+                 (int)(t / 3600),
+                 events_per_time( ot_overall_udp_connections, t ),
+                 events_per_time( ot_overall_udp_successfulannounces+ot_overall_udp_connects, t )
+                 );
+}
+
+static size_t stats_tcpconnections_mrtg( char * reply ) {
+  time_t t = time( NULL ) - ot_start_time;
+  return sprintf( reply,
+                 "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.",
+                 ot_overall_tcp_connections,
+                 ot_overall_tcp_successfulannounces,
+                 (int)t,
+                 (int)(t / 3600),
+                 events_per_time( ot_overall_tcp_connections, t ),
+                 events_per_time( ot_overall_tcp_successfulannounces, t )
+                 );
+}
+
+static size_t stats_scrape_mrtg( char * reply ) {
+  time_t t = time( NULL ) - ot_start_time;
+  return sprintf( reply,
+                 "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)",
+                 ot_overall_tcp_successfulscrapes,
+                 ot_overall_udp_successfulscrapes,
+                 (int)t,
+                 (int)(t / 3600),
+                 events_per_time( (ot_overall_tcp_successfulscrapes+ot_overall_udp_successfulscrapes), t )
+                 );
+}
+
+static size_t stats_fullscrapes_mrtg( char * reply ) {
+  ot_time t = time( NULL ) - ot_start_time;
+  return sprintf( reply,
+                 "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.",
+                 ot_full_scrape_count * 1000,
+                 ot_full_scrape_size,
+                 (int)t,
+                 (int)(t / 3600),
+                 events_per_time( ot_full_scrape_count, t ),
+                 events_per_time( ot_full_scrape_size, t )
+                 );
+}
+
+static size_t stats_peers_mrtg( char * reply ) {
+  torrent_stats stats = {0,0,0};
+
+  iterate_all_torrents( torrent_statter, (uintptr_t)&stats );
+
+  return sprintf( reply, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker",
+                 stats.peer_count,
+                 stats.seed_count,
+                 stats.torrent_count
+                 );
+}
+
+static size_t stats_torrents_mrtg( char * reply )
+{
+  size_t torrent_count = mutex_get_torrent_count();
+
+  return sprintf( reply, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker",
+                 torrent_count,
+                 (size_t)0,
+                 torrent_count
+                 );
+}
+
+static size_t stats_httperrors_txt ( char * reply ) {
+  return sprintf( reply, "302 RED %llu\n400 ... %llu\n400 PAR %llu\n400 COM %llu\n403 IP  %llu\n404 INV %llu\n500 SRV %llu\n",
+                 ot_failed_request_counts[0], ot_failed_request_counts[1], ot_failed_request_counts[2],
+                 ot_failed_request_counts[3], ot_failed_request_counts[4], ot_failed_request_counts[5],
+                 ot_failed_request_counts[6] );
+}
+
+static size_t stats_return_renew_bucket( char * reply ) {
+  char *r = reply;
+  int i;
+
+  for( i=0; i<OT_PEER_TIMEOUT; ++i )
+    r+=sprintf(r,"%02i %llu\n", i, ot_renewed[i] );
+  return r - reply;
+}
+
+static size_t stats_return_sync_mrtg( char * reply ) {
+       ot_time t = time( NULL ) - ot_start_time;
+       return sprintf( reply,
+                 "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.",
+                 ot_overall_sync_count,
+                 0LL,
+                 (int)t,
+                 (int)(t / 3600),
+                 events_per_time( ot_overall_tcp_connections+ot_overall_udp_connections, t ),
+                 events_per_time( ot_overall_tcp_successfulannounces+ot_overall_udp_successfulannounces+ot_overall_udp_connects, t )
+                 );
+}
+
+static size_t stats_return_completed_mrtg( char * reply ) {
+  ot_time t = time( NULL ) - ot_start_time;
+
+  return sprintf( reply,
+                 "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.",
+                 ot_overall_completed,
+                 0LL,
+                 (int)t,
+                 (int)(t / 3600),
+                 events_per_time( ot_overall_completed, t / 3600 )
+                 );
+}
+
+#ifdef WANT_LOG_NUMWANT
+extern unsigned long long numwants[201];
+static size_t stats_return_numwants( char * reply ) {
+  char * r = reply;
+  int i;
+  for( i=0; i<=200; ++i )
+    r += sprintf( r, "%03d => %lld\n", i, numwants[i] );
+  return r-reply;
+}
+#endif
+
+#ifdef WANT_FULLLOG_NETWORKS
+static void stats_return_fulllog( int *iovec_entries, struct iovec **iovector, char *r ) {
+  ot_log *loglist = g_logchain_first, *llnext;
+  char * re = r + OT_STATS_TMPSIZE;
+
+  g_logchain_first = g_logchain_last = 0;
+  
+  while( loglist ) {
+    if( r + ( loglist->size + 64 ) >= re ) {
+      r = iovec_fix_increase_or_free( iovec_entries, iovector, r, 32 * OT_STATS_TMPSIZE );
+      if( !r ) return;
+      re = r + 32 * OT_STATS_TMPSIZE;
+    }
+    r += sprintf( r, "%08ld: ", loglist->time );
+    r += fmt_ip6c( r, loglist->ip );
+    *r++ = '\n';
+    memcpy( r, loglist->data, loglist->size );
+    r += loglist->size;
+    *r++ = '\n';
+    *r++ = '*';
+    *r++ = '\n';
+    *r++ = '\n';
+
+    llnext = loglist->next;
+    free( loglist->data );
+    free( loglist );
+    loglist = llnext;
+  }
+  iovec_fixlast( iovec_entries, iovector, r );
+}
+#endif
+
+static size_t stats_return_everything( char * reply ) {
+  torrent_stats stats = {0,0,0};
+  int i;
+  char * r = reply;
+
+  iterate_all_torrents( torrent_statter, (uintptr_t)&stats );
+
+  r += sprintf( r, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
+  r += sprintf( r, "<stats>\n" );
+  r += sprintf( r, "  <tracker_id>%" PRIu32 "</tracker_id>\n", g_tracker_id );
+  r += sprintf( r, "  <version>\n" ); r += stats_return_tracker_version( r );  r += sprintf( r, "  </version>\n" );
+  r += sprintf( r, "  <uptime>%llu</uptime>\n", (unsigned long long)(time( NULL ) - ot_start_time) );
+  r += sprintf( r, "  <torrents>\n" );
+  r += sprintf( r, "    <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count() );
+  r += sprintf( r, "    <count_iterator>%llu</count_iterator>\n", stats.torrent_count );
+  r += sprintf( r, "  </torrents>\n" );
+  r += sprintf( r, "  <peers>\n    <count>%llu</count>\n  </peers>\n", stats.peer_count );
+  r += sprintf( r, "  <seeds>\n    <count>%llu</count>\n  </seeds>\n", stats.seed_count );
+  r += sprintf( r, "  <completed>\n    <count>%llu</count>\n  </completed>\n", ot_overall_completed );
+  r += sprintf( r, "  <connections>\n" );
+  r += sprintf( r, "    <tcp>\n      <accept>%llu</accept>\n      <announce>%llu</announce>\n      <scrape>%llu</scrape>\n    </tcp>\n", ot_overall_tcp_connections, ot_overall_tcp_successfulannounces, ot_overall_udp_successfulscrapes );
+  r += sprintf( r, "    <udp>\n      <overall>%llu</overall>\n      <connect>%llu</connect>\n      <announce>%llu</announce>\n      <scrape>%llu</scrape>\n      <missmatch>%llu</missmatch>\n    </udp>\n", ot_overall_udp_connections, ot_overall_udp_connects, ot_overall_udp_successfulannounces, ot_overall_udp_successfulscrapes, ot_overall_udp_connectionidmissmatches );
+  r += sprintf( r, "    <livesync>\n      <count>%llu</count>\n    </livesync>\n", ot_overall_sync_count );
+  r += sprintf( r, "  </connections>\n" );
+  r += sprintf( r, "  <debug>\n" );
+  r += sprintf( r, "    <renew>\n" );
+  for( i=0; i<OT_PEER_TIMEOUT; ++i )
+    r += sprintf( r, "      <count interval=\"%02i\">%llu</count>\n", i, ot_renewed[i] );
+  r += sprintf( r, "    </renew>\n" );
+  r += sprintf( r, "    <http_error>\n" );
+  for( i=0; i<CODE_HTTPERROR_COUNT; ++i )
+    r += sprintf( r, "      <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], ot_failed_request_counts[i] );
+  r += sprintf( r, "    </http_error>\n" );
+  r += sprintf( r, "    <mutex_stall>\n      <count>%llu</count>\n    </mutex_stall>\n", ot_overall_stall_count );
+  r += sprintf( r, "  </debug>\n" );
+  r += sprintf( r, "</stats>" );
+  return r - reply;
+}
+
+extern const char
+*g_version_opentracker_c, *g_version_accesslist_c, *g_version_clean_c, *g_version_fullscrape_c, *g_version_http_c,
+*g_version_iovec_c, *g_version_mutex_c, *g_version_stats_c, *g_version_udp_c, *g_version_vector_c,
+*g_version_scan_urlencoded_query_c, *g_version_trackerlogic_c, *g_version_livesync_c, *g_version_rijndael_c;
+
+size_t stats_return_tracker_version( char *reply ) {
+  return sprintf( reply, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+                 g_version_opentracker_c, g_version_accesslist_c, g_version_clean_c, g_version_fullscrape_c, g_version_http_c,
+                 g_version_iovec_c, g_version_mutex_c, g_version_stats_c, g_version_udp_c, g_version_vector_c,
+                 g_version_scan_urlencoded_query_c, g_version_trackerlogic_c, g_version_livesync_c, g_version_rijndael_c );
+}
+
+size_t return_stats_for_tracker( char *reply, int mode, int format ) {
+  (void) format;
+  switch( mode & TASK_TASK_MASK ) {
+    case TASK_STATS_CONNS:
+      return stats_connections_mrtg( reply );
+    case TASK_STATS_SCRAPE:
+      return stats_scrape_mrtg( reply );
+    case TASK_STATS_UDP:
+      return stats_udpconnections_mrtg( reply );
+    case TASK_STATS_TCP:
+      return stats_tcpconnections_mrtg( reply );
+    case TASK_STATS_FULLSCRAPE:
+      return stats_fullscrapes_mrtg( reply );
+    case TASK_STATS_COMPLETED:
+      return stats_return_completed_mrtg( reply );
+    case TASK_STATS_HTTPERRORS:
+      return stats_httperrors_txt( reply );
+    case TASK_STATS_VERSION:
+      return stats_return_tracker_version( reply );
+    case TASK_STATS_RENEW:
+      return stats_return_renew_bucket( reply );
+    case TASK_STATS_SYNCS:
+      return stats_return_sync_mrtg( reply );
+#ifdef WANT_LOG_NUMWANT
+    case TASK_STATS_NUMWANTS:
+      return stats_return_numwants( reply );
+#endif
+    default:
+      return 0;
+  }
+}
+
+static void stats_make( int *iovec_entries, struct iovec **iovector, ot_tasktype mode ) {
+  char *r;
+
+  *iovec_entries = 0;
+  *iovector      = NULL;
+  if( !( r = iovec_increase( iovec_entries, iovector, OT_STATS_TMPSIZE ) ) )
+    return;
+
+  switch( mode & TASK_TASK_MASK ) {
+    case TASK_STATS_TORRENTS:    r += stats_torrents_mrtg( r );             break;
+    case TASK_STATS_PEERS:       r += stats_peers_mrtg( r );                break;
+    case TASK_STATS_SLASH24S:    r += stats_slash24s_txt( r, 128 );         break;
+    case TASK_STATS_TOP10:       r += stats_top_txt( r, 10 );               break;
+    case TASK_STATS_TOP100:
+                                 r = iovec_fix_increase_or_free( iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE );
+                                 if( !r ) return;
+                                 r += stats_top_txt( r, 100 );              break;
+    case TASK_STATS_EVERYTHING:  r += stats_return_everything( r );         break;
+#ifdef WANT_SPOT_WOODPECKER
+    case TASK_STATS_WOODPECKERS: r += stats_return_woodpeckers( r, 128 );   break;
+#endif
+#ifdef WANT_FULLLOG_NETWORKS
+    case TASK_STATS_FULLLOG:      stats_return_fulllog( iovec_entries, iovector, r );
+                                                                            return;
+#endif
+    default:
+      iovec_free(iovec_entries, iovector);
+      return;
+  }
+  iovec_fixlast( iovec_entries, iovector, r );
+}
+
+void stats_issue_event( ot_status_event event, PROTO_FLAG proto, uintptr_t event_data ) {
+  switch( event ) {
+    case EVENT_ACCEPT:
+      if( proto == FLAG_TCP ) ot_overall_tcp_connections++; else ot_overall_udp_connections++;
+#ifdef WANT_LOG_NETWORKS
+      stat_increase_network_count( &stats_network_counters_root, 0, event_data );
+#endif
+      break;
+    case EVENT_ANNOUNCE:
+      if( proto == FLAG_TCP ) ot_overall_tcp_successfulannounces++; else ot_overall_udp_successfulannounces++;
+      break;
+    case EVENT_CONNECT:
+      if( proto == FLAG_TCP ) ot_overall_tcp_connects++; else ot_overall_udp_connects++;
+      break;
+    case EVENT_COMPLETED:
+#ifdef WANT_SYSLOGS
+      if( event_data) {
+        struct ot_workstruct *ws = (struct ot_workstruct *)event_data;
+        char timestring[64];
+        char hash_hex[42], peerid_hex[42], ip_readable[64];
+        struct tm time_now;
+        time_t ttt;
+
+        time( &ttt );
+        localtime_r( &ttt, &time_now );
+        strftime( timestring, sizeof( timestring ), "%FT%T%z", &time_now );
+
+        to_hex( hash_hex, *ws->hash );
+        if( ws->peer_id )
+          to_hex( peerid_hex, (uint8_t*)ws->peer_id );
+        else {
+          *peerid_hex=0;
+        }
+
+#ifdef WANT_V6
+        ip_readable[ fmt_ip6c( ip_readable, (char*)&ws->peer ) ] = 0;
+#else
+        ip_readable[ fmt_ip4( ip_readable, (char*)&ws->peer ) ] = 0;
+#endif
+        syslog( LOG_INFO, "time=%s event=completed info_hash=%s peer_id=%s ip=%s", timestring, hash_hex, peerid_hex, ip_readable );
+      }
+#endif
+      ot_overall_completed++;
+      break;
+    case EVENT_SCRAPE:
+      if( proto == FLAG_TCP ) ot_overall_tcp_successfulscrapes++; else ot_overall_udp_successfulscrapes++;
+    case EVENT_FULLSCRAPE:
+      ot_full_scrape_count++;
+      ot_full_scrape_size += event_data;
+      break;
+    case EVENT_FULLSCRAPE_REQUEST:
+    {
+      ot_ip6 *ip = (ot_ip6*)event_data; /* ugly hack to transfer ip to stats */
+      char _debug[512];
+      int off = snprintf( _debug, sizeof(_debug), "[%08d] scrp:  ", (unsigned int)(g_now_seconds - ot_start_time)/60 );
+      off += fmt_ip6c( _debug+off, *ip );
+      off += snprintf( _debug+off, sizeof(_debug)-off, " - FULL SCRAPE\n" );
+      write( 2, _debug, off );
+      ot_full_scrape_request_count++;
+    }
+      break;
+    case EVENT_FULLSCRAPE_REQUEST_GZIP:
+    {
+      ot_ip6 *ip = (ot_ip6*)event_data; /* ugly hack to transfer ip to stats */
+      char _debug[512];
+      int off = snprintf( _debug, sizeof(_debug), "[%08d] scrp:  ", (unsigned int)(g_now_seconds - ot_start_time)/60 );
+      off += fmt_ip6c(_debug+off, *ip );
+      off += snprintf( _debug+off, sizeof(_debug)-off, " - FULL SCRAPE\n" );
+      write( 2, _debug, off );
+      ot_full_scrape_request_count++;
+    }
+      break;
+    case EVENT_FAILED:
+      ot_failed_request_counts[event_data]++;
+      break;
+    case EVENT_RENEW:
+      ot_renewed[event_data]++;
+      break;
+    case EVENT_SYNC:
+      ot_overall_sync_count+=event_data;
+           break;
+    case EVENT_BUCKET_LOCKED:
+      ot_overall_stall_count++;
+      break;
+#ifdef WANT_SPOT_WOODPECKER
+    case EVENT_WOODPECKER:
+      pthread_mutex_lock( &g_woodpeckers_mutex );
+      stat_increase_network_count( &stats_woodpeckers_tree, 0, event_data );
+      pthread_mutex_unlock( &g_woodpeckers_mutex );
+      break;
+#endif
+    case EVENT_CONNID_MISSMATCH:
+      ++ot_overall_udp_connectionidmissmatches;
+    default:
+      break;
+  }
+}
+
+void stats_cleanup() {
+#ifdef WANT_SPOT_WOODPECKER
+  pthread_mutex_lock( &g_woodpeckers_mutex );
+  stats_shift_down_network_count( &stats_woodpeckers_tree, 0, 1 );
+  pthread_mutex_unlock( &g_woodpeckers_mutex );
+#endif
+}
+
+static void * stats_worker( void * args ) {
+  int iovec_entries;
+  struct iovec *iovector;
+
+  (void) args;
+
+  while( 1 ) {
+    ot_tasktype tasktype = TASK_STATS;
+    ot_taskid   taskid   = mutex_workqueue_poptask( &tasktype );
+    stats_make( &iovec_entries, &iovector, tasktype );
+    if( mutex_workqueue_pushresult( taskid, iovec_entries, iovector ) )
+      iovec_free( &iovec_entries, &iovector );
+  }
+  return NULL;
+}
+
+void stats_deliver( int64 sock, int tasktype ) {
+  mutex_workqueue_pushtask( sock, tasktype );
+}
+
+static pthread_t thread_id;
+void stats_init( ) {
+  ot_start_time = g_now_seconds;
+  pthread_create( &thread_id, NULL, stats_worker, NULL );
+}
+
+void stats_deinit( ) {
+  pthread_cancel( thread_id );
+}
+
+const char *g_version_stats_c = "$Source$: $Revision$\n";
diff --git a/ot_stats.h b/ot_stats.h
new file mode 100644 (file)
index 0000000..4c59f8b
--- /dev/null
@@ -0,0 +1,48 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_STATS_H__
+#define __OT_STATS_H__
+
+typedef enum {
+  EVENT_ACCEPT,
+  EVENT_READ,
+  EVENT_CONNECT,      /* UDP only */
+  EVENT_ANNOUNCE,
+  EVENT_COMPLETED,
+  EVENT_RENEW,
+  EVENT_SYNC,
+  EVENT_SCRAPE,
+  EVENT_FULLSCRAPE_REQUEST,
+  EVENT_FULLSCRAPE_REQUEST_GZIP,
+  EVENT_FULLSCRAPE,   /* TCP only */
+  EVENT_FAILED,
+  EVENT_BUCKET_LOCKED,
+  EVENT_WOODPECKER,
+  EVENT_CONNID_MISSMATCH
+} ot_status_event;
+
+enum {
+  CODE_HTTPERROR_302,
+  CODE_HTTPERROR_400,
+  CODE_HTTPERROR_400_PARAM,
+  CODE_HTTPERROR_400_COMPACT,
+  CODE_HTTPERROR_402_NOTMODEST,
+  CODE_HTTPERROR_403_IP,
+  CODE_HTTPERROR_404,
+  CODE_HTTPERROR_500,
+
+  CODE_HTTPERROR_COUNT
+};
+
+void   stats_issue_event( ot_status_event event, PROTO_FLAG proto, uintptr_t event_data );
+void   stats_deliver( int64 sock, int tasktype );
+void   stats_cleanup();
+size_t return_stats_for_tracker( char *reply, int mode, int format );
+size_t stats_return_tracker_version( char *reply );
+void   stats_init( );
+void   stats_deinit( );
+
+#endif
diff --git a/ot_sync.c b/ot_sync.c
new file mode 100644 (file)
index 0000000..cd66a46
--- /dev/null
+++ b/ot_sync.c
@@ -0,0 +1,166 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* System */
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+#include <stdio.h>
+#include <string.h>
+#include <pthread.h>
+
+/* Libowfat */
+#include "scan.h"
+#include "byte.h"
+#include "io.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_mutex.h"
+#include "ot_sync.h"
+#include "ot_stats.h"
+#include "ot_iovec.h"
+
+#ifdef WANT_SYNC_BATCH
+
+#define OT_SYNC_CHUNK_SIZE (512*1024)
+
+/* Import Changeset from an external authority
+   format: d4:syncd[..]ee
+   [..]:   ( 20:01234567890abcdefghij16:XXXXYYYY )+
+*/
+int add_changeset_to_tracker( uint8_t *data, size_t len ) {
+  ot_hash    *hash;
+  uint8_t    *end = data + len;
+  unsigned long      peer_count;
+
+  /* We do know, that the string is \n terminated, so it cant
+     overflow */
+  if( byte_diff( data, 8, "d4:syncd" ) ) return -1;
+  data += 8;
+
+  while( 1 ) {
+    if( byte_diff( data, 3, "20:" ) ) {
+      if( byte_diff( data, 2, "ee" ) )
+        return -1;
+      return 0;
+    }
+    data += 3;
+    hash = (ot_hash*)data;
+    data += sizeof( ot_hash );
+
+    /* Scan string length indicator */
+    data += ( len = scan_ulong( (char*)data, &peer_count ) );
+
+    /* If no long was scanned, it is not divisible by 8, it is not
+       followed by a colon or claims to need to much memory, we fail */
+    if( !len || !peer_count || ( peer_count & 7 ) || ( *data++ != ':' ) || ( data + peer_count > end ) )
+      return -1;
+
+    while( peer_count > 0 ) {
+      add_peer_to_torrent( hash, (ot_peer*)data, 1 );
+      data += 8; peer_count -= 8;
+    }
+  }
+  return 0;
+}
+
+/* Proposed output format
+   d4:syncd20:<info_hash>8*N:(xxxxyyyy)*Nee
+*/
+static void sync_make( int *iovec_entries, struct iovec **iovector ) {
+  int    bucket;
+  char  *r, *re;
+
+  /* Setup return vector... */
+  *iovec_entries = 0;
+  *iovector = NULL;
+  if( !( r = iovec_increase( iovec_entries, iovector, OT_SYNC_CHUNK_SIZE ) ) )
+    return;
+
+  /* ... and pointer to end of current output buffer.
+     This works as a low watermark */
+  re = r + OT_SYNC_CHUNK_SIZE;
+
+  memmove( r, "d4:syncd", 8 ); r += 8;
+
+  /* For each bucket... */
+  for( bucket=0; bucket<OT_BUCKET_COUNT; ++bucket ) {
+    /* Get exclusive access to that bucket */
+    ot_vector *torrents_list = mutex_bucket_lock( bucket );
+    size_t tor_offset;
+
+    /* For each torrent in this bucket.. */
+    for( tor_offset=0; tor_offset<torrents_list->size; ++tor_offset ) {
+      /* Address torrents members */
+      ot_peerlist *peer_list = ( ((ot_torrent*)(torrents_list->data))[tor_offset] ).peer_list;
+      ot_hash     *hash      =&( ((ot_torrent*)(torrents_list->data))[tor_offset] ).hash;
+      const size_t byte_count = sizeof(ot_peer) * peer_list->changeset.size;
+
+      /* If we reached our low watermark in buffer... */
+      if( re - r <= (ssize_t)(/* strlen( "20:" ) == */ 3 + sizeof( ot_hash ) + /* strlen_max( "%zd" ) == */ 12 + byte_count ) ) {
+
+        /* Allocate a fresh output buffer at the end of our buffers list
+           release bucket and return, if that fails */
+        if( !( r = iovec_fix_increase_or_free( iovec_entries, iovector, r, OT_SYNC_CHUNK_SIZE ) ) )
+          return mutex_bucket_unlock( bucket );
+
+        /* Adjust new end of output buffer */
+        re = r + OT_SYNC_CHUNK_SIZE;
+      }
+
+      *r++ = '2'; *r++ = '0'; *r++ = ':';
+      memmove( r, hash, sizeof( ot_hash ) ); r += sizeof( ot_hash );
+      r += sprintf( r, "%zd:", byte_count );
+      memmove( r, peer_list->changeset.data, byte_count ); r += byte_count;
+    }
+
+    /* All torrents done: release lock on currenct bucket */
+    mutex_bucket_unlock( bucket );
+  }
+
+  /* Close bencoded sync dictionary */
+  *r++='e'; *r++='e';
+
+  /* Release unused memory in current output buffer */
+  iovec_fixlast( iovec_entries, iovector, r );
+}
+
+/* This is the entry point into this worker thread
+   It grabs tasks from mutex_tasklist and delivers results back
+*/
+static void * sync_worker( void * args) {
+  int iovec_entries;
+  struct iovec *iovector;
+
+  args = args;
+
+  while( 1 ) {
+    ot_tasktype tasktype = TASK_SYNC_OUT;
+    ot_taskid   taskid   = mutex_workqueue_poptask( &tasktype );
+    sync_make( &iovec_entries, &iovector );
+    stats_issue_event( EVENT_SYNC_OUT, FLAG_TCP, iovec_length( &iovec_entries, &iovector) );
+    if( mutex_workqueue_pushresult( taskid, iovec_entries, iovector ) )
+      iovec_free( &iovec_entries, &iovector );
+  }
+  return NULL;
+}
+
+static pthread_t thread_id;
+void sync_init( ) {
+  pthread_create( &thread_id, NULL, sync_worker, NULL );
+}
+
+void sync_deinit( ) {
+  pthread_cancel( thread_id );
+}
+
+void sync_deliver( int64 socket ) {
+  mutex_workqueue_pushtask( socket, TASK_SYNC_OUT );
+}
+
+#endif
+
+const char *g_version_sync_c = "$Source$: $Revision$\n";
diff --git a/ot_sync.h b/ot_sync.h
new file mode 100644 (file)
index 0000000..81d4d22
--- /dev/null
+++ b/ot_sync.h
@@ -0,0 +1,24 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_SYNC_H__
+#define __OT_SYNC_H__
+
+#ifdef WANT_SYNC_BATCH
+enum { SYNC_IN, SYNC_OUT };
+
+void sync_init( );
+void sync_deinit( );
+void sync_deliver( int64 socket );
+
+int  add_changeset_to_tracker( uint8_t *data, size_t len );
+#else
+
+#define sync_init()
+#define sync_deinit()
+
+#endif
+
+#endif
diff --git a/ot_udp.c b/ot_udp.c
new file mode 100644 (file)
index 0000000..1d495eb
--- /dev/null
+++ b/ot_udp.c
@@ -0,0 +1,206 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* System */
+#include <stdlib.h>
+#include <pthread.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+
+/* Libowfat */
+#include "socket.h"
+#include "io.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_udp.h"
+#include "ot_stats.h"
+#include "ot_rijndael.h"
+
+static const uint8_t g_static_connid[8] = { 0x23, 0x42, 0x05, 0x17, 0xde, 0x41, 0x50, 0xff };
+static uint32_t g_rijndael_round_key[44] = {0};
+static uint32_t g_key_of_the_hour[2] = {0};
+static ot_time  g_hour_of_the_key;
+
+static void udp_generate_rijndael_round_key() {
+  uint32_t key[16];
+  key[0] = random();
+  key[1] = random();
+  key[2] = random();
+  key[3] = random();
+  rijndaelKeySetupEnc128( g_rijndael_round_key, (uint8_t*)key );
+
+  g_key_of_the_hour[0] = random();
+  g_hour_of_the_key = g_now_minutes;
+}
+
+/* Generate current and previous connection id for ip */
+static void udp_make_connectionid( uint32_t connid[2], const ot_ip6 remoteip, int age ) {
+  uint32_t plain[4], crypt[4];
+  int i;
+  if( g_now_minutes + 60 > g_hour_of_the_key ) {
+    g_hour_of_the_key = g_now_minutes;
+    g_key_of_the_hour[1] = g_key_of_the_hour[0];
+    g_key_of_the_hour[0] = random();
+  }
+
+  memcpy( plain, remoteip, sizeof( plain ) );
+  for( i=0; i<4; ++i ) plain[i] ^= g_key_of_the_hour[age];
+  rijndaelEncrypt128( g_rijndael_round_key, (uint8_t*)remoteip, (uint8_t*)crypt );
+  connid[0] = crypt[0] ^ crypt[1];
+  connid[1] = crypt[2] ^ crypt[3];
+}
+
+/* UDP implementation according to http://xbtt.sourceforge.net/udp_tracker_protocol.html */
+int handle_udp6( int64 serversocket, struct ot_workstruct *ws ) {
+  ot_ip6      remoteip;
+  uint32_t   *inpacket = (uint32_t*)ws->inbuf;
+  uint32_t   *outpacket = (uint32_t*)ws->outbuf;
+  uint32_t    numwant, left, event, scopeid;
+  uint32_t    connid[2];
+  uint16_t    port, remoteport;
+  size_t      byte_count, scrape_count;
+
+  byte_count = socket_recv6( serversocket, ws->inbuf, G_INBUF_SIZE, remoteip, &remoteport, &scopeid );
+  if( !byte_count ) return 0;
+
+  stats_issue_event( EVENT_ACCEPT, FLAG_UDP, (uintptr_t)remoteip );
+  stats_issue_event( EVENT_READ, FLAG_UDP, byte_count );
+
+  /* Minimum udp tracker packet size, also catches error */
+  if( byte_count < 16 )
+    return 1;
+
+  /* Generate the connection id we give out and expect to and from
+     the requesting ip address, this prevents udp spoofing */
+  udp_make_connectionid( connid, remoteip, 0 );
+
+  /* Initialise hash pointer */
+  ws->hash = NULL;
+  ws->peer_id = NULL;
+
+  /* If action is not a ntohl(a) == a == 0, then we
+     expect the derived connection id in first 64 bit */
+  if( inpacket[2] && ( inpacket[0] != connid[0] || inpacket[1] != connid[1] ) ) {
+    /* If connection id does not match, try the one that was
+       valid in the previous hour. Only if this also does not
+       match, return an error packet */
+    udp_make_connectionid( connid, remoteip, 1 );
+    if( inpacket[0] != connid[0] || inpacket[1] != connid[1] ) {
+      const size_t s = sizeof( "Connection ID missmatch." );
+      outpacket[0] = 3; outpacket[1] = inpacket[3];
+      memcpy( &outpacket[2], "Connection ID missmatch.", s );
+      socket_send6( serversocket, ws->outbuf, 8 + s, remoteip, remoteport, 0 );
+      stats_issue_event( EVENT_CONNID_MISSMATCH, FLAG_UDP, 8 + s );
+      return 1;
+    }
+  }
+
+  switch( ntohl( inpacket[2] ) ) {
+    case 0: /* This is a connect action */
+      /* look for udp bittorrent magic id */
+      if( (ntohl(inpacket[0]) != 0x00000417) || (ntohl(inpacket[1]) != 0x27101980) )
+        return 1;
+
+      outpacket[0] = 0;
+      outpacket[1] = inpacket[3];
+      outpacket[2] = connid[0];
+      outpacket[3] = connid[1];
+
+      socket_send6( serversocket, ws->outbuf, 16, remoteip, remoteport, 0 );
+      stats_issue_event( EVENT_CONNECT, FLAG_UDP, 16 );
+      break;
+    case 1: /* This is an announce action */
+      /* Minimum udp announce packet size */
+      if( byte_count < 98 )
+        return 1;
+
+      /* We do only want to know, if it is zero */
+      left  = inpacket[64/4] | inpacket[68/4];
+
+      numwant = ntohl( inpacket[92/4] );
+      if (numwant > 200) numwant = 200;
+
+      event    = ntohl( inpacket[80/4] );
+      port     = *(uint16_t*)( ((char*)inpacket) + 96 );
+      ws->hash = (ot_hash*)( ((char*)inpacket) + 16 );
+
+      OT_SETIP( &ws->peer, remoteip );
+      OT_SETPORT( &ws->peer, &port );
+      OT_PEERFLAG( &ws->peer ) = 0;
+
+      switch( event ) {
+        case 1: OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_COMPLETED; break;
+        case 3: OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_STOPPED; break;
+        default: break;
+      }
+
+      if( !left )
+        OT_PEERFLAG( &ws->peer )         |= PEER_FLAG_SEEDING;
+
+      outpacket[0] = htonl( 1 );    /* announce action */
+      outpacket[1] = inpacket[12/4];
+
+      if( OT_PEERFLAG( &ws->peer ) & PEER_FLAG_STOPPED ) { /* Peer is gone. */
+        ws->reply      = ws->outbuf;
+        ws->reply_size = remove_peer_from_torrent( FLAG_UDP, ws );
+      } else {
+        ws->reply      = ws->outbuf + 8;
+        ws->reply_size = 8 + add_peer_to_torrent_and_return_peers( FLAG_UDP, ws, numwant );
+      }
+
+      socket_send6( serversocket, ws->outbuf, ws->reply_size, remoteip, remoteport, 0 );
+      stats_issue_event( EVENT_ANNOUNCE, FLAG_UDP, ws->reply_size );
+      break;
+
+    case 2: /* This is a scrape action */
+      outpacket[0] = htonl( 2 );    /* scrape action */
+      outpacket[1] = inpacket[12/4];
+
+      for( scrape_count = 0; ( scrape_count * 20 < byte_count - 16) && ( scrape_count <= 74 ); scrape_count++ )
+        return_udp_scrape_for_torrent( *(ot_hash*)( ((char*)inpacket) + 16 + 20 * scrape_count ), ((char*)outpacket) + 8 + 12 * scrape_count );
+
+      socket_send6( serversocket, ws->outbuf, 8 + 12 * scrape_count, remoteip, remoteport, 0 );
+      stats_issue_event( EVENT_SCRAPE, FLAG_UDP, scrape_count );
+      break;
+  }
+  return 1;
+}
+
+static void* udp_worker( void * args ) {
+  int64 sock = (int64)args;
+  struct ot_workstruct ws;
+  memset( &ws, 0, sizeof(ws) );
+
+  ws.inbuf=malloc(G_INBUF_SIZE);
+  ws.outbuf=malloc(G_OUTBUF_SIZE);
+#ifdef    _DEBUG_HTTPERROR
+  ws.debugbuf=malloc(G_DEBUGBUF_SIZE);
+#endif
+
+  while( g_opentracker_running )
+    handle_udp6( sock, &ws );
+
+  free( ws.inbuf );
+  free( ws.outbuf );
+#ifdef    _DEBUG_HTTPERROR
+  free( ws.debugbuf );
+#endif
+  return NULL;
+}
+
+void udp_init( int64 sock, unsigned int worker_count ) {
+  pthread_t thread_id;
+  if( !g_rijndael_round_key[0] )
+    udp_generate_rijndael_round_key();
+#ifdef _DEBUG
+  fprintf( stderr, " installing %d workers on udp socket %ld", worker_count, (unsigned long)sock );
+#endif
+  while( worker_count-- )
+    pthread_create( &thread_id, NULL, udp_worker, (void *)sock );
+}
+
+const char *g_version_udp_c = "$Source$: $Revision$\n";
diff --git a/ot_udp.h b/ot_udp.h
new file mode 100644 (file)
index 0000000..a4f6ce0
--- /dev/null
+++ b/ot_udp.h
@@ -0,0 +1,12 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_UDP_H__
+#define __OT_UDP_H__
+
+void udp_init( int64 sock, unsigned int worker_count );
+int  handle_udp6( int64 serversocket, struct ot_workstruct *ws );
+
+#endif
diff --git a/ot_vector.c b/ot_vector.c
new file mode 100644 (file)
index 0000000..2a632b2
--- /dev/null
@@ -0,0 +1,266 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* System */
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <stdint.h>
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_vector.h"
+
+/* Libowfat */
+#include "uint32.h"
+#include "uint16.h"
+
+static int vector_compare_peer(const void *peer1, const void *peer2 ) {
+  return memcmp( peer1, peer2, OT_PEER_COMPARE_SIZE );
+}
+
+/* This function gives us a binary search that returns a pointer, even if
+   no exact match is found. In that case it sets exactmatch 0 and gives
+   calling functions the chance to insert data
+*/
+void *binary_search( const void * const key, const void * base, const size_t member_count, const size_t member_size,
+                     size_t compare_size, int *exactmatch ) {
+  size_t interval = member_count;
+
+  while( interval ) {
+    uint8_t *lookat = ((uint8_t*)base) + member_size * ( interval / 2 );
+    int cmp = memcmp( lookat, key, compare_size );
+    if(cmp == 0 ) {
+      base = lookat;
+      break;
+    }
+    if(cmp < 0) {
+      base = lookat + member_size;
+      interval--;
+    }
+    interval /= 2;
+  }
+
+  *exactmatch = interval;
+  return (void*)base;
+}
+
+static uint8_t vector_hash_peer( ot_peer *peer, int bucket_count ) {
+  unsigned int hash = 5381, i = OT_PEER_COMPARE_SIZE;
+  uint8_t *p = (uint8_t*)peer;
+  while( i-- ) hash += (hash<<5) + *(p++);
+  return hash % bucket_count;
+}
+
+/* This is the generic insert operation for our vector type.
+   It tries to locate the object at "key" with size "member_size" by comparing its first "compare_size" bytes with
+   those of objects in vector. Our special "binary_search" function does that and either returns the match or a
+   pointer to where the object is to be inserted. vector_find_or_insert makes space for the object and copies it,
+   if it wasn't found in vector. Caller needs to check the passed "exactmatch" variable to see, whether an insert
+   took place. If resizing the vector failed, NULL is returned, else the pointer to the object in vector.
+*/
+void *vector_find_or_insert( ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch ) {
+  uint8_t *match = binary_search( key, vector->data, vector->size, member_size, compare_size, exactmatch );
+
+  if( *exactmatch ) return match;
+
+  if( vector->size + 1 > vector->space ) {
+    size_t   new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
+    uint8_t *new_data = realloc( vector->data, new_space * member_size );
+    if( !new_data ) return NULL;
+    /* Adjust pointer if it moved by realloc */
+    match = new_data + (match - (uint8_t*)vector->data);
+
+    vector->data = new_data;
+    vector->space = new_space;
+  }
+  memmove( match + member_size, match, ((uint8_t*)vector->data) + member_size * vector->size - match );
+
+  vector->size++;
+  return match;
+}
+
+ot_peer *vector_find_or_insert_peer( ot_vector *vector, ot_peer *peer, int *exactmatch ) {
+  ot_peer *match;
+
+  /* If space is zero but size is set, we're dealing with a list of vector->size buckets */
+  if( vector->space < vector->size )
+    vector = ((ot_vector*)vector->data) + vector_hash_peer(peer, vector->size );
+  match = (ot_peer*)binary_search( peer, vector->data, vector->size, sizeof(ot_peer), OT_PEER_COMPARE_SIZE, exactmatch );
+
+  if( *exactmatch ) return match;
+
+  if( vector->size + 1 > vector->space ) {
+    size_t   new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
+    ot_peer *new_data = realloc( vector->data, new_space * sizeof(ot_peer) );
+    if( !new_data ) return NULL;
+    /* Adjust pointer if it moved by realloc */
+    match = new_data + (match - (ot_peer*)vector->data);
+
+    vector->data = new_data;
+    vector->space = new_space;
+  }
+  memmove( match + 1, match, sizeof(ot_peer) * ( ((ot_peer*)vector->data) + vector->size - match ) );
+
+  vector->size++;
+  return match;
+}
+
+/* This is the non-generic delete from vector-operation specialized for peers in pools.
+   It returns 0 if no peer was found (and thus not removed)
+              1 if a non-seeding peer was removed
+              2 if a seeding peer was removed
+*/
+int vector_remove_peer( ot_vector *vector, ot_peer *peer ) {
+  int      exactmatch;
+  ot_peer *match, *end;
+
+  if( !vector->size ) return 0;
+
+  /* If space is zero but size is set, we're dealing with a list of vector->size buckets */
+  if( vector->space < vector->size )
+    vector = ((ot_vector*)vector->data) + vector_hash_peer(peer, vector->size );
+
+  end = ((ot_peer*)vector->data) + vector->size;
+  match = (ot_peer*)binary_search( peer, vector->data, vector->size, sizeof(ot_peer), OT_PEER_COMPARE_SIZE, &exactmatch );
+  if( !exactmatch ) return 0;
+
+  exactmatch = ( OT_PEERFLAG( match ) & PEER_FLAG_SEEDING ) ? 2 : 1;
+  memmove( match, match + 1, sizeof(ot_peer) * ( end - match - 1 ) );
+
+  vector->size--;
+  vector_fixup_peers( vector );
+  return exactmatch;
+}
+
+void vector_remove_torrent( ot_vector *vector, ot_torrent *match ) {
+  ot_torrent *end = ((ot_torrent*)vector->data) + vector->size;
+
+  if( !vector->size ) return;
+
+  /* If this is being called after a unsuccessful malloc() for peer_list
+     in add_peer_to_torrent, match->peer_list actually might be NULL */
+  if( match->peer_list) free_peerlist( match->peer_list );
+
+  memmove( match, match + 1, sizeof(ot_torrent) * ( end - match - 1 ) );
+  if( ( --vector->size * OT_VECTOR_SHRINK_THRESH < vector->space ) && ( vector->space >= OT_VECTOR_SHRINK_RATIO * OT_VECTOR_MIN_MEMBERS ) ) {
+    vector->space /= OT_VECTOR_SHRINK_RATIO;
+    vector->data = realloc( vector->data, vector->space * sizeof( ot_torrent ) );
+  }
+}
+
+void vector_clean_list( ot_vector * vector, int num_buckets ) {
+  while( num_buckets-- )
+    free( vector[num_buckets].data );
+  free( vector );
+  return;
+}
+
+void vector_redistribute_buckets( ot_peerlist * peer_list ) {
+  int tmp, bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1;
+  ot_vector * bucket_list_new, * bucket_list_old = &peer_list->peers;
+
+  if( OT_PEERLIST_HASBUCKETS( peer_list ) ) {
+    num_buckets_old = peer_list->peers.size;
+    bucket_list_old = peer_list->peers.data;
+  }
+
+  if( peer_list->peer_count < 255 )
+    num_buckets_new = 1;
+  else if( peer_list->peer_count > 8192 )
+    num_buckets_new = 64;
+  else if( peer_list->peer_count >= 512 && peer_list->peer_count < 4096 )
+    num_buckets_new = 16;
+  else if( peer_list->peer_count < 512 && num_buckets_old <= 16 )
+    num_buckets_new = num_buckets_old;
+  else if( peer_list->peer_count < 512 )
+    num_buckets_new = 1;
+  else if( peer_list->peer_count < 8192 && num_buckets_old > 1 )
+    num_buckets_new = num_buckets_old;
+  else
+    num_buckets_new = 16;
+
+  if( num_buckets_new == num_buckets_old )
+    return;
+
+  /* Assume near perfect distribution */
+  bucket_list_new = malloc( num_buckets_new * sizeof( ot_vector ) );
+  if( !bucket_list_new) return;
+  bzero( bucket_list_new, num_buckets_new * sizeof( ot_vector ) );
+
+  tmp = peer_list->peer_count / num_buckets_new;
+  bucket_size_new = OT_VECTOR_MIN_MEMBERS;
+  while( bucket_size_new < tmp)
+    bucket_size_new *= OT_VECTOR_GROW_RATIO;
+
+  /* preallocate vectors to hold all peers */
+  for( bucket=0; bucket<num_buckets_new; ++bucket ) {
+    bucket_list_new[bucket].space = bucket_size_new;
+    bucket_list_new[bucket].data  = malloc( bucket_size_new * sizeof(ot_peer) );
+    if( !bucket_list_new[bucket].data )
+      return vector_clean_list( bucket_list_new, num_buckets_new );
+  }
+
+  /* Now sort them into the correct bucket */
+  for( bucket=0; bucket<num_buckets_old; ++bucket ) {
+    ot_peer * peers_old = bucket_list_old[bucket].data, * peers_new;
+    int peer_count_old = bucket_list_old[bucket].size;
+    while( peer_count_old-- ) {
+      ot_vector * bucket_dest = bucket_list_new;
+      if( num_buckets_new > 1 )
+        bucket_dest += vector_hash_peer(peers_old, num_buckets_new);
+      if( bucket_dest->size + 1 > bucket_dest->space ) {
+        void * tmp = realloc( bucket_dest->data, sizeof(ot_peer) * OT_VECTOR_GROW_RATIO * bucket_dest->space );
+        if( !tmp ) return vector_clean_list( bucket_list_new, num_buckets_new );
+        bucket_dest->data   = tmp;
+        bucket_dest->space *= OT_VECTOR_GROW_RATIO;
+      }
+      peers_new = (ot_peer*)bucket_dest->data;
+      memcpy(peers_new + bucket_dest->size++, peers_old++, sizeof(ot_peer));
+    }
+  }
+
+  /* Now sort each bucket to later allow bsearch */
+  for( bucket=0; bucket<num_buckets_new; ++bucket )
+    qsort( bucket_list_new[bucket].data, bucket_list_new[bucket].size, sizeof( ot_peer ), vector_compare_peer );
+
+  /* Everything worked fine. Now link new bucket_list to peer_list */
+  if( OT_PEERLIST_HASBUCKETS( peer_list) )
+    vector_clean_list( (ot_vector*)peer_list->peers.data, peer_list->peers.size );
+  else
+    free( peer_list->peers.data );
+
+  if( num_buckets_new > 1 ) {
+    peer_list->peers.data  = bucket_list_new;
+    peer_list->peers.size  = num_buckets_new;
+    peer_list->peers.space = 0; /* Magic marker for "is list of buckets" */
+  } else {
+    peer_list->peers.data  = bucket_list_new->data;
+    peer_list->peers.size  = bucket_list_new->size;
+    peer_list->peers.space = bucket_list_new->space;
+    free( bucket_list_new );
+  }
+}
+
+void vector_fixup_peers( ot_vector * vector ) {
+  int need_fix = 0;
+
+  if( !vector->size ) {
+    free( vector->data );
+    vector->data = NULL;
+    vector->space = 0;
+    return;
+  }
+
+  while( ( vector->size * OT_VECTOR_SHRINK_THRESH < vector->space ) &&
+         ( vector->space >= OT_VECTOR_SHRINK_RATIO * OT_VECTOR_MIN_MEMBERS ) ) {
+    vector->space /= OT_VECTOR_SHRINK_RATIO;
+    need_fix++;
+  }
+  if( need_fix )
+    vector->data = realloc( vector->data, vector->space * sizeof( ot_peer ) );
+}
+
+const char *g_version_vector_c = "$Source$: $Revision$\n";
diff --git a/ot_vector.h b/ot_vector.h
new file mode 100644 (file)
index 0000000..37135e7
--- /dev/null
@@ -0,0 +1,34 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_VECTOR_H__
+#define __OT_VECTOR_H__
+
+/* These defines control vectors behaviour */
+#define OT_VECTOR_MIN_MEMBERS   2
+#define OT_VECTOR_GROW_RATIO    2
+#define OT_VECTOR_SHRINK_THRESH 4
+#define OT_VECTOR_SHRINK_RATIO  2
+
+#define OT_PEER_BUCKET_MINCOUNT 512
+#define OT_PEER_BUCKET_MAXCOUNT 256
+
+typedef struct {
+  void   *data;
+  size_t  size;
+  size_t  space;
+} ot_vector;
+
+void    *binary_search( const void * const key, const void * base, const size_t member_count, const size_t member_size,
+                        size_t compare_size, int *exactmatch );
+void    *vector_find_or_insert( ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch );
+ot_peer *vector_find_or_insert_peer( ot_vector *vector, ot_peer *peer, int *exactmatch );
+
+int      vector_remove_peer( ot_vector *vector, ot_peer *peer );
+void     vector_remove_torrent( ot_vector *vector, ot_torrent *match );
+void     vector_redistribute_buckets( ot_peerlist * peer_list );
+void     vector_fixup_peers( ot_vector * vector );
+
+#endif
diff --git a/proxy.c b/proxy.c
new file mode 100644 (file)
index 0000000..82789cb
--- /dev/null
+++ b/proxy.c
@@ -0,0 +1,845 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $Id$ */
+
+/* System */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <pwd.h>
+#include <ctype.h>
+#include <pthread.h>
+
+/* Libowfat */
+#include "socket.h"
+#include "io.h"
+#include "iob.h"
+#include "byte.h"
+#include "scan.h"
+#include "ip6.h"
+#include "ndelay.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_vector.h"
+#include "ot_mutex.h"
+#include "ot_stats.h"
+
+#ifndef WANT_SYNC_LIVE
+#define WANT_SYNC_LIVE
+#endif
+#include "ot_livesync.h"
+
+ot_ip6   g_serverip;
+uint16_t g_serverport = 9009;
+uint32_t g_tracker_id;
+char     groupip_1[4] = { 224,0,23,5 };
+int      g_self_pipe[2];
+
+/* If you have more than 10 peers, don't use this proxy
+   Use 20 slots for 10 peers to have room for 10 incoming connection slots
+ */
+#define MAX_PEERS 20
+
+#define LIVESYNC_INCOMING_BUFFSIZE          (256*256)
+#define STREAMSYNC_OUTGOING_BUFFSIZE        (256*256)
+
+#define LIVESYNC_OUTGOING_BUFFSIZE_PEERS     1480
+#define LIVESYNC_OUTGOING_WATERMARK_PEERS   (sizeof(ot_peer)+sizeof(ot_hash))
+#define LIVESYNC_MAXDELAY                    15      /* seconds */
+
+/* The amount of time a complete sync cycle should take */
+#define OT_SYNC_INTERVAL_MINUTES             2
+
+/* So after each bucket wait 1 / OT_BUCKET_COUNT intervals */
+#define OT_SYNC_SLEEP ( ( ( OT_SYNC_INTERVAL_MINUTES ) * 60 * 1000000 ) / ( OT_BUCKET_COUNT ) )
+
+enum { OT_SYNC_PEER };
+enum { FLAG_SERVERSOCKET = 1 };
+
+/* For incoming packets */
+static int64    g_socket_in = -1;
+static uint8_t  g_inbuffer[LIVESYNC_INCOMING_BUFFSIZE];
+
+/* For outgoing packets */
+static int64    g_socket_out = -1;
+static uint8_t  g_peerbuffer_start[LIVESYNC_OUTGOING_BUFFSIZE_PEERS];
+static uint8_t *g_peerbuffer_pos;
+static uint8_t *g_peerbuffer_highwater = g_peerbuffer_start + LIVESYNC_OUTGOING_BUFFSIZE_PEERS - LIVESYNC_OUTGOING_WATERMARK_PEERS;
+static ot_time  g_next_packet_time;
+
+static void * livesync_worker( void * args );
+static void * streamsync_worker( void * args );
+static void   livesync_proxytell( uint8_t prefix, uint8_t *info_hash, uint8_t *peer );
+
+void exerr( char * message ) {
+  fprintf( stderr, "%s\n", message );
+  exit( 111 );
+}
+
+void stats_issue_event( ot_status_event event, PROTO_FLAG proto, uintptr_t event_data ) {
+  (void) event;
+  (void) proto;
+  (void) event_data;
+}
+
+void livesync_bind_mcast( ot_ip6 ip, uint16_t port) {
+  char tmpip[4] = {0,0,0,0};
+  char *v4ip;
+
+  if( !ip6_isv4mapped(ip))
+    exerr("v6 mcast support not yet available.");
+  v4ip = ip+12;
+
+  if( g_socket_in != -1 )
+    exerr("Error: Livesync listen ip specified twice.");
+
+  if( ( g_socket_in = socket_udp4( )) < 0)
+    exerr("Error: Cant create live sync incoming socket." );
+  ndelay_off(g_socket_in);
+
+  if( socket_bind4_reuse( g_socket_in, tmpip, port ) == -1 )
+    exerr("Error: Cant bind live sync incoming socket." );
+
+  if( socket_mcjoin4( g_socket_in, groupip_1, v4ip ) )
+    exerr("Error: Cant make live sync incoming socket join mcast group.");
+
+  if( ( g_socket_out = socket_udp4()) < 0)
+    exerr("Error: Cant create live sync outgoing socket." );
+  if( socket_bind4_reuse( g_socket_out, v4ip, port ) == -1 )
+    exerr("Error: Cant bind live sync outgoing socket." );
+
+  socket_mcttl4(g_socket_out, 1);
+  socket_mcloop4(g_socket_out, 1);
+}
+
+size_t add_peer_to_torrent_proxy( ot_hash hash, ot_peer *peer ) {
+  int         exactmatch;
+  ot_torrent *torrent;
+  ot_peer    *peer_dest;
+  ot_vector  *torrents_list = mutex_bucket_lock_by_hash( hash );
+
+  torrent = vector_find_or_insert( torrents_list, (void*)hash, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
+  if( !torrent )
+    return -1;
+
+  if( !exactmatch ) {
+    /* Create a new torrent entry, then */
+    memcpy( torrent->hash, hash, sizeof(ot_hash) );
+
+    if( !( torrent->peer_list = malloc( sizeof (ot_peerlist) ) ) ) {
+      vector_remove_torrent( torrents_list, torrent );
+      mutex_bucket_unlock_by_hash( hash, 0 );
+      return -1;
+    }
+
+    byte_zero( torrent->peer_list, sizeof( ot_peerlist ) );
+  }
+
+  /* Check for peer in torrent */
+  peer_dest = vector_find_or_insert_peer( &(torrent->peer_list->peers), peer, &exactmatch );
+  if( !peer_dest ) {
+    mutex_bucket_unlock_by_hash( hash, 0 );
+    return -1;
+  }
+  /* Tell peer that it's fresh */
+  OT_PEERTIME( peer ) = 0;
+
+  /* If we hadn't had a match create peer there */
+  if( !exactmatch ) {
+    torrent->peer_list->peer_count++;
+    if( OT_PEERFLAG(peer) & PEER_FLAG_SEEDING )
+      torrent->peer_list->seed_count++;
+  }
+  memcpy( peer_dest, peer, sizeof(ot_peer) );
+  mutex_bucket_unlock_by_hash( hash, 0 );
+  return 0;
+}
+
+size_t remove_peer_from_torrent_proxy( ot_hash hash, ot_peer *peer ) {
+  int          exactmatch;
+  ot_vector   *torrents_list = mutex_bucket_lock_by_hash( hash );
+  ot_torrent  *torrent = binary_search( hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
+
+  if( exactmatch ) {
+    ot_peerlist *peer_list = torrent->peer_list;
+    switch( vector_remove_peer( &peer_list->peers, peer ) ) {
+      case 2:  peer_list->seed_count--; /* Fall throughs intended */
+      case 1:  peer_list->peer_count--; /* Fall throughs intended */
+      default: break;
+    }
+  }
+
+  mutex_bucket_unlock_by_hash( hash, 0 );
+  return 0;
+}
+
+void free_peerlist( ot_peerlist *peer_list ) {
+  if( peer_list->peers.data ) {
+    if( OT_PEERLIST_HASBUCKETS( peer_list ) ) {
+      ot_vector *bucket_list = (ot_vector*)(peer_list->peers.data);
+
+      while( peer_list->peers.size-- )
+        free( bucket_list++->data );
+    }
+    free( peer_list->peers.data );
+  }
+  free( peer_list );
+}
+
+static void livesync_handle_peersync( ssize_t datalen ) {
+  int off = sizeof( g_tracker_id ) + sizeof( uint32_t );
+
+  fprintf( stderr, "." );
+
+  while( off + (ssize_t)sizeof( ot_hash ) + (ssize_t)sizeof( ot_peer ) <= datalen ) {
+    ot_peer *peer = (ot_peer*)(g_inbuffer + off + sizeof(ot_hash));
+    ot_hash *hash = (ot_hash*)(g_inbuffer + off);
+
+    if( OT_PEERFLAG(peer) & PEER_FLAG_STOPPED )
+      remove_peer_from_torrent_proxy( *hash, peer );
+    else
+      add_peer_to_torrent_proxy( *hash, peer );
+
+    off += sizeof( ot_hash ) + sizeof( ot_peer );
+  }
+}
+
+int usage( char *self ) {
+  fprintf( stderr, "Usage: %s -L <livesync_iface_ip> -l <listenip>:<listenport> -c <connectip>:<connectport>\n", self );
+  return 0;
+}
+
+enum {
+  FLAG_OUTGOING      = 0x80,
+
+  FLAG_DISCONNECTED  = 0x00,
+  FLAG_CONNECTING    = 0x01,
+  FLAG_WAITTRACKERID = 0x02,
+  FLAG_CONNECTED     = 0x03,
+
+  FLAG_MASK          = 0x07
+};
+
+#define PROXYPEER_NEEDSCONNECT(flag)      ((flag)==FLAG_OUTGOING)
+#define PROXYPEER_ISCONNECTED(flag)       (((flag)&FLAG_MASK)==FLAG_CONNECTED)
+#define PROXYPEER_SETDISCONNECTED(flag)   (flag)=(((flag)&FLAG_OUTGOING)|FLAG_DISCONNECTED)
+#define PROXYPEER_SETCONNECTING(flag)     (flag)=(((flag)&FLAG_OUTGOING)|FLAG_CONNECTING)
+#define PROXYPEER_SETWAITTRACKERID(flag)  (flag)=(((flag)&FLAG_OUTGOING)|FLAG_WAITTRACKERID)
+#define PROXYPEER_SETCONNECTED(flag)      (flag)=(((flag)&FLAG_OUTGOING)|FLAG_CONNECTED)
+
+typedef struct {
+  int      state;           /* Whether we want to connect, how far our handshake is, etc. */
+  ot_ip6   ip;              /* The peer to connect to */
+  uint16_t port;            /* The peers port */
+  uint8_t  indata[8192*16]; /* Any data not processed yet */
+  size_t   indata_length;   /* Length of unprocessed data */
+  uint32_t tracker_id;      /* How the other end greeted */
+  int64    fd;              /* A file handle, if connected, <= 0 is disconnected (0 initially, -1 else) */
+  io_batch outdata;         /* The iobatch containing our sync data */
+
+  size_t   packet_tcount;   /* Number of unprocessed torrents in packet we currently receive */
+  uint8_t  packet_tprefix;  /* Prefix byte for all torrents in current packet */
+  uint8_t  packet_type;     /* Type of current packet */
+  uint32_t packet_tid;      /* Tracker id for current packet */
+
+} proxy_peer;
+static void process_indata( proxy_peer * peer );
+
+void reset_info_block( proxy_peer * peer ) {
+  peer->indata_length = 0;
+  peer->tracker_id    = 0;
+  peer->fd            = -1;
+  peer->packet_tcount = 0;
+  iob_reset( &peer->outdata );
+  PROXYPEER_SETDISCONNECTED( peer->state );
+}
+
+/* Number of connections to peers
+   * If a peer's IP is set, we try to reconnect, when the connection drops
+   * If we already have a connected tracker_id in our records for an _incoming_ connection, drop it
+   * Multiple connections to/from the same ip are okay, if tracker_id doesn't match
+   * Reconnect attempts occur only twice a minute
+*/
+static int        g_connection_count;
+static ot_time    g_connection_reconn;
+static proxy_peer g_connections[MAX_PEERS];
+
+static void handle_reconnects( void ) {
+  int i;
+  for( i=0; i<g_connection_count; ++i )
+    if( PROXYPEER_NEEDSCONNECT( g_connections[i].state ) ) {
+      int64 newfd = socket_tcp6( );
+      fprintf( stderr, "(Re)connecting to peer..." );
+      if( newfd < 0 ) continue; /* No socket for you */
+      io_fd(newfd);
+      if( socket_bind6_reuse(newfd,g_serverip,g_serverport,0) ) {
+        io_close( newfd );
+        continue;
+      }
+      if( socket_connect6(newfd,g_connections[i].ip,g_connections[i].port,0) == -1 &&
+          errno != EINPROGRESS && errno != EWOULDBLOCK ) {
+        close(newfd);
+        continue;
+      }
+      io_wantwrite(newfd); /* So we will be informed when it is connected */
+      io_setcookie(newfd,g_connections+i);
+
+      /* Prepare connection info block */
+      reset_info_block( g_connections+i );
+      g_connections[i].fd            = newfd;
+      PROXYPEER_SETCONNECTING( g_connections[i].state );
+    }
+  g_connection_reconn = time(NULL) + 30;
+}
+
+/* Handle incoming connection requests, check against whitelist */
+static void handle_accept( int64 serversocket ) {
+  int64 newfd;
+  ot_ip6 ip;
+  uint16 port;
+
+  while( ( newfd = socket_accept6( serversocket, ip, &port, NULL ) ) != -1 ) {
+
+    /* XXX some access control */
+
+    /* Put fd into a non-blocking mode */
+    io_nonblock( newfd );
+
+    if( !io_fd( newfd ) )
+      io_close( newfd );
+    else {
+      /* Find a new home for our incoming connection */
+      int i;
+      for( i=0; i<MAX_PEERS; ++i )
+        if( g_connections[i].state == FLAG_DISCONNECTED )
+          break;
+      if( i == MAX_PEERS ) {
+        fprintf( stderr, "No room for incoming connection." );
+        close( newfd );
+        continue;
+      }
+
+      /* Prepare connection info block */
+      reset_info_block( g_connections+i );
+      PROXYPEER_SETCONNECTING( g_connections[i].state );
+      g_connections[i].port = port;
+      g_connections[i].fd   = newfd;
+
+      io_setcookie( newfd, g_connections + i );
+
+      /* We expect the connecting side to begin with its tracker_id */
+      io_wantread( newfd );
+    }
+  }
+
+  return;
+}
+
+/* New sync data on the stream */
+static void handle_read( int64 peersocket ) {
+  int i;
+  int64 datalen;
+  uint32_t tracker_id;
+  proxy_peer *peer = io_getcookie( peersocket );
+
+  if( !peer ) {
+    /* Can't happen ;) */
+    io_close( peersocket );
+    return;
+  }
+  switch( peer->state & FLAG_MASK ) {
+  case FLAG_DISCONNECTED:
+    io_close( peersocket );
+    break; /* Shouldnt happen */
+  case FLAG_CONNECTING:
+  case FLAG_WAITTRACKERID:
+    /* We want at least the first four bytes to come at once, to avoid keeping extra states (for now)
+       This also catches 0 bytes reads == EOF and negative values, denoting connection errors */
+    if( io_tryread( peersocket, (void*)&tracker_id, sizeof( tracker_id ) ) != sizeof( tracker_id ) )
+      goto close_socket;
+
+    /* See, if we already have a connection to that peer */
+    for( i=0; i<MAX_PEERS; ++i )
+      if( ( g_connections[i].state & FLAG_MASK ) == FLAG_CONNECTED &&
+            g_connections[i].tracker_id == tracker_id ) {
+        fprintf( stderr, "Peer already connected. Closing connection.\n" );
+        goto close_socket;
+      }
+
+    /* Also no need for soliloquy */
+    if( tracker_id == g_tracker_id )
+      goto close_socket;
+
+    /* The new connection is good, send our tracker_id on incoming connections */
+    if( peer->state == FLAG_CONNECTING )
+      if( io_trywrite( peersocket, (void*)&g_tracker_id, sizeof( g_tracker_id ) ) != sizeof( g_tracker_id ) )
+        goto close_socket;
+
+    peer->tracker_id = tracker_id;
+    PROXYPEER_SETCONNECTED( peer->state );
+
+    if( peer->state & FLAG_OUTGOING )
+      fprintf( stderr, "succeeded.\n" );
+    else
+      fprintf( stderr, "Incoming connection successful.\n" );
+
+    break;
+close_socket:
+    fprintf( stderr, "Handshake incomplete, closing socket\n" );
+    io_close( peersocket );
+    reset_info_block( peer );
+    break;
+  case FLAG_CONNECTED:
+    /* Here we acutally expect data from peer
+       indata_length should be less than 20+256*7 bytes, for incomplete torrent entries */
+    datalen = io_tryread( peersocket, (void*)(peer->indata + peer->indata_length), sizeof( peer->indata ) - peer->indata_length );
+    if( !datalen || datalen < -1 ) {
+      fprintf( stderr, "Connection closed by remote peer.\n" );
+      io_close( peersocket );
+      reset_info_block( peer );
+    } else if( datalen > 0 ) {
+      peer->indata_length += datalen;
+      process_indata( peer );
+    }
+    break;
+  }
+}
+
+/* Can write new sync data to the stream */
+static void handle_write( int64 peersocket ) {
+  proxy_peer *peer = io_getcookie( peersocket );
+
+  if( !peer ) {
+    /* Can't happen ;) */
+    io_close( peersocket );
+    return;
+  }
+
+  switch( peer->state & FLAG_MASK ) {
+  case FLAG_DISCONNECTED:
+  default: /* Should not happen */
+    io_close( peersocket );
+    break;
+  case FLAG_CONNECTING:
+    /* Ensure that the connection is established and handle connection error */
+    if( peer->state & FLAG_OUTGOING && !socket_connected( peersocket ) ) {
+        fprintf( stderr, "failed\n" );
+        reset_info_block( peer );
+        io_close( peersocket );
+        break;
+    }
+
+    if( io_trywrite( peersocket, (void*)&g_tracker_id, sizeof( g_tracker_id ) ) == sizeof( g_tracker_id ) ) {
+      PROXYPEER_SETWAITTRACKERID( peer->state );
+      io_dontwantwrite( peersocket );
+      io_wantread( peersocket );
+    } else {
+      fprintf( stderr, "Handshake incomplete, closing socket\n" );
+      io_close( peersocket );
+      reset_info_block( peer );
+    }
+    break;
+  case FLAG_CONNECTED:
+    switch( iob_send( peersocket, &peer->outdata ) ) {
+    case 0: /* all data sent */
+      io_dontwantwrite( peersocket );
+      break;
+    case -3: /* an error occured */
+      io_close( peersocket );
+      reset_info_block( peer );
+      break;
+    default: /* Normal operation or eagain */
+      break;
+    }
+    break;
+  }
+
+  return;
+}
+
+static void server_mainloop() {
+  int64 sock;
+
+  /* inlined livesync_init() */
+  memset( g_peerbuffer_start, 0, sizeof( g_peerbuffer_start ) ); 
+  g_peerbuffer_pos = g_peerbuffer_start;
+  memcpy( g_peerbuffer_pos, &g_tracker_id, sizeof( g_tracker_id ) );
+  uint32_pack_big( (char*)g_peerbuffer_pos + sizeof( g_tracker_id ), OT_SYNC_PEER);
+  g_peerbuffer_pos += sizeof( g_tracker_id ) + sizeof( uint32_t);
+  g_next_packet_time = time(NULL) + LIVESYNC_MAXDELAY;
+
+  while(1) {
+    /* See, if we need to connect to anyone */
+    if( time(NULL) > g_connection_reconn )
+      handle_reconnects( );
+
+    /* Wait for io events until next approx reconn check time */
+    io_waituntil2( 30*1000 );
+
+    /* Loop over readable sockets */
+    while( ( sock = io_canread( ) ) != -1 ) {
+      const void *cookie = io_getcookie( sock );
+      if( (uintptr_t)cookie == FLAG_SERVERSOCKET )
+        handle_accept( sock );
+      else
+        handle_read( sock );
+    }
+
+    /* Loop over writable sockets */
+    while( ( sock = io_canwrite( ) ) != -1 )
+      handle_write( sock );
+
+    livesync_ticker( );
+  }
+}
+
+static void panic( const char *routine ) {
+  fprintf( stderr, "%s: %s\n", routine, strerror(errno) );
+  exit( 111 );
+}
+
+static int64_t ot_try_bind( ot_ip6 ip, uint16_t port ) {
+  int64 sock = socket_tcp6( );
+
+  if( socket_bind6_reuse( sock, ip, port, 0 ) == -1 )
+    panic( "socket_bind6_reuse" );
+
+  if( socket_listen( sock, SOMAXCONN) == -1 )
+    panic( "socket_listen" );
+
+  if( !io_fd( sock ) )
+    panic( "io_fd" );
+
+  io_setcookie( sock, (void*)FLAG_SERVERSOCKET );
+  io_wantread( sock );
+  return sock;
+}
+
+
+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( *s == 0 || isspace(*s)) return s-src;
+  if( *s == ']' && bracket ) ++s;
+  if( !ip6_isv4mapped(ip)){
+    if( ( bracket && *(s) != ':' ) || ( *(s) != '.' ) ) return 0;
+    s++;
+  } else {
+    if( *(s++) != ':' ) return 0;
+  }
+  if( !(off = scan_ushort (s, port ) ) )
+     return 0;
+  return off+s-src;
+}
+
+int main( int argc, char **argv ) {
+  static pthread_t sync_in_thread_id;
+  static pthread_t sync_out_thread_id;
+  ot_ip6 serverip;
+  uint16_t tmpport;
+  int scanon = 1, lbound = 0, sbound = 0;
+
+  srandom( time(NULL) );
+  g_tracker_id = random();
+  noipv6=1;
+
+  while( scanon ) {
+    switch( getopt( argc, argv, ":l:c:L:h" ) ) {
+    case -1: scanon = 0; break;
+    case 'l':
+      tmpport = 0;
+      if( !scan_ip6_port( optarg, serverip, &tmpport ) || !tmpport ) { usage( argv[0] ); exit( 1 ); }
+      ot_try_bind( serverip, tmpport );
+      ++sbound;
+      break;
+    case 'c':
+      if( g_connection_count > MAX_PEERS / 2 ) exerr( "Connection limit exceeded.\n" );
+      tmpport = 0;
+      if( !scan_ip6_port( optarg,
+        g_connections[g_connection_count].ip,
+        &g_connections[g_connection_count].port ) ||
+        !g_connections[g_connection_count].port ) { usage( argv[0] ); exit( 1 ); }
+      g_connections[g_connection_count++].state = FLAG_OUTGOING;
+      break;
+    case 'L':
+      tmpport = 9696;
+      if( !scan_ip6_port( optarg, serverip, &tmpport ) || !tmpport ) { usage( argv[0] ); exit( 1 ); }
+      livesync_bind_mcast( serverip, tmpport); ++lbound; break;
+    default:
+    case '?': usage( argv[0] ); exit( 1 );
+    }
+  }
+
+  if( !lbound ) exerr( "No livesync port bound." );
+  if( !g_connection_count && !sbound ) exerr( "No streamsync port bound." );
+  pthread_create( &sync_in_thread_id, NULL, livesync_worker, NULL );
+  pthread_create( &sync_out_thread_id, NULL, streamsync_worker, NULL );
+
+  server_mainloop();
+  return 0;
+}
+
+static void * streamsync_worker( void * args ) {
+  (void)args;
+  while( 1 ) {
+    int bucket;
+    /* For each bucket... */
+    for( bucket=0; bucket<OT_BUCKET_COUNT; ++bucket ) {
+      /* Get exclusive access to that bucket */
+      ot_vector *torrents_list = mutex_bucket_lock( bucket );
+      size_t tor_offset, count_def = 0, count_one = 0, count_two = 0, count_peers = 0;
+      size_t mem, mem_a = 0, mem_b = 0;
+      uint8_t *ptr = 0, *ptr_a, *ptr_b, *ptr_c;
+
+      if( !torrents_list->size ) goto unlock_continue;
+
+      /* For each torrent in this bucket.. */
+      for( tor_offset=0; tor_offset<torrents_list->size; ++tor_offset ) {
+        /* Address torrents members */
+        ot_peerlist *peer_list = ( ((ot_torrent*)(torrents_list->data))[tor_offset] ).peer_list;
+        switch( peer_list->peer_count ) {
+          case 2:  count_two++; break;
+          case 1:  count_one++; break;
+          case 0:               break;
+          default: count_def++;
+                   count_peers += peer_list->peer_count;
+        }
+      }
+
+      /* Maximal memory requirement: max 3 blocks, max torrents * 20 + max peers * 7 */
+      mem = 3 * ( 1 + 1 + 2 ) + ( count_one + count_two ) * ( 19 + 1 ) + count_def * ( 19 + 8 ) +
+            ( count_one + 2 * count_two + count_peers ) * 7;
+
+      fprintf( stderr, "Mem: %zd\n", mem );
+
+      ptr = ptr_a = ptr_b = ptr_c = malloc( mem );
+      if( !ptr ) goto unlock_continue;
+
+      if( count_one > 4 || !count_def ) {
+        mem_a = 1 + 1 + 2 + count_one * ( 19 + 7 );
+        ptr_b += mem_a; ptr_c += mem_a;
+        ptr_a[0] = 1;                                        /* Offset 0: packet type 1 */
+        ptr_a[1] = (bucket << 8) >> OT_BUCKET_COUNT_BITS;    /* Offset 1: the shared prefix */
+        ptr_a[2] = count_one >> 8;
+        ptr_a[3] = count_one & 255;
+        ptr_a += 4;
+      } else
+        count_def += count_one;
+
+      if( count_two > 4 || !count_def ) {
+        mem_b = 1 + 1 + 2 + count_two * ( 19 + 14 );
+        ptr_c += mem_b;
+        ptr_b[0] = 2;                                        /* Offset 0: packet type 2 */
+        ptr_b[1] = (bucket << 8) >> OT_BUCKET_COUNT_BITS;    /* Offset 1: the shared prefix */
+        ptr_b[2] = count_two >> 8;
+        ptr_b[3] = count_two & 255;
+        ptr_b += 4;
+      } else
+        count_def += count_two;
+
+      if( count_def ) {
+        ptr_c[0] = 0;                                        /* Offset 0: packet type 0 */
+        ptr_c[1] = (bucket << 8) >> OT_BUCKET_COUNT_BITS;    /* Offset 1: the shared prefix */
+        ptr_c[2] = count_def >> 8;
+        ptr_c[3] = count_def & 255;
+        ptr_c += 4;
+      }
+
+      /* For each torrent in this bucket.. */
+      for( tor_offset=0; tor_offset<torrents_list->size; ++tor_offset ) {
+        /* Address torrents members */
+        ot_torrent *torrent = ((ot_torrent*)(torrents_list->data)) + tor_offset;
+        ot_peerlist *peer_list = torrent->peer_list;
+        ot_peer *peers = (ot_peer*)(peer_list->peers.data);
+        uint8_t **dst;
+
+        /* Determine destination slot */
+        count_peers = peer_list->peer_count;
+        switch( count_peers ) {
+          case 0:  continue;
+          case 1:  dst = mem_a ? &ptr_a : &ptr_c; break;
+          case 2:  dst = mem_b ? &ptr_b : &ptr_c; break;
+          default: dst = &ptr_c; break;
+        }
+
+        /* Copy tail of info_hash, advance pointer */
+        memcpy( *dst, ((uint8_t*)torrent->hash) + 1, sizeof( ot_hash ) - 1);
+        *dst += sizeof( ot_hash ) - 1;
+
+        /* Encode peer count */
+        if( dst == &ptr_c )
+          while( count_peers ) {
+            if( count_peers <= 0x7f )
+              *(*dst)++ = count_peers;
+            else
+              *(*dst)++ = 0x80 | ( count_peers & 0x7f );
+            count_peers >>= 7;
+          }
+
+        /* Copy peers */
+        count_peers = peer_list->peer_count;
+        while( count_peers-- ) {
+          memcpy( *dst, peers++, OT_IP_SIZE + 3 );
+          *dst += OT_IP_SIZE + 3;
+        }
+        free_peerlist(peer_list);
+      }
+
+      free( torrents_list->data );
+      memset( torrents_list, 0, sizeof(*torrents_list ) );
+unlock_continue:
+      mutex_bucket_unlock( bucket, 0 );
+
+      if( ptr ) {
+        int i;
+
+        if( ptr_b > ptr_c ) ptr_c = ptr_b;
+        if( ptr_a > ptr_c ) ptr_c = ptr_a;
+        mem = ptr_c - ptr;
+
+        for( i=0; i < MAX_PEERS; ++i ) {
+          if( PROXYPEER_ISCONNECTED(g_connections[i].state) ) {
+            void *tmp = malloc( mem );
+            if( tmp ) {
+              memcpy( tmp, ptr, mem );
+              if( !iob_addbuf_free( &g_connections[i].outdata, tmp, mem ) )
+                free( tmp );
+              io_wantwrite( g_connections[i].fd );
+            }
+          }
+        }
+
+        free( ptr );
+      }
+      usleep( OT_SYNC_SLEEP );
+    }
+  }
+  return 0;
+}
+
+static void livesync_issue_peersync( ) {
+  socket_send4(g_socket_out, (char*)g_peerbuffer_start, g_peerbuffer_pos - g_peerbuffer_start,
+               groupip_1, LIVESYNC_PORT);
+  g_peerbuffer_pos   = g_peerbuffer_start + sizeof( g_tracker_id ) + sizeof( uint32_t );
+  g_next_packet_time = time(NULL) + LIVESYNC_MAXDELAY;
+}
+
+void livesync_ticker( ) {
+  /* livesync_issue_peersync sets g_next_packet_time */
+  if( time(NULL) > g_next_packet_time &&
+     g_peerbuffer_pos > g_peerbuffer_start + sizeof( g_tracker_id ) )
+    livesync_issue_peersync();
+}
+
+static void livesync_proxytell( uint8_t prefix, uint8_t *info_hash, uint8_t *peer ) {
+//  unsigned int i;
+
+  *g_peerbuffer_pos = prefix;
+  memcpy( g_peerbuffer_pos + 1, info_hash, sizeof(ot_hash) - 1 );
+  memcpy( g_peerbuffer_pos + sizeof(ot_hash), peer, sizeof(ot_peer) - 1 );
+
+#if 0
+  /* Dump info_hash */
+  for( i=0; i<sizeof(ot_hash); ++i )
+    printf( "%02X", g_peerbuffer_pos[i] );
+  putchar( ':' );
+#endif
+  g_peerbuffer_pos += sizeof(ot_hash);
+#if 0
+  printf( "%hhu.%hhu.%hhu.%hhu:%hu (%02X %02X)\n", g_peerbuffer_pos[0], g_peerbuffer_pos[1], g_peerbuffer_pos[2], g_peerbuffer_pos[3],
+    g_peerbuffer_pos[4] | ( g_peerbuffer_pos[5] << 8 ), g_peerbuffer_pos[6], g_peerbuffer_pos[7] );
+#endif
+  g_peerbuffer_pos += sizeof(ot_peer);
+
+  if( g_peerbuffer_pos >= g_peerbuffer_highwater )
+    livesync_issue_peersync();
+}
+
+static void process_indata( proxy_peer * peer ) {
+  size_t consumed, peers;
+  uint8_t *data    = peer->indata, *hash;
+  uint8_t *dataend = data + peer->indata_length;
+
+  while( 1 ) {
+    /* If we're not inside of a packet, make a new one */
+    if( !peer->packet_tcount ) {
+      /* Ensure the header is complete or postpone processing */
+      if( data + 4 > dataend ) break;
+      peer->packet_type    = data[0];
+      peer->packet_tprefix = data[1];
+      peer->packet_tcount  = data[2] * 256 + data[3];
+      data += 4;
+printf( "type: %hhu, prefix: %02X, torrentcount: %zd\n", peer->packet_type, peer->packet_tprefix, peer->packet_tcount );
+    }
+
+    /* Ensure size for a minimal torrent block */
+    if( data + sizeof(ot_hash) + OT_IP_SIZE + 3 > dataend ) break;
+
+    /* Advance pointer to peer count or peers */
+    hash = data;
+    data += sizeof(ot_hash) - 1;
+
+    /* Type 0 has peer count encoded before each peers */
+    peers = peer->packet_type;
+    if( !peers ) {
+      int shift = 0;
+      do peers |= ( 0x7f & *data ) << ( 7 * shift );
+        while ( *(data++) & 0x80 && shift++ < 6 );
+    }
+#if 0
+printf( "peers: %zd\n", peers );
+#endif
+    /* Ensure enough data being read to hold all peers */
+    if( data + (OT_IP_SIZE + 3) * peers > dataend ) {
+      data = hash;
+      break;
+    }
+    while( peers-- ) {
+      livesync_proxytell( peer->packet_tprefix, hash, data );
+      data += OT_IP_SIZE + 3;
+    }
+    --peer->packet_tcount;
+  }
+
+  consumed = data - peer->indata;
+  memmove( peer->indata, data, peer->indata_length - consumed );
+  peer->indata_length -= consumed;
+}
+
+static void * livesync_worker( void * args ) {
+  (void)args;
+  while( 1 ) {
+    ot_ip6 in_ip; uint16_t in_port;
+    size_t datalen = socket_recv4(g_socket_in, (char*)g_inbuffer, LIVESYNC_INCOMING_BUFFSIZE, 12+(char*)in_ip, &in_port);
+
+    /* Expect at least tracker id and packet type */
+    if( datalen <= (ssize_t)(sizeof( g_tracker_id ) + sizeof( uint32_t )) )
+      continue;
+    if( !memcmp( g_inbuffer, &g_tracker_id, sizeof( g_tracker_id ) ) ) {
+      /* drop packet coming from ourselves */
+      continue;
+    }
+    switch( uint32_read_big( sizeof( g_tracker_id ) + (char*)g_inbuffer ) ) {
+    case OT_SYNC_PEER:
+      livesync_handle_peersync( datalen );
+      break;
+    default:
+      // fprintf( stderr, "Received an unknown live sync packet type %u.\n", uint32_read_big( sizeof( g_tracker_id ) + (char*)g_inbuffer ) );
+      break;
+    }
+  }
+  return 0;
+}
diff --git a/scan_urlencoded_query.c b/scan_urlencoded_query.c
new file mode 100644 (file)
index 0000000..a4f89c2
--- /dev/null
@@ -0,0 +1,143 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* Opentracker */
+#include "scan_urlencoded_query.h"
+
+/* Libwofat */
+#include "scan.h"
+
+/* System */
+#include <string.h>
+
+/* Idea is to do a in place replacement or guarantee at least
+   strlen( string ) bytes in deststring
+   watch http://www.ietf.org/rfc/rfc2396.txt
+         unreserved    = alphanum | mark
+         mark          = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+   we add '%' to the matrix to not stop at encoded chars.
+   After losing too many requests to being too strict, add the following characters to reserved matrix
+         relax         = "+" | "," | "/" | ";" | "<" | ">" | ":"
+*/
+
+/* This matrix holds for each ascii character the information,
+   whether it is a non-terminating character for on of the three
+   scan states we are in, that is 'path', 'param' and 'value' from
+  /path?param=value&param=value, it is encoded in bit 0, 1 and 2
+  respectively
+
+  The top bit of lower nibble indicates, whether this character is
+  a hard terminator, ie. \0, \n or \s, where the whole scanning
+  process should terminate
+  */
+static const unsigned char is_unreserved[256] = {
+  8,0,0,0,0,0,0,0,0,0,8,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+  8,7,8,8,8,7,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,4,7,6,
+  4,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,7,
+  8,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,7,0,
+  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+};
+
+/* Do a fast nibble to hex representation conversion */
+static unsigned char fromhex(unsigned char x) {
+  x-='0'; if( x<=9) return x;
+  x&=~0x20; x-='A'-'0';
+  if( x<6 ) return x+10;
+  return 0xff;
+}
+
+/* Skip the value of a param=value pair */
+void scan_urlencoded_skipvalue( char **string ) {
+  const unsigned char* s=*(const unsigned char**) string;
+  unsigned char f;
+
+  /* Since we are asked to skip the 'value', we assume to stop at
+     terminators for a 'value' string position */
+  while( ( f = is_unreserved[ *s++ ] ) & SCAN_SEARCHPATH_VALUE );
+
+  /* If we stopped at a hard terminator like \0 or \n, make the
+     next scan_urlencoded_query encounter it again */
+  if( f & SCAN_SEARCHPATH_TERMINATOR ) --s;
+
+  *string = (char*)s;
+}
+
+int scan_find_keywords( const ot_keywords * keywords, char **string, SCAN_SEARCHPATH_FLAG flags) {
+  char *deststring = *string;
+  ssize_t match_length = scan_urlencoded_query(string, deststring, flags );
+
+  if( match_length < 0 ) return match_length;
+  if( match_length == 0 ) return -3;
+
+  while( keywords->key ) {
+    if( !strncmp( keywords->key, deststring, match_length ) && !keywords->key[match_length] )
+      return keywords->value;
+    keywords++;
+  }
+
+  return -3;
+}
+
+ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags) {
+  const unsigned char* s=*(const unsigned char**) string;
+  unsigned char *d = (unsigned char*)deststring;
+  unsigned char b, c;
+
+  /* This is the main decoding loop.
+    'flag' determines, which characters are non-terminating in current context
+    (ie. stop at '=' and '&' if scanning for a 'param'; stop at '?' if scanning for the path )
+  */
+  while( is_unreserved[ c = *s++ ] & flags ) {
+
+    /* When encountering an url escaped character, try to decode */
+    if( c=='%') {
+      if( ( b = fromhex(*s++) ) == 0xff ) return -1;
+      if( ( c = fromhex(*s++) ) == 0xff ) return -1;
+      c|=(b<<4);
+    }
+
+    /* Write (possibly decoded) character to output */
+    *d++ = c;
+  }
+
+  switch( c ) {
+  case 0: case '\r': case '\n': case ' ':
+    /* If we started scanning on a hard terminator, indicate we've finished */
+    if( d == (unsigned char*)deststring ) return -2;
+
+    /* Else make the next call to scan_urlencoded_param encounter it again */
+    --s;
+    break;
+  case '?':
+    if( flags != SCAN_PATH ) return -1;
+    break;
+  case '=':
+    if( flags != SCAN_SEARCHPATH_PARAM ) return -1;
+    break;
+  case '&':
+    if( flags == SCAN_PATH ) return -1;
+    if( flags == SCAN_SEARCHPATH_PARAM ) --s;
+    break;
+  default:
+    return -1;
+  }
+
+  *string = (char *)s;
+  return d - (unsigned char*)deststring;
+}
+
+ssize_t scan_fixed_int( char *data, size_t len, int *tmp ) {
+  int minus = 0;
+  *tmp = 0;
+  if( *data == '-' ) --len, ++data, ++minus;
+  while( (len > 0) && (*data >= '0') && (*data <= '9') ) { --len; *tmp = 10**tmp + *data++-'0'; }
+  if( minus ) *tmp = -*tmp;
+  return len;
+}
+
+const char *g_version_scan_urlencoded_query_c = "$Source$: $Revision$\n";
diff --git a/scan_urlencoded_query.h b/scan_urlencoded_query.h
new file mode 100644 (file)
index 0000000..92e3f34
--- /dev/null
@@ -0,0 +1,55 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __SCAN_URLENCODED_QUERY_H__
+#define __SCAN_URLENCODED_QUERY_H__
+
+#include <sys/types.h>
+
+typedef struct {
+  char *key;
+  int   value;
+} ot_keywords;
+
+typedef enum {
+  SCAN_PATH                  = 1,
+  SCAN_SEARCHPATH_PARAM      = 2,
+  SCAN_SEARCHPATH_VALUE      = 4,
+  SCAN_SEARCHPATH_TERMINATOR = 8
+} SCAN_SEARCHPATH_FLAG;
+
+/* string     in: pointer to source
+              out: pointer to next scan position
+   deststring pointer to destination
+   flags      determines, what to parse
+   returns    number of valid converted characters in deststring
+              or -1 for parse error
+              or -2 for terminator found
+*/
+ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags);
+
+/* string     in: pointer to source
+              out: pointer to next scan position
+   flags      determines, what to parse
+   returns    value for matched keyword
+              or -1 for parse error
+              or -2 for terminator found
+              or -3 for no keyword matched
+ */
+int scan_find_keywords( const ot_keywords * keywords, char **string, SCAN_SEARCHPATH_FLAG flags);
+
+/* string     in: pointer to value of a param=value pair to skip
+              out: pointer to next scan position on return
+*/
+void scan_urlencoded_skipvalue( char **string );
+
+/* data       pointer to len chars of string
+ len        length of chars in data to parse
+ number     number to receive result
+ returns    number of bytes not parsed, mostly !=0 means fail
+ */
+ssize_t scan_fixed_int( char *data, size_t len, int *number );
+
+#endif
diff --git a/sync_daemon.pl b/sync_daemon.pl
new file mode 100644 (file)
index 0000000..9e4bdb9
--- /dev/null
@@ -0,0 +1,294 @@
+#!/usr/bin/perl
+
+# This software was written by Philipp Wuensche <cryx-otsync@h3q.com>
+# It is considered beerware.
+
+use strict;
+
+#use Convert::Bencode_XS qw(:all);
+use Convert::Bencode qw(:all);
+use Data::Dumper;
+use LWP::UserAgent;
+use URI::Escape;
+
+# enable verbose output
+my $debug = 0;
+
+# tracker from where we get our sync data
+my @trackers = ('127.0.0.1:8989');
+# tracker to upload merged data
+my @client_tracker = ('127.0.0.1:8989');
+
+# time to wait between syncs
+my $sleeptime = '300';
+
+# SSL cert and key
+my $ssl_cert = 'cert.pem';
+my $ssl_key = 'key.pem';
+
+foreach(@trackers) {
+        print "Syncing from: $_\n";
+}
+foreach(@client_tracker) {
+        print "Syncing to: $_\n";
+}
+
+my $file = shift;
+
+
+# global hash for storing the merged syncs
+my %merged_syncs;
+
+while(1) {
+        %merged_syncs;
+        my @bencoded_sync_data;
+
+        # fetch the sync from every tracker and put it into an array in bencoded form
+        foreach my $tracker (@trackers) {
+                my $bencoded_sync = fetch_sync($tracker);
+#               my $bencoded_sync = fetch_sync_from_file($file);
+                if($bencoded_sync ne 0 && $bencoded_sync =~ /^d4\:sync/) {
+                        push(@bencoded_sync_data,$bencoded_sync);
+                }
+        }
+
+        # bdecode every sync and throw it into the merged-sync
+        foreach my $bencoded_sync (@bencoded_sync_data) {
+                print "Doing merge...\n";
+                merge_sync(bdecode($bencoded_sync));
+                my $num_torrents = keys(%merged_syncs);
+
+                print "number of torrents: $num_torrents\n";
+        }
+
+        # number of max. peers in one changeset
+        my $peer_limit = 500;
+        # max number of changesets per commit
+        my $max_changesets = 10;
+        my $hash_count = 0;
+        my $peer_count = 0;
+        my $changeset;
+        my @escaped_changesets;
+
+        # run until all hashes are put into changesets and commited to the trackers
+        while(keys(%merged_syncs) != 0) {
+
+                foreach my $hash (keys(%merged_syncs)) {
+                        print "Starting new changeset\n" if($peer_count == 0 && $debug);
+                        my $num_peers = keys(%{$merged_syncs{$hash}});
+
+                        print "\t$peer_count peers for $hash_count hashes in changeset\n" if($debug);
+
+                        my $pack_hash = pack('H*',$hash);
+
+                        # as long as the peer_limit is not reached, add new hashes with peers to the changeset hash-table
+                        if($peer_count < $peer_limit) {
+                                print "\t\tAdd $num_peers peers for $hash changeset\n" if($debug);
+                                $peer_count = $peer_count + $num_peers;
+                                foreach my $peer_socket (keys(%{$merged_syncs{$hash}})) {
+
+                                        my $flags = $merged_syncs{$hash}{$peer_socket};
+
+                                        print "\t\t\tAdd $peer_socket $flags\n" if($debug);
+
+                                        my $pack_peer = packme($peer_socket,$flags);
+
+                                        $changeset->{'sync'}->{$pack_hash} = $changeset->{'sync'}->{$pack_hash}.$pack_peer;
+                                }
+                                $hash_count++;
+                                # hash is stored in the changeset, delete it from the hash-table
+                                delete $merged_syncs{$hash};
+                        }
+
+                        # the peer_limit is reached or we are out of torrents, so start preparing a changeset
+                        if($peer_count >= $peer_limit || keys(%merged_syncs) == 0) {
+
+                                print "Commit changeset for $hash_count hashes with $peer_count peers total\n" if($debug);
+
+                                # bencode the changeset
+                                my $enc_changeset = bencode($changeset);
+
+                                # URL-escape the changeset and put into an array of changesets
+                                my $foobar = uri_escape($enc_changeset);
+                                push(@escaped_changesets,$foobar);
+
+                                # the changeset is ready and stored, so delete it from the changeset hash-table
+                                delete $changeset->{'sync'};
+
+                                $hash_count = 0;
+                                $peer_count = 0;
+                                print "\n\n\n" if($debug);
+                        }
+
+                        # if enought changesets are prepared or we are out of torrents for more changesets,
+                        # sync the changesets to the trackers
+                        if($#escaped_changesets == $max_changesets || keys(%merged_syncs) == 0) {
+                                print "\tSync...\n";
+                                sync_to_tracker(\@escaped_changesets);
+                                undef @escaped_changesets;
+                        }
+
+                }
+        }
+
+        print "Sleeping for $sleeptime seconds\n";
+        sleep $sleeptime;
+}
+
+sub connect_tracker {
+        # connect a tracker via HTTPS, returns the body of the response
+        my $url = shift;
+
+        $ENV{HTTPS_DEBUG} = 0;
+        $ENV{HTTPS_CERT_FILE} = $ssl_cert;
+        $ENV{HTTPS_KEY_FILE}  = $ssl_key;
+
+        my $ua = new LWP::UserAgent;
+        my $req = new HTTP::Request('GET', $url);
+        my $res = $ua->request($req);
+
+        my $content = $res->content;
+
+        if($res->is_success()) {
+                return $content;
+        } else {
+                print $res->code."|".$res->status_line."\n";
+                return 0;
+        }
+}
+
+sub sync_to_tracker {
+        # commit changesets to a tracker
+        my @changesets = @{(shift)};
+
+        # prepare the URI with URL-encoded changesets concatenated by a &
+        my $uri = 'sync?';
+        foreach my $set (@changesets) {
+                $uri .= 'changeset='.$set.'&';
+        }
+        my $uri_length = length($uri);
+
+        # commit the collection of changesets to the tracker via HTTPS
+        foreach my $tracker (@client_tracker) {
+                print "\t\tTracker: $tracker (URI: $uri_length)\n";
+                my $url = "https://$tracker/".$uri;
+                connect_tracker($url);
+        }
+}
+
+sub packme {
+        # pack data
+        # returns ipaddr, port and flags in packed format
+        my $peer_socket = shift;
+        my $flags = shift;
+
+        my($a,$b,$c,$d,$port) = split(/[\.,\:]/,$peer_socket);
+        my $pack_peer = pack('C4 n1 b16',$a,$b,$c,$d,$port,$flags);
+
+        return $pack_peer;
+}
+
+sub unpackme {
+        # unpack packed data
+        # returns ipaddr. in quad-form with port (a.b.c.d:port) and flags as bitfield
+        # data is packed as:
+        # - 4 byte ipaddr. (unsigned char value)
+        # - 2 byte port (unsigned short in "network" (big-endian) order)
+        # - 2 byte flags (bit string (ascending bit order inside each byte))
+        my $data = shift;
+
+        my($a, $b, $c, $d, $port, $flags) = unpack('C4 n1 b16',$data);
+        my $peer_socket = "$a\.$b\.$c\.$d\:$port";
+
+        return($peer_socket,$flags);
+}
+
+sub fetch_sync {
+        # fetch sync from a tracker
+        my $tracker = shift;
+        my $url = "https://$tracker/sync";
+
+        print "Fetching from $url\n";
+
+        my $body = connect_tracker($url);
+
+        if($body && $body =~ /^d4\:sync/) {
+                return $body;
+        } else {
+                return 0;
+        }
+}
+
+sub fetch_sync_from_file {
+        # fetch sync from a file
+        my $file = shift;
+        my $body;
+        print "Fetching from file $file\n";
+        open(FILE,"<$file");
+        while(<FILE>) {
+                $body .= $_;
+        }
+        close(FILE);
+        return $body;
+}
+
+sub merge_sync {
+        # This builds a hash table with the torrenthash as keys. The value is a hash table again with the peer-socket as keys
+        # and flags in the value
+        # Example:
+        # 60dd2beb4197f71677c0f5ba92b956f7d04651e5 =>
+        #       192.168.23.23:2323 => 0000000000000000
+        #       23.23.23.23:2342 => 0000000100000000
+        # b220b4d7136e84a88abc090db88bec8604a808f3 =>
+        #       42.23.42.23:55555 => 0000000000000000
+
+        my $hashref = shift;
+        my $nonuniq_hash_counter = 0;
+        my $nonuniq_peer_counter = 0;
+        my $hash_counter = 0;
+        my $peer_counter = 0;
+
+        foreach my $key (keys(%{$hashref->{'sync'}})) {
+                # start merge for every sha1-hash in the sync
+                my $hash = unpack('H*',$key);
+
+                $hash_counter++;
+                $nonuniq_hash_counter++ if exists $merged_syncs{$hash};
+
+                while(${$hashref->{'sync'}}{$key} ne "")
+                {
+                        # split the value into 8-byte and unpack it for getting peer-socket and flags
+                        my($peer_socket,$flags) = unpackme(substr(${$hashref->{'sync'}}{$key},0,8,''));
+
+                        $peer_counter++;
+                        $nonuniq_peer_counter++ if exists $merged_syncs{$hash}{$peer_socket};
+
+                        # Create a hash table with sha1-hash as key and a hash table as value.
+                        # The hash table in the value has the peer-socket as key and flags as value
+                        # If the entry already exists, the flags are ORed together, if not it is ORed with 0
+                        $merged_syncs{$hash}{$peer_socket} = $flags | $merged_syncs{$hash}{$peer_socket};
+                }
+        }
+        print "$hash_counter hashes $nonuniq_hash_counter non-uniq, $peer_counter peers $nonuniq_peer_counter non-uniq.\n";
+
+}
+
+sub test_decode {
+        my $hashref = shift;
+
+        print "CHANGESET DEBUG OUTPUT\n";
+
+        print Dumper $hashref;
+        foreach my $key (keys(%{$hashref->{'sync'}})) {
+                my $hash = unpack('H*',$key);
+
+                print "Changeset for $hash\n";
+                while(${$hashref->{'sync'}}{$key} ne "")
+                {
+
+                        my($peer_socket,$flags) = unpackme(substr(${$hashref->{'sync'}}{$key},0,8,''));
+                        print "\tSocket: $peer_socket Flags: $flags\n";
+                }
+        }
+}
diff --git a/tests/testsuite.sh b/tests/testsuite.sh
new file mode 100644 (file)
index 0000000..dace2c6
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+while true; do
+  request_string="GET /announce?info_hash=0123456789012345678\
+%$(printf %02X $(( $RANDOM & 0xf )) )\
+&ip=$(( $RANDOM & 0xf )).$(( $RANDOM & 0xf )).13.16&port=$(( $RANDOM & 0xff )) HTTP/1.0\n"
+
+echo $request_string
+#  echo
+  echo $request_string | nc 127.0.0.1 6969 >/dev/null
+#  echo
+
+done
diff --git a/tests/testsuite2.sh b/tests/testsuite2.sh
new file mode 100644 (file)
index 0000000..c9a5a6a
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+while true; do
+  request_string="GET /announce?info_hash=012345678901234567\
+%$(printf %02X $(( $RANDOM & 0xff )) )\
+%$(printf %02X $(( $RANDOM & 0xff )) )\
+&ip=$(( $RANDOM & 0xff )).17.13.15&port=$(( $RANDOM & 0xff )) HTTP/1.0\n"
+
+  echo $request_string
+  echo
+  echo $request_string | nc 23.23.23.237 6969 >/dev/null
+  echo
+
+done
diff --git a/trackerlogic.c b/trackerlogic.c
new file mode 100644 (file)
index 0000000..310466c
--- /dev/null
@@ -0,0 +1,457 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+/* System */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+/* Libowfat */
+#include "byte.h"
+#include "io.h"
+#include "iob.h"
+#include "array.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_mutex.h"
+#include "ot_stats.h"
+#include "ot_clean.h"
+#include "ot_http.h"
+#include "ot_accesslist.h"
+#include "ot_fullscrape.h"
+#include "ot_livesync.h"
+
+/* Forward declaration */
+size_t return_peers_for_torrent( ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto );
+
+void free_peerlist( ot_peerlist *peer_list ) {
+  if( peer_list->peers.data ) {
+    if( OT_PEERLIST_HASBUCKETS( peer_list ) ) {
+      ot_vector *bucket_list = (ot_vector*)(peer_list->peers.data);
+
+      while( peer_list->peers.size-- )
+        free( bucket_list++->data );
+    }
+    free( peer_list->peers.data );
+  }
+  free( peer_list );
+}
+
+void add_torrent_from_saved_state( ot_hash hash, ot_time base, size_t down_count ) {
+  int         exactmatch;
+  ot_torrent *torrent;
+  ot_vector  *torrents_list = mutex_bucket_lock_by_hash( hash );
+
+  if( !accesslist_hashisvalid( hash ) )
+    return mutex_bucket_unlock_by_hash( hash, 0 );
+  
+  torrent = vector_find_or_insert( torrents_list, (void*)hash, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
+  if( !torrent || exactmatch )
+    return mutex_bucket_unlock_by_hash( hash, 0 );
+
+  /* Create a new torrent entry, then */
+  memcpy( torrent->hash, hash, sizeof(ot_hash) );
+    
+  if( !( torrent->peer_list = malloc( sizeof (ot_peerlist) ) ) ) {
+    vector_remove_torrent( torrents_list, torrent );
+    return mutex_bucket_unlock_by_hash( hash, 0 );
+  }
+    
+  byte_zero( torrent->peer_list, sizeof( ot_peerlist ) );
+  torrent->peer_list->base = base;
+  torrent->peer_list->down_count = down_count;
+
+  return mutex_bucket_unlock_by_hash( hash, 1 );
+}
+
+size_t add_peer_to_torrent_and_return_peers( PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount ) {
+  int         exactmatch, delta_torrentcount = 0;
+  ot_torrent *torrent;
+  ot_peer    *peer_dest;
+  ot_vector  *torrents_list = mutex_bucket_lock_by_hash( *ws->hash );
+
+  if( !accesslist_hashisvalid( *ws->hash ) ) {
+    mutex_bucket_unlock_by_hash( *ws->hash, 0 );
+    if( proto == FLAG_TCP ) {
+      const char invalid_hash[] = "d14:failure reason63:Requested download is not authorized for use with this tracker.e";
+      memcpy( ws->reply, invalid_hash, strlen( invalid_hash ) );
+      return strlen( invalid_hash );
+    }
+    return 0;
+  }
+
+  torrent = vector_find_or_insert( torrents_list, (void*)ws->hash, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
+  if( !torrent ) {
+    mutex_bucket_unlock_by_hash( *ws->hash, 0 );
+    return 0;
+  }
+
+  if( !exactmatch ) {
+    /* Create a new torrent entry, then */
+    memcpy( torrent->hash, *ws->hash, sizeof(ot_hash) );
+
+    if( !( torrent->peer_list = malloc( sizeof (ot_peerlist) ) ) ) {
+      vector_remove_torrent( torrents_list, torrent );
+      mutex_bucket_unlock_by_hash( *ws->hash, 0 );
+      return 0;
+    }
+
+    byte_zero( torrent->peer_list, sizeof( ot_peerlist ) );
+    delta_torrentcount = 1;
+  } else
+    clean_single_torrent( torrent );
+
+  torrent->peer_list->base = g_now_minutes;
+
+  /* Check for peer in torrent */
+  peer_dest = vector_find_or_insert_peer( &(torrent->peer_list->peers), &ws->peer, &exactmatch );
+  if( !peer_dest ) {
+    mutex_bucket_unlock_by_hash( *ws->hash, delta_torrentcount );
+    return 0;
+  }
+
+  /* Tell peer that it's fresh */
+  OT_PEERTIME( &ws->peer ) = 0;
+
+  /* Sanitize flags: Whoever claims to have completed download, must be a seeder */
+  if( ( OT_PEERFLAG( &ws->peer ) & ( PEER_FLAG_COMPLETED | PEER_FLAG_SEEDING ) ) == PEER_FLAG_COMPLETED )
+    OT_PEERFLAG( &ws->peer ) ^= PEER_FLAG_COMPLETED;
+
+  /* If we hadn't had a match create peer there */
+  if( !exactmatch ) {
+
+#ifdef WANT_SYNC_LIVE
+    if( proto == FLAG_MCA )
+      OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_FROM_SYNC;
+    else
+      livesync_tell( ws );
+#endif
+
+    torrent->peer_list->peer_count++;
+    if( OT_PEERFLAG(&ws->peer) & PEER_FLAG_COMPLETED ) {
+      torrent->peer_list->down_count++;
+      stats_issue_event( EVENT_COMPLETED, 0, (uintptr_t)ws );
+    }
+    if( OT_PEERFLAG(&ws->peer) & PEER_FLAG_SEEDING )
+      torrent->peer_list->seed_count++;
+
+  } else {
+    stats_issue_event( EVENT_RENEW, 0, OT_PEERTIME( peer_dest ) );
+#ifdef WANT_SPOT_WOODPECKER
+    if( ( OT_PEERTIME(peer_dest) > 0 ) && ( OT_PEERTIME(peer_dest) < 20 ) )
+      stats_issue_event( EVENT_WOODPECKER, 0, (uintptr_t)&ws->peer );
+#endif
+#ifdef WANT_SYNC_LIVE
+    /* Won't live sync peers that come back too fast. Only exception:
+       fresh "completed" reports */
+    if( proto != FLAG_MCA ) {
+      if( OT_PEERTIME( peer_dest ) > OT_CLIENT_SYNC_RENEW_BOUNDARY ||
+         ( !(OT_PEERFLAG(peer_dest) & PEER_FLAG_COMPLETED ) && (OT_PEERFLAG(&ws->peer) & PEER_FLAG_COMPLETED ) ) )
+        livesync_tell( ws );
+    }
+#endif
+
+    if(  (OT_PEERFLAG(peer_dest) & PEER_FLAG_SEEDING )   && !(OT_PEERFLAG(&ws->peer) & PEER_FLAG_SEEDING ) )
+      torrent->peer_list->seed_count--;
+    if( !(OT_PEERFLAG(peer_dest) & PEER_FLAG_SEEDING )   &&  (OT_PEERFLAG(&ws->peer) & PEER_FLAG_SEEDING ) )
+      torrent->peer_list->seed_count++;
+    if( !(OT_PEERFLAG(peer_dest) & PEER_FLAG_COMPLETED ) &&  (OT_PEERFLAG(&ws->peer) & PEER_FLAG_COMPLETED ) ) {
+      torrent->peer_list->down_count++;
+      stats_issue_event( EVENT_COMPLETED, 0, (uintptr_t)ws );
+    }
+    if(   OT_PEERFLAG(peer_dest) & PEER_FLAG_COMPLETED )
+      OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_COMPLETED;
+  }
+
+  memcpy( peer_dest, &ws->peer, sizeof(ot_peer) );
+#ifdef WANT_SYNC
+  if( proto == FLAG_MCA ) {
+    mutex_bucket_unlock_by_hash( *ws->hash, delta_torrentcount );
+    return 0;
+  }
+#endif
+
+  ws->reply_size = return_peers_for_torrent( torrent, amount, ws->reply, proto );
+  mutex_bucket_unlock_by_hash( *ws->hash, delta_torrentcount );
+  return ws->reply_size;
+}
+
+static size_t return_peers_all( ot_peerlist *peer_list, char *reply ) {
+  unsigned int bucket, num_buckets = 1;
+  ot_vector  * bucket_list = &peer_list->peers;
+  size_t       result = OT_PEER_COMPARE_SIZE * peer_list->peer_count;
+  char       * r_end = reply + result;
+
+  if( OT_PEERLIST_HASBUCKETS(peer_list) ) {
+    num_buckets = bucket_list->size;
+    bucket_list = (ot_vector *)bucket_list->data;
+  }
+
+  for( bucket = 0; bucket<num_buckets; ++bucket ) {
+    ot_peer * peers = (ot_peer*)bucket_list[bucket].data;
+    size_t    peer_count = bucket_list[bucket].size;
+    while( peer_count-- ) {
+      if( OT_PEERFLAG(peers) & PEER_FLAG_SEEDING ) {
+        r_end-=OT_PEER_COMPARE_SIZE;
+        memcpy(r_end,peers++,OT_PEER_COMPARE_SIZE);      
+      } else {
+        memcpy(reply,peers++,OT_PEER_COMPARE_SIZE);
+        reply+=OT_PEER_COMPARE_SIZE;
+      }
+    }
+  }
+  return result;
+}
+
+static size_t return_peers_selection( ot_peerlist *peer_list, size_t amount, char *reply ) {
+  unsigned int bucket_offset, bucket_index = 0, num_buckets = 1;
+  ot_vector  * bucket_list = &peer_list->peers;
+  unsigned int shifted_pc = peer_list->peer_count;
+  unsigned int shifted_step = 0;
+  unsigned int shift = 0;
+  size_t       result = OT_PEER_COMPARE_SIZE * amount;
+  char       * r_end = reply + result;
+  
+  if( OT_PEERLIST_HASBUCKETS(peer_list) ) {
+    num_buckets = bucket_list->size;
+    bucket_list = (ot_vector *)bucket_list->data;
+  }
+
+  /* Make fixpoint arithmetic as exact as possible */
+#define MAXPRECBIT (1<<(8*sizeof(int)-3))
+  while( !(shifted_pc & MAXPRECBIT ) ) { shifted_pc <<= 1; shift++; }
+  shifted_step = shifted_pc/amount;
+#undef MAXPRECBIT
+
+  /* Initialize somewhere in the middle of peers so that
+   fixpoint's aliasing doesn't alway miss the same peers */
+  bucket_offset = random() % peer_list->peer_count;
+
+  while( amount-- ) {
+    ot_peer * peer;
+
+    /* This is the aliased, non shifted range, next value may fall into */
+    unsigned int diff = ( ( ( amount + 1 ) * shifted_step ) >> shift ) -
+                        ( (   amount       * shifted_step ) >> shift );
+    bucket_offset += 1 + random() % diff;
+
+    while( bucket_offset >= bucket_list[bucket_index].size ) {
+      bucket_offset -= bucket_list[bucket_index].size;
+      bucket_index = ( bucket_index + 1 ) % num_buckets;
+    }
+    peer = ((ot_peer*)bucket_list[bucket_index].data) + bucket_offset;
+    if( OT_PEERFLAG(peer) & PEER_FLAG_SEEDING ) {
+      r_end-=OT_PEER_COMPARE_SIZE;
+      memcpy(r_end,peer,OT_PEER_COMPARE_SIZE);      
+    } else {
+      memcpy(reply,peer,OT_PEER_COMPARE_SIZE);
+      reply+=OT_PEER_COMPARE_SIZE;
+    }
+  }
+  return result;
+}
+
+/* Compiles a list of random peers for a torrent
+   * reply must have enough space to hold 92+6*amount bytes
+   * does not yet check not to return self
+*/
+size_t return_peers_for_torrent( ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto ) {
+  ot_peerlist *peer_list = torrent->peer_list;
+  char        *r = reply;
+
+  if( amount > peer_list->peer_count )
+    amount = peer_list->peer_count;
+
+  if( proto == FLAG_TCP ) {
+    int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
+    r += sprintf( r, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie" PEERS_BENCODED "%zd:", peer_list->seed_count, peer_list->down_count, peer_list->peer_count-peer_list->seed_count, erval, erval/2, OT_PEER_COMPARE_SIZE*amount );
+  } else {
+    *(uint32_t*)(r+0) = htonl( OT_CLIENT_REQUEST_INTERVAL_RANDOM );
+    *(uint32_t*)(r+4) = htonl( peer_list->peer_count - peer_list->seed_count );
+    *(uint32_t*)(r+8) = htonl( peer_list->seed_count );
+    r += 12;
+  }
+
+  if( amount ) {
+    if( amount == peer_list->peer_count )
+      r += return_peers_all( peer_list, r );
+    else
+      r += return_peers_selection( peer_list, amount, r );
+  }
+
+  if( proto == FLAG_TCP )
+    *r++ = 'e';
+
+  return r - reply;
+}
+
+/* Fetches scrape info for a specific torrent */
+size_t return_udp_scrape_for_torrent( ot_hash hash, char *reply ) {
+  int          exactmatch, delta_torrentcount = 0;
+  ot_vector   *torrents_list = mutex_bucket_lock_by_hash( hash );
+  ot_torrent  *torrent = binary_search( hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
+
+  if( !exactmatch ) {
+    memset( reply, 0, 12);
+  } else {
+    uint32_t *r = (uint32_t*) reply;
+
+    if( clean_single_torrent( torrent ) ) {
+      vector_remove_torrent( torrents_list, torrent );
+      memset( reply, 0, 12);
+      delta_torrentcount = -1;
+    } else {
+      r[0] = htonl( torrent->peer_list->seed_count );
+      r[1] = htonl( torrent->peer_list->down_count );
+      r[2] = htonl( torrent->peer_list->peer_count-torrent->peer_list->seed_count );
+    }
+  }
+  mutex_bucket_unlock_by_hash( hash, delta_torrentcount );
+  return 12;
+}
+
+/* Fetches scrape info for a specific torrent */
+size_t return_tcp_scrape_for_torrent( ot_hash *hash_list, int amount, char *reply ) {
+  char *r = reply;
+  int   exactmatch, i;
+
+  r += sprintf( r, "d5:filesd" );
+
+  for( i=0; i<amount; ++i ) {
+    int          delta_torrentcount = 0;
+    ot_hash     *hash = hash_list + i;
+    ot_vector   *torrents_list = mutex_bucket_lock_by_hash( *hash );
+    ot_torrent  *torrent = binary_search( hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
+
+    if( exactmatch ) {
+      if( clean_single_torrent( torrent ) ) {
+        vector_remove_torrent( torrents_list, torrent );
+        delta_torrentcount = -1;
+      } else {
+        *r++='2';*r++='0';*r++=':';
+        memcpy( r, hash, sizeof(ot_hash) ); r+=sizeof(ot_hash);
+        r += sprintf( r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee",
+          torrent->peer_list->seed_count, torrent->peer_list->down_count, torrent->peer_list->peer_count-torrent->peer_list->seed_count );
+      }
+    }
+    mutex_bucket_unlock_by_hash( *hash, delta_torrentcount );
+  }
+
+  *r++ = 'e'; *r++ = 'e';
+  return r - reply;
+}
+
+static ot_peerlist dummy_list;
+size_t remove_peer_from_torrent( PROTO_FLAG proto, struct ot_workstruct *ws ) {
+  int          exactmatch;
+  ot_vector   *torrents_list = mutex_bucket_lock_by_hash( *ws->hash );
+  ot_torrent  *torrent = binary_search( ws->hash, torrents_list->data, torrents_list->size, sizeof( ot_torrent ), OT_HASH_COMPARE_SIZE, &exactmatch );
+  ot_peerlist *peer_list = &dummy_list;
+
+#ifdef WANT_SYNC_LIVE
+  if( proto != FLAG_MCA ) {
+    OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_STOPPED;
+    livesync_tell( ws );
+  }
+#endif
+
+  if( exactmatch ) {
+    peer_list = torrent->peer_list;
+    switch( vector_remove_peer( &peer_list->peers, &ws->peer ) ) {
+      case 2:  peer_list->seed_count--; /* Fall throughs intended */
+      case 1:  peer_list->peer_count--; /* Fall throughs intended */
+      default: break;
+    }
+  }
+
+  if( proto == FLAG_TCP ) {
+    int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
+    ws->reply_size = sprintf( ws->reply, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie" PEERS_BENCODED "0:e", peer_list->seed_count, peer_list->peer_count - peer_list->seed_count, erval, erval / 2 );
+  }
+
+  /* Handle UDP reply */
+  if( proto == FLAG_UDP ) {
+    ((uint32_t*)ws->reply)[2] = htonl( OT_CLIENT_REQUEST_INTERVAL_RANDOM );
+    ((uint32_t*)ws->reply)[3] = htonl( peer_list->peer_count - peer_list->seed_count );
+    ((uint32_t*)ws->reply)[4] = htonl( peer_list->seed_count);
+    ws->reply_size = 20;
+  }
+
+  mutex_bucket_unlock_by_hash( *ws->hash, 0 );
+  return ws->reply_size;
+}
+
+void iterate_all_torrents( int (*for_each)( ot_torrent* torrent, uintptr_t data ), uintptr_t data ) {
+  int bucket;
+  size_t j;
+
+  for( bucket=0; bucket<OT_BUCKET_COUNT; ++bucket ) {
+    ot_vector  *torrents_list = mutex_bucket_lock( bucket );
+    ot_torrent *torrents = (ot_torrent*)(torrents_list->data);
+
+    for( j=0; j<torrents_list->size; ++j )
+      if( for_each( torrents + j, data ) )
+        break;
+
+    mutex_bucket_unlock( bucket, 0 );
+    if( !g_opentracker_running ) return;
+  }
+}
+
+void exerr( char * message ) {
+  fprintf( stderr, "%s\n", message );
+  exit( 111 );
+}
+
+void trackerlogic_init( ) {
+  g_tracker_id = random();
+
+  if( !g_stats_path )
+    g_stats_path = "stats";
+  g_stats_path_len = strlen( g_stats_path );
+
+  /* Initialise background worker threads */
+  mutex_init( );
+  clean_init( );
+  fullscrape_init( );
+  accesslist_init( );
+  livesync_init( );
+  stats_init( );
+}
+
+void trackerlogic_deinit( void ) {
+  int bucket, delta_torrentcount = 0;
+  size_t j;
+
+  /* Free all torrents... */
+  for(bucket=0; bucket<OT_BUCKET_COUNT; ++bucket ) {
+    ot_vector *torrents_list = mutex_bucket_lock( bucket );
+    if( torrents_list->size ) {
+      for( j=0; j<torrents_list->size; ++j ) {
+        ot_torrent *torrent = ((ot_torrent*)(torrents_list->data)) + j;
+        free_peerlist( torrent->peer_list );
+        delta_torrentcount -= 1;
+      }
+      free( torrents_list->data );
+    }
+    mutex_bucket_unlock( bucket, delta_torrentcount );
+  }
+
+  /* Deinitialise background worker threads */
+  stats_deinit( );
+  livesync_deinit( );
+  accesslist_deinit( );
+  fullscrape_deinit( );
+  clean_deinit( );
+  /* Release mutexes */
+  mutex_deinit( );
+}
+
+const char *g_version_trackerlogic_c = "$Source$: $Revision$\n";
diff --git a/trackerlogic.h b/trackerlogic.h
new file mode 100644 (file)
index 0000000..5ae644b
--- /dev/null
@@ -0,0 +1,172 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever.
+
+   $id$ */
+
+#ifndef __OT_TRACKERLOGIC_H__
+#define __OT_TRACKERLOGIC_H__
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+#include <stdint.h>
+
+typedef uint8_t ot_hash[20];
+typedef time_t  ot_time;
+typedef char    ot_ip6[16];
+typedef struct { ot_ip6 address; int bits; }
+                ot_net;
+#ifdef WANT_V6
+#define OT_IP_SIZE 16
+#define PEERS_BENCODED "6:peers6"
+#else
+#define OT_IP_SIZE 4
+#define PEERS_BENCODED "5:peers"
+#endif
+
+/* Some tracker behaviour tunable */
+#define OT_CLIENT_TIMEOUT 30
+#define OT_CLIENT_TIMEOUT_CHECKINTERVAL 10
+#define OT_CLIENT_TIMEOUT_SEND (60*15)
+#define OT_CLIENT_REQUEST_INTERVAL (60*30)
+#define OT_CLIENT_REQUEST_VARIATION (60*6)
+
+#define OT_TORRENT_TIMEOUT_HOURS 24
+#define OT_TORRENT_TIMEOUT      (60*OT_TORRENT_TIMEOUT_HOURS)
+
+#define OT_CLIENT_REQUEST_INTERVAL_RANDOM ( OT_CLIENT_REQUEST_INTERVAL - OT_CLIENT_REQUEST_VARIATION/2 + (int)( random( ) % OT_CLIENT_REQUEST_VARIATION ) )
+
+/* If WANT_MODEST_FULLSCRAPES is on, ip addresses may not
+   fullscrape more frequently than this amount in seconds */
+#define OT_MODEST_PEER_TIMEOUT (60*5)
+
+/* If peers come back before 10 minutes, don't live sync them */
+#define OT_CLIENT_SYNC_RENEW_BOUNDARY 10
+
+/* Number of tracker admin ip addresses allowed */
+#define OT_ADMINIP_MAX 64
+#define OT_MAX_THREADS 16
+
+#define OT_PEER_TIMEOUT 45
+
+/* We maintain a list of 1024 pointers to sorted list of ot_torrent structs
+ Sort key is, of course, its hash */
+#define OT_BUCKET_COUNT_BITS 10
+
+#define OT_BUCKET_COUNT (1<<OT_BUCKET_COUNT_BITS)
+#define OT_BUCKET_COUNT_SHIFT (32-OT_BUCKET_COUNT_BITS)
+
+/* From opentracker.c */
+extern time_t g_now_seconds;
+extern volatile int g_opentracker_running;
+#define       g_now_minutes (g_now_seconds/60)
+
+extern uint32_t g_tracker_id;
+typedef enum { FLAG_TCP, FLAG_UDP, FLAG_MCA, FLAG_SELFPIPE } PROTO_FLAG;
+
+typedef struct {
+  uint8_t data[OT_IP_SIZE+2+2];
+} ot_peer;
+static const uint8_t PEER_FLAG_SEEDING   = 0x80;
+static const uint8_t PEER_FLAG_COMPLETED = 0x40;
+static const uint8_t PEER_FLAG_STOPPED   = 0x20;
+static const uint8_t PEER_FLAG_FROM_SYNC = 0x10;
+static const uint8_t PEER_FLAG_LEECHING  = 0x00;
+
+#ifdef WANT_V6
+#define OT_SETIP(peer,ip)     memcpy((peer),(ip),(OT_IP_SIZE))
+#else
+#define OT_SETIP(peer,ip)     memcpy((peer),(((uint8_t*)ip)+12),(OT_IP_SIZE))
+#endif
+#define OT_SETPORT(peer,port) memcpy(((uint8_t*)(peer))+(OT_IP_SIZE),(port),2)
+#define OT_PEERFLAG(peer)     (((uint8_t*)(peer))[(OT_IP_SIZE)+2])
+#define OT_PEERTIME(peer)     (((uint8_t*)(peer))[(OT_IP_SIZE)+3])
+
+#define OT_HASH_COMPARE_SIZE (sizeof(ot_hash))
+#define OT_PEER_COMPARE_SIZE ((OT_IP_SIZE)+2)
+
+struct ot_peerlist;
+typedef struct ot_peerlist ot_peerlist;
+typedef struct {
+  ot_hash      hash;
+  ot_peerlist *peer_list;
+} ot_torrent;
+
+#include "ot_vector.h"
+
+struct ot_peerlist {
+  ot_time        base;
+  size_t         seed_count;
+  size_t         peer_count;
+  size_t         down_count;
+/* normal peers vector or
+   pointer to ot_vector[32] buckets if data != NULL and space == 0
+*/
+  ot_vector      peers;
+};
+#define OT_PEERLIST_HASBUCKETS(peer_list) ((peer_list)->peers.size > (peer_list)->peers.space)
+
+struct ot_workstruct {
+  /* Thread specific, static */
+  char    *inbuf;
+#define   G_INBUF_SIZE    8192
+  char    *outbuf;
+#define   G_OUTBUF_SIZE   8192
+#ifdef    _DEBUG_HTTPERROR
+  char    *debugbuf;
+#define   G_DEBUGBUF_SIZE 8192
+#endif
+
+  /* The peer currently in the working */
+  ot_peer  peer;
+
+  /* Pointers into the request buffer */
+  ot_hash *hash;
+  char    *peer_id;
+
+  /* HTTP specific, non static */
+  int      keep_alive;
+  char    *request;
+  ssize_t  request_size;
+  ssize_t  header_size;
+  char    *reply;
+  ssize_t  reply_size;
+};
+
+/*
+   Exported functions
+*/
+
+#ifdef WANT_SYNC_LIVE
+#define WANT_SYNC
+#endif
+
+#ifdef WANT_SYNC
+#define WANT_SYNC_PARAM( param ) , param
+#else
+#define WANT_SYNC_PARAM( param )
+#endif
+
+#ifdef WANT_LOG_NETWORKS
+#error Live logging networks disabled at the moment.
+#endif
+
+void trackerlogic_init( );
+void trackerlogic_deinit( void );
+void exerr( char * message );
+
+/* add_peer_to_torrent does only release the torrent bucket if from_sync is set,
+   otherwise it is released in return_peers_for_torrent */
+size_t  add_peer_to_torrent_and_return_peers( PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount );
+size_t  remove_peer_from_torrent( PROTO_FLAG proto, struct ot_workstruct *ws );
+size_t  return_tcp_scrape_for_torrent( ot_hash *hash, int amount, char *reply );
+size_t  return_udp_scrape_for_torrent( ot_hash hash, char *reply );
+void    add_torrent_from_saved_state( ot_hash hash, ot_time base, size_t down_count );
+
+/* torrent iterator */
+void iterate_all_torrents( int (*for_each)( ot_torrent* torrent, uintptr_t data ), uintptr_t data );
+
+/* Helper, before it moves to its own object */
+void free_peerlist( ot_peerlist *peer_list );
+
+#endif