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