test_dauth_malformed.c (41424B)
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 * @file test_dauth_malformed.c 22 * @brief Testcase for malformed Digest "Authorization:" request headers 23 * @author Christian Grothoff 24 * 25 * The malformed headers used by this test cannot be produced with libcurl, 26 * therefore all the requests are sent over a raw socket. 27 * 28 * The test is a regression test for two remotely triggerable defects: 29 * 30 * - An unrecognised "algorithm=" token is parsed to 31 * #MHD_DIGEST_AUTH_ALGO3_INVALID, the numeric value of which is zero. The 32 * "is the client's algorithm allowed by the application" bit-mask test is 33 * passed trivially by a zero value, so an invalid algorithm used to reach 34 * digest_init_one_time(), which aborts the process with MHD_PANIC(). 35 * 36 * - The "response=" value was hex-decoded into a #MAX_DIGEST bytes long 37 * buffer on the stack, while the only length check allowed four times as 38 * many characters as the digest size, resulting in an out-of-bounds stack 39 * write of up to #MAX_DIGEST bytes. Reaching the decoder requires an 40 * otherwise complete and valid Digest handshake, hence the two-step 41 * exchange performed by this test. 42 * 43 * A number of adjacent malformed, empty, over-long and duplicated parameters 44 * is checked as well. Every sub-case must leave the daemons alive and must 45 * be answered with a regular HTTP error reply. 46 * 47 * Two daemons are used: one that issues SHA-256 challenges and one that 48 * issues the challenges with the default "any non-session" algorithm. This 49 * keeps the length of the nonces recorded by a single daemon uniform, which 50 * is what a real-world application does as well. 51 * 52 * The test honours the daemon option matrix of mhd_opt_matrix.h: when one of 53 * the MHD_TEST_* environment variables selects a profile, its connection 54 * memory limit, client discipline level, threading mode and polling backend 55 * are applied to both daemons. Without those variables nothing changes. A 56 * Digest handshake needs room for a 76 character nonce plus the realm, the 57 * username and the response, so a profile asking for less than 58 * #MIN_POOL_FOR_TEST bytes is raised to that value and the adjustment is 59 * reported. 60 */ 61 62 #include "MHD_config.h" 63 #include "platform.h" 64 #include <microhttpd.h> 65 #include <stdlib.h> 66 #include <string.h> 67 #include <stdarg.h> 68 #include <stdint.h> 69 #include <errno.h> 70 71 #ifdef HAVE_STRINGS_H 72 #include <strings.h> 73 #endif /* HAVE_STRINGS_H */ 74 75 #ifndef WINDOWS 76 #include <unistd.h> 77 #include <sys/socket.h> 78 #endif 79 80 #include "mhd_sockets.h" /* only macros used */ 81 #include "mhd_opt_matrix.h" 82 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this 83 test into a marked, classifiable test error (TESTING.md, P5). */ 84 #include "mhd_panic_tripwire.h" 85 86 /** 87 * The smallest connection memory pool this test can work with: a complete 88 * Digest "Authorization:" header plus the "WWW-Authenticate:" challenge of 89 * the reply do not fit into a smaller pool. 90 */ 91 #define MIN_POOL_FOR_TEST 4096 92 93 #ifndef MHD_STATICSTR_LEN_ 94 /** 95 * Determine length of static string / macro strings at compile time. 96 */ 97 #define MHD_STATICSTR_LEN_(macro) (sizeof(macro) / sizeof(char) - 1) 98 #endif /* ! MHD_STATICSTR_LEN_ */ 99 100 #if defined(DAUTH_SUPPORT) && defined(MHD_SHA256_SUPPORT) 101 102 /** 103 * The realm used by the test daemons. 104 */ 105 #define TEST_REALM "TestRealm" 106 107 /** 108 * The username known to the test daemons. 109 */ 110 #define TEST_USERNAME "testuser" 111 112 /** 113 * The password known to the test daemons. 114 */ 115 #define TEST_PASSWORD "testpass" 116 117 /** 118 * The URL used by all the requests. 119 */ 120 #define TEST_URL "/auth" 121 122 /** 123 * The size of the buffer used to build a request. 124 */ 125 #define REQ_BUF_SIZE 32768 126 127 /** 128 * The size of the buffer used to receive a reply. 129 */ 130 #define RPLY_BUF_SIZE 8192 131 132 /** 133 * The length of the over-long parameter values. 134 */ 135 #define LONG_PARAM_LEN 4000 136 137 /** 138 * Socket send/receive timeout in seconds. 139 */ 140 #define SOCK_TIMEOUT_SEC 5 141 142 /** 143 * The number of the "nonce"/"nc" pairs recorded by a daemon. 144 */ 145 #define NONCE_NC_SIZE 100 146 147 148 /** 149 * The port of the daemon issuing SHA-256 challenges. 150 */ 151 static uint16_t port_sha256; 152 153 /** 154 * The port of the daemon issuing "any non-session" (MD5) challenges. 155 */ 156 static uint16_t port_any; 157 158 /** 159 * The port used by the sub-case being executed. 160 */ 161 static uint16_t test_port; 162 163 /** 164 * The number of the sub-case being executed. 165 */ 166 static unsigned int case_num; 167 168 /** 169 * The number of the failed sub-cases. 170 */ 171 static unsigned int failures; 172 173 /** 174 * The 'nc' value for the next request, incremented for every request so that 175 * a "nonce"/"nc" combination is never re-used. 176 */ 177 static unsigned int nc_counter; 178 179 /** 180 * Run only the sub-cases with a name containing this string, NULL for all. 181 */ 182 static const char *case_filter; 183 184 /** 185 * Set to non-zero by the daemon if a malformed request was authenticated. 186 */ 187 static volatile int wrongly_authenticated; 188 189 /** 190 * The profile selected by the environment, NULL if the built-in daemon 191 * configuration is used (which is the case for a stock "make check"). 192 */ 193 static const struct MHD_OptMatrixProfile *test_prof; 194 195 /** 196 * The (possibly adjusted) copy of the profile @a test_prof points to. 197 */ 198 static struct MHD_OptMatrixProfile test_prof_copy; 199 200 201 _MHD_NORETURN static void 202 hard_error (const char *msg) 203 { 204 fprintf (stderr, 205 "Hard error: %s\n", 206 msg); 207 fflush (stderr); 208 exit (99); 209 } 210 211 212 /** 213 * Fill @a buf with @a len hexadecimal digits and zero-terminate it. 214 * 215 * @param[out] buf the buffer to fill, must be at least @a len + 1 bytes long 216 * @param len the number of the hexadecimal digits to generate 217 * @return the @a buf pointer 218 */ 219 static char * 220 fill_hex (char *buf, 221 size_t len) 222 { 223 static const char hex_chars[] = "0123456789abcdef"; 224 size_t i; 225 226 for (i = 0; i < len; ++i) 227 buf[i] = hex_chars[i % (MHD_STATICSTR_LEN_ (hex_chars))]; 228 buf[len] = 0; 229 return buf; 230 } 231 232 233 /** 234 * Fill @a buf with @a len copies of @a chr and zero-terminate it. 235 * 236 * @param[out] buf the buffer to fill, must be at least @a len + 1 bytes long 237 * @param chr the character to repeat 238 * @param len the number of the characters to generate 239 * @return the @a buf pointer 240 */ 241 static char * 242 fill_chr (char *buf, 243 char chr, 244 size_t len) 245 { 246 memset (buf, chr, len); 247 buf[len] = 0; 248 return buf; 249 } 250 251 252 /* * * * * * * * * * * * The daemon side * * * * * * * * * * * */ 253 254 static enum MHD_Result 255 ahc_check (void *cls, 256 struct MHD_Connection *connection, 257 const char *url, 258 const char *method, 259 const char *version, 260 const char *upload_data, 261 size_t *upload_data_size, 262 void **req_cls) 263 { 264 static int marker; 265 const enum MHD_DigestAuthMultiAlgo3 *const challenge_algo = cls; 266 struct MHD_Response *response; 267 enum MHD_DigestAuthResult check_res; 268 enum MHD_Result ret; 269 (void) url; (void) method; (void) version; (void) upload_data; 270 (void) upload_data_size; 271 272 if (&marker != *req_cls) 273 { /* The first call for the request */ 274 *req_cls = ▮ 275 return MHD_YES; 276 } 277 278 check_res = 279 MHD_digest_auth_check3 (connection, 280 TEST_REALM, 281 TEST_USERNAME, 282 TEST_PASSWORD, 283 300, 284 0, 285 MHD_DIGEST_AUTH_MULT_QOP_ANY_NON_INT, 286 MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION); 287 if (MHD_DAUTH_OK == check_res) 288 { /* None of the sub-cases must be authenticated */ 289 static const char page[] = "Authenticated"; 290 291 wrongly_authenticated = 1; 292 response = 293 MHD_create_response_from_buffer (MHD_STATICSTR_LEN_ (page), 294 (void *) page, 295 MHD_RESPMEM_PERSISTENT); 296 if (NULL == response) 297 return MHD_NO; 298 ret = MHD_queue_response (connection, 299 MHD_HTTP_OK, 300 response); 301 MHD_destroy_response (response); 302 return ret; 303 } 304 305 if (1) 306 { 307 static const char page[] = "Access denied"; 308 309 response = 310 MHD_create_response_from_buffer (MHD_STATICSTR_LEN_ (page), 311 (void *) page, 312 MHD_RESPMEM_PERSISTENT); 313 if (NULL == response) 314 return MHD_NO; 315 ret = 316 MHD_queue_auth_required_response3 (connection, 317 TEST_REALM, 318 "test-opaque", 319 NULL, 320 response, 321 (MHD_DAUTH_NONCE_STALE == check_res) ? 322 MHD_YES : MHD_NO, 323 MHD_DIGEST_AUTH_MULT_QOP_ANY_NON_INT, 324 *challenge_algo, 325 MHD_YES, 326 MHD_NO); 327 MHD_destroy_response (response); 328 } 329 return ret; 330 } 331 332 333 /* * * * * * * * * * * * The raw client side * * * * * * * * * * * */ 334 335 /** 336 * Send @a req to the daemon at #test_port and receive the complete reply. 337 * 338 * @param req the request to send, zero-terminated 339 * @param[out] rply the buffer for the reply 340 * @param rply_size the size of the @a rply buffer 341 * @return the number of the received bytes, 342 * -1 if no reply has been received 343 */ 344 static ssize_t 345 exchange (const char *req, 346 char *rply, 347 size_t rply_size) 348 { 349 MHD_socket sckt; 350 struct sockaddr_in sa; 351 struct timeval tmo; 352 size_t req_len; 353 size_t sent; 354 size_t received; 355 356 sckt = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); 357 if (MHD_INVALID_SOCKET == sckt) 358 hard_error ("socket() failed"); 359 360 tmo.tv_sec = SOCK_TIMEOUT_SEC; 361 tmo.tv_usec = 0; 362 if ( (0 != setsockopt (sckt, SOL_SOCKET, SO_RCVTIMEO, 363 (const void *) &tmo, sizeof (tmo))) || 364 (0 != setsockopt (sckt, SOL_SOCKET, SO_SNDTIMEO, 365 (const void *) &tmo, sizeof (tmo))) ) 366 hard_error ("setsockopt() failed"); 367 368 memset (&sa, 0, sizeof (sa)); 369 sa.sin_family = AF_INET; 370 sa.sin_port = htons (test_port); 371 sa.sin_addr.s_addr = htonl (INADDR_LOOPBACK); 372 if (0 != connect (sckt, (const struct sockaddr *) &sa, sizeof (sa))) 373 { 374 MHD_socket_close_chk_ (sckt); 375 return -1; 376 } 377 378 req_len = strlen (req); 379 sent = 0; 380 while (sent < req_len) 381 { 382 ssize_t res; 383 384 res = send (sckt, req + sent, req_len - sent, 0); 385 if (0 >= res) 386 break; 387 sent += (size_t) res; 388 } 389 if (sent != req_len) 390 { 391 MHD_socket_close_chk_ (sckt); 392 return -1; 393 } 394 395 received = 0; 396 while (received + 1 < rply_size) 397 { 398 ssize_t res; 399 400 res = recv (sckt, rply + received, rply_size - received - 1, 0); 401 if (0 >= res) 402 break; 403 received += (size_t) res; 404 } 405 rply[received] = 0; 406 MHD_socket_close_chk_ (sckt); 407 if (0 == received) 408 return -1; 409 return (ssize_t) received; 410 } 411 412 413 /** 414 * Extract the HTTP status code from a raw reply. 415 * 416 * @param rply the reply, zero-terminated 417 * @return the status code, zero if the reply is not a valid HTTP reply 418 */ 419 static unsigned int 420 get_status (const char *rply) 421 { 422 unsigned int status; 423 424 if (MHD_STATICSTR_LEN_ ("HTTP/1.1 000") > strlen (rply)) 425 return 0; 426 if (0 != memcmp (rply, "HTTP/1.", MHD_STATICSTR_LEN_ ("HTTP/1."))) 427 return 0; 428 status = 0; 429 if (1 != sscanf (rply + MHD_STATICSTR_LEN_ ("HTTP/1.1 "), "%3u", &status)) 430 return 0; 431 return status; 432 } 433 434 435 /** 436 * Request a challenge from the daemon at #test_port and extract the "nonce" 437 * value from it. 438 * 439 * @param[out] nonce the buffer for the "nonce" value 440 * @param nonce_size the size of the @a nonce buffer 441 * @return non-zero on success, zero on failure 442 */ 443 static int 444 get_nonce (char *nonce, 445 size_t nonce_size) 446 { 447 static const char nonce_tk[] = "nonce=\""; 448 static const char req[] = 449 "GET " TEST_URL " HTTP/1.1\r\n" 450 "Host: 127.0.0.1\r\n" 451 "Connection: close\r\n" 452 "\r\n"; 453 char rply[RPLY_BUF_SIZE]; 454 const char *start; 455 const char *end; 456 size_t len; 457 458 if (0 >= exchange (req, rply, sizeof (rply))) 459 return 0; 460 if (MHD_HTTP_UNAUTHORIZED != get_status (rply)) 461 return 0; 462 start = strstr (rply, nonce_tk); 463 if (NULL == start) 464 return 0; 465 start += MHD_STATICSTR_LEN_ (nonce_tk); 466 end = strchr (start, '"'); 467 if (NULL == end) 468 return 0; 469 len = (size_t) (end - start); 470 if (len + 1 > nonce_size) 471 return 0; 472 memcpy (nonce, start, len); 473 nonce[len] = 0; 474 return 0 != len; 475 } 476 477 478 /** 479 * Obtain a "nonce" or fail the whole test. 480 * 481 * @param[out] nonce the buffer for the "nonce" value 482 * @param nonce_size the size of the @a nonce buffer 483 */ 484 static void 485 need_nonce (char *nonce, 486 size_t nonce_size) 487 { 488 if (! get_nonce (nonce, nonce_size)) 489 hard_error ("cannot obtain a 'nonce' from the daemon"); 490 } 491 492 493 /** 494 * Run a single sub-case: send a request with the given "Authorization:" 495 * header value and check that the daemon replies with a regular HTTP error. 496 * 497 * @param name the name of the sub-case, used for reporting 498 * @param auth_fmt the printf-style format of the "Authorization:" header 499 * value, NULL to send no "Authorization:" header 500 * @param ... the arguments for the @a auth_fmt 501 */ 502 static void 503 run_case (const char *name, 504 const char *auth_fmt, 505 ...) 506 { 507 static char req[REQ_BUF_SIZE]; 508 static char auth[REQ_BUF_SIZE - 512]; 509 char rply[RPLY_BUF_SIZE]; 510 unsigned int status; 511 512 ++case_num; 513 if ( (NULL != case_filter) && 514 (NULL == strstr (name, case_filter)) ) 515 return; 516 517 /* Report the sub-case before it is executed: if the daemon aborts the 518 process, the last reported sub-case is the failing one. */ 519 fprintf (stderr, 520 "[%02u] %s\n", 521 case_num, 522 name); 523 fflush (stderr); 524 525 if (NULL != auth_fmt) 526 { 527 va_list ap; 528 529 va_start (ap, auth_fmt); 530 vsnprintf (auth, sizeof (auth), auth_fmt, ap); 531 va_end (ap); 532 snprintf (req, sizeof (req), 533 "GET " TEST_URL " HTTP/1.1\r\n" 534 "Host: 127.0.0.1\r\n" 535 "Connection: close\r\n" 536 "Authorization: %s\r\n" 537 "\r\n", 538 auth); 539 } 540 else 541 snprintf (req, sizeof (req), 542 "GET " TEST_URL " HTTP/1.1\r\n" 543 "Host: 127.0.0.1\r\n" 544 "Connection: close\r\n" 545 "\r\n"); 546 547 if (0 >= exchange (req, rply, sizeof (rply))) 548 { 549 fprintf (stderr, 550 "FAILED sub-case '%s': no reply received.\n", 551 name); 552 fflush (stderr); 553 ++failures; 554 return; 555 } 556 status = get_status (rply); 557 /* Any regular HTTP error reply is fine: the point of the test is that the 558 daemon neither crashes nor accepts the malformed credentials. */ 559 if ( (MHD_HTTP_UNAUTHORIZED != status) && 560 (MHD_HTTP_BAD_REQUEST != status) && 561 (MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE != status) ) 562 { 563 fprintf (stderr, 564 "FAILED sub-case '%s': unexpected HTTP status %u.\n", 565 name, 566 status); 567 fflush (stderr); 568 ++failures; 569 return; 570 } 571 if (0 != wrongly_authenticated) 572 { 573 fprintf (stderr, 574 "FAILED sub-case '%s': the malformed request was " 575 "authenticated.\n", 576 name); 577 fflush (stderr); 578 wrongly_authenticated = 0; 579 ++failures; 580 } 581 } 582 583 584 /** 585 * Build the common part of an otherwise valid Digest "Authorization:" header. 586 * 587 * @param[out] buf the buffer to fill 588 * @param buf_size the size of the @a buf 589 * @param nonce the "nonce" value received from the daemon 590 */ 591 static void 592 make_valid_params (char *buf, 593 size_t buf_size, 594 const char *nonce) 595 { 596 snprintf (buf, buf_size, 597 "username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 598 "nonce=\"%s\", uri=\"" TEST_URL "\", qop=auth, nc=%08x, " 599 "cnonce=\"0123456789abcdef\"", 600 nonce, 601 ++nc_counter); 602 } 603 604 605 /* * * * * * * * * * * * The sub-cases * * * * * * * * * * * */ 606 607 /** 608 * Sub-cases for the missing #MHD_DIGEST_AUTH_ALGO3_INVALID check. 609 * 610 * An unknown "algorithm=" token is parsed to 611 * #MHD_DIGEST_AUTH_ALGO3_INVALID, which has the numeric value of zero and 612 * therefore satisfies the "allowed algorithms" bit-mask test. Without the 613 * explicit check for the invalid value the request reaches 614 * digest_init_one_time() and aborts the process with MHD_PANIC(). 615 * 616 * The algorithm is checked before anything else, so most of the sub-cases 617 * need no other parameter at all. 618 */ 619 static void 620 test_invalid_algorithm (void) 621 { 622 char nonce[256]; 623 char params[512]; 624 char resp[128]; 625 626 fill_hex (resp, 64); 627 628 test_port = port_any; 629 630 run_case ("bug-c/algo-bogus-alone", 631 "Digest algorithm=BOGUS"); 632 run_case ("bug-c/algo-md5-x", 633 "Digest algorithm=MD5-x"); 634 run_case ("bug-c/algo-sha512", 635 "Digest algorithm=SHA-512"); 636 run_case ("bug-c/algo-sha1", 637 "Digest algorithm=SHA1"); 638 run_case ("bug-c/algo-sha256-typo", 639 "Digest algorithm=SHA256"); 640 run_case ("bug-c/algo-empty-quotes", 641 "Digest algorithm=\"\""); 642 run_case ("bug-c/algo-empty-token", 643 "Digest algorithm="); 644 run_case ("bug-c/algo-empty-token-comma", 645 "Digest algorithm=, nonce=\"deadbeef\""); 646 run_case ("bug-c/algo-quoted-bogus", 647 "Digest algorithm=\"BOGUS\""); 648 run_case ("bug-c/algo-space", 649 "Digest algorithm = BOGUS"); 650 /* A backslash inside the value marks the parameter as "quoted", which 651 selects the second, separate branch of the algorithm token parser. */ 652 run_case ("bug-c/algo-escaped-bogus", 653 "Digest algorithm=\"\\BOGUS\""); 654 run_case ("bug-c/algo-escaped-md5-x", 655 "Digest algorithm=\"MD5\\-x\""); 656 /* The "-sess" variants are valid tokens, but they are rejected by the 657 bit-mask test as they are not allowed by the application. */ 658 run_case ("bug-c/algo-md5-sess", 659 "Digest algorithm=MD5-sess"); 660 run_case ("bug-c/algo-sha256-sess", 661 "Digest algorithm=SHA-256-sess"); 662 run_case ("bug-c/algo-sha512-256-sess", 663 "Digest algorithm=SHA-512-256-sess"); 664 run_case ("bug-c/algo-escaped-md5-sess", 665 "Digest algorithm=\"MD5\\-sess\""); 666 run_case ("bug-c/algo-escaped-sha512-256", 667 "Digest algorithm=\"SHA\\-512-256\""); 668 /* The last occurrence of a duplicated parameter is the effective one. */ 669 run_case ("bug-c/algo-duplicated", 670 "Digest algorithm=MD5, algorithm=BOGUS"); 671 run_case ("bug-c/algo-duplicated-rev", 672 "Digest algorithm=BOGUS, algorithm=MD5"); 673 674 /* The same, but with all the other parameters present and valid, so that 675 nothing else can short-circuit the check. */ 676 test_port = port_sha256; 677 678 need_nonce (nonce, sizeof (nonce)); 679 make_valid_params (params, sizeof (params), nonce); 680 run_case ("bug-c/algo-bogus-full", 681 "Digest %s, algorithm=BOGUS, response=\"%s\"", 682 params, 683 resp); 684 685 need_nonce (nonce, sizeof (nonce)); 686 make_valid_params (params, sizeof (params), nonce); 687 run_case ("bug-c/algo-empty-full", 688 "Digest %s, algorithm=\"\", response=\"%s\"", 689 params, 690 resp); 691 692 need_nonce (nonce, sizeof (nonce)); 693 make_valid_params (params, sizeof (params), nonce); 694 run_case ("bug-c/algo-escaped-bogus-full", 695 "Digest %s, algorithm=\"\\BOGUS\", response=\"%s\"", 696 params, 697 resp); 698 } 699 700 701 /** 702 * Sub-cases for the out-of-bounds stack write in the "response=" hex decoder. 703 * 704 * The decoder writes (len + 1) / 2 bytes into a #MAX_DIGEST (32) bytes long 705 * stack buffer, while the length sanity check accepts up to digest_size * 4 706 * (128) characters. All the other parameters must be valid to reach the 707 * decoder, therefore a fresh "nonce" is obtained for every sub-case. 708 */ 709 static void 710 test_overlong_response (void) 711 { 712 /* 128 and 127 characters produce the maximum overflow of 32 bytes, 66 and 713 65 characters produce the minimum overflow of a single byte, 64 714 characters fit exactly and must not overflow. */ 715 static const size_t resp_lengths[] = { 128, 127, 100, 66, 65, 64 }; 716 char nonce[256]; 717 char params[512]; 718 char resp[512]; 719 char name[128]; 720 size_t i; 721 722 test_port = port_sha256; 723 724 for (i = 0; i < sizeof (resp_lengths) / sizeof (resp_lengths[0]); ++i) 725 { 726 const size_t len = resp_lengths[i]; 727 728 /* The value without the quotation marks */ 729 need_nonce (nonce, sizeof (nonce)); 730 make_valid_params (params, sizeof (params), nonce); 731 snprintf (name, sizeof (name), 732 "bug-d/sha256-response-%u-bare", 733 (unsigned) len); 734 run_case (name, 735 "Digest %s, algorithm=SHA-256, response=%s", 736 params, 737 fill_hex (resp, len)); 738 739 /* The same value in the quotation marks */ 740 need_nonce (nonce, sizeof (nonce)); 741 make_valid_params (params, sizeof (params), nonce); 742 snprintf (name, sizeof (name), 743 "bug-d/sha256-response-%u-quoted", 744 (unsigned) len); 745 run_case (name, 746 "Digest %s, algorithm=SHA-256, response=\"%s\"", 747 params, 748 fill_hex (resp, len)); 749 } 750 751 /* A backslash marks the value as "quoted", so it is unquoted into the 752 temporal buffer first and only then decoded. */ 753 need_nonce (nonce, sizeof (nonce)); 754 make_valid_params (params, sizeof (params), nonce); 755 run_case ("bug-d/sha256-response-127-escaped", 756 "Digest %s, algorithm=SHA-256, response=\"\\%s\"", 757 params, 758 fill_hex (resp, 127)); 759 760 /* The SHA-512/256 algorithm uses the same digest size as SHA-256 and the 761 same nonce length, so it reaches the decoder as well. */ 762 #ifdef MHD_SHA512_256_SUPPORT 763 need_nonce (nonce, sizeof (nonce)); 764 make_valid_params (params, sizeof (params), nonce); 765 run_case ("bug-d/sha512-256-response-128-bare", 766 "Digest %s, algorithm=SHA-512-256, response=%s", 767 params, 768 fill_hex (resp, 128)); 769 #endif /* MHD_SHA512_256_SUPPORT */ 770 771 /* The RFC2069 mode: no "qop", no "nc" and no "cnonce". */ 772 need_nonce (nonce, sizeof (nonce)); 773 run_case ("bug-d/sha256-response-128-rfc2069", 774 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 775 "nonce=\"%s\", uri=\"" TEST_URL "\", algorithm=SHA-256, " 776 "response=%s", 777 nonce, 778 fill_hex (resp, 128)); 779 780 /* The value is not a valid hexadecimal string, the decoder must bail out 781 before writing anything. */ 782 need_nonce (nonce, sizeof (nonce)); 783 make_valid_params (params, sizeof (params), nonce); 784 run_case ("bug-d/sha256-response-128-non-hex", 785 "Digest %s, algorithm=SHA-256, response=%s", 786 params, 787 fill_chr (resp, 'z', 128)); 788 789 /* Only the last character is not a hexadecimal digit, so the decoder 790 writes 63 bytes before it detects the error. */ 791 need_nonce (nonce, sizeof (nonce)); 792 make_valid_params (params, sizeof (params), nonce); 793 fill_hex (resp, 128); 794 resp[127] = 'z'; 795 run_case ("bug-d/sha256-response-128-tail-non-hex", 796 "Digest %s, algorithm=SHA-256, response=%s", 797 params, 798 resp); 799 800 /* Duplicated parameter: the last (over-long) value is the effective one. */ 801 need_nonce (nonce, sizeof (nonce)); 802 make_valid_params (params, sizeof (params), nonce); 803 run_case ("bug-d/sha256-response-duplicated", 804 "Digest %s, algorithm=SHA-256, response=\"00112233\", response=%s", 805 params, 806 fill_hex (resp, 128)); 807 808 #ifdef MHD_MD5_SUPPORT 809 /* The MD5 variant: the decoder writes 32 bytes, which overflows the buffer 810 only in an MD5-only build, where MAX_DIGEST is 16. */ 811 test_port = port_any; 812 813 need_nonce (nonce, sizeof (nonce)); 814 make_valid_params (params, sizeof (params), nonce); 815 run_case ("bug-d/md5-response-64-bare", 816 "Digest %s, algorithm=MD5, response=%s", 817 params, 818 fill_hex (resp, 64)); 819 820 need_nonce (nonce, sizeof (nonce)); 821 make_valid_params (params, sizeof (params), nonce); 822 run_case ("bug-d/md5-response-63-bare", 823 "Digest %s, algorithm=MD5, response=%s", 824 params, 825 fill_hex (resp, 63)); 826 827 need_nonce (nonce, sizeof (nonce)); 828 make_valid_params (params, sizeof (params), nonce); 829 run_case ("bug-d/md5-response-34-quoted", 830 "Digest %s, algorithm=MD5, response=\"%s\"", 831 params, 832 fill_hex (resp, 34)); 833 #endif /* MHD_MD5_SUPPORT */ 834 } 835 836 837 /** 838 * Sub-cases for the adjacent malformed, empty, over-long and duplicated 839 * parameters. 840 */ 841 static void 842 test_adjacent (void) 843 { 844 static char big[LONG_PARAM_LEN + 1]; 845 char nonce[256]; 846 char resp[128]; 847 848 fill_hex (resp, 64); 849 850 /* No or unusable "Authorization:" header. These never reach the 851 "nonce" handling, the daemon used does not matter. */ 852 test_port = port_any; 853 854 run_case ("adj/no-auth-header", NULL); 855 run_case ("adj/scheme-only", "Digest"); 856 run_case ("adj/scheme-and-space", "Digest "); 857 run_case ("adj/only-commas", "Digest ,,,,"); 858 run_case ("adj/basic-scheme", "Basic dGVzdHVzZXI6dGVzdHBhc3M="); 859 run_case ("adj/unknown-scheme", "Bogus xyz"); 860 run_case ("adj/unterminated-quote", 861 "Digest username=\"" TEST_USERNAME); 862 run_case ("adj/unterminated-quote-escape", 863 "Digest username=\"" TEST_USERNAME "\\\""); 864 run_case ("adj/no-equal-sign", "Digest username"); 865 run_case ("adj/leading-equal-sign", "Digest =value"); 866 run_case ("adj/semicolon-in-value", "Digest username=a;b"); 867 run_case ("adj/garbage-after-value", "Digest username=\"a\" garbage"); 868 run_case ("adj/unknown-parameter", 869 "Digest bogus=\"x\", username=\"" TEST_USERNAME "\""); 870 871 /* Over-long parameter values */ 872 run_case ("adj/username-too-long", 873 "Digest username=\"%s\", realm=\"" TEST_REALM "\"", 874 fill_chr (big, 'u', LONG_PARAM_LEN)); 875 run_case ("adj/realm-too-long", 876 "Digest username=\"" TEST_USERNAME "\", realm=\"%s\"", 877 fill_chr (big, 'r', LONG_PARAM_LEN)); 878 run_case ("adj/uri-too-long", 879 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 880 "uri=\"%s\"", 881 fill_chr (big, 'p', LONG_PARAM_LEN)); 882 run_case ("adj/cnonce-too-long", 883 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 884 "qop=auth, nc=00000001, cnonce=\"%s\"", 885 fill_chr (big, 'c', LONG_PARAM_LEN)); 886 run_case ("adj/nc-too-long", 887 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 888 "qop=auth, nc=%s, cnonce=\"abc\"", 889 fill_hex (big, LONG_PARAM_LEN)); 890 run_case ("adj/opaque-too-long", 891 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 892 "opaque=\"%s\"", 893 fill_chr (big, 'o', LONG_PARAM_LEN)); 894 run_case ("adj/nonce-too-long", 895 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 896 "nonce=\"%s\"", 897 fill_hex (big, LONG_PARAM_LEN)); 898 run_case ("adj/response-too-long", 899 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 900 "response=\"%s\"", 901 fill_hex (big, LONG_PARAM_LEN)); 902 run_case ("adj/username-ext-too-long", 903 "Digest username*=UTF-8''%s, realm=\"" TEST_REALM "\"", 904 fill_chr (big, 'e', LONG_PARAM_LEN)); 905 906 /* The extended ("username*") notation */ 907 run_case ("adj/username-ext-truncated", 908 "Digest username*=U, realm=\"" TEST_REALM "\""); 909 run_case ("adj/username-ext-no-lang", 910 "Digest username*=UTF-8', realm=\"" TEST_REALM "\""); 911 run_case ("adj/username-ext-empty", 912 "Digest username*=UTF-8'', realm=\"" TEST_REALM "\""); 913 run_case ("adj/username-ext-broken-pct", 914 "Digest username*=UTF-8''%%, realm=\"" TEST_REALM "\""); 915 run_case ("adj/username-ext-broken-pct2", 916 "Digest username*=UTF-8''%%z, realm=\"" TEST_REALM "\""); 917 run_case ("adj/username-ext-broken-pct3", 918 "Digest username*=UTF-8''%%zz, realm=\"" TEST_REALM "\""); 919 run_case ("adj/username-ext-trailing-pct", 920 "Digest username*=UTF-8''abc%%, realm=\"" TEST_REALM "\""); 921 run_case ("adj/username-ext-bad-charset", 922 "Digest username*=ISO-8859-1''abc, realm=\"" TEST_REALM "\""); 923 run_case ("adj/username-ext-quoted", 924 "Digest username*=\"UTF-8''\\abc\", realm=\"" TEST_REALM "\""); 925 run_case ("adj/username-ext-and-plain", 926 "Digest username=\"" TEST_USERNAME "\", " 927 "username*=UTF-8''" TEST_USERNAME ", realm=\"" TEST_REALM "\""); 928 929 /* The "userhash" handling */ 930 run_case ("adj/userhash-username-too-short", 931 "Digest userhash=true, username=\"abcd\", " 932 "realm=\"" TEST_REALM "\""); 933 run_case ("adj/userhash-username-empty", 934 "Digest userhash=true, username=\"\", " 935 "realm=\"" TEST_REALM "\""); 936 run_case ("adj/userhash-username-too-long", 937 "Digest userhash=true, username=\"%s\", " 938 "realm=\"" TEST_REALM "\"", 939 fill_hex (big, 200)); 940 run_case ("adj/userhash-username-ext", 941 "Digest userhash=true, username*=UTF-8''abc, " 942 "realm=\"" TEST_REALM "\""); 943 run_case ("adj/userhash-no-username", 944 "Digest userhash=true, realm=\"" TEST_REALM "\""); 945 946 /* The "nc" handling */ 947 run_case ("adj/nc-zero", 948 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 949 "qop=auth, nc=00000000, cnonce=\"abc\""); 950 run_case ("adj/nc-non-hex", 951 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 952 "qop=auth, nc=zzzzzzzz, cnonce=\"abc\""); 953 run_case ("adj/nc-empty", 954 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 955 "qop=auth, nc=, cnonce=\"abc\""); 956 run_case ("adj/nc-overflow", 957 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 958 "qop=auth, nc=ffffffffffffffffffffffff, cnonce=\"abc\""); 959 run_case ("adj/qop-auth-no-nc", 960 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 961 "qop=auth, cnonce=\"abc\""); 962 run_case ("adj/qop-auth-no-cnonce", 963 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 964 "qop=auth, nc=00000001"); 965 run_case ("adj/cnonce-empty", 966 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 967 "qop=auth, nc=00000001, cnonce=\"\""); 968 969 /* Malformed "nonce" values. These are rejected by the length check 970 before the "nonce"/"nc" map is consulted. */ 971 run_case ("adj/nonce-short", 972 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 973 "nonce=\"deadbeef\", uri=\"" TEST_URL "\", qop=auth, " 974 "nc=00000001, cnonce=\"abc\", response=\"00112233\""); 975 run_case ("adj/nonce-empty", 976 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 977 "nonce=\"\", uri=\"" TEST_URL "\", qop=auth, " 978 "nc=00000001, cnonce=\"abc\", response=\"00112233\""); 979 980 /* Duplicated parameters */ 981 run_case ("adj/duplicated-username", 982 "Digest username=\"a\", username=\"" TEST_USERNAME "\", " 983 "realm=\"" TEST_REALM "\""); 984 run_case ("adj/duplicated-realm", 985 "Digest username=\"" TEST_USERNAME "\", realm=\"x\", " 986 "realm=\"" TEST_REALM "\""); 987 run_case ("adj/duplicated-userhash", 988 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 989 "userhash=false, userhash=true"); 990 991 /* The sub-cases below use the SHA-256 algorithm and therefore need the 992 daemon that issues the SHA-256 (76 characters long) nonces. */ 993 test_port = port_sha256; 994 995 run_case ("adj/nonce-not-generated-by-mhd", 996 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 997 "nonce=\"%s\", uri=\"" TEST_URL "\", algorithm=SHA-256, " 998 "qop=auth, nc=00000001, cnonce=\"abc\", response=\"%s\"", 999 fill_hex (big, 76), 1000 resp); 1001 1002 need_nonce (nonce, sizeof (nonce)); 1003 run_case ("adj/uri-mismatch", 1004 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 1005 "nonce=\"%s\", uri=\"/other\", algorithm=SHA-256, qop=auth, " 1006 "nc=%08x, cnonce=\"abc\", response=\"%s\"", 1007 nonce, 1008 ++nc_counter, 1009 resp); 1010 1011 need_nonce (nonce, sizeof (nonce)); 1012 run_case ("adj/uri-empty", 1013 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 1014 "nonce=\"%s\", uri=\"\", algorithm=SHA-256, qop=auth, " 1015 "nc=%08x, cnonce=\"abc\", response=\"%s\"", 1016 nonce, 1017 ++nc_counter, 1018 resp); 1019 1020 need_nonce (nonce, sizeof (nonce)); 1021 run_case ("adj/uri-with-args", 1022 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 1023 "nonce=\"%s\", uri=\"" TEST_URL "?a=b\", algorithm=SHA-256, " 1024 "qop=auth, nc=%08x, cnonce=\"abc\", response=\"%s\"", 1025 nonce, 1026 ++nc_counter, 1027 resp); 1028 1029 need_nonce (nonce, sizeof (nonce)); 1030 run_case ("adj/duplicated-nonce", 1031 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 1032 "nonce=\"deadbeef\", nonce=\"%s\", uri=\"" TEST_URL "\", " 1033 "algorithm=SHA-256, qop=auth, nc=%08x, cnonce=\"abc\", " 1034 "response=\"%s\"", 1035 nonce, 1036 ++nc_counter, 1037 resp); 1038 1039 need_nonce (nonce, sizeof (nonce)); 1040 run_case ("adj/qop-auth-int", 1041 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 1042 "nonce=\"%s\", uri=\"" TEST_URL "\", algorithm=SHA-256, " 1043 "qop=auth-int, nc=%08x, cnonce=\"abc\", response=\"%s\"", 1044 nonce, 1045 ++nc_counter, 1046 resp); 1047 1048 need_nonce (nonce, sizeof (nonce)); 1049 run_case ("adj/qop-bogus", 1050 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 1051 "nonce=\"%s\", uri=\"" TEST_URL "\", algorithm=SHA-256, " 1052 "qop=BOGUS, nc=%08x, cnonce=\"abc\", response=\"%s\"", 1053 nonce, 1054 ++nc_counter, 1055 resp); 1056 1057 need_nonce (nonce, sizeof (nonce)); 1058 run_case ("adj/userhash-64-non-hex", 1059 "Digest userhash=true, username=\"%s\", realm=\"" TEST_REALM "\", " 1060 "nonce=\"%s\", uri=\"" TEST_URL "\", algorithm=SHA-256, qop=auth, " 1061 "nc=%08x, cnonce=\"abc\", response=\"%s\"", 1062 fill_chr (big, 'z', 64), 1063 nonce, 1064 ++nc_counter, 1065 resp); 1066 1067 need_nonce (nonce, sizeof (nonce)); 1068 run_case ("adj/userhash-128-hex", 1069 "Digest userhash=true, username=\"%s\", realm=\"" TEST_REALM "\", " 1070 "nonce=\"%s\", uri=\"" TEST_URL "\", algorithm=SHA-256, qop=auth, " 1071 "nc=%08x, cnonce=\"abc\", response=\"%s\"", 1072 fill_hex (big, 128), 1073 nonce, 1074 ++nc_counter, 1075 resp); 1076 1077 /* An empty "qop" value ("qop=" or "qop=\"\"") is parsed to 1078 #MHD_DIGEST_AUTH_QOP_INVALID, whose numeric value is zero, so it used to 1079 pass the "allowed QOP" bit-mask test in exactly the same way an unknown 1080 "algorithm" token did before commit bd49ce93, and the empty value then 1081 reached get_unquoted_param(), which asserts that the value is not empty. 1082 Fixed in commit 3b898eae; these two sub-cases are its regression test. */ 1083 need_nonce (nonce, sizeof (nonce)); 1084 run_case ("adj/qop-empty", 1085 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 1086 "nonce=\"%s\", uri=\"" TEST_URL "\", algorithm=SHA-256, qop=, " 1087 "nc=%08x, cnonce=\"abc\", response=\"%s\"", 1088 nonce, 1089 ++nc_counter, 1090 resp); 1091 1092 need_nonce (nonce, sizeof (nonce)); 1093 run_case ("adj/qop-empty-quotes", 1094 "Digest username=\"" TEST_USERNAME "\", realm=\"" TEST_REALM "\", " 1095 "nonce=\"%s\", uri=\"" TEST_URL "\", algorithm=SHA-256, qop=\"\", " 1096 "nc=%08x, cnonce=\"abc\", response=\"%s\"", 1097 nonce, 1098 ++nc_counter, 1099 resp); 1100 } 1101 1102 1103 /** 1104 * Start a test daemon. 1105 * 1106 * @param challenge_algo the pointer to the algorithm used for the challenges 1107 * @param[out] pport the pointer to store the port the daemon listens on 1108 * @return the daemon 1109 */ 1110 static struct MHD_Daemon * 1111 start_test_daemon (const enum MHD_DigestAuthMultiAlgo3 *challenge_algo, 1112 uint16_t *pport) 1113 { 1114 struct MHD_Daemon *daemon; 1115 const union MHD_DaemonInfo *dinfo; 1116 struct MHD_OptMatrixProfile prof; 1117 struct MHD_OptionItem ops[8]; 1118 unsigned int flags; 1119 1120 if (NULL != test_prof) 1121 prof = *test_prof; 1122 else 1123 { 1124 memset (&prof, 0, sizeof (prof)); 1125 prof.name = "built-in"; 1126 prof.threading = MHD_OPT_MATRIX_THR_INTERNAL; 1127 prof.poll_backend = MHD_OPT_MATRIX_POLL_SELECT; 1128 } 1129 if (0 == mhd_opt_matrix_fill_options (&prof, ops, 1130 (unsigned int) (sizeof (ops) 1131 / sizeof (ops[0])))) 1132 hard_error ("The daemon option array is too small"); 1133 /* The client of this test is a plain blocking socket client, so external 1134 polling is served with an internal polling thread instead. */ 1135 flags = mhd_opt_matrix_daemon_flags (&prof, MHD_USE_ERROR_LOG, 0); 1136 1137 daemon = MHD_start_daemon (flags, 1138 0, 1139 NULL, NULL, 1140 &ahc_check, (void *) challenge_algo, 1141 MHD_OPTION_ARRAY, ops, 1142 MHD_OPTION_CONNECTION_TIMEOUT, 1143 (unsigned int) 10, 1144 MHD_OPTION_NONCE_NC_SIZE, 1145 (unsigned int) NONCE_NC_SIZE, 1146 MHD_OPTION_END); 1147 if (NULL == daemon) 1148 hard_error ("MHD_start_daemon() failed"); 1149 dinfo = MHD_get_daemon_info (daemon, MHD_DAEMON_INFO_BIND_PORT); 1150 if ( (NULL == dinfo) || (0 == dinfo->port) ) 1151 { 1152 MHD_stop_daemon (daemon); 1153 hard_error ("MHD_get_daemon_info() failed"); 1154 } 1155 *pport = dinfo->port; 1156 return daemon; 1157 } 1158 1159 1160 int 1161 main (int argc, char *const *argv) 1162 { 1163 static const enum MHD_DigestAuthMultiAlgo3 algo_sha256 = 1164 MHD_DIGEST_AUTH_MULT_ALGO3_SHA256; 1165 static const enum MHD_DigestAuthMultiAlgo3 algo_any = 1166 MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION; 1167 struct MHD_Daemon *daemon_sha256; 1168 struct MHD_Daemon *daemon_any; 1169 char nonce[256]; 1170 1171 if (1 < argc) 1172 case_filter = argv[1]; 1173 1174 mhd_opt_matrix_print_notice ("test_dauth_malformed"); 1175 test_prof = mhd_opt_matrix_from_env (); 1176 if (NULL != test_prof) 1177 { 1178 char desc[256]; 1179 1180 test_prof_copy = *test_prof; 1181 test_prof = &test_prof_copy; 1182 if (! mhd_opt_matrix_profile_supported (test_prof)) 1183 { 1184 printf ("test_dauth_malformed: the selected profile %s is not " 1185 "supported by this build, the test is skipped.\n", 1186 mhd_opt_matrix_describe (test_prof, desc, sizeof (desc))); 1187 return 77; 1188 } 1189 if (mhd_opt_matrix_raise_mem_limit (&test_prof_copy, MIN_POOL_FOR_TEST)) 1190 printf ("test_dauth_malformed: the connection memory limit of the " 1191 "profile was raised to %u, the smallest pool this test can " 1192 "work with.\n", (unsigned) MIN_POOL_FOR_TEST); 1193 } 1194 1195 daemon_sha256 = start_test_daemon (&algo_sha256, &port_sha256); 1196 daemon_any = start_test_daemon (&algo_any, &port_any); 1197 1198 test_invalid_algorithm (); 1199 test_overlong_response (); 1200 test_adjacent (); 1201 1202 /* Both daemons must still be alive and functional. */ 1203 test_port = port_sha256; 1204 if (! get_nonce (nonce, sizeof (nonce))) 1205 { 1206 fprintf (stderr, 1207 "FAILED: the SHA-256 daemon does not respond after the test.\n"); 1208 ++failures; 1209 } 1210 test_port = port_any; 1211 if (! get_nonce (nonce, sizeof (nonce))) 1212 { 1213 fprintf (stderr, 1214 "FAILED: the default daemon does not respond after the test.\n"); 1215 ++failures; 1216 } 1217 1218 MHD_stop_daemon (daemon_any); 1219 MHD_stop_daemon (daemon_sha256); 1220 1221 if (0 != failures) 1222 { 1223 fprintf (stderr, 1224 "Test FAILED: %u of %u sub-cases failed.\n", 1225 failures, 1226 case_num); 1227 return 1; 1228 } 1229 printf ("Test PASSED: %u sub-cases.\n", 1230 case_num); 1231 return 0; 1232 } 1233 1234 1235 #else /* ! DAUTH_SUPPORT || ! MHD_SHA256_SUPPORT */ 1236 1237 1238 int 1239 main (void) 1240 { 1241 fprintf (stderr, 1242 "Digest Auth or SHA-256 support is not enabled in this MHD build, " 1243 "the test is skipped.\n"); 1244 return 77; 1245 } 1246 1247 1248 #endif /* ! DAUTH_SUPPORT || ! MHD_SHA256_SUPPORT */