summaryrefslogtreecommitdiff
path: root/src/ot_config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ot_config.c')
-rw-r--r--src/ot_config.c592
1 files changed, 592 insertions, 0 deletions
diff --git a/src/ot_config.c b/src/ot_config.c
new file mode 100644
index 0000000..4064d1b
--- /dev/null
+++ b/src/ot_config.c
@@ -0,0 +1,592 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <ctype.h>
6#include <errno.h>
7#include <limits.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <unistd.h>
12
13/* Opentracker */
14#include "ot_accesslist.h"
15#include "ot_event.h"
16#include "ot_config.h"
17#include "ot_http.h"
18#include "ot_ip.h"
19#include "ot_livesync.h"
20#include "ot_scan.h"
21#include "ot_stats.h"
22#include "trackerlogic.h"
23
24const char *g_redirecturl;
25const char *g_serverdir;
26const char *g_serveruser;
27const char *g_statefile;
28
29#define OT_LISTEN_INHERIT (-1)
30
31static ot_listen *g_listen;
32static size_t g_listen_count;
33static int g_listen_shared;
34static int g_listen_workers_udp = 1;
35static int g_listen_workers_tcp = 0;
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_uint(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
125static const char *set_config_option(const 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((char *)*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_parse(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_uint(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_parse(s, net->address)))
173 return 0;
174 s += off;
175 if (*s != '/')
176 net->bits = 128;
177 else {
178 s++;
179 if (!(off = scan_uint(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_TCP_WORKERS,
200 CONFIG_LISTEN_SHARED,
201 CONFIG_ACCESS_WHITELIST,
202 CONFIG_ACCESS_BLACKLIST,
203 CONFIG_ACCESS_FIFO_ADD,
204 CONFIG_ACCESS_FIFO_DELETE,
205 CONFIG_ACCESS_STATS,
206 CONFIG_ACCESS_STATS_PATH,
207 CONFIG_ACCESS_PROXY,
208 CONFIG_TRACKER_REDIRECT_URL,
209 CONFIG_LIVESYNC_CLUSTER_NODE_IP,
210 CONFIG_LIVESYNC_CLUSTER_LISTEN,
211 CONFIG_LIVESYNC_CLUSTER_GROUP,
212} config_kind;
213
214typedef struct {
215 const char *name;
216 size_t len;
217 config_kind kind;
218} config_key;
219
220#define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k }
221
222static const config_key config_keys[] = {
223 CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR),
224 CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER),
225 CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP),
226 CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP),
227 CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP),
228 CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS),
229 CONFIG_KEY("listen.tcp.workers", CONFIG_LISTEN_TCP_WORKERS),
230 CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED),
231#ifdef WANT_ACCESSLIST_WHITE
232 CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST),
233#elif defined(WANT_ACCESSLIST_BLACK)
234 CONFIG_KEY("access.blacklist", CONFIG_ACCESS_BLACKLIST),
235#endif
236#ifdef WANT_DYNAMIC_ACCESSLIST
237 CONFIG_KEY("access.fifo_add", CONFIG_ACCESS_FIFO_ADD),
238 CONFIG_KEY("access.fifo_delete", CONFIG_ACCESS_FIFO_DELETE),
239#endif
240#ifdef WANT_RESTRICT_STATS
241 CONFIG_KEY("access.stats", CONFIG_ACCESS_STATS),
242#endif
243 CONFIG_KEY("access.stats_path", CONFIG_ACCESS_STATS_PATH),
244#ifdef WANT_IP_FROM_PROXY
245 CONFIG_KEY("access.proxy", CONFIG_ACCESS_PROXY),
246#endif
247 CONFIG_KEY("tracker.redirect_url", CONFIG_TRACKER_REDIRECT_URL),
248#ifdef WANT_SYNC_LIVE
249 CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP),
250 CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN),
251 CONFIG_KEY("livesync.cluster.group", CONFIG_LIVESYNC_CLUSTER_GROUP),
252#endif
253};
254static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]);
255
256static config_kind parse_config_key(char **p) {
257 size_t i;
258 for (i = 0; i < config_entry_count; ++i) {
259 const config_key *key = &config_keys[i];
260 if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) {
261 *p += key->len + 1;
262 /* Skip white spaces */
263 while (isspace((unsigned char)**p))
264 ++(*p);
265 return key->kind;
266 }
267 }
268 return CONFIG_UNKNOWN;
269}
270
271static int parse_configfile(char *config_filename) {
272 FILE *config_file_handle;
273 char inbuf[512];
274 ot_ip6 tmpip;
275#if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE)
276 ot_net tmpnet;
277#endif
278 int bound = 0;
279
280 config_file_handle = fopen(config_filename, "r");
281
282 if (config_file_handle == NULL) {
283 fprintf(stderr, "Warning: Can't open config file: %s.", config_filename);
284 return 0;
285 }
286
287 while (fgets(inbuf, sizeof(inbuf), config_file_handle)) {
288 char *p = inbuf, *end;
289 uint64_t value;
290 uint16_t tmpport = 6969;
291 config_kind kind;
292
293 /* Skip white spaces */
294 while (isspace((unsigned char)*p))
295 ++p;
296
297 /* Ignore comments and empty lines */
298 if ((*p == '#') || (*p == '\n') || (*p == 0))
299 continue;
300
301 /* consume trailing new lines and spaces */
302 end = p + strlen(p);
303 while (end > p && isspace((unsigned char)end[-1]))
304 *--end = 0;
305
306 /* Scan for commands */
307 kind = parse_config_key(&p);
308 switch (kind) {
309 case CONFIG_TRACKER_ROOTDIR:
310 set_config_option(&g_serverdir, p);
311 break;
312 case CONFIG_TRACKER_USER:
313 set_config_option(&g_serveruser, p);
314 break;
315 case CONFIG_LISTEN_TCP_UDP:
316 case CONFIG_LISTEN_TCP:
317 case CONFIG_LISTEN_UDP: {
318 int shared = OT_LISTEN_INHERIT;
319 int workers = OT_LISTEN_INHERIT;
320 int off = scan_ip6_port(p, tmpip, &tmpport);
321
322 if (!off || !listen_scan_options(p + off, &shared, &workers))
323 goto parse_error;
324 if (kind != CONFIG_LISTEN_UDP)
325 listen_append(tmpip, tmpport, FLAG_TCP, shared, workers), ++bound;
326 if (kind != CONFIG_LISTEN_TCP)
327 listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound;
328 break;
329 }
330 case CONFIG_LISTEN_UDP_WORKERS:
331 if (!scan_uint(p, &value, UINT_MAX))
332 goto parse_error;
333 g_listen_workers_udp = (int)value;
334 break;
335 case CONFIG_LISTEN_TCP_WORKERS:
336 if (!scan_uint(p, &value, UINT_MAX))
337 goto parse_error;
338 g_listen_workers_tcp = (int)value;
339 break;
340 case CONFIG_LISTEN_SHARED:
341 if (!scan_uint(p, &value, 1))
342 goto parse_error;
343 g_listen_shared = (int)value;
344 break;
345#ifdef WANT_ACCESSLIST_WHITE
346 case CONFIG_ACCESS_WHITELIST:
347 set_config_option(&g_accesslist_filename, p);
348 break;
349#elif defined(WANT_ACCESSLIST_BLACK)
350 case CONFIG_ACCESS_BLACKLIST:
351 set_config_option(&g_accesslist_filename, p);
352 break;
353#endif
354#ifdef WANT_DYNAMIC_ACCESSLIST
355 case CONFIG_ACCESS_FIFO_ADD:
356 set_config_option(&g_accesslist_pipe_add, p);
357 break;
358 case CONFIG_ACCESS_FIFO_DELETE:
359 set_config_option(&g_accesslist_pipe_delete, p);
360 break;
361#endif
362#ifdef WANT_RESTRICT_STATS
363 case CONFIG_ACCESS_STATS:
364 if (!scan_ip6_net(p, &tmpnet))
365 goto parse_error;
366 accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_STAT);
367 break;
368#endif
369 case CONFIG_ACCESS_STATS_PATH:
370 set_config_option(&g_stats_path, p);
371 break;
372#ifdef WANT_IP_FROM_PROXY
373 case CONFIG_ACCESS_PROXY:
374 if (!scan_ip6_net(p, &tmpnet))
375 goto parse_error;
376 accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_PROXY);
377 break;
378#endif
379 case CONFIG_TRACKER_REDIRECT_URL:
380 set_config_option(&g_redirecturl, p);
381 break;
382#ifdef WANT_SYNC_LIVE
383 case CONFIG_LIVESYNC_CLUSTER_GROUP:
384 if (!ip_parse(p, tmpip) || livesync_set_group(tmpip) == -1)
385 goto parse_error;
386 break;
387 case CONFIG_LIVESYNC_CLUSTER_NODE_IP:
388 if (!scan_ip6_net(p, &tmpnet))
389 goto parse_error;
390 accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_LIVESYNC);
391 break;
392 case CONFIG_LIVESYNC_CLUSTER_LISTEN:
393 tmpport = LIVESYNC_PORT;
394 {
395 int shared = OT_LISTEN_INHERIT, dummy = OT_LISTEN_INHERIT;
396 int off = scan_ip6_port(p, tmpip, &tmpport);
397
398 if (!off || !listen_scan_options(p + off, &shared, &dummy))
399 goto parse_error;
400 listen_append(tmpip, tmpport, FLAG_MCA, shared, OT_LISTEN_INHERIT);
401 }
402 break;
403#endif
404 default:
405 fprintf(stderr, "Unhandled line in config file: %s\n", inbuf);
406 break;
407 parse_error:
408 fprintf(stderr, "Parse error in config file: %s\n", inbuf);
409 }
410 }
411 fclose(config_file_handle);
412 return bound;
413}
414
415static void usage(char *name) {
416 fprintf(stderr,
417 "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]"
418#ifdef WANT_ACCESSLIST_BLACK
419 " [-b blacklistfile]"
420#elif defined(WANT_ACCESSLIST_WHITE)
421 " [-w whitelistfile]"
422#endif
423 "\n",
424 name);
425}
426
427#define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc)
428static void help(char *name) {
429 usage(name);
430
431 HELPLINE("-f config", "include and execute the config file");
432 HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)");
433 HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)");
434 HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)");
435 HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)");
436 HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")");
437 HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")");
438 HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)");
439 HELPLINE("-v", "report version, event mechanism and the feature flags opentracker was built with");
440#ifdef WANT_ACCESSLIST_BLACK
441 HELPLINE("-b file", "specify blacklist file.");
442#elif defined(WANT_ACCESSLIST_WHITE)
443 HELPLINE("-w file", "specify whitelist file.");
444#endif
445
446 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");
447 fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n");
448 fprintf(stderr, " If no port is bound from config file or command line, the last address given\n");
449 fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n");
450}
451#undef HELPLINE
452
453
454static void listen_resolve_defaults(void) {
455 size_t i;
456
457 for (i = 0; i < g_listen_count; ++i) {
458 ot_listen *entry = g_listen + i;
459
460 if (entry->shared == OT_LISTEN_INHERIT)
461 entry->shared = g_listen_shared;
462 if (entry->workers == OT_LISTEN_INHERIT)
463 entry->workers = entry->proto == FLAG_TCP ? g_listen_workers_tcp : g_listen_workers_udp;
464
465 /* A tcp worker hands a connection it cannot finish to the event loop,
466 which only one thread may touch where the set is ours rather than the
467 kernel's */
468 if (entry->proto == FLAG_TCP && entry->workers > 1 && !event_can_share()) {
469 fprintf(stderr, "Warning: Multiple workers impossible on this platform with event mechanism %s chosen.\n",
470 event_mechanism());
471 entry->workers = 1;
472 }
473 }
474}
475
476ot_listen *config_listen_entries(size_t *count) {
477 *count = g_listen_count;
478 return g_listen;
479}
480
481void config_parse(int argc, char **argv) {
482 ot_ip6 serverip;
483 ot_net tmpnet;
484 int bound = 0, scanon = 1;
485 uint64_t value;
486
487 memset(serverip, 0, sizeof(ot_ip6));
488#ifdef WANT_V4_ONLY
489 serverip[10] = serverip[11] = -1;
490#endif
491
492 while (scanon) {
493 switch (getopt(argc, argv,
494 ":i:p:A:P:d:u:r:s:f:l:v"
495#ifdef WANT_ACCESSLIST_BLACK
496 "b:"
497#elif defined(WANT_ACCESSLIST_WHITE)
498 "w:"
499#endif
500 "h")) {
501 case -1:
502 scanon = 0;
503 break;
504 case 'i':
505 if (!ip_parse(optarg, serverip)) {
506 usage(argv[0]);
507 exit(1);
508 }
509 break;
510#ifdef WANT_ACCESSLIST_BLACK
511 case 'b':
512 set_config_option(&g_accesslist_filename, optarg);
513 break;
514#elif defined(WANT_ACCESSLIST_WHITE)
515 case 'w':
516 set_config_option(&g_accesslist_filename, optarg);
517 break;
518#endif
519 case 'p':
520 if (!scan_uint(optarg, &value, 65535)) {
521 usage(argv[0]);
522 exit(1);
523 }
524 listen_append(serverip, (uint16_t)value, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
525 bound++;
526 break;
527 case 'P':
528 if (!scan_uint(optarg, &value, 65535)) {
529 usage(argv[0]);
530 exit(1);
531 }
532 listen_append(serverip, (uint16_t)value, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
533 bound++;
534 break;
535#ifdef WANT_SYNC_LIVE
536 case 's':
537 if (!scan_uint(optarg, &value, 65535)) {
538 usage(argv[0]);
539 exit(1);
540 }
541 listen_append(serverip, (uint16_t)value, FLAG_MCA, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
542 break;
543#endif
544 case 'd':
545 set_config_option(&g_serverdir, optarg);
546 break;
547 case 'u':
548 set_config_option(&g_serveruser, optarg);
549 break;
550 case 'r':
551 set_config_option(&g_redirecturl, optarg);
552 break;
553 case 'l':
554 g_statefile = optarg;
555 break;
556 case 'A':
557 if (!scan_ip6_net(optarg, &tmpnet)) {
558 usage(argv[0]);
559 exit(1);
560 }
561 accesslist_bless_net(&tmpnet, 0xffff); /* Allow everything for now */
562 break;
563 case 'f':
564 bound += parse_configfile(optarg);
565 break;
566 case 'h':
567 help(argv[0]);
568 exit(0);
569 case 'v': {
570 char buffer[8192];
571 char *cursor = buffer;
572
573 stats_return_tracker_version(&cursor, buffer + sizeof(buffer) - 1);
574 stats_return_tracker_features(&cursor, buffer + sizeof(buffer) - 1);
575 *cursor = 0;
576 fputs(buffer, stderr);
577 exit(0);
578 }
579 default:
580 case '?':
581 usage(argv[0]);
582 exit(1);
583 }
584 }
585
586 if (!bound) {
587 listen_append(serverip, 6969, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
588 listen_append(serverip, 6969, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
589 }
590
591 listen_resolve_defaults();
592}