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