/* 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" const char *g_redirecturl; const char *g_serverdir; const char *g_serveruser; const 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; } } } static const char *set_config_option(const char **option, char *value) { #ifdef _DEBUG fprintf(stderr, "Setting config option: %s\n", value); #endif while (isspace(*value)) ++value; free((char *)*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; } static 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(); }