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"
45 #include "hostname-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
);
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
, "w+");
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
))
341 m
->cursor
= mfree(m
->cursor
);
346 static int request_parse_arguments_iterator(
348 enum MHD_ValueKind kind
,
352 RequestMeta
*m
= cls
;
353 _cleanup_free_
char *p
= NULL
;
359 m
->argument_parse_error
= -EINVAL
;
363 if (streq(key
, "follow")) {
364 if (isempty(value
)) {
369 r
= parse_boolean(value
);
371 m
->argument_parse_error
= r
;
379 if (streq(key
, "discrete")) {
380 if (isempty(value
)) {
385 r
= parse_boolean(value
);
387 m
->argument_parse_error
= r
;
395 if (streq(key
, "boot")) {
399 r
= parse_boolean(value
);
401 m
->argument_parse_error
= r
;
407 char match
[9 + 32 + 1] = "_BOOT_ID=";
410 r
= sd_id128_get_boot(&bid
);
412 log_error_errno(r
, "Failed to get boot ID: %m");
416 sd_id128_to_string(bid
, match
+ 9);
417 r
= sd_journal_add_match(m
->journal
, match
, sizeof(match
)-1);
419 m
->argument_parse_error
= r
;
427 p
= strjoin(key
, "=", strempty(value
), NULL
);
429 m
->argument_parse_error
= log_oom();
433 r
= sd_journal_add_match(m
->journal
, p
, 0);
435 m
->argument_parse_error
= r
;
442 static int request_parse_arguments(
444 struct MHD_Connection
*connection
) {
449 m
->argument_parse_error
= 0;
450 MHD_get_connection_values(connection
, MHD_GET_ARGUMENT_KIND
, request_parse_arguments_iterator
, m
);
452 return m
->argument_parse_error
;
455 static int request_handler_entries(
456 struct MHD_Connection
*connection
,
457 void *connection_cls
) {
459 struct MHD_Response
*response
;
460 RequestMeta
*m
= connection_cls
;
468 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
470 if (request_parse_accept(m
, connection
) < 0)
471 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.\n");
473 if (request_parse_range(m
, connection
) < 0)
474 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Range header.\n");
476 if (request_parse_arguments(m
, connection
) < 0)
477 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse URL arguments.\n");
481 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Discrete seeks require a cursor specification.\n");
484 m
->n_entries_set
= true;
488 r
= sd_journal_seek_cursor(m
->journal
, m
->cursor
);
489 else if (m
->n_skip
>= 0)
490 r
= sd_journal_seek_head(m
->journal
);
491 else if (m
->n_skip
< 0)
492 r
= sd_journal_seek_tail(m
->journal
);
494 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to seek in journal.\n");
496 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_entries
, m
, NULL
);
498 return respond_oom(connection
);
500 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
]);
502 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
503 MHD_destroy_response(response
);
508 static int output_field(FILE *f
, OutputMode m
, const char *d
, size_t l
) {
512 eq
= memchr(d
, '=', l
);
516 j
= l
- (eq
- d
+ 1);
518 if (m
== OUTPUT_JSON
) {
519 fprintf(f
, "{ \"%.*s\" : ", (int) (eq
- d
), d
);
520 json_escape(f
, eq
+1, j
, OUTPUT_FULL_WIDTH
);
523 fwrite(eq
+1, 1, j
, f
);
530 static ssize_t
request_reader_fields(
536 RequestMeta
*m
= cls
;
543 assert(pos
>= m
->delta
);
547 while (pos
>= m
->size
) {
552 /* End of this field, so let's serialize the next
555 if (m
->n_fields_set
&&
557 return MHD_CONTENT_READER_END_OF_STREAM
;
559 r
= sd_journal_enumerate_unique(m
->journal
, &d
, &l
);
561 log_error_errno(r
, "Failed to advance field index: %m");
562 return MHD_CONTENT_READER_END_WITH_ERROR
;
564 return MHD_CONTENT_READER_END_OF_STREAM
;
572 r
= request_meta_ensure_tmp(m
);
574 log_error_errno(r
, "Failed to create temporary file: %m");
575 return MHD_CONTENT_READER_END_WITH_ERROR
;
578 r
= output_field(m
->tmp
, m
->mode
, d
, l
);
580 log_error_errno(r
, "Failed to serialize item: %m");
581 return MHD_CONTENT_READER_END_WITH_ERROR
;
585 if (sz
== (off_t
) -1) {
586 log_error_errno(errno
, "Failed to retrieve file position: %m");
587 return MHD_CONTENT_READER_END_WITH_ERROR
;
590 m
->size
= (uint64_t) sz
;
593 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
594 log_error_errno(errno
, "Failed to seek to position: %m");
595 return MHD_CONTENT_READER_END_WITH_ERROR
;
603 k
= fread(buf
, 1, n
, m
->tmp
);
605 log_error("Failed to read from file: %s", errno
? strerror(errno
) : "Premature EOF");
606 return MHD_CONTENT_READER_END_WITH_ERROR
;
612 static int request_handler_fields(
613 struct MHD_Connection
*connection
,
615 void *connection_cls
) {
617 struct MHD_Response
*response
;
618 RequestMeta
*m
= connection_cls
;
626 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
628 if (request_parse_accept(m
, connection
) < 0)
629 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.\n");
631 r
= sd_journal_query_unique(m
->journal
, field
);
633 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to query unique fields.\n");
635 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_fields
, m
, NULL
);
637 return respond_oom(connection
);
639 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
== OUTPUT_JSON
? OUTPUT_JSON
: OUTPUT_SHORT
]);
641 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
642 MHD_destroy_response(response
);
647 static int request_handler_redirect(
648 struct MHD_Connection
*connection
,
649 const char *target
) {
652 struct MHD_Response
*response
;
658 if (asprintf(&page
, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target
) < 0)
659 return respond_oom(connection
);
661 response
= MHD_create_response_from_buffer(strlen(page
), page
, MHD_RESPMEM_MUST_FREE
);
664 return respond_oom(connection
);
667 MHD_add_response_header(response
, "Content-Type", "text/html");
668 MHD_add_response_header(response
, "Location", target
);
670 ret
= MHD_queue_response(connection
, MHD_HTTP_MOVED_PERMANENTLY
, response
);
671 MHD_destroy_response(response
);
676 static int request_handler_file(
677 struct MHD_Connection
*connection
,
679 const char *mime_type
) {
681 struct MHD_Response
*response
;
683 _cleanup_close_
int fd
= -1;
690 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
692 return mhd_respondf(connection
, MHD_HTTP_NOT_FOUND
, "Failed to open file %s: %m\n", path
);
694 if (fstat(fd
, &st
) < 0)
695 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to stat file: %m\n");
697 response
= MHD_create_response_from_fd_at_offset(st
.st_size
, fd
, 0);
699 return respond_oom(connection
);
703 MHD_add_response_header(response
, "Content-Type", mime_type
);
705 ret
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
706 MHD_destroy_response(response
);
711 static int get_virtualization(char **v
) {
712 _cleanup_bus_unref_ sd_bus
*bus
= NULL
;
716 r
= sd_bus_default_system(&bus
);
720 r
= sd_bus_get_property_string(
722 "org.freedesktop.systemd1",
723 "/org/freedesktop/systemd1",
724 "org.freedesktop.systemd1.Manager",
741 static int request_handler_machine(
742 struct MHD_Connection
*connection
,
743 void *connection_cls
) {
745 struct MHD_Response
*response
;
746 RequestMeta
*m
= connection_cls
;
748 _cleanup_free_
char* hostname
= NULL
, *os_name
= NULL
;
749 uint64_t cutoff_from
= 0, cutoff_to
= 0, usage
= 0;
752 _cleanup_free_
char *v
= NULL
;
759 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
761 r
= sd_id128_get_machine(&mid
);
763 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine machine ID: %s\n", strerror(-r
));
765 r
= sd_id128_get_boot(&bid
);
767 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine boot ID: %s\n", strerror(-r
));
769 hostname
= gethostname_malloc();
771 return respond_oom(connection
);
773 r
= sd_journal_get_usage(m
->journal
, &usage
);
775 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %s\n", strerror(-r
));
777 r
= sd_journal_get_cutoff_realtime_usec(m
->journal
, &cutoff_from
, &cutoff_to
);
779 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %s\n", strerror(-r
));
781 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &os_name
, NULL
) == -ENOENT
)
782 (void) parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &os_name
, NULL
);
784 get_virtualization(&v
);
787 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR
"\","
788 "\"boot_id\" : \"" SD_ID128_FORMAT_STR
"\","
789 "\"hostname\" : \"%s\","
790 "\"os_pretty_name\" : \"%s\","
791 "\"virtualization\" : \"%s\","
792 "\"usage\" : \"%"PRIu64
"\","
793 "\"cutoff_from_realtime\" : \"%"PRIu64
"\","
794 "\"cutoff_to_realtime\" : \"%"PRIu64
"\" }\n",
795 SD_ID128_FORMAT_VAL(mid
),
796 SD_ID128_FORMAT_VAL(bid
),
797 hostname_cleanup(hostname
),
798 os_name
? os_name
: "Linux",
805 return respond_oom(connection
);
807 response
= MHD_create_response_from_buffer(strlen(json
), json
, MHD_RESPMEM_MUST_FREE
);
810 return respond_oom(connection
);
813 MHD_add_response_header(response
, "Content-Type", "application/json");
814 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
815 MHD_destroy_response(response
);
820 static int request_handler(
822 struct MHD_Connection
*connection
,
826 const char *upload_data
,
827 size_t *upload_data_size
,
828 void **connection_cls
) {
832 assert(connection_cls
);
836 if (!streq(method
, "GET"))
837 return mhd_respond(connection
, MHD_HTTP_METHOD_NOT_ACCEPTABLE
,
838 "Unsupported method.\n");
841 if (!*connection_cls
) {
842 if (!request_meta(connection_cls
))
843 return respond_oom(connection
);
848 r
= check_permissions(connection
, &code
, NULL
);
854 return request_handler_redirect(connection
, "/browse");
856 if (streq(url
, "/entries"))
857 return request_handler_entries(connection
, *connection_cls
);
859 if (startswith(url
, "/fields/"))
860 return request_handler_fields(connection
, url
+ 8, *connection_cls
);
862 if (streq(url
, "/browse"))
863 return request_handler_file(connection
, DOCUMENT_ROOT
"/browse.html", "text/html");
865 if (streq(url
, "/machine"))
866 return request_handler_machine(connection
, *connection_cls
);
868 return mhd_respond(connection
, MHD_HTTP_NOT_FOUND
, "Not found.\n");
871 static void help(void) {
872 printf("%s [OPTIONS...] ...\n\n"
873 "HTTP server for journal events.\n\n"
874 " -h --help Show this help\n"
875 " --version Show package version\n"
876 " --cert=CERT.PEM Server certificate in PEM format\n"
877 " --key=KEY.PEM Server key in PEM format\n"
878 " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
879 program_invocation_short_name
);
882 static int parse_argv(int argc
, char *argv
[]) {
892 static const struct option options
[] = {
893 { "help", no_argument
, NULL
, 'h' },
894 { "version", no_argument
, NULL
, ARG_VERSION
},
895 { "key", required_argument
, NULL
, ARG_KEY
},
896 { "cert", required_argument
, NULL
, ARG_CERT
},
897 { "trust", required_argument
, NULL
, ARG_TRUST
},
904 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
913 puts(PACKAGE_STRING
);
914 puts(SYSTEMD_FEATURES
);
919 log_error("Key file specified twice");
922 r
= read_full_file(optarg
, &arg_key_pem
, NULL
);
924 return log_error_errno(r
, "Failed to read key file: %m");
930 log_error("Certificate file specified twice");
933 r
= read_full_file(optarg
, &arg_cert_pem
, NULL
);
935 return log_error_errno(r
, "Failed to read certificate file: %m");
936 assert(arg_cert_pem
);
942 log_error("CA certificate file specified twice");
945 r
= read_full_file(optarg
, &arg_trust_pem
, NULL
);
947 return log_error_errno(r
, "Failed to read CA certificate file: %m");
948 assert(arg_trust_pem
);
951 log_error("Option --trust is not available.");
958 assert_not_reached("Unhandled option");
962 log_error("This program does not take arguments.");
966 if (!!arg_key_pem
!= !!arg_cert_pem
) {
967 log_error("Certificate and key files must be specified together");
971 if (arg_trust_pem
&& !arg_key_pem
) {
972 log_error("CA certificate can only be used with certificate file");
979 int main(int argc
, char *argv
[]) {
980 struct MHD_Daemon
*d
= NULL
;
983 log_set_target(LOG_TARGET_AUTO
);
984 log_parse_environment();
987 r
= parse_argv(argc
, argv
);
995 r
= setup_gnutls_logger(NULL
);
999 n
= sd_listen_fds(1);
1001 log_error_errno(n
, "Failed to determine passed sockets: %m");
1004 log_error("Can't listen on more than one socket.");
1007 struct MHD_OptionItem opts
[] = {
1008 { MHD_OPTION_NOTIFY_COMPLETED
,
1009 (intptr_t) request_meta_free
, NULL
},
1010 { MHD_OPTION_EXTERNAL_LOGGER
,
1011 (intptr_t) microhttpd_logger
, NULL
},
1012 { MHD_OPTION_END
, 0, NULL
},
1013 { MHD_OPTION_END
, 0, NULL
},
1014 { MHD_OPTION_END
, 0, NULL
},
1015 { MHD_OPTION_END
, 0, NULL
},
1016 { MHD_OPTION_END
, 0, NULL
}};
1018 int flags
= MHD_USE_THREAD_PER_CONNECTION
|MHD_USE_POLL
|MHD_USE_DEBUG
;
1021 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1022 {MHD_OPTION_LISTEN_SOCKET
, SD_LISTEN_FDS_START
};
1024 assert(arg_cert_pem
);
1025 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1026 {MHD_OPTION_HTTPS_MEM_KEY
, 0, arg_key_pem
};
1027 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1028 {MHD_OPTION_HTTPS_MEM_CERT
, 0, arg_cert_pem
};
1029 flags
|= MHD_USE_SSL
;
1031 if (arg_trust_pem
) {
1032 assert(flags
& MHD_USE_SSL
);
1033 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1034 {MHD_OPTION_HTTPS_MEM_TRUST
, 0, arg_trust_pem
};
1037 d
= MHD_start_daemon(flags
, 19531,
1039 request_handler
, NULL
,
1040 MHD_OPTION_ARRAY
, opts
,
1045 log_error("Failed to start daemon!");