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