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