]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal-remote/journal-gatewayd.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / journal-remote / journal-gatewayd.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <getopt.h>
5 #include <microhttpd.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
10 #include <unistd.h>
11
12 #include "sd-bus.h"
13 #include "sd-daemon.h"
14 #include "sd-journal.h"
15
16 #include "alloc-util.h"
17 #include "build.h"
18 #include "bus-util.h"
19 #include "errno-util.h"
20 #include "fd-util.h"
21 #include "fileio.h"
22 #include "glob-util.h"
23 #include "hostname-util.h"
24 #include "log.h"
25 #include "logs-show.h"
26 #include "main-func.h"
27 #include "memory-util.h"
28 #include "microhttpd-util.h"
29 #include "os-util.h"
30 #include "parse-util.h"
31 #include "pretty-print.h"
32 #include "sigbus.h"
33 #include "tmpfile-util.h"
34
35 #define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC)
36
37 static char *arg_key_pem = NULL;
38 static char *arg_cert_pem = NULL;
39 static char *arg_trust_pem = NULL;
40 static bool arg_merge = false;
41 static int arg_journal_type = 0;
42 static const char *arg_directory = NULL;
43 static char **arg_file = NULL;
44
45 STATIC_DESTRUCTOR_REGISTER(arg_key_pem, erase_and_freep);
46 STATIC_DESTRUCTOR_REGISTER(arg_cert_pem, freep);
47 STATIC_DESTRUCTOR_REGISTER(arg_trust_pem, freep);
48
49 typedef struct RequestMeta {
50 sd_journal *journal;
51
52 OutputMode mode;
53
54 char *cursor;
55 int64_t n_skip;
56 uint64_t n_entries;
57 bool n_entries_set;
58
59 FILE *tmp;
60 uint64_t delta, size;
61
62 int argument_parse_error;
63
64 bool follow;
65 bool discrete;
66 } RequestMeta;
67
68 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
69 [OUTPUT_SHORT] = "text/plain",
70 [OUTPUT_JSON] = "application/json",
71 [OUTPUT_JSON_SSE] = "text/event-stream",
72 [OUTPUT_JSON_SEQ] = "application/json-seq",
73 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
74 };
75
76 static RequestMeta *request_meta(void **connection_cls) {
77 RequestMeta *m;
78
79 assert(connection_cls);
80 if (*connection_cls)
81 return *connection_cls;
82
83 m = new0(RequestMeta, 1);
84 if (!m)
85 return NULL;
86
87 *connection_cls = m;
88 return m;
89 }
90
91 static void request_meta_free(
92 void *cls,
93 struct MHD_Connection *connection,
94 void **connection_cls,
95 enum MHD_RequestTerminationCode toe) {
96
97 RequestMeta *m = *connection_cls;
98
99 if (!m)
100 return;
101
102 sd_journal_close(m->journal);
103
104 safe_fclose(m->tmp);
105
106 free(m->cursor);
107 free(m);
108 }
109
110 static int open_journal(RequestMeta *m) {
111 assert(m);
112
113 if (m->journal)
114 return 0;
115
116 if (arg_directory)
117 return sd_journal_open_directory(&m->journal, arg_directory, arg_journal_type);
118 else if (arg_file)
119 return sd_journal_open_files(&m->journal, (const char**) arg_file, 0);
120 else
121 return sd_journal_open(&m->journal, (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) | arg_journal_type);
122 }
123
124 static int request_meta_ensure_tmp(RequestMeta *m) {
125 assert(m);
126
127 if (m->tmp)
128 rewind(m->tmp);
129 else {
130 _cleanup_close_ int fd = -EBADF;
131
132 fd = open_tmpfile_unlinkable("/tmp", O_RDWR|O_CLOEXEC);
133 if (fd < 0)
134 return fd;
135
136 m->tmp = take_fdopen(&fd, "w+");
137 if (!m->tmp)
138 return -errno;
139 }
140
141 return 0;
142 }
143
144 static ssize_t request_reader_entries(
145 void *cls,
146 uint64_t pos,
147 char *buf,
148 size_t max) {
149
150 RequestMeta *m = ASSERT_PTR(cls);
151 dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL;
152 sd_id128_t previous_boot_id = SD_ID128_NULL;
153 int r;
154 size_t n, k;
155
156 assert(buf);
157 assert(max > 0);
158 assert(pos >= m->delta);
159
160 pos -= m->delta;
161
162 while (pos >= m->size) {
163 off_t sz;
164
165 /* End of this entry, so let's serialize the next
166 * one */
167
168 if (m->n_entries_set &&
169 m->n_entries <= 0)
170 return MHD_CONTENT_READER_END_OF_STREAM;
171
172 if (m->n_skip < 0)
173 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
174 else if (m->n_skip > 0)
175 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
176 else
177 r = sd_journal_next(m->journal);
178
179 if (r < 0) {
180 log_error_errno(r, "Failed to advance journal pointer: %m");
181 return MHD_CONTENT_READER_END_WITH_ERROR;
182 } else if (r == 0) {
183
184 if (m->follow) {
185 r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT);
186 if (r < 0) {
187 log_error_errno(r, "Couldn't wait for journal event: %m");
188 return MHD_CONTENT_READER_END_WITH_ERROR;
189 }
190 if (r == SD_JOURNAL_NOP)
191 break;
192
193 continue;
194 }
195
196 return MHD_CONTENT_READER_END_OF_STREAM;
197 }
198
199 if (m->discrete) {
200 assert(m->cursor);
201
202 r = sd_journal_test_cursor(m->journal, m->cursor);
203 if (r < 0) {
204 log_error_errno(r, "Failed to test cursor: %m");
205 return MHD_CONTENT_READER_END_WITH_ERROR;
206 }
207
208 if (r == 0)
209 return MHD_CONTENT_READER_END_OF_STREAM;
210 }
211
212 pos -= m->size;
213 m->delta += m->size;
214
215 if (m->n_entries_set)
216 m->n_entries -= 1;
217
218 m->n_skip = 0;
219
220 r = request_meta_ensure_tmp(m);
221 if (r < 0) {
222 log_error_errno(r, "Failed to create temporary file: %m");
223 return MHD_CONTENT_READER_END_WITH_ERROR;
224 }
225
226 r = show_journal_entry(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH,
227 NULL, NULL, NULL, &previous_ts, &previous_boot_id);
228 if (r < 0) {
229 log_error_errno(r, "Failed to serialize item: %m");
230 return MHD_CONTENT_READER_END_WITH_ERROR;
231 }
232
233 sz = ftello(m->tmp);
234 if (sz == (off_t) -1) {
235 log_error_errno(errno, "Failed to retrieve file position: %m");
236 return MHD_CONTENT_READER_END_WITH_ERROR;
237 }
238
239 m->size = (uint64_t) sz;
240 }
241
242 if (m->tmp == NULL && m->follow)
243 return 0;
244
245 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
246 log_error_errno(errno, "Failed to seek to position: %m");
247 return MHD_CONTENT_READER_END_WITH_ERROR;
248 }
249
250 n = m->size - pos;
251 if (n < 1)
252 return 0;
253 if (n > max)
254 n = max;
255
256 errno = 0;
257 k = fread(buf, 1, n, m->tmp);
258 if (k != n) {
259 log_error("Failed to read from file: %s", STRERROR_OR_EOF(errno));
260 return MHD_CONTENT_READER_END_WITH_ERROR;
261 }
262
263 return (ssize_t) k;
264 }
265
266 static int request_parse_accept(
267 RequestMeta *m,
268 struct MHD_Connection *connection) {
269
270 const char *header;
271
272 assert(m);
273 assert(connection);
274
275 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
276 if (!header)
277 return 0;
278
279 if (streq(header, mime_types[OUTPUT_JSON]))
280 m->mode = OUTPUT_JSON;
281 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
282 m->mode = OUTPUT_JSON_SSE;
283 else if (streq(header, mime_types[OUTPUT_JSON_SEQ]))
284 m->mode = OUTPUT_JSON_SEQ;
285 else if (streq(header, mime_types[OUTPUT_EXPORT]))
286 m->mode = OUTPUT_EXPORT;
287 else
288 m->mode = OUTPUT_SHORT;
289
290 return 0;
291 }
292
293 static int request_parse_range(
294 RequestMeta *m,
295 struct MHD_Connection *connection) {
296
297 const char *range, *colon, *colon2;
298 int r;
299
300 assert(m);
301 assert(connection);
302
303 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
304 if (!range)
305 return 0;
306
307 if (!startswith(range, "entries="))
308 return 0;
309
310 range += 8;
311 range += strspn(range, WHITESPACE);
312
313 colon = strchr(range, ':');
314 if (!colon)
315 m->cursor = strdup(range);
316 else {
317 const char *p;
318
319 colon2 = strchr(colon + 1, ':');
320 if (colon2) {
321 _cleanup_free_ char *t = NULL;
322
323 t = strndup(colon + 1, colon2 - colon - 1);
324 if (!t)
325 return -ENOMEM;
326
327 r = safe_atoi64(t, &m->n_skip);
328 if (r < 0)
329 return r;
330 }
331
332 p = (colon2 ? colon2 : colon) + 1;
333 if (*p) {
334 r = safe_atou64(p, &m->n_entries);
335 if (r < 0)
336 return r;
337
338 if (m->n_entries <= 0)
339 return -EINVAL;
340
341 m->n_entries_set = true;
342 }
343
344 m->cursor = strndup(range, colon - range);
345 }
346
347 if (!m->cursor)
348 return -ENOMEM;
349
350 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
351 if (isempty(m->cursor))
352 m->cursor = mfree(m->cursor);
353
354 return 0;
355 }
356
357 static mhd_result request_parse_arguments_iterator(
358 void *cls,
359 enum MHD_ValueKind kind,
360 const char *key,
361 const char *value) {
362
363 RequestMeta *m = ASSERT_PTR(cls);
364 _cleanup_free_ char *p = NULL;
365 int r;
366
367 if (isempty(key)) {
368 m->argument_parse_error = -EINVAL;
369 return MHD_NO;
370 }
371
372 if (streq(key, "follow")) {
373 if (isempty(value)) {
374 m->follow = true;
375 return MHD_YES;
376 }
377
378 r = parse_boolean(value);
379 if (r < 0) {
380 m->argument_parse_error = r;
381 return MHD_NO;
382 }
383
384 m->follow = r;
385 return MHD_YES;
386 }
387
388 if (streq(key, "discrete")) {
389 if (isempty(value)) {
390 m->discrete = true;
391 return MHD_YES;
392 }
393
394 r = parse_boolean(value);
395 if (r < 0) {
396 m->argument_parse_error = r;
397 return MHD_NO;
398 }
399
400 m->discrete = r;
401 return MHD_YES;
402 }
403
404 if (streq(key, "boot")) {
405 if (isempty(value))
406 r = true;
407 else {
408 r = parse_boolean(value);
409 if (r < 0) {
410 m->argument_parse_error = r;
411 return MHD_NO;
412 }
413 }
414
415 if (r) {
416 char match[9 + 32 + 1] = "_BOOT_ID=";
417 sd_id128_t bid;
418
419 r = sd_id128_get_boot(&bid);
420 if (r < 0) {
421 log_error_errno(r, "Failed to get boot ID: %m");
422 return MHD_NO;
423 }
424
425 sd_id128_to_string(bid, match + 9);
426 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
427 if (r < 0) {
428 m->argument_parse_error = r;
429 return MHD_NO;
430 }
431 }
432
433 return MHD_YES;
434 }
435
436 p = strjoin(key, "=", strempty(value));
437 if (!p) {
438 m->argument_parse_error = log_oom();
439 return MHD_NO;
440 }
441
442 r = sd_journal_add_match(m->journal, p, 0);
443 if (r < 0) {
444 m->argument_parse_error = r;
445 return MHD_NO;
446 }
447
448 return MHD_YES;
449 }
450
451 static int request_parse_arguments(
452 RequestMeta *m,
453 struct MHD_Connection *connection) {
454
455 assert(m);
456 assert(connection);
457
458 m->argument_parse_error = 0;
459 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
460
461 return m->argument_parse_error;
462 }
463
464 static int request_handler_entries(
465 struct MHD_Connection *connection,
466 void *connection_cls) {
467
468 _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL;
469 RequestMeta *m = ASSERT_PTR(connection_cls);
470 int r;
471
472 assert(connection);
473
474 r = open_journal(m);
475 if (r < 0)
476 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m");
477
478 if (request_parse_accept(m, connection) < 0)
479 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.");
480
481 if (request_parse_range(m, connection) < 0)
482 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.");
483
484 if (request_parse_arguments(m, connection) < 0)
485 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.");
486
487 if (m->discrete) {
488 if (!m->cursor)
489 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.");
490
491 m->n_entries = 1;
492 m->n_entries_set = true;
493 }
494
495 if (m->cursor)
496 r = sd_journal_seek_cursor(m->journal, m->cursor);
497 else if (m->n_skip >= 0)
498 r = sd_journal_seek_head(m->journal);
499 else if (m->n_skip < 0)
500 r = sd_journal_seek_tail(m->journal);
501 if (r < 0)
502 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.");
503
504 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
505 if (!response)
506 return respond_oom(connection);
507
508 if (MHD_add_response_header(response, "Content-Type", mime_types[m->mode]) == MHD_NO)
509 return respond_oom(connection);
510
511 return MHD_queue_response(connection, MHD_HTTP_OK, response);
512 }
513
514 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
515 const char *eq;
516 size_t j;
517
518 eq = memchr(d, '=', l);
519 if (!eq)
520 return -EINVAL;
521
522 j = l - (eq - d + 1);
523
524 if (m == OUTPUT_JSON) {
525 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
526 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
527 fputs(" }\n", f);
528 } else {
529 fwrite(eq+1, 1, j, f);
530 fputc('\n', f);
531 }
532
533 return 0;
534 }
535
536 static ssize_t request_reader_fields(
537 void *cls,
538 uint64_t pos,
539 char *buf,
540 size_t max) {
541
542 RequestMeta *m = ASSERT_PTR(cls);
543 int r;
544 size_t n, k;
545
546 assert(buf);
547 assert(max > 0);
548 assert(pos >= m->delta);
549
550 pos -= m->delta;
551
552 while (pos >= m->size) {
553 off_t sz;
554 const void *d;
555 size_t l;
556
557 /* End of this field, so let's serialize the next
558 * one */
559
560 r = sd_journal_enumerate_unique(m->journal, &d, &l);
561 if (r < 0) {
562 log_error_errno(r, "Failed to advance field index: %m");
563 return MHD_CONTENT_READER_END_WITH_ERROR;
564 } else if (r == 0)
565 return MHD_CONTENT_READER_END_OF_STREAM;
566
567 pos -= m->size;
568 m->delta += m->size;
569
570 r = request_meta_ensure_tmp(m);
571 if (r < 0) {
572 log_error_errno(r, "Failed to create temporary file: %m");
573 return MHD_CONTENT_READER_END_WITH_ERROR;
574 }
575
576 r = output_field(m->tmp, m->mode, d, l);
577 if (r < 0) {
578 log_error_errno(r, "Failed to serialize item: %m");
579 return MHD_CONTENT_READER_END_WITH_ERROR;
580 }
581
582 sz = ftello(m->tmp);
583 if (sz == (off_t) -1) {
584 log_error_errno(errno, "Failed to retrieve file position: %m");
585 return MHD_CONTENT_READER_END_WITH_ERROR;
586 }
587
588 m->size = (uint64_t) sz;
589 }
590
591 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
592 log_error_errno(errno, "Failed to seek to position: %m");
593 return MHD_CONTENT_READER_END_WITH_ERROR;
594 }
595
596 n = m->size - pos;
597 if (n > max)
598 n = max;
599
600 errno = 0;
601 k = fread(buf, 1, n, m->tmp);
602 if (k != n) {
603 log_error("Failed to read from file: %s", STRERROR_OR_EOF(errno));
604 return MHD_CONTENT_READER_END_WITH_ERROR;
605 }
606
607 return (ssize_t) k;
608 }
609
610 static int request_handler_fields(
611 struct MHD_Connection *connection,
612 const char *field,
613 void *connection_cls) {
614
615 _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL;
616 RequestMeta *m = ASSERT_PTR(connection_cls);
617 int r;
618
619 assert(connection);
620
621 r = open_journal(m);
622 if (r < 0)
623 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m");
624
625 if (request_parse_accept(m, connection) < 0)
626 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.");
627
628 r = sd_journal_query_unique(m->journal, field);
629 if (r < 0)
630 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.");
631
632 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
633 if (!response)
634 return respond_oom(connection);
635
636 if (MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]) == MHD_NO)
637 return respond_oom(connection);
638
639 return MHD_queue_response(connection, MHD_HTTP_OK, response);
640 }
641
642 static int request_handler_redirect(
643 struct MHD_Connection *connection,
644 const char *target) {
645
646 _cleanup_free_ char *page = NULL;
647 _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL;
648
649 assert(connection);
650 assert(target);
651
652 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
653 return respond_oom(connection);
654
655 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
656 if (!response)
657 return respond_oom(connection);
658 TAKE_PTR(page);
659
660 if (MHD_add_response_header(response, "Content-Type", "text/html") == MHD_NO ||
661 MHD_add_response_header(response, "Location", target) == MHD_NO)
662 return respond_oom(connection);
663
664 return MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
665 }
666
667 static int request_handler_file(
668 struct MHD_Connection *connection,
669 const char *path,
670 const char *mime_type) {
671
672 _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL;
673 _cleanup_close_ int fd = -EBADF;
674 struct stat st;
675
676 assert(connection);
677 assert(path);
678 assert(mime_type);
679
680 fd = open(path, O_RDONLY|O_CLOEXEC);
681 if (fd < 0)
682 return mhd_respondf(connection, errno, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m", path);
683
684 if (fstat(fd, &st) < 0)
685 return mhd_respondf(connection, errno, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m");
686
687 response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0);
688 if (!response)
689 return respond_oom(connection);
690 TAKE_FD(fd);
691
692 if (MHD_add_response_header(response, "Content-Type", mime_type) == MHD_NO)
693 return respond_oom(connection);
694
695 return MHD_queue_response(connection, MHD_HTTP_OK, response);
696 }
697
698 static int get_virtualization(char **v) {
699 _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
700 char *b = NULL;
701 int r;
702
703 r = sd_bus_default_system(&bus);
704 if (r < 0)
705 return r;
706
707 r = sd_bus_get_property_string(
708 bus,
709 "org.freedesktop.systemd1",
710 "/org/freedesktop/systemd1",
711 "org.freedesktop.systemd1.Manager",
712 "Virtualization",
713 NULL,
714 &b);
715 if (r < 0)
716 return r;
717
718 if (isempty(b)) {
719 free(b);
720 *v = NULL;
721 return 0;
722 }
723
724 *v = b;
725 return 1;
726 }
727
728 static int request_handler_machine(
729 struct MHD_Connection *connection,
730 void *connection_cls) {
731
732 _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL;
733 RequestMeta *m = ASSERT_PTR(connection_cls);
734 int r;
735 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
736 uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0;
737 sd_id128_t mid, bid;
738 _cleanup_free_ char *v = NULL, *json = NULL;
739
740 assert(connection);
741
742 r = open_journal(m);
743 if (r < 0)
744 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m");
745
746 r = sd_id128_get_machine(&mid);
747 if (r < 0)
748 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %m");
749
750 r = sd_id128_get_boot(&bid);
751 if (r < 0)
752 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %m");
753
754 hostname = gethostname_malloc();
755 if (!hostname)
756 return respond_oom(connection);
757
758 r = sd_journal_get_usage(m->journal, &usage);
759 if (r < 0)
760 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %m");
761
762 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
763 if (r < 0)
764 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %m");
765
766 (void) parse_os_release(NULL, "PRETTY_NAME", &os_name);
767 (void) get_virtualization(&v);
768
769 r = asprintf(&json,
770 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
771 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
772 "\"hostname\" : \"%s\","
773 "\"os_pretty_name\" : \"%s\","
774 "\"virtualization\" : \"%s\","
775 "\"usage\" : \"%"PRIu64"\","
776 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
777 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
778 SD_ID128_FORMAT_VAL(mid),
779 SD_ID128_FORMAT_VAL(bid),
780 hostname_cleanup(hostname),
781 os_name ? os_name : "Linux",
782 v ? v : "bare",
783 usage,
784 cutoff_from,
785 cutoff_to);
786 if (r < 0)
787 return respond_oom(connection);
788
789 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
790 if (!response)
791 return respond_oom(connection);
792 TAKE_PTR(json);
793
794 if (MHD_add_response_header(response, "Content-Type", "application/json") == MHD_NO)
795 return respond_oom(connection);
796
797 return MHD_queue_response(connection, MHD_HTTP_OK, response);
798 }
799
800 static mhd_result request_handler(
801 void *cls,
802 struct MHD_Connection *connection,
803 const char *url,
804 const char *method,
805 const char *version,
806 const char *upload_data,
807 size_t *upload_data_size,
808 void **connection_cls) {
809 int r, code;
810
811 assert(connection);
812 assert(connection_cls);
813 assert(url);
814 assert(method);
815
816 if (!streq(method, "GET"))
817 return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method.");
818
819 if (!*connection_cls) {
820 if (!request_meta(connection_cls))
821 return respond_oom(connection);
822 return MHD_YES;
823 }
824
825 if (arg_trust_pem) {
826 r = check_permissions(connection, &code, NULL);
827 if (r < 0)
828 return code;
829 }
830
831 if (streq(url, "/"))
832 return request_handler_redirect(connection, "/browse");
833
834 if (streq(url, "/entries"))
835 return request_handler_entries(connection, *connection_cls);
836
837 if (startswith(url, "/fields/"))
838 return request_handler_fields(connection, url + 8, *connection_cls);
839
840 if (streq(url, "/browse"))
841 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
842
843 if (streq(url, "/machine"))
844 return request_handler_machine(connection, *connection_cls);
845
846 return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.");
847 }
848
849 static int help(void) {
850 _cleanup_free_ char *link = NULL;
851 int r;
852
853 r = terminal_urlify_man("systemd-journal-gatewayd.service", "8", &link);
854 if (r < 0)
855 return log_oom();
856
857 printf("%s [OPTIONS...] ...\n\n"
858 "HTTP server for journal events.\n\n"
859 " -h --help Show this help\n"
860 " --version Show package version\n"
861 " --cert=CERT.PEM Server certificate in PEM format\n"
862 " --key=KEY.PEM Server key in PEM format\n"
863 " --trust=CERT.PEM Certificate authority certificate in PEM format\n"
864 " --system Serve system journal\n"
865 " --user Serve the user journal for the current user\n"
866 " -m --merge Serve all available journals\n"
867 " -D --directory=PATH Serve journal files in directory\n"
868 " --file=PATH Serve this journal file\n"
869 "\nSee the %s for details.\n",
870 program_invocation_short_name,
871 link);
872
873 return 0;
874 }
875
876 static int parse_argv(int argc, char *argv[]) {
877 enum {
878 ARG_VERSION = 0x100,
879 ARG_KEY,
880 ARG_CERT,
881 ARG_TRUST,
882 ARG_USER,
883 ARG_SYSTEM,
884 ARG_MERGE,
885 ARG_FILE,
886 };
887
888 int r, c;
889
890 static const struct option options[] = {
891 { "help", no_argument, NULL, 'h' },
892 { "version", no_argument, NULL, ARG_VERSION },
893 { "key", required_argument, NULL, ARG_KEY },
894 { "cert", required_argument, NULL, ARG_CERT },
895 { "trust", required_argument, NULL, ARG_TRUST },
896 { "user", no_argument, NULL, ARG_USER },
897 { "system", no_argument, NULL, ARG_SYSTEM },
898 { "merge", no_argument, NULL, 'm' },
899 { "directory", required_argument, NULL, 'D' },
900 { "file", required_argument, NULL, ARG_FILE },
901 {}
902 };
903
904 assert(argc >= 0);
905 assert(argv);
906
907 while ((c = getopt_long(argc, argv, "hD:", options, NULL)) >= 0)
908
909 switch (c) {
910
911 case 'h':
912 return help();
913
914 case ARG_VERSION:
915 return version();
916
917 case ARG_KEY:
918 if (arg_key_pem)
919 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
920 "Key file specified twice");
921 r = read_full_file_full(
922 AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
923 READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
924 NULL,
925 &arg_key_pem, NULL);
926 if (r < 0)
927 return log_error_errno(r, "Failed to read key file: %m");
928 assert(arg_key_pem);
929 break;
930
931 case ARG_CERT:
932 if (arg_cert_pem)
933 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
934 "Certificate file specified twice");
935 r = read_full_file_full(
936 AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
937 READ_FULL_FILE_CONNECT_SOCKET,
938 NULL,
939 &arg_cert_pem, NULL);
940 if (r < 0)
941 return log_error_errno(r, "Failed to read certificate file: %m");
942 assert(arg_cert_pem);
943 break;
944
945 case ARG_TRUST:
946 #if HAVE_GNUTLS
947 if (arg_trust_pem)
948 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
949 "CA certificate file specified twice");
950 r = read_full_file_full(
951 AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
952 READ_FULL_FILE_CONNECT_SOCKET,
953 NULL,
954 &arg_trust_pem, NULL);
955 if (r < 0)
956 return log_error_errno(r, "Failed to read CA certificate file: %m");
957 assert(arg_trust_pem);
958 break;
959 #else
960 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
961 "Option --trust= is not available.");
962 #endif
963
964 case ARG_SYSTEM:
965 arg_journal_type |= SD_JOURNAL_SYSTEM;
966 break;
967
968 case ARG_USER:
969 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
970 break;
971
972 case 'm':
973 arg_merge = true;
974 break;
975
976 case 'D':
977 arg_directory = optarg;
978 break;
979
980 case ARG_FILE:
981 r = glob_extend(&arg_file, optarg, GLOB_NOCHECK);
982 if (r < 0)
983 return log_error_errno(r, "Failed to add paths: %m");
984 break;
985
986 case '?':
987 return -EINVAL;
988
989 default:
990 assert_not_reached();
991 }
992
993 if (optind < argc)
994 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
995 "This program does not take arguments.");
996
997 if (!!arg_key_pem != !!arg_cert_pem)
998 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
999 "Certificate and key files must be specified together");
1000
1001 if (arg_trust_pem && !arg_key_pem)
1002 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1003 "CA certificate can only be used with certificate file");
1004
1005 return 1;
1006 }
1007
1008 static int run(int argc, char *argv[]) {
1009 _cleanup_(MHD_stop_daemonp) struct MHD_Daemon *d = NULL;
1010 struct MHD_OptionItem opts[] = {
1011 { MHD_OPTION_NOTIFY_COMPLETED,
1012 (intptr_t) request_meta_free, NULL },
1013 { MHD_OPTION_EXTERNAL_LOGGER,
1014 (intptr_t) microhttpd_logger, NULL },
1015 { MHD_OPTION_END, 0, NULL },
1016 { MHD_OPTION_END, 0, NULL },
1017 { MHD_OPTION_END, 0, NULL },
1018 { MHD_OPTION_END, 0, NULL },
1019 { MHD_OPTION_END, 0, NULL },
1020 };
1021 int opts_pos = 2;
1022
1023 /* We force MHD_USE_ITC here, in order to make sure
1024 * libmicrohttpd doesn't use shutdown() on our listening
1025 * socket, which would break socket re-activation. See
1026 *
1027 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1028 * https://github.com/systemd/systemd/pull/1286
1029 */
1030
1031 int flags =
1032 MHD_USE_DEBUG |
1033 MHD_USE_DUAL_STACK |
1034 MHD_USE_ITC |
1035 MHD_USE_POLL_INTERNAL_THREAD |
1036 MHD_USE_THREAD_PER_CONNECTION;
1037 int r, n;
1038
1039 log_setup();
1040
1041 r = parse_argv(argc, argv);
1042 if (r <= 0)
1043 return r;
1044
1045 sigbus_install();
1046
1047 r = setup_gnutls_logger(NULL);
1048 if (r < 0)
1049 return r;
1050
1051 n = sd_listen_fds(1);
1052 if (n < 0)
1053 return log_error_errno(n, "Failed to determine passed sockets: %m");
1054 if (n > 1)
1055 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't listen on more than one socket.");
1056
1057 if (n == 1)
1058 opts[opts_pos++] = (struct MHD_OptionItem)
1059 { MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START };
1060
1061 if (arg_key_pem) {
1062 assert(arg_cert_pem);
1063 opts[opts_pos++] = (struct MHD_OptionItem)
1064 { MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem };
1065 opts[opts_pos++] = (struct MHD_OptionItem)
1066 { MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem };
1067 flags |= MHD_USE_TLS;
1068 }
1069
1070 if (arg_trust_pem) {
1071 assert(flags & MHD_USE_TLS);
1072 opts[opts_pos++] = (struct MHD_OptionItem)
1073 { MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem };
1074 }
1075
1076 d = MHD_start_daemon(flags, 19531,
1077 NULL, NULL,
1078 request_handler, NULL,
1079 MHD_OPTION_ARRAY, opts,
1080 MHD_OPTION_END);
1081 if (!d)
1082 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to start daemon!");
1083
1084 pause();
1085
1086 return 0;
1087 }
1088
1089 DEFINE_MAIN_FUNCTION(run);