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