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