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) {
82 return *connection_cls;
84 m = new0(RequestMeta, 1);
92 static void request_meta_free(
94 struct MHD_Connection *connection,
95 void **connection_cls,
96 enum MHD_RequestTerminationCode toe) {
98 RequestMeta *m = *connection_cls;
104 sd_journal_close(m->journal);
113 static int open_journal(RequestMeta *m) {
119 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
122 static ssize_t request_reader_entries(
128 RequestMeta *m = cls;
135 assert(pos >= m->delta);
139 while (pos >= m->size) {
142 /* End of this entry, so let's serialize the next
145 if (m->n_entries_set &&
147 return MHD_CONTENT_READER_END_OF_STREAM;
150 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
151 else if (m->n_skip > 0)
152 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
154 r = sd_journal_next(m->journal);
157 log_error("Failed to advance journal pointer: %s", strerror(-r));
158 return MHD_CONTENT_READER_END_WITH_ERROR;
162 r = sd_journal_wait(m->journal, (uint64_t) -1);
164 log_error("Couldn't wait for journal event: %s", strerror(-r));
165 return MHD_CONTENT_READER_END_WITH_ERROR;
171 return MHD_CONTENT_READER_END_OF_STREAM;
177 r = sd_journal_test_cursor(m->journal, m->cursor);
179 log_error("Failed to test cursor: %s", strerror(-r));
180 return MHD_CONTENT_READER_END_WITH_ERROR;
184 return MHD_CONTENT_READER_END_OF_STREAM;
190 if (m->n_entries_set)
200 log_error("Failed to create temporary file: %m");
201 return MHD_CONTENT_READER_END_WITH_ERROR;
205 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
207 log_error("Failed to serialize item: %s", strerror(-r));
208 return MHD_CONTENT_READER_END_WITH_ERROR;
212 if (sz == (off_t) -1) {
213 log_error("Failed to retrieve file position: %m");
214 return MHD_CONTENT_READER_END_WITH_ERROR;
217 m->size = (uint64_t) sz;
220 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
221 log_error("Failed to seek to position: %m");
222 return MHD_CONTENT_READER_END_WITH_ERROR;
230 k = fread(buf, 1, n, m->tmp);
232 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
233 return MHD_CONTENT_READER_END_WITH_ERROR;
239 static int request_parse_accept(
241 struct MHD_Connection *connection) {
248 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
252 if (streq(header, mime_types[OUTPUT_JSON]))
253 m->mode = OUTPUT_JSON;
254 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
255 m->mode = OUTPUT_JSON_SSE;
256 else if (streq(header, mime_types[OUTPUT_EXPORT]))
257 m->mode = OUTPUT_EXPORT;
259 m->mode = OUTPUT_SHORT;
264 static int request_parse_range(
266 struct MHD_Connection *connection) {
268 const char *range, *colon, *colon2;
274 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
278 if (!startswith(range, "entries="))
282 range += strspn(range, WHITESPACE);
284 colon = strchr(range, ':');
286 m->cursor = strdup(range);
290 colon2 = strchr(colon + 1, ':');
292 _cleanup_free_ char *t;
294 t = strndup(colon + 1, colon2 - colon - 1);
298 r = safe_atoi64(t, &m->n_skip);
303 p = (colon2 ? colon2 : colon) + 1;
305 r = safe_atou64(p, &m->n_entries);
309 if (m->n_entries <= 0)
312 m->n_entries_set = true;
315 m->cursor = strndup(range, colon - range);
321 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
322 if (isempty(m->cursor)) {
330 static int request_parse_arguments_iterator(
332 enum MHD_ValueKind kind,
336 RequestMeta *m = cls;
337 _cleanup_free_ char *p = NULL;
343 m->argument_parse_error = -EINVAL;
347 if (streq(key, "follow")) {
348 if (isempty(value)) {
353 r = parse_boolean(value);
355 m->argument_parse_error = r;
363 if (streq(key, "discrete")) {
364 if (isempty(value)) {
369 r = parse_boolean(value);
371 m->argument_parse_error = r;
379 if (streq(key, "boot")) {
383 r = parse_boolean(value);
385 m->argument_parse_error = r;
391 char match[9 + 32 + 1] = "_BOOT_ID=";
394 r = sd_id128_get_boot(&bid);
396 log_error("Failed to get boot ID: %s", strerror(-r));
400 sd_id128_to_string(bid, match + 9);
401 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
403 m->argument_parse_error = r;
411 p = strjoin(key, "=", strempty(value), NULL);
413 m->argument_parse_error = log_oom();
417 r = sd_journal_add_match(m->journal, p, 0);
419 m->argument_parse_error = r;
426 static int request_parse_arguments(
428 struct MHD_Connection *connection) {
433 m->argument_parse_error = 0;
434 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
436 return m->argument_parse_error;
439 static int request_handler_entries(
440 struct MHD_Connection *connection,
441 void *connection_cls) {
443 struct MHD_Response *response;
444 RequestMeta *m = connection_cls;
452 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
454 if (request_parse_accept(m, connection) < 0)
455 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
457 if (request_parse_range(m, connection) < 0)
458 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
460 if (request_parse_arguments(m, connection) < 0)
461 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
465 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
468 m->n_entries_set = true;
472 r = sd_journal_seek_cursor(m->journal, m->cursor);
473 else if (m->n_skip >= 0)
474 r = sd_journal_seek_head(m->journal);
475 else if (m->n_skip < 0)
476 r = sd_journal_seek_tail(m->journal);
478 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
480 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
482 return respond_oom(connection);
484 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
486 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
487 MHD_destroy_response(response);
492 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
496 eq = memchr(d, '=', l);
500 j = l - (eq - d + 1);
502 if (m == OUTPUT_JSON) {
503 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
504 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
507 fwrite(eq+1, 1, j, f);
514 static ssize_t request_reader_fields(
520 RequestMeta *m = cls;
527 assert(pos >= m->delta);
531 while (pos >= m->size) {
536 /* End of this field, so let's serialize the next
539 if (m->n_fields_set &&
541 return MHD_CONTENT_READER_END_OF_STREAM;
543 r = sd_journal_enumerate_unique(m->journal, &d, &l);
545 log_error("Failed to advance field index: %s", strerror(-r));
546 return MHD_CONTENT_READER_END_WITH_ERROR;
548 return MHD_CONTENT_READER_END_OF_STREAM;
561 log_error("Failed to create temporary file: %m");
562 return MHD_CONTENT_READER_END_WITH_ERROR;
566 r = output_field(m->tmp, m->mode, d, l);
568 log_error("Failed to serialize item: %s", strerror(-r));
569 return MHD_CONTENT_READER_END_WITH_ERROR;
573 if (sz == (off_t) -1) {
574 log_error("Failed to retrieve file position: %m");
575 return MHD_CONTENT_READER_END_WITH_ERROR;
578 m->size = (uint64_t) sz;
581 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
582 log_error("Failed to seek to position: %m");
583 return MHD_CONTENT_READER_END_WITH_ERROR;
591 k = fread(buf, 1, n, m->tmp);
593 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
594 return MHD_CONTENT_READER_END_WITH_ERROR;
600 static int request_handler_fields(
601 struct MHD_Connection *connection,
603 void *connection_cls) {
605 struct MHD_Response *response;
606 RequestMeta *m = connection_cls;
614 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
616 if (request_parse_accept(m, connection) < 0)
617 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
619 r = sd_journal_query_unique(m->journal, field);
621 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
623 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
625 return respond_oom(connection);
627 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
629 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
630 MHD_destroy_response(response);
635 static int request_handler_redirect(
636 struct MHD_Connection *connection,
637 const char *target) {
640 struct MHD_Response *response;
646 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
647 return respond_oom(connection);
649 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
652 return respond_oom(connection);
655 MHD_add_response_header(response, "Content-Type", "text/html");
656 MHD_add_response_header(response, "Location", target);
658 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
659 MHD_destroy_response(response);
664 static int request_handler_file(
665 struct MHD_Connection *connection,
667 const char *mime_type) {
669 struct MHD_Response *response;
671 _cleanup_close_ int fd = -1;
678 fd = open(path, O_RDONLY|O_CLOEXEC);
680 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
682 if (fstat(fd, &st) < 0)
683 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
685 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
687 return respond_oom(connection);
691 MHD_add_response_header(response, "Content-Type", mime_type);
693 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
694 MHD_destroy_response(response);
699 static int get_virtualization(char **v) {
700 _cleanup_bus_unref_ sd_bus *bus = NULL;
704 r = sd_bus_default_system(&bus);
708 r = sd_bus_get_property_string(
710 "org.freedesktop.systemd1",
711 "/org/freedesktop/systemd1",
712 "org.freedesktop.systemd1.Manager",
729 static int request_handler_machine(
730 struct MHD_Connection *connection,
731 void *connection_cls) {
733 struct MHD_Response *response;
734 RequestMeta *m = connection_cls;
736 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
737 uint64_t cutoff_from = 0, cutoff_to = 0, usage;
740 _cleanup_free_ char *v = NULL;
747 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
749 r = sd_id128_get_machine(&mid);
751 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
753 r = sd_id128_get_boot(&bid);
755 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
757 hostname = gethostname_malloc();
759 return respond_oom(connection);
761 r = sd_journal_get_usage(m->journal, &usage);
763 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
765 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
767 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
769 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
771 get_virtualization(&v);
774 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
775 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
776 "\"hostname\" : \"%s\","
777 "\"os_pretty_name\" : \"%s\","
778 "\"virtualization\" : \"%s\","
779 "\"usage\" : \"%"PRIu64"\","
780 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
781 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
782 SD_ID128_FORMAT_VAL(mid),
783 SD_ID128_FORMAT_VAL(bid),
784 hostname_cleanup(hostname, false),
785 os_name ? os_name : "Linux",
792 return respond_oom(connection);
794 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
797 return respond_oom(connection);
800 MHD_add_response_header(response, "Content-Type", "application/json");
801 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
802 MHD_destroy_response(response);
807 static int request_handler(
809 struct MHD_Connection *connection,
813 const char *upload_data,
814 size_t *upload_data_size,
815 void **connection_cls) {
819 assert(connection_cls);
823 if (!streq(method, "GET"))
824 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
825 "Unsupported method.\n");
828 if (!*connection_cls) {
829 if (!request_meta(connection_cls))
830 return respond_oom(connection);
835 r = check_permissions(connection, &code);
841 return request_handler_redirect(connection, "/browse");
843 if (streq(url, "/entries"))
844 return request_handler_entries(connection, *connection_cls);
846 if (startswith(url, "/fields/"))
847 return request_handler_fields(connection, url + 8, *connection_cls);
849 if (streq(url, "/browse"))
850 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
852 if (streq(url, "/machine"))
853 return request_handler_machine(connection, *connection_cls);
855 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
858 static int help(void) {
860 printf("%s [OPTIONS...] ...\n\n"
861 "HTTP server for journal events.\n\n"
862 " -h --help Show this help\n"
863 " --version Show package version\n"
864 " --cert=CERT.PEM Server certificate in PEM format\n"
865 " --key=KEY.PEM Server key in PEM format\n"
866 " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
867 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)
902 puts(PACKAGE_STRING);
903 puts(SYSTEMD_FEATURES);
908 log_error("Key file specified twice");
911 r = read_full_file(optarg, &key_pem, NULL);
913 log_error("Failed to read key file: %s", strerror(-r));
921 log_error("Certificate file specified twice");
924 r = read_full_file(optarg, &cert_pem, NULL);
926 log_error("Failed to read certificate file: %s", strerror(-r));
935 log_error("CA certificate file specified twice");
938 r = read_full_file(optarg, &trust_pem, NULL);
940 log_error("Failed to read CA certificate file: %s", strerror(-r));
946 log_error("Option --trust is not available.");
953 assert_not_reached("Unhandled option");
957 log_error("This program does not take arguments.");
961 if (!!key_pem != !!cert_pem) {
962 log_error("Certificate and key files must be specified together");
966 if (trust_pem && !key_pem) {
967 log_error("CA certificate can only be used with certificate file");
974 int main(int argc, char *argv[]) {
975 struct MHD_Daemon *d = NULL;
978 log_set_target(LOG_TARGET_AUTO);
979 log_parse_environment();
982 r = parse_argv(argc, argv);
989 gnutls_global_set_log_function(log_func_gnutls);
990 gnutls_global_set_log_level(GNUTLS_LOG_LEVEL);
993 n = sd_listen_fds(1);
995 log_error("Failed to determine passed sockets: %s", strerror(-n));
998 log_error("Can't listen on more than one socket.");
1001 struct MHD_OptionItem opts[] = {
1002 { MHD_OPTION_NOTIFY_COMPLETED,
1003 (intptr_t) request_meta_free, NULL },
1004 { MHD_OPTION_EXTERNAL_LOGGER,
1005 (intptr_t) microhttpd_logger, NULL },
1006 { MHD_OPTION_END, 0, NULL },
1007 { MHD_OPTION_END, 0, NULL },
1008 { MHD_OPTION_END, 0, NULL },
1009 { MHD_OPTION_END, 0, NULL },
1010 { MHD_OPTION_END, 0, NULL }};
1012 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1015 opts[opts_pos++] = (struct MHD_OptionItem)
1016 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1019 opts[opts_pos++] = (struct MHD_OptionItem)
1020 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1021 opts[opts_pos++] = (struct MHD_OptionItem)
1022 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1023 flags |= MHD_USE_SSL;
1026 assert(flags & MHD_USE_SSL);
1027 opts[opts_pos++] = (struct MHD_OptionItem)
1028 {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
1031 d = MHD_start_daemon(flags, 19531,
1033 request_handler, NULL,
1034 MHD_OPTION_ARRAY, opts,
1039 log_error("Failed to start daemon!");