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/>.
25 #include <gnutls/gnutls.h>
27 #include <microhttpd.h>
33 #include "sd-daemon.h"
34 #include "sd-journal.h"
39 #include "hostname-util.h"
41 #include "logs-show.h"
42 #include "microhttpd-util.h"
43 #include "parse-util.h"
47 static char *arg_key_pem
= NULL
;
48 static char *arg_cert_pem
= NULL
;
49 static char *arg_trust_pem
= NULL
;
51 typedef struct RequestMeta
{
64 int argument_parse_error
;
73 static const char* const mime_types
[_OUTPUT_MODE_MAX
] = {
74 [OUTPUT_SHORT
] = "text/plain",
75 [OUTPUT_JSON
] = "application/json",
76 [OUTPUT_JSON_SSE
] = "text/event-stream",
77 [OUTPUT_EXPORT
] = "application/vnd.fdo.journal",
80 static RequestMeta
*request_meta(void **connection_cls
) {
83 assert(connection_cls
);
85 return *connection_cls
;
87 m
= new0(RequestMeta
, 1);
95 static void request_meta_free(
97 struct MHD_Connection
*connection
,
98 void **connection_cls
,
99 enum MHD_RequestTerminationCode toe
) {
101 RequestMeta
*m
= *connection_cls
;
106 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 int request_meta_ensure_tmp(RequestMeta
*m
) {
129 fd
= open_tmpfile("/tmp", O_RDWR
|O_CLOEXEC
);
133 m
->tmp
= fdopen(fd
, "w+");
143 static ssize_t
request_reader_entries(
149 RequestMeta
*m
= cls
;
156 assert(pos
>= m
->delta
);
160 while (pos
>= m
->size
) {
163 /* End of this entry, so let's serialize the next
166 if (m
->n_entries_set
&&
168 return MHD_CONTENT_READER_END_OF_STREAM
;
171 r
= sd_journal_previous_skip(m
->journal
, (uint64_t) -m
->n_skip
+ 1);
172 else if (m
->n_skip
> 0)
173 r
= sd_journal_next_skip(m
->journal
, (uint64_t) m
->n_skip
+ 1);
175 r
= sd_journal_next(m
->journal
);
178 log_error_errno(r
, "Failed to advance journal pointer: %m");
179 return MHD_CONTENT_READER_END_WITH_ERROR
;
183 r
= sd_journal_wait(m
->journal
, (uint64_t) -1);
185 log_error_errno(r
, "Couldn't wait for journal event: %m");
186 return MHD_CONTENT_READER_END_WITH_ERROR
;
192 return MHD_CONTENT_READER_END_OF_STREAM
;
198 r
= sd_journal_test_cursor(m
->journal
, m
->cursor
);
200 log_error_errno(r
, "Failed to test cursor: %m");
201 return MHD_CONTENT_READER_END_WITH_ERROR
;
205 return MHD_CONTENT_READER_END_OF_STREAM
;
211 if (m
->n_entries_set
)
216 r
= request_meta_ensure_tmp(m
);
218 log_error_errno(r
, "Failed to create temporary file: %m");
219 return MHD_CONTENT_READER_END_WITH_ERROR
;
222 r
= output_journal(m
->tmp
, m
->journal
, m
->mode
, 0, OUTPUT_FULL_WIDTH
, NULL
);
224 log_error_errno(r
, "Failed to serialize item: %m");
225 return MHD_CONTENT_READER_END_WITH_ERROR
;
229 if (sz
== (off_t
) -1) {
230 log_error_errno(errno
, "Failed to retrieve file position: %m");
231 return MHD_CONTENT_READER_END_WITH_ERROR
;
234 m
->size
= (uint64_t) sz
;
237 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
238 log_error_errno(errno
, "Failed to seek to position: %m");
239 return MHD_CONTENT_READER_END_WITH_ERROR
;
247 k
= fread(buf
, 1, n
, m
->tmp
);
249 log_error("Failed to read from file: %s", errno
? strerror(errno
) : "Premature EOF");
250 return MHD_CONTENT_READER_END_WITH_ERROR
;
256 static int request_parse_accept(
258 struct MHD_Connection
*connection
) {
265 header
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Accept");
269 if (streq(header
, mime_types
[OUTPUT_JSON
]))
270 m
->mode
= OUTPUT_JSON
;
271 else if (streq(header
, mime_types
[OUTPUT_JSON_SSE
]))
272 m
->mode
= OUTPUT_JSON_SSE
;
273 else if (streq(header
, mime_types
[OUTPUT_EXPORT
]))
274 m
->mode
= OUTPUT_EXPORT
;
276 m
->mode
= OUTPUT_SHORT
;
281 static int request_parse_range(
283 struct MHD_Connection
*connection
) {
285 const char *range
, *colon
, *colon2
;
291 range
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Range");
295 if (!startswith(range
, "entries="))
299 range
+= strspn(range
, WHITESPACE
);
301 colon
= strchr(range
, ':');
303 m
->cursor
= strdup(range
);
307 colon2
= strchr(colon
+ 1, ':');
309 _cleanup_free_
char *t
;
311 t
= strndup(colon
+ 1, colon2
- colon
- 1);
315 r
= safe_atoi64(t
, &m
->n_skip
);
320 p
= (colon2
? colon2
: colon
) + 1;
322 r
= safe_atou64(p
, &m
->n_entries
);
326 if (m
->n_entries
<= 0)
329 m
->n_entries_set
= true;
332 m
->cursor
= strndup(range
, colon
- range
);
338 m
->cursor
[strcspn(m
->cursor
, WHITESPACE
)] = 0;
339 if (isempty(m
->cursor
))
340 m
->cursor
= mfree(m
->cursor
);
345 static int request_parse_arguments_iterator(
347 enum MHD_ValueKind kind
,
351 RequestMeta
*m
= cls
;
352 _cleanup_free_
char *p
= NULL
;
358 m
->argument_parse_error
= -EINVAL
;
362 if (streq(key
, "follow")) {
363 if (isempty(value
)) {
368 r
= parse_boolean(value
);
370 m
->argument_parse_error
= r
;
378 if (streq(key
, "discrete")) {
379 if (isempty(value
)) {
384 r
= parse_boolean(value
);
386 m
->argument_parse_error
= r
;
394 if (streq(key
, "boot")) {
398 r
= parse_boolean(value
);
400 m
->argument_parse_error
= r
;
406 char match
[9 + 32 + 1] = "_BOOT_ID=";
409 r
= sd_id128_get_boot(&bid
);
411 log_error_errno(r
, "Failed to get boot ID: %m");
415 sd_id128_to_string(bid
, match
+ 9);
416 r
= sd_journal_add_match(m
->journal
, match
, sizeof(match
)-1);
418 m
->argument_parse_error
= r
;
426 p
= strjoin(key
, "=", strempty(value
), NULL
);
428 m
->argument_parse_error
= log_oom();
432 r
= sd_journal_add_match(m
->journal
, p
, 0);
434 m
->argument_parse_error
= r
;
441 static int request_parse_arguments(
443 struct MHD_Connection
*connection
) {
448 m
->argument_parse_error
= 0;
449 MHD_get_connection_values(connection
, MHD_GET_ARGUMENT_KIND
, request_parse_arguments_iterator
, m
);
451 return m
->argument_parse_error
;
454 static int request_handler_entries(
455 struct MHD_Connection
*connection
,
456 void *connection_cls
) {
458 struct MHD_Response
*response
;
459 RequestMeta
*m
= connection_cls
;
467 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
469 if (request_parse_accept(m
, connection
) < 0)
470 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.\n");
472 if (request_parse_range(m
, connection
) < 0)
473 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Range header.\n");
475 if (request_parse_arguments(m
, connection
) < 0)
476 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse URL arguments.\n");
480 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Discrete seeks require a cursor specification.\n");
483 m
->n_entries_set
= true;
487 r
= sd_journal_seek_cursor(m
->journal
, m
->cursor
);
488 else if (m
->n_skip
>= 0)
489 r
= sd_journal_seek_head(m
->journal
);
490 else if (m
->n_skip
< 0)
491 r
= sd_journal_seek_tail(m
->journal
);
493 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to seek in journal.\n");
495 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_entries
, m
, NULL
);
497 return respond_oom(connection
);
499 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
]);
501 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
502 MHD_destroy_response(response
);
507 static int output_field(FILE *f
, OutputMode m
, const char *d
, size_t l
) {
511 eq
= memchr(d
, '=', l
);
515 j
= l
- (eq
- d
+ 1);
517 if (m
== OUTPUT_JSON
) {
518 fprintf(f
, "{ \"%.*s\" : ", (int) (eq
- d
), d
);
519 json_escape(f
, eq
+1, j
, OUTPUT_FULL_WIDTH
);
522 fwrite(eq
+1, 1, j
, f
);
529 static ssize_t
request_reader_fields(
535 RequestMeta
*m
= cls
;
542 assert(pos
>= m
->delta
);
546 while (pos
>= m
->size
) {
551 /* End of this field, so let's serialize the next
554 if (m
->n_fields_set
&&
556 return MHD_CONTENT_READER_END_OF_STREAM
;
558 r
= sd_journal_enumerate_unique(m
->journal
, &d
, &l
);
560 log_error_errno(r
, "Failed to advance field index: %m");
561 return MHD_CONTENT_READER_END_WITH_ERROR
;
563 return MHD_CONTENT_READER_END_OF_STREAM
;
571 r
= request_meta_ensure_tmp(m
);
573 log_error_errno(r
, "Failed to create temporary file: %m");
574 return MHD_CONTENT_READER_END_WITH_ERROR
;
577 r
= output_field(m
->tmp
, m
->mode
, d
, l
);
579 log_error_errno(r
, "Failed to serialize item: %m");
580 return MHD_CONTENT_READER_END_WITH_ERROR
;
584 if (sz
== (off_t
) -1) {
585 log_error_errno(errno
, "Failed to retrieve file position: %m");
586 return MHD_CONTENT_READER_END_WITH_ERROR
;
589 m
->size
= (uint64_t) sz
;
592 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
593 log_error_errno(errno
, "Failed to seek to position: %m");
594 return MHD_CONTENT_READER_END_WITH_ERROR
;
602 k
= fread(buf
, 1, n
, m
->tmp
);
604 log_error("Failed to read from file: %s", errno
? strerror(errno
) : "Premature EOF");
605 return MHD_CONTENT_READER_END_WITH_ERROR
;
611 static int request_handler_fields(
612 struct MHD_Connection
*connection
,
614 void *connection_cls
) {
616 struct MHD_Response
*response
;
617 RequestMeta
*m
= connection_cls
;
625 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
627 if (request_parse_accept(m
, connection
) < 0)
628 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.\n");
630 r
= sd_journal_query_unique(m
->journal
, field
);
632 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to query unique fields.\n");
634 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_fields
, m
, NULL
);
636 return respond_oom(connection
);
638 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
== OUTPUT_JSON
? OUTPUT_JSON
: OUTPUT_SHORT
]);
640 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
641 MHD_destroy_response(response
);
646 static int request_handler_redirect(
647 struct MHD_Connection
*connection
,
648 const char *target
) {
651 struct MHD_Response
*response
;
657 if (asprintf(&page
, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target
) < 0)
658 return respond_oom(connection
);
660 response
= MHD_create_response_from_buffer(strlen(page
), page
, MHD_RESPMEM_MUST_FREE
);
663 return respond_oom(connection
);
666 MHD_add_response_header(response
, "Content-Type", "text/html");
667 MHD_add_response_header(response
, "Location", target
);
669 ret
= MHD_queue_response(connection
, MHD_HTTP_MOVED_PERMANENTLY
, response
);
670 MHD_destroy_response(response
);
675 static int request_handler_file(
676 struct MHD_Connection
*connection
,
678 const char *mime_type
) {
680 struct MHD_Response
*response
;
682 _cleanup_close_
int fd
= -1;
689 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
691 return mhd_respondf(connection
, MHD_HTTP_NOT_FOUND
, "Failed to open file %s: %m\n", path
);
693 if (fstat(fd
, &st
) < 0)
694 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to stat file: %m\n");
696 response
= MHD_create_response_from_fd_at_offset(st
.st_size
, fd
, 0);
698 return respond_oom(connection
);
702 MHD_add_response_header(response
, "Content-Type", mime_type
);
704 ret
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
705 MHD_destroy_response(response
);
710 static int get_virtualization(char **v
) {
711 _cleanup_bus_unref_ sd_bus
*bus
= NULL
;
715 r
= sd_bus_default_system(&bus
);
719 r
= sd_bus_get_property_string(
721 "org.freedesktop.systemd1",
722 "/org/freedesktop/systemd1",
723 "org.freedesktop.systemd1.Manager",
740 static int request_handler_machine(
741 struct MHD_Connection
*connection
,
742 void *connection_cls
) {
744 struct MHD_Response
*response
;
745 RequestMeta
*m
= connection_cls
;
747 _cleanup_free_
char* hostname
= NULL
, *os_name
= NULL
;
748 uint64_t cutoff_from
= 0, cutoff_to
= 0, usage
= 0;
751 _cleanup_free_
char *v
= NULL
;
758 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
760 r
= sd_id128_get_machine(&mid
);
762 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine machine ID: %s\n", strerror(-r
));
764 r
= sd_id128_get_boot(&bid
);
766 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine boot ID: %s\n", strerror(-r
));
768 hostname
= gethostname_malloc();
770 return respond_oom(connection
);
772 r
= sd_journal_get_usage(m
->journal
, &usage
);
774 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %s\n", strerror(-r
));
776 r
= sd_journal_get_cutoff_realtime_usec(m
->journal
, &cutoff_from
, &cutoff_to
);
778 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %s\n", strerror(-r
));
780 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &os_name
, NULL
) == -ENOENT
)
781 (void) parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &os_name
, NULL
);
783 get_virtualization(&v
);
786 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR
"\","
787 "\"boot_id\" : \"" SD_ID128_FORMAT_STR
"\","
788 "\"hostname\" : \"%s\","
789 "\"os_pretty_name\" : \"%s\","
790 "\"virtualization\" : \"%s\","
791 "\"usage\" : \"%"PRIu64
"\","
792 "\"cutoff_from_realtime\" : \"%"PRIu64
"\","
793 "\"cutoff_to_realtime\" : \"%"PRIu64
"\" }\n",
794 SD_ID128_FORMAT_VAL(mid
),
795 SD_ID128_FORMAT_VAL(bid
),
796 hostname_cleanup(hostname
),
797 os_name
? os_name
: "Linux",
804 return respond_oom(connection
);
806 response
= MHD_create_response_from_buffer(strlen(json
), json
, MHD_RESPMEM_MUST_FREE
);
809 return respond_oom(connection
);
812 MHD_add_response_header(response
, "Content-Type", "application/json");
813 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
814 MHD_destroy_response(response
);
819 static int request_handler(
821 struct MHD_Connection
*connection
,
825 const char *upload_data
,
826 size_t *upload_data_size
,
827 void **connection_cls
) {
831 assert(connection_cls
);
835 if (!streq(method
, "GET"))
836 return mhd_respond(connection
, MHD_HTTP_METHOD_NOT_ACCEPTABLE
,
837 "Unsupported method.\n");
840 if (!*connection_cls
) {
841 if (!request_meta(connection_cls
))
842 return respond_oom(connection
);
847 r
= check_permissions(connection
, &code
, NULL
);
853 return request_handler_redirect(connection
, "/browse");
855 if (streq(url
, "/entries"))
856 return request_handler_entries(connection
, *connection_cls
);
858 if (startswith(url
, "/fields/"))
859 return request_handler_fields(connection
, url
+ 8, *connection_cls
);
861 if (streq(url
, "/browse"))
862 return request_handler_file(connection
, DOCUMENT_ROOT
"/browse.html", "text/html");
864 if (streq(url
, "/machine"))
865 return request_handler_machine(connection
, *connection_cls
);
867 return mhd_respond(connection
, MHD_HTTP_NOT_FOUND
, "Not found.\n");
870 static void help(void) {
871 printf("%s [OPTIONS...] ...\n\n"
872 "HTTP server for journal events.\n\n"
873 " -h --help Show this help\n"
874 " --version Show package version\n"
875 " --cert=CERT.PEM Server certificate in PEM format\n"
876 " --key=KEY.PEM Server key in PEM format\n"
877 " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
878 program_invocation_short_name
);
881 static int parse_argv(int argc
, char *argv
[]) {
891 static const struct option options
[] = {
892 { "help", no_argument
, NULL
, 'h' },
893 { "version", no_argument
, NULL
, ARG_VERSION
},
894 { "key", required_argument
, NULL
, ARG_KEY
},
895 { "cert", required_argument
, NULL
, ARG_CERT
},
896 { "trust", required_argument
, NULL
, ARG_TRUST
},
903 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
916 log_error("Key file specified twice");
919 r
= read_full_file(optarg
, &arg_key_pem
, NULL
);
921 return log_error_errno(r
, "Failed to read key file: %m");
927 log_error("Certificate file specified twice");
930 r
= read_full_file(optarg
, &arg_cert_pem
, NULL
);
932 return log_error_errno(r
, "Failed to read certificate file: %m");
933 assert(arg_cert_pem
);
939 log_error("CA certificate file specified twice");
942 r
= read_full_file(optarg
, &arg_trust_pem
, NULL
);
944 return log_error_errno(r
, "Failed to read CA certificate file: %m");
945 assert(arg_trust_pem
);
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 (!!arg_key_pem
!= !!arg_cert_pem
) {
964 log_error("Certificate and key files must be specified together");
968 if (arg_trust_pem
&& !arg_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
);
992 r
= setup_gnutls_logger(NULL
);
996 n
= sd_listen_fds(1);
998 log_error_errno(n
, "Failed to determine passed sockets: %m");
1001 log_error("Can't listen on more than one socket.");
1004 struct MHD_OptionItem opts
[] = {
1005 { MHD_OPTION_NOTIFY_COMPLETED
,
1006 (intptr_t) request_meta_free
, NULL
},
1007 { MHD_OPTION_EXTERNAL_LOGGER
,
1008 (intptr_t) microhttpd_logger
, 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
},
1013 { MHD_OPTION_END
, 0, NULL
}};
1016 /* We force MHD_USE_PIPE_FOR_SHUTDOWN here, in order
1017 * to make sure libmicrohttpd doesn't use shutdown()
1018 * on our listening socket, which would break socket
1019 * re-activation. See
1021 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1022 * https://github.com/systemd/systemd/pull/1286
1027 MHD_USE_DUAL_STACK
|
1028 MHD_USE_PIPE_FOR_SHUTDOWN
|
1030 MHD_USE_THREAD_PER_CONNECTION
;
1033 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1034 {MHD_OPTION_LISTEN_SOCKET
, SD_LISTEN_FDS_START
};
1036 assert(arg_cert_pem
);
1037 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1038 {MHD_OPTION_HTTPS_MEM_KEY
, 0, arg_key_pem
};
1039 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1040 {MHD_OPTION_HTTPS_MEM_CERT
, 0, arg_cert_pem
};
1041 flags
|= MHD_USE_SSL
;
1043 if (arg_trust_pem
) {
1044 assert(flags
& MHD_USE_SSL
);
1045 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1046 {MHD_OPTION_HTTPS_MEM_TRUST
, 0, arg_trust_pem
};
1049 d
= MHD_start_daemon(flags
, 19531,
1051 request_handler
, NULL
,
1052 MHD_OPTION_ARRAY
, opts
,
1057 log_error("Failed to start daemon!");