libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

acme_http_01_token.c (23537B)


      1 /* SPDX-License-Identifier: 0BSD */
      2 /*
      3   This file is part of GNU libmicrohttpd.
      4   Copyright (C) 2026 Evgeny Grin (Karlson2k)
      5 
      6   Permission to use, copy, modify, and/or distribute this software for
      7   any purpose with or without fee is hereby granted.
      8 
      9   THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
     10   WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
     11   OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
     12   FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
     13   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
     14   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
     15   OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16 */
     17 /**
     18  * @file src/examples2/acme/acme_http_01_token.c
     19  * @brief  Answering the ACME HTTP-01 challenge given on the command line
     20  * @author Karlson2k (Evgeny Grin)
     21  */
     22 
     23 /* A minimal HTTP server that answers a single ACME HTTP-01 challenge given
     24    on the command line.  Nothing else is served.
     25 
     26    Unlike the file-based example, nothing is read from the disk: the key
     27    authorization is given directly, so the server is fully described by its
     28    arguments.  The ACME servers always use port 80.  Another port may be
     29    given if the traffic from port 80 is forwarded to it.
     30 
     31    The example can be used as a ready-to-run program, and its code can be
     32    taken into an application as well: the daemon request processing callback
     33    (req_cb()) and the data shared by all the requests
     34    (struct RequestHandlingContextData) do not depend on how the daemon is
     35    created.  Everything else is just the stand-alone program around them.
     36 
     37    Any path other than a challenge path is answered with "404 Not Found",
     38    a challenge path requested with a method other than GET or HEAD with
     39    "405 Method Not Allowed".
     40 
     41    Usage:  acme_http_01_token [--naive] PORT KEY_AUTHORIZATION
     42    The arguments are told apart by their length, so their order is not
     43    significant.  With "--naive" the account thumbprint is given instead of
     44    the key authorization; see the note at print_naive_warning().          */
     45 
     46 #include <stdio.h>
     47 #include <stdlib.h>
     48 #include <string.h>
     49 #include <microhttpd2.h>
     50 
     51 /**
     52  * The minimal length of the challenge token.
     53  * The token carries at least 128 bits of entropy, which needs at least
     54  * twenty-two characters of the base64url alphabet.
     55  * See RFC 8555, section 8.3.
     56  */
     57 #define MIN_TOKEN_LEN 22
     58 
     59 /**
     60  * The maximal length of the port number given on the command line.
     61  * An argument of at least #MIN_TOKEN_LEN characters is the key
     62  * authorization or the thumbprint; the lengths in between are rejected.
     63  */
     64 #define MAX_PORT_LEN 5
     65 
     66 /**
     67  * The maximal length of the key authorization built in the "naive" mode,
     68  * without the terminating null character.  The other mode sends the string
     69  * given on the command line and needs no buffer at all.
     70  */
     71 #define MAX_KEY_AUTH_LEN 511
     72 
     73 /**
     74  * The fixed prefix of the path of the ACME HTTP-01 challenge resources.
     75  */
     76 static const char chlng_prfx[] = MHD_ACME_HTTP_01_CHALLENGE_PATH_PREFIX;
     77 
     78 /**
     79  * The length of #chlng_prfx, without the terminating null character.
     80  */
     81 static const size_t chlng_prfx_len = sizeof(chlng_prfx) / sizeof(char) - 1;
     82 
     83 /**
     84  * The data shared by all requests handled by this example.
     85  */
     86 struct RequestHandlingContextData
     87 {
     88   /**
     89    * The key authorization to send, NULL in the "naive" mode.
     90    */
     91   const char *key_auth;
     92 
     93   /**
     94    * The length of #key_auth, without the terminating null character.
     95    */
     96   size_t key_auth_len;
     97 
     98   /**
     99    * The length of the token part of #key_auth, zero in the "naive" mode.
    100    * The token is the part of the key authorization before the first dot,
    101    * so it is also the prefix of #key_auth.  See RFC 8555, section 8.1.
    102    */
    103   size_t token_len;
    104 
    105   /**
    106    * The account thumbprint, NULL unless the "naive" mode is used.
    107    */
    108   const char *thumbprint;
    109 
    110   /**
    111    * The length of #thumbprint, without the terminating null character.
    112    */
    113   size_t thumbprint_len;
    114 
    115   /**
    116    * The ready response with the key authorization, used for every matching
    117    * request.  NULL in the "naive" mode.
    118    */
    119   struct MHD_Response *resp_challenge;
    120 
    121   /**
    122    * The response for every request that is not answered with the key
    123    * authorization: another resource, another token or a too long token.
    124    */
    125   struct MHD_Response *resp_not_found;
    126 
    127   /**
    128    * The response for the ACME challenge resources requested with
    129    * an unsupported method.
    130    */
    131   struct MHD_Response *resp_not_allowed;
    132 
    133   /**
    134    * If not zero, every served challenge is reported to standard error.
    135    */
    136   int log_challenges;
    137 };
    138 
    139 
    140 /**
    141  * Parse the TCP port number given on the command line.
    142  *
    143  * @param str the string to parse
    144  * @param[out] port_out the resulting port number, set only on success
    145  * @return non-zero if succeed,
    146  *         zero if @p str is not a valid port number
    147  */
    148 static int
    149 parse_port (const char *str,
    150             uint_least16_t *port_out)
    151 {
    152   unsigned long value;
    153   char *endptr;
    154 
    155   if ('\0' == str[0])
    156     return 0;
    157 
    158   value = strtoul (str,
    159                    &endptr,
    160                    10);
    161   if ('\0' != endptr[0])
    162     return 0;
    163   /* A negative number is wrapped by strtoul() to a large value and
    164      is rejected by the range check below. */
    165   if ((1UL > value) || (65535UL < value))
    166     return 0;
    167 
    168   *port_out = (uint_least16_t)value;
    169 
    170   return 1;
    171 }
    172 
    173 
    174 /**
    175  * Check the challenge token taken from the request.
    176  *
    177  * In the "naive" mode the token is copied from the request to the reply,
    178  * which makes this check mandatory: RFC 8555, section 8.3, requires such
    179  * a client to validate the token syntax.
    180  *
    181  * @param token the token to check
    182  * @param token_len the length of @p token, without the terminating null
    183  *                  character
    184  * @return non-zero if the token is syntactically valid,
    185  *         zero otherwise
    186  */
    187 static int
    188 check_token (const char *token,
    189              size_t token_len)
    190 {
    191   size_t i;
    192 
    193   if (MIN_TOKEN_LEN > token_len)
    194     return 0;
    195 
    196   for (i = 0; token_len > i; ++i)
    197   {
    198     const unsigned char chr = (unsigned char)token[i];
    199 
    200     if ((('A' > chr) || ('Z' < chr))
    201         && (('a' > chr) || ('z' < chr))
    202         && (('0' > chr) || ('9' < chr))
    203         && ('-' != chr) && ('_' != chr))
    204       return 0;
    205   }
    206 
    207   return 1;
    208 }
    209 
    210 
    211 /**
    212  * Report the "naive" mode to the user.
    213  *
    214  * In this mode the reply is built from the token of the request instead of
    215  * being compared with the configured one, so any syntactically valid token
    216  * is answered.  RFC 8555, section 11.3, requires the entropy of the token
    217  * partly to make such servers harder to write.
    218  */
    219 static void
    220 print_naive_warning (void)
    221 {
    222   fprintf (stderr,
    223            "WARNING: the \"naive\" mode (also known as \"stateless\")"
    224            " answers every syntactically valid token with the account"
    225            " thumbprint, thus confirming any challenge that is asked for.\n"
    226            "It is not recommended by RFC 8555, section 8.3, "
    227            "and is less secure.\n");
    228 }
    229 
    230 
    231 /**
    232  * Create a re-usable response with the given body.
    233  *
    234  * @param sc the HTTP status code of the response
    235  * @param body_len the length of @p body, without the terminating null
    236  *                 character
    237  * @param body the body of the response, must be valid for the whole
    238  *             lifetime of the response
    239  * @param content_type the value of the "Content-Type:" header
    240  * @param allow the value of the "Allow:" header,
    241  *              NULL if the header is not needed
    242  * @return the new response,
    243  *         NULL if failed
    244  */
    245 static struct MHD_Response *
    246 make_static_response (enum MHD_HTTP_StatusCode sc,
    247                       size_t body_len,
    248                       const char *body,
    249                       const char *content_type,
    250                       const char *allow)
    251 {
    252   struct MHD_ResponseOptionAndValue reusable;
    253   struct MHD_Response *r;
    254 
    255   /* The option is kept in a variable: the address of the value returned by
    256      the option helper cannot be taken in every supported language mode. */
    257   reusable = MHD_R_OPTION_REUSABLE (MHD_YES);
    258 
    259   r = MHD_response_from_buffer_static (sc,
    260                                        body_len,
    261                                        body);
    262   if (NULL != r)
    263   {
    264     if (MHD_SC_OK ==
    265         MHD_response_add_header (r,
    266                                  MHD_HTTP_HEADER_CONTENT_TYPE,
    267                                  content_type))
    268     {
    269       if ((NULL == allow)
    270           || (MHD_SC_OK ==
    271               MHD_response_add_header (r,
    272                                        MHD_HTTP_HEADER_ALLOW,
    273                                        allow)))
    274       {
    275         if (MHD_SC_OK ==
    276             MHD_response_set_option (r,
    277                                      &reusable))
    278           return r; /* Success exit point */
    279       }
    280     }
    281 
    282     /* Below is a clean-up path */
    283     MHD_response_destroy (r);
    284   }
    285 
    286   return NULL; /* Failure exit point */
    287 }
    288 
    289 
    290 /**
    291  * Report the challenge that is being served.
    292  *
    293  * @param method the HTTP method used for the request, GET or HEAD
    294  * @param token the token of the challenge resource
    295  * @param token_len the length of @p token, without the terminating null
    296  *                  character
    297  */
    298 static void
    299 log_challenge (enum MHD_HTTP_Method method,
    300                const char *token,
    301                size_t token_len)
    302 {
    303   /* No other method reaches this point. */
    304   fprintf (stderr,
    305            "Replying to the %s request for challenge %.*s\n",
    306            (MHD_HTTP_METHOD_HEAD == method) ? "HEAD" : "GET",
    307            (int)token_len,
    308            token);
    309 }
    310 
    311 
    312 /**
    313  * Check whether the request is for an ACME HTTP-01 challenge resource.
    314  * The token is checked as well, so the token of a matching request is
    315  * syntactically valid.
    316  *
    317  * @param path the requested path
    318  * @return non-zero if the request is for a challenge resource,
    319  *         zero otherwise
    320  */
    321 static int
    322 is_req_acme_http_01_challenge (const struct MHD_String *MHD_RESTRICT path)
    323 {
    324   return ((chlng_prfx_len < path->len)
    325           && (0 == memcmp (path->cstr,
    326                            chlng_prfx,
    327                            chlng_prfx_len))
    328           && (check_token (path->cstr + chlng_prfx_len,
    329                            path->len - chlng_prfx_len)));
    330 }
    331 
    332 
    333 /**
    334  * Handle the request for an ACME HTTP-01 challenge resource.
    335  *
    336  * @param request the request to handle
    337  * @param path the requested path, must be a challenge resource path
    338  * @param method the HTTP method used for the request
    339  * @param context_data the data shared by all the requests
    340  * @return the action to perform for the @p request
    341  */
    342 static const struct MHD_Action *
    343 handle_acme_http_01_challenge (
    344   struct MHD_Request *MHD_RESTRICT request,
    345   const struct MHD_String *MHD_RESTRICT path,
    346   enum MHD_HTTP_Method method,
    347   const struct RequestHandlingContextData *const context_data)
    348 {
    349   const char *const token = path->cstr + chlng_prfx_len;
    350   const size_t token_len = path->len - chlng_prfx_len;
    351   char key_auth[MAX_KEY_AUTH_LEN + 1];
    352   struct MHD_Response *r;
    353 
    354   /* Only the GET and the HEAD methods are allowed for the challenge
    355      resources. */
    356   if ((MHD_HTTP_METHOD_GET != method)
    357       && (MHD_HTTP_METHOD_HEAD != method))
    358     return MHD_action_from_response (request,
    359                                      context_data->resp_not_allowed);
    360 
    361   if (NULL != context_data->key_auth)
    362   {
    363     /* The key authorization is known, so only the configured token is
    364        answered.  The token is the prefix of the key authorization. */
    365     if ((token_len != context_data->token_len)
    366         || (0 != memcmp (token,
    367                          context_data->key_auth,
    368                          token_len)))
    369       return MHD_action_from_response (request,
    370                                        context_data->resp_not_found);
    371 
    372     if (context_data->log_challenges)
    373       log_challenge (method,
    374                      token,
    375                      token_len);
    376 
    377     /* There is only one key authorization, so the reply never changes:
    378        the ready response is used and nothing has to be built here. */
    379     return MHD_action_from_response (request,
    380                                      context_data->resp_challenge);
    381   }
    382 
    383   /* The "naive" mode: the key authorization is not known in advance, so it
    384      is built for every request from the token of the request and the
    385      configured account thumbprint, joined with a dot.
    386      See RFC 8555, section 8.1. */
    387   if (MAX_KEY_AUTH_LEN - context_data->thumbprint_len - 1 < token_len)
    388     return MHD_action_from_response (request,
    389                                      context_data->resp_not_found);
    390 
    391   memcpy (key_auth,
    392           token,
    393           token_len);
    394   key_auth[token_len] = '.';
    395   memcpy (key_auth + token_len + 1,
    396           context_data->thumbprint,
    397           context_data->thumbprint_len);
    398 
    399   r = MHD_response_from_buffer_copy (MHD_HTTP_STATUS_OK,
    400                                      token_len + 1
    401                                      + context_data->thumbprint_len,
    402                                      key_auth);
    403   if (NULL != r)
    404   {
    405     if (MHD_SC_OK ==
    406         MHD_response_add_header (r,
    407                                  MHD_HTTP_HEADER_CONTENT_TYPE,
    408                                  "application/octet-stream"))
    409     {
    410       if (context_data->log_challenges)
    411         log_challenge (method,
    412                        token,
    413                        token_len);
    414 
    415       return MHD_action_from_response (request,
    416                                        r); /* Success exit point */
    417     }
    418 
    419     /* Below is a clean-up path */
    420     MHD_response_destroy (r);
    421   }
    422 
    423   return MHD_action_abort_request (request); /* Failure exit point */
    424 }
    425 
    426 
    427 /**
    428  * The handler of the incoming requests.
    429  *
    430  * @param cls the pointer to the shared #RequestHandlingContextData
    431  * @param request the request to handle
    432  * @param path the requested path
    433  * @param method the HTTP method used for the request
    434  * @param upload_size the size of the request content, unused
    435  * @return the action to perform for the @p request
    436  */
    437 static MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3)
    438 const struct MHD_Action *
    439 req_cb (void *cls,
    440         struct MHD_Request *MHD_RESTRICT request,
    441         const struct MHD_String *MHD_RESTRICT path,
    442         enum MHD_HTTP_Method method,
    443         uint_fast64_t upload_size)
    444 {
    445   const struct RequestHandlingContextData *const context_data =
    446     (const struct RequestHandlingContextData *)cls;
    447 
    448   /* If the request is for an ACME HTTP-01 challenge resource, handle it
    449      accordingly. */
    450   if (is_req_acme_http_01_challenge (path))
    451     return handle_acme_http_01_challenge (request,
    452                                           path,
    453                                           method,
    454                                           context_data);
    455 
    456   /* Here other requests can be handled for different needs.
    457      This example just sends 404 (Not Found) replies. */
    458   (void)upload_size;  /* Unused */
    459 
    460   return MHD_action_from_response (request,
    461                                    context_data->resp_not_found);
    462 }
    463 
    464 
    465 /**
    466  * Create the responses that do not change from request to request.
    467  *
    468  * @param[in,out] context_data the data to set the responses in
    469  * @return non-zero if succeed,
    470  *         zero if failed
    471  */
    472 static int
    473 init_responses (struct RequestHandlingContextData *context_data)
    474 {
    475   static const char page_not_found[] =
    476     "<html><body>Not found</body></html>";
    477   static const char page_not_allowed[] =
    478     "<html><body>Method not allowed</body></html>";
    479 
    480   context_data->resp_challenge = NULL;
    481   if (NULL != context_data->key_auth)
    482   {
    483     /* The key authorization never changes, so the response for it is built
    484        once here instead of being built for every request. */
    485     context_data->resp_challenge =
    486       make_static_response (MHD_HTTP_STATUS_OK,
    487                             context_data->key_auth_len,
    488                             context_data->key_auth,
    489                             "application/octet-stream",
    490                             NULL);
    491     if (NULL == context_data->resp_challenge)
    492       return 0; /* Failure exit point */
    493   }
    494 
    495   context_data->resp_not_found =
    496     make_static_response (MHD_HTTP_STATUS_NOT_FOUND,
    497                           sizeof(page_not_found) / sizeof(char) - 1,
    498                           page_not_found,
    499                           "text/html",
    500                           NULL);
    501   if (NULL != context_data->resp_not_found)
    502   {
    503     /* The challenge resource is read-only.  The "Allow:" header is required
    504        for this status code by RFC 9110, section 15.5.6. */
    505     context_data->resp_not_allowed =
    506       make_static_response (MHD_HTTP_STATUS_METHOD_NOT_ALLOWED,
    507                             sizeof(page_not_allowed) / sizeof(char) - 1,
    508                             page_not_allowed,
    509                             "text/html",
    510                             "GET, HEAD");
    511     if (NULL != context_data->resp_not_allowed)
    512       return 1; /* Success exit point */
    513 
    514     /* Below is a clean-up path */
    515     MHD_response_destroy (context_data->resp_not_found);
    516   }
    517   if (NULL != context_data->resp_challenge)
    518     MHD_response_destroy (context_data->resp_challenge);
    519 
    520   return 0; /* Failure exit point */
    521 }
    522 
    523 
    524 /**
    525  * Destroy the responses created by init_responses().
    526  *
    527  * @param[in] context_data the data with the responses to destroy
    528  */
    529 static void
    530 deinit_responses (struct RequestHandlingContextData *context_data)
    531 {
    532   MHD_response_destroy (context_data->resp_not_allowed);
    533   MHD_response_destroy (context_data->resp_not_found);
    534   if (NULL != context_data->resp_challenge)
    535     MHD_response_destroy (context_data->resp_challenge);
    536 }
    537 
    538 
    539 /**
    540  * Create, configure, start and run the daemon until the user presses ENTER.
    541  *
    542  * @param data the data shared by all requests, must be valid until this
    543  *             function returns
    544  * @param port the TCP port to listen on
    545  * @return zero if the daemon has been started and stopped normally,
    546  *         the exit code of the program otherwise
    547  */
    548 static int
    549 run_daemon (struct RequestHandlingContextData *data,
    550             uint_least16_t port)
    551 {
    552   struct MHD_Daemon *d;
    553   int ret;
    554 
    555   if (!init_responses (data))
    556   {
    557     fprintf (stderr,
    558              "Failed to create a response object.\n");
    559     return 4;
    560   }
    561 
    562   d = MHD_daemon_create (&req_cb,
    563                          data);
    564   if (NULL == d)
    565   {
    566     fprintf (stderr,
    567              "Failed to create MHD daemon.\n");
    568     deinit_responses (data);
    569     return 3;
    570   }
    571 
    572   ret = 0;
    573   if (MHD_SC_OK !=
    574       MHD_DAEMON_SET_OPTIONS (
    575         d,
    576         MHD_D_OPTION_WM_WORKER_THREADS (1),
    577         MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO,
    578                                 port),
    579         /* This daemon is exposed to the open Internet and talks to
    580            well-behaving clients only, so the protocol is enforced strictly.
    581            The nearest stricter level is taken if this exact level is not
    582            available in the library build. */
    583         MHD_D_OPTION_PROTOCOL_STRICT_LEVEL (MHD_PSL_STRICT,
    584                                             MHD_USL_THIS_OR_STRICTER),
    585         /* The following three options are an optional optimisation only:
    586            this daemon uses none of these features, so MHD is told not to
    587            spend any resources on them.  Everything works without them. */
    588         MHD_D_OPTION_DISABLE_COOKIES (MHD_YES),
    589         MHD_D_OPTION_DISALLOW_UPGRADE (MHD_YES),
    590         MHD_D_OPTION_DISALLOW_SUSPEND_RESUME (MHD_YES)))
    591   {
    592     fprintf (stderr,
    593              "Failed to set MHD daemon run parameters.\n");
    594     ret = 3;
    595   }
    596   else if (MHD_SC_OK !=
    597            MHD_daemon_start (d))
    598   {
    599     fprintf (stderr,
    600              "Failed to start MHD daemon.\n");
    601     ret = 3;
    602   }
    603   else
    604   {
    605     printf ("The MHD daemon is listening on port %u\n"
    606             "Press ENTER to stop.\n",
    607             (unsigned int)port);
    608     (void)fgetc (stdin);
    609   }
    610   printf ("Stopping... ");
    611   fflush (stdout);
    612   MHD_daemon_destroy (d);
    613   printf ("OK\n");
    614   deinit_responses (data);
    615 
    616   return ret;
    617 }
    618 
    619 
    620 /**
    621  * Parse the command line arguments.
    622  *
    623  * The port number and the key authorization are told apart by their length,
    624  * so the arguments may be given in any order.  All the errors are reported
    625  * by this function.
    626  *
    627  * @param argc the number of the command line arguments
    628  * @param argv the command line arguments
    629  * @param[out] port_out the TCP port to listen on, set only on success
    630  * @param[out] auth_out the key authorization, or the account thumbprint if
    631  *                      the "naive" mode is requested, set only on success
    632  * @param[out] naive_out set to non-zero if the "naive" mode is requested
    633  * @return non-zero if succeed,
    634  *         zero if the arguments are not valid
    635  */
    636 static int
    637 parse_cmd_line (int argc,
    638                 char *const *argv,
    639                 uint_least16_t *port_out,
    640                 const char **auth_out,
    641                 int *naive_out)
    642 {
    643   const char *port_str = NULL;
    644   const char *auth = NULL;
    645   int i;
    646 
    647   *naive_out = 0;
    648   for (i = 1; argc > i; ++i)
    649   {
    650     const size_t arg_len = strlen (argv[i]);
    651 
    652     if ((0 == strcmp (argv[i],
    653                       "--naive"))
    654         || (0 == strcmp (argv[i],
    655                          "--stateless")))
    656       *naive_out = !0;
    657     else if ((MAX_PORT_LEN >= arg_len) && (NULL == port_str))
    658       port_str = argv[i];
    659     else if ((MIN_TOKEN_LEN <= arg_len) && (NULL == auth))
    660       auth = argv[i];
    661     else
    662     {
    663       fprintf (stderr,
    664                "Unexpected argument \"%s\": wrong length or given twice.\n",
    665                argv[i]);
    666       return 0;
    667     }
    668   }
    669   if ((NULL == port_str) || (NULL == auth))
    670   {
    671     fprintf (stderr,
    672              "Usage:\n%s [--naive] PORT KEY_AUTHORIZATION\n",
    673              argv[0]);
    674     return 0;
    675   }
    676   if (!parse_port (port_str,
    677                    port_out))
    678   {
    679     fprintf (stderr,
    680              "The PORT must be a numeric value between 1 and 65535.\n");
    681     return 0;
    682   }
    683 
    684   *auth_out = auth;
    685 
    686   return 1;
    687 }
    688 
    689 
    690 /**
    691  * Run the example.
    692  *
    693  * @param argc the number of the command line arguments
    694  * @param argv the command line arguments: the TCP port to listen on, the key
    695  *             authorization and the optional "--naive" option
    696  * @return zero if succeed,
    697  *         non-zero otherwise
    698  */
    699 int
    700 main (int argc,
    701       char *const *argv)
    702 {
    703   struct RequestHandlingContextData data;
    704   const char *auth;
    705   const char *dot;
    706   uint_least16_t port;
    707   int naive;
    708 
    709   if (!parse_cmd_line (argc,
    710                        argv,
    711                        &port,
    712                        &auth,
    713                        &naive))
    714     return 1;
    715 
    716   data.key_auth = NULL;
    717   data.key_auth_len = 0;
    718   data.token_len = 0;
    719   data.thumbprint = NULL;
    720   data.thumbprint_len = 0;
    721   if (naive)
    722   {
    723     print_naive_warning ();
    724     data.thumbprint = auth;
    725     data.thumbprint_len = strlen (auth);
    726     if (MAX_KEY_AUTH_LEN - MIN_TOKEN_LEN - 1 < data.thumbprint_len)
    727     {
    728       fprintf (stderr,
    729                "The thumbprint is too long.\n");
    730       return 2;
    731     }
    732   }
    733   else
    734   {
    735     /* The token is the part of the key authorization before the first dot.
    736        See RFC 8555, section 8.1. */
    737     dot = strchr (auth, '.');
    738     if ((NULL == dot)
    739         || (!check_token (auth,
    740                           (size_t)(dot - auth))))
    741     {
    742       fprintf (stderr,
    743                "The KEY_AUTHORIZATION must be the token, a dot and"
    744                " the account thumbprint.\n");
    745       return 2;
    746     }
    747     data.key_auth = auth;
    748     data.key_auth_len = strlen (auth);
    749     data.token_len = (size_t)(dot - auth);
    750   }
    751   /* A real application would take this from its configuration. */
    752   data.log_challenges = !0;
    753 
    754   return run_daemon (&data,
    755                      port);
    756 }