]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal-remote/journal-gatewayd.c
a2b7fd6bd97a3c73a3d5f76cff6620037abbd62e
[thirdparty/systemd.git] / src / journal-remote / journal-gatewayd.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
6 ***/
7
8 #include <fcntl.h>
9 #include <getopt.h>
10 #include <microhttpd.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14
15 #include "sd-bus.h"
16 #include "sd-daemon.h"
17 #include "sd-journal.h"
18
19 #include "alloc-util.h"
20 #include "bus-util.h"
21 #include "fd-util.h"
22 #include "fileio.h"
23 #include "hostname-util.h"
24 #include "log.h"
25 #include "logs-show.h"
26 #include "microhttpd-util.h"
27 #include "os-util.h"
28 #include "parse-util.h"
29 #include "sigbus.h"
30 #include "util.h"
31
32 #define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC)
33
34 static char *arg_key_pem = NULL;
35 static char *arg_cert_pem = NULL;
36 static char *arg_trust_pem = NULL;
37 static char *arg_directory = NULL;
38
39 typedef struct RequestMeta {
40 sd_journal *journal;
41
42 OutputMode mode;
43
44 char *cursor;
45 int64_t n_skip;
46 uint64_t n_entries;
47 bool n_entries_set;
48
49 FILE *tmp;
50 uint64_t delta, size;
51
52 int argument_parse_error;
53
54 bool follow;
55 bool discrete;
56
57 uint64_t n_fields;
58 bool n_fields_set;
59 } RequestMeta;
60
61 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
62 [OUTPUT_SHORT] = "text/plain",
63 [OUTPUT_JSON] = "application/json",
64 [OUTPUT_JSON_SSE] = "text/event-stream",
65 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
66 };
67
68 static RequestMeta *request_meta(void **connection_cls) {
69 RequestMeta *m;
70
71 assert(connection_cls);
72 if (*connection_cls)
73 return *connection_cls;
74
75 m = new0(RequestMeta, 1);
76 if (!m)
77 return NULL;
78
79 *connection_cls = m;
80 return m;
81 }
82
83 static void request_meta_free(
84 void *cls,
85 struct MHD_Connection *connection,
86 void **connection_cls,
87 enum MHD_RequestTerminationCode toe) {
88
89 RequestMeta *m = *connection_cls;
90
91 if (!m)
92 return;
93
94 sd_journal_close(m->journal);
95
96 safe_fclose(m->tmp);
97
98 free(m->cursor);
99 free(m);
100 }
101
102 static int open_journal(RequestMeta *m) {
103 assert(m);
104
105 if (m->journal)
106 return 0;
107
108 if (arg_directory)
109 return sd_journal_open_directory(&m->journal, arg_directory, 0);
110 else
111 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
112 }
113
114 static int request_meta_ensure_tmp(RequestMeta *m) {
115 assert(m);
116
117 if (m->tmp)
118 rewind(m->tmp);
119 else {
120 int fd;
121
122 fd = open_tmpfile_unlinkable("/tmp", O_RDWR|O_CLOEXEC);
123 if (fd < 0)
124 return fd;
125
126 m->tmp = fdopen(fd, "w+");
127 if (!m->tmp) {
128 safe_close(fd);
129 return -errno;
130 }
131 }
132
133 return 0;
134 }
135
136 static ssize_t request_reader_entries(
137 void *cls,
138 uint64_t pos,
139 char *buf,
140 size_t max) {
141
142 RequestMeta *m = cls;
143 int r;
144 size_t n, k;
145
146 assert(m);
147 assert(buf);
148 assert(max > 0);
149 assert(pos >= m->delta);
150
151 pos -= m->delta;
152
153 while (pos >= m->size) {
154 off_t sz;
155
156 /* End of this entry, so let's serialize the next
157 * one */
158
159 if (m->n_entries_set &&
160 m->n_entries <= 0)
161 return MHD_CONTENT_READER_END_OF_STREAM;
162
163 if (m->n_skip < 0)
164 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
165 else if (m->n_skip > 0)
166 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
167 else
168 r = sd_journal_next(m->journal);
169
170 if (r < 0) {
171 log_error_errno(r, "Failed to advance journal pointer: %m");
172 return MHD_CONTENT_READER_END_WITH_ERROR;
173 } else if (r == 0) {
174
175 if (m->follow) {
176 r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT);
177 if (r < 0) {
178 log_error_errno(r, "Couldn't wait for journal event: %m");
179 return MHD_CONTENT_READER_END_WITH_ERROR;
180 }
181 if (r == SD_JOURNAL_NOP)
182 break;
183
184 continue;
185 }
186
187 return MHD_CONTENT_READER_END_OF_STREAM;
188 }
189
190 if (m->discrete) {
191 assert(m->cursor);
192
193 r = sd_journal_test_cursor(m->journal, m->cursor);
194 if (r < 0) {
195 log_error_errno(r, "Failed to test cursor: %m");
196 return MHD_CONTENT_READER_END_WITH_ERROR;
197 }
198
199 if (r == 0)
200 return MHD_CONTENT_READER_END_OF_STREAM;
201 }
202
203 pos -= m->size;
204 m->delta += m->size;
205
206 if (m->n_entries_set)
207 m->n_entries -= 1;
208
209 m->n_skip = 0;
210
211 r = request_meta_ensure_tmp(m);
212 if (r < 0) {
213 log_error_errno(r, "Failed to create temporary file: %m");
214 return MHD_CONTENT_READER_END_WITH_ERROR;
215 }
216
217 r = show_journal_entry(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH,
218 NULL, NULL, NULL);
219 if (r < 0) {
220 log_error_errno(r, "Failed to serialize item: %m");
221 return MHD_CONTENT_READER_END_WITH_ERROR;
222 }
223
224 sz = ftello(m->tmp);
225 if (sz == (off_t) -1) {
226 log_error_errno(errno, "Failed to retrieve file position: %m");
227 return MHD_CONTENT_READER_END_WITH_ERROR;
228 }
229
230 m->size = (uint64_t) sz;
231 }
232
233 if (m->tmp == NULL && m->follow)
234 return 0;
235
236 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
237 log_error_errno(errno, "Failed to seek to position: %m");
238 return MHD_CONTENT_READER_END_WITH_ERROR;
239 }
240
241 n = m->size - pos;
242 if (n < 1)
243 return 0;
244 if (n > max)
245 n = max;
246
247 errno = 0;
248 k = fread(buf, 1, n, m->tmp);
249 if (k != n) {
250 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
251 return MHD_CONTENT_READER_END_WITH_ERROR;
252 }
253
254 return (ssize_t) k;
255 }
256
257 static int request_parse_accept(
258 RequestMeta *m,
259 struct MHD_Connection *connection) {
260
261 const char *header;
262
263 assert(m);
264 assert(connection);
265
266 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
267 if (!header)
268 return 0;
269
270 if (streq(header, mime_types[OUTPUT_JSON]))
271 m->mode = OUTPUT_JSON;
272 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
273 m->mode = OUTPUT_JSON_SSE;
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 void help(void) {
868 printf("%s [OPTIONS...] ...\n\n"
869 "HTTP server for journal events.\n\n"
870 " -h --help Show this help\n"
871 " --version Show package version\n"
872 " --cert=CERT.PEM Server certificate in PEM format\n"
873 " --key=KEY.PEM Server key in PEM format\n"
874 " --trust=CERT.PEM Certificate authority certificate in PEM format\n"
875 " -D --directory=PATH Serve journal files in directory\n",
876 program_invocation_short_name);
877 }
878
879 static int parse_argv(int argc, char *argv[]) {
880 enum {
881 ARG_VERSION = 0x100,
882 ARG_KEY,
883 ARG_CERT,
884 ARG_TRUST,
885 };
886
887 int r, c;
888
889 static const struct option options[] = {
890 { "help", no_argument, NULL, 'h' },
891 { "version", no_argument, NULL, ARG_VERSION },
892 { "key", required_argument, NULL, ARG_KEY },
893 { "cert", required_argument, NULL, ARG_CERT },
894 { "trust", required_argument, NULL, ARG_TRUST },
895 { "directory", required_argument, NULL, 'D' },
896 {}
897 };
898
899 assert(argc >= 0);
900 assert(argv);
901
902 while ((c = getopt_long(argc, argv, "hD:", options, NULL)) >= 0)
903
904 switch(c) {
905
906 case 'h':
907 help();
908 return 0;
909
910 case ARG_VERSION:
911 return version();
912
913 case ARG_KEY:
914 if (arg_key_pem) {
915 log_error("Key file specified twice");
916 return -EINVAL;
917 }
918 r = read_full_file(optarg, &arg_key_pem, NULL);
919 if (r < 0)
920 return log_error_errno(r, "Failed to read key file: %m");
921 assert(arg_key_pem);
922 break;
923
924 case ARG_CERT:
925 if (arg_cert_pem) {
926 log_error("Certificate file specified twice");
927 return -EINVAL;
928 }
929 r = read_full_file(optarg, &arg_cert_pem, NULL);
930 if (r < 0)
931 return log_error_errno(r, "Failed to read certificate file: %m");
932 assert(arg_cert_pem);
933 break;
934
935 case ARG_TRUST:
936 #if HAVE_GNUTLS
937 if (arg_trust_pem) {
938 log_error("CA certificate file specified twice");
939 return -EINVAL;
940 }
941 r = read_full_file(optarg, &arg_trust_pem, NULL);
942 if (r < 0)
943 return log_error_errno(r, "Failed to read CA certificate file: %m");
944 assert(arg_trust_pem);
945 break;
946 #else
947 log_error("Option --trust is not available.");
948 return -EINVAL;
949 #endif
950 case 'D':
951 arg_directory = optarg;
952 break;
953
954 case '?':
955 return -EINVAL;
956
957 default:
958 assert_not_reached("Unhandled option");
959 }
960
961 if (optind < argc) {
962 log_error("This program does not take arguments.");
963 return -EINVAL;
964 }
965
966 if (!!arg_key_pem != !!arg_cert_pem) {
967 log_error("Certificate and key files must be specified together");
968 return -EINVAL;
969 }
970
971 if (arg_trust_pem && !arg_key_pem) {
972 log_error("CA certificate can only be used with certificate file");
973 return -EINVAL;
974 }
975
976 return 1;
977 }
978
979 int main(int argc, char *argv[]) {
980 struct MHD_Daemon *d = NULL;
981 int r, n;
982
983 log_set_target(LOG_TARGET_AUTO);
984 log_parse_environment();
985 log_open();
986
987 r = parse_argv(argc, argv);
988 if (r < 0)
989 return EXIT_FAILURE;
990 if (r == 0)
991 return EXIT_SUCCESS;
992
993 sigbus_install();
994
995 r = setup_gnutls_logger(NULL);
996 if (r < 0)
997 return EXIT_FAILURE;
998
999 n = sd_listen_fds(1);
1000 if (n < 0) {
1001 log_error_errno(n, "Failed to determine passed sockets: %m");
1002 goto finish;
1003 } else if (n > 1) {
1004 log_error("Can't listen on more than one socket.");
1005 goto finish;
1006 } else {
1007 struct MHD_OptionItem opts[] = {
1008 { MHD_OPTION_NOTIFY_COMPLETED,
1009 (intptr_t) request_meta_free, NULL },
1010 { MHD_OPTION_EXTERNAL_LOGGER,
1011 (intptr_t) microhttpd_logger, NULL },
1012 { MHD_OPTION_END, 0, NULL },
1013 { MHD_OPTION_END, 0, NULL },
1014 { MHD_OPTION_END, 0, NULL },
1015 { MHD_OPTION_END, 0, NULL },
1016 { MHD_OPTION_END, 0, NULL }};
1017 int opts_pos = 2;
1018
1019 /* We force MHD_USE_ITC here, in order to make sure
1020 * libmicrohttpd doesn't use shutdown() on our listening
1021 * socket, which would break socket re-activation. See
1022 *
1023 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1024 * https://github.com/systemd/systemd/pull/1286
1025 */
1026
1027 int flags =
1028 MHD_USE_DEBUG |
1029 MHD_USE_DUAL_STACK |
1030 MHD_USE_ITC |
1031 MHD_USE_POLL_INTERNAL_THREAD |
1032 MHD_USE_THREAD_PER_CONNECTION;
1033
1034 if (n > 0)
1035 opts[opts_pos++] = (struct MHD_OptionItem)
1036 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1037 if (arg_key_pem) {
1038 assert(arg_cert_pem);
1039 opts[opts_pos++] = (struct MHD_OptionItem)
1040 {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem};
1041 opts[opts_pos++] = (struct MHD_OptionItem)
1042 {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem};
1043 flags |= MHD_USE_TLS;
1044 }
1045 if (arg_trust_pem) {
1046 assert(flags & MHD_USE_TLS);
1047 opts[opts_pos++] = (struct MHD_OptionItem)
1048 {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem};
1049 }
1050
1051 d = MHD_start_daemon(flags, 19531,
1052 NULL, NULL,
1053 request_handler, NULL,
1054 MHD_OPTION_ARRAY, opts,
1055 MHD_OPTION_END);
1056 }
1057
1058 if (!d) {
1059 log_error("Failed to start daemon!");
1060 goto finish;
1061 }
1062
1063 pause();
1064
1065 r = EXIT_SUCCESS;
1066
1067 finish:
1068 if (d)
1069 MHD_stop_daemon(d);
1070
1071 return r;
1072 }