test_digestauth.c (17206B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2010 Christian Grothoff 4 Copyright (C) 2016-2022 Evgeny Grin (Karlson2k) 5 6 libmicrohttpd is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published 8 by the Free Software Foundation; either version 2, or (at your 9 option) any later version. 10 11 libmicrohttpd is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with libmicrohttpd; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 Boston, MA 02110-1301, USA. 20 */ 21 22 /** 23 * @file daemontest_digestauth.c 24 * @brief Testcase for libmicrohttpd Digest Auth 25 * @author Amr Ali 26 * @author Karlson2k (Evgeny Grin) 27 */ 28 29 #include "mhd_options.h" 30 #include "platform.h" 31 #include <curl/curl.h> 32 #include <microhttpd.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <time.h> 36 #include <errno.h> 37 38 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this 39 test into a marked, classifiable test error (TESTING.md, P5). */ 40 #include "mhd_panic_tripwire.h" 41 42 #if defined(MHD_HTTPS_REQUIRE_GCRYPT) && \ 43 (defined(MHD_SHA256_TLSLIB) || defined(MHD_MD5_TLSLIB)) 44 #define NEED_GCRYP_INIT 1 45 #include <gcrypt.h> 46 #endif /* MHD_HTTPS_REQUIRE_GCRYPT && (MHD_SHA256_TLSLIB || MHD_MD5_TLSLIB) */ 47 48 #ifndef WINDOWS 49 #include <sys/socket.h> 50 #include <unistd.h> 51 #else 52 #include <wincrypt.h> 53 #endif 54 55 #ifndef CURL_VERSION_BITS 56 #define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|(z)) 57 #endif /* ! CURL_VERSION_BITS */ 58 #ifndef CURL_AT_LEAST_VERSION 59 #define CURL_AT_LEAST_VERSION(x,y,z) \ 60 (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z)) 61 #endif /* ! CURL_AT_LEAST_VERSION */ 62 63 #ifndef _MHD_INSTRMACRO 64 /* Quoted macro parameter */ 65 #define _MHD_INSTRMACRO(a) #a 66 #endif /* ! _MHD_INSTRMACRO */ 67 #ifndef _MHD_STRMACRO 68 /* Quoted expanded macro parameter */ 69 #define _MHD_STRMACRO(a) _MHD_INSTRMACRO (a) 70 #endif /* ! _MHD_STRMACRO */ 71 72 #if defined(HAVE___FUNC__) 73 #define externalErrorExit(ignore) \ 74 _externalErrorExit_func(NULL, __func__, __LINE__) 75 #define externalErrorExitDesc(errDesc) \ 76 _externalErrorExit_func(errDesc, __func__, __LINE__) 77 #define libcurlErrorExit(ignore) \ 78 _libcurlErrorExit_func(NULL, __func__, __LINE__) 79 #define libcurlErrorExitDesc(errDesc) \ 80 _libcurlErrorExit_func(errDesc, __func__, __LINE__) 81 #define mhdErrorExit(ignore) \ 82 _mhdErrorExit_func(NULL, __func__, __LINE__) 83 #define mhdErrorExitDesc(errDesc) \ 84 _mhdErrorExit_func(errDesc, __func__, __LINE__) 85 #define checkCURLE_OK(libcurlcall) \ 86 _checkCURLE_OK_func((libcurlcall), _MHD_STRMACRO(libcurlcall), \ 87 __func__, __LINE__) 88 #elif defined(HAVE___FUNCTION__) 89 #define externalErrorExit(ignore) \ 90 _externalErrorExit_func(NULL, __FUNCTION__, __LINE__) 91 #define externalErrorExitDesc(errDesc) \ 92 _externalErrorExit_func(errDesc, __FUNCTION__, __LINE__) 93 #define libcurlErrorExit(ignore) \ 94 _libcurlErrorExit_func(NULL, __FUNCTION__, __LINE__) 95 #define libcurlErrorExitDesc(errDesc) \ 96 _libcurlErrorExit_func(errDesc, __FUNCTION__, __LINE__) 97 #define mhdErrorExit(ignore) \ 98 _mhdErrorExit_func(NULL, __FUNCTION__, __LINE__) 99 #define mhdErrorExitDesc(errDesc) \ 100 _mhdErrorExit_func(errDesc, __FUNCTION__, __LINE__) 101 #define checkCURLE_OK(libcurlcall) \ 102 _checkCURLE_OK_func((libcurlcall), _MHD_STRMACRO(libcurlcall), \ 103 __FUNCTION__, __LINE__) 104 #else 105 #define externalErrorExit(ignore) _externalErrorExit_func(NULL, NULL, __LINE__) 106 #define externalErrorExitDesc(errDesc) \ 107 _externalErrorExit_func(errDesc, NULL, __LINE__) 108 #define libcurlErrorExit(ignore) _libcurlErrorExit_func(NULL, NULL, __LINE__) 109 #define libcurlErrorExitDesc(errDesc) \ 110 _libcurlErrorExit_func(errDesc, NULL, __LINE__) 111 #define mhdErrorExit(ignore) _mhdErrorExit_func(NULL, NULL, __LINE__) 112 #define mhdErrorExitDesc(errDesc) _mhdErrorExit_func(errDesc, NULL, __LINE__) 113 #define checkCURLE_OK(libcurlcall) \ 114 _checkCURLE_OK_func((libcurlcall), _MHD_STRMACRO(libcurlcall), NULL, __LINE__) 115 #endif 116 117 118 _MHD_NORETURN static void 119 _externalErrorExit_func (const char *errDesc, const char *funcName, int lineNum) 120 { 121 fflush (stdout); 122 if ((NULL != errDesc) && (0 != errDesc[0])) 123 fprintf (stderr, "%s", errDesc); 124 else 125 fprintf (stderr, "System or external library call failed"); 126 if ((NULL != funcName) && (0 != funcName[0])) 127 fprintf (stderr, " in %s", funcName); 128 if (0 < lineNum) 129 fprintf (stderr, " at line %d", lineNum); 130 131 fprintf (stderr, ".\nLast errno value: %d (%s)\n", (int) errno, 132 strerror (errno)); 133 #ifdef MHD_WINSOCK_SOCKETS 134 fprintf (stderr, "WSAGetLastError() value: %d\n", (int) WSAGetLastError ()); 135 #endif /* MHD_WINSOCK_SOCKETS */ 136 fflush (stderr); 137 exit (99); 138 } 139 140 141 static char libcurl_errbuf[CURL_ERROR_SIZE] = ""; 142 143 _MHD_NORETURN static void 144 _libcurlErrorExit_func (const char *errDesc, const char *funcName, int lineNum) 145 { 146 fflush (stdout); 147 if ((NULL != errDesc) && (0 != errDesc[0])) 148 fprintf (stderr, "%s", errDesc); 149 else 150 fprintf (stderr, "CURL library call failed"); 151 if ((NULL != funcName) && (0 != funcName[0])) 152 fprintf (stderr, " in %s", funcName); 153 if (0 < lineNum) 154 fprintf (stderr, " at line %d", lineNum); 155 156 fprintf (stderr, ".\nLast errno value: %d (%s)\n", (int) errno, 157 strerror (errno)); 158 #ifdef MHD_WINSOCK_SOCKETS 159 fprintf (stderr, "WSAGetLastError() value: %d\n", (int) WSAGetLastError ()); 160 #endif /* MHD_WINSOCK_SOCKETS */ 161 if (0 != libcurl_errbuf[0]) 162 fprintf (stderr, "Last libcurl error description: %s\n", libcurl_errbuf); 163 164 fflush (stderr); 165 exit (99); 166 } 167 168 169 _MHD_NORETURN static void 170 _mhdErrorExit_func (const char *errDesc, const char *funcName, int lineNum) 171 { 172 fflush (stdout); 173 if ((NULL != errDesc) && (0 != errDesc[0])) 174 fprintf (stderr, "%s", errDesc); 175 else 176 fprintf (stderr, "MHD unexpected error"); 177 if ((NULL != funcName) && (0 != funcName[0])) 178 fprintf (stderr, " in %s", funcName); 179 if (0 < lineNum) 180 fprintf (stderr, " at line %d", lineNum); 181 182 fprintf (stderr, ".\nLast errno value: %d (%s)\n", (int) errno, 183 strerror (errno)); 184 #ifdef MHD_WINSOCK_SOCKETS 185 fprintf (stderr, "WSAGetLastError() value: %d\n", (int) WSAGetLastError ()); 186 #endif /* MHD_WINSOCK_SOCKETS */ 187 188 fflush (stderr); 189 exit (8); 190 } 191 192 193 static void 194 _checkCURLE_OK_func (CURLcode code, const char *curlFunc, 195 const char *funcName, int lineNum) 196 { 197 if (CURLE_OK == code) 198 return; 199 200 fflush (stdout); 201 if ((NULL != curlFunc) && (0 != curlFunc[0])) 202 fprintf (stderr, "'%s' resulted in '%s'", curlFunc, 203 curl_easy_strerror (code)); 204 else 205 fprintf (stderr, "libcurl function call resulted in '%s'", 206 curl_easy_strerror (code)); 207 if ((NULL != funcName) && (0 != funcName[0])) 208 fprintf (stderr, " in %s", funcName); 209 if (0 < lineNum) 210 fprintf (stderr, " at line %d", lineNum); 211 212 fprintf (stderr, ".\nLast errno value: %d (%s)\n", (int) errno, 213 strerror (errno)); 214 if (0 != libcurl_errbuf[0]) 215 fprintf (stderr, "Last libcurl error description: %s\n", libcurl_errbuf); 216 217 fflush (stderr); 218 exit (9); 219 } 220 221 222 /* Could be increased to facilitate debugging */ 223 #define TIMEOUTS_VAL 5 224 225 #define MHD_URI_BASE_PATH "/bar%20foo%20without%20args" 226 227 #define PAGE \ 228 "<html><head><title>libmicrohttpd demo</title></head><body>Access granted</body></html>" 229 230 #define DENIED \ 231 "<html><head><title>libmicrohttpd demo</title></head><body>Access denied</body></html>" 232 233 #define MY_OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4" 234 235 struct CBC 236 { 237 char *buf; 238 size_t pos; 239 size_t size; 240 }; 241 242 243 static size_t 244 copyBuffer (void *ptr, 245 size_t size, 246 size_t nmemb, 247 void *ctx) 248 { 249 struct CBC *cbc = ctx; 250 251 if (cbc->pos + size * nmemb > cbc->size) 252 mhdErrorExitDesc ("Wrong too large data"); /* overflow */ 253 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); 254 cbc->pos += size * nmemb; 255 return size * nmemb; 256 } 257 258 259 static enum MHD_Result 260 ahc_echo (void *cls, 261 struct MHD_Connection *connection, 262 const char *url, 263 const char *method, 264 const char *version, 265 const char *upload_data, 266 size_t *upload_data_size, 267 void **req_cls) 268 { 269 struct MHD_Response *response; 270 char *username; 271 const char *password = "testpass"; 272 const char *realm = "test@example.com"; 273 enum MHD_Result ret; 274 int ret_i; 275 static int already_called_marker; 276 (void) cls; (void) url; /* Unused. Silent compiler warning. */ 277 (void) method; (void) version; (void) upload_data; /* Unused. Silent compiler warning. */ 278 (void) upload_data_size; (void) req_cls; /* Unused. Silent compiler warning. */ 279 280 if (&already_called_marker != *req_cls) 281 { /* Called for the first time, request not fully read yet */ 282 *req_cls = &already_called_marker; 283 /* Wait for complete request */ 284 return MHD_YES; 285 } 286 287 username = MHD_digest_auth_get_username (connection); 288 if ( (username == NULL) || 289 (0 != strcmp (username, "testuser")) ) 290 { 291 response = MHD_create_response_from_buffer_static (strlen (DENIED), 292 DENIED); 293 if (NULL == response) 294 mhdErrorExitDesc ("MHD_create_response_from_buffer failed"); 295 ret = MHD_queue_auth_fail_response2 (connection, 296 realm, 297 MY_OPAQUE, 298 response, 299 MHD_NO, 300 MHD_DIGEST_ALG_MD5); 301 if (MHD_YES != ret) 302 mhdErrorExitDesc ("MHD_queue_auth_fail_response2 failed"); 303 MHD_destroy_response (response); 304 return ret; 305 } 306 ret_i = MHD_digest_auth_check2 (connection, 307 realm, 308 username, 309 password, 310 300, 311 MHD_DIGEST_ALG_MD5); 312 MHD_free (username); 313 if (ret_i != MHD_YES) 314 { 315 response = MHD_create_response_from_buffer_static (strlen (DENIED), 316 DENIED); 317 if (NULL == response) 318 mhdErrorExitDesc ("MHD_create_response_from_buffer() failed"); 319 ret = MHD_queue_auth_fail_response2 (connection, 320 realm, 321 MY_OPAQUE, 322 response, 323 (MHD_INVALID_NONCE == ret_i) ? 324 MHD_YES : MHD_NO, 325 MHD_DIGEST_ALG_MD5); 326 if (MHD_YES != ret) 327 mhdErrorExitDesc ("MHD_queue_auth_fail_response2() failed"); 328 MHD_destroy_response (response); 329 return ret; 330 } 331 response = MHD_create_response_from_buffer_static (strlen (PAGE), 332 PAGE); 333 if (NULL == response) 334 mhdErrorExitDesc ("MHD_create_response_from_buffer() failed"); 335 ret = MHD_queue_response (connection, 336 MHD_HTTP_OK, 337 response); 338 if (MHD_YES != ret) 339 mhdErrorExitDesc ("MHD_queue_auth_fail_response2() failed"); 340 MHD_destroy_response (response); 341 return ret; 342 } 343 344 345 static CURL * 346 setupCURL (void *cbc, uint16_t port) 347 { 348 CURL *c; 349 char url[512]; 350 351 if (1) 352 { 353 int res; 354 /* A workaround for some old libcurl versions, which ignore the specified 355 * port by CURLOPT_PORT when digest authorisation is used. */ 356 res = snprintf (url, (sizeof(url) / sizeof(url[0])), 357 "http://127.0.0.1:%u%s", 358 (unsigned int) port, MHD_URI_BASE_PATH); 359 if ((0 >= res) || ((sizeof(url) / sizeof(url[0])) <= (size_t) res)) 360 externalErrorExitDesc ("Cannot form request URL"); 361 } 362 363 c = curl_easy_init (); 364 if (NULL == c) 365 libcurlErrorExitDesc ("curl_easy_init() failed"); 366 367 if ((CURLE_OK != curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L)) || 368 (CURLE_OK != curl_easy_setopt (c, CURLOPT_ERRORBUFFER, 369 libcurl_errbuf)) || 370 (CURLE_OK != curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, 371 ©Buffer)) || 372 (CURLE_OK != curl_easy_setopt (c, CURLOPT_WRITEDATA, cbc)) || 373 (CURLE_OK != curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 374 ((long) TIMEOUTS_VAL))) || 375 (CURLE_OK != curl_easy_setopt (c, CURLOPT_TIMEOUT, 376 ((long) TIMEOUTS_VAL))) || 377 (CURLE_OK != curl_easy_setopt (c, CURLOPT_HTTP_VERSION, 378 CURL_HTTP_VERSION_1_1)) || 379 (CURLE_OK != curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L)) || 380 #if CURL_AT_LEAST_VERSION (7, 85, 0) 381 (CURLE_OK != curl_easy_setopt (c, CURLOPT_PROTOCOLS_STR, "http")) || 382 #elif CURL_AT_LEAST_VERSION (7, 19, 4) 383 (CURLE_OK != curl_easy_setopt (c, CURLOPT_PROTOCOLS, CURLPROTO_HTTP)) || 384 #endif /* CURL_AT_LEAST_VERSION (7, 19, 4) */ 385 #if CURL_AT_LEAST_VERSION (7, 45, 0) 386 (CURLE_OK != curl_easy_setopt (c, CURLOPT_DEFAULT_PROTOCOL, "http")) || 387 #endif /* CURL_AT_LEAST_VERSION (7, 45, 0) */ 388 (CURLE_OK != curl_easy_setopt (c, CURLOPT_PORT, ((long) port))) || 389 (CURLE_OK != curl_easy_setopt (c, CURLOPT_URL, url))) 390 libcurlErrorExitDesc ("curl_easy_setopt() failed"); 391 if ((CURLE_OK != curl_easy_setopt (c, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST)) || 392 (CURLE_OK != curl_easy_setopt (c, CURLOPT_USERPWD, 393 "testuser:testpass"))) 394 libcurlErrorExitDesc ("curl_easy_setopt() authorization options failed"); 395 return c; 396 } 397 398 399 static unsigned int 400 testDigestAuth (void) 401 { 402 CURL *c; 403 struct MHD_Daemon *d; 404 struct CBC cbc; 405 char buf[2048]; 406 char rnd[8]; 407 uint16_t port; 408 #ifndef WINDOWS 409 int fd; 410 size_t len; 411 size_t off = 0; 412 #endif /* ! WINDOWS */ 413 414 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 415 port = 0; 416 else 417 port = 1165; 418 419 cbc.buf = buf; 420 cbc.size = 2048; 421 cbc.pos = 0; 422 #ifndef WINDOWS 423 fd = open ("/dev/urandom", 424 O_RDONLY); 425 if (-1 == fd) 426 externalErrorExitDesc ("Failed to open '/dev/urandom'"); 427 428 while (off < 8) 429 { 430 len = (size_t) read (fd, 431 rnd + off, 432 8 - off); 433 if (len == (size_t) -1) 434 externalErrorExitDesc ("Failed to read '/dev/urandom'"); 435 off += len; 436 } 437 (void) close (fd); 438 #else 439 { 440 HCRYPTPROV cc; 441 BOOL b; 442 443 b = CryptAcquireContext (&cc, 444 NULL, 445 NULL, 446 PROV_RSA_FULL, 447 CRYPT_VERIFYCONTEXT); 448 if (b == 0) 449 externalErrorExitDesc ("CryptAcquireContext() failed"); 450 b = CryptGenRandom (cc, 8, (BYTE *) rnd); 451 if (b == 0) 452 externalErrorExitDesc ("CryptGenRandom() failed"); 453 CryptReleaseContext (cc, 0); 454 } 455 #endif 456 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, 457 port, NULL, NULL, 458 &ahc_echo, NULL, 459 MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof (rnd), rnd, 460 MHD_OPTION_NONCE_NC_SIZE, 300, 461 MHD_OPTION_DIGEST_AUTH_DEFAULT_MAX_NC, (uint32_t) 999, 462 MHD_OPTION_END); 463 if (d == NULL) 464 return 1; 465 if (0 == port) 466 { 467 const union MHD_DaemonInfo *dinfo; 468 469 dinfo = MHD_get_daemon_info (d, 470 MHD_DAEMON_INFO_BIND_PORT); 471 if ( (NULL == dinfo) || 472 (0 == dinfo->port) ) 473 mhdErrorExitDesc ("MHD_get_daemon_info() failed"); 474 port = dinfo->port; 475 } 476 c = setupCURL (&cbc, port); 477 478 checkCURLE_OK (curl_easy_perform (c)); 479 480 curl_easy_cleanup (c); 481 MHD_stop_daemon (d); 482 if (cbc.pos != strlen (PAGE)) 483 { 484 fprintf (stderr, "Got %u bytes ('%.*s'), expected %u bytes. ", 485 (unsigned) cbc.pos, (int) cbc.pos, cbc.buf, 486 (unsigned) strlen (MHD_URI_BASE_PATH)); 487 mhdErrorExitDesc ("Wrong returned data length"); 488 } 489 if (0 != strncmp (PAGE, cbc.buf, strlen (PAGE))) 490 { 491 fprintf (stderr, "Got invalid response '%.*s'. ", (int) cbc.pos, cbc.buf); 492 mhdErrorExitDesc ("Wrong returned data"); 493 } 494 return 0; 495 } 496 497 498 int 499 main (int argc, char *const *argv) 500 { 501 unsigned int errorCount = 0; 502 (void) argc; (void) argv; /* Unused. Silent compiler warning. */ 503 504 #ifdef NEED_GCRYP_INIT 505 gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); 506 #ifdef GCRYCTL_INITIALIZATION_FINISHED 507 gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); 508 #endif /* GCRYCTL_INITIALIZATION_FINISHED */ 509 #endif /* NEED_GCRYP_INIT */ 510 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 511 return 2; 512 errorCount += testDigestAuth (); 513 if (errorCount != 0) 514 fprintf (stderr, "Error (code: %u)\n", errorCount); 515 curl_global_cleanup (); 516 return (0 == errorCount) ? 0 : 1; /* 0 == pass */ 517 }