]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal-remote/journal-gatewayd.c
tree-wide: simplify x ? x : y to x ?: y where applicable
[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 ?: 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, *pretty_name = 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(
767 NULL,
768 "PRETTY_NAME", &pretty_name,
769 "NAME=", &os_name);
770 (void) get_virtualization(&v);
771
772 r = asprintf(&json,
773 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
774 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
775 "\"hostname\" : \"%s\","
776 "\"os_pretty_name\" : \"%s\","
777 "\"virtualization\" : \"%s\","
778 "\"usage\" : \"%"PRIu64"\","
779 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
780 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
781 SD_ID128_FORMAT_VAL(mid),
782 SD_ID128_FORMAT_VAL(bid),
783 hostname_cleanup(hostname),
784 os_release_pretty_name(pretty_name, os_name),
785 v ? v : "bare",
786 usage,
787 cutoff_from,
788 cutoff_to);
789 if (r < 0)
790 return respond_oom(connection);
791
792 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
793 if (!response)
794 return respond_oom(connection);
795 TAKE_PTR(json);
796
797 if (MHD_add_response_header(response, "Content-Type", "application/json") == MHD_NO)
798 return respond_oom(connection);
799
800 return MHD_queue_response(connection, MHD_HTTP_OK, response);
801 }
802
803 static mhd_result request_handler(
804 void *cls,
805 struct MHD_Connection *connection,
806 const char *url,
807 const char *method,
808 const char *version,
809 const char *upload_data,
810 size_t *upload_data_size,
811 void **connection_cls) {
812 int r, code;
813
814 assert(connection);
815 assert(connection_cls);
816 assert(url);
817 assert(method);
818
819 if (!streq(method, "GET"))
820 return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method.");
821
822 if (!*connection_cls) {
823 if (!request_meta(connection_cls))
824 return respond_oom(connection);
825 return MHD_YES;
826 }
827
828 if (arg_trust_pem) {
829 r = check_permissions(connection, &code, NULL);
830 if (r < 0)
831 return code;
832 }
833
834 if (streq(url, "/"))
835 return request_handler_redirect(connection, "/browse");
836
837 if (streq(url, "/entries"))
838 return request_handler_entries(connection, *connection_cls);
839
840 if (startswith(url, "/fields/"))
841 return request_handler_fields(connection, url + 8, *connection_cls);
842
843 if (streq(url, "/browse"))
844 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
845
846 if (streq(url, "/machine"))
847 return request_handler_machine(connection, *connection_cls);
848
849 return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.");
850 }
851
852 static int help(void) {
853 _cleanup_free_ char *link = NULL;
854 int r;
855
856 r = terminal_urlify_man("systemd-journal-gatewayd.service", "8", &link);
857 if (r < 0)
858 return log_oom();
859
860 printf("%s [OPTIONS...] ...\n\n"
861 "HTTP server for journal events.\n\n"
862 " -h --help Show this help\n"
863 " --version Show package version\n"
864 " --cert=CERT.PEM Server certificate in PEM format\n"
865 " --key=KEY.PEM Server key in PEM format\n"
866 " --trust=CERT.PEM Certificate authority certificate in PEM format\n"
867 " --system Serve system journal\n"
868 " --user Serve the user journal for the current user\n"
869 " -m --merge Serve all available journals\n"
870 " -D --directory=PATH Serve journal files in directory\n"
871 " --file=PATH Serve this journal file\n"
872 "\nSee the %s for details.\n",
873 program_invocation_short_name,
874 link);
875
876 return 0;
877 }
878
879 static int parse_argv(int argc, char *argv[]) {
880 enum {
881 ARG_VERSION = 0x100,
882 ARG_KEY,
883 ARG_CERT,
884 ARG_TRUST,
885 ARG_USER,
886 ARG_SYSTEM,
887 ARG_MERGE,
888 ARG_FILE,
889 };
890
891 int r, c;
892
893 static const struct option options[] = {
894 { "help", no_argument, NULL, 'h' },
895 { "version", no_argument, NULL, ARG_VERSION },
896 { "key", required_argument, NULL, ARG_KEY },
897 { "cert", required_argument, NULL, ARG_CERT },
898 { "trust", required_argument, NULL, ARG_TRUST },
899 { "user", no_argument, NULL, ARG_USER },
900 { "system", no_argument, NULL, ARG_SYSTEM },
901 { "merge", no_argument, NULL, 'm' },
902 { "directory", required_argument, NULL, 'D' },
903 { "file", required_argument, NULL, ARG_FILE },
904 {}
905 };
906
907 assert(argc >= 0);
908 assert(argv);
909
910 while ((c = getopt_long(argc, argv, "hD:", options, NULL)) >= 0)
911
912 switch (c) {
913
914 case 'h':
915 return help();
916
917 case ARG_VERSION:
918 return version();
919
920 case ARG_KEY:
921 if (arg_key_pem)
922 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
923 "Key file specified twice");
924 r = read_full_file_full(
925 AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
926 READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
927 NULL,
928 &arg_key_pem, NULL);
929 if (r < 0)
930 return log_error_errno(r, "Failed to read key file: %m");
931 assert(arg_key_pem);
932 break;
933
934 case ARG_CERT:
935 if (arg_cert_pem)
936 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
937 "Certificate file specified twice");
938 r = read_full_file_full(
939 AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
940 READ_FULL_FILE_CONNECT_SOCKET,
941 NULL,
942 &arg_cert_pem, NULL);
943 if (r < 0)
944 return log_error_errno(r, "Failed to read certificate file: %m");
945 assert(arg_cert_pem);
946 break;
947
948 case ARG_TRUST:
949 #if HAVE_GNUTLS
950 if (arg_trust_pem)
951 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
952 "CA certificate file specified twice");
953 r = read_full_file_full(
954 AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
955 READ_FULL_FILE_CONNECT_SOCKET,
956 NULL,
957 &arg_trust_pem, NULL);
958 if (r < 0)
959 return log_error_errno(r, "Failed to read CA certificate file: %m");
960 assert(arg_trust_pem);
961 break;
962 #else
963 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
964 "Option --trust= is not available.");
965 #endif
966
967 case ARG_SYSTEM:
968 arg_journal_type |= SD_JOURNAL_SYSTEM;
969 break;
970
971 case ARG_USER:
972 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
973 break;
974
975 case 'm':
976 arg_merge = true;
977 break;
978
979 case 'D':
980 arg_directory = optarg;
981 break;
982
983 case ARG_FILE:
984 r = glob_extend(&arg_file, optarg, GLOB_NOCHECK);
985 if (r < 0)
986 return log_error_errno(r, "Failed to add paths: %m");
987 break;
988
989 case '?':
990 return -EINVAL;
991
992 default:
993 assert_not_reached();
994 }
995
996 if (optind < argc)
997 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
998 "This program does not take arguments.");
999
1000 if (!!arg_key_pem != !!arg_cert_pem)
1001 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1002 "Certificate and key files must be specified together");
1003
1004 if (arg_trust_pem && !arg_key_pem)
1005 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1006 "CA certificate can only be used with certificate file");
1007
1008 return 1;
1009 }
1010
1011 static int run(int argc, char *argv[]) {
1012 _cleanup_(MHD_stop_daemonp) struct MHD_Daemon *d = NULL;
1013 struct MHD_OptionItem opts[] = {
1014 { MHD_OPTION_NOTIFY_COMPLETED,
1015 (intptr_t) request_meta_free, NULL },
1016 { MHD_OPTION_EXTERNAL_LOGGER,
1017 (intptr_t) microhttpd_logger, NULL },
1018 { MHD_OPTION_END, 0, NULL },
1019 { MHD_OPTION_END, 0, NULL },
1020 { MHD_OPTION_END, 0, NULL },
1021 { MHD_OPTION_END, 0, NULL },
1022 { MHD_OPTION_END, 0, NULL },
1023 };
1024 int opts_pos = 2;
1025
1026 /* We force MHD_USE_ITC here, in order to make sure
1027 * libmicrohttpd doesn't use shutdown() on our listening
1028 * socket, which would break socket re-activation. See
1029 *
1030 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1031 * https://github.com/systemd/systemd/pull/1286
1032 */
1033
1034 int flags =
1035 MHD_USE_DEBUG |
1036 MHD_USE_DUAL_STACK |
1037 MHD_USE_ITC |
1038 MHD_USE_POLL_INTERNAL_THREAD |
1039 MHD_USE_THREAD_PER_CONNECTION;
1040 int r, n;
1041
1042 log_setup();
1043
1044 r = parse_argv(argc, argv);
1045 if (r <= 0)
1046 return r;
1047
1048 sigbus_install();
1049
1050 r = setup_gnutls_logger(NULL);
1051 if (r < 0)
1052 return r;
1053
1054 n = sd_listen_fds(1);
1055 if (n < 0)
1056 return log_error_errno(n, "Failed to determine passed sockets: %m");
1057 if (n > 1)
1058 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't listen on more than one socket.");
1059
1060 if (n == 1)
1061 opts[opts_pos++] = (struct MHD_OptionItem)
1062 { MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START };
1063
1064 if (arg_key_pem) {
1065 assert(arg_cert_pem);
1066 opts[opts_pos++] = (struct MHD_OptionItem)
1067 { MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem };
1068 opts[opts_pos++] = (struct MHD_OptionItem)
1069 { MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem };
1070 flags |= MHD_USE_TLS;
1071 }
1072
1073 if (arg_trust_pem) {
1074 assert(flags & MHD_USE_TLS);
1075 opts[opts_pos++] = (struct MHD_OptionItem)
1076 { MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem };
1077 }
1078
1079 d = MHD_start_daemon(flags, 19531,
1080 NULL, NULL,
1081 request_handler, NULL,
1082 MHD_OPTION_ARRAY, opts,
1083 MHD_OPTION_END);
1084 if (!d)
1085 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to start daemon!");
1086
1087 pause();
1088
1089 return 0;
1090 }
1091
1092 DEFINE_MAIN_FUNCTION(run);