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>
31 #include <gnutls/gnutls.h>
34 #include "sd-journal.h"
35 #include "sd-daemon.h"
40 #include "logs-show.h"
41 #include "microhttpd-util.h"
46 static char *arg_key_pem = NULL;
47 static char *arg_cert_pem = NULL;
48 static char *arg_trust_pem = NULL;
50 typedef struct RequestMeta {
63 int argument_parse_error;
72 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
73 [OUTPUT_SHORT] = "text/plain",
74 [OUTPUT_JSON] = "application/json",
75 [OUTPUT_JSON_SSE] = "text/event-stream",
76 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
79 static RequestMeta *request_meta(void **connection_cls) {
82 assert(connection_cls);
84 return *connection_cls;
86 m = new0(RequestMeta, 1);
94 static void request_meta_free(
96 struct MHD_Connection *connection,
97 void **connection_cls,
98 enum MHD_RequestTerminationCode toe) {
100 RequestMeta *m = *connection_cls;
106 sd_journal_close(m->journal);
115 static int open_journal(RequestMeta *m) {
121 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
124 static ssize_t request_reader_entries(
130 RequestMeta *m = cls;
137 assert(pos >= m->delta);
141 while (pos >= m->size) {
144 /* End of this entry, so let's serialize the next
147 if (m->n_entries_set &&
149 return MHD_CONTENT_READER_END_OF_STREAM;
152 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
153 else if (m->n_skip > 0)
154 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
156 r = sd_journal_next(m->journal);
159 log_error_errno(r, "Failed to advance journal pointer: %m");
160 return MHD_CONTENT_READER_END_WITH_ERROR;
164 r = sd_journal_wait(m->journal, (uint64_t) -1);
166 log_error_errno(r, "Couldn't wait for journal event: %m");
167 return MHD_CONTENT_READER_END_WITH_ERROR;
173 return MHD_CONTENT_READER_END_OF_STREAM;
179 r = sd_journal_test_cursor(m->journal, m->cursor);
181 log_error_errno(r, "Failed to test cursor: %m");
182 return MHD_CONTENT_READER_END_WITH_ERROR;
186 return MHD_CONTENT_READER_END_OF_STREAM;
192 if (m->n_entries_set)
202 log_error_errno(errno, "Failed to create temporary file: %m");
203 return MHD_CONTENT_READER_END_WITH_ERROR;
207 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
209 log_error_errno(r, "Failed to serialize item: %m");
210 return MHD_CONTENT_READER_END_WITH_ERROR;
214 if (sz == (off_t) -1) {
215 log_error_errno(errno, "Failed to retrieve file position: %m");
216 return MHD_CONTENT_READER_END_WITH_ERROR;
219 m->size = (uint64_t) sz;
222 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
223 log_error_errno(errno, "Failed to seek to position: %m");
224 return MHD_CONTENT_READER_END_WITH_ERROR;
232 k = fread(buf, 1, n, m->tmp);
234 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
235 return MHD_CONTENT_READER_END_WITH_ERROR;
241 static int request_parse_accept(
243 struct MHD_Connection *connection) {
250 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
254 if (streq(header, mime_types[OUTPUT_JSON]))
255 m->mode = OUTPUT_JSON;
256 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
257 m->mode = OUTPUT_JSON_SSE;
258 else if (streq(header, mime_types[OUTPUT_EXPORT]))
259 m->mode = OUTPUT_EXPORT;
261 m->mode = OUTPUT_SHORT;
266 static int request_parse_range(
268 struct MHD_Connection *connection) {
270 const char *range, *colon, *colon2;
276 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
280 if (!startswith(range, "entries="))
284 range += strspn(range, WHITESPACE);
286 colon = strchr(range, ':');
288 m->cursor = strdup(range);
292 colon2 = strchr(colon + 1, ':');
294 _cleanup_free_ char *t;
296 t = strndup(colon + 1, colon2 - colon - 1);
300 r = safe_atoi64(t, &m->n_skip);
305 p = (colon2 ? colon2 : colon) + 1;
307 r = safe_atou64(p, &m->n_entries);
311 if (m->n_entries <= 0)
314 m->n_entries_set = true;
317 m->cursor = strndup(range, colon - range);
323 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
324 if (isempty(m->cursor)) {
332 static int request_parse_arguments_iterator(
334 enum MHD_ValueKind kind,
338 RequestMeta *m = cls;
339 _cleanup_free_ char *p = NULL;
345 m->argument_parse_error = -EINVAL;
349 if (streq(key, "follow")) {
350 if (isempty(value)) {
355 r = parse_boolean(value);
357 m->argument_parse_error = r;
365 if (streq(key, "discrete")) {
366 if (isempty(value)) {
371 r = parse_boolean(value);
373 m->argument_parse_error = r;
381 if (streq(key, "boot")) {
385 r = parse_boolean(value);
387 m->argument_parse_error = r;
393 char match[9 + 32 + 1] = "_BOOT_ID=";
396 r = sd_id128_get_boot(&bid);
398 log_error_errno(r, "Failed to get boot ID: %m");
402 sd_id128_to_string(bid, match + 9);
403 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
405 m->argument_parse_error = r;
413 p = strjoin(key, "=", strempty(value), NULL);
415 m->argument_parse_error = log_oom();
419 r = sd_journal_add_match(m->journal, p, 0);
421 m->argument_parse_error = r;
428 static int request_parse_arguments(
430 struct MHD_Connection *connection) {
435 m->argument_parse_error = 0;
436 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
438 return m->argument_parse_error;
441 static int request_handler_entries(
442 struct MHD_Connection *connection,
443 void *connection_cls) {
445 struct MHD_Response *response;
446 RequestMeta *m = connection_cls;
454 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
456 if (request_parse_accept(m, connection) < 0)
457 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
459 if (request_parse_range(m, connection) < 0)
460 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
462 if (request_parse_arguments(m, connection) < 0)
463 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
467 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
470 m->n_entries_set = true;
474 r = sd_journal_seek_cursor(m->journal, m->cursor);
475 else if (m->n_skip >= 0)
476 r = sd_journal_seek_head(m->journal);
477 else if (m->n_skip < 0)
478 r = sd_journal_seek_tail(m->journal);
480 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
482 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
484 return respond_oom(connection);
486 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
488 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
489 MHD_destroy_response(response);
494 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
498 eq = memchr(d, '=', l);
502 j = l - (eq - d + 1);
504 if (m == OUTPUT_JSON) {
505 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
506 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
509 fwrite(eq+1, 1, j, f);
516 static ssize_t request_reader_fields(
522 RequestMeta *m = cls;
529 assert(pos >= m->delta);
533 while (pos >= m->size) {
538 /* End of this field, so let's serialize the next
541 if (m->n_fields_set &&
543 return MHD_CONTENT_READER_END_OF_STREAM;
545 r = sd_journal_enumerate_unique(m->journal, &d, &l);
547 log_error_errno(r, "Failed to advance field index: %m");
548 return MHD_CONTENT_READER_END_WITH_ERROR;
550 return MHD_CONTENT_READER_END_OF_STREAM;
563 log_error_errno(errno, "Failed to create temporary file: %m");
564 return MHD_CONTENT_READER_END_WITH_ERROR;
568 r = output_field(m->tmp, m->mode, d, l);
570 log_error_errno(r, "Failed to serialize item: %m");
571 return MHD_CONTENT_READER_END_WITH_ERROR;
575 if (sz == (off_t) -1) {
576 log_error_errno(errno, "Failed to retrieve file position: %m");
577 return MHD_CONTENT_READER_END_WITH_ERROR;
580 m->size = (uint64_t) sz;
583 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
584 log_error_errno(errno, "Failed to seek to position: %m");
585 return MHD_CONTENT_READER_END_WITH_ERROR;
593 k = fread(buf, 1, n, m->tmp);
595 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
596 return MHD_CONTENT_READER_END_WITH_ERROR;
602 static int request_handler_fields(
603 struct MHD_Connection *connection,
605 void *connection_cls) {
607 struct MHD_Response *response;
608 RequestMeta *m = connection_cls;
616 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
618 if (request_parse_accept(m, connection) < 0)
619 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
621 r = sd_journal_query_unique(m->journal, field);
623 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
625 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
627 return respond_oom(connection);
629 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
631 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
632 MHD_destroy_response(response);
637 static int request_handler_redirect(
638 struct MHD_Connection *connection,
639 const char *target) {
642 struct MHD_Response *response;
648 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
649 return respond_oom(connection);
651 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
654 return respond_oom(connection);
657 MHD_add_response_header(response, "Content-Type", "text/html");
658 MHD_add_response_header(response, "Location", target);
660 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
661 MHD_destroy_response(response);
666 static int request_handler_file(
667 struct MHD_Connection *connection,
669 const char *mime_type) {
671 struct MHD_Response *response;
673 _cleanup_close_ int fd = -1;
680 fd = open(path, O_RDONLY|O_CLOEXEC);
682 return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
684 if (fstat(fd, &st) < 0)
685 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
687 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
689 return respond_oom(connection);
693 MHD_add_response_header(response, "Content-Type", mime_type);
695 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
696 MHD_destroy_response(response);
701 static int get_virtualization(char **v) {
702 _cleanup_bus_unref_ sd_bus *bus = NULL;
706 r = sd_bus_default_system(&bus);
710 r = sd_bus_get_property_string(
712 "org.freedesktop.systemd1",
713 "/org/freedesktop/systemd1",
714 "org.freedesktop.systemd1.Manager",
731 static int request_handler_machine(
732 struct MHD_Connection *connection,
733 void *connection_cls) {
735 struct MHD_Response *response;
736 RequestMeta *m = connection_cls;
738 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
739 uint64_t cutoff_from = 0, cutoff_to = 0, usage;
742 _cleanup_free_ char *v = NULL;
749 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
751 r = sd_id128_get_machine(&mid);
753 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
755 r = sd_id128_get_boot(&bid);
757 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
759 hostname = gethostname_malloc();
761 return respond_oom(connection);
763 r = sd_journal_get_usage(m->journal, &usage);
765 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
767 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
769 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
771 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
772 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
774 get_virtualization(&v);
777 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
778 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
779 "\"hostname\" : \"%s\","
780 "\"os_pretty_name\" : \"%s\","
781 "\"virtualization\" : \"%s\","
782 "\"usage\" : \"%"PRIu64"\","
783 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
784 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
785 SD_ID128_FORMAT_VAL(mid),
786 SD_ID128_FORMAT_VAL(bid),
787 hostname_cleanup(hostname, false),
788 os_name ? os_name : "Linux",
795 return respond_oom(connection);
797 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
800 return respond_oom(connection);
803 MHD_add_response_header(response, "Content-Type", "application/json");
804 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
805 MHD_destroy_response(response);
810 static int request_handler(
812 struct MHD_Connection *connection,
816 const char *upload_data,
817 size_t *upload_data_size,
818 void **connection_cls) {
822 assert(connection_cls);
826 if (!streq(method, "GET"))
827 return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
828 "Unsupported method.\n");
831 if (!*connection_cls) {
832 if (!request_meta(connection_cls))
833 return respond_oom(connection);
838 r = check_permissions(connection, &code, NULL);
844 return request_handler_redirect(connection, "/browse");
846 if (streq(url, "/entries"))
847 return request_handler_entries(connection, *connection_cls);
849 if (startswith(url, "/fields/"))
850 return request_handler_fields(connection, url + 8, *connection_cls);
852 if (streq(url, "/browse"))
853 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
855 if (streq(url, "/machine"))
856 return request_handler_machine(connection, *connection_cls);
858 return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
861 static void help(void) {
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 Certificat authority certificate in PEM format\n",
869 program_invocation_short_name);
872 static int parse_argv(int argc, char *argv[]) {
882 static const struct option options[] = {
883 { "help", no_argument, NULL, 'h' },
884 { "version", no_argument, NULL, ARG_VERSION },
885 { "key", required_argument, NULL, ARG_KEY },
886 { "cert", required_argument, NULL, ARG_CERT },
887 { "trust", required_argument, NULL, ARG_TRUST },
894 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
903 puts(PACKAGE_STRING);
904 puts(SYSTEMD_FEATURES);
909 log_error("Key file specified twice");
912 r = read_full_file(optarg, &arg_key_pem, NULL);
914 return log_error_errno(r, "Failed to read key file: %m");
920 log_error("Certificate file specified twice");
923 r = read_full_file(optarg, &arg_cert_pem, NULL);
925 return log_error_errno(r, "Failed to read certificate file: %m");
926 assert(arg_cert_pem);
932 log_error("CA certificate file specified twice");
935 r = read_full_file(optarg, &arg_trust_pem, NULL);
937 return log_error_errno(r, "Failed to read CA certificate file: %m");
938 assert(arg_trust_pem);
941 log_error("Option --trust is not available.");
948 assert_not_reached("Unhandled option");
952 log_error("This program does not take arguments.");
956 if (!!arg_key_pem != !!arg_cert_pem) {
957 log_error("Certificate and key files must be specified together");
961 if (arg_trust_pem && !arg_key_pem) {
962 log_error("CA certificate can only be used with certificate file");
969 int main(int argc, char *argv[]) {
970 struct MHD_Daemon *d = NULL;
973 log_set_target(LOG_TARGET_AUTO);
974 log_parse_environment();
977 r = parse_argv(argc, argv);
986 gnutls_global_set_log_function(log_func_gnutls);
987 log_reset_gnutls_level();
990 n = sd_listen_fds(1);
992 log_error_errno(n, "Failed to determine passed sockets: %m");
995 log_error("Can't listen on more than one socket.");
998 struct MHD_OptionItem opts[] = {
999 { MHD_OPTION_NOTIFY_COMPLETED,
1000 (intptr_t) request_meta_free, NULL },
1001 { MHD_OPTION_EXTERNAL_LOGGER,
1002 (intptr_t) microhttpd_logger, NULL },
1003 { MHD_OPTION_END, 0, NULL },
1004 { MHD_OPTION_END, 0, NULL },
1005 { MHD_OPTION_END, 0, NULL },
1006 { MHD_OPTION_END, 0, NULL },
1007 { MHD_OPTION_END, 0, NULL }};
1009 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1012 opts[opts_pos++] = (struct MHD_OptionItem)
1013 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1015 assert(arg_cert_pem);
1016 opts[opts_pos++] = (struct MHD_OptionItem)
1017 {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem};
1018 opts[opts_pos++] = (struct MHD_OptionItem)
1019 {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem};
1020 flags |= MHD_USE_SSL;
1022 if (arg_trust_pem) {
1023 assert(flags & MHD_USE_SSL);
1024 opts[opts_pos++] = (struct MHD_OptionItem)
1025 {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem};
1028 d = MHD_start_daemon(flags, 19531,
1030 request_handler, NULL,
1031 MHD_OPTION_ARRAY, opts,
1036 log_error("Failed to start daemon!");