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>
36 #include "sd-journal.h"
37 #include "sd-daemon.h"
40 #include "logs-show.h"
41 #include "microhttpd-util.h"
45 static char *key_pem = NULL;
46 static char *cert_pem = NULL;
47 static char *trust_pem = NULL;
49 typedef struct RequestMeta {
62 int argument_parse_error;
71 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
72 [OUTPUT_SHORT] = "text/plain",
73 [OUTPUT_JSON] = "application/json",
74 [OUTPUT_JSON_SSE] = "text/event-stream",
75 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
78 static RequestMeta *request_meta(void **connection_cls) {
81 assert(connection_cls);
83 return *connection_cls;
85 m = new0(RequestMeta, 1);
93 static void request_meta_free(
95 struct MHD_Connection *connection,
96 void **connection_cls,
97 enum MHD_RequestTerminationCode toe) {
99 RequestMeta *m = *connection_cls;
105 sd_journal_close(m->journal);
114 static int open_journal(RequestMeta *m) {
120 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
123 static ssize_t request_reader_entries(
129 RequestMeta *m = cls;
136 assert(pos >= m->delta);
140 while (pos >= m->size) {
143 /* End of this entry, so let's serialize the next
146 if (m->n_entries_set &&
148 return MHD_CONTENT_READER_END_OF_STREAM;
151 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
152 else if (m->n_skip > 0)
153 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
155 r = sd_journal_next(m->journal);
158 log_error("Failed to advance journal pointer: %s", strerror(-r));
159 return MHD_CONTENT_READER_END_WITH_ERROR;
163 r = sd_journal_wait(m->journal, (uint64_t) -1);
165 log_error("Couldn't wait for journal event: %s", strerror(-r));
166 return MHD_CONTENT_READER_END_WITH_ERROR;
172 return MHD_CONTENT_READER_END_OF_STREAM;
178 r = sd_journal_test_cursor(m->journal, m->cursor);
180 log_error("Failed to test cursor: %s", strerror(-r));
181 return MHD_CONTENT_READER_END_WITH_ERROR;
185 return MHD_CONTENT_READER_END_OF_STREAM;
191 if (m->n_entries_set)
201 log_error("Failed to create temporary file: %m");
202 return MHD_CONTENT_READER_END_WITH_ERROR;
206 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
208 log_error("Failed to serialize item: %s", strerror(-r));
209 return MHD_CONTENT_READER_END_WITH_ERROR;
213 if (sz == (off_t) -1) {
214 log_error("Failed to retrieve file position: %m");
215 return MHD_CONTENT_READER_END_WITH_ERROR;
218 m->size = (uint64_t) sz;
221 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
222 log_error("Failed to seek to position: %m");
223 return MHD_CONTENT_READER_END_WITH_ERROR;
231 k = fread(buf, 1, n, m->tmp);
233 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
234 return MHD_CONTENT_READER_END_WITH_ERROR;
240 static int request_parse_accept(
242 struct MHD_Connection *connection) {
249 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
253 if (streq(header, mime_types[OUTPUT_JSON]))
254 m->mode = OUTPUT_JSON;
255 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
256 m->mode = OUTPUT_JSON_SSE;
257 else if (streq(header, mime_types[OUTPUT_EXPORT]))
258 m->mode = OUTPUT_EXPORT;
260 m->mode = OUTPUT_SHORT;
265 static int request_parse_range(
267 struct MHD_Connection *connection) {
269 const char *range, *colon, *colon2;
275 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
279 if (!startswith(range, "entries="))
283 range += strspn(range, WHITESPACE);
285 colon = strchr(range, ':');
287 m->cursor = strdup(range);
291 colon2 = strchr(colon + 1, ':');
293 _cleanup_free_ char *t;
295 t = strndup(colon + 1, colon2 - colon - 1);
299 r = safe_atoi64(t, &m->n_skip);
304 p = (colon2 ? colon2 : colon) + 1;
306 r = safe_atou64(p, &m->n_entries);
310 if (m->n_entries <= 0)
313 m->n_entries_set = true;
316 m->cursor = strndup(range, colon - range);
322 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
323 if (isempty(m->cursor)) {
331 static int request_parse_arguments_iterator(
333 enum MHD_ValueKind kind,
337 RequestMeta *m = cls;
338 _cleanup_free_ char *p = NULL;
344 m->argument_parse_error = -EINVAL;
348 if (streq(key, "follow")) {
349 if (isempty(value)) {
354 r = parse_boolean(value);
356 m->argument_parse_error = r;
364 if (streq(key, "discrete")) {
365 if (isempty(value)) {
370 r = parse_boolean(value);
372 m->argument_parse_error = r;
380 if (streq(key, "boot")) {
384 r = parse_boolean(value);
386 m->argument_parse_error = r;
392 char match[9 + 32 + 1] = "_BOOT_ID=";
395 r = sd_id128_get_boot(&bid);
397 log_error("Failed to get boot ID: %s", strerror(-r));
401 sd_id128_to_string(bid, match + 9);
402 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
404 m->argument_parse_error = r;
412 p = strjoin(key, "=", strempty(value), NULL);
414 m->argument_parse_error = log_oom();
418 r = sd_journal_add_match(m->journal, p, 0);
420 m->argument_parse_error = r;
427 static int request_parse_arguments(
429 struct MHD_Connection *connection) {
434 m->argument_parse_error = 0;
435 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
437 return m->argument_parse_error;
440 static int request_handler_entries(
441 struct MHD_Connection *connection,
442 void *connection_cls) {
444 struct MHD_Response *response;
445 RequestMeta *m = connection_cls;
453 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
455 if (request_parse_accept(m, connection) < 0)
456 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
458 if (request_parse_range(m, connection) < 0)
459 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
461 if (request_parse_arguments(m, connection) < 0)
462 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
466 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
469 m->n_entries_set = true;
473 r = sd_journal_seek_cursor(m->journal, m->cursor);
474 else if (m->n_skip >= 0)
475 r = sd_journal_seek_head(m->journal);
476 else if (m->n_skip < 0)
477 r = sd_journal_seek_tail(m->journal);
479 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
481 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
483 return respond_oom(connection);
485 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
487 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
488 MHD_destroy_response(response);
493 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
497 eq = memchr(d, '=', l);
501 j = l - (eq - d + 1);
503 if (m == OUTPUT_JSON) {
504 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
505 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
508 fwrite(eq+1, 1, j, f);
515 static ssize_t request_reader_fields(
521 RequestMeta *m = cls;
528 assert(pos >= m->delta);
532 while (pos >= m->size) {
537 /* End of this field, so let's serialize the next
540 if (m->n_fields_set &&
542 return MHD_CONTENT_READER_END_OF_STREAM;
544 r = sd_journal_enumerate_unique(m->journal, &d, &l);
546 log_error("Failed to advance field index: %s", strerror(-r));
547 return MHD_CONTENT_READER_END_WITH_ERROR;
549 return MHD_CONTENT_READER_END_OF_STREAM;
562 log_error("Failed to create temporary file: %m");
563 return MHD_CONTENT_READER_END_WITH_ERROR;
567 r = output_field(m->tmp, m->mode, d, l);
569 log_error("Failed to serialize item: %s", strerror(-r));
570 return MHD_CONTENT_READER_END_WITH_ERROR;
574 if (sz == (off_t) -1) {
575 log_error("Failed to retrieve file position: %m");
576 return MHD_CONTENT_READER_END_WITH_ERROR;
579 m->size = (uint64_t) sz;
582 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
583 log_error("Failed to seek to position: %m");
584 return MHD_CONTENT_READER_END_WITH_ERROR;
592 k = fread(buf, 1, n, m->tmp);
594 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
595 return MHD_CONTENT_READER_END_WITH_ERROR;
601 static int request_handler_fields(
602 struct MHD_Connection *connection,
604 void *connection_cls) {
606 struct MHD_Response *response;
607 RequestMeta *m = connection_cls;
615 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
617 if (request_parse_accept(m, connection) < 0)
618 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
620 r = sd_journal_query_unique(m->journal, field);
622 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
624 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
626 return respond_oom(connection);
628 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
630 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
631 MHD_destroy_response(response);
636 static int request_handler_redirect(
637 struct MHD_Connection *connection,
638 const char *target) {
641 struct MHD_Response *response;
647 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
648 return respond_oom(connection);
650 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
653 return respond_oom(connection);
656 MHD_add_response_header(response, "Content-Type", "text/html");
657 MHD_add_response_header(response, "Location", target);
659 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
660 MHD_destroy_response(response);
665 static int request_handler_file(
666 struct MHD_Connection *connection,
668 const char *mime_type) {
670 struct MHD_Response *response;
672 _cleanup_close_ int fd = -1;
679 fd = open(path, O_RDONLY|O_CLOEXEC);
681 return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
683 if (fstat(fd, &st) < 0)
684 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
686 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
688 return respond_oom(connection);
692 MHD_add_response_header(response, "Content-Type", mime_type);
694 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
695 MHD_destroy_response(response);
700 static int get_virtualization(char **v) {
701 _cleanup_bus_unref_ sd_bus *bus = NULL;
705 r = sd_bus_default_system(&bus);
709 r = sd_bus_get_property_string(
711 "org.freedesktop.systemd1",
712 "/org/freedesktop/systemd1",
713 "org.freedesktop.systemd1.Manager",
730 static int request_handler_machine(
731 struct MHD_Connection *connection,
732 void *connection_cls) {
734 struct MHD_Response *response;
735 RequestMeta *m = connection_cls;
737 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
738 uint64_t cutoff_from = 0, cutoff_to = 0, usage;
741 _cleanup_free_ char *v = NULL;
748 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
750 r = sd_id128_get_machine(&mid);
752 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
754 r = sd_id128_get_boot(&bid);
756 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
758 hostname = gethostname_malloc();
760 return respond_oom(connection);
762 r = sd_journal_get_usage(m->journal, &usage);
764 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
766 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
768 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
770 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
771 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
773 get_virtualization(&v);
776 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
777 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
778 "\"hostname\" : \"%s\","
779 "\"os_pretty_name\" : \"%s\","
780 "\"virtualization\" : \"%s\","
781 "\"usage\" : \"%"PRIu64"\","
782 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
783 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
784 SD_ID128_FORMAT_VAL(mid),
785 SD_ID128_FORMAT_VAL(bid),
786 hostname_cleanup(hostname, false),
787 os_name ? os_name : "Linux",
794 return respond_oom(connection);
796 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
799 return respond_oom(connection);
802 MHD_add_response_header(response, "Content-Type", "application/json");
803 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
804 MHD_destroy_response(response);
809 static int request_handler(
811 struct MHD_Connection *connection,
815 const char *upload_data,
816 size_t *upload_data_size,
817 void **connection_cls) {
821 assert(connection_cls);
825 if (!streq(method, "GET"))
826 return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
827 "Unsupported method.\n");
830 if (!*connection_cls) {
831 if (!request_meta(connection_cls))
832 return respond_oom(connection);
837 r = check_permissions(connection, &code);
843 return request_handler_redirect(connection, "/browse");
845 if (streq(url, "/entries"))
846 return request_handler_entries(connection, *connection_cls);
848 if (startswith(url, "/fields/"))
849 return request_handler_fields(connection, url + 8, *connection_cls);
851 if (streq(url, "/browse"))
852 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
854 if (streq(url, "/machine"))
855 return request_handler_machine(connection, *connection_cls);
857 return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
860 static int 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);
874 static int parse_argv(int argc, char *argv[]) {
884 static const struct option options[] = {
885 { "help", no_argument, NULL, 'h' },
886 { "version", no_argument, NULL, ARG_VERSION },
887 { "key", required_argument, NULL, ARG_KEY },
888 { "cert", required_argument, NULL, ARG_CERT },
889 { "trust", required_argument, NULL, ARG_TRUST },
896 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
904 puts(PACKAGE_STRING);
905 puts(SYSTEMD_FEATURES);
910 log_error("Key file specified twice");
913 r = read_full_file(optarg, &key_pem, NULL);
915 log_error("Failed to read key file: %s", strerror(-r));
923 log_error("Certificate file specified twice");
926 r = read_full_file(optarg, &cert_pem, NULL);
928 log_error("Failed to read certificate file: %s", strerror(-r));
937 log_error("CA certificate file specified twice");
940 r = read_full_file(optarg, &trust_pem, NULL);
942 log_error("Failed to read CA certificate file: %s", strerror(-r));
948 log_error("Option --trust is not available.");
955 assert_not_reached("Unhandled option");
959 log_error("This program does not take arguments.");
963 if (!!key_pem != !!cert_pem) {
964 log_error("Certificate and key files must be specified together");
968 if (trust_pem && !key_pem) {
969 log_error("CA certificate can only be used with certificate file");
976 int main(int argc, char *argv[]) {
977 struct MHD_Daemon *d = NULL;
980 log_set_target(LOG_TARGET_AUTO);
981 log_parse_environment();
984 r = parse_argv(argc, argv);
991 gnutls_global_set_log_function(log_func_gnutls);
992 gnutls_global_set_log_level(GNUTLS_LOG_LEVEL);
995 n = sd_listen_fds(1);
997 log_error("Failed to determine passed sockets: %s", strerror(-n));
1000 log_error("Can't listen on more than one socket.");
1003 struct MHD_OptionItem opts[] = {
1004 { MHD_OPTION_NOTIFY_COMPLETED,
1005 (intptr_t) request_meta_free, NULL },
1006 { MHD_OPTION_EXTERNAL_LOGGER,
1007 (intptr_t) microhttpd_logger, NULL },
1008 { MHD_OPTION_END, 0, NULL },
1009 { MHD_OPTION_END, 0, NULL },
1010 { MHD_OPTION_END, 0, NULL },
1011 { MHD_OPTION_END, 0, NULL },
1012 { MHD_OPTION_END, 0, NULL }};
1014 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1017 opts[opts_pos++] = (struct MHD_OptionItem)
1018 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1021 opts[opts_pos++] = (struct MHD_OptionItem)
1022 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1023 opts[opts_pos++] = (struct MHD_OptionItem)
1024 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1025 flags |= MHD_USE_SSL;
1028 assert(flags & MHD_USE_SSL);
1029 opts[opts_pos++] = (struct MHD_OptionItem)
1030 {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
1033 d = MHD_start_daemon(flags, 19531,
1035 request_handler, NULL,
1036 MHD_OPTION_ARRAY, opts,
1041 log_error("Failed to start daemon!");