]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal-remote/journal-gatewayd.c
process-util: add new FORK_DEATHSIG_SIGKILL flag, rename FORK_DEATHSIG → FORK_DEATHSI...
[thirdparty/systemd.git] / src / journal-remote / journal-gatewayd.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
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"
d6b4d1c7 17#include "build.h"
b7147168 18#include "bus-locator.h"
40ca29a1 19#include "bus-util.h"
4bbccb02 20#include "errno-util.h"
3ffd4af2 21#include "fd-util.h"
3f6fd1ba 22#include "fileio.h"
c362a432 23#include "glob-util.h"
3f6fd1ba
LP
24#include "hostname-util.h"
25#include "log.h"
7b17a7d7 26#include "logs-show.h"
29cd4c8f 27#include "main-func.h"
f362fe73 28#include "memory-util.h"
e64690a8 29#include "microhttpd-util.h"
d58ad743 30#include "os-util.h"
6bedfcbb 31#include "parse-util.h"
294bf0c3 32#include "pretty-print.h"
2cf4172a 33#include "sigbus.h"
f9d1806e 34#include "signal-util.h"
e4de7287 35#include "tmpfile-util.h"
7b17a7d7 36
11bb5147
DD
37#define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC)
38
2cf4172a
LP
39static char *arg_key_pem = NULL;
40static char *arg_cert_pem = NULL;
41static char *arg_trust_pem = NULL;
c362a432
SB
42static bool arg_merge = false;
43static int arg_journal_type = 0;
29cd4c8f 44static const char *arg_directory = NULL;
c362a432 45static char **arg_file = NULL;
29cd4c8f 46
f362fe73 47STATIC_DESTRUCTOR_REGISTER(arg_key_pem, erase_and_freep);
29cd4c8f
YW
48STATIC_DESTRUCTOR_REGISTER(arg_cert_pem, freep);
49STATIC_DESTRUCTOR_REGISTER(arg_trust_pem, freep);
f12be7e8 50
7b17a7d7
LP
51typedef struct RequestMeta {
52 sd_journal *journal;
53
54 OutputMode mode;
55
56 char *cursor;
57 int64_t n_skip;
58 uint64_t n_entries;
59 bool n_entries_set;
60
61 FILE *tmp;
62 uint64_t delta, size;
98206c93
LP
63
64 int argument_parse_error;
7a69007a
LP
65
66 bool follow;
c6511e85 67 bool discrete;
7b17a7d7
LP
68} RequestMeta;
69
70static const char* const mime_types[_OUTPUT_MODE_MAX] = {
71 [OUTPUT_SHORT] = "text/plain",
72 [OUTPUT_JSON] = "application/json",
48383c25 73 [OUTPUT_JSON_SSE] = "text/event-stream",
8e044443 74 [OUTPUT_JSON_SEQ] = "application/json-seq",
48383c25 75 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
7b17a7d7
LP
76};
77
78static RequestMeta *request_meta(void **connection_cls) {
79 RequestMeta *m;
80
fdfccdbc 81 assert(connection_cls);
7b17a7d7
LP
82 if (*connection_cls)
83 return *connection_cls;
84
85 m = new0(RequestMeta, 1);
86 if (!m)
87 return NULL;
88
89 *connection_cls = m;
90 return m;
91}
92
93static void request_meta_free(
94 void *cls,
95 struct MHD_Connection *connection,
96 void **connection_cls,
97 enum MHD_RequestTerminationCode toe) {
98
99 RequestMeta *m = *connection_cls;
100
101 if (!m)
102 return;
103
3e044c49 104 sd_journal_close(m->journal);
7b17a7d7 105
74ca738f 106 safe_fclose(m->tmp);
7b17a7d7
LP
107
108 free(m->cursor);
109 free(m);
110}
111
112static int open_journal(RequestMeta *m) {
113 assert(m);
114
115 if (m->journal)
116 return 0;
117
1aa1e59c 118 if (arg_directory)
c362a432
SB
119 return sd_journal_open_directory(&m->journal, arg_directory, arg_journal_type);
120 else if (arg_file)
121 return sd_journal_open_files(&m->journal, (const char**) arg_file, 0);
1aa1e59c 122 else
c362a432 123 return sd_journal_open(&m->journal, (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) | arg_journal_type);
7b17a7d7
LP
124}
125
cc02a7b3 126static int request_meta_ensure_tmp(RequestMeta *m) {
03532f0a
LP
127 assert(m);
128
cc02a7b3
ZJS
129 if (m->tmp)
130 rewind(m->tmp);
131 else {
254d1313 132 _cleanup_close_ int fd = -EBADF;
cc02a7b3 133
03532f0a 134 fd = open_tmpfile_unlinkable("/tmp", O_RDWR|O_CLOEXEC);
cc02a7b3
ZJS
135 if (fd < 0)
136 return fd;
137
b46c3e49
VC
138 m->tmp = take_fdopen(&fd, "w+");
139 if (!m->tmp)
cc02a7b3 140 return -errno;
cc02a7b3
ZJS
141 }
142
143 return 0;
144}
145
7b17a7d7
LP
146static ssize_t request_reader_entries(
147 void *cls,
148 uint64_t pos,
149 char *buf,
150 size_t max) {
151
99534007 152 RequestMeta *m = ASSERT_PTR(cls);
893bcd3d
DB
153 dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL;
154 sd_id128_t previous_boot_id = SD_ID128_NULL;
7b17a7d7
LP
155 int r;
156 size_t n, k;
157
7b17a7d7
LP
158 assert(buf);
159 assert(max > 0);
160 assert(pos >= m->delta);
161
162 pos -= m->delta;
163
164 while (pos >= m->size) {
165 off_t sz;
166
167 /* End of this entry, so let's serialize the next
168 * one */
169
170 if (m->n_entries_set &&
171 m->n_entries <= 0)
172 return MHD_CONTENT_READER_END_OF_STREAM;
173
77ad3b93
LP
174 if (m->n_skip < 0)
175 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
176 else if (m->n_skip > 0)
7b17a7d7
LP
177 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
178 else
179 r = sd_journal_next(m->journal);
180
181 if (r < 0) {
da927ba9 182 log_error_errno(r, "Failed to advance journal pointer: %m");
7b17a7d7 183 return MHD_CONTENT_READER_END_WITH_ERROR;
7a69007a
LP
184 } else if (r == 0) {
185
186 if (m->follow) {
11bb5147 187 r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT);
7a69007a 188 if (r < 0) {
da927ba9 189 log_error_errno(r, "Couldn't wait for journal event: %m");
7a69007a
LP
190 return MHD_CONTENT_READER_END_WITH_ERROR;
191 }
11bb5147
DD
192 if (r == SD_JOURNAL_NOP)
193 break;
7a69007a
LP
194
195 continue;
196 }
197
7b17a7d7 198 return MHD_CONTENT_READER_END_OF_STREAM;
7a69007a 199 }
7b17a7d7 200
c6511e85
LP
201 if (m->discrete) {
202 assert(m->cursor);
203
204 r = sd_journal_test_cursor(m->journal, m->cursor);
205 if (r < 0) {
da927ba9 206 log_error_errno(r, "Failed to test cursor: %m");
c6511e85
LP
207 return MHD_CONTENT_READER_END_WITH_ERROR;
208 }
209
210 if (r == 0)
211 return MHD_CONTENT_READER_END_OF_STREAM;
212 }
213
7b17a7d7
LP
214 pos -= m->size;
215 m->delta += m->size;
216
217 if (m->n_entries_set)
218 m->n_entries -= 1;
219
220 m->n_skip = 0;
221
cc02a7b3
ZJS
222 r = request_meta_ensure_tmp(m);
223 if (r < 0) {
224 log_error_errno(r, "Failed to create temporary file: %m");
225 return MHD_CONTENT_READER_END_WITH_ERROR;
7b17a7d7
LP
226 }
227
9b972c9a 228 r = show_journal_entry(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH,
893bcd3d 229 NULL, NULL, NULL, &previous_ts, &previous_boot_id);
7b17a7d7 230 if (r < 0) {
da927ba9 231 log_error_errno(r, "Failed to serialize item: %m");
7b17a7d7
LP
232 return MHD_CONTENT_READER_END_WITH_ERROR;
233 }
234
235 sz = ftello(m->tmp);
86cbbc6d 236 if (sz < 0) {
56f64d95 237 log_error_errno(errno, "Failed to retrieve file position: %m");
7b17a7d7
LP
238 return MHD_CONTENT_READER_END_WITH_ERROR;
239 }
240
241 m->size = (uint64_t) sz;
242 }
243
3475fc58
YE
244 if (m->tmp == NULL && m->follow)
245 return 0;
246
7b17a7d7 247 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
56f64d95 248 log_error_errno(errno, "Failed to seek to position: %m");
7b17a7d7
LP
249 return MHD_CONTENT_READER_END_WITH_ERROR;
250 }
251
252 n = m->size - pos;
11bb5147
DD
253 if (n < 1)
254 return 0;
7b17a7d7
LP
255 if (n > max)
256 n = max;
257
258 errno = 0;
259 k = fread(buf, 1, n, m->tmp);
260 if (k != n) {
f69ae858 261 log_error("Failed to read from file: %s", STRERROR_OR_EOF(errno));
7b17a7d7
LP
262 return MHD_CONTENT_READER_END_WITH_ERROR;
263 }
264
265 return (ssize_t) k;
266}
267
268static int request_parse_accept(
269 RequestMeta *m,
270 struct MHD_Connection *connection) {
271
6374a73b 272 const char *header;
7b17a7d7
LP
273
274 assert(m);
275 assert(connection);
276
6374a73b
ZJS
277 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
278 if (!header)
7b17a7d7
LP
279 return 0;
280
6374a73b 281 if (streq(header, mime_types[OUTPUT_JSON]))
7b17a7d7 282 m->mode = OUTPUT_JSON;
6374a73b 283 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
48383c25 284 m->mode = OUTPUT_JSON_SSE;
8e044443
LP
285 else if (streq(header, mime_types[OUTPUT_JSON_SEQ]))
286 m->mode = OUTPUT_JSON_SEQ;
6374a73b 287 else if (streq(header, mime_types[OUTPUT_EXPORT]))
7b17a7d7
LP
288 m->mode = OUTPUT_EXPORT;
289 else
290 m->mode = OUTPUT_SHORT;
291
292 return 0;
293}
294
295static int request_parse_range(
296 RequestMeta *m,
297 struct MHD_Connection *connection) {
298
299 const char *range, *colon, *colon2;
300 int r;
301
302 assert(m);
303 assert(connection);
304
305 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
306 if (!range)
307 return 0;
308
309 if (!startswith(range, "entries="))
310 return 0;
311
312 range += 8;
313 range += strspn(range, WHITESPACE);
314
315 colon = strchr(range, ':');
316 if (!colon)
317 m->cursor = strdup(range);
318 else {
319 const char *p;
320
321 colon2 = strchr(colon + 1, ':');
322 if (colon2) {
c2b2df60 323 _cleanup_free_ char *t = NULL;
7b17a7d7
LP
324
325 t = strndup(colon + 1, colon2 - colon - 1);
326 if (!t)
327 return -ENOMEM;
328
329 r = safe_atoi64(t, &m->n_skip);
7b17a7d7
LP
330 if (r < 0)
331 return r;
332 }
333
1da3cb81 334 p = (colon2 ?: colon) + 1;
7b17a7d7
LP
335 if (*p) {
336 r = safe_atou64(p, &m->n_entries);
337 if (r < 0)
338 return r;
339
340 if (m->n_entries <= 0)
341 return -EINVAL;
342
343 m->n_entries_set = true;
344 }
345
346 m->cursor = strndup(range, colon - range);
347 }
348
349 if (!m->cursor)
350 return -ENOMEM;
351
352 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
ece174c5 353 if (isempty(m->cursor))
a1e58e8e 354 m->cursor = mfree(m->cursor);
7b17a7d7
LP
355
356 return 0;
357}
358
d17eabb1 359static mhd_result request_parse_arguments_iterator(
98206c93
LP
360 void *cls,
361 enum MHD_ValueKind kind,
362 const char *key,
363 const char *value) {
364
99534007 365 RequestMeta *m = ASSERT_PTR(cls);
98206c93
LP
366 _cleanup_free_ char *p = NULL;
367 int r;
368
98206c93
LP
369 if (isempty(key)) {
370 m->argument_parse_error = -EINVAL;
371 return MHD_NO;
372 }
373
7a69007a
LP
374 if (streq(key, "follow")) {
375 if (isempty(value)) {
376 m->follow = true;
377 return MHD_YES;
378 }
379
380 r = parse_boolean(value);
381 if (r < 0) {
382 m->argument_parse_error = r;
383 return MHD_NO;
384 }
385
386 m->follow = r;
387 return MHD_YES;
388 }
389
c6511e85
LP
390 if (streq(key, "discrete")) {
391 if (isempty(value)) {
392 m->discrete = true;
393 return MHD_YES;
394 }
395
396 r = parse_boolean(value);
397 if (r < 0) {
398 m->argument_parse_error = r;
399 return MHD_NO;
400 }
401
402 m->discrete = r;
403 return MHD_YES;
404 }
405
082d0180
LP
406 if (streq(key, "boot")) {
407 if (isempty(value))
408 r = true;
409 else {
410 r = parse_boolean(value);
411 if (r < 0) {
412 m->argument_parse_error = r;
413 return MHD_NO;
414 }
415 }
416
417 if (r) {
418 char match[9 + 32 + 1] = "_BOOT_ID=";
419 sd_id128_t bid;
420
421 r = sd_id128_get_boot(&bid);
422 if (r < 0) {
da927ba9 423 log_error_errno(r, "Failed to get boot ID: %m");
082d0180
LP
424 return MHD_NO;
425 }
426
427 sd_id128_to_string(bid, match + 9);
428 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
429 if (r < 0) {
430 m->argument_parse_error = r;
431 return MHD_NO;
432 }
433 }
434
435 return MHD_YES;
436 }
437
605405c6 438 p = strjoin(key, "=", strempty(value));
98206c93
LP
439 if (!p) {
440 m->argument_parse_error = log_oom();
441 return MHD_NO;
442 }
443
444 r = sd_journal_add_match(m->journal, p, 0);
445 if (r < 0) {
446 m->argument_parse_error = r;
447 return MHD_NO;
448 }
449
450 return MHD_YES;
451}
452
453static int request_parse_arguments(
454 RequestMeta *m,
455 struct MHD_Connection *connection) {
456
457 assert(m);
458 assert(connection);
459
460 m->argument_parse_error = 0;
461 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
462
463 return m->argument_parse_error;
464}
465
7b17a7d7
LP
466static int request_handler_entries(
467 struct MHD_Connection *connection,
8530a143 468 void *connection_cls) {
7b17a7d7 469
d101fb24 470 _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL;
99534007 471 RequestMeta *m = ASSERT_PTR(connection_cls);
7b17a7d7
LP
472 int r;
473
474 assert(connection);
7b17a7d7
LP
475
476 r = open_journal(m);
477 if (r < 0)
f5e757f1 478 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m");
7b17a7d7
LP
479
480 if (request_parse_accept(m, connection) < 0)
f5e757f1 481 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.");
7b17a7d7
LP
482
483 if (request_parse_range(m, connection) < 0)
f5e757f1 484 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.");
7b17a7d7 485
98206c93 486 if (request_parse_arguments(m, connection) < 0)
f5e757f1 487 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.");
98206c93 488
c6511e85
LP
489 if (m->discrete) {
490 if (!m->cursor)
f5e757f1 491 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.");
c6511e85
LP
492
493 m->n_entries = 1;
494 m->n_entries_set = true;
495 }
496
7b17a7d7
LP
497 if (m->cursor)
498 r = sd_journal_seek_cursor(m->journal, m->cursor);
499 else if (m->n_skip >= 0)
500 r = sd_journal_seek_head(m->journal);
501 else if (m->n_skip < 0)
502 r = sd_journal_seek_tail(m->journal);
503 if (r < 0)
f5e757f1 504 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.");
7b17a7d7
LP
505
506 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
507 if (!response)
508 return respond_oom(connection);
509
60d9c4f3
ZJS
510 if (MHD_add_response_header(response, "Content-Type", mime_types[m->mode]) == MHD_NO)
511 return respond_oom(connection);
512
d101fb24 513 return MHD_queue_response(connection, MHD_HTTP_OK, response);
7b17a7d7
LP
514}
515
240a5fe8
LP
516static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
517 const char *eq;
518 size_t j;
519
520 eq = memchr(d, '=', l);
521 if (!eq)
522 return -EINVAL;
523
524 j = l - (eq - d + 1);
525
526 if (m == OUTPUT_JSON) {
527 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
528 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
529 fputs(" }\n", f);
530 } else {
531 fwrite(eq+1, 1, j, f);
532 fputc('\n', f);
533 }
534
535 return 0;
536}
537
538static ssize_t request_reader_fields(
539 void *cls,
540 uint64_t pos,
541 char *buf,
542 size_t max) {
543
99534007 544 RequestMeta *m = ASSERT_PTR(cls);
240a5fe8
LP
545 int r;
546 size_t n, k;
547
240a5fe8
LP
548 assert(buf);
549 assert(max > 0);
550 assert(pos >= m->delta);
551
552 pos -= m->delta;
553
554 while (pos >= m->size) {
555 off_t sz;
556 const void *d;
557 size_t l;
558
559 /* End of this field, so let's serialize the next
560 * one */
561
240a5fe8
LP
562 r = sd_journal_enumerate_unique(m->journal, &d, &l);
563 if (r < 0) {
da927ba9 564 log_error_errno(r, "Failed to advance field index: %m");
240a5fe8
LP
565 return MHD_CONTENT_READER_END_WITH_ERROR;
566 } else if (r == 0)
567 return MHD_CONTENT_READER_END_OF_STREAM;
568
569 pos -= m->size;
570 m->delta += m->size;
571
cc02a7b3
ZJS
572 r = request_meta_ensure_tmp(m);
573 if (r < 0) {
574 log_error_errno(r, "Failed to create temporary file: %m");
575 return MHD_CONTENT_READER_END_WITH_ERROR;
240a5fe8
LP
576 }
577
578 r = output_field(m->tmp, m->mode, d, l);
579 if (r < 0) {
da927ba9 580 log_error_errno(r, "Failed to serialize item: %m");
240a5fe8
LP
581 return MHD_CONTENT_READER_END_WITH_ERROR;
582 }
583
584 sz = ftello(m->tmp);
86cbbc6d 585 if (sz < 0) {
56f64d95 586 log_error_errno(errno, "Failed to retrieve file position: %m");
240a5fe8
LP
587 return MHD_CONTENT_READER_END_WITH_ERROR;
588 }
589
590 m->size = (uint64_t) sz;
591 }
592
593 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
56f64d95 594 log_error_errno(errno, "Failed to seek to position: %m");
240a5fe8
LP
595 return MHD_CONTENT_READER_END_WITH_ERROR;
596 }
597
598 n = m->size - pos;
599 if (n > max)
600 n = max;
601
602 errno = 0;
603 k = fread(buf, 1, n, m->tmp);
604 if (k != n) {
f69ae858 605 log_error("Failed to read from file: %s", STRERROR_OR_EOF(errno));
240a5fe8
LP
606 return MHD_CONTENT_READER_END_WITH_ERROR;
607 }
608
609 return (ssize_t) k;
610}
611
612static int request_handler_fields(
613 struct MHD_Connection *connection,
614 const char *field,
615 void *connection_cls) {
616
d101fb24 617 _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL;
99534007 618 RequestMeta *m = ASSERT_PTR(connection_cls);
240a5fe8
LP
619 int r;
620
621 assert(connection);
240a5fe8
LP
622
623 r = open_journal(m);
624 if (r < 0)
f5e757f1 625 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m");
240a5fe8
LP
626
627 if (request_parse_accept(m, connection) < 0)
f5e757f1 628 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.");
240a5fe8
LP
629
630 r = sd_journal_query_unique(m->journal, field);
631 if (r < 0)
f5e757f1 632 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.");
240a5fe8
LP
633
634 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
635 if (!response)
636 return respond_oom(connection);
637
60d9c4f3
ZJS
638 if (MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]) == MHD_NO)
639 return respond_oom(connection);
640
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
c60d95ca 648 _cleanup_free_ char *page = NULL;
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);
c60d95ca 658 if (!response)
7b17a7d7 659 return respond_oom(connection);
c60d95ca 660 TAKE_PTR(page);
7b17a7d7 661
60d9c4f3
ZJS
662 if (MHD_add_response_header(response, "Content-Type", "text/html") == MHD_NO ||
663 MHD_add_response_header(response, "Location", target) == MHD_NO)
664 return respond_oom(connection);
665
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;
254d1313 675 _cleanup_close_ int fd = -EBADF;
7b17a7d7
LP
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 693
60d9c4f3
ZJS
694 if (MHD_add_response_header(response, "Content-Type", mime_type) == MHD_NO)
695 return respond_oom(connection);
696
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
b7147168 709 r = bus_get_property_string(bus, bus_systemd_mgr, "Virtualization", NULL, &b);
a7edaadd
LP
710 if (r < 0)
711 return r;
712
47c649b5
LP
713 if (isempty(b)) {
714 free(b);
a7edaadd
LP
715 *v = NULL;
716 return 0;
717 }
718
a7edaadd
LP
719 *v = b;
720 return 1;
721}
722
7b17a7d7
LP
723static int request_handler_machine(
724 struct MHD_Connection *connection,
8530a143 725 void *connection_cls) {
7b17a7d7 726
d101fb24 727 _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL;
99534007 728 RequestMeta *m = ASSERT_PTR(connection_cls);
7b17a7d7 729 int r;
02b7005e 730 _cleanup_free_ char* hostname = NULL, *pretty_name = NULL, *os_name = NULL;
a7f7d1bd 731 uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0;
7b17a7d7 732 sd_id128_t mid, bid;
d101fb24 733 _cleanup_free_ char *v = NULL, *json = NULL;
7b17a7d7
LP
734
735 assert(connection);
7b17a7d7
LP
736
737 r = open_journal(m);
738 if (r < 0)
f5e757f1 739 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m");
7b17a7d7
LP
740
741 r = sd_id128_get_machine(&mid);
742 if (r < 0)
f5e757f1 743 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %m");
7b17a7d7
LP
744
745 r = sd_id128_get_boot(&bid);
746 if (r < 0)
f5e757f1 747 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %m");
7b17a7d7
LP
748
749 hostname = gethostname_malloc();
750 if (!hostname)
751 return respond_oom(connection);
752
753 r = sd_journal_get_usage(m->journal, &usage);
754 if (r < 0)
e4662e55 755 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %m");
7b17a7d7
LP
756
757 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
758 if (r < 0)
e4662e55 759 return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %m");
7b17a7d7 760
02b7005e
LP
761 (void) parse_os_release(
762 NULL,
763 "PRETTY_NAME", &pretty_name,
764 "NAME=", &os_name);
d58ad743 765 (void) get_virtualization(&v);
7b17a7d7
LP
766
767 r = asprintf(&json,
768 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
769 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
770 "\"hostname\" : \"%s\","
771 "\"os_pretty_name\" : \"%s\","
772 "\"virtualization\" : \"%s\","
507f22bd
ZJS
773 "\"usage\" : \"%"PRIu64"\","
774 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
775 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
7b17a7d7
LP
776 SD_ID128_FORMAT_VAL(mid),
777 SD_ID128_FORMAT_VAL(bid),
ae691c1d 778 hostname_cleanup(hostname),
02b7005e 779 os_release_pretty_name(pretty_name, os_name),
a7edaadd 780 v ? v : "bare",
507f22bd
ZJS
781 usage,
782 cutoff_from,
783 cutoff_to);
7b17a7d7
LP
784 if (r < 0)
785 return respond_oom(connection);
786
787 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
d101fb24 788 if (!response)
7b17a7d7 789 return respond_oom(connection);
d101fb24 790 TAKE_PTR(json);
7b17a7d7 791
60d9c4f3
ZJS
792 if (MHD_add_response_header(response, "Content-Type", "application/json") == MHD_NO)
793 return respond_oom(connection);
794
d101fb24 795 return MHD_queue_response(connection, MHD_HTTP_OK, response);
7b17a7d7
LP
796}
797
d17eabb1 798static mhd_result request_handler(
7b17a7d7
LP
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"
c362a432
SB
862 " --system Serve system journal\n"
863 " --user Serve the user journal for the current user\n"
864 " -m --merge Serve all available journals\n"
37ec0fdd 865 " -D --directory=PATH Serve journal files in directory\n"
c362a432 866 " --file=PATH Serve this journal file\n"
bc556335
DDM
867 "\nSee the %s for details.\n",
868 program_invocation_short_name,
869 link);
37ec0fdd
LP
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,
c362a432
SB
880 ARG_USER,
881 ARG_SYSTEM,
882 ARG_MERGE,
883 ARG_FILE,
858634ff
ZJS
884 };
885
886 int r, c;
887
888 static const struct option options[] = {
1aa1e59c
YE
889 { "help", no_argument, NULL, 'h' },
890 { "version", no_argument, NULL, ARG_VERSION },
891 { "key", required_argument, NULL, ARG_KEY },
892 { "cert", required_argument, NULL, ARG_CERT },
893 { "trust", required_argument, NULL, ARG_TRUST },
c362a432
SB
894 { "user", no_argument, NULL, ARG_USER },
895 { "system", no_argument, NULL, ARG_SYSTEM },
896 { "merge", no_argument, NULL, 'm' },
29b5b0c9 897 { "directory", required_argument, NULL, 'D' },
c362a432 898 { "file", required_argument, NULL, ARG_FILE },
eb9da376 899 {}
858634ff
ZJS
900 };
901
902 assert(argc >= 0);
903 assert(argv);
904
554597a1 905 while ((c = getopt_long(argc, argv, "hD:", options, NULL)) >= 0)
eb9da376 906
79893116 907 switch (c) {
eb9da376
LP
908
909 case 'h':
37ec0fdd 910 return help();
eb9da376 911
858634ff 912 case ARG_VERSION:
3f6fd1ba 913 return version();
858634ff
ZJS
914
915 case ARG_KEY:
baaa35ad
ZJS
916 if (arg_key_pem)
917 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
918 "Key file specified twice");
e5de42e6 919 r = read_full_file_full(
986311c2 920 AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
e5de42e6
LP
921 READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
922 NULL,
923 &arg_key_pem, NULL);
23bbb0de
MS
924 if (r < 0)
925 return log_error_errno(r, "Failed to read key file: %m");
2cf4172a 926 assert(arg_key_pem);
858634ff 927 break;
7b17a7d7 928
858634ff 929 case ARG_CERT:
baaa35ad
ZJS
930 if (arg_cert_pem)
931 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
932 "Certificate file specified twice");
986311c2
LP
933 r = read_full_file_full(
934 AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
935 READ_FULL_FILE_CONNECT_SOCKET,
936 NULL,
937 &arg_cert_pem, NULL);
23bbb0de
MS
938 if (r < 0)
939 return log_error_errno(r, "Failed to read certificate file: %m");
2cf4172a 940 assert(arg_cert_pem);
858634ff
ZJS
941 break;
942
e5ebe12b 943 case ARG_TRUST:
349cc4a5 944#if HAVE_GNUTLS
baaa35ad
ZJS
945 if (arg_trust_pem)
946 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
947 "CA certificate file specified twice");
986311c2
LP
948 r = read_full_file_full(
949 AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
950 READ_FULL_FILE_CONNECT_SOCKET,
951 NULL,
952 &arg_trust_pem, NULL);
23bbb0de
MS
953 if (r < 0)
954 return log_error_errno(r, "Failed to read CA certificate file: %m");
2cf4172a 955 assert(arg_trust_pem);
e5ebe12b 956 break;
f12be7e8 957#else
baaa35ad 958 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
c61f46fe 959 "Option --trust= is not available.");
f12be7e8 960#endif
c362a432
SB
961
962 case ARG_SYSTEM:
963 arg_journal_type |= SD_JOURNAL_SYSTEM;
964 break;
965
966 case ARG_USER:
967 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
968 break;
969
970 case 'm':
971 arg_merge = true;
972 break;
973
1aa1e59c
YE
974 case 'D':
975 arg_directory = optarg;
976 break;
e5ebe12b 977
c362a432
SB
978 case ARG_FILE:
979 r = glob_extend(&arg_file, optarg, GLOB_NOCHECK);
980 if (r < 0)
981 return log_error_errno(r, "Failed to add paths: %m");
982 break;
983
858634ff
ZJS
984 case '?':
985 return -EINVAL;
986
987 default:
04499a70 988 assert_not_reached();
858634ff
ZJS
989 }
990
baaa35ad
ZJS
991 if (optind < argc)
992 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
993 "This program does not take arguments.");
858634ff 994
baaa35ad
ZJS
995 if (!!arg_key_pem != !!arg_cert_pem)
996 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
997 "Certificate and key files must be specified together");
7b17a7d7 998
baaa35ad
ZJS
999 if (arg_trust_pem && !arg_key_pem)
1000 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1001 "CA certificate can only be used with certificate file");
e5ebe12b 1002
858634ff
ZJS
1003 return 1;
1004}
1005
29cd4c8f 1006static int run(int argc, char *argv[]) {
0d2a1a20 1007 _cleanup_(MHD_stop_daemonp) struct MHD_Daemon *d = NULL;
f9d1806e
FS
1008 static const struct sigaction sigterm = {
1009 .sa_handler = nop_signal_handler,
1010 .sa_flags = SA_RESTART,
1011 };
29cd4c8f 1012 struct MHD_OptionItem opts[] = {
29cd4c8f
YW
1013 { MHD_OPTION_EXTERNAL_LOGGER,
1014 (intptr_t) microhttpd_logger, NULL },
b2f03d73
FS
1015 { MHD_OPTION_NOTIFY_COMPLETED,
1016 (intptr_t) request_meta_free, NULL },
29cd4c8f
YW
1017 { MHD_OPTION_END, 0, NULL },
1018 { MHD_OPTION_END, 0, NULL },
1019 { MHD_OPTION_END, 0, NULL },
1020 { MHD_OPTION_END, 0, NULL },
1021 { MHD_OPTION_END, 0, NULL },
1022 };
1023 int opts_pos = 2;
1024
1025 /* We force MHD_USE_ITC here, in order to make sure
1026 * libmicrohttpd doesn't use shutdown() on our listening
1027 * socket, which would break socket re-activation. See
1028 *
1029 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1030 * https://github.com/systemd/systemd/pull/1286
1031 */
1032
1033 int flags =
1034 MHD_USE_DEBUG |
1035 MHD_USE_DUAL_STACK |
1036 MHD_USE_ITC |
1037 MHD_USE_POLL_INTERNAL_THREAD |
1038 MHD_USE_THREAD_PER_CONNECTION;
858634ff
ZJS
1039 int r, n;
1040
d2acb93d 1041 log_setup();
7b17a7d7 1042
858634ff 1043 r = parse_argv(argc, argv);
29cd4c8f
YW
1044 if (r <= 0)
1045 return r;
858634ff 1046
2cf4172a 1047 sigbus_install();
f9d1806e 1048 assert_se(sigaction(SIGTERM, &sigterm, NULL) >= 0);
2cf4172a 1049
d357562c
ZJS
1050 r = setup_gnutls_logger(NULL);
1051 if (r < 0)
29cd4c8f 1052 return r;
cafc7f91 1053
7b17a7d7 1054 n = sd_listen_fds(1);
29cd4c8f
YW
1055 if (n < 0)
1056 return log_error_errno(n, "Failed to determine passed sockets: %m");
1057 if (n > 1)
1058 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't listen on more than one socket.");
1059
1060 if (n == 1)
1061 opts[opts_pos++] = (struct MHD_OptionItem)
1062 { MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START };
1063
1064 if (arg_key_pem) {
1065 assert(arg_cert_pem);
1066 opts[opts_pos++] = (struct MHD_OptionItem)
1067 { MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem };
1068 opts[opts_pos++] = (struct MHD_OptionItem)
1069 { MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem };
1070 flags |= MHD_USE_TLS;
7b17a7d7
LP
1071 }
1072
29cd4c8f
YW
1073 if (arg_trust_pem) {
1074 assert(flags & MHD_USE_TLS);
1075 opts[opts_pos++] = (struct MHD_OptionItem)
1076 { MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem };
7b17a7d7
LP
1077 }
1078
29cd4c8f
YW
1079 d = MHD_start_daemon(flags, 19531,
1080 NULL, NULL,
1081 request_handler, NULL,
1082 MHD_OPTION_ARRAY, opts,
1083 MHD_OPTION_END);
1084 if (!d)
1085 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to start daemon!");
7b17a7d7 1086
29cd4c8f 1087 pause();
7b17a7d7 1088
29cd4c8f 1089 return 0;
7b17a7d7 1090}
29cd4c8f
YW
1091
1092DEFINE_MAIN_FUNCTION(run);