1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include <microhttpd.h>
13 #include "sd-daemon.h"
14 #include "sd-journal.h"
16 #include "alloc-util.h"
19 #include "errno-util.h"
22 #include "glob-util.h"
23 #include "hostname-util.h"
25 #include "logs-show.h"
26 #include "main-func.h"
27 #include "memory-util.h"
28 #include "microhttpd-util.h"
30 #include "parse-util.h"
31 #include "pretty-print.h"
33 #include "tmpfile-util.h"
35 #define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC)
37 static char *arg_key_pem
= NULL
;
38 static char *arg_cert_pem
= NULL
;
39 static char *arg_trust_pem
= NULL
;
40 static bool arg_merge
= false;
41 static int arg_journal_type
= 0;
42 static const char *arg_directory
= NULL
;
43 static char **arg_file
= NULL
;
45 STATIC_DESTRUCTOR_REGISTER(arg_key_pem
, erase_and_freep
);
46 STATIC_DESTRUCTOR_REGISTER(arg_cert_pem
, freep
);
47 STATIC_DESTRUCTOR_REGISTER(arg_trust_pem
, freep
);
49 typedef struct RequestMeta
{
62 int argument_parse_error
;
68 static const char* const mime_types
[_OUTPUT_MODE_MAX
] = {
69 [OUTPUT_SHORT
] = "text/plain",
70 [OUTPUT_JSON
] = "application/json",
71 [OUTPUT_JSON_SSE
] = "text/event-stream",
72 [OUTPUT_JSON_SEQ
] = "application/json-seq",
73 [OUTPUT_EXPORT
] = "application/vnd.fdo.journal",
76 static RequestMeta
*request_meta(void **connection_cls
) {
79 assert(connection_cls
);
81 return *connection_cls
;
83 m
= new0(RequestMeta
, 1);
91 static void request_meta_free(
93 struct MHD_Connection
*connection
,
94 void **connection_cls
,
95 enum MHD_RequestTerminationCode toe
) {
97 RequestMeta
*m
= *connection_cls
;
102 sd_journal_close(m
->journal
);
110 static int open_journal(RequestMeta
*m
) {
117 return sd_journal_open_directory(&m
->journal
, arg_directory
, arg_journal_type
);
119 return sd_journal_open_files(&m
->journal
, (const char**) arg_file
, 0);
121 return sd_journal_open(&m
->journal
, (arg_merge
? 0 : SD_JOURNAL_LOCAL_ONLY
) | arg_journal_type
);
124 static int request_meta_ensure_tmp(RequestMeta
*m
) {
130 _cleanup_close_
int fd
= -EBADF
;
132 fd
= open_tmpfile_unlinkable("/tmp", O_RDWR
|O_CLOEXEC
);
136 m
->tmp
= take_fdopen(&fd
, "w+");
144 static ssize_t
request_reader_entries(
150 RequestMeta
*m
= ASSERT_PTR(cls
);
151 dual_timestamp previous_ts
= DUAL_TIMESTAMP_NULL
;
152 sd_id128_t previous_boot_id
= SD_ID128_NULL
;
158 assert(pos
>= m
->delta
);
162 while (pos
>= m
->size
) {
165 /* End of this entry, so let's serialize the next
168 if (m
->n_entries_set
&&
170 return MHD_CONTENT_READER_END_OF_STREAM
;
173 r
= sd_journal_previous_skip(m
->journal
, (uint64_t) -m
->n_skip
+ 1);
174 else if (m
->n_skip
> 0)
175 r
= sd_journal_next_skip(m
->journal
, (uint64_t) m
->n_skip
+ 1);
177 r
= sd_journal_next(m
->journal
);
180 log_error_errno(r
, "Failed to advance journal pointer: %m");
181 return MHD_CONTENT_READER_END_WITH_ERROR
;
185 r
= sd_journal_wait(m
->journal
, (uint64_t) JOURNAL_WAIT_TIMEOUT
);
187 log_error_errno(r
, "Couldn't wait for journal event: %m");
188 return MHD_CONTENT_READER_END_WITH_ERROR
;
190 if (r
== SD_JOURNAL_NOP
)
196 return MHD_CONTENT_READER_END_OF_STREAM
;
202 r
= sd_journal_test_cursor(m
->journal
, m
->cursor
);
204 log_error_errno(r
, "Failed to test cursor: %m");
205 return MHD_CONTENT_READER_END_WITH_ERROR
;
209 return MHD_CONTENT_READER_END_OF_STREAM
;
215 if (m
->n_entries_set
)
220 r
= request_meta_ensure_tmp(m
);
222 log_error_errno(r
, "Failed to create temporary file: %m");
223 return MHD_CONTENT_READER_END_WITH_ERROR
;
226 r
= show_journal_entry(m
->tmp
, m
->journal
, m
->mode
, 0, OUTPUT_FULL_WIDTH
,
227 NULL
, NULL
, NULL
, &previous_ts
, &previous_boot_id
);
229 log_error_errno(r
, "Failed to serialize item: %m");
230 return MHD_CONTENT_READER_END_WITH_ERROR
;
234 if (sz
== (off_t
) -1) {
235 log_error_errno(errno
, "Failed to retrieve file position: %m");
236 return MHD_CONTENT_READER_END_WITH_ERROR
;
239 m
->size
= (uint64_t) sz
;
242 if (m
->tmp
== NULL
&& m
->follow
)
245 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
246 log_error_errno(errno
, "Failed to seek to position: %m");
247 return MHD_CONTENT_READER_END_WITH_ERROR
;
257 k
= fread(buf
, 1, n
, m
->tmp
);
259 log_error("Failed to read from file: %s", STRERROR_OR_EOF(errno
));
260 return MHD_CONTENT_READER_END_WITH_ERROR
;
266 static int request_parse_accept(
268 struct MHD_Connection
*connection
) {
275 header
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Accept");
279 if (streq(header
, mime_types
[OUTPUT_JSON
]))
280 m
->mode
= OUTPUT_JSON
;
281 else if (streq(header
, mime_types
[OUTPUT_JSON_SSE
]))
282 m
->mode
= OUTPUT_JSON_SSE
;
283 else if (streq(header
, mime_types
[OUTPUT_JSON_SEQ
]))
284 m
->mode
= OUTPUT_JSON_SEQ
;
285 else if (streq(header
, mime_types
[OUTPUT_EXPORT
]))
286 m
->mode
= OUTPUT_EXPORT
;
288 m
->mode
= OUTPUT_SHORT
;
293 static int request_parse_range(
295 struct MHD_Connection
*connection
) {
297 const char *range
, *colon
, *colon2
;
303 range
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Range");
307 if (!startswith(range
, "entries="))
311 range
+= strspn(range
, WHITESPACE
);
313 colon
= strchr(range
, ':');
315 m
->cursor
= strdup(range
);
319 colon2
= strchr(colon
+ 1, ':');
321 _cleanup_free_
char *t
= NULL
;
323 t
= strndup(colon
+ 1, colon2
- colon
- 1);
327 r
= safe_atoi64(t
, &m
->n_skip
);
332 p
= (colon2
?: colon
) + 1;
334 r
= safe_atou64(p
, &m
->n_entries
);
338 if (m
->n_entries
<= 0)
341 m
->n_entries_set
= true;
344 m
->cursor
= strndup(range
, colon
- range
);
350 m
->cursor
[strcspn(m
->cursor
, WHITESPACE
)] = 0;
351 if (isempty(m
->cursor
))
352 m
->cursor
= mfree(m
->cursor
);
357 static mhd_result
request_parse_arguments_iterator(
359 enum MHD_ValueKind kind
,
363 RequestMeta
*m
= ASSERT_PTR(cls
);
364 _cleanup_free_
char *p
= NULL
;
368 m
->argument_parse_error
= -EINVAL
;
372 if (streq(key
, "follow")) {
373 if (isempty(value
)) {
378 r
= parse_boolean(value
);
380 m
->argument_parse_error
= r
;
388 if (streq(key
, "discrete")) {
389 if (isempty(value
)) {
394 r
= parse_boolean(value
);
396 m
->argument_parse_error
= r
;
404 if (streq(key
, "boot")) {
408 r
= parse_boolean(value
);
410 m
->argument_parse_error
= r
;
416 char match
[9 + 32 + 1] = "_BOOT_ID=";
419 r
= sd_id128_get_boot(&bid
);
421 log_error_errno(r
, "Failed to get boot ID: %m");
425 sd_id128_to_string(bid
, match
+ 9);
426 r
= sd_journal_add_match(m
->journal
, match
, sizeof(match
)-1);
428 m
->argument_parse_error
= r
;
436 p
= strjoin(key
, "=", strempty(value
));
438 m
->argument_parse_error
= log_oom();
442 r
= sd_journal_add_match(m
->journal
, p
, 0);
444 m
->argument_parse_error
= r
;
451 static int request_parse_arguments(
453 struct MHD_Connection
*connection
) {
458 m
->argument_parse_error
= 0;
459 MHD_get_connection_values(connection
, MHD_GET_ARGUMENT_KIND
, request_parse_arguments_iterator
, m
);
461 return m
->argument_parse_error
;
464 static int request_handler_entries(
465 struct MHD_Connection
*connection
,
466 void *connection_cls
) {
468 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
469 RequestMeta
*m
= ASSERT_PTR(connection_cls
);
476 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
478 if (request_parse_accept(m
, connection
) < 0)
479 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.");
481 if (request_parse_range(m
, connection
) < 0)
482 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Range header.");
484 if (request_parse_arguments(m
, connection
) < 0)
485 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse URL arguments.");
489 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Discrete seeks require a cursor specification.");
492 m
->n_entries_set
= true;
496 r
= sd_journal_seek_cursor(m
->journal
, m
->cursor
);
497 else if (m
->n_skip
>= 0)
498 r
= sd_journal_seek_head(m
->journal
);
499 else if (m
->n_skip
< 0)
500 r
= sd_journal_seek_tail(m
->journal
);
502 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to seek in journal.");
504 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_entries
, m
, NULL
);
506 return respond_oom(connection
);
508 if (MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
]) == MHD_NO
)
509 return respond_oom(connection
);
511 return MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
514 static int output_field(FILE *f
, OutputMode m
, const char *d
, size_t l
) {
518 eq
= memchr(d
, '=', l
);
522 j
= l
- (eq
- d
+ 1);
524 if (m
== OUTPUT_JSON
) {
525 fprintf(f
, "{ \"%.*s\" : ", (int) (eq
- d
), d
);
526 json_escape(f
, eq
+1, j
, OUTPUT_FULL_WIDTH
);
529 fwrite(eq
+1, 1, j
, f
);
536 static ssize_t
request_reader_fields(
542 RequestMeta
*m
= ASSERT_PTR(cls
);
548 assert(pos
>= m
->delta
);
552 while (pos
>= m
->size
) {
557 /* End of this field, so let's serialize the next
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
;
570 r
= request_meta_ensure_tmp(m
);
572 log_error_errno(r
, "Failed to create temporary file: %m");
573 return MHD_CONTENT_READER_END_WITH_ERROR
;
576 r
= output_field(m
->tmp
, m
->mode
, d
, l
);
578 log_error_errno(r
, "Failed to serialize item: %m");
579 return MHD_CONTENT_READER_END_WITH_ERROR
;
583 if (sz
== (off_t
) -1) {
584 log_error_errno(errno
, "Failed to retrieve file position: %m");
585 return MHD_CONTENT_READER_END_WITH_ERROR
;
588 m
->size
= (uint64_t) sz
;
591 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
592 log_error_errno(errno
, "Failed to seek to position: %m");
593 return MHD_CONTENT_READER_END_WITH_ERROR
;
601 k
= fread(buf
, 1, n
, m
->tmp
);
603 log_error("Failed to read from file: %s", STRERROR_OR_EOF(errno
));
604 return MHD_CONTENT_READER_END_WITH_ERROR
;
610 static int request_handler_fields(
611 struct MHD_Connection
*connection
,
613 void *connection_cls
) {
615 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
616 RequestMeta
*m
= ASSERT_PTR(connection_cls
);
623 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
625 if (request_parse_accept(m
, connection
) < 0)
626 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.");
628 r
= sd_journal_query_unique(m
->journal
, field
);
630 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to query unique fields.");
632 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_fields
, m
, NULL
);
634 return respond_oom(connection
);
636 if (MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
== OUTPUT_JSON
? OUTPUT_JSON
: OUTPUT_SHORT
]) == MHD_NO
)
637 return respond_oom(connection
);
639 return MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
642 static int request_handler_redirect(
643 struct MHD_Connection
*connection
,
644 const char *target
) {
646 _cleanup_free_
char *page
= NULL
;
647 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
652 if (asprintf(&page
, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target
) < 0)
653 return respond_oom(connection
);
655 response
= MHD_create_response_from_buffer(strlen(page
), page
, MHD_RESPMEM_MUST_FREE
);
657 return respond_oom(connection
);
660 if (MHD_add_response_header(response
, "Content-Type", "text/html") == MHD_NO
||
661 MHD_add_response_header(response
, "Location", target
) == MHD_NO
)
662 return respond_oom(connection
);
664 return MHD_queue_response(connection
, MHD_HTTP_MOVED_PERMANENTLY
, response
);
667 static int request_handler_file(
668 struct MHD_Connection
*connection
,
670 const char *mime_type
) {
672 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
673 _cleanup_close_
int fd
= -EBADF
;
680 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
682 return mhd_respondf(connection
, errno
, MHD_HTTP_NOT_FOUND
, "Failed to open file %s: %m", path
);
684 if (fstat(fd
, &st
) < 0)
685 return mhd_respondf(connection
, errno
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to stat file: %m");
687 response
= MHD_create_response_from_fd_at_offset64(st
.st_size
, fd
, 0);
689 return respond_oom(connection
);
692 if (MHD_add_response_header(response
, "Content-Type", mime_type
) == MHD_NO
)
693 return respond_oom(connection
);
695 return MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
698 static int get_virtualization(char **v
) {
699 _cleanup_(sd_bus_unrefp
) sd_bus
*bus
= NULL
;
703 r
= sd_bus_default_system(&bus
);
707 r
= sd_bus_get_property_string(
709 "org.freedesktop.systemd1",
710 "/org/freedesktop/systemd1",
711 "org.freedesktop.systemd1.Manager",
728 static int request_handler_machine(
729 struct MHD_Connection
*connection
,
730 void *connection_cls
) {
732 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
733 RequestMeta
*m
= ASSERT_PTR(connection_cls
);
735 _cleanup_free_
char* hostname
= NULL
, *pretty_name
= NULL
, *os_name
= NULL
;
736 uint64_t cutoff_from
= 0, cutoff_to
= 0, usage
= 0;
738 _cleanup_free_
char *v
= NULL
, *json
= NULL
;
744 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
746 r
= sd_id128_get_machine(&mid
);
748 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine machine ID: %m");
750 r
= sd_id128_get_boot(&bid
);
752 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine boot ID: %m");
754 hostname
= gethostname_malloc();
756 return respond_oom(connection
);
758 r
= sd_journal_get_usage(m
->journal
, &usage
);
760 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %m");
762 r
= sd_journal_get_cutoff_realtime_usec(m
->journal
, &cutoff_from
, &cutoff_to
);
764 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %m");
766 (void) parse_os_release(
768 "PRETTY_NAME", &pretty_name
,
770 (void) get_virtualization(&v
);
773 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR
"\","
774 "\"boot_id\" : \"" SD_ID128_FORMAT_STR
"\","
775 "\"hostname\" : \"%s\","
776 "\"os_pretty_name\" : \"%s\","
777 "\"virtualization\" : \"%s\","
778 "\"usage\" : \"%"PRIu64
"\","
779 "\"cutoff_from_realtime\" : \"%"PRIu64
"\","
780 "\"cutoff_to_realtime\" : \"%"PRIu64
"\" }\n",
781 SD_ID128_FORMAT_VAL(mid
),
782 SD_ID128_FORMAT_VAL(bid
),
783 hostname_cleanup(hostname
),
784 os_release_pretty_name(pretty_name
, os_name
),
790 return respond_oom(connection
);
792 response
= MHD_create_response_from_buffer(strlen(json
), json
, MHD_RESPMEM_MUST_FREE
);
794 return respond_oom(connection
);
797 if (MHD_add_response_header(response
, "Content-Type", "application/json") == MHD_NO
)
798 return respond_oom(connection
);
800 return MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
803 static mhd_result
request_handler(
805 struct MHD_Connection
*connection
,
809 const char *upload_data
,
810 size_t *upload_data_size
,
811 void **connection_cls
) {
815 assert(connection_cls
);
819 if (!streq(method
, "GET"))
820 return mhd_respond(connection
, MHD_HTTP_NOT_ACCEPTABLE
, "Unsupported method.");
822 if (!*connection_cls
) {
823 if (!request_meta(connection_cls
))
824 return respond_oom(connection
);
829 r
= check_permissions(connection
, &code
, NULL
);
835 return request_handler_redirect(connection
, "/browse");
837 if (streq(url
, "/entries"))
838 return request_handler_entries(connection
, *connection_cls
);
840 if (startswith(url
, "/fields/"))
841 return request_handler_fields(connection
, url
+ 8, *connection_cls
);
843 if (streq(url
, "/browse"))
844 return request_handler_file(connection
, DOCUMENT_ROOT
"/browse.html", "text/html");
846 if (streq(url
, "/machine"))
847 return request_handler_machine(connection
, *connection_cls
);
849 return mhd_respond(connection
, MHD_HTTP_NOT_FOUND
, "Not found.");
852 static int help(void) {
853 _cleanup_free_
char *link
= NULL
;
856 r
= terminal_urlify_man("systemd-journal-gatewayd.service", "8", &link
);
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 Certificate authority certificate in PEM format\n"
867 " --system Serve system journal\n"
868 " --user Serve the user journal for the current user\n"
869 " -m --merge Serve all available journals\n"
870 " -D --directory=PATH Serve journal files in directory\n"
871 " --file=PATH Serve this journal file\n"
872 "\nSee the %s for details.\n",
873 program_invocation_short_name
,
879 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
},
899 { "user", no_argument
, NULL
, ARG_USER
},
900 { "system", no_argument
, NULL
, ARG_SYSTEM
},
901 { "merge", no_argument
, NULL
, 'm' },
902 { "directory", required_argument
, NULL
, 'D' },
903 { "file", required_argument
, NULL
, ARG_FILE
},
910 while ((c
= getopt_long(argc
, argv
, "hD:", options
, NULL
)) >= 0)
922 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
923 "Key file specified twice");
924 r
= read_full_file_full(
925 AT_FDCWD
, optarg
, UINT64_MAX
, SIZE_MAX
,
926 READ_FULL_FILE_SECURE
|READ_FULL_FILE_WARN_WORLD_READABLE
|READ_FULL_FILE_CONNECT_SOCKET
,
930 return log_error_errno(r
, "Failed to read key file: %m");
936 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
937 "Certificate file specified twice");
938 r
= read_full_file_full(
939 AT_FDCWD
, optarg
, UINT64_MAX
, SIZE_MAX
,
940 READ_FULL_FILE_CONNECT_SOCKET
,
942 &arg_cert_pem
, NULL
);
944 return log_error_errno(r
, "Failed to read certificate file: %m");
945 assert(arg_cert_pem
);
951 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
952 "CA certificate file specified twice");
953 r
= read_full_file_full(
954 AT_FDCWD
, optarg
, UINT64_MAX
, SIZE_MAX
,
955 READ_FULL_FILE_CONNECT_SOCKET
,
957 &arg_trust_pem
, NULL
);
959 return log_error_errno(r
, "Failed to read CA certificate file: %m");
960 assert(arg_trust_pem
);
963 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
964 "Option --trust= is not available.");
968 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
972 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
980 arg_directory
= optarg
;
984 r
= glob_extend(&arg_file
, optarg
, GLOB_NOCHECK
);
986 return log_error_errno(r
, "Failed to add paths: %m");
993 assert_not_reached();
997 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
998 "This program does not take arguments.");
1000 if (!!arg_key_pem
!= !!arg_cert_pem
)
1001 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1002 "Certificate and key files must be specified together");
1004 if (arg_trust_pem
&& !arg_key_pem
)
1005 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1006 "CA certificate can only be used with certificate file");
1011 static int run(int argc
, char *argv
[]) {
1012 _cleanup_(MHD_stop_daemonp
) struct MHD_Daemon
*d
= NULL
;
1013 struct MHD_OptionItem opts
[] = {
1014 { MHD_OPTION_NOTIFY_COMPLETED
,
1015 (intptr_t) request_meta_free
, NULL
},
1016 { MHD_OPTION_EXTERNAL_LOGGER
,
1017 (intptr_t) microhttpd_logger
, NULL
},
1018 { MHD_OPTION_END
, 0, NULL
},
1019 { MHD_OPTION_END
, 0, NULL
},
1020 { MHD_OPTION_END
, 0, NULL
},
1021 { MHD_OPTION_END
, 0, NULL
},
1022 { MHD_OPTION_END
, 0, NULL
},
1026 /* We force MHD_USE_ITC here, in order to make sure
1027 * libmicrohttpd doesn't use shutdown() on our listening
1028 * socket, which would break socket re-activation. See
1030 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1031 * https://github.com/systemd/systemd/pull/1286
1036 MHD_USE_DUAL_STACK
|
1038 MHD_USE_POLL_INTERNAL_THREAD
|
1039 MHD_USE_THREAD_PER_CONNECTION
;
1044 r
= parse_argv(argc
, argv
);
1050 r
= setup_gnutls_logger(NULL
);
1054 n
= sd_listen_fds(1);
1056 return log_error_errno(n
, "Failed to determine passed sockets: %m");
1058 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Can't listen on more than one socket.");
1061 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1062 { MHD_OPTION_LISTEN_SOCKET
, SD_LISTEN_FDS_START
};
1065 assert(arg_cert_pem
);
1066 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1067 { MHD_OPTION_HTTPS_MEM_KEY
, 0, arg_key_pem
};
1068 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1069 { MHD_OPTION_HTTPS_MEM_CERT
, 0, arg_cert_pem
};
1070 flags
|= MHD_USE_TLS
;
1073 if (arg_trust_pem
) {
1074 assert(flags
& MHD_USE_TLS
);
1075 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1076 { MHD_OPTION_HTTPS_MEM_TRUST
, 0, arg_trust_pem
};
1079 d
= MHD_start_daemon(flags
, 19531,
1081 request_handler
, NULL
,
1082 MHD_OPTION_ARRAY
, opts
,
1085 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Failed to start daemon!");
1092 DEFINE_MAIN_FUNCTION(run
);