1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include <microhttpd.h>
32 #include "sd-journal.h"
33 #include "sd-daemon.h"
36 #include "logs-show.h"
37 #include "microhttpd-util.h"
41 typedef struct RequestMeta {
54 int argument_parse_error;
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_EXPORT] = "application/vnd.fdo.journal",
70 static RequestMeta *request_meta(void **connection_cls) {
74 return *connection_cls;
76 m = new0(RequestMeta, 1);
84 static void request_meta_free(
86 struct MHD_Connection *connection,
87 void **connection_cls,
88 enum MHD_RequestTerminationCode toe) {
90 RequestMeta *m = *connection_cls;
96 sd_journal_close(m->journal);
105 static int open_journal(RequestMeta *m) {
111 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
114 static int respond_oom_internal(struct MHD_Connection *connection) {
115 struct MHD_Response *response;
116 const char m[] = "Out of memory.\n";
121 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
125 MHD_add_response_header(response, "Content-Type", "text/plain");
126 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
127 MHD_destroy_response(response);
132 #define respond_oom(connection) log_oom(), respond_oom_internal(connection)
135 static int respond_error(
136 struct MHD_Connection *connection,
138 const char *format, ...) {
140 struct MHD_Response *response;
148 va_start(ap, format);
149 r = vasprintf(&m, format, ap);
153 return respond_oom(connection);
155 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
158 return respond_oom(connection);
161 MHD_add_response_header(response, "Content-Type", "text/plain");
162 r = MHD_queue_response(connection, code, response);
163 MHD_destroy_response(response);
168 static ssize_t request_reader_entries(
174 RequestMeta *m = cls;
181 assert(pos >= m->delta);
185 while (pos >= m->size) {
188 /* End of this entry, so let's serialize the next
191 if (m->n_entries_set &&
193 return MHD_CONTENT_READER_END_OF_STREAM;
196 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
197 else if (m->n_skip > 0)
198 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
200 r = sd_journal_next(m->journal);
203 log_error("Failed to advance journal pointer: %s", strerror(-r));
204 return MHD_CONTENT_READER_END_WITH_ERROR;
208 r = sd_journal_wait(m->journal, (uint64_t) -1);
210 log_error("Couldn't wait for journal event: %s", strerror(-r));
211 return MHD_CONTENT_READER_END_WITH_ERROR;
217 return MHD_CONTENT_READER_END_OF_STREAM;
223 r = sd_journal_test_cursor(m->journal, m->cursor);
225 log_error("Failed to test cursor: %s", strerror(-r));
226 return MHD_CONTENT_READER_END_WITH_ERROR;
230 return MHD_CONTENT_READER_END_OF_STREAM;
236 if (m->n_entries_set)
246 log_error("Failed to create temporary file: %m");
247 return MHD_CONTENT_READER_END_WITH_ERROR;
251 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
253 log_error("Failed to serialize item: %s", strerror(-r));
254 return MHD_CONTENT_READER_END_WITH_ERROR;
258 if (sz == (off_t) -1) {
259 log_error("Failed to retrieve file position: %m");
260 return MHD_CONTENT_READER_END_WITH_ERROR;
263 m->size = (uint64_t) sz;
266 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
267 log_error("Failed to seek to position: %m");
268 return MHD_CONTENT_READER_END_WITH_ERROR;
276 k = fread(buf, 1, n, m->tmp);
278 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
279 return MHD_CONTENT_READER_END_WITH_ERROR;
285 static int request_parse_accept(
287 struct MHD_Connection *connection) {
294 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
298 if (streq(header, mime_types[OUTPUT_JSON]))
299 m->mode = OUTPUT_JSON;
300 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
301 m->mode = OUTPUT_JSON_SSE;
302 else if (streq(header, mime_types[OUTPUT_EXPORT]))
303 m->mode = OUTPUT_EXPORT;
305 m->mode = OUTPUT_SHORT;
310 static int request_parse_range(
312 struct MHD_Connection *connection) {
314 const char *range, *colon, *colon2;
320 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
324 if (!startswith(range, "entries="))
328 range += strspn(range, WHITESPACE);
330 colon = strchr(range, ':');
332 m->cursor = strdup(range);
336 colon2 = strchr(colon + 1, ':');
338 _cleanup_free_ char *t;
340 t = strndup(colon + 1, colon2 - colon - 1);
344 r = safe_atoi64(t, &m->n_skip);
349 p = (colon2 ? colon2 : colon) + 1;
351 r = safe_atou64(p, &m->n_entries);
355 if (m->n_entries <= 0)
358 m->n_entries_set = true;
361 m->cursor = strndup(range, colon - range);
367 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
368 if (isempty(m->cursor)) {
376 static int request_parse_arguments_iterator(
378 enum MHD_ValueKind kind,
382 RequestMeta *m = cls;
383 _cleanup_free_ char *p = NULL;
389 m->argument_parse_error = -EINVAL;
393 if (streq(key, "follow")) {
394 if (isempty(value)) {
399 r = parse_boolean(value);
401 m->argument_parse_error = r;
409 if (streq(key, "discrete")) {
410 if (isempty(value)) {
415 r = parse_boolean(value);
417 m->argument_parse_error = r;
425 if (streq(key, "boot")) {
429 r = parse_boolean(value);
431 m->argument_parse_error = r;
437 char match[9 + 32 + 1] = "_BOOT_ID=";
440 r = sd_id128_get_boot(&bid);
442 log_error("Failed to get boot ID: %s", strerror(-r));
446 sd_id128_to_string(bid, match + 9);
447 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
449 m->argument_parse_error = r;
457 p = strjoin(key, "=", strempty(value), NULL);
459 m->argument_parse_error = log_oom();
463 r = sd_journal_add_match(m->journal, p, 0);
465 m->argument_parse_error = r;
472 static int request_parse_arguments(
474 struct MHD_Connection *connection) {
479 m->argument_parse_error = 0;
480 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
482 return m->argument_parse_error;
485 static int request_handler_entries(
486 struct MHD_Connection *connection,
487 void *connection_cls) {
489 struct MHD_Response *response;
490 RequestMeta *m = connection_cls;
498 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
500 if (request_parse_accept(m, connection) < 0)
501 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
503 if (request_parse_range(m, connection) < 0)
504 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
506 if (request_parse_arguments(m, connection) < 0)
507 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
511 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
514 m->n_entries_set = true;
518 r = sd_journal_seek_cursor(m->journal, m->cursor);
519 else if (m->n_skip >= 0)
520 r = sd_journal_seek_head(m->journal);
521 else if (m->n_skip < 0)
522 r = sd_journal_seek_tail(m->journal);
524 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
526 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
528 return respond_oom(connection);
530 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
532 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
533 MHD_destroy_response(response);
538 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
542 eq = memchr(d, '=', l);
546 j = l - (eq - d + 1);
548 if (m == OUTPUT_JSON) {
549 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
550 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
553 fwrite(eq+1, 1, j, f);
560 static ssize_t request_reader_fields(
566 RequestMeta *m = cls;
573 assert(pos >= m->delta);
577 while (pos >= m->size) {
582 /* End of this field, so let's serialize the next
585 if (m->n_fields_set &&
587 return MHD_CONTENT_READER_END_OF_STREAM;
589 r = sd_journal_enumerate_unique(m->journal, &d, &l);
591 log_error("Failed to advance field index: %s", strerror(-r));
592 return MHD_CONTENT_READER_END_WITH_ERROR;
594 return MHD_CONTENT_READER_END_OF_STREAM;
607 log_error("Failed to create temporary file: %m");
608 return MHD_CONTENT_READER_END_WITH_ERROR;
612 r = output_field(m->tmp, m->mode, d, l);
614 log_error("Failed to serialize item: %s", strerror(-r));
615 return MHD_CONTENT_READER_END_WITH_ERROR;
619 if (sz == (off_t) -1) {
620 log_error("Failed to retrieve file position: %m");
621 return MHD_CONTENT_READER_END_WITH_ERROR;
624 m->size = (uint64_t) sz;
627 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
628 log_error("Failed to seek to position: %m");
629 return MHD_CONTENT_READER_END_WITH_ERROR;
637 k = fread(buf, 1, n, m->tmp);
639 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
640 return MHD_CONTENT_READER_END_WITH_ERROR;
646 static int request_handler_fields(
647 struct MHD_Connection *connection,
649 void *connection_cls) {
651 struct MHD_Response *response;
652 RequestMeta *m = connection_cls;
660 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
662 if (request_parse_accept(m, connection) < 0)
663 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
665 r = sd_journal_query_unique(m->journal, field);
667 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
669 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
671 return respond_oom(connection);
673 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
675 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
676 MHD_destroy_response(response);
681 static int request_handler_redirect(
682 struct MHD_Connection *connection,
683 const char *target) {
686 struct MHD_Response *response;
692 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
693 return respond_oom(connection);
695 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
698 return respond_oom(connection);
701 MHD_add_response_header(response, "Content-Type", "text/html");
702 MHD_add_response_header(response, "Location", target);
704 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
705 MHD_destroy_response(response);
710 static int request_handler_file(
711 struct MHD_Connection *connection,
713 const char *mime_type) {
715 struct MHD_Response *response;
717 _cleanup_close_ int fd = -1;
724 fd = open(path, O_RDONLY|O_CLOEXEC);
726 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
728 if (fstat(fd, &st) < 0)
729 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
731 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
733 return respond_oom(connection);
737 MHD_add_response_header(response, "Content-Type", mime_type);
739 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
740 MHD_destroy_response(response);
745 static int get_virtualization(char **v) {
746 _cleanup_bus_unref_ sd_bus *bus = NULL;
750 r = sd_bus_default_system(&bus);
754 r = sd_bus_get_property_string(
756 "org.freedesktop.systemd1",
757 "/org/freedesktop/systemd1",
758 "org.freedesktop.systemd1.Manager",
775 static int request_handler_machine(
776 struct MHD_Connection *connection,
777 void *connection_cls) {
779 struct MHD_Response *response;
780 RequestMeta *m = connection_cls;
782 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
783 uint64_t cutoff_from = 0, cutoff_to = 0, usage;
786 _cleanup_free_ char *v = NULL;
793 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
795 r = sd_id128_get_machine(&mid);
797 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
799 r = sd_id128_get_boot(&bid);
801 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
803 hostname = gethostname_malloc();
805 return respond_oom(connection);
807 r = sd_journal_get_usage(m->journal, &usage);
809 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
811 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
813 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
815 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
817 get_virtualization(&v);
820 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
821 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
822 "\"hostname\" : \"%s\","
823 "\"os_pretty_name\" : \"%s\","
824 "\"virtualization\" : \"%s\","
825 "\"usage\" : \"%"PRIu64"\","
826 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
827 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
828 SD_ID128_FORMAT_VAL(mid),
829 SD_ID128_FORMAT_VAL(bid),
830 hostname_cleanup(hostname, false),
831 os_name ? os_name : "Linux",
838 return respond_oom(connection);
840 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
843 return respond_oom(connection);
846 MHD_add_response_header(response, "Content-Type", "application/json");
847 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
848 MHD_destroy_response(response);
853 static int request_handler(
855 struct MHD_Connection *connection,
859 const char *upload_data,
860 size_t *upload_data_size,
861 void **connection_cls) {
864 assert(connection_cls);
868 if (!streq(method, "GET"))
869 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
870 "Unsupported method.\n");
873 if (!*connection_cls) {
874 if (!request_meta(connection_cls))
875 return respond_oom(connection);
880 return request_handler_redirect(connection, "/browse");
882 if (streq(url, "/entries"))
883 return request_handler_entries(connection, *connection_cls);
885 if (startswith(url, "/fields/"))
886 return request_handler_fields(connection, url + 8, *connection_cls);
888 if (streq(url, "/browse"))
889 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
891 if (streq(url, "/machine"))
892 return request_handler_machine(connection, *connection_cls);
894 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
897 static int help(void) {
899 printf("%s [OPTIONS...] ...\n\n"
900 "HTTP server for journal events.\n\n"
901 " -h --help Show this help\n"
902 " --version Show package version\n"
903 " --cert=CERT.PEM Server certificate in PEM format\n"
904 " --key=KEY.PEM Server key in PEM format\n"
905 " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
906 program_invocation_short_name);
911 static char *key_pem = NULL;
912 static char *cert_pem = NULL;
913 static char *trust_pem = NULL;
915 static int parse_argv(int argc, char *argv[]) {
925 static const struct option options[] = {
926 { "help", no_argument, NULL, 'h' },
927 { "version", no_argument, NULL, ARG_VERSION },
928 { "key", required_argument, NULL, ARG_KEY },
929 { "cert", required_argument, NULL, ARG_CERT },
930 { "trust", required_argument, NULL, ARG_TRUST },
937 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
945 puts(PACKAGE_STRING);
946 puts(SYSTEMD_FEATURES);
951 log_error("Key file specified twice");
954 r = read_full_file(optarg, &key_pem, NULL);
956 log_error("Failed to read key file: %s", strerror(-r));
964 log_error("Certificate file specified twice");
967 r = read_full_file(optarg, &cert_pem, NULL);
969 log_error("Failed to read certificate file: %s", strerror(-r));
977 log_error("CA certificate file specified twice");
980 r = read_full_file(optarg, &trust_pem, NULL);
982 log_error("Failed to read CA certificate file: %s", strerror(-r));
992 assert_not_reached("Unhandled option");
996 log_error("This program does not take arguments.");
1000 if (!!key_pem != !!cert_pem) {
1001 log_error("Certificate and key files must be specified together");
1005 if (trust_pem && !key_pem) {
1006 log_error("CA certificate can only be used with certificate file");
1013 int main(int argc, char *argv[]) {
1014 struct MHD_Daemon *d = NULL;
1017 log_set_target(LOG_TARGET_AUTO);
1018 log_parse_environment();
1021 r = parse_argv(argc, argv);
1023 return EXIT_FAILURE;
1025 return EXIT_SUCCESS;
1028 gnutls_global_set_log_function(log_func_gnutls);
1029 gnutls_global_set_log_level(GNUTLS_LOG_LEVEL);
1032 n = sd_listen_fds(1);
1034 log_error("Failed to determine passed sockets: %s", strerror(-n));
1037 log_error("Can't listen on more than one socket.");
1040 struct MHD_OptionItem opts[] = {
1041 { MHD_OPTION_NOTIFY_COMPLETED,
1042 (intptr_t) request_meta_free, NULL },
1043 { MHD_OPTION_EXTERNAL_LOGGER,
1044 (intptr_t) microhttpd_logger, NULL },
1045 { MHD_OPTION_END, 0, NULL },
1046 { MHD_OPTION_END, 0, NULL },
1047 { MHD_OPTION_END, 0, NULL },
1048 { MHD_OPTION_END, 0, NULL },
1049 { MHD_OPTION_END, 0, NULL }};
1051 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1054 opts[opts_pos++] = (struct MHD_OptionItem)
1055 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1058 opts[opts_pos++] = (struct MHD_OptionItem)
1059 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1060 opts[opts_pos++] = (struct MHD_OptionItem)
1061 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1062 flags |= MHD_USE_SSL;
1065 assert(flags & MHD_USE_SSL);
1066 opts[opts_pos++] = (struct MHD_OptionItem)
1067 {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
1070 d = MHD_start_daemon(flags, 19531,
1072 request_handler, NULL,
1073 MHD_OPTION_ARRAY, opts,
1078 log_error("Failed to start daemon!");