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 int request_meta_ensure_tmp(RequestMeta *m) {
130 fd = open_tmpfile("/tmp", O_RDWR|O_CLOEXEC);
134 m->tmp = fdopen(fd, "rw");
144 static ssize_t request_reader_entries(
150 RequestMeta *m = cls;
157 assert(pos >= m->delta);
161 while (pos >= m->size) {
164 /* End of this entry, so let's serialize the next
167 if (m->n_entries_set &&
169 return MHD_CONTENT_READER_END_OF_STREAM;
172 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
173 else if (m->n_skip > 0)
174 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
176 r = sd_journal_next(m->journal);
179 log_error_errno(r, "Failed to advance journal pointer: %m");
180 return MHD_CONTENT_READER_END_WITH_ERROR;
184 r = sd_journal_wait(m->journal, (uint64_t) -1);
186 log_error_errno(r, "Couldn't wait for journal event: %m");
187 return MHD_CONTENT_READER_END_WITH_ERROR;
193 return MHD_CONTENT_READER_END_OF_STREAM;
199 r = sd_journal_test_cursor(m->journal, m->cursor);
201 log_error_errno(r, "Failed to test cursor: %m");
202 return MHD_CONTENT_READER_END_WITH_ERROR;
206 return MHD_CONTENT_READER_END_OF_STREAM;
212 if (m->n_entries_set)
217 r = request_meta_ensure_tmp(m);
219 log_error_errno(r, "Failed to create temporary file: %m");
220 return MHD_CONTENT_READER_END_WITH_ERROR;
223 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
225 log_error_errno(r, "Failed to serialize item: %m");
226 return MHD_CONTENT_READER_END_WITH_ERROR;
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;
235 m->size = (uint64_t) sz;
238 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
239 log_error_errno(errno, "Failed to seek to position: %m");
240 return MHD_CONTENT_READER_END_WITH_ERROR;
248 k = fread(buf, 1, n, m->tmp);
250 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
251 return MHD_CONTENT_READER_END_WITH_ERROR;
257 static int request_parse_accept(
259 struct MHD_Connection *connection) {
266 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
270 if (streq(header, mime_types[OUTPUT_JSON]))
271 m->mode = OUTPUT_JSON;
272 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
273 m->mode = OUTPUT_JSON_SSE;
274 else if (streq(header, mime_types[OUTPUT_EXPORT]))
275 m->mode = OUTPUT_EXPORT;
277 m->mode = OUTPUT_SHORT;
282 static int request_parse_range(
284 struct MHD_Connection *connection) {
286 const char *range, *colon, *colon2;
292 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
296 if (!startswith(range, "entries="))
300 range += strspn(range, WHITESPACE);
302 colon = strchr(range, ':');
304 m->cursor = strdup(range);
308 colon2 = strchr(colon + 1, ':');
310 _cleanup_free_ char *t;
312 t = strndup(colon + 1, colon2 - colon - 1);
316 r = safe_atoi64(t, &m->n_skip);
321 p = (colon2 ? colon2 : colon) + 1;
323 r = safe_atou64(p, &m->n_entries);
327 if (m->n_entries <= 0)
330 m->n_entries_set = true;
333 m->cursor = strndup(range, colon - range);
339 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
340 if (isempty(m->cursor)) {
348 static int request_parse_arguments_iterator(
350 enum MHD_ValueKind kind,
354 RequestMeta *m = cls;
355 _cleanup_free_ char *p = NULL;
361 m->argument_parse_error = -EINVAL;
365 if (streq(key, "follow")) {
366 if (isempty(value)) {
371 r = parse_boolean(value);
373 m->argument_parse_error = r;
381 if (streq(key, "discrete")) {
382 if (isempty(value)) {
387 r = parse_boolean(value);
389 m->argument_parse_error = r;
397 if (streq(key, "boot")) {
401 r = parse_boolean(value);
403 m->argument_parse_error = r;
409 char match[9 + 32 + 1] = "_BOOT_ID=";
412 r = sd_id128_get_boot(&bid);
414 log_error_errno(r, "Failed to get boot ID: %m");
418 sd_id128_to_string(bid, match + 9);
419 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
421 m->argument_parse_error = r;
429 p = strjoin(key, "=", strempty(value), NULL);
431 m->argument_parse_error = log_oom();
435 r = sd_journal_add_match(m->journal, p, 0);
437 m->argument_parse_error = r;
444 static int request_parse_arguments(
446 struct MHD_Connection *connection) {
451 m->argument_parse_error = 0;
452 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
454 return m->argument_parse_error;
457 static int request_handler_entries(
458 struct MHD_Connection *connection,
459 void *connection_cls) {
461 struct MHD_Response *response;
462 RequestMeta *m = connection_cls;
470 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
472 if (request_parse_accept(m, connection) < 0)
473 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
475 if (request_parse_range(m, connection) < 0)
476 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
478 if (request_parse_arguments(m, connection) < 0)
479 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
483 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
486 m->n_entries_set = true;
490 r = sd_journal_seek_cursor(m->journal, m->cursor);
491 else if (m->n_skip >= 0)
492 r = sd_journal_seek_head(m->journal);
493 else if (m->n_skip < 0)
494 r = sd_journal_seek_tail(m->journal);
496 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
498 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
500 return respond_oom(connection);
502 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
504 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
505 MHD_destroy_response(response);
510 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
514 eq = memchr(d, '=', l);
518 j = l - (eq - d + 1);
520 if (m == OUTPUT_JSON) {
521 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
522 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
525 fwrite(eq+1, 1, j, f);
532 static ssize_t request_reader_fields(
538 RequestMeta *m = cls;
545 assert(pos >= m->delta);
549 while (pos >= m->size) {
554 /* End of this field, so let's serialize the next
557 if (m->n_fields_set &&
559 return MHD_CONTENT_READER_END_OF_STREAM;
561 r = sd_journal_enumerate_unique(m->journal, &d, &l);
563 log_error_errno(r, "Failed to advance field index: %m");
564 return MHD_CONTENT_READER_END_WITH_ERROR;
566 return MHD_CONTENT_READER_END_OF_STREAM;
574 r = request_meta_ensure_tmp(m);
576 log_error_errno(r, "Failed to create temporary file: %m");
577 return MHD_CONTENT_READER_END_WITH_ERROR;
580 r = output_field(m->tmp, m->mode, d, l);
582 log_error_errno(r, "Failed to serialize item: %m");
583 return MHD_CONTENT_READER_END_WITH_ERROR;
587 if (sz == (off_t) -1) {
588 log_error_errno(errno, "Failed to retrieve file position: %m");
589 return MHD_CONTENT_READER_END_WITH_ERROR;
592 m->size = (uint64_t) sz;
595 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
596 log_error_errno(errno, "Failed to seek to position: %m");
597 return MHD_CONTENT_READER_END_WITH_ERROR;
605 k = fread(buf, 1, n, m->tmp);
607 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
608 return MHD_CONTENT_READER_END_WITH_ERROR;
614 static int request_handler_fields(
615 struct MHD_Connection *connection,
617 void *connection_cls) {
619 struct MHD_Response *response;
620 RequestMeta *m = connection_cls;
628 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
630 if (request_parse_accept(m, connection) < 0)
631 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
633 r = sd_journal_query_unique(m->journal, field);
635 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
637 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
639 return respond_oom(connection);
641 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
643 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
644 MHD_destroy_response(response);
649 static int request_handler_redirect(
650 struct MHD_Connection *connection,
651 const char *target) {
654 struct MHD_Response *response;
660 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
661 return respond_oom(connection);
663 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
666 return respond_oom(connection);
669 MHD_add_response_header(response, "Content-Type", "text/html");
670 MHD_add_response_header(response, "Location", target);
672 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
673 MHD_destroy_response(response);
678 static int request_handler_file(
679 struct MHD_Connection *connection,
681 const char *mime_type) {
683 struct MHD_Response *response;
685 _cleanup_close_ int fd = -1;
692 fd = open(path, O_RDONLY|O_CLOEXEC);
694 return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
696 if (fstat(fd, &st) < 0)
697 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
699 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
701 return respond_oom(connection);
705 MHD_add_response_header(response, "Content-Type", mime_type);
707 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
708 MHD_destroy_response(response);
713 static int get_virtualization(char **v) {
714 _cleanup_bus_unref_ sd_bus *bus = NULL;
718 r = sd_bus_default_system(&bus);
722 r = sd_bus_get_property_string(
724 "org.freedesktop.systemd1",
725 "/org/freedesktop/systemd1",
726 "org.freedesktop.systemd1.Manager",
743 static int request_handler_machine(
744 struct MHD_Connection *connection,
745 void *connection_cls) {
747 struct MHD_Response *response;
748 RequestMeta *m = connection_cls;
750 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
751 uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0;
754 _cleanup_free_ char *v = NULL;
761 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
763 r = sd_id128_get_machine(&mid);
765 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
767 r = sd_id128_get_boot(&bid);
769 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
771 hostname = gethostname_malloc();
773 return respond_oom(connection);
775 r = sd_journal_get_usage(m->journal, &usage);
777 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
779 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
781 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
783 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
784 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
786 get_virtualization(&v);
789 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
790 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
791 "\"hostname\" : \"%s\","
792 "\"os_pretty_name\" : \"%s\","
793 "\"virtualization\" : \"%s\","
794 "\"usage\" : \"%"PRIu64"\","
795 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
796 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
797 SD_ID128_FORMAT_VAL(mid),
798 SD_ID128_FORMAT_VAL(bid),
799 hostname_cleanup(hostname, false),
800 os_name ? os_name : "Linux",
807 return respond_oom(connection);
809 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
812 return respond_oom(connection);
815 MHD_add_response_header(response, "Content-Type", "application/json");
816 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
817 MHD_destroy_response(response);
822 static int request_handler(
824 struct MHD_Connection *connection,
828 const char *upload_data,
829 size_t *upload_data_size,
830 void **connection_cls) {
834 assert(connection_cls);
838 if (!streq(method, "GET"))
839 return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
840 "Unsupported method.\n");
843 if (!*connection_cls) {
844 if (!request_meta(connection_cls))
845 return respond_oom(connection);
850 r = check_permissions(connection, &code, NULL);
856 return request_handler_redirect(connection, "/browse");
858 if (streq(url, "/entries"))
859 return request_handler_entries(connection, *connection_cls);
861 if (startswith(url, "/fields/"))
862 return request_handler_fields(connection, url + 8, *connection_cls);
864 if (streq(url, "/browse"))
865 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
867 if (streq(url, "/machine"))
868 return request_handler_machine(connection, *connection_cls);
870 return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
873 static void help(void) {
874 printf("%s [OPTIONS...] ...\n\n"
875 "HTTP server for journal events.\n\n"
876 " -h --help Show this help\n"
877 " --version Show package version\n"
878 " --cert=CERT.PEM Server certificate in PEM format\n"
879 " --key=KEY.PEM Server key in PEM format\n"
880 " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
881 program_invocation_short_name);
884 static int parse_argv(int argc, char *argv[]) {
894 static const struct option options[] = {
895 { "help", no_argument, NULL, 'h' },
896 { "version", no_argument, NULL, ARG_VERSION },
897 { "key", required_argument, NULL, ARG_KEY },
898 { "cert", required_argument, NULL, ARG_CERT },
899 { "trust", required_argument, NULL, ARG_TRUST },
906 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
915 puts(PACKAGE_STRING);
916 puts(SYSTEMD_FEATURES);
921 log_error("Key file specified twice");
924 r = read_full_file(optarg, &arg_key_pem, NULL);
926 return log_error_errno(r, "Failed to read key file: %m");
932 log_error("Certificate file specified twice");
935 r = read_full_file(optarg, &arg_cert_pem, NULL);
937 return log_error_errno(r, "Failed to read certificate file: %m");
938 assert(arg_cert_pem);
944 log_error("CA certificate file specified twice");
947 r = read_full_file(optarg, &arg_trust_pem, NULL);
949 return log_error_errno(r, "Failed to read CA certificate file: %m");
950 assert(arg_trust_pem);
953 log_error("Option --trust is not available.");
960 assert_not_reached("Unhandled option");
964 log_error("This program does not take arguments.");
968 if (!!arg_key_pem != !!arg_cert_pem) {
969 log_error("Certificate and key files must be specified together");
973 if (arg_trust_pem && !arg_key_pem) {
974 log_error("CA certificate can only be used with certificate file");
981 int main(int argc, char *argv[]) {
982 struct MHD_Daemon *d = NULL;
985 log_set_target(LOG_TARGET_AUTO);
986 log_parse_environment();
989 r = parse_argv(argc, argv);
997 r = setup_gnutls_logger(NULL);
1001 n = sd_listen_fds(1);
1003 log_error_errno(n, "Failed to determine passed sockets: %m");
1006 log_error("Can't listen on more than one socket.");
1009 struct MHD_OptionItem opts[] = {
1010 { MHD_OPTION_NOTIFY_COMPLETED,
1011 (intptr_t) request_meta_free, NULL },
1012 { MHD_OPTION_EXTERNAL_LOGGER,
1013 (intptr_t) microhttpd_logger, NULL },
1014 { MHD_OPTION_END, 0, NULL },
1015 { MHD_OPTION_END, 0, NULL },
1016 { MHD_OPTION_END, 0, NULL },
1017 { MHD_OPTION_END, 0, NULL },
1018 { MHD_OPTION_END, 0, NULL }};
1020 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1023 opts[opts_pos++] = (struct MHD_OptionItem)
1024 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1026 assert(arg_cert_pem);
1027 opts[opts_pos++] = (struct MHD_OptionItem)
1028 {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem};
1029 opts[opts_pos++] = (struct MHD_OptionItem)
1030 {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem};
1031 flags |= MHD_USE_SSL;
1033 if (arg_trust_pem) {
1034 assert(flags & MHD_USE_SSL);
1035 opts[opts_pos++] = (struct MHD_OptionItem)
1036 {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem};
1039 d = MHD_start_daemon(flags, 19531,
1041 request_handler, NULL,
1042 MHD_OPTION_ARRAY, opts,
1047 log_error("Failed to start daemon!");