]> git.proxmox.com Git - systemd.git/blame - src/journal-remote/journal-remote-main.c
New upstream version 242
[systemd.git] / src / journal-remote / journal-remote-main.c
CommitLineData
b012e921
MB
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#include <getopt.h>
4#include <unistd.h>
5
6#include "sd-daemon.h"
7
8#include "conf-parser.h"
6e866b33 9#include "daemon-util.h"
b012e921
MB
10#include "def.h"
11#include "fd-util.h"
12#include "fileio.h"
13#include "journal-remote-write.h"
14#include "journal-remote.h"
6e866b33
MB
15#include "main-func.h"
16#include "pretty-print.h"
b012e921 17#include "process-util.h"
6e866b33 18#include "rlimit-util.h"
b012e921
MB
19#include "signal-util.h"
20#include "socket-util.h"
21#include "stat-util.h"
22#include "string-table.h"
23#include "strv.h"
24
25#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem"
26#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem"
27#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
28
6e866b33
MB
29static const char* arg_url = NULL;
30static const char* arg_getter = NULL;
31static const char* arg_listen_raw = NULL;
32static const char* arg_listen_http = NULL;
33static const char* arg_listen_https = NULL;
34static char** arg_files = NULL; /* Do not free this. */
b012e921
MB
35static int arg_compress = true;
36static int arg_seal = false;
37static int http_socket = -1, https_socket = -1;
38static char** arg_gnutls_log = NULL;
39
40static JournalWriteSplitMode arg_split_mode = _JOURNAL_WRITE_SPLIT_INVALID;
6e866b33 41static const char* arg_output = NULL;
b012e921
MB
42
43static char *arg_key = NULL;
44static char *arg_cert = NULL;
45static char *arg_trust = NULL;
46static bool arg_trust_all = false;
47
6e866b33
MB
48STATIC_DESTRUCTOR_REGISTER(arg_gnutls_log, strv_freep);
49STATIC_DESTRUCTOR_REGISTER(arg_key, freep);
50STATIC_DESTRUCTOR_REGISTER(arg_cert, freep);
51STATIC_DESTRUCTOR_REGISTER(arg_trust, freep);
52
b012e921
MB
53static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = {
54 [JOURNAL_WRITE_SPLIT_NONE] = "none",
55 [JOURNAL_WRITE_SPLIT_HOST] = "host",
56};
57
58DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode);
59static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode,
60 journal_write_split_mode,
61 JournalWriteSplitMode,
62 "Failed to parse split mode setting");
63
64/**********************************************************************
65 **********************************************************************
66 **********************************************************************/
67
68static int spawn_child(const char* child, char** argv) {
69 pid_t child_pid;
70 int fd[2], r;
71
72 if (pipe(fd) < 0)
73 return log_error_errno(errno, "Failed to create pager pipe: %m");
74
75 r = safe_fork("(remote)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &child_pid);
76 if (r < 0) {
77 safe_close_pair(fd);
78 return r;
79 }
80
81 /* In the child */
82 if (r == 0) {
83 safe_close(fd[0]);
84
85 r = rearrange_stdio(STDIN_FILENO, fd[1], STDERR_FILENO);
86 if (r < 0) {
87 log_error_errno(r, "Failed to dup pipe to stdout: %m");
88 _exit(EXIT_FAILURE);
89 }
90
6e866b33
MB
91 (void) rlimit_nofile_safe();
92
b012e921
MB
93 execvp(child, argv);
94 log_error_errno(errno, "Failed to exec child %s: %m", child);
95 _exit(EXIT_FAILURE);
96 }
97
98 safe_close(fd[1]);
99
100 r = fd_nonblock(fd[0], true);
101 if (r < 0)
102 log_warning_errno(errno, "Failed to set child pipe to non-blocking: %m");
103
104 return fd[0];
105}
106
107static int spawn_curl(const char* url) {
108 char **argv = STRV_MAKE("curl",
109 "-HAccept: application/vnd.fdo.journal",
110 "--silent",
111 "--show-error",
112 url);
113 int r;
114
115 r = spawn_child("curl", argv);
116 if (r < 0)
117 log_error_errno(r, "Failed to spawn curl: %m");
118 return r;
119}
120
121static int spawn_getter(const char *getter) {
122 int r;
123 _cleanup_strv_free_ char **words = NULL;
124
125 assert(getter);
126 r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES);
127 if (r < 0)
128 return log_error_errno(r, "Failed to split getter option: %m");
129
130 r = spawn_child(words[0], words);
131 if (r < 0)
132 log_error_errno(r, "Failed to spawn getter %s: %m", getter);
133
134 return r;
135}
136
137/**********************************************************************
138 **********************************************************************
139 **********************************************************************/
140
141static int null_timer_event_handler(sd_event_source *s,
142 uint64_t usec,
143 void *userdata);
144static int dispatch_http_event(sd_event_source *event,
145 int fd,
146 uint32_t revents,
147 void *userdata);
148
149static int request_meta(void **connection_cls, int fd, char *hostname) {
150 RemoteSource *source;
151 Writer *writer;
152 int r;
153
154 assert(connection_cls);
155 if (*connection_cls)
156 return 0;
157
158 r = journal_remote_get_writer(journal_remote_server_global, hostname, &writer);
159 if (r < 0)
160 return log_warning_errno(r, "Failed to get writer for source %s: %m",
161 hostname);
162
163 source = source_new(fd, true, hostname, writer);
164 if (!source) {
165 writer_unref(writer);
166 return log_oom();
167 }
168
169 log_debug("Added RemoteSource as connection metadata %p", source);
170
171 *connection_cls = source;
172 return 0;
173}
174
175static void request_meta_free(void *cls,
176 struct MHD_Connection *connection,
177 void **connection_cls,
178 enum MHD_RequestTerminationCode toe) {
179 RemoteSource *s;
180
181 assert(connection_cls);
182 s = *connection_cls;
183
184 if (s) {
185 log_debug("Cleaning up connection metadata %p", s);
186 source_free(s);
187 *connection_cls = NULL;
188 }
189}
190
191static int process_http_upload(
192 struct MHD_Connection *connection,
193 const char *upload_data,
194 size_t *upload_data_size,
195 RemoteSource *source) {
196
197 bool finished = false;
198 size_t remaining;
199 int r;
200
201 assert(source);
202
203 log_trace("%s: connection %p, %zu bytes",
204 __func__, connection, *upload_data_size);
205
206 if (*upload_data_size) {
207 log_trace("Received %zu bytes", *upload_data_size);
208
209 r = journal_importer_push_data(&source->importer,
210 upload_data, *upload_data_size);
211 if (r < 0)
212 return mhd_respond_oom(connection);
213
214 *upload_data_size = 0;
215 } else
216 finished = true;
217
218 for (;;) {
219 r = process_source(source,
220 journal_remote_server_global->compress,
221 journal_remote_server_global->seal);
222 if (r == -EAGAIN)
223 break;
7c20daf6
FS
224 if (r < 0) {
225 if (r == -ENOBUFS)
226 log_warning_errno(r, "Entry is above the maximum of %u, aborting connection %p.",
227 DATA_SIZE_MAX, connection);
228 else if (r == -E2BIG)
229 log_warning_errno(r, "Entry with more fields than the maximum of %u, aborting connection %p.",
230 ENTRY_FIELD_COUNT_MAX, connection);
b012e921 231 else
7c20daf6
FS
232 log_warning_errno(r, "Failed to process data, aborting connection %p: %m",
233 connection);
234 return MHD_NO;
b012e921
MB
235 }
236 }
237
238 if (!finished)
239 return MHD_YES;
240
241 /* The upload is finished */
242
243 remaining = journal_importer_bytes_remaining(&source->importer);
244 if (remaining > 0) {
245 log_warning("Premature EOF byte. %zu bytes lost.", remaining);
246 return mhd_respondf(connection,
247 0, MHD_HTTP_EXPECTATION_FAILED,
248 "Premature EOF. %zu bytes of trailing data not processed.",
249 remaining);
250 }
251
252 return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.");
253};
254
255static int request_handler(
256 void *cls,
257 struct MHD_Connection *connection,
258 const char *url,
259 const char *method,
260 const char *version,
261 const char *upload_data,
262 size_t *upload_data_size,
263 void **connection_cls) {
264
265 const char *header;
266 int r, code, fd;
267 _cleanup_free_ char *hostname = NULL;
bb4f798a 268 bool chunked = false;
7c20daf6 269 size_t len;
b012e921
MB
270
271 assert(connection);
272 assert(connection_cls);
273 assert(url);
274 assert(method);
275
276 log_trace("Handling a connection %s %s %s", method, url, version);
277
278 if (*connection_cls)
279 return process_http_upload(connection,
280 upload_data, upload_data_size,
281 *connection_cls);
282
283 if (!streq(method, "POST"))
284 return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method.");
285
286 if (!streq(url, "/upload"))
287 return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.");
288
7c20daf6 289 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Type");
b012e921
MB
290 if (!header || !streq(header, "application/vnd.fdo.journal"))
291 return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
292 "Content-Type: application/vnd.fdo.journal is required.");
293
bb4f798a
MB
294 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Transfer-Encoding");
295 if (header) {
296 if (!strcaseeq(header, "chunked"))
297 return mhd_respondf(connection, 0, MHD_HTTP_BAD_REQUEST,
298 "Unsupported Transfer-Encoding type: %s", header);
299
300 chunked = true;
301 }
302
7c20daf6 303 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Length");
bb4f798a
MB
304 if (header) {
305 if (chunked)
306 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST,
307 "Content-Length must not specified when Transfer-Encoding type is 'chuncked'");
308
309 r = safe_atozu(header, &len);
310 if (r < 0)
311 return mhd_respondf(connection, r, MHD_HTTP_LENGTH_REQUIRED,
312 "Content-Length: %s cannot be parsed: %m", header);
313
314 if (len > ENTRY_SIZE_MAX)
315 /* When serialized, an entry of maximum size might be slightly larger,
316 * so this does not correspond exactly to the limit in journald. Oh well.
317 */
318 return mhd_respondf(connection, 0, MHD_HTTP_PAYLOAD_TOO_LARGE,
319 "Payload larger than maximum size of %u bytes", ENTRY_SIZE_MAX);
320 }
7c20daf6 321
b012e921
MB
322 {
323 const union MHD_ConnectionInfo *ci;
324
325 ci = MHD_get_connection_info(connection,
326 MHD_CONNECTION_INFO_CONNECTION_FD);
327 if (!ci) {
328 log_error("MHD_get_connection_info failed: cannot get remote fd");
329 return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
330 "Cannot check remote address.");
331 }
332
333 fd = ci->connect_fd;
334 assert(fd >= 0);
335 }
336
337 if (journal_remote_server_global->check_trust) {
338 r = check_permissions(connection, &code, &hostname);
339 if (r < 0)
340 return code;
341 } else {
342 r = getpeername_pretty(fd, false, &hostname);
343 if (r < 0)
344 return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
345 "Cannot check remote hostname.");
346 }
347
348 assert(hostname);
349
350 r = request_meta(connection_cls, fd, hostname);
351 if (r == -ENOMEM)
352 return respond_oom(connection);
353 else if (r < 0)
354 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "%m");
355
356 hostname = NULL;
357 return MHD_YES;
358}
359
360static int setup_microhttpd_server(RemoteServer *s,
361 int fd,
362 const char *key,
363 const char *cert,
364 const char *trust) {
365 struct MHD_OptionItem opts[] = {
366 { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free},
367 { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger},
368 { MHD_OPTION_LISTEN_SOCKET, fd},
369 { MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128*1024},
370 { MHD_OPTION_END},
371 { MHD_OPTION_END},
372 { MHD_OPTION_END},
373 { MHD_OPTION_END},
374 { MHD_OPTION_END}};
375 int opts_pos = 4;
376 int flags =
377 MHD_USE_DEBUG |
378 MHD_USE_DUAL_STACK |
379 MHD_USE_EPOLL |
380 MHD_USE_ITC;
381
382 const union MHD_DaemonInfo *info;
383 int r, epoll_fd;
384 MHDDaemonWrapper *d;
385
386 assert(fd >= 0);
387
388 r = fd_nonblock(fd, true);
389 if (r < 0)
390 return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd);
391
392/* MHD_OPTION_STRICT_FOR_CLIENT is introduced in microhttpd 0.9.54,
393 * and MHD_USE_PEDANTIC_CHECKS will be deprecated in future.
394 * If MHD_USE_PEDANTIC_CHECKS is '#define'd, then it is deprecated
395 * and we should use MHD_OPTION_STRICT_FOR_CLIENT. On the other hand,
396 * if MHD_USE_PEDANTIC_CHECKS is not '#define'd, then it is not
397 * deprecated yet and there exists an enum element with the same name.
398 * So we can safely use it. */
399#ifdef MHD_USE_PEDANTIC_CHECKS
400 opts[opts_pos++] = (struct MHD_OptionItem)
401 {MHD_OPTION_STRICT_FOR_CLIENT, 1};
402#else
403 flags |= MHD_USE_PEDANTIC_CHECKS;
404#endif
405
406 if (key) {
407 assert(cert);
408
409 opts[opts_pos++] = (struct MHD_OptionItem)
410 {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key};
411 opts[opts_pos++] = (struct MHD_OptionItem)
412 {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert};
413
414 flags |= MHD_USE_TLS;
415
416 if (trust)
417 opts[opts_pos++] = (struct MHD_OptionItem)
418 {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust};
419 }
420
421 d = new(MHDDaemonWrapper, 1);
422 if (!d)
423 return log_oom();
424
425 d->fd = (uint64_t) fd;
426
427 d->daemon = MHD_start_daemon(flags, 0,
428 NULL, NULL,
429 request_handler, NULL,
430 MHD_OPTION_ARRAY, opts,
431 MHD_OPTION_END);
432 if (!d->daemon) {
433 log_error("Failed to start µhttp daemon");
434 r = -EINVAL;
435 goto error;
436 }
437
438 log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)",
439 key ? "HTTPS" : "HTTP", fd, d);
440
441 info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
442 if (!info) {
443 log_error("µhttp returned NULL daemon info");
444 r = -EOPNOTSUPP;
445 goto error;
446 }
447
448 epoll_fd = info->listen_fd;
449 if (epoll_fd < 0) {
450 log_error("µhttp epoll fd is invalid");
451 r = -EUCLEAN;
452 goto error;
453 }
454
455 r = sd_event_add_io(s->events, &d->io_event,
456 epoll_fd, EPOLLIN,
457 dispatch_http_event, d);
458 if (r < 0) {
459 log_error_errno(r, "Failed to add event callback: %m");
460 goto error;
461 }
462
463 r = sd_event_source_set_description(d->io_event, "io_event");
464 if (r < 0) {
465 log_error_errno(r, "Failed to set source name: %m");
466 goto error;
467 }
468
469 r = sd_event_add_time(s->events, &d->timer_event,
470 CLOCK_MONOTONIC, (uint64_t) -1, 0,
471 null_timer_event_handler, d);
472 if (r < 0) {
473 log_error_errno(r, "Failed to add timer_event: %m");
474 goto error;
475 }
476
477 r = sd_event_source_set_description(d->timer_event, "timer_event");
478 if (r < 0) {
479 log_error_errno(r, "Failed to set source name: %m");
480 goto error;
481 }
482
483 r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops);
484 if (r < 0) {
485 log_oom();
486 goto error;
487 }
488
489 r = hashmap_put(s->daemons, &d->fd, d);
490 if (r < 0) {
491 log_error_errno(r, "Failed to add daemon to hashmap: %m");
492 goto error;
493 }
494
495 s->active++;
496 return 0;
497
498error:
499 MHD_stop_daemon(d->daemon);
500 free(d->daemon);
501 free(d);
502 return r;
503}
504
505static int setup_microhttpd_socket(RemoteServer *s,
506 const char *address,
507 const char *key,
508 const char *cert,
509 const char *trust) {
510 int fd;
511
512 fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC);
513 if (fd < 0)
514 return fd;
515
516 return setup_microhttpd_server(s, fd, key, cert, trust);
517}
518
519static int null_timer_event_handler(sd_event_source *timer_event,
520 uint64_t usec,
521 void *userdata) {
522 return dispatch_http_event(timer_event, 0, 0, userdata);
523}
524
525static int dispatch_http_event(sd_event_source *event,
526 int fd,
527 uint32_t revents,
528 void *userdata) {
529 MHDDaemonWrapper *d = userdata;
530 int r;
531 MHD_UNSIGNED_LONG_LONG timeout = ULONG_LONG_MAX;
532
533 assert(d);
534
535 r = MHD_run(d->daemon);
6e866b33
MB
536 if (r == MHD_NO)
537 // FIXME: unregister daemon
538 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
539 "MHD_run failed!");
b012e921
MB
540 if (MHD_get_timeout(d->daemon, &timeout) == MHD_NO)
541 timeout = ULONG_LONG_MAX;
542
543 r = sd_event_source_set_time(d->timer_event, timeout);
544 if (r < 0) {
545 log_warning_errno(r, "Unable to set event loop timeout: %m, this may result in indefinite blocking!");
546 return 1;
547 }
548
549 r = sd_event_source_set_enabled(d->timer_event, SD_EVENT_ON);
550 if (r < 0)
551 log_warning_errno(r, "Unable to enable timer_event: %m, this may result in indefinite blocking!");
552
553 return 1; /* work to do */
554}
555
556/**********************************************************************
557 **********************************************************************
558 **********************************************************************/
559
560static int setup_signals(RemoteServer *s) {
561 int r;
562
563 assert(s);
564
565 assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0);
566
567 r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s);
568 if (r < 0)
569 return r;
570
571 r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s);
572 if (r < 0)
573 return r;
574
575 return 0;
576}
577
578static int setup_raw_socket(RemoteServer *s, const char *address) {
579 int fd;
580
581 fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM, SOCK_CLOEXEC);
582 if (fd < 0)
583 return fd;
584
585 return journal_remote_add_raw_socket(s, fd);
586}
587
588static int create_remoteserver(
589 RemoteServer *s,
590 const char* key,
591 const char* cert,
592 const char* trust) {
593
594 int r, n, fd;
595 char **file;
596
597 r = journal_remote_server_init(s, arg_output, arg_split_mode, arg_compress, arg_seal);
598 if (r < 0)
599 return r;
600
6e866b33
MB
601 r = setup_signals(s);
602 if (r < 0)
603 return log_error_errno(r, "Failed to set up signals: %m");
b012e921
MB
604
605 n = sd_listen_fds(true);
606 if (n < 0)
607 return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
608 else
609 log_debug("Received %d descriptors", n);
610
6e866b33
MB
611 if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n)
612 return log_error_errno(SYNTHETIC_ERRNO(EBADFD),
613 "Received fewer sockets than expected");
b012e921
MB
614
615 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
616 if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
617 log_debug("Received a listening socket (fd:%d)", fd);
618
619 if (fd == http_socket)
620 r = setup_microhttpd_server(s, fd, NULL, NULL, NULL);
621 else if (fd == https_socket)
622 r = setup_microhttpd_server(s, fd, key, cert, trust);
623 else
624 r = journal_remote_add_raw_socket(s, fd);
625 } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) {
626 char *hostname;
627
628 r = getpeername_pretty(fd, false, &hostname);
629 if (r < 0)
630 return log_error_errno(r, "Failed to retrieve remote name: %m");
631
632 log_debug("Received a connection socket (fd:%d) from %s", fd, hostname);
633
634 r = journal_remote_add_source(s, fd, hostname, true);
6e866b33
MB
635 } else
636 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
637 "Unknown socket passed on fd:%d", fd);
b012e921
MB
638
639 if (r < 0)
6e866b33 640 return log_error_errno(r, "Failed to register socket (fd:%d): %m", fd);
b012e921
MB
641 }
642
643 if (arg_getter) {
644 log_info("Spawning getter %s...", arg_getter);
645 fd = spawn_getter(arg_getter);
646 if (fd < 0)
647 return fd;
648
649 r = journal_remote_add_source(s, fd, (char*) arg_output, false);
650 if (r < 0)
651 return r;
652 }
653
654 if (arg_url) {
6e866b33 655 const char *url, *hostname;
b012e921
MB
656
657 if (!strstr(arg_url, "/entries")) {
658 if (endswith(arg_url, "/"))
659 url = strjoina(arg_url, "entries");
660 else
661 url = strjoina(arg_url, "/entries");
6e866b33 662 } else
b012e921
MB
663 url = strdupa(arg_url);
664
665 log_info("Spawning curl %s...", url);
666 fd = spawn_curl(url);
667 if (fd < 0)
668 return fd;
669
6e866b33
MB
670 hostname = STARTSWITH_SET(arg_url, "https://", "http://");
671 if (!hostname)
672 hostname = arg_url;
b012e921 673
6e866b33 674 hostname = strndupa(hostname, strcspn(hostname, "/:"));
b012e921 675
6e866b33 676 r = journal_remote_add_source(s, fd, (char *) hostname, false);
b012e921
MB
677 if (r < 0)
678 return r;
679 }
680
681 if (arg_listen_raw) {
682 log_debug("Listening on a socket...");
683 r = setup_raw_socket(s, arg_listen_raw);
684 if (r < 0)
685 return r;
686 }
687
688 if (arg_listen_http) {
689 r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL);
690 if (r < 0)
691 return r;
692 }
693
694 if (arg_listen_https) {
695 r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust);
696 if (r < 0)
697 return r;
698 }
699
700 STRV_FOREACH(file, arg_files) {
701 const char *output_name;
702
703 if (streq(*file, "-")) {
704 log_debug("Using standard input as source.");
705
706 fd = STDIN_FILENO;
707 output_name = "stdin";
708 } else {
709 log_debug("Reading file %s...", *file);
710
711 fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
712 if (fd < 0)
713 return log_error_errno(errno, "Failed to open %s: %m", *file);
714 output_name = *file;
715 }
716
717 r = journal_remote_add_source(s, fd, (char*) output_name, false);
718 if (r < 0)
719 return r;
720 }
721
6e866b33
MB
722 if (s->active == 0)
723 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
724 "Zero sources specified");
b012e921
MB
725
726 if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) {
727 /* In this case we know what the writer will be
728 called, so we can create it and verify that we can
729 create output as expected. */
730 r = journal_remote_get_writer(s, NULL, &s->_single_writer);
731 if (r < 0)
732 return r;
733 }
734
735 return 0;
736}
737
738static int negative_fd(const char *spec) {
739 /* Return a non-positive number as its inverse, -EINVAL otherwise. */
740
741 int fd, r;
742
743 r = safe_atoi(spec, &fd);
744 if (r < 0)
745 return r;
746
747 if (fd > 0)
748 return -EINVAL;
749 else
750 return -fd;
751}
752
753static int parse_config(void) {
754 const ConfigTableItem items[] = {
755 { "Remote", "Seal", config_parse_bool, 0, &arg_seal },
756 { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode },
757 { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key },
758 { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert },
759 { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust },
6e866b33
MB
760 {}
761 };
b012e921
MB
762
763 return config_parse_many_nulstr(PKGSYSCONFDIR "/journal-remote.conf",
764 CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"),
765 "Remote\0", config_item_table_lookup, items,
766 CONFIG_PARSE_WARN, NULL);
767}
768
6e866b33
MB
769static int help(void) {
770 _cleanup_free_ char *link = NULL;
771 int r;
772
773 r = terminal_urlify_man("systemd-journal-remote.service", "8", &link);
774 if (r < 0)
775 return log_oom();
776
b012e921
MB
777 printf("%s [OPTIONS...] {FILE|-}...\n\n"
778 "Write external journal events to journal file(s).\n\n"
779 " -h --help Show this help\n"
780 " --version Show package version\n"
781 " --url=URL Read events from systemd-journal-gatewayd at URL\n"
782 " --getter=COMMAND Read events from the output of COMMAND\n"
783 " --listen-raw=ADDR Listen for connections at ADDR\n"
784 " --listen-http=ADDR Listen for HTTP connections at ADDR\n"
785 " --listen-https=ADDR Listen for HTTPS connections at ADDR\n"
786 " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
787 " --compress[=BOOL] XZ-compress the output journal (default: yes)\n"
788 " --seal[=BOOL] Use event sealing (default: no)\n"
789 " --key=FILENAME SSL key in PEM format (default:\n"
790 " \"" PRIV_KEY_FILE "\")\n"
791 " --cert=FILENAME SSL certificate in PEM format (default:\n"
792 " \"" CERT_FILE "\")\n"
793 " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n"
794 " \"" TRUST_FILE "\")\n"
795 " --gnutls-log=CATEGORY...\n"
796 " Specify a list of gnutls logging categories\n"
797 " --split-mode=none|host How many output files to create\n"
6e866b33
MB
798 "\nNote: file descriptors from sd_listen_fds() will be consumed, too.\n"
799 "\nSee the %s for details.\n"
800 , program_invocation_short_name
801 , link
802 );
803
804 return 0;
b012e921
MB
805}
806
807static int parse_argv(int argc, char *argv[]) {
808 enum {
809 ARG_VERSION = 0x100,
810 ARG_URL,
811 ARG_LISTEN_RAW,
812 ARG_LISTEN_HTTP,
813 ARG_LISTEN_HTTPS,
814 ARG_GETTER,
815 ARG_SPLIT_MODE,
816 ARG_COMPRESS,
817 ARG_SEAL,
818 ARG_KEY,
819 ARG_CERT,
820 ARG_TRUST,
821 ARG_GNUTLS_LOG,
822 };
823
824 static const struct option options[] = {
825 { "help", no_argument, NULL, 'h' },
826 { "version", no_argument, NULL, ARG_VERSION },
827 { "url", required_argument, NULL, ARG_URL },
828 { "getter", required_argument, NULL, ARG_GETTER },
829 { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW },
830 { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP },
831 { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS },
832 { "output", required_argument, NULL, 'o' },
833 { "split-mode", required_argument, NULL, ARG_SPLIT_MODE },
834 { "compress", optional_argument, NULL, ARG_COMPRESS },
835 { "seal", optional_argument, NULL, ARG_SEAL },
836 { "key", required_argument, NULL, ARG_KEY },
837 { "cert", required_argument, NULL, ARG_CERT },
838 { "trust", required_argument, NULL, ARG_TRUST },
839 { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG },
840 {}
841 };
842
843 int c, r;
844 bool type_a, type_b;
845
846 assert(argc >= 0);
847 assert(argv);
848
849 while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
850 switch(c) {
6e866b33 851
b012e921 852 case 'h':
6e866b33 853 return help();
b012e921
MB
854
855 case ARG_VERSION:
856 return version();
857
858 case ARG_URL:
6e866b33
MB
859 if (arg_url)
860 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
861 "cannot currently set more than one --url");
b012e921
MB
862
863 arg_url = optarg;
864 break;
865
866 case ARG_GETTER:
6e866b33
MB
867 if (arg_getter)
868 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
869 "cannot currently use --getter more than once");
b012e921
MB
870
871 arg_getter = optarg;
872 break;
873
874 case ARG_LISTEN_RAW:
6e866b33
MB
875 if (arg_listen_raw)
876 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
877 "cannot currently use --listen-raw more than once");
b012e921
MB
878
879 arg_listen_raw = optarg;
880 break;
881
882 case ARG_LISTEN_HTTP:
6e866b33
MB
883 if (arg_listen_http || http_socket >= 0)
884 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
885 "cannot currently use --listen-http more than once");
b012e921
MB
886
887 r = negative_fd(optarg);
888 if (r >= 0)
889 http_socket = r;
890 else
891 arg_listen_http = optarg;
892 break;
893
894 case ARG_LISTEN_HTTPS:
6e866b33
MB
895 if (arg_listen_https || https_socket >= 0)
896 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
897 "cannot currently use --listen-https more than once");
b012e921
MB
898
899 r = negative_fd(optarg);
900 if (r >= 0)
901 https_socket = r;
902 else
903 arg_listen_https = optarg;
904
905 break;
906
907 case ARG_KEY:
6e866b33
MB
908 if (arg_key)
909 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
910 "Key file specified twice");
b012e921
MB
911
912 arg_key = strdup(optarg);
913 if (!arg_key)
914 return log_oom();
915
916 break;
917
918 case ARG_CERT:
6e866b33
MB
919 if (arg_cert)
920 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
921 "Certificate file specified twice");
b012e921
MB
922
923 arg_cert = strdup(optarg);
924 if (!arg_cert)
925 return log_oom();
926
927 break;
928
929 case ARG_TRUST:
6e866b33
MB
930 if (arg_trust || arg_trust_all)
931 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
932 "Confusing trusted CA configuration");
b012e921
MB
933
934 if (streq(optarg, "all"))
935 arg_trust_all = true;
936 else {
937#if HAVE_GNUTLS
938 arg_trust = strdup(optarg);
939 if (!arg_trust)
940 return log_oom();
941#else
6e866b33
MB
942 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
943 "Option --trust is not available.");
b012e921
MB
944#endif
945 }
946
947 break;
948
949 case 'o':
6e866b33
MB
950 if (arg_output)
951 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
952 "cannot use --output/-o more than once");
b012e921
MB
953
954 arg_output = optarg;
955 break;
956
957 case ARG_SPLIT_MODE:
958 arg_split_mode = journal_write_split_mode_from_string(optarg);
6e866b33
MB
959 if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID)
960 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
961 "Invalid split mode: %s", optarg);
b012e921
MB
962 break;
963
964 case ARG_COMPRESS:
965 if (optarg) {
966 r = parse_boolean(optarg);
6e866b33
MB
967 if (r < 0)
968 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
969 "Failed to parse --compress= parameter.");
b012e921
MB
970
971 arg_compress = !!r;
972 } else
973 arg_compress = true;
974
975 break;
976
977 case ARG_SEAL:
978 if (optarg) {
979 r = parse_boolean(optarg);
6e866b33
MB
980 if (r < 0)
981 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
982 "Failed to parse --seal= parameter.");
b012e921
MB
983
984 arg_seal = !!r;
985 } else
986 arg_seal = true;
987
988 break;
989
990 case ARG_GNUTLS_LOG: {
991#if HAVE_GNUTLS
992 const char* p = optarg;
993 for (;;) {
994 _cleanup_free_ char *word = NULL;
995
996 r = extract_first_word(&p, &word, ",", 0);
997 if (r < 0)
998 return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m");
b012e921
MB
999 if (r == 0)
1000 break;
1001
1002 if (strv_push(&arg_gnutls_log, word) < 0)
1003 return log_oom();
1004
1005 word = NULL;
1006 }
1007 break;
1008#else
6e866b33
MB
1009 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1010 "Option --gnutls-log is not available.");
b012e921
MB
1011#endif
1012 }
1013
1014 case '?':
1015 return -EINVAL;
1016
1017 default:
1018 assert_not_reached("Unknown option code.");
1019 }
1020
1021 if (optind < argc)
1022 arg_files = argv + optind;
1023
1024 type_a = arg_getter || !strv_isempty(arg_files);
1025 type_b = arg_url
1026 || arg_listen_raw
1027 || arg_listen_http || arg_listen_https
1028 || sd_listen_fds(false) > 0;
6e866b33
MB
1029 if (type_a && type_b)
1030 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1031 "Cannot use file input or --getter with "
1032 "--arg-listen-... or socket activation.");
b012e921 1033 if (type_a) {
6e866b33
MB
1034 if (!arg_output)
1035 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1036 "Option --output must be specified with file input or --getter.");
b012e921 1037
6e866b33
MB
1038 if (!IN_SET(arg_split_mode, JOURNAL_WRITE_SPLIT_NONE, _JOURNAL_WRITE_SPLIT_INVALID))
1039 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1040 "For active sources, only --split-mode=none is allowed.");
b012e921
MB
1041
1042 arg_split_mode = JOURNAL_WRITE_SPLIT_NONE;
1043 }
1044
1045 if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID)
1046 arg_split_mode = JOURNAL_WRITE_SPLIT_HOST;
1047
1048 if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE && arg_output) {
6e866b33
MB
1049 if (is_dir(arg_output, true) > 0)
1050 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1051 "For SplitMode=none, output must be a file.");
1052 if (!endswith(arg_output, ".journal"))
1053 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1054 "For SplitMode=none, output file name must end with .journal.");
b012e921
MB
1055 }
1056
1057 if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST
6e866b33
MB
1058 && arg_output && is_dir(arg_output, true) <= 0)
1059 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1060 "For SplitMode=host, output must be a directory.");
b012e921
MB
1061
1062 log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s",
1063 journal_write_split_mode_to_string(arg_split_mode),
1064 strna(arg_key),
1065 strna(arg_cert),
1066 strna(arg_trust));
1067
1068 return 1 /* work to do */;
1069}
1070
1071static int load_certificates(char **key, char **cert, char **trust) {
1072 int r;
1073
1074 r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL);
1075 if (r < 0)
1076 return log_error_errno(r, "Failed to read key from file '%s': %m",
1077 arg_key ?: PRIV_KEY_FILE);
1078
1079 r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL);
1080 if (r < 0)
1081 return log_error_errno(r, "Failed to read certificate from file '%s': %m",
1082 arg_cert ?: CERT_FILE);
1083
1084 if (arg_trust_all)
1085 log_info("Certificate checking disabled.");
1086 else {
1087 r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL);
1088 if (r < 0)
1089 return log_error_errno(r, "Failed to read CA certificate file '%s': %m",
1090 arg_trust ?: TRUST_FILE);
1091 }
1092
6e866b33
MB
1093 if ((arg_listen_raw || arg_listen_http) && *trust)
1094 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1095 "Option --trust makes all non-HTTPS connections untrusted.");
b012e921
MB
1096
1097 return 0;
1098}
1099
6e866b33
MB
1100static int run(int argc, char **argv) {
1101 _cleanup_(notify_on_cleanup) const char *notify_message = NULL;
1102 _cleanup_(journal_remote_server_destroy) RemoteServer s = {};
b012e921 1103 _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL;
6e866b33 1104 int r;
b012e921
MB
1105
1106 log_show_color(true);
1107 log_parse_environment();
1108
6e866b33
MB
1109 /* The journal merging logic potentially needs a lot of fds. */
1110 (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
1111
b012e921
MB
1112 r = parse_config();
1113 if (r < 0)
6e866b33 1114 return r;
b012e921
MB
1115
1116 r = parse_argv(argc, argv);
1117 if (r <= 0)
6e866b33 1118 return r;
b012e921
MB
1119
1120 if (arg_listen_http || arg_listen_https) {
1121 r = setup_gnutls_logger(arg_gnutls_log);
1122 if (r < 0)
6e866b33 1123 return r;
b012e921
MB
1124 }
1125
6e866b33
MB
1126 if (arg_listen_https || https_socket >= 0) {
1127 r = load_certificates(&key, &cert, &trust);
1128 if (r < 0)
1129 return r;
1130
1131 s.check_trust = !arg_trust_all;
1132 }
b012e921 1133
6e866b33
MB
1134 r = create_remoteserver(&s, key, cert, trust);
1135 if (r < 0)
1136 return r;
b012e921
MB
1137
1138 r = sd_event_set_watchdog(s.events, true);
1139 if (r < 0)
6e866b33
MB
1140 return log_error_errno(r, "Failed to enable watchdog: %m");
1141
1142 log_debug("Watchdog is %sd.", enable_disable(r > 0));
b012e921
MB
1143
1144 log_debug("%s running as pid "PID_FMT,
1145 program_invocation_short_name, getpid_cached());
6e866b33
MB
1146
1147 notify_message = notify_start(NOTIFY_READY, NOTIFY_STOPPING);
b012e921
MB
1148
1149 while (s.active) {
1150 r = sd_event_get_state(s.events);
1151 if (r < 0)
6e866b33 1152 return r;
b012e921
MB
1153 if (r == SD_EVENT_FINISHED)
1154 break;
1155
1156 r = sd_event_run(s.events, -1);
6e866b33
MB
1157 if (r < 0)
1158 return log_error_errno(r, "Failed to run event loop: %m");
b012e921
MB
1159 }
1160
6e866b33
MB
1161 notify_message = NULL;
1162 (void) sd_notifyf(false,
1163 "STOPPING=1\n"
1164 "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count);
b012e921 1165
6e866b33 1166 log_info("Finishing after writing %" PRIu64 " entries", s.event_count);
b012e921 1167
6e866b33 1168 return 0;
b012e921 1169}
6e866b33
MB
1170
1171DEFINE_MAIN_FUNCTION(run);