summaryrefslogtreecommitdiff
path: root/opentracker.c
diff options
context:
space:
mode:
Diffstat (limited to 'opentracker.c')
-rw-r--r--opentracker.c445
1 files changed, 0 insertions, 445 deletions
diff --git a/opentracker.c b/opentracker.c
deleted file mode 100644
index 2f2746d..0000000
--- a/opentracker.c
+++ /dev/null
@@ -1,445 +0,0 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3 Some of the stuff below is stolen from Fefes example libowfat httpd.
4
5 $Id$ */
6
7/* System */
8#include <ctype.h>
9#include <errno.h>
10#include <limits.h>
11#include <pthread.h>
12#include <pwd.h>
13#include <signal.h>
14#include <stdint.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <sys/socket.h>
19#include <unistd.h>
20#ifdef WANT_SYSLOGS
21#include <syslog.h>
22#endif
23
24/* Opentracker */
25#include "ot_accesslist.h"
26#include "ot_config.h"
27#include "ot_hex.h"
28#include "ot_http.h"
29#include "ot_ip.h"
30#include "ot_livesync.h"
31#include "ot_mutex.h"
32#include "ot_workqueue.h"
33#include "ot_random.h"
34#include "ot_scan.h"
35#include "ot_event.h"
36#include "ot_fdstate.h"
37#include "ot_socket.h"
38#include "ot_stats.h"
39#include "ot_tcp.h"
40#include "ot_udp.h"
41#include "trackerlogic.h"
42
43/* Globals */
44uint32_t g_tracker_id;
45_Atomic int g_opentracker_running = 1;
46int g_self_pipe[2];
47
48static void panic(const char *routine) __attribute__((noreturn));
49static void panic(const char *routine) {
50 fprintf(stderr, "%s: %s\n", routine, strerror(errno));
51 exit(111);
52}
53
54/* Say we are stopping, wake the loop, and let main do the work on its own
55 stack. Everything other than SIGINT falls through and does nothing, which
56 is what OT_SIGNAL_WAKE wants: returning from the read is all it is for. */
57static void signal_handler(int s) {
58 if (s == SIGINT) {
59 const char byte = 0;
60 int saved = errno;
61
62 /* Any new interrupt signal quits the application */
63 signal(SIGINT, SIG_DFL);
64
65 /* Tell all other threads to not acquire any new lock on a bucket
66 but cancel their operations and return */
67 g_opentracker_running = 0;
68
69 (void)!write(g_self_pipe[1], &byte, 1);
70 errno = saved;
71 }
72}
73
74static void defaul_signal_handlers(void) {
75 sigset_t signal_mask;
76 sigemptyset(&signal_mask);
77 sigaddset(&signal_mask, SIGPIPE);
78 sigaddset(&signal_mask, SIGHUP);
79 sigaddset(&signal_mask, SIGINT);
80 sigaddset(&signal_mask, SIGALRM);
81 pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
82}
83
84static void install_signal_handlers(void) {
85 struct sigaction sa;
86 sigset_t signal_mask;
87 sigemptyset(&signal_mask);
88
89 sa.sa_handler = signal_handler;
90 sigemptyset(&sa.sa_mask);
91 sa.sa_flags = 0;
92 if ((sigaction(SIGINT, &sa, NULL) == -1) || (sigaction(SIGALRM, &sa, NULL) == -1) ||
93 (sigaction(OT_SIGNAL_WAKE, &sa, NULL) == -1))
94 panic("install_signal_handlers");
95
96 sigaddset(&signal_mask, SIGINT);
97 pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL);
98}
99
100
101/* How many ready descriptors one wait hands back at a time */
102#define OT_EVENT_BATCH 512
103
104static void *server_mainloop(void *args) {
105 struct ot_workstruct ws;
106 time_t next_timeout_check = time_future_seconds(OT_CLIENT_TIMEOUT_CHECKINTERVAL);
107 struct iovec *iovector;
108 size_t iovec_entries;
109 int is_partial;
110
111 (void)args;
112
113 if (workstruct_init(&ws) == -1)
114 panic("Initializing worker failed");
115
116 while (g_opentracker_running) {
117 ot_event ready[OT_EVENT_BATCH];
118 int64_t sock;
119 int count, i;
120
121 count = event_wait(ready, OT_EVENT_BATCH, OT_CLIENT_TIMEOUT_CHECKINTERVAL * 1000);
122
123 for (i = 0; i < count; ++i) {
124 const void *cookie;
125
126 sock = ready[i].fd;
127 /* A descriptor closed earlier in this same batch may already have been
128 handed out again by accept, so an event is only ours if the
129 generation it was registered under is still the one in place */
130 if (fdstate_generation((int)sock) != ready[i].generation)
131 continue;
132 cookie = fdstate_cookie((int)sock);
133
134 if ((intptr_t)cookie == FLAG_TCP)
135 tcp_accept(sock, &ws);
136 else if ((intptr_t)cookie == FLAG_UDP)
137 handle_udp6(sock, &ws);
138 else if ((intptr_t)cookie == FLAG_SELFPIPE)
139 while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0)
140 ;
141 else if (fdstate_claim((int)sock, ready[i].generation)) {
142 if (ready[i].what & EVENT_IN)
143 tcp_read(sock, &ws);
144 /* The read may have closed it, so look again before writing */
145 if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation)
146 tcp_write(sock);
147 if (fdstate_generation((int)sock) == ready[i].generation)
148 fdstate_release((int)sock);
149 }
150 }
151
152 while ((sock = workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1)
153 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial);
154
155 if (time_now_seconds() > next_timeout_check) {
156 int cursor = 0;
157
158 while ((sock = fdstate_next_expired(time_now_seconds(), &cursor)) != -1)
159 http_close(sock);
160 next_timeout_check = time_future_seconds(OT_CLIENT_TIMEOUT_CHECKINTERVAL);
161 }
162
163 livesync_ticker();
164 }
165
166 /* This work struct is about to go out of scope, so hand its tally over
167 before anything can read it there */
168 workstruct_deinit(&ws);
169 return 0;
170}
171
172
173/* A group of sockets sharing one address only spreads datagrams if every
174 member asked for it before binding, the first one included, so whether
175 this entry will have workers has to be known here. */
176static socket_sharing listen_sharing_for(const ot_listen *entry) {
177 if (entry->proto != FLAG_MCA && entry->workers > 1 && socket_can_balance())
178 return SOCKET_BALANCED;
179 return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE;
180}
181
182static void listen_bind_one(ot_listen *entry) {
183 socket_sharing sharing = listen_sharing_for(entry);
184
185#ifdef _DEBUG
186 {
187 const char *protos[] = {"TCP", "UDP", "UDP mcast"};
188 char _debug[512];
189 int off = snprintf(_debug, sizeof(_debug), "Binding socket type %s to address [", protos[entry->proto]);
190 off += ip_format(_debug + off, entry->ip);
191 snprintf(_debug + off, sizeof(_debug) - off, "]:%d...", entry->port);
192 fputs(_debug, stderr);
193 }
194#endif
195
196#ifdef WANT_SYNC_LIVE
197 if (entry->proto == FLAG_MCA) {
198 livesync_bind_mcast(entry->ip, entry->port);
199#ifdef _DEBUG
200 fputs(" success.\n", stderr);
201#endif
202 return;
203 }
204#endif
205
206 if (entry->proto == FLAG_TCP) {
207 if ((entry->sock = socket_tcp_listen(entry->ip, entry->port, sharing)) == -1)
208 panic("socket_tcp_listen");
209 socket_defer_accept((int)entry->sock);
210 } else {
211 if ((entry->sock = socket_udp_bind(entry->ip, entry->port, sharing)) == -1)
212 panic("socket_udp_bind");
213 }
214
215 if (event_add((int)entry->sock, EVENT_IN, fdstate_open(entry->sock, (void *)entry->proto, 0)) == -1)
216 panic("event_add");
217
218#ifdef _DEBUG
219 fputs(" success.\n", stderr);
220#endif
221}
222
223/* Bound in the order the entries were given.
224 Note: On macOS SO_REUSEPORT delivers everything to the socket that bound last. */
225static void listen_bind_all(void) {
226 size_t count, i;
227 ot_listen *entries = config_listen_entries(&count);
228
229 for (i = 0; i < count; ++i)
230 listen_bind_one(entries + i);
231}
232
233/* The socket the event loop was using is passed to the first worker.
234 If start up tests show that the kernel can spread connections over
235 a group bound to one address, every worker binds one of its own.
236 If not, all workers call accept on the first. */
237static void listen_start_tcp_workers(void) {
238 size_t count, i;
239 ot_listen *entries = config_listen_entries(&count);
240
241 for (i = 0; i < count; ++i) {
242 ot_listen *entry = entries + i;
243 int balance = listen_sharing_for(entry) == SOCKET_BALANCED;
244 int worker;
245
246 if (entry->proto != FLAG_TCP || entry->workers <= 0)
247 continue;
248
249 event_del((int)entry->sock);
250 tcp_worker_install(entry->sock);
251
252 for (worker = 1; worker < entry->workers; ++worker) {
253 int64_t sock = entry->sock;
254
255 if (balance) {
256 sock = socket_tcp_listen(entry->ip, entry->port, SOCKET_BALANCED);
257 if (sock == -1) {
258 fprintf(stderr, "Warning: could not bind another socket for tcp worker %d: %s. Sharing the first instead.\n", worker,
259 strerror(errno));
260 sock = entry->sock;
261 }
262 }
263 tcp_worker_install(sock);
264 }
265 }
266}
267
268static void listen_start_udp_workers(void) {
269 size_t count, i;
270 ot_listen *entries = config_listen_entries(&count);
271
272 udp_init();
273
274 for (i = 0; i < count; ++i) {
275 ot_listen *entry = entries + i;
276 int balance = listen_sharing_for(entry) == SOCKET_BALANCED;
277 int worker;
278
279 if (entry->proto != FLAG_UDP || entry->workers <= 0)
280 continue;
281
282 event_del((int)entry->sock);
283 udp_worker_install(entry->sock);
284
285 for (worker = 1; worker < entry->workers; ++worker) {
286 int64_t sock = entry->sock;
287
288 if (balance) {
289 sock = socket_udp_bind(entry->ip, entry->port, SOCKET_BALANCED);
290 if (sock == -1) {
291 fprintf(stderr, "Warning: could not bind another socket for udp worker %d: %s. Sharing the first instead.\n", worker, strerror(errno));
292 sock = entry->sock;
293 }
294 }
295 udp_worker_install(sock);
296 }
297 }
298}
299
300void load_state(const char *const state_filename) {
301 FILE *state_filehandle;
302 char inbuf[512];
303 ot_hash infohash;
304 uint64_t base, downcount;
305 int consumed;
306
307 state_filehandle = fopen(state_filename, "r");
308
309 if (state_filehandle == NULL) {
310 fprintf(stderr, "Warning: Can't open config file: %s.", state_filename);
311 return;
312 }
313
314 /* We do ignore anything that is not of the form "^[:xdigit:]:\d+:\d+" */
315 while (fgets(inbuf, sizeof(inbuf), state_filehandle)) {
316 size_t i;
317
318 if (!hex_to_hash(infohash, inbuf))
319 continue;
320 i = 2 * sizeof(ot_hash);
321
322 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &base, UINT64_MAX)))
323 continue;
324 i += consumed;
325 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &downcount, UINT64_MAX)))
326 continue;
327 add_torrent_from_saved_state(infohash, base, downcount);
328 }
329
330 fclose(state_filehandle);
331}
332
333int drop_privileges(const char *const serveruser, const char *const serverdir) {
334 struct passwd *pws = NULL;
335
336#ifdef _DEBUG
337 if (!geteuid())
338 fprintf(stderr, "Dropping to user %s.\n", serveruser);
339 if (serverdir)
340 fprintf(stderr, "ch%s'ing to directory %s.\n", geteuid() ? "dir" : "root", serverdir);
341#endif
342
343 /* Grab pws entry before chrooting */
344 pws = getpwnam(serveruser);
345 endpwent();
346
347 if (geteuid() == 0) {
348 /* Running as root: chroot and drop privileges */
349 if (serverdir && chroot(serverdir)) {
350 fprintf(stderr, "Could not chroot to %s, because: %s\n", serverdir, strerror(errno));
351 return -1;
352 }
353
354 if (chdir("/"))
355 panic("chdir() failed after chrooting: ");
356
357 /* If we can't find server user, revert to nobody's default uid */
358 if (!pws) {
359 fprintf(stderr, "Warning: Could not get password entry for %s. Reverting to uid -2.\n", serveruser);
360 if (setegid((gid_t)-2) || setgid((gid_t)-2) || setuid((uid_t)-2) || seteuid((uid_t)-2))
361 panic("Could not set uid to value -2");
362 } else {
363 if (setegid(pws->pw_gid) || setgid(pws->pw_gid) || setuid(pws->pw_uid) || seteuid(pws->pw_uid))
364 panic("Could not set uid to specified value");
365 }
366
367 if (geteuid() == 0 || getegid() == 0)
368 panic("Still running with root privileges?!");
369 } else {
370 /* Normal user, just chdir() */
371 if (serverdir && chdir(serverdir)) {
372 fprintf(stderr, "Could not chroot to %s, because: %s\n", serverdir, strerror(errno));
373 return -1;
374 }
375 }
376
377 return 0;
378}
379
380/* Maintain our copy of the clock. time() on BSDs is very expensive. */
381int main(int argc, char **argv) {
382
383 random_init();
384 if (fdstate_init() == -1)
385 panic("fdstate_init");
386 if (event_init() == -1)
387 panic("event_init");
388 config_parse(argc, argv);
389
390 listen_bind_all();
391
392 defaul_signal_handlers();
393
394#ifdef WANT_SYSLOGS
395 openlog("opentracker", 0, LOG_USER);
396 setlogmask(LOG_UPTO(LOG_INFO));
397#endif
398
399 if (drop_privileges(g_serveruser ? g_serveruser : "nobody", g_serverdir) == -1)
400 panic("drop_privileges failed, exiting. Last error");
401
402
403 /* Create our self pipe which allows us to interrupt mainloops
404 wait in case some data is available to send out */
405 if (pipe(g_self_pipe) == -1)
406 panic("selfpipe failed: ");
407 /* Both ends have to be non blocking: the loop drains the read end until it
408 is empty, and a worker nudging a full pipe has already made its point */
409 socket_set_blocking(g_self_pipe[0], 0);
410 socket_set_blocking(g_self_pipe[1], 0);
411 if (event_add(g_self_pipe[0], EVENT_IN, fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0)) == -1)
412 panic("selfpipe event_add");
413
414 /* Init all sub systems. This call may fail with an exit() */
415 trackerlogic_init();
416
417#ifdef _DEBUG_RANDOMTORRENTS
418 fprintf(stderr, "DEBUG: Generating %d random peers on random torrents. This may take a while. (Setting RANDOMTORRENTS in trackerlogic.h)\n", RANDOMTORRENTS);
419 trackerlogic_add_random_torrents(RANDOMTORRENTS);
420 fprintf(stderr, "... done.\n");
421#endif
422
423 if (g_statefile)
424 load_state(g_statefile);
425
426 install_signal_handlers();
427
428 listen_start_tcp_workers();
429 listen_start_udp_workers();
430
431 server_mainloop(0);
432 tcp_workers_deinit();
433 udp_workers_deinit();
434
435 /* No thread is serving any more, so the teardown can run where a debugger
436 and the sanitizers can follow it */
437 trackerlogic_deinit();
438#ifdef WANT_SYSLOGS
439 closelog();
440#endif
441
442 event_deinit();
443 fdstate_deinit();
444 return 0;
445}