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"
18 #include "bus-locator.h"
20 #include "errno-util.h"
23 #include "glob-util.h"
24 #include "hostname-util.h"
26 #include "logs-show.h"
27 #include "main-func.h"
28 #include "memory-util.h"
29 #include "microhttpd-util.h"
31 #include "parse-util.h"
32 #include "pretty-print.h"
34 #include "tmpfile-util.h"
36 #define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC)
38 static char *arg_key_pem
= NULL
;
39 static char *arg_cert_pem
= NULL
;
40 static char *arg_trust_pem
= NULL
;
41 static bool arg_merge
= false;
42 static int arg_journal_type
= 0;
43 static const char *arg_directory
= NULL
;
44 static char **arg_file
= NULL
;
46 STATIC_DESTRUCTOR_REGISTER(arg_key_pem
, erase_and_freep
);
47 STATIC_DESTRUCTOR_REGISTER(arg_cert_pem
, freep
);
48 STATIC_DESTRUCTOR_REGISTER(arg_trust_pem
, freep
);
50 typedef struct RequestMeta
{
63 int argument_parse_error
;
69 static const char* const mime_types
[_OUTPUT_MODE_MAX
] = {
70 [OUTPUT_SHORT
] = "text/plain",
71 [OUTPUT_JSON
] = "application/json",
72 [OUTPUT_JSON_SSE
] = "text/event-stream",
73 [OUTPUT_JSON_SEQ
] = "application/json-seq",
74 [OUTPUT_EXPORT
] = "application/vnd.fdo.journal",
77 static RequestMeta
*request_meta(void **connection_cls
) {
80 assert(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
;
103 sd_journal_close(m
->journal
);
111 static int open_journal(RequestMeta
*m
) {
118 return sd_journal_open_directory(&m
->journal
, arg_directory
, arg_journal_type
);
120 return sd_journal_open_files(&m
->journal
, (const char**) arg_file
, 0);
122 return sd_journal_open(&m
->journal
, (arg_merge
? 0 : SD_JOURNAL_LOCAL_ONLY
) | arg_journal_type
);
125 static int request_meta_ensure_tmp(RequestMeta
*m
) {
131 _cleanup_close_
int fd
= -EBADF
;
133 fd
= open_tmpfile_unlinkable("/tmp", O_RDWR
|O_CLOEXEC
);
137 m
->tmp
= take_fdopen(&fd
, "w+");
145 static ssize_t
request_reader_entries(
151 RequestMeta
*m
= ASSERT_PTR(cls
);
152 dual_timestamp previous_ts
= DUAL_TIMESTAMP_NULL
;
153 sd_id128_t previous_boot_id
= SD_ID128_NULL
;
159 assert(pos
>= m
->delta
);
163 while (pos
>= m
->size
) {
166 /* End of this entry, so let's serialize the next
169 if (m
->n_entries_set
&&
171 return MHD_CONTENT_READER_END_OF_STREAM
;
174 r
= sd_journal_previous_skip(m
->journal
, (uint64_t) -m
->n_skip
+ 1);
175 else if (m
->n_skip
> 0)
176 r
= sd_journal_next_skip(m
->journal
, (uint64_t) m
->n_skip
+ 1);
178 r
= sd_journal_next(m
->journal
);
181 log_error_errno(r
, "Failed to advance journal pointer: %m");
182 return MHD_CONTENT_READER_END_WITH_ERROR
;
186 r
= sd_journal_wait(m
->journal
, (uint64_t) JOURNAL_WAIT_TIMEOUT
);
188 log_error_errno(r
, "Couldn't wait for journal event: %m");
189 return MHD_CONTENT_READER_END_WITH_ERROR
;
191 if (r
== SD_JOURNAL_NOP
)
197 return MHD_CONTENT_READER_END_OF_STREAM
;
203 r
= sd_journal_test_cursor(m
->journal
, m
->cursor
);
205 log_error_errno(r
, "Failed to test cursor: %m");
206 return MHD_CONTENT_READER_END_WITH_ERROR
;
210 return MHD_CONTENT_READER_END_OF_STREAM
;
216 if (m
->n_entries_set
)
221 r
= request_meta_ensure_tmp(m
);
223 log_error_errno(r
, "Failed to create temporary file: %m");
224 return MHD_CONTENT_READER_END_WITH_ERROR
;
227 r
= show_journal_entry(m
->tmp
, m
->journal
, m
->mode
, 0, OUTPUT_FULL_WIDTH
,
228 NULL
, NULL
, NULL
, &previous_ts
, &previous_boot_id
);
230 log_error_errno(r
, "Failed to serialize item: %m");
231 return MHD_CONTENT_READER_END_WITH_ERROR
;
235 if (sz
== (off_t
) -1) {
236 log_error_errno(errno
, "Failed to retrieve file position: %m");
237 return MHD_CONTENT_READER_END_WITH_ERROR
;
240 m
->size
= (uint64_t) sz
;
243 if (m
->tmp
== NULL
&& m
->follow
)
246 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
247 log_error_errno(errno
, "Failed to seek to position: %m");
248 return MHD_CONTENT_READER_END_WITH_ERROR
;
258 k
= fread(buf
, 1, n
, m
->tmp
);
260 log_error("Failed to read from file: %s", STRERROR_OR_EOF(errno
));
261 return MHD_CONTENT_READER_END_WITH_ERROR
;
267 static int request_parse_accept(
269 struct MHD_Connection
*connection
) {
276 header
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Accept");
280 if (streq(header
, mime_types
[OUTPUT_JSON
]))
281 m
->mode
= OUTPUT_JSON
;
282 else if (streq(header
, mime_types
[OUTPUT_JSON_SSE
]))
283 m
->mode
= OUTPUT_JSON_SSE
;
284 else if (streq(header
, mime_types
[OUTPUT_JSON_SEQ
]))
285 m
->mode
= OUTPUT_JSON_SEQ
;
286 else if (streq(header
, mime_types
[OUTPUT_EXPORT
]))
287 m
->mode
= OUTPUT_EXPORT
;
289 m
->mode
= OUTPUT_SHORT
;
294 static int request_parse_range(
296 struct MHD_Connection
*connection
) {
298 const char *range
, *colon
, *colon2
;
304 range
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Range");
308 if (!startswith(range
, "entries="))
312 range
+= strspn(range
, WHITESPACE
);
314 colon
= strchr(range
, ':');
316 m
->cursor
= strdup(range
);
320 colon2
= strchr(colon
+ 1, ':');
322 _cleanup_free_
char *t
= NULL
;
324 t
= strndup(colon
+ 1, colon2
- colon
- 1);
328 r
= safe_atoi64(t
, &m
->n_skip
);
333 p
= (colon2
?: colon
) + 1;
335 r
= safe_atou64(p
, &m
->n_entries
);
339 if (m
->n_entries
<= 0)
342 m
->n_entries_set
= true;
345 m
->cursor
= strndup(range
, colon
- range
);
351 m
->cursor
[strcspn(m
->cursor
, WHITESPACE
)] = 0;
352 if (isempty(m
->cursor
))
353 m
->cursor
= mfree(m
->cursor
);
358 static mhd_result
request_parse_arguments_iterator(
360 enum MHD_ValueKind kind
,
364 RequestMeta
*m
= ASSERT_PTR(cls
);
365 _cleanup_free_
char *p
= NULL
;
369 m
->argument_parse_error
= -EINVAL
;
373 if (streq(key
, "follow")) {
374 if (isempty(value
)) {
379 r
= parse_boolean(value
);
381 m
->argument_parse_error
= r
;
389 if (streq(key
, "discrete")) {
390 if (isempty(value
)) {
395 r
= parse_boolean(value
);
397 m
->argument_parse_error
= r
;
405 if (streq(key
, "boot")) {
409 r
= parse_boolean(value
);
411 m
->argument_parse_error
= r
;
417 char match
[9 + 32 + 1] = "_BOOT_ID=";
420 r
= sd_id128_get_boot(&bid
);
422 log_error_errno(r
, "Failed to get boot ID: %m");
426 sd_id128_to_string(bid
, match
+ 9);
427 r
= sd_journal_add_match(m
->journal
, match
, sizeof(match
)-1);
429 m
->argument_parse_error
= r
;
437 p
= strjoin(key
, "=", strempty(value
));
439 m
->argument_parse_error
= log_oom();
443 r
= sd_journal_add_match(m
->journal
, p
, 0);
445 m
->argument_parse_error
= r
;
452 static int request_parse_arguments(
454 struct MHD_Connection
*connection
) {
459 m
->argument_parse_error
= 0;
460 MHD_get_connection_values(connection
, MHD_GET_ARGUMENT_KIND
, request_parse_arguments_iterator
, m
);
462 return m
->argument_parse_error
;
465 static int request_handler_entries(
466 struct MHD_Connection
*connection
,
467 void *connection_cls
) {
469 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
470 RequestMeta
*m
= ASSERT_PTR(connection_cls
);
477 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
479 if (request_parse_accept(m
, connection
) < 0)
480 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.");
482 if (request_parse_range(m
, connection
) < 0)
483 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Range header.");
485 if (request_parse_arguments(m
, connection
) < 0)
486 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse URL arguments.");
490 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Discrete seeks require a cursor specification.");
493 m
->n_entries_set
= true;
497 r
= sd_journal_seek_cursor(m
->journal
, m
->cursor
);
498 else if (m
->n_skip
>= 0)
499 r
= sd_journal_seek_head(m
->journal
);
500 else if (m
->n_skip
< 0)
501 r
= sd_journal_seek_tail(m
->journal
);
503 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to seek in journal.");
505 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_entries
, m
, NULL
);
507 return respond_oom(connection
);
509 if (MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
]) == MHD_NO
)
510 return respond_oom(connection
);
512 return MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
515 static int output_field(FILE *f
, OutputMode m
, const char *d
, size_t l
) {
519 eq
= memchr(d
, '=', l
);
523 j
= l
- (eq
- d
+ 1);
525 if (m
== OUTPUT_JSON
) {
526 fprintf(f
, "{ \"%.*s\" : ", (int) (eq
- d
), d
);
527 json_escape(f
, eq
+1, j
, OUTPUT_FULL_WIDTH
);
530 fwrite(eq
+1, 1, j
, f
);
537 static ssize_t
request_reader_fields(
543 RequestMeta
*m
= ASSERT_PTR(cls
);
549 assert(pos
>= m
->delta
);
553 while (pos
>= m
->size
) {
558 /* End of this field, so let's serialize the next
561 r
= sd_journal_enumerate_unique(m
->journal
, &d
, &l
);
563 log_error_errno(r
, "Failed to advance field index: %m");
564 return MHD_CONTENT_READER_END_WITH_ERROR
;
566 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", STRERROR_OR_EOF(errno
));
605 return MHD_CONTENT_READER_END_WITH_ERROR
;
611 static int request_handler_fields(
612 struct MHD_Connection
*connection
,
614 void *connection_cls
) {
616 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
617 RequestMeta
*m
= ASSERT_PTR(connection_cls
);
624 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
626 if (request_parse_accept(m
, connection
) < 0)
627 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.");
629 r
= sd_journal_query_unique(m
->journal
, field
);
631 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to query unique fields.");
633 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_fields
, m
, NULL
);
635 return respond_oom(connection
);
637 if (MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
== OUTPUT_JSON
? OUTPUT_JSON
: OUTPUT_SHORT
]) == MHD_NO
)
638 return respond_oom(connection
);
640 return MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
643 static int request_handler_redirect(
644 struct MHD_Connection
*connection
,
645 const char *target
) {
647 _cleanup_free_
char *page
= NULL
;
648 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
653 if (asprintf(&page
, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target
) < 0)
654 return respond_oom(connection
);
656 response
= MHD_create_response_from_buffer(strlen(page
), page
, MHD_RESPMEM_MUST_FREE
);
658 return respond_oom(connection
);
661 if (MHD_add_response_header(response
, "Content-Type", "text/html") == MHD_NO
||
662 MHD_add_response_header(response
, "Location", target
) == MHD_NO
)
663 return respond_oom(connection
);
665 return MHD_queue_response(connection
, MHD_HTTP_MOVED_PERMANENTLY
, response
);
668 static int request_handler_file(
669 struct MHD_Connection
*connection
,
671 const char *mime_type
) {
673 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
674 _cleanup_close_
int fd
= -EBADF
;
681 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
683 return mhd_respondf(connection
, errno
, MHD_HTTP_NOT_FOUND
, "Failed to open file %s: %m", path
);
685 if (fstat(fd
, &st
) < 0)
686 return mhd_respondf(connection
, errno
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to stat file: %m");
688 response
= MHD_create_response_from_fd_at_offset64(st
.st_size
, fd
, 0);
690 return respond_oom(connection
);
693 if (MHD_add_response_header(response
, "Content-Type", mime_type
) == MHD_NO
)
694 return respond_oom(connection
);
696 return MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
699 static int get_virtualization(char **v
) {
700 _cleanup_(sd_bus_unrefp
) sd_bus
*bus
= NULL
;
704 r
= sd_bus_default_system(&bus
);
708 r
= bus_get_property_string(bus
, bus_systemd_mgr
, "Virtualization", NULL
, &b
);
722 static int request_handler_machine(
723 struct MHD_Connection
*connection
,
724 void *connection_cls
) {
726 _cleanup_(MHD_destroy_responsep
) struct MHD_Response
*response
= NULL
;
727 RequestMeta
*m
= ASSERT_PTR(connection_cls
);
729 _cleanup_free_
char* hostname
= NULL
, *pretty_name
= NULL
, *os_name
= NULL
;
730 uint64_t cutoff_from
= 0, cutoff_to
= 0, usage
= 0;
732 _cleanup_free_
char *v
= NULL
, *json
= NULL
;
738 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
740 r
= sd_id128_get_machine(&mid
);
742 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine machine ID: %m");
744 r
= sd_id128_get_boot(&bid
);
746 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine boot ID: %m");
748 hostname
= gethostname_malloc();
750 return respond_oom(connection
);
752 r
= sd_journal_get_usage(m
->journal
, &usage
);
754 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %m");
756 r
= sd_journal_get_cutoff_realtime_usec(m
->journal
, &cutoff_from
, &cutoff_to
);
758 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %m");
760 (void) parse_os_release(
762 "PRETTY_NAME", &pretty_name
,
764 (void) get_virtualization(&v
);
767 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR
"\","
768 "\"boot_id\" : \"" SD_ID128_FORMAT_STR
"\","
769 "\"hostname\" : \"%s\","
770 "\"os_pretty_name\" : \"%s\","
771 "\"virtualization\" : \"%s\","
772 "\"usage\" : \"%"PRIu64
"\","
773 "\"cutoff_from_realtime\" : \"%"PRIu64
"\","
774 "\"cutoff_to_realtime\" : \"%"PRIu64
"\" }\n",
775 SD_ID128_FORMAT_VAL(mid
),
776 SD_ID128_FORMAT_VAL(bid
),
777 hostname_cleanup(hostname
),
778 os_release_pretty_name(pretty_name
, os_name
),
784 return respond_oom(connection
);
786 response
= MHD_create_response_from_buffer(strlen(json
), json
, MHD_RESPMEM_MUST_FREE
);
788 return respond_oom(connection
);
791 if (MHD_add_response_header(response
, "Content-Type", "application/json") == MHD_NO
)
792 return respond_oom(connection
);
794 return MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
797 static mhd_result
request_handler(
799 struct MHD_Connection
*connection
,
803 const char *upload_data
,
804 size_t *upload_data_size
,
805 void **connection_cls
) {
809 assert(connection_cls
);
813 if (!streq(method
, "GET"))
814 return mhd_respond(connection
, MHD_HTTP_NOT_ACCEPTABLE
, "Unsupported method.");
816 if (!*connection_cls
) {
817 if (!request_meta(connection_cls
))
818 return respond_oom(connection
);
823 r
= check_permissions(connection
, &code
, NULL
);
829 return request_handler_redirect(connection
, "/browse");
831 if (streq(url
, "/entries"))
832 return request_handler_entries(connection
, *connection_cls
);
834 if (startswith(url
, "/fields/"))
835 return request_handler_fields(connection
, url
+ 8, *connection_cls
);
837 if (streq(url
, "/browse"))
838 return request_handler_file(connection
, DOCUMENT_ROOT
"/browse.html", "text/html");
840 if (streq(url
, "/machine"))
841 return request_handler_machine(connection
, *connection_cls
);
843 return mhd_respond(connection
, MHD_HTTP_NOT_FOUND
, "Not found.");
846 static int help(void) {
847 _cleanup_free_
char *link
= NULL
;
850 r
= terminal_urlify_man("systemd-journal-gatewayd.service", "8", &link
);
854 printf("%s [OPTIONS...] ...\n\n"
855 "HTTP server for journal events.\n\n"
856 " -h --help Show this help\n"
857 " --version Show package version\n"
858 " --cert=CERT.PEM Server certificate in PEM format\n"
859 " --key=KEY.PEM Server key in PEM format\n"
860 " --trust=CERT.PEM Certificate authority certificate in PEM format\n"
861 " --system Serve system journal\n"
862 " --user Serve the user journal for the current user\n"
863 " -m --merge Serve all available journals\n"
864 " -D --directory=PATH Serve journal files in directory\n"
865 " --file=PATH Serve this journal file\n"
866 "\nSee the %s for details.\n",
867 program_invocation_short_name
,
873 static int parse_argv(int argc
, char *argv
[]) {
887 static const struct option options
[] = {
888 { "help", no_argument
, NULL
, 'h' },
889 { "version", no_argument
, NULL
, ARG_VERSION
},
890 { "key", required_argument
, NULL
, ARG_KEY
},
891 { "cert", required_argument
, NULL
, ARG_CERT
},
892 { "trust", required_argument
, NULL
, ARG_TRUST
},
893 { "user", no_argument
, NULL
, ARG_USER
},
894 { "system", no_argument
, NULL
, ARG_SYSTEM
},
895 { "merge", no_argument
, NULL
, 'm' },
896 { "directory", required_argument
, NULL
, 'D' },
897 { "file", required_argument
, NULL
, ARG_FILE
},
904 while ((c
= getopt_long(argc
, argv
, "hD:", options
, NULL
)) >= 0)
916 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
917 "Key file specified twice");
918 r
= read_full_file_full(
919 AT_FDCWD
, optarg
, UINT64_MAX
, SIZE_MAX
,
920 READ_FULL_FILE_SECURE
|READ_FULL_FILE_WARN_WORLD_READABLE
|READ_FULL_FILE_CONNECT_SOCKET
,
924 return log_error_errno(r
, "Failed to read key file: %m");
930 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
931 "Certificate file specified twice");
932 r
= read_full_file_full(
933 AT_FDCWD
, optarg
, UINT64_MAX
, SIZE_MAX
,
934 READ_FULL_FILE_CONNECT_SOCKET
,
936 &arg_cert_pem
, NULL
);
938 return log_error_errno(r
, "Failed to read certificate file: %m");
939 assert(arg_cert_pem
);
945 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
946 "CA certificate file specified twice");
947 r
= read_full_file_full(
948 AT_FDCWD
, optarg
, UINT64_MAX
, SIZE_MAX
,
949 READ_FULL_FILE_CONNECT_SOCKET
,
951 &arg_trust_pem
, NULL
);
953 return log_error_errno(r
, "Failed to read CA certificate file: %m");
954 assert(arg_trust_pem
);
957 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
958 "Option --trust= is not available.");
962 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
966 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
974 arg_directory
= optarg
;
978 r
= glob_extend(&arg_file
, optarg
, GLOB_NOCHECK
);
980 return log_error_errno(r
, "Failed to add paths: %m");
987 assert_not_reached();
991 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
992 "This program does not take arguments.");
994 if (!!arg_key_pem
!= !!arg_cert_pem
)
995 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
996 "Certificate and key files must be specified together");
998 if (arg_trust_pem
&& !arg_key_pem
)
999 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1000 "CA certificate can only be used with certificate file");
1005 static int run(int argc
, char *argv
[]) {
1006 _cleanup_(MHD_stop_daemonp
) struct MHD_Daemon
*d
= NULL
;
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
},
1020 /* We force MHD_USE_ITC here, in order to make sure
1021 * libmicrohttpd doesn't use shutdown() on our listening
1022 * socket, which would break socket re-activation. See
1024 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1025 * https://github.com/systemd/systemd/pull/1286
1030 MHD_USE_DUAL_STACK
|
1032 MHD_USE_POLL_INTERNAL_THREAD
|
1033 MHD_USE_THREAD_PER_CONNECTION
;
1038 r
= parse_argv(argc
, argv
);
1044 r
= setup_gnutls_logger(NULL
);
1048 n
= sd_listen_fds(1);
1050 return log_error_errno(n
, "Failed to determine passed sockets: %m");
1052 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Can't listen on more than one socket.");
1055 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1056 { MHD_OPTION_LISTEN_SOCKET
, SD_LISTEN_FDS_START
};
1059 assert(arg_cert_pem
);
1060 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1061 { MHD_OPTION_HTTPS_MEM_KEY
, 0, arg_key_pem
};
1062 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1063 { MHD_OPTION_HTTPS_MEM_CERT
, 0, arg_cert_pem
};
1064 flags
|= MHD_USE_TLS
;
1067 if (arg_trust_pem
) {
1068 assert(flags
& MHD_USE_TLS
);
1069 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1070 { MHD_OPTION_HTTPS_MEM_TRUST
, 0, arg_trust_pem
};
1073 d
= MHD_start_daemon(flags
, 19531,
1075 request_handler
, NULL
,
1076 MHD_OPTION_ARRAY
, opts
,
1079 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Failed to start daemon!");
1086 DEFINE_MAIN_FUNCTION(run
);