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 --- ot_config.c | 565 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 565 insertions(+) create mode 100644 ot_config.c (limited to 'ot_config.c') 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(); +} -- cgit v1.3