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
);
347 static int request_parse_arguments_iterator(
349 enum MHD_ValueKind kind
,
353 RequestMeta
*m
= cls
;
354 _cleanup_free_
char *p
= NULL
;
360 m
->argument_parse_error
= -EINVAL
;
364 if (streq(key
, "follow")) {
365 if (isempty(value
)) {
370 r
= parse_boolean(value
);
372 m
->argument_parse_error
= r
;
380 if (streq(key
, "discrete")) {
381 if (isempty(value
)) {
386 r
= parse_boolean(value
);
388 m
->argument_parse_error
= r
;
396 if (streq(key
, "boot")) {
400 r
= parse_boolean(value
);
402 m
->argument_parse_error
= r
;
408 char match
[9 + 32 + 1] = "_BOOT_ID=";
411 r
= sd_id128_get_boot(&bid
);
413 log_error_errno(r
, "Failed to get boot ID: %m");
417 sd_id128_to_string(bid
, match
+ 9);
418 r
= sd_journal_add_match(m
->journal
, match
, sizeof(match
)-1);
420 m
->argument_parse_error
= r
;
428 p
= strjoin(key
, "=", strempty(value
), NULL
);
430 m
->argument_parse_error
= log_oom();
434 r
= sd_journal_add_match(m
->journal
, p
, 0);
436 m
->argument_parse_error
= r
;
443 static int request_parse_arguments(
445 struct MHD_Connection
*connection
) {
450 m
->argument_parse_error
= 0;
451 MHD_get_connection_values(connection
, MHD_GET_ARGUMENT_KIND
, request_parse_arguments_iterator
, m
);
453 return m
->argument_parse_error
;
456 static int request_handler_entries(
457 struct MHD_Connection
*connection
,
458 void *connection_cls
) {
460 struct MHD_Response
*response
;
461 RequestMeta
*m
= connection_cls
;
469 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
471 if (request_parse_accept(m
, connection
) < 0)
472 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.\n");
474 if (request_parse_range(m
, connection
) < 0)
475 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Range header.\n");
477 if (request_parse_arguments(m
, connection
) < 0)
478 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse URL arguments.\n");
482 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Discrete seeks require a cursor specification.\n");
485 m
->n_entries_set
= true;
489 r
= sd_journal_seek_cursor(m
->journal
, m
->cursor
);
490 else if (m
->n_skip
>= 0)
491 r
= sd_journal_seek_head(m
->journal
);
492 else if (m
->n_skip
< 0)
493 r
= sd_journal_seek_tail(m
->journal
);
495 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to seek in journal.\n");
497 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_entries
, m
, NULL
);
499 return respond_oom(connection
);
501 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
]);
503 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
504 MHD_destroy_response(response
);
509 static int output_field(FILE *f
, OutputMode m
, const char *d
, size_t l
) {
513 eq
= memchr(d
, '=', l
);
517 j
= l
- (eq
- d
+ 1);
519 if (m
== OUTPUT_JSON
) {
520 fprintf(f
, "{ \"%.*s\" : ", (int) (eq
- d
), d
);
521 json_escape(f
, eq
+1, j
, OUTPUT_FULL_WIDTH
);
524 fwrite(eq
+1, 1, j
, f
);
531 static ssize_t
request_reader_fields(
537 RequestMeta
*m
= cls
;
544 assert(pos
>= m
->delta
);
548 while (pos
>= m
->size
) {
553 /* End of this field, so let's serialize the next
556 if (m
->n_fields_set
&&
558 return MHD_CONTENT_READER_END_OF_STREAM
;
560 r
= sd_journal_enumerate_unique(m
->journal
, &d
, &l
);
562 log_error_errno(r
, "Failed to advance field index: %m");
563 return MHD_CONTENT_READER_END_WITH_ERROR
;
565 return MHD_CONTENT_READER_END_OF_STREAM
;
573 r
= request_meta_ensure_tmp(m
);
575 log_error_errno(r
, "Failed to create temporary file: %m");
576 return MHD_CONTENT_READER_END_WITH_ERROR
;
579 r
= output_field(m
->tmp
, m
->mode
, d
, l
);
581 log_error_errno(r
, "Failed to serialize item: %m");
582 return MHD_CONTENT_READER_END_WITH_ERROR
;
586 if (sz
== (off_t
) -1) {
587 log_error_errno(errno
, "Failed to retrieve file position: %m");
588 return MHD_CONTENT_READER_END_WITH_ERROR
;
591 m
->size
= (uint64_t) sz
;
594 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
595 log_error_errno(errno
, "Failed to seek to position: %m");
596 return MHD_CONTENT_READER_END_WITH_ERROR
;
604 k
= fread(buf
, 1, n
, m
->tmp
);
606 log_error("Failed to read from file: %s", errno
? strerror(errno
) : "Premature EOF");
607 return MHD_CONTENT_READER_END_WITH_ERROR
;
613 static int request_handler_fields(
614 struct MHD_Connection
*connection
,
616 void *connection_cls
) {
618 struct MHD_Response
*response
;
619 RequestMeta
*m
= connection_cls
;
627 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
629 if (request_parse_accept(m
, connection
) < 0)
630 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.\n");
632 r
= sd_journal_query_unique(m
->journal
, field
);
634 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to query unique fields.\n");
636 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_fields
, m
, NULL
);
638 return respond_oom(connection
);
640 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
== OUTPUT_JSON
? OUTPUT_JSON
: OUTPUT_SHORT
]);
642 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
643 MHD_destroy_response(response
);
648 static int request_handler_redirect(
649 struct MHD_Connection
*connection
,
650 const char *target
) {
653 struct MHD_Response
*response
;
659 if (asprintf(&page
, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target
) < 0)
660 return respond_oom(connection
);
662 response
= MHD_create_response_from_buffer(strlen(page
), page
, MHD_RESPMEM_MUST_FREE
);
665 return respond_oom(connection
);
668 MHD_add_response_header(response
, "Content-Type", "text/html");
669 MHD_add_response_header(response
, "Location", target
);
671 ret
= MHD_queue_response(connection
, MHD_HTTP_MOVED_PERMANENTLY
, response
);
672 MHD_destroy_response(response
);
677 static int request_handler_file(
678 struct MHD_Connection
*connection
,
680 const char *mime_type
) {
682 struct MHD_Response
*response
;
684 _cleanup_close_
int fd
= -1;
691 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
693 return mhd_respondf(connection
, MHD_HTTP_NOT_FOUND
, "Failed to open file %s: %m\n", path
);
695 if (fstat(fd
, &st
) < 0)
696 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to stat file: %m\n");
698 response
= MHD_create_response_from_fd_at_offset(st
.st_size
, fd
, 0);
700 return respond_oom(connection
);
704 MHD_add_response_header(response
, "Content-Type", mime_type
);
706 ret
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
707 MHD_destroy_response(response
);
712 static int get_virtualization(char **v
) {
713 _cleanup_bus_unref_ sd_bus
*bus
= NULL
;
717 r
= sd_bus_default_system(&bus
);
721 r
= sd_bus_get_property_string(
723 "org.freedesktop.systemd1",
724 "/org/freedesktop/systemd1",
725 "org.freedesktop.systemd1.Manager",
742 static int request_handler_machine(
743 struct MHD_Connection
*connection
,
744 void *connection_cls
) {
746 struct MHD_Response
*response
;
747 RequestMeta
*m
= connection_cls
;
749 _cleanup_free_
char* hostname
= NULL
, *os_name
= NULL
;
750 uint64_t cutoff_from
= 0, cutoff_to
= 0, usage
= 0;
753 _cleanup_free_
char *v
= NULL
;
760 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
762 r
= sd_id128_get_machine(&mid
);
764 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine machine ID: %s\n", strerror(-r
));
766 r
= sd_id128_get_boot(&bid
);
768 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine boot ID: %s\n", strerror(-r
));
770 hostname
= gethostname_malloc();
772 return respond_oom(connection
);
774 r
= sd_journal_get_usage(m
->journal
, &usage
);
776 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %s\n", strerror(-r
));
778 r
= sd_journal_get_cutoff_realtime_usec(m
->journal
, &cutoff_from
, &cutoff_to
);
780 return mhd_respondf(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %s\n", strerror(-r
));
782 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &os_name
, NULL
) == -ENOENT
)
783 (void) parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &os_name
, NULL
);
785 get_virtualization(&v
);
788 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR
"\","
789 "\"boot_id\" : \"" SD_ID128_FORMAT_STR
"\","
790 "\"hostname\" : \"%s\","
791 "\"os_pretty_name\" : \"%s\","
792 "\"virtualization\" : \"%s\","
793 "\"usage\" : \"%"PRIu64
"\","
794 "\"cutoff_from_realtime\" : \"%"PRIu64
"\","
795 "\"cutoff_to_realtime\" : \"%"PRIu64
"\" }\n",
796 SD_ID128_FORMAT_VAL(mid
),
797 SD_ID128_FORMAT_VAL(bid
),
798 hostname_cleanup(hostname
),
799 os_name
? os_name
: "Linux",
806 return respond_oom(connection
);
808 response
= MHD_create_response_from_buffer(strlen(json
), json
, MHD_RESPMEM_MUST_FREE
);
811 return respond_oom(connection
);
814 MHD_add_response_header(response
, "Content-Type", "application/json");
815 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
816 MHD_destroy_response(response
);
821 static int request_handler(
823 struct MHD_Connection
*connection
,
827 const char *upload_data
,
828 size_t *upload_data_size
,
829 void **connection_cls
) {
833 assert(connection_cls
);
837 if (!streq(method
, "GET"))
838 return mhd_respond(connection
, MHD_HTTP_METHOD_NOT_ACCEPTABLE
,
839 "Unsupported method.\n");
842 if (!*connection_cls
) {
843 if (!request_meta(connection_cls
))
844 return respond_oom(connection
);
849 r
= check_permissions(connection
, &code
, NULL
);
855 return request_handler_redirect(connection
, "/browse");
857 if (streq(url
, "/entries"))
858 return request_handler_entries(connection
, *connection_cls
);
860 if (startswith(url
, "/fields/"))
861 return request_handler_fields(connection
, url
+ 8, *connection_cls
);
863 if (streq(url
, "/browse"))
864 return request_handler_file(connection
, DOCUMENT_ROOT
"/browse.html", "text/html");
866 if (streq(url
, "/machine"))
867 return request_handler_machine(connection
, *connection_cls
);
869 return mhd_respond(connection
, MHD_HTTP_NOT_FOUND
, "Not found.\n");
872 static void help(void) {
873 printf("%s [OPTIONS...] ...\n\n"
874 "HTTP server for journal events.\n\n"
875 " -h --help Show this help\n"
876 " --version Show package version\n"
877 " --cert=CERT.PEM Server certificate in PEM format\n"
878 " --key=KEY.PEM Server key in PEM format\n"
879 " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
880 program_invocation_short_name
);
883 static int parse_argv(int argc
, char *argv
[]) {
893 static const struct option options
[] = {
894 { "help", no_argument
, NULL
, 'h' },
895 { "version", no_argument
, NULL
, ARG_VERSION
},
896 { "key", required_argument
, NULL
, ARG_KEY
},
897 { "cert", required_argument
, NULL
, ARG_CERT
},
898 { "trust", required_argument
, NULL
, ARG_TRUST
},
905 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
914 puts(PACKAGE_STRING
);
915 puts(SYSTEMD_FEATURES
);
920 log_error("Key file specified twice");
923 r
= read_full_file(optarg
, &arg_key_pem
, NULL
);
925 return log_error_errno(r
, "Failed to read key file: %m");
931 log_error("Certificate file specified twice");
934 r
= read_full_file(optarg
, &arg_cert_pem
, NULL
);
936 return log_error_errno(r
, "Failed to read certificate file: %m");
937 assert(arg_cert_pem
);
943 log_error("CA certificate file specified twice");
946 r
= read_full_file(optarg
, &arg_trust_pem
, NULL
);
948 return log_error_errno(r
, "Failed to read CA certificate file: %m");
949 assert(arg_trust_pem
);
952 log_error("Option --trust is not available.");
959 assert_not_reached("Unhandled option");
963 log_error("This program does not take arguments.");
967 if (!!arg_key_pem
!= !!arg_cert_pem
) {
968 log_error("Certificate and key files must be specified together");
972 if (arg_trust_pem
&& !arg_key_pem
) {
973 log_error("CA certificate can only be used with certificate file");
980 int main(int argc
, char *argv
[]) {
981 struct MHD_Daemon
*d
= NULL
;
984 log_set_target(LOG_TARGET_AUTO
);
985 log_parse_environment();
988 r
= parse_argv(argc
, argv
);
996 r
= setup_gnutls_logger(NULL
);
1000 n
= sd_listen_fds(1);
1002 log_error_errno(n
, "Failed to determine passed sockets: %m");
1005 log_error("Can't listen on more than one socket.");
1008 struct MHD_OptionItem opts
[] = {
1009 { MHD_OPTION_NOTIFY_COMPLETED
,
1010 (intptr_t) request_meta_free
, NULL
},
1011 { MHD_OPTION_EXTERNAL_LOGGER
,
1012 (intptr_t) microhttpd_logger
, 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
},
1017 { MHD_OPTION_END
, 0, NULL
}};
1019 int flags
= MHD_USE_THREAD_PER_CONNECTION
|MHD_USE_POLL
|MHD_USE_DEBUG
;
1022 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1023 {MHD_OPTION_LISTEN_SOCKET
, SD_LISTEN_FDS_START
};
1025 assert(arg_cert_pem
);
1026 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1027 {MHD_OPTION_HTTPS_MEM_KEY
, 0, arg_key_pem
};
1028 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1029 {MHD_OPTION_HTTPS_MEM_CERT
, 0, arg_cert_pem
};
1030 flags
|= MHD_USE_SSL
;
1032 if (arg_trust_pem
) {
1033 assert(flags
& MHD_USE_SSL
);
1034 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1035 {MHD_OPTION_HTTPS_MEM_TRUST
, 0, arg_trust_pem
};
1038 d
= MHD_start_daemon(flags
, 19531,
1040 request_handler
, NULL
,
1041 MHD_OPTION_ARRAY
, opts
,
1046 log_error("Failed to start daemon!");