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