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