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