summaryrefslogtreecommitdiff
path: root/ot_config.c
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-25 19:13:36 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-10-25 19:13:36 +0100
commit7d9a0b7bb11ca99513cb48a6611d7121ecc35868 (patch)
treedd8b0df7df4ac80152406005c5a97e2b4acedcbd /ot_config.c
parent28f43d02149c96b87555e08f79e0c33c7a07e1af (diff)
Split options parsing and config code from opentracker.c, it has grown humongous
Diffstat (limited to 'ot_config.c')
-rw-r--r--ot_config.c565
1 files changed, 565 insertions, 0 deletions
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 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <ctype.h>
8#include <errno.h>
9#include <limits.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <unistd.h>
14
15/* Opentracker */
16#include "ot_accesslist.h"
17#include "ot_config.h"
18#include "ot_http.h"
19#include "ot_ip.h"
20#include "ot_livesync.h"
21#include "ot_scan.h"
22#include "ot_stats.h"
23#include "trackerlogic.h"
24
25char *g_redirecturl;
26char *g_serverdir;
27char *g_serveruser;
28char *g_statefile;
29
30#define OT_LISTEN_INHERIT (-1)
31
32static ot_listen *g_listen;
33static size_t g_listen_count;
34static int g_listen_shared;
35static int g_listen_workers;
36
37static void panic(const char *routine) __attribute__((noreturn));
38static void panic(const char *routine) {
39 fprintf(stderr, "%s: %s\n", routine, strerror(errno));
40 exit(111);
41}
42
43static void listen_append(const ot_ip6 ip, uint16_t port, PROTO_FLAG proto, int shared, int workers) {
44 ot_listen *grown = realloc(g_listen, (g_listen_count + 1) * sizeof(ot_listen));
45 ot_listen *entry;
46
47 if (!grown)
48 panic("listen_append");
49 g_listen = grown;
50
51 entry = g_listen + g_listen_count++;
52 memcpy(entry->ip, ip, sizeof(ot_ip6));
53 entry->port = port;
54 entry->proto = proto;
55 entry->shared = shared;
56 entry->workers = workers;
57 entry->sock = -1;
58}
59
60typedef enum { LISTEN_OPTION_UNKNOWN, LISTEN_OPTION_SHARED, LISTEN_OPTION_WORKERS } listen_option_kind;
61
62typedef struct {
63 const char *name;
64 size_t len;
65 listen_option_kind kind;
66 int takes_value;
67} listen_option_key;
68
69#define LISTEN_OPTION_KEY(s, k, v) {s, sizeof(s) - 1, k, v}
70
71static const listen_option_key listen_option_keys[] = {
72 LISTEN_OPTION_KEY("shared", LISTEN_OPTION_SHARED, 0),
73 LISTEN_OPTION_KEY("workers", LISTEN_OPTION_WORKERS, 1),
74};
75static const size_t listen_option_count = sizeof(listen_option_keys) / sizeof(listen_option_keys[0]);
76
77static listen_option_kind parse_listen_option(char **p) {
78 size_t i;
79
80 for (i = 0; i < listen_option_count; ++i) {
81 const listen_option_key *key = &listen_option_keys[i];
82 char delimiter = (*p)[key->len];
83
84 if (strncmp(*p, key->name, key->len))
85 continue;
86 if (key->takes_value ? delimiter != '=' : (delimiter && !isspace((unsigned char)delimiter)))
87 continue;
88
89 *p += key->len + (key->takes_value ? 1 : 0);
90 return key->kind;
91 }
92 return LISTEN_OPTION_UNKNOWN;
93}
94
95/* Options trailing an address in a listen statement, in any order */
96static int listen_scan_options(char *p, int *shared, int *workers) {
97 uint64_t value;
98
99 for (;;) {
100 while (isspace((unsigned char)*p))
101 ++p;
102 if (!*p)
103 return 1;
104
105 switch (parse_listen_option(&p)) {
106 case LISTEN_OPTION_SHARED:
107 *shared = 1;
108 break;
109 case LISTEN_OPTION_WORKERS: {
110 size_t off = scan_udec(p, &value, UINT_MAX);
111
112 if (!off)
113 return 0;
114 *workers = (int)value;
115 p += off;
116 break;
117 }
118 default:
119 return 0;
120 }
121 }
122}
123
124
125char *set_config_option(char **option, char *value) {
126#ifdef _DEBUG
127 fprintf(stderr, "Setting config option: %s\n", value);
128#endif
129 while (isspace(*value))
130 ++value;
131 free(*option);
132 return *option = strdup(value);
133}
134
135static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
136 const char *s = src;
137 uint64_t value;
138 int off, bracket = 0;
139 while (isspace(*s))
140 ++s;
141 if (*s == '[')
142 ++s, ++bracket; /* for v6 style notation */
143 if (!(off = ip_scan(s, ip)))
144 return 0;
145 s += off;
146 if (bracket && *s == ']')
147 ++s;
148 if (*s == 0 || isspace(*s))
149 return s - src;
150 if (!ip_is_v4mapped(ip)) {
151 if (*s != ':' && *s != '.')
152 return 0;
153 if (!bracket && *(s) == ':')
154 return 0;
155 s++;
156 } else {
157 if (*(s++) != ':')
158 return 0;
159 }
160 if (!(off = scan_udec(s, &value, 65535)))
161 return 0;
162 *port = (uint16_t)value;
163 return off + s - src;
164}
165
166static int scan_ip6_net(const char *src, ot_net *net) {
167 const char *s = src;
168 uint64_t value;
169 int off;
170 while (isspace(*s))
171 ++s;
172 if (!(off = ip_scan(s, net->address)))
173 return 0;
174 s += off;
175 if (*s != '/')
176 net->bits = 128;
177 else {
178 s++;
179 if (!(off = scan_udec(s, &value, 128)))
180 return 0;
181 net->bits = (int)value;
182 if (ip_is_v4mapped(net->address))
183 net->bits += 96;
184 if (net->bits > 128)
185 return 0;
186 s += off;
187 }
188 return off + s - src;
189}
190
191typedef enum {
192 CONFIG_UNKNOWN,
193 CONFIG_TRACKER_ROOTDIR,
194 CONFIG_TRACKER_USER,
195 CONFIG_LISTEN_TCP_UDP,
196 CONFIG_LISTEN_TCP,
197 CONFIG_LISTEN_UDP,
198 CONFIG_LISTEN_UDP_WORKERS,
199 CONFIG_LISTEN_SHARED,
200 CONFIG_ACCESS_WHITELIST,
201 CONFIG_ACCESS_BLACKLIST,
202 CONFIG_ACCESS_FIFO_ADD,
203 CONFIG_ACCESS_FIFO_DELETE,
204 CONFIG_ACCESS_STATS,
205 CONFIG_ACCESS_STATS_PATH,
206 CONFIG_ACCESS_PROXY,
207 CONFIG_TRACKER_REDIRECT_URL,
208 CONFIG_LIVESYNC_CLUSTER_NODE_IP,
209 CONFIG_LIVESYNC_CLUSTER_LISTEN,
210} config_kind;
211
212typedef struct {
213 const char *name;
214 size_t len;
215 config_kind kind;
216} config_key;
217
218#define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k }
219
220static const config_key config_keys[] = {
221 CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR),
222 CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER),
223 CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP),
224 CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP),
225 CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP),
226 CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS),
227 CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED),
228#ifdef WANT_ACCESSLIST_WHITE
229 CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST),
230#elif defined(WANT_ACCESSLIST_BLACK)
231 CONFIG_KEY("access.blacklist", CONFIG_ACCESS_BLACKLIST),
232#endif
233#ifdef WANT_DYNAMIC_ACCESSLIST
234 CONFIG_KEY("access.fifo_add", CONFIG_ACCESS_FIFO_ADD),
235 CONFIG_KEY("access.fifo_delete", CONFIG_ACCESS_FIFO_DELETE),
236#endif
237#ifdef WANT_RESTRICT_STATS
238 CONFIG_KEY("access.stats", CONFIG_ACCESS_STATS),
239#endif
240 CONFIG_KEY("access.stats_path", CONFIG_ACCESS_STATS_PATH),
241#ifdef WANT_IP_FROM_PROXY
242 CONFIG_KEY("access.proxy", CONFIG_ACCESS_PROXY),
243#endif
244 CONFIG_KEY("tracker.redirect_url", CONFIG_TRACKER_REDIRECT_URL),
245#ifdef WANT_SYNC_LIVE
246 CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP),
247 CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN),
248#endif
249};
250static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]);
251
252static config_kind parse_config_key(char **p) {
253 size_t i;
254 for (i = 0; i < config_entry_count; ++i) {
255 const config_key *key = &config_keys[i];
256 if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) {
257 *p += key->len + 1;
258 /* Skip white spaces */
259 while (isspace((unsigned char)**p))
260 ++(*p);
261 return key->kind;
262 }
263 }
264 return CONFIG_UNKNOWN;
265}
266
267int parse_configfile(char *config_filename) {
268 FILE *config_file_handle;
269 char inbuf[512];
270 ot_ip6 tmpip;
271#if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE)
272 ot_net tmpnet;
273#endif
274 int bound = 0;
275
276 config_file_handle = fopen(config_filename, "r");
277
278 if (config_file_handle == NULL) {
279 fprintf(stderr, "Warning: Can't open config file: %s.", config_filename);
280 return 0;
281 }
282
283 while (fgets(inbuf, sizeof(inbuf), config_file_handle)) {
284 char *p = inbuf, *end;
285 uint64_t value;
286 uint16_t tmpport = 6969;
287 config_kind kind;
288
289 /* Skip white spaces */
290 while (isspace((unsigned char)*p))
291 ++p;
292
293 /* Ignore comments and empty lines */
294 if ((*p == '#') || (*p == '\n') || (*p == 0))
295 continue;
296
297 /* consume trailing new lines and spaces */
298 end = p + strlen(p);
299 while (end > p && isspace((unsigned char)end[-1]))
300 *--end = 0;
301
302 /* Scan for commands */
303 kind = parse_config_key(&p);
304 switch (kind) {
305 case CONFIG_TRACKER_ROOTDIR:
306 set_config_option(&g_serverdir, p);
307 break;
308 case CONFIG_TRACKER_USER:
309 set_config_option(&g_serveruser, p);
310 break;
311 case CONFIG_LISTEN_TCP_UDP:
312 case CONFIG_LISTEN_TCP:
313 case CONFIG_LISTEN_UDP: {
314 int shared = OT_LISTEN_INHERIT;
315 int workers = OT_LISTEN_INHERIT;
316 int off = scan_ip6_port(p, tmpip, &tmpport);
317
318 if (!off || !listen_scan_options(p + off, &shared, &workers))
319 goto parse_error;
320 if (kind != CONFIG_LISTEN_UDP)
321 listen_append(tmpip, tmpport, FLAG_TCP, shared, OT_LISTEN_INHERIT), ++bound;
322 if (kind != CONFIG_LISTEN_TCP)
323 listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound;
324 break;
325 }
326 case CONFIG_LISTEN_UDP_WORKERS:
327 if (!scan_udec(p, &value, UINT_MAX))
328 goto parse_error;
329 g_listen_workers = (int)value;
330 break;
331 case CONFIG_LISTEN_SHARED:
332 if (!scan_udec(p, &value, 1))
333 goto parse_error;
334 g_listen_shared = (int)value;
335 break;
336#ifdef WANT_ACCESSLIST_WHITE
337 case CONFIG_ACCESS_WHITELIST:
338 set_config_option(&g_accesslist_filename, p);
339 break;
340#elif defined(WANT_ACCESSLIST_BLACK)
341 case CONFIG_ACCESS_BLACKLIST:
342 set_config_option(&g_accesslist_filename, p);
343 break;
344#endif
345#ifdef WANT_DYNAMIC_ACCESSLIST
346 case CONFIG_ACCESS_FIFO_ADD:
347 set_config_option(&g_accesslist_pipe_add, p);
348 break;
349 case CONFIG_ACCESS_FIFO_DELETE:
350 set_config_option(&g_accesslist_pipe_delete, p);
351 break;
352#endif
353#ifdef WANT_RESTRICT_STATS
354 case CONFIG_ACCESS_STATS:
355 if (!scan_ip6_net(p, &tmpnet))
356 goto parse_error;
357 accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_STAT);
358 break;
359#endif
360 case CONFIG_ACCESS_STATS_PATH:
361 set_config_option(&g_stats_path, p);
362 break;
363#ifdef WANT_IP_FROM_PROXY
364 case CONFIG_ACCESS_PROXY:
365 if (!scan_ip6_net(p, &tmpnet))
366 goto parse_error;
367 accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_PROXY);
368 break;
369#endif
370 case CONFIG_TRACKER_REDIRECT_URL:
371 set_config_option(&g_redirecturl, p);
372 break;
373#ifdef WANT_SYNC_LIVE
374 case CONFIG_LIVESYNC_CLUSTER_NODE_IP:
375 if (!scan_ip6_net(p, &tmpnet))
376 goto parse_error;
377 accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_LIVESYNC);
378 break;
379 case CONFIG_LIVESYNC_CLUSTER_LISTEN:
380 tmpport = LIVESYNC_PORT;
381 {
382 int shared = OT_LISTEN_INHERIT, dummy = OT_LISTEN_INHERIT;
383 int off = scan_ip6_port(p, tmpip, &tmpport);
384
385 if (!off || !listen_scan_options(p + off, &shared, &dummy))
386 goto parse_error;
387 listen_append(tmpip, tmpport, FLAG_MCA, shared, OT_LISTEN_INHERIT);
388 }
389 break;
390#endif
391 default:
392 fprintf(stderr, "Unhandled line in config file: %s\n", inbuf);
393 break;
394 parse_error:
395 fprintf(stderr, "Parse error in config file: %s\n", inbuf);
396 }
397 }
398 fclose(config_file_handle);
399 return bound;
400}
401
402static void usage(char *name) {
403 fprintf(stderr,
404 "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]"
405#ifdef WANT_ACCESSLIST_BLACK
406 " [-b blacklistfile]"
407#elif defined(WANT_ACCESSLIST_WHITE)
408 " [-w whitelistfile]"
409#endif
410 "\n",
411 name);
412}
413
414#define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc)
415static void help(char *name) {
416 usage(name);
417
418 HELPLINE("-f config", "include and execute the config file");
419 HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)");
420 HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)");
421 HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)");
422 HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)");
423 HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")");
424 HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")");
425 HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)");
426#ifdef WANT_ACCESSLIST_BLACK
427 HELPLINE("-b file", "specify blacklist file.");
428#elif defined(WANT_ACCESSLIST_WHITE)
429 HELPLINE("-w file", "specify whitelist file.");
430#endif
431
432 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");
433 fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n");
434 fprintf(stderr, " If no port is bound from config file or command line, the last address given\n");
435 fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n");
436}
437#undef HELPLINE
438
439
440static void listen_resolve_defaults(void) {
441 size_t i;
442
443 for (i = 0; i < g_listen_count; ++i) {
444 ot_listen *entry = g_listen + i;
445
446 if (entry->shared == OT_LISTEN_INHERIT)
447 entry->shared = g_listen_shared;
448 if (entry->workers == OT_LISTEN_INHERIT)
449 entry->workers = g_listen_workers;
450 }
451}
452
453ot_listen *config_listen_entries(size_t *count) {
454 *count = g_listen_count;
455 return g_listen;
456}
457
458void config_parse(int argc, char **argv) {
459 ot_ip6 serverip;
460 ot_net tmpnet;
461 int bound = 0, scanon = 1;
462 uint64_t value;
463
464 memset(serverip, 0, sizeof(ot_ip6));
465#ifdef WANT_V4_ONLY
466 serverip[10] = serverip[11] = -1;
467#endif
468
469 while (scanon) {
470 switch (getopt(argc, argv,
471 ":i:p:A:P:d:u:r:s:f:l:v"
472#ifdef WANT_ACCESSLIST_BLACK
473 "b:"
474#elif defined(WANT_ACCESSLIST_WHITE)
475 "w:"
476#endif
477 "h")) {
478 case -1:
479 scanon = 0;
480 break;
481 case 'i':
482 if (!ip_scan(optarg, serverip)) {
483 usage(argv[0]);
484 exit(1);
485 }
486 break;
487#ifdef WANT_ACCESSLIST_BLACK
488 case 'b':
489 set_config_option(&g_accesslist_filename, optarg);
490 break;
491#elif defined(WANT_ACCESSLIST_WHITE)
492 case 'w':
493 set_config_option(&g_accesslist_filename, optarg);
494 break;
495#endif
496 case 'p':
497 if (!scan_udec(optarg, &value, 65535)) {
498 usage(argv[0]);
499 exit(1);
500 }
501 listen_append(serverip, (uint16_t)value, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
502 bound++;
503 break;
504 case 'P':
505 if (!scan_udec(optarg, &value, 65535)) {
506 usage(argv[0]);
507 exit(1);
508 }
509 listen_append(serverip, (uint16_t)value, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
510 bound++;
511 break;
512#ifdef WANT_SYNC_LIVE
513 case 's':
514 if (!scan_udec(optarg, &value, 65535)) {
515 usage(argv[0]);
516 exit(1);
517 }
518 listen_append(serverip, (uint16_t)value, FLAG_MCA, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
519 break;
520#endif
521 case 'd':
522 set_config_option(&g_serverdir, optarg);
523 break;
524 case 'u':
525 set_config_option(&g_serveruser, optarg);
526 break;
527 case 'r':
528 set_config_option(&g_redirecturl, optarg);
529 break;
530 case 'l':
531 g_statefile = optarg;
532 break;
533 case 'A':
534 if (!scan_ip6_net(optarg, &tmpnet)) {
535 usage(argv[0]);
536 exit(1);
537 }
538 accesslist_bless_net(&tmpnet, 0xffff); /* Allow everything for now */
539 break;
540 case 'f':
541 bound += parse_configfile(optarg);
542 break;
543 case 'h':
544 help(argv[0]);
545 exit(0);
546 case 'v': {
547 char buffer[8192];
548 stats_return_tracker_version(buffer);
549 fputs(buffer, stderr);
550 exit(0);
551 }
552 default:
553 case '?':
554 usage(argv[0]);
555 exit(1);
556 }
557 }
558
559 if (!bound) {
560 listen_append(serverip, 6969, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
561 listen_append(serverip, 6969, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
562 }
563
564 listen_resolve_defaults();
565}