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