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