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