libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

test_digest_auth.c (21665B)


      1 /*
      2      This file is part of libmicrohttpd
      3      Copyright (C) 2026 Christian Grothoff
      4 
      5      libmicrohttpd is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 2, or (at your
      8      option) any later version.
      9 
     10      libmicrohttpd is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libmicrohttpd; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19 */
     20 
     21 /**
     22  * @file testzzuf/test_digest_auth.c
     23  * @brief  Fuzzing testcase for Digest Authentication
     24  * @author Christian Grothoff
     25  *
     26  * The Digest Authentication parser of MHD used to be completely outside of
     27  * the reach of the fuzzing tests of this directory: no test ever produced
     28  * an "Authorization: Digest" header.
     29  *
     30  * This test closes that gap from two sides:
     31  * - libcurl performs a complete, well-formed digest handshake (two round
     32  *   trips), so zzuf's bit flips land inside real "WWW-Authenticate:" and
     33  *   "Authorization:" headers;
     34  * - the raw socket client sends hand-crafted "Authorization: Digest"
     35  *   headers that are *syntactically* well-formed but semantically hostile
     36  *   (unknown "algorithm" token, over-long "response", over-long userhash,
     37  *   over-long username, missing parameters, ...).  Random bit flipping
     38  *   essentially never produces such an input; these headers have to be
     39  *   written on purpose.
     40  *
     41  * The hostile headers that need a server generated nonce (everything that
     42  * is checked *after* the nonce validation) are built from the nonce that
     43  * MHD hands out in its own 401 reply, which the raw client parses.
     44  */
     45 
     46 #include "platform.h"
     47 #include <curl/curl.h>
     48 #include <microhttpd.h>
     49 #include <stdlib.h>
     50 #include <string.h>
     51 
     52 #include "mhd_zzuf_common.h"
     53 
     54 #define TEST_MAGIC_MARKER 0xFEE1C0DE
     55 
     56 #define DENIED_PAGE "Access denied."
     57 #define GRANTED_PAGE "Access granted."
     58 
     59 /**
     60  * The port offset used if the port cannot be auto-detected.
     61  */
     62 #define TEST_PORT_OFFSET 155
     63 
     64 #define TEST_REALM "TestRealm"
     65 #define TEST_OPAQUE "0123456789abcdef"
     66 #define TEST_USERNAME "testuser"
     67 #define TEST_PASSWORD "testpass"
     68 
     69 /**
     70  * The path that makes MHD issue an MD5 challenge (used by libcurl).
     71  */
     72 #define URI_MD5 "/dauth/md5"
     73 
     74 /**
     75  * The path that makes MHD issue a SHA-256 challenge.  A SHA-256 nonce is
     76  * needed to reach the code that decodes the client's "response" parameter
     77  * with a SHA-256 sized digest.
     78  */
     79 #define URI_SHA256 "/dauth/sha256"
     80 
     81 #define HOST_HDR "Host: " ZZUF_MHD_LISTEN_IP "\r\n"
     82 
     83 /**
     84  * The size of the buffer used to build the raw requests.
     85  */
     86 #define RAW_BUF_SIZE 2048
     87 
     88 /**
     89  * The random data for the nonce generation.
     90  */
     91 static const char digest_rnd_data[32] =
     92 { 'z','z','u','f','_','d','i','g','e','s','t','_','a','u','t','h',
     93   '_','r','a','n','d','o','m','_','s','e','e','d','_','0','0','1' };
     94 
     95 
     96 /**
     97  * The closure of the access handler callback.
     98  */
     99 struct ahc_param
    100 {
    101   /**
    102    * Must have #TEST_MAGIC_MARKER value.
    103    */
    104   unsigned int magic;
    105 
    106   /**
    107    * Non-zero if any error has been encountered.
    108    */
    109   unsigned int err_flag;
    110 
    111   /**
    112    * The number of successful authentications.
    113    */
    114   unsigned int num_ok;
    115 };
    116 
    117 
    118 static enum MHD_Result
    119 ahc_digest (void *cls,
    120             struct MHD_Connection *connection,
    121             const char *url,
    122             const char *method,
    123             const char *version,
    124             const char *upload_data,
    125             size_t *upload_data_size,
    126             void **req_cls)
    127 {
    128   static int marker;
    129   struct ahc_param *param = (struct ahc_param *) cls;
    130   struct MHD_Response *response;
    131   enum MHD_Result ret;
    132   enum MHD_DigestAuthResult check_res;
    133   enum MHD_DigestAuthMultiAlgo3 challenge_algo;
    134 
    135   (void) method; (void) version; (void) upload_data;
    136 
    137   if ((NULL == param) || (TEST_MAGIC_MARKER != param->magic))
    138   {
    139     fprintf (stderr, "The 'cls' parameter is invalid "
    140              "at line %d.\n", (int) __LINE__);
    141     fflush (stderr);
    142     abort ();
    143   }
    144   if (NULL == *req_cls)
    145   {
    146     *req_cls = &marker;
    147     return MHD_YES;
    148   }
    149   if ((NULL != upload_data_size) && (0 != *upload_data_size))
    150   {
    151     *upload_data_size = 0; /* Discard the body */
    152     return MHD_YES;
    153   }
    154 
    155   /* This is the call that parses the (fuzzed or hand-crafted) client's
    156      "Authorization: Digest" header. */
    157   check_res = MHD_digest_auth_check3 (connection,
    158                                       TEST_REALM,
    159                                       TEST_USERNAME,
    160                                       TEST_PASSWORD,
    161                                       0 /* daemon default timeout */,
    162                                       0 /* daemon default max nc */,
    163                                       MHD_DIGEST_AUTH_MULT_QOP_ANY_NON_INT,
    164                                       MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION);
    165   if (MHD_DAUTH_OK == check_res)
    166   {
    167     param->num_ok++;
    168     response =
    169       MHD_create_response_from_buffer_static (MHD_STATICSTR_LEN_ (
    170                                                 GRANTED_PAGE),
    171                                               GRANTED_PAGE);
    172     if (NULL == response)
    173       return MHD_NO; /* External error */
    174     if (zzuf_use_close || ! zzuf_oneone)
    175       (void) MHD_add_response_header (response,
    176                                       MHD_HTTP_HEADER_CONNECTION, "close");
    177     ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    178     MHD_destroy_response (response);
    179     return ret;
    180   }
    181 
    182   /* Not authenticated (or the header was broken): send a challenge. */
    183   if ((NULL != url) && (0 == strcmp (url, URI_SHA256)))
    184     challenge_algo = MHD_DIGEST_AUTH_MULT_ALGO3_SHA256;
    185   else
    186     challenge_algo = MHD_DIGEST_AUTH_MULT_ALGO3_MD5;
    187   response =
    188     MHD_create_response_from_buffer_static (MHD_STATICSTR_LEN_ (DENIED_PAGE),
    189                                             DENIED_PAGE);
    190   if (NULL == response)
    191     return MHD_NO; /* External error */
    192   if (zzuf_use_close || ! zzuf_oneone)
    193     (void) MHD_add_response_header (response,
    194                                     MHD_HTTP_HEADER_CONNECTION, "close");
    195   ret = MHD_queue_auth_required_response3 (connection,
    196                                            TEST_REALM,
    197                                            TEST_OPAQUE,
    198                                            NULL,
    199                                            response,
    200                                            (MHD_DAUTH_NONCE_STALE ==
    201                                             check_res) ? MHD_YES : MHD_NO,
    202                                            MHD_DIGEST_AUTH_MULT_QOP_AUTH,
    203                                            challenge_algo,
    204                                            MHD_YES /* userhash support */,
    205                                            MHD_NO);
    206   MHD_destroy_response (response);
    207   return ret;
    208 }
    209 
    210 
    211 /**
    212  * Extract the value of the "nonce" parameter from a reply.
    213  *
    214  * @param reply the reply, not necessarily zero-terminated
    215  * @param reply_len the number of valid bytes in @a reply
    216  * @param[out] nonce the buffer for the nonce
    217  * @param nonce_size the size of @a nonce
    218  * @return non-zero if a nonce has been extracted, zero otherwise
    219  */
    220 static int
    221 extract_nonce (const char *reply,
    222                size_t reply_len,
    223                char *nonce,
    224                size_t nonce_size)
    225 {
    226   static const char marker[] = "nonce=\"";
    227   size_t i;
    228 
    229   if (reply_len < MHD_STATICSTR_LEN_ (marker) + 1)
    230     return 0;
    231   for (i = 0; i <= reply_len - MHD_STATICSTR_LEN_ (marker) - 1; ++i)
    232   {
    233     size_t k;
    234 
    235     if (0 != memcmp (reply + i, marker, MHD_STATICSTR_LEN_ (marker)))
    236       continue;
    237     i += MHD_STATICSTR_LEN_ (marker);
    238     for (k = 0; (i + k < reply_len) && (k + 1 < nonce_size); ++k)
    239     {
    240       if ('"' == reply[i + k])
    241       {
    242         nonce[k] = 0;
    243         return (0 != k) ? ! 0 : 0;
    244       }
    245       if (('\r' == reply[i + k]) || ('\n' == reply[i + k]))
    246         return 0;
    247       nonce[k] = reply[i + k];
    248     }
    249     return 0;
    250   }
    251   return 0;
    252 }
    253 
    254 
    255 /**
    256  * Fill @a buf with @a len hexadecimal digits and zero-terminate it.
    257  *
    258  * @param[out] buf the buffer to fill
    259  * @param len the number of digits to write
    260  */
    261 static void
    262 fill_hex (char *buf, size_t len)
    263 {
    264   static const char hex[] = "0123456789abcdef";
    265   size_t i;
    266 
    267   for (i = 0; i < len; ++i)
    268     buf[i] = hex[i % 16];
    269   buf[len] = 0;
    270 }
    271 
    272 
    273 /**
    274  * Build one hostile "Authorization: Digest" request.
    275  *
    276  * @param variant the number of the variant to build
    277  * @param nonce the server generated nonce, may be an empty string
    278  * @param[out] buf the buffer for the request
    279  * @param buf_size the size of @a buf
    280  * @return the length of the request, or zero on failure
    281  */
    282 static size_t
    283 build_hostile_request (unsigned int variant,
    284                        const char *nonce,
    285                        char *buf,
    286                        size_t buf_size)
    287 {
    288   char big1[512];
    289   char big2[512];
    290   int len;
    291 
    292   switch (variant % 10)
    293   {
    294   case 0:
    295     /* An "algorithm" token that MHD does not know.  Before the fix of
    296        2026-07-27 this made MHD_digest_auth_check3() run into
    297        MHD_PANIC("Wrong 'malgo3' value, API violation") and abort().
    298        No valid nonce is needed: the algorithm is checked first. */
    299     len = snprintf (buf, buf_size,
    300                     "GET " URI_MD5 " HTTP/1.1\r\n" HOST_HDR
    301                     "Authorization: Digest username=\"" TEST_USERNAME "\", "
    302                     "realm=\"" TEST_REALM "\", nonce=\"%s\", uri=\"" URI_MD5
    303                     "\", algorithm=BOGUS-ALGO, qop=auth, nc=00000001, "
    304                     "cnonce=\"0a0b0c0d\", response=\""
    305                     "0123456789abcdef0123456789abcdef\"\r\n"
    306                     "Connection: close\r\n\r\n",
    307                     nonce);
    308     break;
    309   case 1:
    310     /* An empty (quoted) "algorithm" value. */
    311     len = snprintf (buf, buf_size,
    312                     "GET " URI_MD5 " HTTP/1.1\r\n" HOST_HDR
    313                     "Authorization: Digest username=\"" TEST_USERNAME "\", "
    314                     "realm=\"" TEST_REALM "\", nonce=\"%s\", uri=\"" URI_MD5
    315                     "\", algorithm=\"\", qop=auth, nc=00000001, "
    316                     "cnonce=\"0a0b0c0d\", response=\""
    317                     "0123456789abcdef0123456789abcdef\"\r\n"
    318                     "Connection: close\r\n\r\n",
    319                     nonce);
    320     break;
    321   case 2:
    322     /* A "-sess" algorithm, which MHD does not support. */
    323     len = snprintf (buf, buf_size,
    324                     "GET " URI_MD5 " HTTP/1.1\r\n" HOST_HDR
    325                     "Authorization: Digest username=\"" TEST_USERNAME "\", "
    326                     "realm=\"" TEST_REALM "\", nonce=\"%s\", uri=\"" URI_MD5
    327                     "\", algorithm=MD5-sess, qop=auth, nc=00000001, "
    328                     "cnonce=\"0a0b0c0d\", response=\""
    329                     "0123456789abcdef0123456789abcdef\"\r\n"
    330                     "Connection: close\r\n\r\n",
    331                     nonce);
    332     break;
    333   case 3:
    334     /* An over-long "response" together with a SHA-256 nonce.  Before the
    335        fix of 2026-07-27 the hex decoder wrote 64 bytes into a 32 byte
    336        stack buffer.  This needs a *valid* server nonce, otherwise the
    337        nonce check rejects the request before the "response" is decoded. */
    338     fill_hex (big1, 128);
    339     len = snprintf (buf, buf_size,
    340                     "GET " URI_SHA256 " HTTP/1.1\r\n" HOST_HDR
    341                     "Authorization: Digest username=\"" TEST_USERNAME "\", "
    342                     "realm=\"" TEST_REALM "\", nonce=\"%s\", uri=\""
    343                     URI_SHA256 "\", algorithm=SHA-256, qop=auth, "
    344                     "nc=00000001, cnonce=\"0a0b0c0d\", response=\"%s\"\r\n"
    345                     "Connection: close\r\n\r\n",
    346                     nonce, big1);
    347     break;
    348   case 4:
    349     /* The same, unquoted. */
    350     fill_hex (big1, 128);
    351     len = snprintf (buf, buf_size,
    352                     "GET " URI_SHA256 " HTTP/1.1\r\n" HOST_HDR
    353                     "Authorization: Digest username=\"" TEST_USERNAME "\", "
    354                     "realm=\"" TEST_REALM "\", nonce=%s, uri=\""
    355                     URI_SHA256 "\", algorithm=SHA-256, qop=auth, "
    356                     "nc=00000001, cnonce=\"0a0b0c0d\", response=%s\r\n"
    357                     "Connection: close\r\n\r\n",
    358                     nonce, big1);
    359     break;
    360   case 5:
    361     /* An over-long hex encoded userhash. */
    362     fill_hex (big1, 256);
    363     fill_hex (big2, 64);
    364     len = snprintf (buf, buf_size,
    365                     "GET " URI_SHA256 " HTTP/1.1\r\n" HOST_HDR
    366                     "Authorization: Digest username=\"%s\", userhash=true, "
    367                     "realm=\"" TEST_REALM "\", nonce=\"%s\", uri=\""
    368                     URI_SHA256 "\", algorithm=SHA-256, qop=auth, "
    369                     "nc=00000001, cnonce=\"0a0b0c0d\", response=\"%s\"\r\n"
    370                     "Connection: close\r\n\r\n",
    371                     big1, nonce, big2);
    372     break;
    373   case 6:
    374     /* A very long username. */
    375     fill_hex (big1, 500);
    376     len = snprintf (buf, buf_size,
    377                     "GET " URI_MD5 " HTTP/1.1\r\n" HOST_HDR
    378                     "Authorization: Digest username=\"%s\", "
    379                     "realm=\"" TEST_REALM "\", nonce=\"%s\", uri=\"" URI_MD5
    380                     "\", algorithm=MD5, qop=auth, nc=00000001, "
    381                     "cnonce=\"0a0b0c0d\", response=\""
    382                     "0123456789abcdef0123456789abcdef\"\r\n"
    383                     "Connection: close\r\n\r\n",
    384                     big1, nonce);
    385     break;
    386   case 7:
    387     /* No "response" parameter at all, plus the extended notation for the
    388        username. */
    389     len = snprintf (buf, buf_size,
    390                     "GET " URI_MD5 " HTTP/1.1\r\n" HOST_HDR
    391                     "Authorization: Digest username*=UTF-8''%%41%%42, "
    392                     "realm=\"" TEST_REALM "\", nonce=\"%s\", uri=\"" URI_MD5
    393                     "\", algorithm=MD5, qop=auth, nc=00000001, "
    394                     "cnonce=\"0a0b0c0d\"\r\n"
    395                     "Connection: close\r\n\r\n",
    396                     nonce);
    397     break;
    398   case 8:
    399     /* Garbage in "nc" and an unterminated quoted string. */
    400     len = snprintf (buf, buf_size,
    401                     "GET " URI_MD5 " HTTP/1.1\r\n" HOST_HDR
    402                     "Authorization: Digest username=\"" TEST_USERNAME "\", "
    403                     "realm=\"" TEST_REALM "\", nonce=\"%s\", uri=\"" URI_MD5
    404                     "\", algorithm=MD5, qop=auth, nc=zzzzzzzz, "
    405                     "cnonce=\"0a0b0c0d, response=\""
    406                     "0123456789abcdef0123456789abcdef\"\r\n"
    407                     "Connection: close\r\n\r\n",
    408                     nonce);
    409     break;
    410   case 9:
    411   default:
    412     /* A "Digest" scheme without any parameter, and a second, empty
    413        "Authorization" header. */
    414     len = snprintf (buf, buf_size,
    415                     "GET " URI_MD5 " HTTP/1.1\r\n" HOST_HDR
    416                     "Authorization: Digest\r\n"
    417                     "Authorization: \r\n"
    418                     "Connection: close\r\n\r\n");
    419     break;
    420   }
    421   if ((0 > len) || (buf_size <= (size_t) len))
    422     return 0;
    423   return (size_t) len;
    424 }
    425 
    426 
    427 /**
    428  * Ask MHD for a challenge and extract the nonce from the reply.
    429  *
    430  * @param d_extern the daemon to drive, or NULL
    431  * @param port the port MHD is listening on
    432  * @param uri the URI to request
    433  * @param[out] nonce the buffer for the nonce
    434  * @param nonce_size the size of @a nonce
    435  */
    436 static void
    437 fetch_nonce (struct MHD_Daemon *d_extern,
    438              uint16_t port,
    439              const char *uri,
    440              char *nonce,
    441              size_t nonce_size)
    442 {
    443   struct zzuf_raw_part part;
    444   char req[256];
    445   char reply[2048];
    446   size_t reply_len = 0;
    447   int len;
    448 
    449   nonce[0] = 0;
    450   len = snprintf (req, sizeof(req),
    451                   "GET %s HTTP/1.1\r\n" HOST_HDR "Connection: close\r\n\r\n",
    452                   uri);
    453   if ((0 > len) || (sizeof(req) <= (size_t) len))
    454     return;
    455   part.data = req;
    456   part.size = (size_t) len;
    457   /* Bypass socat (when it is used): a nonce that has been mangled by zzuf
    458      is useless for building a request that must survive the nonce check. */
    459   if (ZZUF_RAW_OK !=
    460       zzuf_raw_exchange2 (d_extern, port, 1, &part, 1,
    461                           (unsigned int) ZZUF_CLIENT_TIMEOUT,
    462                           reply, sizeof(reply), &reply_len))
    463     return;
    464   (void) extract_nonce (reply, reply_len, nonce, nonce_size);
    465 }
    466 
    467 
    468 static unsigned int
    469 client_run (struct MHD_Daemon *d_extern,
    470             uint16_t port,
    471             void *cls)
    472 {
    473   struct ahc_param *param = (struct ahc_param *) cls;
    474   struct zzuf_curl_driver drv;
    475   struct zzuf_curl_sink sink;
    476   char nonce_md5[256];
    477   char nonce_sha256[256];
    478   char *req;
    479   unsigned int i;
    480   unsigned int loops;
    481 
    482   loops = zzuf_loop_count ();
    483   if (! zzuf_curl_driver_init (&drv, d_extern))
    484     return 99; /* Not an MHD error */
    485   req = malloc (RAW_BUF_SIZE);
    486   if (NULL == req)
    487   {
    488     zzuf_curl_driver_deinit (&drv);
    489     return 99; /* Not an MHD error */
    490   }
    491   nonce_md5[0] = 0;
    492   nonce_sha256[0] = 0;
    493 
    494   for (i = 0; i < loops; ++i)
    495   {
    496     CURL *c;
    497     struct zzuf_raw_part part;
    498     size_t req_len;
    499     const char *nonce;
    500 
    501     fprintf (stderr, ".");
    502     /* 1. A complete, well-formed digest handshake performed by libcurl.
    503        The traffic goes through the fuzzing layer. */
    504     c = zzuf_curl_setup (port, URI_MD5, &sink);
    505     if (NULL == c)
    506     {
    507       free (req);
    508       zzuf_curl_driver_deinit (&drv);
    509       return 99; /* Not an MHD error */
    510     }
    511     if ((CURLE_OK != curl_easy_setopt (c, CURLOPT_HTTPAUTH,
    512                                        (long) CURLAUTH_DIGEST)) ||
    513         (CURLE_OK != curl_easy_setopt (c, CURLOPT_USERPWD,
    514                                        TEST_USERNAME ":" TEST_PASSWORD)))
    515     {
    516       curl_easy_cleanup (c);
    517       free (req);
    518       zzuf_curl_driver_deinit (&drv);
    519       return 99; /* Not an MHD error */
    520     }
    521     zzuf_curl_driver_perform (&drv, c);
    522     curl_easy_cleanup (c);
    523 
    524     /* 2. Refresh the nonces from time to time: MHD only accepts a given
    525        nonce/nc combination once. */
    526     if (0 == i % 2)
    527     {
    528       fetch_nonce (d_extern, port, URI_MD5, nonce_md5, sizeof(nonce_md5));
    529       fetch_nonce (d_extern, port, URI_SHA256,
    530                    nonce_sha256, sizeof(nonce_sha256));
    531     }
    532 
    533     /* 3. The hand-crafted hostile headers. */
    534     nonce = ((3 == i % 10) || (4 == i % 10) || (5 == i % 10)) ?
    535             nonce_sha256 : nonce_md5;
    536     req_len = build_hostile_request (i, nonce, req, RAW_BUF_SIZE);
    537     if (0 != req_len)
    538     {
    539       part.data = req;
    540       part.size = req_len;
    541       /* Once through the fuzzing layer ... */
    542       if (ZZUF_RAW_SETUP_FAILED ==
    543           zzuf_raw_exchange (d_extern, port, &part, 1,
    544                              (unsigned int) ZZUF_CLIENT_TIMEOUT))
    545       {
    546         free (req);
    547         zzuf_curl_driver_deinit (&drv);
    548         return 99; /* Not an MHD error */
    549       }
    550       /* ... and once verbatim, so that the hostile header is guaranteed
    551          to reach the parser unmodified at least once. */
    552       if (ZZUF_RAW_SETUP_FAILED ==
    553           zzuf_raw_exchange2 (d_extern, port, 1, &part, 1,
    554                               (unsigned int) ZZUF_CLIENT_TIMEOUT,
    555                               NULL, 0, NULL))
    556       {
    557         free (req);
    558         zzuf_curl_driver_deinit (&drv);
    559         return 99; /* Not an MHD error */
    560       }
    561     }
    562     fflush (stderr);
    563   }
    564   free (req);
    565   zzuf_curl_driver_deinit (&drv);
    566 
    567   if (0 != param->err_flag)
    568   {
    569     fprintf (stderr, "One or more errors have been detected by the access "
    570              "handler callback function. At line %d.\n", (int) __LINE__);
    571     return 1;
    572   }
    573   return 0;
    574 }
    575 
    576 
    577 int
    578 main (int argc, char *const *argv)
    579 {
    580   struct ahc_param param;
    581   struct zzuf_run_params rp;
    582   struct MHD_OptionItem extra_opts[3];
    583   unsigned int res;
    584   int use_magic_exit_codes;
    585 
    586   zzuf_parse_common_args (argc, argv);
    587   use_magic_exit_codes = zzuf_run_with_socat || zzuf_dry_run;
    588 
    589   if (MHD_NO == MHD_is_feature_supported (MHD_FEATURE_DIGEST_AUTH))
    590   {
    591     fprintf (stderr, "Digest Authentication is not supported by this "
    592              "MHD build.\n");
    593     return use_magic_exit_codes ? 77 : 0;
    594   }
    595   res = zzuf_check_runnable ();
    596   if (0 != res)
    597     return use_magic_exit_codes ? (int) res : 0;
    598 
    599   if (CURLE_OK != curl_global_init (CURL_GLOBAL_WIN32))
    600   {
    601     fprintf (stderr, "curl_global_init() failed at line %d.\n",
    602              (int) __LINE__);
    603     return use_magic_exit_codes ? 99 : 0;
    604   }
    605 
    606   param.magic = (unsigned int) TEST_MAGIC_MARKER;
    607   param.err_flag = 0;
    608   param.num_ok = 0;
    609 
    610   extra_opts[0].option = MHD_OPTION_DIGEST_AUTH_RANDOM;
    611   extra_opts[0].value = (intptr_t) sizeof(digest_rnd_data);
    612   extra_opts[0].ptr_value = (void *) (intptr_t) digest_rnd_data;
    613   extra_opts[1].option = MHD_OPTION_NONCE_NC_SIZE;
    614   extra_opts[1].value = (intptr_t) 300;
    615   extra_opts[1].ptr_value = NULL;
    616   extra_opts[2].option = MHD_OPTION_END;
    617   extra_opts[2].value = 0;
    618   extra_opts[2].ptr_value = NULL;
    619 
    620   memset (&rp, 0, sizeof(rp));
    621   rp.port = zzuf_pick_port (TEST_PORT_OFFSET);
    622   rp.ahc = &ahc_digest;
    623   rp.ahc_cls = &param;
    624   rp.rcc = NULL;
    625   rp.rcc_cls = NULL;
    626   /* The digest headers are large, use a memory limit that can hold them. */
    627   rp.mem_limit_override = 4096;
    628   rp.sweep_profiles = 1;
    629   rp.profile_start = 0;
    630   rp.client = &client_run;
    631   rp.client_cls = &param;
    632   rp.extra_opts = extra_opts;
    633 
    634   res = zzuf_run_polling_modes (&rp);
    635   curl_global_cleanup ();
    636 
    637   if (99 == res)
    638     return use_magic_exit_codes ? 99 : 0;
    639   if (77 == res)
    640     return use_magic_exit_codes ? 77 : 0;
    641   return (0 == res) ? 0 : 1; /* 0 == pass */
    642 }