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