digest_auth_example_adv.c (33257B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2010 Christian Grothoff (and other contributing authors) 4 Copyright (C) 2016-2024 Evgeny Grin (Karlson2k) 5 6 This library is free software; you can redistribute it and/or 7 modify it under the terms of the GNU Lesser General Public 8 License as published by the Free Software Foundation; either 9 version 2.1 of the License, or (at your option) any later version. 10 11 This library is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 Lesser General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with this library; if not, write to the Free Software 18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 */ 20 /** 21 * @file digest_auth_example_adv.c 22 * @brief Advanced example for digest auth with libmicrohttpd 23 * @author Karlson2k (Evgeny Grin) 24 */ 25 26 #include <microhttpd.h> 27 #include <stdlib.h> 28 #include <stdio.h> 29 #include <stdint.h> 30 #include <string.h> 31 #if ! defined(_WIN32) || defined(__CYGWIN__) 32 # include <errno.h> 33 # include <fcntl.h> 34 # include <unistd.h> 35 #else /* Native W32 */ 36 # include <wincrypt.h> 37 #endif /* Native W32 */ 38 39 #define SEC_AREA1_URL "/secret_page/" 40 #define SEC_AREA2_URL "/super_secret_page/" 41 42 #define MAIN_PAGE \ 43 "<html><head><title>Welcome to the site</title></head>" \ 44 "<body><p><a href=\"" SEC_AREA1_URL "\">Restricted Page</a></p>" \ 45 "<p><a href=\"" SEC_AREA2_URL "\">Very Restricted Page</a></p></body></html>" 46 47 #define OPAQUE_DATA "ServerOpaqueData" 48 49 #define REALM "authenticated_users@thishost" 50 51 /** 52 * Force select "MD5" algorithm instead of MHD default (currently the same) if non-zero. 53 */ 54 static int force_md5 = 0; 55 /** 56 * Force select "SHA-256" algorithm instead of MHD default (MD5) if non-zero. 57 */ 58 static int force_sha256 = 0; 59 /** 60 * Force select "SHA-512/256" algorithm instead of MHD default (MD5) if non-zero. 61 */ 62 static int force_sha512_256 = 0; 63 /** 64 * Disable fallback to (less secure) RFC2069 if non-zero. 65 */ 66 static int allow_rfc2069 = 0; 67 68 /** 69 * The daemon's port 70 */ 71 static uint16_t daemon_port = 0; 72 73 74 /* *** "Database" of users and "database" functions *** */ 75 76 /** 77 * User record. 78 * This kind of data (or something similar) should be stored in some database 79 * or file. 80 */ 81 struct UserEntry 82 { 83 /** 84 * The username. 85 * Static data is used in this example. 86 * In real application dynamic buffer or fixed size array could be used. 87 */ 88 const char *username; 89 #if 0 /* Disabled code */ 90 /* The cleartext password is not stored in the database. 91 The more secure "userdigest" is used instead. */ 92 /** 93 * The password. 94 * Static data is used in this example. 95 * In real application dynamic buffer or fixed size array could be used. 96 */ 97 const char *password; 98 #endif /* Disabled code */ 99 /** 100 * The realm for this entry. 101 * Static data is used in this example. 102 * In real application dynamic buffer or fixed size array could be used. 103 */ 104 const char *realm; 105 106 /** 107 * The MD5 hash of the username together with the realm. 108 * This hash can be used by the client to send the username in encrypted 109 * form. 110 * The purpose of userhash is to hide user identity when transmitting 111 * requests over insecure link. 112 */ 113 uint8_t userhash_md5[MHD_MD5_DIGEST_SIZE]; 114 /** 115 * The MD5 hash of the username with the password and the realm. 116 * It is used to verify that password used by the client matches password 117 * required by the server. 118 * The purpose of userhash is to avoid keeping the password in cleartext 119 * on the server side. 120 */ 121 uint8_t userdigest_md5[MHD_MD5_DIGEST_SIZE]; 122 123 /** 124 * The SHA-256 hash of the username together with the realm. 125 * This hash can be used by the client to send the username in encrypted 126 * form. 127 * The purpose of userhash is to hide user identity when transmitting 128 * requests over insecure link. 129 */ 130 uint8_t userhash_sha256[MHD_SHA256_DIGEST_SIZE]; 131 /** 132 * The SHA-256 hash of the username with the password and the realm. 133 * It is used to verify that password used by the client matches password 134 * required by the server. 135 * The purpose of userhash is to avoid keeping the password in cleartext 136 * on the server side. 137 */ 138 uint8_t userdigest_sha256[MHD_SHA256_DIGEST_SIZE]; 139 140 /** 141 * The SHA-512/256 hash of the username together with the realm. 142 * This hash can be used by the client to send the username in encrypted 143 * form. 144 * The purpose of userhash is to hide user identity when transmitting 145 * requests over insecure link. 146 */ 147 uint8_t userhash_sha512_256[MHD_SHA512_256_DIGEST_SIZE]; 148 /** 149 * The SHA-512/256 hash of the username with the password and the realm. 150 * It is used to verify that password used by the client matches password 151 * required by the server. 152 * The purpose of userhash is to avoid keeping the password in cleartext 153 * on the server side. 154 */ 155 uint8_t userdigest_sha512_256[MHD_SHA512_256_DIGEST_SIZE]; 156 157 /** 158 * User has access to "area 1" if non-zero 159 */ 160 int allow_area_1; 161 162 /** 163 * User has access to "area 2" if non-zero 164 */ 165 int allow_area_2; 166 }; 167 168 /** 169 * The array of user entries. 170 * In real application it should be loaded from external sources 171 * at the application startup. 172 */ 173 static struct UserEntry user_ids[2]; 174 175 /** 176 * The number of entries used in @a user_ids. 177 */ 178 static size_t user_ids_used = 0; 179 180 /** 181 * Add new user to the users database/array. 182 * 183 * This kind of function must be used only when the new user is introduced. 184 * It must not be used at the every start of the application. The database 185 * of users should be stored somewhere and reloaded when application is 186 * started. 187 * 188 * @param username the username of the new user 189 * @param password the password of the new user 190 * @param realm the realm (the protection space) for which the new user 191 * is added 192 * @param allow_area_1 if non-zero than user has access to the "area 1" 193 * @param allow_area_2 if non-zero than user has access to the "area 2" 194 * @return non-zero on success, 195 * zero on failure (like no more space in the database). 196 */ 197 static int 198 add_new_user_entry (const char *const username, 199 const char *const password, 200 const char *const realm, 201 int allow_area_1, 202 int allow_area_2) 203 { 204 struct UserEntry *entry; 205 enum MHD_Result res; 206 207 if ((sizeof(user_ids) / sizeof(user_ids[0])) <= user_ids_used) 208 return 0; /* No more space to add new entry */ 209 210 entry = user_ids + user_ids_used; 211 212 entry->username = username; 213 entry->realm = realm; 214 215 res = MHD_YES; 216 217 if (MHD_NO != res) 218 res = MHD_digest_auth_calc_userhash (MHD_DIGEST_AUTH_ALGO3_MD5, 219 username, 220 realm, 221 entry->userhash_md5, 222 sizeof(entry->userhash_md5)); 223 if (MHD_NO != res) 224 res = MHD_digest_auth_calc_userdigest (MHD_DIGEST_AUTH_ALGO3_MD5, 225 username, 226 realm, 227 password, 228 entry->userdigest_md5, 229 sizeof(entry->userdigest_md5)); 230 231 if (MHD_NO != res) 232 res = MHD_digest_auth_calc_userhash (MHD_DIGEST_AUTH_ALGO3_SHA256, 233 username, 234 realm, 235 entry->userhash_sha256, 236 sizeof(entry->userhash_sha256)); 237 if (MHD_NO != res) 238 res = MHD_digest_auth_calc_userdigest (MHD_DIGEST_AUTH_ALGO3_SHA256, 239 username, 240 realm, 241 password, 242 entry->userdigest_sha256, 243 sizeof(entry->userdigest_sha256)); 244 245 if (MHD_NO != res) 246 res = MHD_digest_auth_calc_userhash (MHD_DIGEST_AUTH_ALGO3_SHA512_256, 247 username, 248 realm, 249 entry->userhash_sha512_256, 250 sizeof(entry->userhash_sha512_256)); 251 if (MHD_NO != res) 252 res = 253 MHD_digest_auth_calc_userdigest (MHD_DIGEST_AUTH_ALGO3_SHA512_256, 254 username, 255 realm, 256 password, 257 entry->userdigest_sha512_256, 258 sizeof(entry->userdigest_sha512_256)); 259 260 if (MHD_NO == res) 261 return 0; /* Failure exit point */ 262 263 entry->allow_area_1 = allow_area_1; 264 entry->allow_area_2 = allow_area_2; 265 266 user_ids_used++; 267 268 return ! 0; 269 } 270 271 272 /** 273 * Find the user entry for specified username 274 * @param username the username to find 275 * @return NULL if no entry for specified username is found, 276 * pointer to user entry if found 277 */ 278 static struct UserEntry * 279 find_entry_by_username (const char *const username) 280 { 281 size_t i; 282 283 for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i) 284 { 285 struct UserEntry *entry; 286 287 entry = user_ids + i; 288 if (0 == strcmp (username, entry->username)) 289 return entry; 290 } 291 return NULL; 292 } 293 294 295 /** 296 * Find the user entry for specified userhash 297 * @param algo3 the algorithm used for userhash calculation 298 * @param userhash the userhash identifier to find 299 * @param userhash_size the size @a userhash in bytes 300 * @return NULL if no entry for specified userhash is found, 301 * pointer to user entry if found 302 */ 303 static struct UserEntry * 304 find_entry_by_userhash (enum MHD_DigestAuthAlgo3 algo3, 305 const void *userhash, 306 size_t userhash_size) 307 { 308 size_t i; 309 310 if (MHD_digest_get_hash_size (algo3) != userhash_size) 311 return NULL; /* Wrong length of the userhash */ 312 313 switch (algo3) 314 { 315 case MHD_DIGEST_AUTH_ALGO3_MD5: 316 case MHD_DIGEST_AUTH_ALGO3_MD5_SESSION: /* An extra case not used currently */ 317 if (sizeof(user_ids[0].userhash_md5) != userhash_size) /* Extra check. The size was checked before */ 318 return NULL; 319 for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i) 320 { 321 struct UserEntry *entry; 322 323 entry = user_ids + i; 324 if (0 == memcmp (userhash, entry->userhash_md5, 325 sizeof(entry->userhash_md5))) 326 return entry; 327 } 328 break; 329 case MHD_DIGEST_AUTH_ALGO3_SHA256: 330 case MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION: /* An extra case not used currently */ 331 if (sizeof(user_ids[0].userhash_sha256) != userhash_size) /* Extra check. The size was checked before */ 332 return NULL; 333 for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i) 334 { 335 struct UserEntry *entry; 336 337 entry = user_ids + i; 338 if (0 == memcmp (userhash, entry->userhash_sha256, 339 sizeof(entry->userhash_sha256))) 340 return entry; 341 } 342 break; 343 case MHD_DIGEST_AUTH_ALGO3_SHA512_256: 344 case MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION: /* An extra case not used currently */ 345 if (sizeof(user_ids[0].userhash_sha512_256) != userhash_size) /* Extra check. The size was checked before */ 346 return NULL; 347 for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i) 348 { 349 struct UserEntry *entry; 350 351 entry = user_ids + i; 352 if (0 == memcmp (userhash, entry->userhash_sha512_256, 353 sizeof(entry->userhash_sha512_256))) 354 return entry; 355 } 356 break; 357 case MHD_DIGEST_AUTH_ALGO3_INVALID: /* Mute compiler warning. Impossible value in this context. */ 358 default: 359 break; 360 } 361 return NULL; 362 } 363 364 365 /** 366 * Find the user entry for the user specified by provided username info 367 * @param user_info the pointer to the structure username info returned by MHD 368 * @return NULL if no entry for specified username info is found, 369 * pointer to user entry if found 370 */ 371 static struct UserEntry * 372 find_entry_by_userinfo (const struct MHD_DigestAuthUsernameInfo *username_info) 373 { 374 if (MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD <= username_info->uname_type) 375 return find_entry_by_username (username_info->username); 376 377 if (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == username_info->uname_type) 378 return find_entry_by_userhash (username_info->algo3, 379 username_info->userhash_bin, 380 username_info->userhash_hex_len / 2); 381 382 return NULL; /* Should be unreachable as all cases are covered before */ 383 } 384 385 386 /* *** End of "database" of users and "database" functions *** */ 387 388 /* *** Requests handling *** */ 389 390 /** 391 * Send "Requested HTTP method is not supported" page 392 * @param c the connection structure 393 * @return MHD_YES if response was successfully queued, 394 * MHD_NO otherwise 395 */ 396 static enum MHD_Result 397 reply_with_page_not_found (struct MHD_Connection *c) 398 { 399 static const char page_content[] = 400 "<html><head><title>Page Not Found</title></head>" \ 401 "<body>The requested page not found.</body></html>"; 402 static const size_t page_content_len = 403 (sizeof(page_content) / sizeof(char)) - 1; 404 struct MHD_Response *resp; 405 enum MHD_Result ret; 406 407 resp = MHD_create_response_from_buffer_static (page_content_len, 408 page_content); 409 if (NULL == resp) 410 return MHD_NO; 411 412 /* Ignore possible error when adding the header as the reply will work even 413 without this header. */ 414 (void) MHD_add_response_header (resp, 415 MHD_HTTP_HEADER_CONTENT_TYPE, 416 "text/html"); 417 418 ret = MHD_queue_response (c, MHD_HTTP_NOT_FOUND, resp); 419 MHD_destroy_response (resp); 420 return ret; 421 } 422 423 424 /** 425 * Get enum MHD_DigestAuthMultiAlgo3 value to be used for authentication. 426 * @return the algorithm number/value 427 */ 428 static enum MHD_DigestAuthMultiAlgo3 429 get_m_algo (void) 430 { 431 if (force_md5) 432 return MHD_DIGEST_AUTH_MULT_ALGO3_MD5; 433 else if (force_sha256) 434 return MHD_DIGEST_AUTH_MULT_ALGO3_SHA256; 435 else if (force_sha512_256) 436 return MHD_DIGEST_AUTH_MULT_ALGO3_SHA512_256; 437 438 /* No forced algorithm selection, let MHD to use default */ 439 return MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION; 440 } 441 442 443 /** 444 * Get enum MHD_DigestAuthMultiQOP value to be used for authentication. 445 * @return the "Quality Of Protection" number/value 446 */ 447 static enum MHD_DigestAuthMultiQOP 448 get_m_QOP (void) 449 { 450 if (allow_rfc2069) 451 return MHD_DIGEST_AUTH_MULT_QOP_ANY_NON_INT; 452 453 return MHD_DIGEST_AUTH_MULT_QOP_AUTH; 454 } 455 456 457 /** 458 * Send "Authentication required" page 459 * @param c the connection structure 460 * @param stale if non-zero then "nonce stale" is indicated in the reply 461 * @param wrong_cred if non-zero then client is informed the previously 462 * it used wrong credentials 463 * @return MHD_YES if response was successfully queued, 464 * MHD_NO otherwise 465 */ 466 static enum MHD_Result 467 reply_with_auth_required (struct MHD_Connection *c, 468 int stale, 469 int wrong_cred) 470 { 471 static const char auth_required_content[] = 472 "<html><head><title>Authentication required</title></head>" \ 473 "<body>The requested page needs authentication.</body></html>"; 474 static const size_t auth_required_content_len = 475 (sizeof(auth_required_content) / sizeof(char)) - 1; 476 static const char wrong_creds_content[] = 477 "<html><head><title>Wrong credentials</title></head>" \ 478 "<body>The provided credentials are incorrect.</body></html>"; 479 static const size_t wrong_creds_content_len = 480 (sizeof(wrong_creds_content) / sizeof(char)) - 1; 481 struct MHD_Response *resp; 482 enum MHD_Result ret; 483 484 if (wrong_cred) 485 stale = 0; /* Force client to ask user for username and password */ 486 487 if (! wrong_cred) 488 resp = MHD_create_response_from_buffer_static (auth_required_content_len, 489 auth_required_content); 490 else 491 resp = MHD_create_response_from_buffer_static (wrong_creds_content_len, 492 wrong_creds_content); 493 if (NULL == resp) 494 return MHD_NO; 495 496 /* Ignore possible error when adding the header as the reply will work even 497 without this header. */ 498 (void) MHD_add_response_header (resp, 499 MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); 500 501 502 ret = MHD_queue_auth_required_response3 ( 503 c, 504 REALM, 505 OPAQUE_DATA, /* The "opaque data", not really useful */ 506 SEC_AREA1_URL " " SEC_AREA2_URL, /* Space-separated list of URLs' initial parts */ 507 resp, 508 stale, 509 get_m_QOP (), 510 get_m_algo (), 511 ! 0, /* Userhash support enabled */ 512 ! 0 /* UTF-8 is preferred */); 513 MHD_destroy_response (resp); 514 return ret; 515 } 516 517 518 /** 519 * Send "Forbidden" page 520 * @param c the connection structure 521 * @return MHD_YES if response was successfully queued, 522 * MHD_NO otherwise 523 */ 524 static enum MHD_Result 525 reply_with_forbidden (struct MHD_Connection *c) 526 { 527 static const char page_content[] = 528 "<html><head><title>Forbidden</title></head>" \ 529 "<body>You do not have access to this page.</body></html>"; 530 static const size_t page_content_len = 531 (sizeof(page_content) / sizeof(char)) - 1; 532 struct MHD_Response *resp; 533 enum MHD_Result ret; 534 535 resp = MHD_create_response_from_buffer_static (page_content_len, page_content) 536 ; 537 if (NULL == resp) 538 return MHD_NO; 539 540 /* Ignore possible error when adding the header as the reply will work even 541 without this header. */ 542 (void) MHD_add_response_header (resp, 543 MHD_HTTP_HEADER_CONTENT_TYPE, 544 "text/html"); 545 546 ret = MHD_queue_response (c, MHD_HTTP_FORBIDDEN, resp); 547 MHD_destroy_response (resp); 548 return ret; 549 } 550 551 552 /** 553 * Send "Area 1" pages 554 * @param c the connection structure 555 * @param url the requested URL 556 * @return MHD_YES if response was successfully queued, 557 * MHD_NO otherwise 558 */ 559 static enum MHD_Result 560 reply_with_area1_pages (struct MHD_Connection *c, 561 const char *url) 562 { 563 564 if (0 == strcmp (url, SEC_AREA1_URL "")) 565 { 566 static const char page_content[] = 567 "<html><head><title>Restricted secret page</title></head>" \ 568 "<body>Welcome to the restricted area</body></html>"; 569 static const size_t page_content_len = 570 (sizeof(page_content) / sizeof(char)) - 1; 571 struct MHD_Response *resp; 572 enum MHD_Result ret; 573 574 resp = MHD_create_response_from_buffer_static (page_content_len, 575 page_content); 576 if (NULL == resp) 577 return MHD_NO; 578 579 /* Ignore possible error when adding the header as the reply will work even 580 without this header. */ 581 (void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE, 582 "text/html"); 583 584 ret = MHD_queue_response (c, MHD_HTTP_OK, resp); 585 MHD_destroy_response (resp); 586 return ret; 587 } 588 /* If needed: add handlers for other URLs in this area */ 589 #if 0 /* Disabled code */ 590 if (0 == strcmp (url, SEC_AREA1_URL "some_path/some_page")) 591 { 592 /* Add page creation/processing code */ 593 } 594 #endif /* Disabled code */ 595 596 /* The requested URL is unknown */ 597 return reply_with_page_not_found (c); 598 } 599 600 601 /** 602 * Send "Area 2" pages 603 * @param c the connection structure 604 * @param url the requested URL 605 * @return MHD_YES if response was successfully queued, 606 * MHD_NO otherwise 607 */ 608 static enum MHD_Result 609 reply_with_area2_pages (struct MHD_Connection *c, 610 const char *url) 611 { 612 613 if (0 == strcmp (url, SEC_AREA2_URL "")) 614 { 615 static const char page_content[] = 616 "<html><head><title>Very restricted secret page</title></head>" \ 617 "<body>Welcome to the super restricted area</body></html>"; 618 static const size_t page_content_len = 619 (sizeof(page_content) / sizeof(char)) - 1; 620 struct MHD_Response *resp; 621 enum MHD_Result ret; 622 623 resp = MHD_create_response_from_buffer_static (page_content_len, 624 page_content); 625 if (NULL == resp) 626 return MHD_NO; 627 628 /* Ignore possible error when adding the header as the reply will work even 629 without this header. */ 630 (void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE, 631 "text/html"); 632 633 ret = MHD_queue_response (c, MHD_HTTP_OK, resp); 634 MHD_destroy_response (resp); 635 return ret; 636 } 637 /* If needed: add handlers for other URLs in this area */ 638 #if 0 /* Disabled code */ 639 if (0 == strcmp (url, SEC_AREA2_URL "other_path/other_page")) 640 { 641 /* Add page creation/processing code */ 642 } 643 #endif /* Disabled code */ 644 645 /* The requested URL is unknown */ 646 return reply_with_page_not_found (c); 647 } 648 649 650 /** 651 * Handle client's request for secured areas 652 * @param c the connection structure 653 * @param url the URL requested by the client 654 * @param sec_area_num the number of secured area 655 * @return MHD_YES if request was handled (either with "denied" or with 656 * "allowed" result), 657 * MHD_NO if it was an error handling the request. 658 */ 659 static enum MHD_Result 660 handle_sec_areas_req (struct MHD_Connection *c, const char *url, unsigned int 661 sec_area_num) 662 { 663 struct MHD_DigestAuthUsernameInfo *username_info; 664 struct UserEntry *user_entry; 665 void *userdigest; 666 size_t userdigest_size; 667 enum MHD_DigestAuthResult auth_res; 668 669 username_info = MHD_digest_auth_get_username3 (c); 670 671 if (NULL == username_info) 672 return reply_with_auth_required (c, 0, 0); 673 674 user_entry = find_entry_by_userinfo (username_info); 675 676 if (NULL == user_entry) 677 return reply_with_auth_required (c, 0, 1); 678 679 switch (username_info->algo3) 680 { 681 case MHD_DIGEST_AUTH_ALGO3_MD5: 682 userdigest = user_entry->userdigest_md5; 683 userdigest_size = sizeof(user_entry->userdigest_md5); 684 break; 685 case MHD_DIGEST_AUTH_ALGO3_SHA256: 686 userdigest = user_entry->userdigest_sha256; 687 userdigest_size = sizeof(user_entry->userdigest_sha256); 688 break; 689 case MHD_DIGEST_AUTH_ALGO3_SHA512_256: 690 userdigest = user_entry->userdigest_sha512_256; 691 userdigest_size = sizeof(user_entry->userdigest_sha512_256); 692 break; 693 case MHD_DIGEST_AUTH_ALGO3_MD5_SESSION: 694 case MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION: 695 case MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION: 696 /* Not supported currently and not used by MHD. 697 The client incorrectly used algorithm not advertised by the server. */ 698 return reply_with_auth_required (c, 0, 1); 699 case MHD_DIGEST_AUTH_ALGO3_INVALID: /* Mute compiler warning */ 700 default: 701 return MHD_NO; /* Should be unreachable */ 702 } 703 704 auth_res = MHD_digest_auth_check_digest3 ( 705 c, 706 REALM, /* Make sure to use the proper realm, not the realm provided by the client and returned by "user_entry" */ 707 user_entry->username, 708 userdigest, 709 userdigest_size, 710 0, /* Use daemon's default value for nonce_timeout*/ 711 0, /* Use daemon's default value for max_nc */ 712 get_m_QOP (), 713 (enum MHD_DigestAuthMultiAlgo3) username_info->algo3 /* Direct cast from "single algorithm" to "multi-algorithm" is allowed */ 714 ); 715 716 if (MHD_DAUTH_OK != auth_res) 717 { 718 int need_just_refresh_nonce; 719 /* Actually MHD_DAUTH_NONCE_OTHER_COND should not be returned as 720 MHD_OPTION_DIGEST_AUTH_NONCE_BIND_TYPE is not used for the daemon. 721 To keep the code universal the MHD_DAUTH_NONCE_OTHER_COND is 722 still checked here. */ 723 need_just_refresh_nonce = 724 (MHD_DAUTH_NONCE_STALE == auth_res) 725 || (MHD_DAUTH_NONCE_OTHER_COND == auth_res); 726 return reply_with_auth_required (c, 727 need_just_refresh_nonce, 728 ! need_just_refresh_nonce); 729 } 730 731 /* The user successfully authenticated */ 732 733 /* Check whether access to the request area is allowed for the user */ 734 if (1 == sec_area_num) 735 { 736 if (user_entry->allow_area_1) 737 return reply_with_area1_pages (c, url); 738 else 739 return reply_with_forbidden (c); 740 } 741 else if (2 == sec_area_num) 742 { 743 if (user_entry->allow_area_2) 744 return reply_with_area2_pages (c, url); 745 else 746 return reply_with_forbidden (c); 747 } 748 749 return MHD_NO; /* Should be unreachable */ 750 } 751 752 753 /** 754 * Send the main page 755 * @param c the connection structure 756 * @return MHD_YES if response was successfully queued, 757 * MHD_NO otherwise 758 */ 759 static enum MHD_Result 760 reply_with_main_page (struct MHD_Connection *c) 761 { 762 static const char page_content[] = MAIN_PAGE; 763 static const size_t page_content_len = 764 (sizeof(page_content) / sizeof(char)) - 1; 765 struct MHD_Response *resp; 766 enum MHD_Result ret; 767 768 resp = MHD_create_response_from_buffer_static (page_content_len, page_content) 769 ; 770 if (NULL == resp) 771 return MHD_NO; 772 773 /* Ignore possible error when adding the header as the reply will work even 774 without this header. */ 775 (void) MHD_add_response_header (resp, 776 MHD_HTTP_HEADER_CONTENT_TYPE, 777 "text/html"); 778 779 ret = MHD_queue_response (c, MHD_HTTP_OK, resp); 780 MHD_destroy_response (resp); 781 return ret; 782 } 783 784 785 /** 786 * Send "Requested HTTP method is not supported" page 787 * @param c the connection structure 788 * @return MHD_YES if response was successfully queued, 789 * MHD_NO otherwise 790 */ 791 static enum MHD_Result 792 reply_with_method_not_supported (struct MHD_Connection *c) 793 { 794 static const char page_content[] = 795 "<html><head><title>Requested HTTP Method Is Not Supported</title></head>" \ 796 "<body>The requested HTTP method is not supported.</body></html>"; 797 static const size_t page_content_len = 798 (sizeof(page_content) / sizeof(char)) - 1; 799 struct MHD_Response *resp; 800 enum MHD_Result ret; 801 802 resp = MHD_create_response_from_buffer_static (page_content_len, page_content) 803 ; 804 if (NULL == resp) 805 return MHD_NO; 806 807 /* Ignore possible error when adding the header as the reply will work even 808 without this header. */ 809 (void) MHD_add_response_header (resp, 810 MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); 811 812 ret = MHD_queue_response (c, MHD_HTTP_NOT_IMPLEMENTED, resp); 813 MHD_destroy_response (resp); 814 return ret; 815 } 816 817 818 static enum MHD_Result 819 ahc_main (void *cls, 820 struct MHD_Connection *connection, 821 const char *url, 822 const char *method, 823 const char *version, 824 const char *upload_data, size_t *upload_data_size, 825 void **req_cls) 826 { 827 static int already_called_marker; 828 size_t url_len; 829 (void) cls; /* Unused. Silent compiler warning. */ 830 (void) version; /* Unused. Silent compiler warning. */ 831 (void) upload_data; /* Unused. Silent compiler warning. */ 832 833 if ((0 != strcmp (method, MHD_HTTP_METHOD_GET)) 834 && (0 != strcmp (method, MHD_HTTP_METHOD_HEAD))) 835 return reply_with_method_not_supported (connection); 836 837 if (0 != *upload_data_size) 838 return MHD_NO; /* No upload expected for GET or HEAD */ 839 840 if (&already_called_marker != *req_cls) 841 { /* Called for the first time, request not fully read yet */ 842 *req_cls = &already_called_marker; 843 /* Wait for complete request */ 844 return MHD_YES; 845 } 846 847 if (0 == strcmp (url, "/")) 848 return reply_with_main_page (connection); 849 850 url_len = strlen (url); 851 852 if ((strlen (SEC_AREA1_URL) <= url_len) 853 && (0 == memcmp (url, SEC_AREA1_URL, strlen (SEC_AREA1_URL)))) 854 return handle_sec_areas_req (connection, url, 1); /* The requested URL is within SEC_AREA1_URL */ 855 856 if ((strlen (SEC_AREA2_URL) <= url_len) 857 && (0 == memcmp (url, SEC_AREA2_URL, strlen (SEC_AREA2_URL)))) 858 return handle_sec_areas_req (connection, url, 2); /* The requested URL is within SEC_AREA2_URL */ 859 860 return reply_with_page_not_found (connection); 861 } 862 863 864 /* *** End of requests handling *** */ 865 866 867 /** 868 * Add new users to the users "database". 869 * 870 * In real application this kind of function must NOT be called at 871 * the application startup. Instead similar function should be 872 * called only when new user is introduced. The users "database" 873 * should be stored somewhere and reloaded at the application 874 * startup. 875 * 876 * @return non-zero on success, 877 * zero in case of error. 878 */ 879 static int 880 add_new_users (void) 881 { 882 if (! add_new_user_entry ("joepublic", 883 "password", 884 REALM, 885 ! 0, 886 0)) 887 return 0; 888 889 if (! add_new_user_entry ("superadmin", 890 "pA$$w0Rd", 891 REALM, 892 ! 0, 893 ! 0)) 894 return 0; 895 896 return ! 0; 897 } 898 899 900 /** 901 * Check and apply application parameters 902 * @param argc the argc of the @a main function 903 * @param argv the argv of the @a main function 904 * @return non-zero on success, 905 * zero in case of any error (like wrong parameters). 906 */ 907 static int 908 check_params (int argc, char *const *const argv) 909 { 910 size_t i; 911 unsigned int port_value; 912 913 if (2 > argc) 914 return 0; 915 916 for (i = 1; i < (unsigned int) argc; ++i) 917 { 918 if (0 == strcmp (argv[i], "--md5")) 919 { /* Force use MD5 */ 920 force_md5 = ! 0; 921 force_sha256 = 0; 922 force_sha512_256 = 0; 923 } 924 else if (0 == strcmp (argv[i], "--sha256")) 925 { /* Force use SHA-256 instead of default MD5 */ 926 force_md5 = 0; 927 force_sha256 = ! 0; 928 force_sha512_256 = 0; 929 } 930 else if (0 == strcmp (argv[i], "--sha512-256")) 931 { /* Force use SHA-512/256 instead of default MD5 */ 932 force_md5 = 0; 933 force_sha256 = 0; 934 force_sha512_256 = ! 0; 935 } 936 else if (0 == strcmp (argv[i], "--allow-rfc2069")) 937 allow_rfc2069 = ! 0; /* Allow fallback to RFC2069. Not recommended! */ 938 else if ((1 == sscanf (argv[i], "%u", &port_value)) 939 && (0 < port_value) && (65535 >= port_value)) 940 daemon_port = (uint16_t) port_value; 941 else 942 { 943 fprintf (stderr, "Unrecognized parameter: %s\n", 944 argv[i]); 945 return 0; 946 } 947 } 948 949 if (force_sha512_256) 950 printf ( 951 "Note: when testing with curl/libcurl do not be surprised with failures as " 952 "libcurl incorrectly implements SHA-512/256 algorithm.\n"); 953 return ! 0; 954 } 955 956 957 /** 958 * The cryptographically secure random data 959 */ 960 static uint8_t rand_data[8]; 961 962 /** 963 * Initialise the random data 964 * @return non-zero if succeed, 965 * zero if failed 966 */ 967 static int 968 init_rand_data (void) 969 { 970 #if ! defined(_WIN32) || defined(__CYGWIN__) 971 int fd; 972 ssize_t len; 973 size_t off; 974 975 fd = open ("/dev/urandom", O_RDONLY); 976 if (-1 == fd) 977 { 978 fprintf (stderr, "Failed to open '%s': %s\n", 979 "/dev/urandom", 980 strerror (errno)); 981 return 0; 982 } 983 for (off = 0; off < sizeof(rand_data); off += (size_t) len) 984 { 985 len = read (fd, rand_data, 8); 986 if (0 > len) 987 { 988 fprintf (stderr, "Failed to read '%s': %s\n", 989 "/dev/urandom", 990 strerror (errno)); 991 (void) close (fd); 992 return 0; 993 } 994 } 995 (void) close (fd); 996 #else /* Native W32 */ 997 HCRYPTPROV cc; 998 BOOL b; 999 1000 b = CryptAcquireContext (&cc, 1001 NULL, 1002 NULL, 1003 PROV_RSA_FULL, 1004 CRYPT_VERIFYCONTEXT); 1005 if (FALSE == b) 1006 { 1007 fprintf (stderr, 1008 "Failed to acquire crypto provider context: %lu\n", 1009 (unsigned long) GetLastError ()); 1010 return 0; 1011 } 1012 b = CryptGenRandom (cc, sizeof(rand_data), (BYTE *) rand_data); 1013 if (FALSE == b) 1014 { 1015 fprintf (stderr, 1016 "Failed to generate 8 random bytes: %lu\n", 1017 GetLastError ()); 1018 } 1019 CryptReleaseContext (cc, 0); 1020 if (FALSE == b) 1021 return 0; 1022 #endif /* Native W32 */ 1023 1024 return ! 0; 1025 } 1026 1027 1028 int 1029 main (int argc, char *const *argv) 1030 { 1031 struct MHD_Daemon *d; 1032 1033 if (! check_params (argc, argv)) 1034 { 1035 fprintf (stderr, "Usage: %s [--md5|--sha256|--sha512-256] " 1036 "[--allow-rfc2069] PORT\n", argv[0]); 1037 return 1; 1038 } 1039 if (! add_new_users ()) 1040 { 1041 fprintf (stderr, "Failed to add new users to the users database.\n"); 1042 return 2; 1043 } 1044 if (! init_rand_data ()) 1045 { 1046 fprintf (stderr, "Failed to initialise random data.\n"); 1047 return 2; 1048 } 1049 1050 d = MHD_start_daemon ( 1051 MHD_USE_INTERNAL_POLLING_THREAD 1052 | MHD_USE_THREAD_PER_CONNECTION 1053 | MHD_USE_ERROR_LOG, 1054 daemon_port, 1055 NULL, NULL, &ahc_main, NULL, 1056 MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof(rand_data), rand_data, 1057 MHD_OPTION_NONCE_NC_SIZE, 500, 1058 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 180, 1059 MHD_OPTION_END); 1060 if (d == NULL) 1061 { 1062 fprintf (stderr, "Failed to start the server on port %lu.\n", 1063 (unsigned long) daemon_port); 1064 return 1; 1065 } 1066 printf ("Running server on port %lu.\nPress ENTER to stop.\n", 1067 (unsigned long) daemon_port); 1068 (void) getc (stdin); 1069 MHD_stop_daemon (d); 1070 return 0; 1071 } 1072 1073 1074 /* End of digest_auth_example_adv.c */