From 7d9a0b7bb11ca99513cb48a6611d7121ecc35868 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Sun, 25 Oct 2026 19:13:36 +0100 Subject: Split options parsing and config code from opentracker.c, it has grown humongous --- Makefile | 4 +- opentracker.c | 543 ++----------------------------------------------------- ot_config.c | 565 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ot_config.h | 35 ++++ 4 files changed, 617 insertions(+), 530 deletions(-) create mode 100644 ot_config.c create mode 100644 ot_config.h diff --git a/Makefile b/Makefile index 9ba0e2d..55be5ba 100644 --- a/Makefile +++ b/Makefile @@ -68,8 +68,8 @@ CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread BINARY =opentracker -HEADERS=trackerlogic.h scan_urlencoded_query.h ot_buf.h ot_hex.h ot_ip.h ot_scan.h ot_socket.h ot_mutex.h ot_random.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_buf.c ot_hex.c ot_ip.c ot_scan.c ot_socket.c ot_mutex.c ot_random.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 +HEADERS=trackerlogic.h scan_urlencoded_query.h ot_buf.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.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_buf.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.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 ot_random.c ot_scan.c ot_ip.c ot_socket.c OBJECTS = $(SOURCES:%.c=%.o) diff --git a/opentracker.c b/opentracker.c index 1b0e119..698669c 100644 --- a/opentracker.c +++ b/opentracker.c @@ -27,6 +27,7 @@ /* Opentracker */ #include "ot_accesslist.h" +#include "ot_config.h" #include "ot_hex.h" #include "ot_http.h" #include "ot_ip.h" @@ -41,29 +42,10 @@ /* 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; - -typedef struct { - ot_ip6 ip; - uint16_t port; - PROTO_FLAG proto; - int shared; - int workers; - int64_t sock; -} ot_listen; -#define OT_LISTEN_INHERIT (-1) - -/* Config entries collected from command line and config files */ -static ot_listen *g_listen; -static size_t g_listen_count; -static int g_listen_shared; -static int g_listen_workers; static void panic(const char *routine) __attribute__((noreturn)); static void panic(const char *routine) { @@ -115,42 +97,6 @@ static void install_signal_handlers(void) { pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); } -static void usage(char *name) { - fprintf(stderr, - "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]" -#ifdef WANT_ACCESSLIST_BLACK - " [-b blacklistfile]" -#elif defined(WANT_ACCESSLIST_WHITE) - " [-w whitelistfile]" -#endif - "\n", - name); -} - -#define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc) -static void help(char *name) { - usage(name); - - HELPLINE("-f config", "include and execute the config file"); - HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)"); - HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)"); - HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)"); - HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)"); - HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")"); - HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")"); - HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)"); -#ifdef WANT_ACCESSLIST_BLACK - HELPLINE("-b file", "specify blacklist file."); -#elif defined(WANT_ACCESSLIST_WHITE) - HELPLINE("-w file", "specify whitelist file."); -#endif - - fprintf(stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n"); - fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n"); - fprintf(stderr, " If no port is bound from config file or command line, the last address given\n"); - fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n"); -} -#undef HELPLINE static ssize_t header_complete(char *request, ssize_t byte_count) { ssize_t i = 0, state = 0; @@ -353,91 +299,9 @@ static void *server_mainloop(void *args) { return 0; } -static void listen_append(const ot_ip6 ip, uint16_t port, PROTO_FLAG proto, int shared, int workers) { - ot_listen *grown = realloc(g_listen, (g_listen_count + 1) * sizeof(ot_listen)); - ot_listen *entry; - - if (!grown) - panic("listen_append"); - g_listen = grown; - - entry = g_listen + g_listen_count++; - memcpy(entry->ip, ip, sizeof(ot_ip6)); - entry->port = port; - entry->proto = proto; - entry->shared = shared; - entry->workers = workers; - entry->sock = -1; -} - -typedef enum { LISTEN_OPTION_UNKNOWN, LISTEN_OPTION_SHARED, LISTEN_OPTION_WORKERS } listen_option_kind; - -typedef struct { - const char *name; - size_t len; - listen_option_kind kind; - int takes_value; -} listen_option_key; - -#define LISTEN_OPTION_KEY(s, k, v) {s, sizeof(s) - 1, k, v} - -static const listen_option_key listen_option_keys[] = { - LISTEN_OPTION_KEY("shared", LISTEN_OPTION_SHARED, 0), - LISTEN_OPTION_KEY("workers", LISTEN_OPTION_WORKERS, 1), -}; -static const size_t listen_option_count = sizeof(listen_option_keys) / sizeof(listen_option_keys[0]); - -static listen_option_kind parse_listen_option(char **p) { - size_t i; - - for (i = 0; i < listen_option_count; ++i) { - const listen_option_key *key = &listen_option_keys[i]; - char delimiter = (*p)[key->len]; - - if (strncmp(*p, key->name, key->len)) - continue; - if (key->takes_value ? delimiter != '=' : (delimiter && !isspace((unsigned char)delimiter))) - continue; - - *p += key->len + (key->takes_value ? 1 : 0); - return key->kind; - } - return LISTEN_OPTION_UNKNOWN; -} - -/* Options trailing an address in a listen statement, in any order */ -static int listen_scan_options(char *p, int *shared, int *workers) { - uint64_t value; - - for (;;) { - while (isspace((unsigned char)*p)) - ++p; - if (!*p) - return 1; - - switch (parse_listen_option(&p)) { - case LISTEN_OPTION_SHARED: - *shared = 1; - break; - case LISTEN_OPTION_WORKERS: { - size_t off = scan_udec(p, &value, UINT_MAX); - - if (!off) - return 0; - *workers = (int)value; - p += off; - break; - } - default: - return 0; - } - } -} - -static int listen_option(int value, int fallback) { return value == OT_LISTEN_INHERIT ? fallback : value; } static void listen_bind_one(ot_listen *entry) { - socket_sharing sharing = listen_option(entry->shared, g_listen_shared) ? SOCKET_SHARED : SOCKET_EXCLUSIVE; + socket_sharing sharing = entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; #ifdef _DEBUG { @@ -482,305 +346,31 @@ static void listen_bind_one(ot_listen *entry) { /* Bound in the order the entries were given. Note: On macOS SO_REUSEPORT delivers everything to the socket that bound last. */ static void listen_bind_all(void) { - size_t i; + size_t count, i; + ot_listen *entries = config_listen_entries(&count); - for (i = 0; i < g_listen_count; ++i) - listen_bind_one(g_listen + i); + for (i = 0; i < count; ++i) + listen_bind_one(entries + i); } static void listen_start_udp_workers(void) { - size_t i; + size_t count, i; + ot_listen *entries = config_listen_entries(&count); udp_init(); - for (i = 0; i < g_listen_count; ++i) { - ot_listen *entry = g_listen + i; - int workers = listen_option(entry->workers, g_listen_workers); + for (i = 0; i < count; ++i) { + ot_listen *entry = entries + i; - if (entry->proto != FLAG_UDP || workers <= 0) + if (entry->proto != FLAG_UDP || entry->workers <= 0) continue; io_block(entry->sock); - udp_workers_install(entry->sock, (unsigned int)workers); + udp_workers_install(entry->sock, (unsigned int)entry->workers); } } -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_t *port) { - const char *s = src; - uint64_t value; - int off, bracket = 0; - while (isspace(*s)) - ++s; - if (*s == '[') - ++s, ++bracket; /* for v6 style notation */ - if (!(off = ip_scan(s, ip))) - return 0; - s += off; - if (bracket && *s == ']') - ++s; - if (*s == 0 || isspace(*s)) - return s - src; - if (!ip_is_v4mapped(ip)) { - if (*s != ':' && *s != '.') - return 0; - if (!bracket && *(s) == ':') - return 0; - s++; - } else { - if (*(s++) != ':') - return 0; - } - if (!(off = scan_udec(s, &value, 65535))) - return 0; - *port = (uint16_t)value; - return off + s - src; -} - -static int scan_ip6_net(const char *src, ot_net *net) { - const char *s = src; - uint64_t value; - int off; - while (isspace(*s)) - ++s; - if (!(off = ip_scan(s, net->address))) - return 0; - s += off; - if (*s != '/') - net->bits = 128; - else { - s++; - if (!(off = scan_udec(s, &value, 128))) - return 0; - net->bits = (int)value; - if (ip_is_v4mapped(net->address)) - net->bits += 96; - if (net->bits > 128) - return 0; - s += off; - } - return off + s - src; -} - -typedef enum { - CONFIG_UNKNOWN, - CONFIG_TRACKER_ROOTDIR, - CONFIG_TRACKER_USER, - CONFIG_LISTEN_TCP_UDP, - CONFIG_LISTEN_TCP, - CONFIG_LISTEN_UDP, - CONFIG_LISTEN_UDP_WORKERS, - CONFIG_LISTEN_SHARED, - CONFIG_ACCESS_WHITELIST, - CONFIG_ACCESS_BLACKLIST, - CONFIG_ACCESS_FIFO_ADD, - CONFIG_ACCESS_FIFO_DELETE, - CONFIG_ACCESS_STATS, - CONFIG_ACCESS_STATS_PATH, - CONFIG_ACCESS_PROXY, - CONFIG_TRACKER_REDIRECT_URL, - CONFIG_LIVESYNC_CLUSTER_NODE_IP, - CONFIG_LIVESYNC_CLUSTER_LISTEN, -} config_kind; - -typedef struct { - const char *name; - size_t len; - config_kind kind; -} config_key; - -#define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k } - -static const config_key config_keys[] = { - CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR), - CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER), - CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP), - CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), - CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), - CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), - CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED), -#ifdef WANT_ACCESSLIST_WHITE - CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), -#elif defined(WANT_ACCESSLIST_BLACK) - CONFIG_KEY("access.blacklist", CONFIG_ACCESS_BLACKLIST), -#endif -#ifdef WANT_DYNAMIC_ACCESSLIST - CONFIG_KEY("access.fifo_add", CONFIG_ACCESS_FIFO_ADD), - CONFIG_KEY("access.fifo_delete", CONFIG_ACCESS_FIFO_DELETE), -#endif -#ifdef WANT_RESTRICT_STATS - CONFIG_KEY("access.stats", CONFIG_ACCESS_STATS), -#endif - CONFIG_KEY("access.stats_path", CONFIG_ACCESS_STATS_PATH), -#ifdef WANT_IP_FROM_PROXY - CONFIG_KEY("access.proxy", CONFIG_ACCESS_PROXY), -#endif - CONFIG_KEY("tracker.redirect_url", CONFIG_TRACKER_REDIRECT_URL), -#ifdef WANT_SYNC_LIVE - CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP), - CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN), -#endif -}; -static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]); - -static config_kind parse_config_key(char **p) { - size_t i; - for (i = 0; i < config_entry_count; ++i) { - const config_key *key = &config_keys[i]; - if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) { - *p += key->len + 1; - /* Skip white spaces */ - while (isspace((unsigned char)**p)) - ++(*p); - return key->kind; - } - } - return CONFIG_UNKNOWN; -} - -int parse_configfile(char *config_filename) { - FILE *config_file_handle; - char inbuf[512]; - ot_ip6 tmpip; -#if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE) - ot_net tmpnet; -#endif - int bound = 0; - - config_file_handle = fopen(config_filename, "r"); - - if (config_file_handle == NULL) { - fprintf(stderr, "Warning: Can't open config file: %s.", config_filename); - return 0; - } - - while (fgets(inbuf, sizeof(inbuf), config_file_handle)) { - char *p = inbuf, *end; - uint64_t value; - uint16_t tmpport = 6969; - config_kind kind; - - /* Skip white spaces */ - while (isspace((unsigned char)*p)) - ++p; - - /* Ignore comments and empty lines */ - if ((*p == '#') || (*p == '\n') || (*p == 0)) - continue; - - /* consume trailing new lines and spaces */ - end = p + strlen(p); - while (end > p && isspace((unsigned char)end[-1])) - *--end = 0; - - /* Scan for commands */ - kind = parse_config_key(&p); - switch (kind) { - case CONFIG_TRACKER_ROOTDIR: - set_config_option(&g_serverdir, p); - break; - case CONFIG_TRACKER_USER: - set_config_option(&g_serveruser, p); - break; - case CONFIG_LISTEN_TCP_UDP: - case CONFIG_LISTEN_TCP: - case CONFIG_LISTEN_UDP: { - int shared = OT_LISTEN_INHERIT; - int workers = OT_LISTEN_INHERIT; - int off = scan_ip6_port(p, tmpip, &tmpport); - - if (!off || !listen_scan_options(p + off, &shared, &workers)) - goto parse_error; - if (kind != CONFIG_LISTEN_UDP) - listen_append(tmpip, tmpport, FLAG_TCP, shared, OT_LISTEN_INHERIT), ++bound; - if (kind != CONFIG_LISTEN_TCP) - listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound; - break; - } - case CONFIG_LISTEN_UDP_WORKERS: - if (!scan_udec(p, &value, UINT_MAX)) - goto parse_error; - g_listen_workers = (int)value; - break; - case CONFIG_LISTEN_SHARED: - if (!scan_udec(p, &value, 1)) - goto parse_error; - g_listen_shared = (int)value; - break; -#ifdef WANT_ACCESSLIST_WHITE - case CONFIG_ACCESS_WHITELIST: - set_config_option(&g_accesslist_filename, p); - break; -#elif defined(WANT_ACCESSLIST_BLACK) - case CONFIG_ACCESS_BLACKLIST: - set_config_option(&g_accesslist_filename, p); - break; -#endif -#ifdef WANT_DYNAMIC_ACCESSLIST - case CONFIG_ACCESS_FIFO_ADD: - set_config_option(&g_accesslist_pipe_add, p); - break; - case CONFIG_ACCESS_FIFO_DELETE: - set_config_option(&g_accesslist_pipe_delete, p); - break; -#endif -#ifdef WANT_RESTRICT_STATS - case CONFIG_ACCESS_STATS: - if (!scan_ip6_net(p, &tmpnet)) - goto parse_error; - accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_STAT); - break; -#endif - case CONFIG_ACCESS_STATS_PATH: - set_config_option(&g_stats_path, p); - break; -#ifdef WANT_IP_FROM_PROXY - case CONFIG_ACCESS_PROXY: - if (!scan_ip6_net(p, &tmpnet)) - goto parse_error; - accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_PROXY); - break; -#endif - case CONFIG_TRACKER_REDIRECT_URL: - set_config_option(&g_redirecturl, p); - break; -#ifdef WANT_SYNC_LIVE - case CONFIG_LIVESYNC_CLUSTER_NODE_IP: - if (!scan_ip6_net(p, &tmpnet)) - goto parse_error; - accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_LIVESYNC); - break; - case CONFIG_LIVESYNC_CLUSTER_LISTEN: - tmpport = LIVESYNC_PORT; - { - int shared = OT_LISTEN_INHERIT, dummy = OT_LISTEN_INHERIT; - int off = scan_ip6_port(p, tmpip, &tmpport); - - if (!off || !listen_scan_options(p + off, &shared, &dummy)) - goto parse_error; - listen_append(tmpip, tmpport, FLAG_MCA, shared, OT_LISTEN_INHERIT); - } - break; -#endif - default: - fprintf(stderr, "Unhandled line in config file: %s\n", inbuf); - break; - parse_error: - fprintf(stderr, "Parse error in config file: %s\n", inbuf); - } - } - fclose(config_file_handle); - return bound; -} void load_state(const char *const state_filename) { FILE *state_filehandle; @@ -873,114 +463,11 @@ static void *time_caching_worker(void *args) { } int main(int argc, char **argv) { - ot_ip6 serverip; - ot_net tmpnet; - int bound = 0, scanon = 1; - uint64_t value; - char *statefile = 0; pthread_t thread_id; /* time cacher */ - memset(serverip, 0, sizeof(ot_ip6)); -#ifdef WANT_V4_ONLY - serverip[10] = serverip[11] = -1; -#endif - random_init(); + config_parse(argc, argv); - 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 (!ip_scan(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_udec(optarg, &value, 65535)) { - usage(argv[0]); - exit(1); - } - listen_append(serverip, (uint16_t)value, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); - bound++; - break; - case 'P': - if (!scan_udec(optarg, &value, 65535)) { - usage(argv[0]); - exit(1); - } - listen_append(serverip, (uint16_t)value, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); - bound++; - break; -#ifdef WANT_SYNC_LIVE - case 's': - if (!scan_udec(optarg, &value, 65535)) { - usage(argv[0]); - exit(1); - } - listen_append(serverip, (uint16_t)value, FLAG_MCA, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); - break; -#endif - case 'd': - set_config_option(&g_serverdir, optarg); - break; - case 'u': - set_config_option(&g_serveruser, optarg); - break; - case 'r': - set_config_option(&g_redirecturl, optarg); - break; - case 'l': - statefile = optarg; - break; - case 'A': - if (!scan_ip6_net(optarg, &tmpnet)) { - usage(argv[0]); - exit(1); - } - accesslist_bless_net(&tmpnet, 0xffff); /* Allow everything for now */ - break; - case 'f': - bound += parse_configfile(optarg); - break; - case 'h': - help(argv[0]); - exit(0); - case 'v': { - char buffer[8192]; - stats_return_tracker_version(buffer); - fputs(buffer, stderr); - exit(0); - } - default: - case '?': - usage(argv[0]); - exit(1); - } - } - - if (!bound) { - listen_append(serverip, 6969, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); - listen_append(serverip, 6969, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); - } listen_bind_all(); @@ -1017,8 +504,8 @@ int main(int argc, char **argv) { fprintf(stderr, "... done.\n"); #endif - if (statefile) - load_state(statefile); + if (g_statefile) + load_state(g_statefile); install_signal_handlers(); diff --git a/ot_config.c b/ot_config.c new file mode 100644 index 0000000..43c07c5 --- /dev/null +++ b/ot_config.c @@ -0,0 +1,565 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +/* System */ +#include +#include +#include +#include +#include +#include +#include + +/* Opentracker */ +#include "ot_accesslist.h" +#include "ot_config.h" +#include "ot_http.h" +#include "ot_ip.h" +#include "ot_livesync.h" +#include "ot_scan.h" +#include "ot_stats.h" +#include "trackerlogic.h" + +char *g_redirecturl; +char *g_serverdir; +char *g_serveruser; +char *g_statefile; + +#define OT_LISTEN_INHERIT (-1) + +static ot_listen *g_listen; +static size_t g_listen_count; +static int g_listen_shared; +static int g_listen_workers; + +static void panic(const char *routine) __attribute__((noreturn)); +static void panic(const char *routine) { + fprintf(stderr, "%s: %s\n", routine, strerror(errno)); + exit(111); +} + +static void listen_append(const ot_ip6 ip, uint16_t port, PROTO_FLAG proto, int shared, int workers) { + ot_listen *grown = realloc(g_listen, (g_listen_count + 1) * sizeof(ot_listen)); + ot_listen *entry; + + if (!grown) + panic("listen_append"); + g_listen = grown; + + entry = g_listen + g_listen_count++; + memcpy(entry->ip, ip, sizeof(ot_ip6)); + entry->port = port; + entry->proto = proto; + entry->shared = shared; + entry->workers = workers; + entry->sock = -1; +} + +typedef enum { LISTEN_OPTION_UNKNOWN, LISTEN_OPTION_SHARED, LISTEN_OPTION_WORKERS } listen_option_kind; + +typedef struct { + const char *name; + size_t len; + listen_option_kind kind; + int takes_value; +} listen_option_key; + +#define LISTEN_OPTION_KEY(s, k, v) {s, sizeof(s) - 1, k, v} + +static const listen_option_key listen_option_keys[] = { + LISTEN_OPTION_KEY("shared", LISTEN_OPTION_SHARED, 0), + LISTEN_OPTION_KEY("workers", LISTEN_OPTION_WORKERS, 1), +}; +static const size_t listen_option_count = sizeof(listen_option_keys) / sizeof(listen_option_keys[0]); + +static listen_option_kind parse_listen_option(char **p) { + size_t i; + + for (i = 0; i < listen_option_count; ++i) { + const listen_option_key *key = &listen_option_keys[i]; + char delimiter = (*p)[key->len]; + + if (strncmp(*p, key->name, key->len)) + continue; + if (key->takes_value ? delimiter != '=' : (delimiter && !isspace((unsigned char)delimiter))) + continue; + + *p += key->len + (key->takes_value ? 1 : 0); + return key->kind; + } + return LISTEN_OPTION_UNKNOWN; +} + +/* Options trailing an address in a listen statement, in any order */ +static int listen_scan_options(char *p, int *shared, int *workers) { + uint64_t value; + + for (;;) { + while (isspace((unsigned char)*p)) + ++p; + if (!*p) + return 1; + + switch (parse_listen_option(&p)) { + case LISTEN_OPTION_SHARED: + *shared = 1; + break; + case LISTEN_OPTION_WORKERS: { + size_t off = scan_udec(p, &value, UINT_MAX); + + if (!off) + return 0; + *workers = (int)value; + p += off; + break; + } + default: + return 0; + } + } +} + + +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_t *port) { + const char *s = src; + uint64_t value; + int off, bracket = 0; + while (isspace(*s)) + ++s; + if (*s == '[') + ++s, ++bracket; /* for v6 style notation */ + if (!(off = ip_scan(s, ip))) + return 0; + s += off; + if (bracket && *s == ']') + ++s; + if (*s == 0 || isspace(*s)) + return s - src; + if (!ip_is_v4mapped(ip)) { + if (*s != ':' && *s != '.') + return 0; + if (!bracket && *(s) == ':') + return 0; + s++; + } else { + if (*(s++) != ':') + return 0; + } + if (!(off = scan_udec(s, &value, 65535))) + return 0; + *port = (uint16_t)value; + return off + s - src; +} + +static int scan_ip6_net(const char *src, ot_net *net) { + const char *s = src; + uint64_t value; + int off; + while (isspace(*s)) + ++s; + if (!(off = ip_scan(s, net->address))) + return 0; + s += off; + if (*s != '/') + net->bits = 128; + else { + s++; + if (!(off = scan_udec(s, &value, 128))) + return 0; + net->bits = (int)value; + if (ip_is_v4mapped(net->address)) + net->bits += 96; + if (net->bits > 128) + return 0; + s += off; + } + return off + s - src; +} + +typedef enum { + CONFIG_UNKNOWN, + CONFIG_TRACKER_ROOTDIR, + CONFIG_TRACKER_USER, + CONFIG_LISTEN_TCP_UDP, + CONFIG_LISTEN_TCP, + CONFIG_LISTEN_UDP, + CONFIG_LISTEN_UDP_WORKERS, + CONFIG_LISTEN_SHARED, + CONFIG_ACCESS_WHITELIST, + CONFIG_ACCESS_BLACKLIST, + CONFIG_ACCESS_FIFO_ADD, + CONFIG_ACCESS_FIFO_DELETE, + CONFIG_ACCESS_STATS, + CONFIG_ACCESS_STATS_PATH, + CONFIG_ACCESS_PROXY, + CONFIG_TRACKER_REDIRECT_URL, + CONFIG_LIVESYNC_CLUSTER_NODE_IP, + CONFIG_LIVESYNC_CLUSTER_LISTEN, +} config_kind; + +typedef struct { + const char *name; + size_t len; + config_kind kind; +} config_key; + +#define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k } + +static const config_key config_keys[] = { + CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR), + CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER), + CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP), + CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), + CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), + CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), + CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED), +#ifdef WANT_ACCESSLIST_WHITE + CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), +#elif defined(WANT_ACCESSLIST_BLACK) + CONFIG_KEY("access.blacklist", CONFIG_ACCESS_BLACKLIST), +#endif +#ifdef WANT_DYNAMIC_ACCESSLIST + CONFIG_KEY("access.fifo_add", CONFIG_ACCESS_FIFO_ADD), + CONFIG_KEY("access.fifo_delete", CONFIG_ACCESS_FIFO_DELETE), +#endif +#ifdef WANT_RESTRICT_STATS + CONFIG_KEY("access.stats", CONFIG_ACCESS_STATS), +#endif + CONFIG_KEY("access.stats_path", CONFIG_ACCESS_STATS_PATH), +#ifdef WANT_IP_FROM_PROXY + CONFIG_KEY("access.proxy", CONFIG_ACCESS_PROXY), +#endif + CONFIG_KEY("tracker.redirect_url", CONFIG_TRACKER_REDIRECT_URL), +#ifdef WANT_SYNC_LIVE + CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP), + CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN), +#endif +}; +static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]); + +static config_kind parse_config_key(char **p) { + size_t i; + for (i = 0; i < config_entry_count; ++i) { + const config_key *key = &config_keys[i]; + if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) { + *p += key->len + 1; + /* Skip white spaces */ + while (isspace((unsigned char)**p)) + ++(*p); + return key->kind; + } + } + return CONFIG_UNKNOWN; +} + +int parse_configfile(char *config_filename) { + FILE *config_file_handle; + char inbuf[512]; + ot_ip6 tmpip; +#if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE) + ot_net tmpnet; +#endif + int bound = 0; + + config_file_handle = fopen(config_filename, "r"); + + if (config_file_handle == NULL) { + fprintf(stderr, "Warning: Can't open config file: %s.", config_filename); + return 0; + } + + while (fgets(inbuf, sizeof(inbuf), config_file_handle)) { + char *p = inbuf, *end; + uint64_t value; + uint16_t tmpport = 6969; + config_kind kind; + + /* Skip white spaces */ + while (isspace((unsigned char)*p)) + ++p; + + /* Ignore comments and empty lines */ + if ((*p == '#') || (*p == '\n') || (*p == 0)) + continue; + + /* consume trailing new lines and spaces */ + end = p + strlen(p); + while (end > p && isspace((unsigned char)end[-1])) + *--end = 0; + + /* Scan for commands */ + kind = parse_config_key(&p); + switch (kind) { + case CONFIG_TRACKER_ROOTDIR: + set_config_option(&g_serverdir, p); + break; + case CONFIG_TRACKER_USER: + set_config_option(&g_serveruser, p); + break; + case CONFIG_LISTEN_TCP_UDP: + case CONFIG_LISTEN_TCP: + case CONFIG_LISTEN_UDP: { + int shared = OT_LISTEN_INHERIT; + int workers = OT_LISTEN_INHERIT; + int off = scan_ip6_port(p, tmpip, &tmpport); + + if (!off || !listen_scan_options(p + off, &shared, &workers)) + goto parse_error; + if (kind != CONFIG_LISTEN_UDP) + listen_append(tmpip, tmpport, FLAG_TCP, shared, OT_LISTEN_INHERIT), ++bound; + if (kind != CONFIG_LISTEN_TCP) + listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound; + break; + } + case CONFIG_LISTEN_UDP_WORKERS: + if (!scan_udec(p, &value, UINT_MAX)) + goto parse_error; + g_listen_workers = (int)value; + break; + case CONFIG_LISTEN_SHARED: + if (!scan_udec(p, &value, 1)) + goto parse_error; + g_listen_shared = (int)value; + break; +#ifdef WANT_ACCESSLIST_WHITE + case CONFIG_ACCESS_WHITELIST: + set_config_option(&g_accesslist_filename, p); + break; +#elif defined(WANT_ACCESSLIST_BLACK) + case CONFIG_ACCESS_BLACKLIST: + set_config_option(&g_accesslist_filename, p); + break; +#endif +#ifdef WANT_DYNAMIC_ACCESSLIST + case CONFIG_ACCESS_FIFO_ADD: + set_config_option(&g_accesslist_pipe_add, p); + break; + case CONFIG_ACCESS_FIFO_DELETE: + set_config_option(&g_accesslist_pipe_delete, p); + break; +#endif +#ifdef WANT_RESTRICT_STATS + case CONFIG_ACCESS_STATS: + if (!scan_ip6_net(p, &tmpnet)) + goto parse_error; + accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_STAT); + break; +#endif + case CONFIG_ACCESS_STATS_PATH: + set_config_option(&g_stats_path, p); + break; +#ifdef WANT_IP_FROM_PROXY + case CONFIG_ACCESS_PROXY: + if (!scan_ip6_net(p, &tmpnet)) + goto parse_error; + accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_PROXY); + break; +#endif + case CONFIG_TRACKER_REDIRECT_URL: + set_config_option(&g_redirecturl, p); + break; +#ifdef WANT_SYNC_LIVE + case CONFIG_LIVESYNC_CLUSTER_NODE_IP: + if (!scan_ip6_net(p, &tmpnet)) + goto parse_error; + accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_LIVESYNC); + break; + case CONFIG_LIVESYNC_CLUSTER_LISTEN: + tmpport = LIVESYNC_PORT; + { + int shared = OT_LISTEN_INHERIT, dummy = OT_LISTEN_INHERIT; + int off = scan_ip6_port(p, tmpip, &tmpport); + + if (!off || !listen_scan_options(p + off, &shared, &dummy)) + goto parse_error; + listen_append(tmpip, tmpport, FLAG_MCA, shared, OT_LISTEN_INHERIT); + } + break; +#endif + default: + fprintf(stderr, "Unhandled line in config file: %s\n", inbuf); + break; + parse_error: + fprintf(stderr, "Parse error in config file: %s\n", inbuf); + } + } + fclose(config_file_handle); + return bound; +} + +static void usage(char *name) { + fprintf(stderr, + "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]" +#ifdef WANT_ACCESSLIST_BLACK + " [-b blacklistfile]" +#elif defined(WANT_ACCESSLIST_WHITE) + " [-w whitelistfile]" +#endif + "\n", + name); +} + +#define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc) +static void help(char *name) { + usage(name); + + HELPLINE("-f config", "include and execute the config file"); + HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)"); + HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)"); + HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)"); + HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)"); + HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")"); + HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")"); + HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)"); +#ifdef WANT_ACCESSLIST_BLACK + HELPLINE("-b file", "specify blacklist file."); +#elif defined(WANT_ACCESSLIST_WHITE) + HELPLINE("-w file", "specify whitelist file."); +#endif + + fprintf(stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n"); + fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n"); + fprintf(stderr, " If no port is bound from config file or command line, the last address given\n"); + fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n"); +} +#undef HELPLINE + + +static void listen_resolve_defaults(void) { + size_t i; + + for (i = 0; i < g_listen_count; ++i) { + ot_listen *entry = g_listen + i; + + if (entry->shared == OT_LISTEN_INHERIT) + entry->shared = g_listen_shared; + if (entry->workers == OT_LISTEN_INHERIT) + entry->workers = g_listen_workers; + } +} + +ot_listen *config_listen_entries(size_t *count) { + *count = g_listen_count; + return g_listen; +} + +void config_parse(int argc, char **argv) { + ot_ip6 serverip; + ot_net tmpnet; + int bound = 0, scanon = 1; + uint64_t value; + + memset(serverip, 0, sizeof(ot_ip6)); +#ifdef WANT_V4_ONLY + serverip[10] = serverip[11] = -1; +#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 (!ip_scan(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_udec(optarg, &value, 65535)) { + usage(argv[0]); + exit(1); + } + listen_append(serverip, (uint16_t)value, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); + bound++; + break; + case 'P': + if (!scan_udec(optarg, &value, 65535)) { + usage(argv[0]); + exit(1); + } + listen_append(serverip, (uint16_t)value, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); + bound++; + break; +#ifdef WANT_SYNC_LIVE + case 's': + if (!scan_udec(optarg, &value, 65535)) { + usage(argv[0]); + exit(1); + } + listen_append(serverip, (uint16_t)value, FLAG_MCA, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); + 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': + g_statefile = optarg; + break; + case 'A': + if (!scan_ip6_net(optarg, &tmpnet)) { + usage(argv[0]); + exit(1); + } + accesslist_bless_net(&tmpnet, 0xffff); /* Allow everything for now */ + break; + case 'f': + bound += parse_configfile(optarg); + break; + case 'h': + help(argv[0]); + exit(0); + case 'v': { + char buffer[8192]; + stats_return_tracker_version(buffer); + fputs(buffer, stderr); + exit(0); + } + default: + case '?': + usage(argv[0]); + exit(1); + } + } + + if (!bound) { + listen_append(serverip, 6969, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); + listen_append(serverip, 6969, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); + } + + listen_resolve_defaults(); +} diff --git a/ot_config.h b/ot_config.h new file mode 100644 index 0000000..ab25e2c --- /dev/null +++ b/ot_config.h @@ -0,0 +1,35 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +#ifndef OT_CONFIG_H__ +#define OT_CONFIG_H__ + +#include + +#include "trackerlogic.h" + +/* An address opentracker was asked to listen on. Options the statement did + not name have been filled in from the file wide defaults by the time these + are handed out. sock is filled in by whoever binds them. */ +typedef struct { + ot_ip6 ip; + uint16_t port; + PROTO_FLAG proto; + int shared; + int workers; + int64_t sock; +} ot_listen; + +extern char *g_redirecturl; +extern char *g_serverdir; +extern char *g_serveruser; +extern char *g_statefile; + +/* Reads the command line and every config file it names. Exits on error. */ +void config_parse(int argc, char **argv); + +ot_listen *config_listen_entries(size_t *count); + +#endif -- cgit v1.3