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