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