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