]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal-remote/journal-remote.c
journal-remote: small fixes
[thirdparty/systemd.git] / src / journal-remote / journal-remote.c
CommitLineData
fdfccdbc
ZJS
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2012 Zbigniew Jędrzejewski-Szmek
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <errno.h>
23#include <fcntl.h>
24#include <inttypes.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <sys/prctl.h>
29#include <sys/socket.h>
30#include <sys/stat.h>
31#include <sys/types.h>
32#include <unistd.h>
33#include <getopt.h>
34
35#include "sd-daemon.h"
36#include "sd-event.h"
37#include "journal-file.h"
38#include "journald-native.h"
39#include "socket-util.h"
40#include "mkdir.h"
41#include "build.h"
42#include "macro.h"
43#include "strv.h"
cc64d017 44#include "fileio.h"
cc64d017
ZJS
45#include "microhttpd-util.h"
46
47#ifdef HAVE_GNUTLS
48#include <gnutls/gnutls.h>
49#endif
fdfccdbc
ZJS
50
51#include "journal-remote-parse.h"
52#include "journal-remote-write.h"
53
54#define REMOTE_JOURNAL_PATH "/var/log/journal/" SD_ID128_FORMAT_STR "/remote-%s.journal"
55
56static char* arg_output = NULL;
57static char* arg_url = NULL;
58static char* arg_getter = NULL;
fdfccdbc 59static char* arg_listen_raw = NULL;
cc64d017
ZJS
60static char* arg_listen_http = NULL;
61static char* arg_listen_https = NULL;
8a8d55f2 62static char** arg_files = NULL;
fdfccdbc
ZJS
63static int arg_compress = true;
64static int arg_seal = false;
8a8d55f2 65static int http_socket = -1, https_socket = -1;
5937a4d4 66static char** arg_gnutls_log = NULL;
fdfccdbc 67
cc64d017
ZJS
68static char *key_pem = NULL;
69static char *cert_pem = NULL;
70static char *trust_pem = NULL;
71
fdfccdbc
ZJS
72/**********************************************************************
73 **********************************************************************
74 **********************************************************************/
75
76static int spawn_child(const char* child, char** argv) {
77 int fd[2];
78 pid_t parent_pid, child_pid;
79 int r;
80
81 if (pipe(fd) < 0) {
82 log_error("Failed to create pager pipe: %m");
83 return -errno;
84 }
85
86 parent_pid = getpid();
87
88 child_pid = fork();
89 if (child_pid < 0) {
90 r = -errno;
91 log_error("Failed to fork: %m");
3d94f76c 92 safe_close_pair(fd);
fdfccdbc
ZJS
93 return r;
94 }
95
96 /* In the child */
97 if (child_pid == 0) {
98 r = dup2(fd[1], STDOUT_FILENO);
99 if (r < 0) {
100 log_error("Failed to dup pipe to stdout: %m");
101 _exit(EXIT_FAILURE);
102 }
103
3d94f76c 104 safe_close_pair(fd);
fdfccdbc
ZJS
105
106 /* Make sure the child goes away when the parent dies */
107 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
108 _exit(EXIT_FAILURE);
109
110 /* Check whether our parent died before we were able
111 * to set the death signal */
112 if (getppid() != parent_pid)
113 _exit(EXIT_SUCCESS);
114
115 execvp(child, argv);
116 log_error("Failed to exec child %s: %m", child);
117 _exit(EXIT_FAILURE);
118 }
119
120 r = close(fd[1]);
121 if (r < 0)
122 log_warning("Failed to close write end of pipe: %m");
123
124 return fd[0];
125}
126
127static int spawn_curl(char* url) {
128 char **argv = STRV_MAKE("curl",
129 "-HAccept: application/vnd.fdo.journal",
130 "--silent",
131 "--show-error",
132 url);
133 int r;
134
135 r = spawn_child("curl", argv);
136 if (r < 0)
137 log_error("Failed to spawn curl: %m");
138 return r;
139}
140
141static int spawn_getter(char *getter, char *url) {
142 int r;
c8b32e11 143 _cleanup_strv_free_ char **words = NULL;
fdfccdbc
ZJS
144
145 assert(getter);
146 words = strv_split_quoted(getter);
147 if (!words)
148 return log_oom();
149
150 r = spawn_child(words[0], words);
151 if (r < 0)
152 log_error("Failed to spawn getter %s: %m", getter);
153
154 return r;
155}
156
157static int open_output(Writer *s, const char* url) {
c8b32e11 158 _cleanup_free_ char *name, *output = NULL;
fdfccdbc
ZJS
159 char *c;
160 int r;
161
162 assert(url);
163 name = strdup(url);
164 if (!name)
165 return log_oom();
166
167 for(c = name; *c; c++) {
168 if (*c == '/' || *c == ':' || *c == ' ')
169 *c = '~';
170 else if (*c == '?') {
171 *c = '\0';
172 break;
173 }
174 }
175
176 if (!arg_output) {
177 sd_id128_t machine;
178 r = sd_id128_get_machine(&machine);
179 if (r < 0) {
180 log_error("failed to determine machine ID128: %s", strerror(-r));
181 return r;
182 }
183
184 r = asprintf(&output, REMOTE_JOURNAL_PATH,
185 SD_ID128_FORMAT_VAL(machine), name);
186 if (r < 0)
187 return log_oom();
188 } else {
e73a03e0 189 r = is_dir(arg_output, true);
fdfccdbc
ZJS
190 if (r > 0) {
191 r = asprintf(&output,
192 "%s/remote-%s.journal", arg_output, name);
193 if (r < 0)
194 return log_oom();
195 } else {
196 output = strdup(arg_output);
197 if (!output)
198 return log_oom();
199 }
200 }
201
202 r = journal_file_open_reliably(output,
203 O_RDWR|O_CREAT, 0640,
204 arg_compress, arg_seal,
205 &s->metrics,
206 s->mmap,
207 NULL, &s->journal);
208 if (r < 0)
209 log_error("Failed to open output journal %s: %s",
210 arg_output, strerror(-r));
211 else
212 log_info("Opened output file %s", s->journal->path);
213 return r;
214}
215
cc64d017
ZJS
216/**********************************************************************
217 **********************************************************************
218 **********************************************************************/
219
220typedef struct MHDDaemonWrapper {
221 uint64_t fd;
222 struct MHD_Daemon *daemon;
223
224 sd_event_source *event;
225} MHDDaemonWrapper;
226
fdfccdbc
ZJS
227typedef struct RemoteServer {
228 RemoteSource **sources;
ca2d3784
ZJS
229 size_t sources_size;
230 size_t active;
fdfccdbc
ZJS
231
232 sd_event *events;
233 sd_event_source *sigterm_event, *sigint_event, *listen_event;
234
235 Writer writer;
cc64d017
ZJS
236
237 Hashmap *daemons;
fdfccdbc
ZJS
238} RemoteServer;
239
cc64d017
ZJS
240/* This should go away as soon as µhttpd allows state to be passed around. */
241static RemoteServer *server;
242
fdfccdbc
ZJS
243static int dispatch_raw_source_event(sd_event_source *event,
244 int fd,
245 uint32_t revents,
246 void *userdata);
247static int dispatch_raw_connection_event(sd_event_source *event,
248 int fd,
249 uint32_t revents,
250 void *userdata);
cc64d017
ZJS
251static int dispatch_http_event(sd_event_source *event,
252 int fd,
253 uint32_t revents,
254 void *userdata);
fdfccdbc
ZJS
255
256static int get_source_for_fd(RemoteServer *s, int fd, RemoteSource **source) {
257 assert(fd >= 0);
258 assert(source);
259
ca2d3784 260 if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1))
fdfccdbc
ZJS
261 return log_oom();
262
263 if (s->sources[fd] == NULL) {
264 s->sources[fd] = new0(RemoteSource, 1);
265 if (!s->sources[fd])
266 return log_oom();
267 s->sources[fd]->fd = -1;
268 s->active++;
269 }
270
271 *source = s->sources[fd];
272 return 0;
273}
274
275static int remove_source(RemoteServer *s, int fd) {
276 RemoteSource *source;
277
278 assert(s);
ca2d3784 279 assert(fd >= 0 && fd < (ssize_t) s->sources_size);
fdfccdbc
ZJS
280
281 source = s->sources[fd];
282 if (source) {
283 source_free(source);
284 s->sources[fd] = NULL;
285 s->active--;
286 }
287
288 close(fd);
289
290 return 0;
291}
292
293static int add_source(RemoteServer *s, int fd, const char* name) {
294 RemoteSource *source = NULL;
e94b5a7b 295 _cleanup_free_ char *realname = NULL;
fdfccdbc
ZJS
296 int r;
297
298 assert(s);
299 assert(fd >= 0);
300
301 if (name) {
302 realname = strdup(name);
303 if (!realname)
304 return log_oom();
305 } else {
306 r = asprintf(&realname, "fd:%d", fd);
307 if (r < 0)
308 return log_oom();
309 }
310
e94b5a7b 311 log_debug("Creating source for fd:%d (%s)", fd, realname);
fdfccdbc
ZJS
312
313 r = get_source_for_fd(s, fd, &source);
314 if (r < 0) {
e94b5a7b 315 log_error("Failed to create source for fd:%d (%s)", fd, realname);
fdfccdbc
ZJS
316 return r;
317 }
318 assert(source);
319 assert(source->fd < 0);
320 source->fd = fd;
321
322 r = sd_event_add_io(s->events, &source->event,
323 fd, EPOLLIN, dispatch_raw_source_event, s);
324 if (r < 0) {
325 log_error("Failed to register event source for fd:%d: %s",
326 fd, strerror(-r));
327 goto error;
328 }
329
330 return 1; /* work to do */
331
332 error:
333 remove_source(s, fd);
334 return r;
335}
336
8a8d55f2
ZJS
337static int add_raw_socket(RemoteServer *s, int fd) {
338 int r;
fdfccdbc
ZJS
339
340 r = sd_event_add_io(s->events, &s->listen_event, fd, EPOLLIN,
341 dispatch_raw_connection_event, s);
342 if (r < 0) {
343 close(fd);
344 return r;
345 }
346
347 s->active ++;
348 return 0;
349}
350
8a8d55f2
ZJS
351static int setup_raw_socket(RemoteServer *s, const char *address) {
352 int fd;
353
354 fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
355 if (fd < 0)
356 return fd;
357
358 return add_raw_socket(s, fd);
359}
360
cc64d017
ZJS
361/**********************************************************************
362 **********************************************************************
363 **********************************************************************/
364
365static RemoteSource *request_meta(void **connection_cls) {
366 RemoteSource *source;
367
368 assert(connection_cls);
369 if (*connection_cls)
370 return *connection_cls;
371
372 source = new0(RemoteSource, 1);
373 if (!source)
374 return NULL;
375 source->fd = -1;
376
377 log_debug("Added RemoteSource as connection metadata %p", source);
378
379 *connection_cls = source;
380 return source;
381}
382
383static void request_meta_free(void *cls,
384 struct MHD_Connection *connection,
385 void **connection_cls,
386 enum MHD_RequestTerminationCode toe) {
387 RemoteSource *s;
388
389 assert(connection_cls);
390 s = *connection_cls;
391
392 log_debug("Cleaning up connection metadata %p", s);
393 source_free(s);
394 *connection_cls = NULL;
395}
396
397static int process_http_upload(
398 struct MHD_Connection *connection,
399 const char *upload_data,
400 size_t *upload_data_size,
401 RemoteSource *source) {
402
403 bool finished = false;
404 int r;
405
406 assert(source);
407
408 log_debug("request_handler_upload: connection %p, %zu bytes",
409 connection, *upload_data_size);
410
411 if (*upload_data_size) {
412 log_info("Received %zu bytes", *upload_data_size);
413
414 r = push_data(source, upload_data, *upload_data_size);
415 if (r < 0) {
416 log_error("Failed to store received data of size %zu: %s",
417 *upload_data_size, strerror(-r));
e7216d11 418 return mhd_respond_oom(connection);
cc64d017
ZJS
419 }
420 *upload_data_size = 0;
421 } else
422 finished = true;
423
424 while (true) {
425 r = process_source(source, &server->writer, arg_compress, arg_seal);
426 if (r == -E2BIG)
427 log_warning("Entry too big, skipped");
428 else if (r == -EAGAIN || r == -EWOULDBLOCK)
429 break;
430 else if (r < 0) {
431 log_warning("Failed to process data for connection %p", connection);
e7216d11
ZJS
432 return mhd_respondf(connection, MHD_HTTP_UNPROCESSABLE_ENTITY,
433 "Processing failed: %s", strerror(-r));
cc64d017
ZJS
434 }
435 }
436
437 if (!finished)
438 return MHD_YES;
439
440 /* The upload is finished */
441
442 if (source_non_empty(source)) {
443 log_warning("EOF reached with incomplete data");
e7216d11
ZJS
444 return mhd_respond(connection, MHD_HTTP_EXPECTATION_FAILED,
445 "Trailing data not processed.");
cc64d017
ZJS
446 }
447
e7216d11 448 return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n");
cc64d017
ZJS
449};
450
451static int request_handler(
452 void *cls,
453 struct MHD_Connection *connection,
454 const char *url,
455 const char *method,
456 const char *version,
457 const char *upload_data,
458 size_t *upload_data_size,
459 void **connection_cls) {
460
461 const char *header;
462 int r ,code;
463
464 assert(connection);
465 assert(connection_cls);
466 assert(url);
467 assert(method);
468
469 log_debug("Handling a connection %s %s %s", method, url, version);
470
471 if (*connection_cls)
472 return process_http_upload(connection,
473 upload_data, upload_data_size,
474 *connection_cls);
475
476 if (!streq(method, "POST"))
e7216d11
ZJS
477 return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
478 "Unsupported method.\n");
cc64d017
ZJS
479
480 if (!streq(url, "/upload"))
e7216d11
ZJS
481 return mhd_respond(connection, MHD_HTTP_NOT_FOUND,
482 "Not found.\n");
cc64d017
ZJS
483
484 header = MHD_lookup_connection_value(connection,
485 MHD_HEADER_KIND, "Content-Type");
486 if (!header || !streq(header, "application/vnd.fdo.journal"))
e7216d11
ZJS
487 return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
488 "Content-Type: application/vnd.fdo.journal"
489 " is required.\n");
cc64d017
ZJS
490
491 if (trust_pem) {
492 r = check_permissions(connection, &code);
493 if (r < 0)
494 return code;
495 }
496
497 if (!request_meta(connection_cls))
498 return respond_oom(connection);
499 return MHD_YES;
500}
501
502static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) {
503 struct MHD_OptionItem opts[] = {
504 { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free},
505 { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger},
506 { MHD_OPTION_LISTEN_SOCKET, fd},
507 { MHD_OPTION_END},
508 { MHD_OPTION_END},
509 { MHD_OPTION_END},
510 { MHD_OPTION_END}};
511 int opts_pos = 3;
512 int flags =
513 MHD_USE_DEBUG |
514 MHD_USE_PEDANTIC_CHECKS |
515 MHD_USE_EPOLL_LINUX_ONLY |
516 MHD_USE_DUAL_STACK;
517
518 const union MHD_DaemonInfo *info;
519 int r, epoll_fd;
520 MHDDaemonWrapper *d;
521
522 assert(fd >= 0);
523
524 r = fd_nonblock(fd, true);
525 if (r < 0) {
526 log_error("Failed to make fd:%d nonblocking: %s", fd, strerror(-r));
527 return r;
528 }
529
530 if (https) {
531 opts[opts_pos++] = (struct MHD_OptionItem)
532 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
533 opts[opts_pos++] = (struct MHD_OptionItem)
534 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
535
536 flags |= MHD_USE_SSL;
537
538 if (trust_pem)
539 opts[opts_pos++] = (struct MHD_OptionItem)
540 {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
541 }
542
543 d = new(MHDDaemonWrapper, 1);
544 if (!d)
545 return log_oom();
546
547 d->fd = (uint64_t) fd;
548
549 d->daemon = MHD_start_daemon(flags, 0,
550 NULL, NULL,
551 request_handler, NULL,
552 MHD_OPTION_ARRAY, opts,
553 MHD_OPTION_END);
554 if (!d->daemon) {
555 log_error("Failed to start µhttp daemon");
556 r = -EINVAL;
557 goto error;
558 }
559
560 log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)",
561 https ? "HTTPS" : "HTTP", fd, d);
562
563
564 info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
565 if (!info) {
566 log_error("µhttp returned NULL daemon info");
567 r = -ENOTSUP;
568 goto error;
569 }
570
571 epoll_fd = info->listen_fd;
572 if (epoll_fd < 0) {
573 log_error("µhttp epoll fd is invalid");
574 r = -EUCLEAN;
575 goto error;
576 }
577
578 r = sd_event_add_io(s->events, &d->event,
579 epoll_fd, EPOLLIN, dispatch_http_event, d);
580 if (r < 0) {
581 log_error("Failed to add event callback: %s", strerror(-r));
582 goto error;
583 }
584
585 r = hashmap_ensure_allocated(&s->daemons, uint64_hash_func, uint64_compare_func);
586 if (r < 0) {
587 log_oom();
588 goto error;
589 }
590
591 r = hashmap_put(s->daemons, &d->fd, d);
592 if (r < 0) {
593 log_error("Failed to add daemon to hashmap: %s", strerror(-r));
594 goto error;
595 }
596
597 s->active ++;
598 return 0;
599
600error:
601 MHD_stop_daemon(d->daemon);
602 free(d->daemon);
603 free(d);
604 return r;
605}
606
607static int setup_microhttpd_socket(RemoteServer *s,
608 const char *address,
609 bool https) {
610 int fd;
611
612 fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
613 if (fd < 0)
614 return fd;
615
616 return setup_microhttpd_server(s, fd, https);
617}
618
619static int dispatch_http_event(sd_event_source *event,
620 int fd,
621 uint32_t revents,
622 void *userdata) {
623 MHDDaemonWrapper *d = userdata;
624 int r;
625
626 assert(d);
627
628 log_info("%s", __func__);
629
630 r = MHD_run(d->daemon);
631 if (r == MHD_NO) {
632 log_error("MHD_run failed!");
633 // XXX: unregister daemon
634 return -EINVAL;
635 }
636
637 return 1; /* work to do */
638}
639
fdfccdbc
ZJS
640/**********************************************************************
641 **********************************************************************
642 **********************************************************************/
643
644static int dispatch_sigterm(sd_event_source *event,
645 const struct signalfd_siginfo *si,
646 void *userdata) {
647 RemoteServer *s = userdata;
648
649 assert(s);
650
651 log_received_signal(LOG_INFO, si);
652
653 sd_event_exit(s->events, 0);
654 return 0;
655}
656
657static int setup_signals(RemoteServer *s) {
658 sigset_t mask;
659 int r;
660
661 assert(s);
662
663 assert_se(sigemptyset(&mask) == 0);
664 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
665 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
666
667 r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, dispatch_sigterm, s);
668 if (r < 0)
669 return r;
670
671 r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, dispatch_sigterm, s);
672 if (r < 0)
673 return r;
674
675 return 0;
676}
677
8a8d55f2
ZJS
678static int fd_fd(const char *spec) {
679 int fd, r;
680
681 r = safe_atoi(spec, &fd);
682 if (r < 0)
683 return r;
684
685 if (fd >= 0)
686 return -ENOENT;
687
688 return -fd;
689}
690
691
fdfccdbc
ZJS
692static int remoteserver_init(RemoteServer *s) {
693 int r, n, fd;
694 const char *output_name = NULL;
8a8d55f2 695 char **file;
fdfccdbc
ZJS
696
697 assert(s);
698
699 sd_event_default(&s->events);
700
701 setup_signals(s);
702
cc64d017
ZJS
703 assert(server == NULL);
704 server = s;
705
fdfccdbc
ZJS
706 n = sd_listen_fds(true);
707 if (n < 0) {
708 log_error("Failed to read listening file descriptors from environment: %s",
709 strerror(-n));
710 return n;
711 } else
712 log_info("Received %d descriptors", n);
713
8a8d55f2
ZJS
714 if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) {
715 log_error("Received fewer sockets than expected");
716 return -EBADFD;
717 }
718
fdfccdbc
ZJS
719 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
720 if (sd_is_socket(fd, AF_UNSPEC, 0, false)) {
8a8d55f2
ZJS
721 log_info("Received a listening socket (fd:%d)", fd);
722
723 if (fd == http_socket)
724 r = setup_microhttpd_server(s, fd, false);
725 else if (fd == https_socket)
726 r = setup_microhttpd_server(s, fd, true);
727 else
728 r = add_raw_socket(s, fd);
fdfccdbc
ZJS
729 } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
730 log_info("Received a connection socket (fd:%d)", fd);
731
732 r = add_source(s, fd, NULL);
fdfccdbc
ZJS
733 } else {
734 log_error("Unknown socket passed on fd:%d", fd);
8a8d55f2 735
fdfccdbc
ZJS
736 return -EINVAL;
737 }
8a8d55f2
ZJS
738
739 if(r < 0) {
740 log_error("Failed to register socket (fd:%d): %s",
741 fd, strerror(-r));
742 return r;
743 }
744
745 output_name = "socket";
fdfccdbc
ZJS
746 }
747
748 if (arg_url) {
c8b32e11
TA
749 _cleanup_free_ char *url = NULL;
750 _cleanup_strv_free_ char **urlv = strv_new(arg_url, "/entries", NULL);
fdfccdbc
ZJS
751 if (!urlv)
752 return log_oom();
753 url = strv_join(urlv, "");
754 if (!url)
755 return log_oom();
756
757 if (arg_getter) {
758 log_info("Spawning getter %s...", url);
759 fd = spawn_getter(arg_getter, url);
760 } else {
761 log_info("Spawning curl %s...", url);
762 fd = spawn_curl(url);
763 }
764 if (fd < 0)
765 return fd;
766
767 r = add_source(s, fd, arg_url);
768 if (r < 0)
769 return r;
770
771 output_name = arg_url;
772 }
773
774 if (arg_listen_raw) {
775 log_info("Listening on a socket...");
776 r = setup_raw_socket(s, arg_listen_raw);
777 if (r < 0)
778 return r;
779
780 output_name = arg_listen_raw;
781 }
782
cc64d017
ZJS
783 if (arg_listen_http) {
784 r = setup_microhttpd_socket(s, arg_listen_http, false);
785 if (r < 0)
786 return r;
787
788 output_name = arg_listen_http;
789 }
790
791 if (arg_listen_https) {
792 r = setup_microhttpd_socket(s, arg_listen_https, true);
793 if (r < 0)
794 return r;
795
796 output_name = arg_listen_https;
797 }
798
8a8d55f2
ZJS
799 STRV_FOREACH(file, arg_files) {
800 if (streq(*file, "-")) {
801 log_info("Reading standard input...");
802
803 fd = STDIN_FILENO;
804 output_name = "stdin";
805 } else {
806 log_info("Reading file %s...", *file);
807
808 fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
809 if (fd < 0) {
810 log_error("Failed to open %s: %m", *file);
811 return -errno;
812 }
813 output_name = *file;
814 }
815
816 r = add_source(s, fd, output_name);
fdfccdbc
ZJS
817 if (r < 0)
818 return r;
fdfccdbc
ZJS
819 }
820
821 if (s->active == 0) {
822 log_error("Zarro sources specified");
823 return -EINVAL;
824 }
825
8a8d55f2 826 if (!!n + !!arg_url + !!arg_listen_raw + !!arg_files)
fdfccdbc
ZJS
827 output_name = "multiple";
828
829 r = writer_init(&s->writer);
830 if (r < 0)
831 return r;
832
833 r = open_output(&s->writer, output_name);
834 return r;
835}
836
837static int server_destroy(RemoteServer *s) {
838 int r;
ca2d3784 839 size_t i;
cc64d017 840 MHDDaemonWrapper *d;
fdfccdbc
ZJS
841
842 r = writer_close(&s->writer);
843
cc64d017
ZJS
844 while ((d = hashmap_steal_first(s->daemons))) {
845 MHD_stop_daemon(d->daemon);
846 sd_event_source_unref(d->event);
847 free(d);
848 }
849
850 hashmap_free(s->daemons);
851
fdfccdbc 852 assert(s->sources_size == 0 || s->sources);
cc64d017 853 for (i = 0; i < s->sources_size; i++)
fdfccdbc
ZJS
854 remove_source(s, i);
855
856 free(s->sources);
857
858 sd_event_source_unref(s->sigterm_event);
859 sd_event_source_unref(s->sigint_event);
860 sd_event_source_unref(s->listen_event);
861 sd_event_unref(s->events);
862
863 /* fds that we're listening on remain open... */
864
865 return r;
866}
867
868/**********************************************************************
869 **********************************************************************
870 **********************************************************************/
871
872static int dispatch_raw_source_event(sd_event_source *event,
873 int fd,
874 uint32_t revents,
875 void *userdata) {
876
877 RemoteServer *s = userdata;
878 RemoteSource *source;
879 int r;
880
ca2d3784 881 assert(fd >= 0 && fd < (ssize_t) s->sources_size);
fdfccdbc
ZJS
882 source = s->sources[fd];
883 assert(source->fd == fd);
884
885 r = process_source(source, &s->writer, arg_compress, arg_seal);
886 if (source->state == STATE_EOF) {
887 log_info("EOF reached with source fd:%d (%s)",
888 source->fd, source->name);
889 if (source_non_empty(source))
890 log_warning("EOF reached with incomplete data");
891 remove_source(s, source->fd);
892 log_info("%zd active source remaining", s->active);
893 } else if (r == -E2BIG) {
894 log_error("Entry too big, skipped");
895 r = 1;
896 }
897
898 return r;
899}
900
cc64d017 901static int accept_connection(const char* type, int fd, SocketAddress *addr) {
fdfccdbc
ZJS
902 int fd2, r;
903
cc64d017
ZJS
904 log_debug("Accepting new %s connection on fd:%d", type, fd);
905 fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC);
fdfccdbc
ZJS
906 if (fd2 < 0) {
907 log_error("accept() on fd:%d failed: %m", fd);
908 return -errno;
909 }
910
cc64d017 911 switch(socket_address_family(addr)) {
fdfccdbc
ZJS
912 case AF_INET:
913 case AF_INET6: {
914 char* _cleanup_free_ a = NULL;
915
cc64d017 916 r = socket_address_print(addr, &a);
fdfccdbc
ZJS
917 if (r < 0) {
918 log_error("socket_address_print(): %s", strerror(-r));
919 close(fd2);
920 return r;
921 }
922
cc64d017
ZJS
923 log_info("Accepted %s %s connection from %s",
924 type,
925 socket_address_family(addr) == AF_INET ? "IP" : "IPv6",
fdfccdbc 926 a);
cc64d017
ZJS
927
928 return fd2;
fdfccdbc
ZJS
929 };
930 default:
cc64d017
ZJS
931 log_error("Rejected %s connection with unsupported family %d",
932 type, socket_address_family(addr));
fdfccdbc 933 close(fd2);
cc64d017 934
fdfccdbc
ZJS
935 return -EINVAL;
936 }
cc64d017 937}
fdfccdbc 938
cc64d017
ZJS
939static int dispatch_raw_connection_event(sd_event_source *event,
940 int fd,
941 uint32_t revents,
942 void *userdata) {
943 RemoteServer *s = userdata;
944 int fd2;
945 SocketAddress addr = {
946 .size = sizeof(union sockaddr_union),
947 .type = SOCK_STREAM,
948 };
fdfccdbc 949
cc64d017
ZJS
950 fd2 = accept_connection("raw", fd, &addr);
951 if (fd2 < 0)
952 return fd2;
fdfccdbc 953
cc64d017
ZJS
954 return add_source(s, fd2, NULL);
955}
fdfccdbc
ZJS
956
957/**********************************************************************
958 **********************************************************************
959 **********************************************************************/
960
5c879495 961static void help(void) {
8a8d55f2 962 printf("%s [OPTIONS...] {FILE|-}...\n\n"
fdfccdbc
ZJS
963 "Write external journal events to a journal file.\n\n"
964 "Options:\n"
965 " --url=URL Read events from systemd-journal-gatewayd at URL\n"
966 " --getter=COMMAND Read events from the output of COMMAND\n"
967 " --listen-raw=ADDR Listen for connections at ADDR\n"
cc64d017
ZJS
968 " --listen-http=ADDR Listen for HTTP connections at ADDR\n"
969 " --listen-https=ADDR Listen for HTTPS connections at ADDR\n"
fdfccdbc
ZJS
970 " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
971 " --[no-]compress Use XZ-compression in the output journal (default: yes)\n"
972 " --[no-]seal Use Event sealing in the output journal (default: no)\n"
5937a4d4
ZJS
973 " --key=FILENAME Specify key in PEM format\n"
974 " --cert=FILENAME Specify certificate in PEM format\n"
975 " --trust=FILENAME Specify CA certificate in PEM format\n"
976 " --gnutls-log=CATEGORY...\n"
977 " Specify a list of gnutls logging categories\n"
fdfccdbc
ZJS
978 " -h --help Show this help and exit\n"
979 " --version Print version string and exit\n"
980 "\n"
981 "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
982 , program_invocation_short_name);
fdfccdbc
ZJS
983}
984
985static int parse_argv(int argc, char *argv[]) {
986 enum {
987 ARG_VERSION = 0x100,
988 ARG_URL,
989 ARG_LISTEN_RAW,
cc64d017
ZJS
990 ARG_LISTEN_HTTP,
991 ARG_LISTEN_HTTPS,
fdfccdbc
ZJS
992 ARG_GETTER,
993 ARG_COMPRESS,
994 ARG_NO_COMPRESS,
995 ARG_SEAL,
996 ARG_NO_SEAL,
cc64d017
ZJS
997 ARG_KEY,
998 ARG_CERT,
999 ARG_TRUST,
5937a4d4 1000 ARG_GNUTLS_LOG,
fdfccdbc
ZJS
1001 };
1002
1003 static const struct option options[] = {
1004 { "help", no_argument, NULL, 'h' },
1005 { "version", no_argument, NULL, ARG_VERSION },
1006 { "url", required_argument, NULL, ARG_URL },
1007 { "getter", required_argument, NULL, ARG_GETTER },
1008 { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW },
cc64d017
ZJS
1009 { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP },
1010 { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS },
fdfccdbc
ZJS
1011 { "output", required_argument, NULL, 'o' },
1012 { "compress", no_argument, NULL, ARG_COMPRESS },
1013 { "no-compress", no_argument, NULL, ARG_NO_COMPRESS },
1014 { "seal", no_argument, NULL, ARG_SEAL },
1015 { "no-seal", no_argument, NULL, ARG_NO_SEAL },
cc64d017
ZJS
1016 { "key", required_argument, NULL, ARG_KEY },
1017 { "cert", required_argument, NULL, ARG_CERT },
1018 { "trust", required_argument, NULL, ARG_TRUST },
5937a4d4 1019 { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG },
fdfccdbc
ZJS
1020 {}
1021 };
1022
cc64d017 1023 int c, r;
fdfccdbc
ZJS
1024
1025 assert(argc >= 0);
1026 assert(argv);
1027
1028 while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
1029 switch(c) {
1030 case 'h':
1031 help();
1032 return 0 /* done */;
1033
1034 case ARG_VERSION:
1035 puts(PACKAGE_STRING);
1036 puts(SYSTEMD_FEATURES);
1037 return 0 /* done */;
1038
1039 case ARG_URL:
1040 if (arg_url) {
1041 log_error("cannot currently set more than one --url");
1042 return -EINVAL;
1043 }
1044
1045 arg_url = optarg;
1046 break;
1047
1048 case ARG_GETTER:
1049 if (arg_getter) {
1050 log_error("cannot currently use --getter more than once");
1051 return -EINVAL;
1052 }
1053
1054 arg_getter = optarg;
1055 break;
1056
1057 case ARG_LISTEN_RAW:
1058 if (arg_listen_raw) {
1059 log_error("cannot currently use --listen-raw more than once");
1060 return -EINVAL;
1061 }
1062
1063 arg_listen_raw = optarg;
1064 break;
1065
cc64d017 1066 case ARG_LISTEN_HTTP:
8a8d55f2 1067 if (arg_listen_http || http_socket >= 0) {
cc64d017
ZJS
1068 log_error("cannot currently use --listen-http more than once");
1069 return -EINVAL;
1070 }
1071
8a8d55f2
ZJS
1072 r = fd_fd(optarg);
1073 if (r >= 0)
1074 http_socket = r;
1075 else if (r == -ENOENT)
1076 arg_listen_http = optarg;
1077 else {
1078 log_error("Invalid port/fd specification %s: %s",
1079 optarg, strerror(-r));
1080 return -EINVAL;
1081 }
1082
cc64d017
ZJS
1083 break;
1084
1085 case ARG_LISTEN_HTTPS:
8a8d55f2 1086 if (arg_listen_https || https_socket >= 0) {
cc64d017
ZJS
1087 log_error("cannot currently use --listen-https more than once");
1088 return -EINVAL;
1089 }
1090
8a8d55f2
ZJS
1091 r = fd_fd(optarg);
1092 if (r >= 0)
1093 https_socket = r;
1094 else if (r == -ENOENT)
1095 arg_listen_https = optarg;
1096 else {
1097 log_error("Invalid port/fd specification %s: %s",
1098 optarg, strerror(-r));
1099 return -EINVAL;
1100 }
1101
cc64d017
ZJS
1102 break;
1103
1104 case ARG_KEY:
1105 if (key_pem) {
1106 log_error("Key file specified twice");
1107 return -EINVAL;
1108 }
1109 r = read_full_file(optarg, &key_pem, NULL);
1110 if (r < 0) {
1111 log_error("Failed to read key file: %s", strerror(-r));
1112 return r;
1113 }
1114 assert(key_pem);
1115 break;
1116
1117 case ARG_CERT:
1118 if (cert_pem) {
1119 log_error("Certificate file specified twice");
1120 return -EINVAL;
1121 }
1122 r = read_full_file(optarg, &cert_pem, NULL);
1123 if (r < 0) {
1124 log_error("Failed to read certificate file: %s", strerror(-r));
1125 return r;
1126 }
1127 assert(cert_pem);
1128 break;
1129
1130 case ARG_TRUST:
1131#ifdef HAVE_GNUTLS
1132 if (trust_pem) {
1133 log_error("CA certificate file specified twice");
1134 return -EINVAL;
1135 }
1136 r = read_full_file(optarg, &trust_pem, NULL);
1137 if (r < 0) {
1138 log_error("Failed to read CA certificate file: %s", strerror(-r));
1139 return r;
1140 }
1141 assert(trust_pem);
1142 break;
1143#else
1144 log_error("Option --trust is not available.");
5937a4d4 1145 return -EINVAL;
cc64d017
ZJS
1146#endif
1147
fdfccdbc
ZJS
1148 case 'o':
1149 if (arg_output) {
1150 log_error("cannot use --output/-o more than once");
1151 return -EINVAL;
1152 }
1153
1154 arg_output = optarg;
1155 break;
1156
1157 case ARG_COMPRESS:
1158 arg_compress = true;
1159 break;
1160 case ARG_NO_COMPRESS:
1161 arg_compress = false;
1162 break;
1163 case ARG_SEAL:
1164 arg_seal = true;
1165 break;
1166 case ARG_NO_SEAL:
1167 arg_seal = false;
1168 break;
1169
5937a4d4
ZJS
1170 case ARG_GNUTLS_LOG: {
1171#ifdef HAVE_GNUTLS
1172 char *word, *state;
1173 size_t size;
1174
1175 FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) {
1176 char *cat;
1177
1178 cat = strndup(word, size);
1179 if (!cat)
1180 return log_oom();
1181
1182 if (strv_consume(&arg_gnutls_log, cat) < 0)
1183 return log_oom();
1184 }
1185 break;
1186#else
1187 log_error("Option --gnutls-log is not available.");
1188 return -EINVAL;
1189#endif
1190 }
1191
fdfccdbc
ZJS
1192 case '?':
1193 return -EINVAL;
1194
1195 default:
1196 log_error("Unknown option code %c", c);
1197 return -EINVAL;
1198 }
1199
cc64d017
ZJS
1200 if (arg_listen_https && !(key_pem && cert_pem)) {
1201 log_error("Options --key and --cert must be used when https sources are specified");
1202 return -EINVAL;
1203 }
1204
8a8d55f2
ZJS
1205 if (optind < argc)
1206 arg_files = argv + optind;
fdfccdbc
ZJS
1207
1208 return 1 /* work to do */;
1209}
1210
5937a4d4 1211static int setup_gnutls_logger(char **categories) {
cc64d017
ZJS
1212 if (!arg_listen_http && !arg_listen_https)
1213 return 0;
1214
1215#ifdef HAVE_GNUTLS
5937a4d4
ZJS
1216 {
1217 char **cat;
1218 int r;
1219
1220 gnutls_global_set_log_function(log_func_gnutls);
1221
1222 if (categories)
1223 STRV_FOREACH(cat, categories) {
1224 r = log_enable_gnutls_category(*cat);
1225 if (r < 0)
1226 return r;
1227 }
1228 else
1229 log_reset_gnutls_level();
1230 }
cc64d017
ZJS
1231#endif
1232
1233 return 0;
1234}
1235
fdfccdbc
ZJS
1236int main(int argc, char **argv) {
1237 RemoteServer s = {};
1238 int r, r2;
1239
fdfccdbc
ZJS
1240 log_show_color(true);
1241 log_parse_environment();
1242
1243 r = parse_argv(argc, argv);
1244 if (r <= 0)
1245 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
1246
5937a4d4 1247 r = setup_gnutls_logger(arg_gnutls_log);
cc64d017
ZJS
1248 if (r < 0)
1249 return EXIT_FAILURE;
1250
fdfccdbc
ZJS
1251 if (remoteserver_init(&s) < 0)
1252 return EXIT_FAILURE;
1253
de0671ee
ZJS
1254 log_debug("%s running as pid "PID_FMT,
1255 program_invocation_short_name, getpid());
fdfccdbc
ZJS
1256 sd_notify(false,
1257 "READY=1\n"
1258 "STATUS=Processing requests...");
1259
1260 while (s.active) {
1261 r = sd_event_get_state(s.events);
1262 if (r < 0)
1263 break;
1264 if (r == SD_EVENT_FINISHED)
1265 break;
1266
1267 r = sd_event_run(s.events, -1);
1268 if (r < 0) {
1269 log_error("Failed to run event loop: %s", strerror(-r));
1270 break;
1271 }
1272 }
1273
1274 log_info("Finishing after writing %" PRIu64 " entries", s.writer.seqnum);
1275 r2 = server_destroy(&s);
1276
1277 sd_notify(false, "STATUS=Shutting down...");
1278
1279 return r >= 0 && r2 >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
1280}