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