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