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