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