commit 2432d969f8dc7372d2a130ef66ee0f83865394ce
parent 3319b8ab9364242670218517ccdfec6e9ab9a6bf
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date: Sat, 22 Aug 2026 17:48:25 +0200
Added ACME HTTP-01 token response example
Diffstat:
3 files changed, 759 insertions(+), 1 deletion(-)
diff --git a/src/examples2/acme/.gitignore b/src/examples2/acme/.gitignore
@@ -1,2 +1,3 @@
/acme_http_01_redirect
/acme_http_01_files
+/acme_http_01_token
diff --git a/src/examples2/acme/Makefile.am b/src/examples2/acme/Makefile.am
@@ -22,4 +22,5 @@ $(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile
# example programs
noinst_PROGRAMS = \
acme_http_01_redirect \
- acme_http_01_files
+ acme_http_01_files \
+ acme_http_01_token
diff --git a/src/examples2/acme/acme_http_01_token.c b/src/examples2/acme/acme_http_01_token.c
@@ -0,0 +1,756 @@
+/* SPDX-License-Identifier: 0BSD */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2026 Evgeny Grin (Karlson2k)
+
+ Permission to use, copy, modify, and/or distribute this software for
+ any purpose with or without fee is hereby granted.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
+ FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+ DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+/**
+ * @file src/examples2/acme/acme_http_01_token.c
+ * @brief Answering the ACME HTTP-01 challenge given on the command line
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+/* A minimal HTTP server that answers a single ACME HTTP-01 challenge given
+ on the command line. Nothing else is served.
+
+ Unlike the file-based example, nothing is read from the disk: the key
+ authorization is given directly, so the server is fully described by its
+ arguments. The ACME servers always use port 80. Another port may be
+ given if the traffic from port 80 is forwarded to it.
+
+ The example can be used as a ready-to-run program, and its code can be
+ taken into an application as well: the daemon request processing callback
+ (req_cb()) and the data shared by all the requests
+ (struct RequestHandlingContextData) do not depend on how the daemon is
+ created. Everything else is just the stand-alone program around them.
+
+ Any path other than a challenge path is answered with "404 Not Found",
+ a challenge path requested with a method other than GET or HEAD with
+ "405 Method Not Allowed".
+
+ Usage: acme_http_01_token [--naive] PORT KEY_AUTHORIZATION
+ The arguments are told apart by their length, so their order is not
+ significant. With "--naive" the account thumbprint is given instead of
+ the key authorization; see the note at print_naive_warning(). */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <microhttpd2.h>
+
+/**
+ * The minimal length of the challenge token.
+ * The token carries at least 128 bits of entropy, which needs at least
+ * twenty-two characters of the base64url alphabet.
+ * See RFC 8555, section 8.3.
+ */
+#define MIN_TOKEN_LEN 22
+
+/**
+ * The maximal length of the port number given on the command line.
+ * An argument of at least #MIN_TOKEN_LEN characters is the key
+ * authorization or the thumbprint; the lengths in between are rejected.
+ */
+#define MAX_PORT_LEN 5
+
+/**
+ * The maximal length of the key authorization built in the "naive" mode,
+ * without the terminating null character. The other mode sends the string
+ * given on the command line and needs no buffer at all.
+ */
+#define MAX_KEY_AUTH_LEN 511
+
+/**
+ * The fixed prefix of the path of the ACME HTTP-01 challenge resources.
+ */
+static const char chlng_prfx[] = MHD_ACME_HTTP_01_CHALLENGE_PATH_PREFIX;
+
+/**
+ * The length of #chlng_prfx, without the terminating null character.
+ */
+static const size_t chlng_prfx_len = sizeof(chlng_prfx) / sizeof(char) - 1;
+
+/**
+ * The data shared by all requests handled by this example.
+ */
+struct RequestHandlingContextData
+{
+ /**
+ * The key authorization to send, NULL in the "naive" mode.
+ */
+ const char *key_auth;
+
+ /**
+ * The length of #key_auth, without the terminating null character.
+ */
+ size_t key_auth_len;
+
+ /**
+ * The length of the token part of #key_auth, zero in the "naive" mode.
+ * The token is the part of the key authorization before the first dot,
+ * so it is also the prefix of #key_auth. See RFC 8555, section 8.1.
+ */
+ size_t token_len;
+
+ /**
+ * The account thumbprint, NULL unless the "naive" mode is used.
+ */
+ const char *thumbprint;
+
+ /**
+ * The length of #thumbprint, without the terminating null character.
+ */
+ size_t thumbprint_len;
+
+ /**
+ * The ready response with the key authorization, used for every matching
+ * request. NULL in the "naive" mode.
+ */
+ struct MHD_Response *resp_challenge;
+
+ /**
+ * The response for every request that is not answered with the key
+ * authorization: another resource, another token or a too long token.
+ */
+ struct MHD_Response *resp_not_found;
+
+ /**
+ * The response for the ACME challenge resources requested with
+ * an unsupported method.
+ */
+ struct MHD_Response *resp_not_allowed;
+
+ /**
+ * If not zero, every served challenge is reported to standard error.
+ */
+ int log_challenges;
+};
+
+
+/**
+ * Parse the TCP port number given on the command line.
+ *
+ * @param str the string to parse
+ * @param[out] port_out the resulting port number, set only on success
+ * @return non-zero if succeed,
+ * zero if @p str is not a valid port number
+ */
+static int
+parse_port (const char *str,
+ uint_least16_t *port_out)
+{
+ unsigned long value;
+ char *endptr;
+
+ if ('\0' == str[0])
+ return 0;
+
+ value = strtoul (str,
+ &endptr,
+ 10);
+ if ('\0' != endptr[0])
+ return 0;
+ /* A negative number is wrapped by strtoul() to a large value and
+ is rejected by the range check below. */
+ if ((1UL > value) || (65535UL < value))
+ return 0;
+
+ *port_out = (uint_least16_t)value;
+
+ return 1;
+}
+
+
+/**
+ * Check the challenge token taken from the request.
+ *
+ * In the "naive" mode the token is copied from the request to the reply,
+ * which makes this check mandatory: RFC 8555, section 8.3, requires such
+ * a client to validate the token syntax.
+ *
+ * @param token the token to check
+ * @param token_len the length of @p token, without the terminating null
+ * character
+ * @return non-zero if the token is syntactically valid,
+ * zero otherwise
+ */
+static int
+check_token (const char *token,
+ size_t token_len)
+{
+ size_t i;
+
+ if (MIN_TOKEN_LEN > token_len)
+ return 0;
+
+ for (i = 0; token_len > i; ++i)
+ {
+ const unsigned char chr = (unsigned char)token[i];
+
+ if ((('A' > chr) || ('Z' < chr))
+ && (('a' > chr) || ('z' < chr))
+ && (('0' > chr) || ('9' < chr))
+ && ('-' != chr) && ('_' != chr))
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/**
+ * Report the "naive" mode to the user.
+ *
+ * In this mode the reply is built from the token of the request instead of
+ * being compared with the configured one, so any syntactically valid token
+ * is answered. RFC 8555, section 11.3, requires the entropy of the token
+ * partly to make such servers harder to write.
+ */
+static void
+print_naive_warning (void)
+{
+ fprintf (stderr,
+ "WARNING: the \"naive\" mode (also known as \"stateless\")"
+ " answers every syntactically valid token with the account"
+ " thumbprint, thus confirming any challenge that is asked for.\n"
+ "It is not recommended by RFC 8555, section 8.3, "
+ "and is less secure.\n");
+}
+
+
+/**
+ * Create a re-usable response with the given body.
+ *
+ * @param sc the HTTP status code of the response
+ * @param body_len the length of @p body, without the terminating null
+ * character
+ * @param body the body of the response, must be valid for the whole
+ * lifetime of the response
+ * @param content_type the value of the "Content-Type:" header
+ * @param allow the value of the "Allow:" header,
+ * NULL if the header is not needed
+ * @return the new response,
+ * NULL if failed
+ */
+static struct MHD_Response *
+make_static_response (enum MHD_HTTP_StatusCode sc,
+ size_t body_len,
+ const char *body,
+ const char *content_type,
+ const char *allow)
+{
+ struct MHD_ResponseOptionAndValue reusable;
+ struct MHD_Response *r;
+
+ /* The option is kept in a variable: the address of the value returned by
+ the option helper cannot be taken in every supported language mode. */
+ reusable = MHD_R_OPTION_REUSABLE (MHD_YES);
+
+ r = MHD_response_from_buffer_static (sc,
+ body_len,
+ body);
+ if (NULL != r)
+ {
+ if (MHD_SC_OK ==
+ MHD_response_add_header (r,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ content_type))
+ {
+ if ((NULL == allow)
+ || (MHD_SC_OK ==
+ MHD_response_add_header (r,
+ MHD_HTTP_HEADER_ALLOW,
+ allow)))
+ {
+ if (MHD_SC_OK ==
+ MHD_response_set_option (r,
+ &reusable))
+ return r; /* Success exit point */
+ }
+ }
+
+ /* Below is a clean-up path */
+ MHD_response_destroy (r);
+ }
+
+ return NULL; /* Failure exit point */
+}
+
+
+/**
+ * Report the challenge that is being served.
+ *
+ * @param method the HTTP method used for the request, GET or HEAD
+ * @param token the token of the challenge resource
+ * @param token_len the length of @p token, without the terminating null
+ * character
+ */
+static void
+log_challenge (enum MHD_HTTP_Method method,
+ const char *token,
+ size_t token_len)
+{
+ /* No other method reaches this point. */
+ fprintf (stderr,
+ "Replying to the %s request for challenge %.*s\n",
+ (MHD_HTTP_METHOD_HEAD == method) ? "HEAD" : "GET",
+ (int)token_len,
+ token);
+}
+
+
+/**
+ * Check whether the request is for an ACME HTTP-01 challenge resource.
+ * The token is checked as well, so the token of a matching request is
+ * syntactically valid.
+ *
+ * @param path the requested path
+ * @return non-zero if the request is for a challenge resource,
+ * zero otherwise
+ */
+static int
+is_req_acme_http_01_challenge (const struct MHD_String *MHD_RESTRICT path)
+{
+ return ((chlng_prfx_len < path->len)
+ && (0 == memcmp (path->cstr,
+ chlng_prfx,
+ chlng_prfx_len))
+ && (check_token (path->cstr + chlng_prfx_len,
+ path->len - chlng_prfx_len)));
+}
+
+
+/**
+ * Handle the request for an ACME HTTP-01 challenge resource.
+ *
+ * @param request the request to handle
+ * @param path the requested path, must be a challenge resource path
+ * @param method the HTTP method used for the request
+ * @param context_data the data shared by all the requests
+ * @return the action to perform for the @p request
+ */
+static const struct MHD_Action *
+handle_acme_http_01_challenge (
+ struct MHD_Request *MHD_RESTRICT request,
+ const struct MHD_String *MHD_RESTRICT path,
+ enum MHD_HTTP_Method method,
+ const struct RequestHandlingContextData *const context_data)
+{
+ const char *const token = path->cstr + chlng_prfx_len;
+ const size_t token_len = path->len - chlng_prfx_len;
+ char key_auth[MAX_KEY_AUTH_LEN + 1];
+ struct MHD_Response *r;
+
+ /* Only the GET and the HEAD methods are allowed for the challenge
+ resources. */
+ if ((MHD_HTTP_METHOD_GET != method)
+ && (MHD_HTTP_METHOD_HEAD != method))
+ return MHD_action_from_response (request,
+ context_data->resp_not_allowed);
+
+ if (NULL != context_data->key_auth)
+ {
+ /* The key authorization is known, so only the configured token is
+ answered. The token is the prefix of the key authorization. */
+ if ((token_len != context_data->token_len)
+ || (0 != memcmp (token,
+ context_data->key_auth,
+ token_len)))
+ return MHD_action_from_response (request,
+ context_data->resp_not_found);
+
+ if (context_data->log_challenges)
+ log_challenge (method,
+ token,
+ token_len);
+
+ /* There is only one key authorization, so the reply never changes:
+ the ready response is used and nothing has to be built here. */
+ return MHD_action_from_response (request,
+ context_data->resp_challenge);
+ }
+
+ /* The "naive" mode: the key authorization is not known in advance, so it
+ is built for every request from the token of the request and the
+ configured account thumbprint, joined with a dot.
+ See RFC 8555, section 8.1. */
+ if (MAX_KEY_AUTH_LEN - context_data->thumbprint_len - 1 < token_len)
+ return MHD_action_from_response (request,
+ context_data->resp_not_found);
+
+ memcpy (key_auth,
+ token,
+ token_len);
+ key_auth[token_len] = '.';
+ memcpy (key_auth + token_len + 1,
+ context_data->thumbprint,
+ context_data->thumbprint_len);
+
+ r = MHD_response_from_buffer_copy (MHD_HTTP_STATUS_OK,
+ token_len + 1
+ + context_data->thumbprint_len,
+ key_auth);
+ if (NULL != r)
+ {
+ if (MHD_SC_OK ==
+ MHD_response_add_header (r,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "application/octet-stream"))
+ {
+ if (context_data->log_challenges)
+ log_challenge (method,
+ token,
+ token_len);
+
+ return MHD_action_from_response (request,
+ r); /* Success exit point */
+ }
+
+ /* Below is a clean-up path */
+ MHD_response_destroy (r);
+ }
+
+ return MHD_action_abort_request (request); /* Failure exit point */
+}
+
+
+/**
+ * The handler of the incoming requests.
+ *
+ * @param cls the pointer to the shared #RequestHandlingContextData
+ * @param request the request to handle
+ * @param path the requested path
+ * @param method the HTTP method used for the request
+ * @param upload_size the size of the request content, unused
+ * @return the action to perform for the @p request
+ */
+static MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3)
+const struct MHD_Action *
+req_cb (void *cls,
+ struct MHD_Request *MHD_RESTRICT request,
+ const struct MHD_String *MHD_RESTRICT path,
+ enum MHD_HTTP_Method method,
+ uint_fast64_t upload_size)
+{
+ const struct RequestHandlingContextData *const context_data =
+ (const struct RequestHandlingContextData *)cls;
+
+ /* If the request is for an ACME HTTP-01 challenge resource, handle it
+ accordingly. */
+ if (is_req_acme_http_01_challenge (path))
+ return handle_acme_http_01_challenge (request,
+ path,
+ method,
+ context_data);
+
+ /* Here other requests can be handled for different needs.
+ This example just sends 404 (Not Found) replies. */
+ (void)upload_size; /* Unused */
+
+ return MHD_action_from_response (request,
+ context_data->resp_not_found);
+}
+
+
+/**
+ * Create the responses that do not change from request to request.
+ *
+ * @param[in,out] context_data the data to set the responses in
+ * @return non-zero if succeed,
+ * zero if failed
+ */
+static int
+init_responses (struct RequestHandlingContextData *context_data)
+{
+ static const char page_not_found[] =
+ "<html><body>Not found</body></html>";
+ static const char page_not_allowed[] =
+ "<html><body>Method not allowed</body></html>";
+
+ context_data->resp_challenge = NULL;
+ if (NULL != context_data->key_auth)
+ {
+ /* The key authorization never changes, so the response for it is built
+ once here instead of being built for every request. */
+ context_data->resp_challenge =
+ make_static_response (MHD_HTTP_STATUS_OK,
+ context_data->key_auth_len,
+ context_data->key_auth,
+ "application/octet-stream",
+ NULL);
+ if (NULL == context_data->resp_challenge)
+ return 0; /* Failure exit point */
+ }
+
+ context_data->resp_not_found =
+ make_static_response (MHD_HTTP_STATUS_NOT_FOUND,
+ sizeof(page_not_found) / sizeof(char) - 1,
+ page_not_found,
+ "text/html",
+ NULL);
+ if (NULL != context_data->resp_not_found)
+ {
+ /* The challenge resource is read-only. The "Allow:" header is required
+ for this status code by RFC 9110, section 15.5.6. */
+ context_data->resp_not_allowed =
+ make_static_response (MHD_HTTP_STATUS_METHOD_NOT_ALLOWED,
+ sizeof(page_not_allowed) / sizeof(char) - 1,
+ page_not_allowed,
+ "text/html",
+ "GET, HEAD");
+ if (NULL != context_data->resp_not_allowed)
+ return 1; /* Success exit point */
+
+ /* Below is a clean-up path */
+ MHD_response_destroy (context_data->resp_not_found);
+ }
+ if (NULL != context_data->resp_challenge)
+ MHD_response_destroy (context_data->resp_challenge);
+
+ return 0; /* Failure exit point */
+}
+
+
+/**
+ * Destroy the responses created by init_responses().
+ *
+ * @param[in] context_data the data with the responses to destroy
+ */
+static void
+deinit_responses (struct RequestHandlingContextData *context_data)
+{
+ MHD_response_destroy (context_data->resp_not_allowed);
+ MHD_response_destroy (context_data->resp_not_found);
+ if (NULL != context_data->resp_challenge)
+ MHD_response_destroy (context_data->resp_challenge);
+}
+
+
+/**
+ * Create, configure, start and run the daemon until the user presses ENTER.
+ *
+ * @param data the data shared by all requests, must be valid until this
+ * function returns
+ * @param port the TCP port to listen on
+ * @return zero if the daemon has been started and stopped normally,
+ * the exit code of the program otherwise
+ */
+static int
+run_daemon (struct RequestHandlingContextData *data,
+ uint_least16_t port)
+{
+ struct MHD_Daemon *d;
+ int ret;
+
+ if (!init_responses (data))
+ {
+ fprintf (stderr,
+ "Failed to create a response object.\n");
+ return 4;
+ }
+
+ d = MHD_daemon_create (&req_cb,
+ data);
+ if (NULL == d)
+ {
+ fprintf (stderr,
+ "Failed to create MHD daemon.\n");
+ deinit_responses (data);
+ return 3;
+ }
+
+ ret = 0;
+ if (MHD_SC_OK !=
+ MHD_DAEMON_SET_OPTIONS (
+ d,
+ MHD_D_OPTION_WM_WORKER_THREADS (1),
+ MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO,
+ port),
+ /* This daemon is exposed to the open Internet and talks to
+ well-behaving clients only, so the protocol is enforced strictly.
+ The nearest stricter level is taken if this exact level is not
+ available in the library build. */
+ MHD_D_OPTION_PROTOCOL_STRICT_LEVEL (MHD_PSL_STRICT,
+ MHD_USL_THIS_OR_STRICTER),
+ /* The following three options are an optional optimisation only:
+ this daemon uses none of these features, so MHD is told not to
+ spend any resources on them. Everything works without them. */
+ MHD_D_OPTION_DISABLE_COOKIES (MHD_YES),
+ MHD_D_OPTION_DISALLOW_UPGRADE (MHD_YES),
+ MHD_D_OPTION_DISALLOW_SUSPEND_RESUME (MHD_YES)))
+ {
+ fprintf (stderr,
+ "Failed to set MHD daemon run parameters.\n");
+ ret = 3;
+ }
+ else if (MHD_SC_OK !=
+ MHD_daemon_start (d))
+ {
+ fprintf (stderr,
+ "Failed to start MHD daemon.\n");
+ ret = 3;
+ }
+ else
+ {
+ printf ("The MHD daemon is listening on port %u\n"
+ "Press ENTER to stop.\n",
+ (unsigned int)port);
+ (void)fgetc (stdin);
+ }
+ printf ("Stopping... ");
+ fflush (stdout);
+ MHD_daemon_destroy (d);
+ printf ("OK\n");
+ deinit_responses (data);
+
+ return ret;
+}
+
+
+/**
+ * Parse the command line arguments.
+ *
+ * The port number and the key authorization are told apart by their length,
+ * so the arguments may be given in any order. All the errors are reported
+ * by this function.
+ *
+ * @param argc the number of the command line arguments
+ * @param argv the command line arguments
+ * @param[out] port_out the TCP port to listen on, set only on success
+ * @param[out] auth_out the key authorization, or the account thumbprint if
+ * the "naive" mode is requested, set only on success
+ * @param[out] naive_out set to non-zero if the "naive" mode is requested
+ * @return non-zero if succeed,
+ * zero if the arguments are not valid
+ */
+static int
+parse_cmd_line (int argc,
+ char *const *argv,
+ uint_least16_t *port_out,
+ const char **auth_out,
+ int *naive_out)
+{
+ const char *port_str = NULL;
+ const char *auth = NULL;
+ int i;
+
+ *naive_out = 0;
+ for (i = 1; argc > i; ++i)
+ {
+ const size_t arg_len = strlen (argv[i]);
+
+ if ((0 == strcmp (argv[i],
+ "--naive"))
+ || (0 == strcmp (argv[i],
+ "--stateless")))
+ *naive_out = !0;
+ else if ((MAX_PORT_LEN >= arg_len) && (NULL == port_str))
+ port_str = argv[i];
+ else if ((MIN_TOKEN_LEN <= arg_len) && (NULL == auth))
+ auth = argv[i];
+ else
+ {
+ fprintf (stderr,
+ "Unexpected argument \"%s\": wrong length or given twice.\n",
+ argv[i]);
+ return 0;
+ }
+ }
+ if ((NULL == port_str) || (NULL == auth))
+ {
+ fprintf (stderr,
+ "Usage:\n%s [--naive] PORT KEY_AUTHORIZATION\n",
+ argv[0]);
+ return 0;
+ }
+ if (!parse_port (port_str,
+ port_out))
+ {
+ fprintf (stderr,
+ "The PORT must be a numeric value between 1 and 65535.\n");
+ return 0;
+ }
+
+ *auth_out = auth;
+
+ return 1;
+}
+
+
+/**
+ * Run the example.
+ *
+ * @param argc the number of the command line arguments
+ * @param argv the command line arguments: the TCP port to listen on, the key
+ * authorization and the optional "--naive" option
+ * @return zero if succeed,
+ * non-zero otherwise
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct RequestHandlingContextData data;
+ const char *auth;
+ const char *dot;
+ uint_least16_t port;
+ int naive;
+
+ if (!parse_cmd_line (argc,
+ argv,
+ &port,
+ &auth,
+ &naive))
+ return 1;
+
+ data.key_auth = NULL;
+ data.key_auth_len = 0;
+ data.token_len = 0;
+ data.thumbprint = NULL;
+ data.thumbprint_len = 0;
+ if (naive)
+ {
+ print_naive_warning ();
+ data.thumbprint = auth;
+ data.thumbprint_len = strlen (auth);
+ if (MAX_KEY_AUTH_LEN - MIN_TOKEN_LEN - 1 < data.thumbprint_len)
+ {
+ fprintf (stderr,
+ "The thumbprint is too long.\n");
+ return 2;
+ }
+ }
+ else
+ {
+ /* The token is the part of the key authorization before the first dot.
+ See RFC 8555, section 8.1. */
+ dot = strchr (auth, '.');
+ if ((NULL == dot)
+ || (!check_token (auth,
+ (size_t)(dot - auth))))
+ {
+ fprintf (stderr,
+ "The KEY_AUTHORIZATION must be the token, a dot and"
+ " the account thumbprint.\n");
+ return 2;
+ }
+ data.key_auth = auth;
+ data.key_auth_len = strlen (auth);
+ data.token_len = (size_t)(dot - auth);
+ }
+ /* A real application would take this from its configuration. */
+ data.log_challenges = !0;
+
+ return run_daemon (&data,
+ port);
+}