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