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