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