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