1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include <microhttpd.h>
11 #include "sd-daemon.h"
12 #include "sd-journal.h"
14 #include "alloc-util.h"
18 #include "hostname-util.h"
20 #include "logs-show.h"
21 #include "main-func.h"
22 #include "microhttpd-util.h"
24 #include "parse-util.h"
25 #include "pretty-print.h"
27 #include "tmpfile-util.h"
30 #define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC)
32 static char *arg_key_pem
= NULL
;
33 static char *arg_cert_pem
= NULL
;
34 static char *arg_trust_pem
= NULL
;
35 static const char *arg_directory
= NULL
;
37 STATIC_DESTRUCTOR_REGISTER(arg_key_pem
, freep
);
38 STATIC_DESTRUCTOR_REGISTER(arg_cert_pem
, freep
);
39 STATIC_DESTRUCTOR_REGISTER(arg_trust_pem
, freep
);
41 typedef struct RequestMeta
{
54 int argument_parse_error
;
63 static const char* const mime_types
[_OUTPUT_MODE_MAX
] = {
64 [OUTPUT_SHORT
] = "text/plain",
65 [OUTPUT_JSON
] = "application/json",
66 [OUTPUT_JSON_SSE
] = "text/event-stream",
67 [OUTPUT_JSON_SEQ
] = "application/json-seq",
68 [OUTPUT_EXPORT
] = "application/vnd.fdo.journal",
71 static RequestMeta
*request_meta(void **connection_cls
) {
74 assert(connection_cls
);
76 return *connection_cls
;
78 m
= new0(RequestMeta
, 1);
86 static void request_meta_free(
88 struct MHD_Connection
*connection
,
89 void **connection_cls
,
90 enum MHD_RequestTerminationCode toe
) {
92 RequestMeta
*m
= *connection_cls
;
97 sd_journal_close(m
->journal
);
105 static int open_journal(RequestMeta
*m
) {
112 return sd_journal_open_directory(&m
->journal
, arg_directory
, 0);
114 return sd_journal_open(&m
->journal
, SD_JOURNAL_LOCAL_ONLY
|SD_JOURNAL_SYSTEM
);
117 static int request_meta_ensure_tmp(RequestMeta
*m
) {
125 fd
= open_tmpfile_unlinkable("/tmp", O_RDWR
|O_CLOEXEC
);
129 m
->tmp
= fdopen(fd
, "w+");
139 static ssize_t
request_reader_entries(
145 RequestMeta
*m
= cls
;
152 assert(pos
>= m
->delta
);
156 while (pos
>= m
->size
) {
159 /* End of this entry, so let's serialize the next
162 if (m
->n_entries_set
&&
164 return MHD_CONTENT_READER_END_OF_STREAM
;
167 r
= sd_journal_previous_skip(m
->journal
, (uint64_t) -m
->n_skip
+ 1);
168 else if (m
->n_skip
> 0)
169 r
= sd_journal_next_skip(m
->journal
, (uint64_t) m
->n_skip
+ 1);
171 r
= sd_journal_next(m
->journal
);
174 log_error_errno(r
, "Failed to advance journal pointer: %m");
175 return MHD_CONTENT_READER_END_WITH_ERROR
;
179 r
= sd_journal_wait(m
->journal
, (uint64_t) JOURNAL_WAIT_TIMEOUT
);
181 log_error_errno(r
, "Couldn't wait for journal event: %m");
182 return MHD_CONTENT_READER_END_WITH_ERROR
;
184 if (r
== SD_JOURNAL_NOP
)
190 return MHD_CONTENT_READER_END_OF_STREAM
;
196 r
= sd_journal_test_cursor(m
->journal
, m
->cursor
);
198 log_error_errno(r
, "Failed to test cursor: %m");
199 return MHD_CONTENT_READER_END_WITH_ERROR
;
203 return MHD_CONTENT_READER_END_OF_STREAM
;
209 if (m
->n_entries_set
)
214 r
= request_meta_ensure_tmp(m
);
216 log_error_errno(r
, "Failed to create temporary file: %m");
217 return MHD_CONTENT_READER_END_WITH_ERROR
;
220 r
= show_journal_entry(m
->tmp
, m
->journal
, m
->mode
, 0, OUTPUT_FULL_WIDTH
,
223 log_error_errno(r
, "Failed to serialize item: %m");
224 return MHD_CONTENT_READER_END_WITH_ERROR
;
228 if (sz
== (off_t
) -1) {
229 log_error_errno(errno
, "Failed to retrieve file position: %m");
230 return MHD_CONTENT_READER_END_WITH_ERROR
;
233 m
->size
= (uint64_t) sz
;
236 if (m
->tmp
== NULL
&& m
->follow
)
239 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
240 log_error_errno(errno
, "Failed to seek to position: %m");
241 return MHD_CONTENT_READER_END_WITH_ERROR
;
251 k
= fread(buf
, 1, n
, m
->tmp
);
253 log_error("Failed to read from file: %s", errno
? strerror(errno
) : "Premature EOF");
254 return MHD_CONTENT_READER_END_WITH_ERROR
;
260 static int request_parse_accept(
262 struct MHD_Connection
*connection
) {
269 header
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Accept");
273 if (streq(header
, mime_types
[OUTPUT_JSON
]))
274 m
->mode
= OUTPUT_JSON
;
275 else if (streq(header
, mime_types
[OUTPUT_JSON_SSE
]))
276 m
->mode
= OUTPUT_JSON_SSE
;
277 else if (streq(header
, mime_types
[OUTPUT_JSON_SEQ
]))
278 m
->mode
= OUTPUT_JSON_SEQ
;
279 else if (streq(header
, mime_types
[OUTPUT_EXPORT
]))
280 m
->mode
= OUTPUT_EXPORT
;
282 m
->mode
= OUTPUT_SHORT
;
287 static int request_parse_range(
289 struct MHD_Connection
*connection
) {
291 const char *range
, *colon
, *colon2
;
297 range
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Range");
301 if (!startswith(range
, "entries="))
305 range
+= strspn(range
, WHITESPACE
);
307 colon
= strchr(range
, ':');
309 m
->cursor
= strdup(range
);
313 colon2
= strchr(colon
+ 1, ':');
315 _cleanup_free_
char *t
;
317 t
= strndup(colon
+ 1, colon2
- colon
- 1);
321 r
= safe_atoi64(t
, &m
->n_skip
);
326 p
= (colon2
? colon2
: colon
) + 1;
328 r
= safe_atou64(p
, &m
->n_entries
);
332 if (m
->n_entries
<= 0)
335 m
->n_entries_set
= true;
338 m
->cursor
= strndup(range
, colon
- range
);
344 m
->cursor
[strcspn(m
->cursor
, WHITESPACE
)] = 0;
345 if (isempty(m
->cursor
))
346 m
->cursor
= mfree(m
->cursor
);
351 static int request_parse_arguments_iterator(
353 enum MHD_ValueKind kind
,
357 RequestMeta
*m
= cls
;
358 _cleanup_free_
char *p
= NULL
;
364 m
->argument_parse_error
= -EINVAL
;
368 if (streq(key
, "follow")) {
369 if (isempty(value
)) {
374 r
= parse_boolean(value
);
376 m
->argument_parse_error
= r
;
384 if (streq(key
, "discrete")) {
385 if (isempty(value
)) {
390 r
= parse_boolean(value
);
392 m
->argument_parse_error
= r
;
400 if (streq(key
, "boot")) {
404 r
= parse_boolean(value
);
406 m
->argument_parse_error
= r
;
412 char match
[9 + 32 + 1] = "_BOOT_ID=";
415 r
= sd_id128_get_boot(&bid
);
417 log_error_errno(r
, "Failed to get boot ID: %m");
421 sd_id128_to_string(bid
, match
+ 9);
422 r
= sd_journal_add_match(m
->journal
, match
, sizeof(match
)-1);
424 m
->argument_parse_error
= r
;
432 p
= strjoin(key
, "=", strempty(value
));
434 m
->argument_parse_error
= log_oom();
438 r
= sd_journal_add_match(m
->journal
, p
, 0);
440 m
->argument_parse_error
= r
;
447 static int request_parse_arguments(
449 struct MHD_Connection
*connection
) {
454 m
->argument_parse_error
= 0;
455 MHD_get_connection_values(connection
, MHD_GET_ARGUMENT_KIND
, request_parse_arguments_iterator
, m
);
457 return m
->argument_parse_error
;
460 static int request_handler_entries(
461 struct MHD_Connection
*connection
,
462 void *connection_cls
) {
464 struct MHD_Response
*response
;
465 RequestMeta
*m
= connection_cls
;
473 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
475 if (request_parse_accept(m
, connection
) < 0)
476 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.");
478 if (request_parse_range(m
, connection
) < 0)
479 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Range header.");
481 if (request_parse_arguments(m
, connection
) < 0)
482 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse URL arguments.");
486 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Discrete seeks require a cursor specification.");
489 m
->n_entries_set
= true;
493 r
= sd_journal_seek_cursor(m
->journal
, m
->cursor
);
494 else if (m
->n_skip
>= 0)
495 r
= sd_journal_seek_head(m
->journal
);
496 else if (m
->n_skip
< 0)
497 r
= sd_journal_seek_tail(m
->journal
);
499 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to seek in journal.");
501 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_entries
, m
, NULL
);
503 return respond_oom(connection
);
505 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
]);
507 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
508 MHD_destroy_response(response
);
513 static int output_field(FILE *f
, OutputMode m
, const char *d
, size_t l
) {
517 eq
= memchr(d
, '=', l
);
521 j
= l
- (eq
- d
+ 1);
523 if (m
== OUTPUT_JSON
) {
524 fprintf(f
, "{ \"%.*s\" : ", (int) (eq
- d
), d
);
525 json_escape(f
, eq
+1, j
, OUTPUT_FULL_WIDTH
);
528 fwrite(eq
+1, 1, j
, f
);
535 static ssize_t
request_reader_fields(
541 RequestMeta
*m
= cls
;
548 assert(pos
>= m
->delta
);
552 while (pos
>= m
->size
) {
557 /* End of this field, so let's serialize the next
560 if (m
->n_fields_set
&&
562 return MHD_CONTENT_READER_END_OF_STREAM
;
564 r
= sd_journal_enumerate_unique(m
->journal
, &d
, &l
);
566 log_error_errno(r
, "Failed to advance field index: %m");
567 return MHD_CONTENT_READER_END_WITH_ERROR
;
569 return MHD_CONTENT_READER_END_OF_STREAM
;
577 r
= request_meta_ensure_tmp(m
);
579 log_error_errno(r
, "Failed to create temporary file: %m");
580 return MHD_CONTENT_READER_END_WITH_ERROR
;
583 r
= output_field(m
->tmp
, m
->mode
, d
, l
);
585 log_error_errno(r
, "Failed to serialize item: %m");
586 return MHD_CONTENT_READER_END_WITH_ERROR
;
590 if (sz
== (off_t
) -1) {
591 log_error_errno(errno
, "Failed to retrieve file position: %m");
592 return MHD_CONTENT_READER_END_WITH_ERROR
;
595 m
->size
= (uint64_t) sz
;
598 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
599 log_error_errno(errno
, "Failed to seek to position: %m");
600 return MHD_CONTENT_READER_END_WITH_ERROR
;
608 k
= fread(buf
, 1, n
, m
->tmp
);
610 log_error("Failed to read from file: %s", errno
? strerror(errno
) : "Premature EOF");
611 return MHD_CONTENT_READER_END_WITH_ERROR
;
617 static int request_handler_fields(
618 struct MHD_Connection
*connection
,
620 void *connection_cls
) {
622 struct MHD_Response
*response
;
623 RequestMeta
*m
= connection_cls
;
631 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
633 if (request_parse_accept(m
, connection
) < 0)
634 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.");
636 r
= sd_journal_query_unique(m
->journal
, field
);
638 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to query unique fields.");
640 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_fields
, m
, NULL
);
642 return respond_oom(connection
);
644 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
== OUTPUT_JSON
? OUTPUT_JSON
: OUTPUT_SHORT
]);
646 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
647 MHD_destroy_response(response
);
652 static int request_handler_redirect(
653 struct MHD_Connection
*connection
,
654 const char *target
) {
657 struct MHD_Response
*response
;
663 if (asprintf(&page
, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target
) < 0)
664 return respond_oom(connection
);
666 response
= MHD_create_response_from_buffer(strlen(page
), page
, MHD_RESPMEM_MUST_FREE
);
669 return respond_oom(connection
);
672 MHD_add_response_header(response
, "Content-Type", "text/html");
673 MHD_add_response_header(response
, "Location", target
);
675 ret
= MHD_queue_response(connection
, MHD_HTTP_MOVED_PERMANENTLY
, response
);
676 MHD_destroy_response(response
);
681 static int request_handler_file(
682 struct MHD_Connection
*connection
,
684 const char *mime_type
) {
686 struct MHD_Response
*response
;
688 _cleanup_close_
int fd
= -1;
695 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
697 return mhd_respondf(connection
, errno
, MHD_HTTP_NOT_FOUND
, "Failed to open file %s: %m", path
);
699 if (fstat(fd
, &st
) < 0)
700 return mhd_respondf(connection
, errno
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to stat file: %m");
702 response
= MHD_create_response_from_fd_at_offset64(st
.st_size
, fd
, 0);
704 return respond_oom(connection
);
708 MHD_add_response_header(response
, "Content-Type", mime_type
);
710 ret
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
711 MHD_destroy_response(response
);
716 static int get_virtualization(char **v
) {
717 _cleanup_(sd_bus_unrefp
) sd_bus
*bus
= NULL
;
721 r
= sd_bus_default_system(&bus
);
725 r
= sd_bus_get_property_string(
727 "org.freedesktop.systemd1",
728 "/org/freedesktop/systemd1",
729 "org.freedesktop.systemd1.Manager",
746 static int request_handler_machine(
747 struct MHD_Connection
*connection
,
748 void *connection_cls
) {
750 struct MHD_Response
*response
;
751 RequestMeta
*m
= connection_cls
;
753 _cleanup_free_
char* hostname
= NULL
, *os_name
= NULL
;
754 uint64_t cutoff_from
= 0, cutoff_to
= 0, usage
= 0;
757 _cleanup_free_
char *v
= NULL
;
764 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
766 r
= sd_id128_get_machine(&mid
);
768 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine machine ID: %m");
770 r
= sd_id128_get_boot(&bid
);
772 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine boot ID: %m");
774 hostname
= gethostname_malloc();
776 return respond_oom(connection
);
778 r
= sd_journal_get_usage(m
->journal
, &usage
);
780 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %m");
782 r
= sd_journal_get_cutoff_realtime_usec(m
->journal
, &cutoff_from
, &cutoff_to
);
784 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %m");
786 (void) parse_os_release(NULL
, "PRETTY_NAME", &os_name
, NULL
);
787 (void) get_virtualization(&v
);
790 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR
"\","
791 "\"boot_id\" : \"" SD_ID128_FORMAT_STR
"\","
792 "\"hostname\" : \"%s\","
793 "\"os_pretty_name\" : \"%s\","
794 "\"virtualization\" : \"%s\","
795 "\"usage\" : \"%"PRIu64
"\","
796 "\"cutoff_from_realtime\" : \"%"PRIu64
"\","
797 "\"cutoff_to_realtime\" : \"%"PRIu64
"\" }\n",
798 SD_ID128_FORMAT_VAL(mid
),
799 SD_ID128_FORMAT_VAL(bid
),
800 hostname_cleanup(hostname
),
801 os_name
? os_name
: "Linux",
808 return respond_oom(connection
);
810 response
= MHD_create_response_from_buffer(strlen(json
), json
, MHD_RESPMEM_MUST_FREE
);
813 return respond_oom(connection
);
816 MHD_add_response_header(response
, "Content-Type", "application/json");
817 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
818 MHD_destroy_response(response
);
823 static int request_handler(
825 struct MHD_Connection
*connection
,
829 const char *upload_data
,
830 size_t *upload_data_size
,
831 void **connection_cls
) {
835 assert(connection_cls
);
839 if (!streq(method
, "GET"))
840 return mhd_respond(connection
, MHD_HTTP_NOT_ACCEPTABLE
, "Unsupported method.");
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.");
872 static int help(void) {
873 _cleanup_free_
char *link
= NULL
;
876 r
= terminal_urlify_man("systemd-journal-gatewayd.service", "8", &link
);
880 printf("%s [OPTIONS...] ...\n\n"
881 "HTTP server for journal events.\n\n"
882 " -h --help Show this help\n"
883 " --version Show package version\n"
884 " --cert=CERT.PEM Server certificate in PEM format\n"
885 " --key=KEY.PEM Server key in PEM format\n"
886 " --trust=CERT.PEM Certificate authority certificate in PEM format\n"
887 " -D --directory=PATH Serve journal files in directory\n"
888 "\nSee the %s for details.\n"
889 , program_invocation_short_name
896 static int parse_argv(int argc
, char *argv
[]) {
906 static const struct option options
[] = {
907 { "help", no_argument
, NULL
, 'h' },
908 { "version", no_argument
, NULL
, ARG_VERSION
},
909 { "key", required_argument
, NULL
, ARG_KEY
},
910 { "cert", required_argument
, NULL
, ARG_CERT
},
911 { "trust", required_argument
, NULL
, ARG_TRUST
},
912 { "directory", required_argument
, NULL
, 'D' },
919 while ((c
= getopt_long(argc
, argv
, "hD:", options
, NULL
)) >= 0)
931 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
932 "Key file specified twice");
933 r
= read_full_file(optarg
, &arg_key_pem
, NULL
);
935 return log_error_errno(r
, "Failed to read key file: %m");
941 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
942 "Certificate file specified twice");
943 r
= read_full_file(optarg
, &arg_cert_pem
, NULL
);
945 return log_error_errno(r
, "Failed to read certificate file: %m");
946 assert(arg_cert_pem
);
952 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
953 "CA certificate file specified twice");
954 r
= read_full_file(optarg
, &arg_trust_pem
, NULL
);
956 return log_error_errno(r
, "Failed to read CA certificate file: %m");
957 assert(arg_trust_pem
);
960 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
961 "Option --trust is not available.");
964 arg_directory
= optarg
;
971 assert_not_reached("Unhandled option");
975 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
976 "This program does not take arguments.");
978 if (!!arg_key_pem
!= !!arg_cert_pem
)
979 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
980 "Certificate and key files must be specified together");
982 if (arg_trust_pem
&& !arg_key_pem
)
983 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
984 "CA certificate can only be used with certificate file");
989 static int run(int argc
, char *argv
[]) {
990 _cleanup_(MHD_stop_daemonp
) struct MHD_Daemon
*d
= NULL
;
991 struct MHD_OptionItem opts
[] = {
992 { MHD_OPTION_NOTIFY_COMPLETED
,
993 (intptr_t) request_meta_free
, NULL
},
994 { MHD_OPTION_EXTERNAL_LOGGER
,
995 (intptr_t) microhttpd_logger
, NULL
},
996 { MHD_OPTION_END
, 0, NULL
},
997 { MHD_OPTION_END
, 0, NULL
},
998 { MHD_OPTION_END
, 0, NULL
},
999 { MHD_OPTION_END
, 0, NULL
},
1000 { MHD_OPTION_END
, 0, NULL
},
1004 /* We force MHD_USE_ITC here, in order to make sure
1005 * libmicrohttpd doesn't use shutdown() on our listening
1006 * socket, which would break socket re-activation. See
1008 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1009 * https://github.com/systemd/systemd/pull/1286
1014 MHD_USE_DUAL_STACK
|
1016 MHD_USE_POLL_INTERNAL_THREAD
|
1017 MHD_USE_THREAD_PER_CONNECTION
;
1020 log_setup_service();
1022 r
= parse_argv(argc
, argv
);
1028 r
= setup_gnutls_logger(NULL
);
1032 n
= sd_listen_fds(1);
1034 return log_error_errno(n
, "Failed to determine passed sockets: %m");
1036 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Can't listen on more than one socket.");
1039 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1040 { MHD_OPTION_LISTEN_SOCKET
, SD_LISTEN_FDS_START
};
1043 assert(arg_cert_pem
);
1044 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1045 { MHD_OPTION_HTTPS_MEM_KEY
, 0, arg_key_pem
};
1046 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1047 { MHD_OPTION_HTTPS_MEM_CERT
, 0, arg_cert_pem
};
1048 flags
|= MHD_USE_TLS
;
1051 if (arg_trust_pem
) {
1052 assert(flags
& MHD_USE_TLS
);
1053 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1054 { MHD_OPTION_HTTPS_MEM_TRUST
, 0, arg_trust_pem
};
1057 d
= MHD_start_daemon(flags
, 19531,
1059 request_handler
, NULL
,
1060 MHD_OPTION_ARRAY
, opts
,
1063 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Failed to start daemon!");
1070 DEFINE_MAIN_FUNCTION(run
);