response_auth_digest.c (17629B)
1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ 2 /* 3 This file is part of GNU libmicrohttpd. 4 Copyright (C) 2022-2024 Evgeny Grin (Karlson2k) 5 6 GNU libmicrohttpd 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 GNU libmicrohttpd 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 Alternatively, you can redistribute GNU libmicrohttpd and/or 17 modify it under the terms of the GNU General Public License as 18 published by the Free Software Foundation; either version 2 of 19 the License, or (at your option) any later version, together 20 with the eCos exception, as follows: 21 22 As a special exception, if other files instantiate templates or 23 use macros or inline functions from this file, or you compile this 24 file and link it with other works to produce a work based on this 25 file, this file does not by itself cause the resulting work to be 26 covered by the GNU General Public License. However the source code 27 for this file must still be made available in accordance with 28 section (3) of the GNU General Public License v2. 29 30 This exception does not invalidate any other reasons why a work 31 based on this file might be covered by the GNU General Public 32 License. 33 34 You should have received copies of the GNU Lesser General Public 35 License and the GNU General Public License along with this library; 36 if not, see <https://www.gnu.org/licenses/>. 37 */ 38 39 /** 40 * @file src/mhd2/response_auth_digest.c 41 * @brief The definitions of MHD_response_add_auth_basic_challenge() function 42 * @author Karlson2k (Evgeny Grin) 43 */ 44 45 #include "mhd_sys_options.h" 46 47 #include "sys_bool_type.h" 48 49 #include "mhd_unreachable.h" 50 #include "mhd_assert.h" 51 52 #include "mhd_digest_auth_data.h" 53 54 #include "mhd_response.h" 55 #include "mhd_locks.h" 56 #include "mhd_str_types.h" 57 #include <string.h> 58 #include "sys_malloc.h" 59 60 #include "mhd_str_macros.h" 61 #include "mhd_str.h" 62 63 #include "mhd_public_api.h" 64 65 #include "response_auth_digest.h" 66 #include "mhd_auth_digest_hdr.h" 67 68 69 MHD_INTERNAL 70 MHD_FN_PAR_NONNULL_ (1) void 71 mhd_response_remove_auth_digest_headers (struct MHD_Response*response) 72 { 73 struct mhd_RespAuthDigestHeader *hdr_d; 74 75 for (hdr_d = mhd_DLINKEDL_GET_LAST (response, auth_d_hdrs); 76 NULL != hdr_d; 77 hdr_d = mhd_DLINKEDL_GET_LAST (response, auth_d_hdrs)) 78 { 79 mhd_DLINKEDL_DEL (response, hdr_d, auth_d_hdrs); 80 free (hdr_d); 81 } 82 } 83 84 85 /** 86 * Create and add Digest Auth challenge header with specified algorithm 87 * @param response the response to update 88 * @param rlm the realm to use 89 * @param opq the "opaque" string to use 90 * @param dmn the "domain" string to use 91 * @param indicate_stale whether to indicate "stale" nonce 92 * @param qop_none whether to use RFC 2069 subset only 93 * @param algo the algorithm to use 94 * @param userhash_support whether to indicate support for "userhash" 95 * @param prefer_utf8 whether to indicate UTF-8 support 96 * @return #MHD_SC_OK on success, 97 * error code otherwise 98 */ 99 static MHD_FN_PAR_NONNULL_ALL_ enum MHD_StatusCode 100 response_add_auth_digest_challenge_alg ( 101 struct MHD_Response *restrict response, 102 const struct MHD_String *restrict rlm, 103 const struct MHD_StringNullable *restrict opq, 104 const struct MHD_StringNullable *restrict dmn, 105 enum MHD_Bool indicate_stale, 106 bool qop_none, 107 enum MHD_DigestAuthAlgo algo, 108 enum MHD_Bool userhash_support, 109 enum MHD_Bool prefer_utf8) 110 { 111 static const struct MHD_String empty_str = 112 mhd_MSTR_INIT (""); 113 static const struct MHD_String hdr_pref_realm_pref = 114 mhd_MSTR_INIT (MHD_HTTP_HEADER_WWW_AUTHENTICATE ": " \ 115 mhd_AUTH_DIGEST_SCHEME " realm=\""); 116 static const struct MHD_String qop_str = 117 mhd_MSTR_INIT (", qop=\"auth\""); 118 #ifdef MHD_SUPPORT_MD5 119 static const struct MHD_String algo_mh5_str = 120 mhd_MSTR_INIT (", algorithm=MD5"); 121 #endif 122 #ifdef MHD_SUPPORT_SHA256 123 static const struct MHD_String algo_sha256_str = 124 mhd_MSTR_INIT (", algorithm=SHA-256"); 125 #endif 126 #ifdef MHD_SUPPORT_SHA512_256 127 static const struct MHD_String algo_sha512_256_str = 128 mhd_MSTR_INIT (", algorithm=SHA-512-256"); 129 #endif 130 static const struct MHD_String nonce_str = 131 mhd_MSTR_INIT (", nonce=\"" \ 132 "################################" \ 133 "################################" \ 134 "########" \ 135 "\""); 136 static size_t nonce_off = 9; /* Position of nonce value in the nonce_str */ 137 static const struct MHD_String opaque_pref = 138 mhd_MSTR_INIT (", opaque=\""); 139 static const struct MHD_String domain_pref = 140 mhd_MSTR_INIT (", domain=\""); 141 static const struct MHD_String stale_str = 142 mhd_MSTR_INIT (", stale=true"); 143 static const struct MHD_String charset_str = 144 mhd_MSTR_INIT (", charset=UTF-8"); 145 static const struct MHD_String userhash_str = 146 mhd_MSTR_INIT (", userhash=true"); 147 148 /* Header content: 149 + Scheme name and space; 150 + realm, quoted; 151 + qop="auth", quoted (optional); 152 + algorithm, NOT quoted (optional); 153 + nonce (placeholder), quoted; 154 + opaque, quoted (optional); 155 + domain, quoted (optional); 156 + stale=true (optional); 157 + charset=UTF-8 (optional); 158 + userhash=true (optional). 159 */ 160 161 struct MHD_String algo_str; 162 char *hdr_str; 163 size_t hdr_maxlen; 164 struct mhd_RespAuthDigestHeader *new_hdr; 165 size_t pos; 166 size_t elm_len; 167 168 mhd_assert ('#' == nonce_str.cstr[nonce_off]); 169 mhd_assert ('#' == nonce_str.cstr[nonce_off + mhd_AUTH_DIGEST_NONCE_LEN - 1]); 170 mhd_assert ('"' == nonce_str.cstr[nonce_off - 1]); 171 mhd_assert ('"' == nonce_str.cstr[nonce_off + mhd_AUTH_DIGEST_NONCE_LEN]); 172 173 #ifdef MHD_SUPPORT_MD5 174 if (MHD_DIGEST_AUTH_ALGO_MD5 == algo) 175 algo_str = qop_none ? empty_str : algo_mh5_str; 176 #endif 177 #ifdef MHD_SUPPORT_SHA256 178 else if (MHD_DIGEST_AUTH_ALGO_SHA256 == algo) 179 algo_str = algo_sha256_str; 180 #endif 181 #ifdef MHD_SUPPORT_SHA512_256 182 else if (MHD_DIGEST_AUTH_ALGO_SHA512_256 == algo) 183 algo_str = algo_sha512_256_str; 184 #endif 185 else 186 { 187 mhd_UNREACHABLE (); 188 algo_str = empty_str; /* Mute compiler warning */ 189 } 190 191 /* ** Calculate the maximum length of the header string ** */ 192 hdr_maxlen = 0; 193 194 /* realm */ 195 hdr_maxlen += hdr_pref_realm_pref.len; 196 hdr_maxlen += rlm->len * 2; /* Double length for quoting */ 197 hdr_maxlen += 1; /* closing quote (") */ 198 199 /* qop */ 200 hdr_maxlen += qop_str.len; 201 202 /* algorithm */ 203 hdr_maxlen += algo_str.len; 204 205 /* nonce */ 206 hdr_maxlen += nonce_str.len; 207 208 /* opaque */ 209 hdr_maxlen += opaque_pref.len; 210 hdr_maxlen += opq->len * 2; /* Double length for quoting */ 211 hdr_maxlen += 1; /* closing quote (") */ 212 213 /* domain */ 214 hdr_maxlen += domain_pref.len; 215 hdr_maxlen += dmn->len; 216 hdr_maxlen += 1; /* closing quote (") */ 217 218 /* stale */ 219 hdr_maxlen += stale_str.len; 220 221 /* charset */ 222 hdr_maxlen += charset_str.len; 223 224 /* userhash */ 225 hdr_maxlen += userhash_str.len; 226 227 /* CRLF */ 228 hdr_maxlen += 2; 229 230 /* ** Allocate ** */ 231 new_hdr = (struct mhd_RespAuthDigestHeader *) 232 malloc (sizeof(struct mhd_RespAuthDigestHeader) 233 + hdr_maxlen + 1); 234 if (NULL == new_hdr) 235 return MHD_SC_RESPONSE_HEADER_MEM_ALLOC_FAILED; 236 hdr_str = (char *) (new_hdr + 1); 237 238 /* ** Build the header ** */ 239 pos = 0; 240 241 /* realm */ 242 memcpy (hdr_str + pos, 243 hdr_pref_realm_pref.cstr, 244 hdr_pref_realm_pref.len); 245 pos += hdr_pref_realm_pref.len; 246 elm_len = mhd_str_quote (rlm->cstr, 247 rlm->len, 248 hdr_str + pos, 249 hdr_maxlen - pos); 250 mhd_assert (0 != elm_len); 251 pos += elm_len; 252 hdr_str[pos++] = '"'; 253 254 /* qop */ 255 if (! qop_none) 256 { 257 memcpy (hdr_str + pos, 258 qop_str.cstr, 259 qop_str.len); 260 pos += qop_str.len; 261 } 262 263 /* algorithm */ 264 if (0 != algo_str.len) 265 { 266 memcpy (hdr_str + pos, 267 algo_str.cstr, 268 algo_str.len); 269 pos += algo_str.len; 270 } 271 272 /* nonce */ 273 memcpy (hdr_str + pos, 274 nonce_str.cstr, 275 nonce_str.len); 276 new_hdr->nonce_pos = pos + nonce_off; 277 pos += nonce_str.len; 278 279 /* opaque */ 280 if (0 != opq->len) 281 { 282 memcpy (hdr_str + pos, 283 opaque_pref.cstr, 284 opaque_pref.len); 285 pos += opaque_pref.len; 286 elm_len = mhd_str_quote (opq->cstr, 287 opq->len, 288 hdr_str + pos, 289 hdr_maxlen - pos); 290 mhd_assert (0 != elm_len); 291 pos += elm_len; 292 hdr_str[pos++] = '"'; 293 } 294 295 /* domain */ 296 if (0 != dmn->len) 297 { 298 memcpy (hdr_str + pos, 299 domain_pref.cstr, 300 domain_pref.len); 301 pos += domain_pref.len; 302 memcpy (hdr_str + pos, 303 dmn->cstr, 304 dmn->len); 305 pos += dmn->len; 306 hdr_str[pos++] = '"'; 307 } 308 309 /* stale */ 310 if (MHD_NO != indicate_stale) 311 { 312 memcpy (hdr_str + pos, 313 stale_str.cstr, 314 stale_str.len); 315 pos += stale_str.len; 316 } 317 318 /* charset */ 319 if ((! qop_none) && 320 (MHD_NO != prefer_utf8)) 321 { 322 memcpy (hdr_str + pos, 323 charset_str.cstr, 324 charset_str.len); 325 pos += charset_str.len; 326 } 327 328 /* userhash */ 329 if ((! qop_none) && 330 (MHD_NO != userhash_support)) 331 { 332 memcpy (hdr_str + pos, 333 userhash_str.cstr, 334 userhash_str.len); 335 pos += userhash_str.len; 336 } 337 338 /* CRLF */ 339 hdr_str[pos++] = '\r'; 340 hdr_str[pos++] = '\n'; 341 342 mhd_assert (pos <= hdr_maxlen); 343 hdr_str[pos] = 0; /* Zero-terminate the string */ 344 345 if (1) 346 { /* Try to shrink malloc'ed area */ 347 void *new_ptr; 348 new_ptr = realloc (new_hdr, 349 sizeof(struct mhd_RespAuthDigestHeader) 350 + pos + 1); 351 /* Just use the old pointer if realloc() failed */ 352 if (NULL != new_ptr) 353 new_hdr = (struct mhd_RespAuthDigestHeader *) new_ptr; 354 } 355 356 new_hdr->hdr.cstr = (char *) (new_hdr + 1); 357 new_hdr->hdr.len = pos; 358 mhd_assert (0 == \ 359 memcmp (new_hdr->hdr.cstr, \ 360 MHD_HTTP_HEADER_WWW_AUTHENTICATE ": ", \ 361 mhd_SSTR_LEN (MHD_HTTP_HEADER_WWW_AUTHENTICATE ": "))); 362 mhd_assert (0 == new_hdr->hdr.cstr[new_hdr->hdr.len]); 363 mhd_assert ('\r' == new_hdr->hdr.cstr[new_hdr->hdr.len - 2]); 364 mhd_assert ('\n' == new_hdr->hdr.cstr[new_hdr->hdr.len - 1]); 365 366 mhd_DLINKEDL_INIT_LINKS (new_hdr, auth_d_hdrs); 367 mhd_DLINKEDL_INS_LAST (response, new_hdr, auth_d_hdrs); 368 369 return MHD_SC_OK; 370 } 371 372 373 /** 374 * Create and add Digest Auth challenge headers for all specified algorithms 375 * @param response the response to update 376 * @param realm the real to use 377 * @param opaque the "opaque" string, could be NULL 378 * @param domain the "domain" string, could be NULL 379 * @param indicate_stale whether to indicate "stale" nonce 380 * @param mqop the QOP values to use 381 * @param malgo the algorithms to use 382 * @param userhash_support whether to indicate support for "userhash" 383 * @param prefer_utf8 whether to indicate UTF-8 support 384 * @return #MHD_SC_OK on success, 385 * error code otherwise 386 */ 387 static MHD_FN_PAR_NONNULL_ (1) 388 MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) 389 MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) enum MHD_StatusCode 390 response_add_auth_digest_challenge_int (struct MHD_Response *restrict response, 391 const char *restrict realm, 392 const char *restrict opaque, 393 const char *restrict domain, 394 enum MHD_Bool indicate_stale, 395 enum MHD_DigestAuthMultiQOP mqop, 396 enum MHD_DigestAuthMultiAlgo malgo, 397 enum MHD_Bool userhash_support, 398 enum MHD_Bool prefer_utf8) 399 { 400 struct MHD_String rlm; 401 struct MHD_StringNullable opq = {0, NULL }; 402 struct MHD_StringNullable dmn = {0, NULL}; 403 const bool qop_none = 404 (0 != (MHD_DIGEST_AUTH_QOP_NONE & ((unsigned int) mqop))); 405 enum MHD_StatusCode res; 406 407 rlm.len = strlen (realm); 408 rlm.cstr = realm; 409 if (NULL != opaque) 410 { 411 opq.len = strlen (opaque); 412 opq.cstr = opaque; 413 } 414 if (NULL != domain) 415 { 416 dmn.len = strlen (domain); 417 dmn.cstr = domain; 418 } 419 420 /* Check validity of the input data */ 421 422 if (0 == rlm.len) 423 return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */ 424 if ((NULL != memchr (rlm.cstr, '\n', rlm.len)) || 425 (NULL != memchr (rlm.cstr, '\r', rlm.len))) 426 return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */ 427 428 if ((0 != opq.len) && 429 ((NULL != memchr (opq.cstr, '\n', opq.len)) || 430 (NULL != memchr (opq.cstr, '\r', opq.len)))) 431 return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */ 432 433 if ((0 != dmn.len) && 434 ((NULL != memchr (dmn.cstr, '\n', dmn.len)) || 435 (NULL != memchr (dmn.cstr, '\r', dmn.len)) || 436 (NULL != memchr (dmn.cstr, '"', dmn.len)))) 437 return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */ 438 439 if (0 == (MHD_DIGEST_AUTH_ALGO_NON_SESSION & ((unsigned int) malgo))) 440 return MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED; /* Failure exit point */ 441 442 if (0 == ((MHD_DIGEST_AUTH_QOP_NONE | MHD_DIGEST_AUTH_QOP_AUTH) 443 & ((unsigned int) mqop))) 444 return MHD_SC_AUTH_DIGEST_QOP_NOT_SUPPORTED; /* Failure exit point */ 445 446 res = MHD_SC_OK; 447 448 #ifdef MHD_SUPPORT_MD5 449 if ((MHD_SC_OK == res) && 450 (0 != (MHD_DIGEST_BASE_ALGO_MD5 & ((unsigned int) malgo)))) 451 res = response_add_auth_digest_challenge_alg (response, 452 &rlm, 453 &opq, 454 &dmn, 455 indicate_stale, 456 qop_none, 457 MHD_DIGEST_AUTH_ALGO_MD5, 458 userhash_support, 459 prefer_utf8); 460 #endif 461 #ifdef MHD_SUPPORT_SHA256 462 if ((MHD_SC_OK == res) && 463 (0 != (MHD_DIGEST_BASE_ALGO_SHA256 & ((unsigned int) malgo)))) 464 res = response_add_auth_digest_challenge_alg (response, 465 &rlm, 466 &opq, 467 &dmn, 468 indicate_stale, 469 qop_none, 470 MHD_DIGEST_AUTH_ALGO_SHA256, 471 userhash_support, 472 prefer_utf8); 473 #endif 474 #ifdef MHD_SUPPORT_SHA512_256 475 if ((MHD_SC_OK == res) && 476 (0 != (MHD_DIGEST_BASE_ALGO_SHA512_256 & ((unsigned int) malgo)))) 477 res = response_add_auth_digest_challenge_alg ( 478 response, 479 &rlm, 480 &opq, 481 &dmn, 482 indicate_stale, 483 qop_none, 484 MHD_DIGEST_AUTH_ALGO_SHA512_256, 485 userhash_support, 486 prefer_utf8); 487 #endif 488 489 if (MHD_SC_OK != res) 490 { 491 mhd_response_remove_auth_digest_headers (response); 492 return res; /* Failure exit point */ 493 } 494 495 if (NULL == mhd_DLINKEDL_GET_FIRST (response, auth_d_hdrs)) 496 return MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED; /* Failure exit point */ 497 498 return MHD_SC_OK; /* Success exit point */ 499 } 500 501 502 MHD_EXTERN_ 503 MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) 504 MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) enum MHD_StatusCode 505 MHD_response_add_auth_digest_challenge ( 506 struct MHD_Response *MHD_RESTRICT response, 507 const char *MHD_RESTRICT realm, 508 const char *MHD_RESTRICT opaque, 509 const char *MHD_RESTRICT domain, 510 enum MHD_Bool indicate_stale, 511 enum MHD_DigestAuthMultiQOP mqop, 512 enum MHD_DigestAuthMultiAlgo malgo, 513 enum MHD_Bool userhash_support, 514 enum MHD_Bool prefer_utf8) 515 { 516 bool need_unlock; 517 enum MHD_StatusCode res; 518 519 if (NULL == response) 520 return MHD_SC_RESP_POINTER_NULL; 521 if (response->frozen) 522 return MHD_SC_TOO_LATE; 523 if (MHD_HTTP_STATUS_UNAUTHORIZED != response->sc) 524 return MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE; 525 526 if (response->reuse.reusable) 527 { 528 need_unlock = true; 529 if (! mhd_mutex_lock (&(response->reuse.settings_lock))) 530 return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED; 531 mhd_assert (1 == mhd_atomic_counter_get (&(response->reuse.counter))); 532 } 533 else 534 need_unlock = false; 535 536 if (response->frozen) /* Re-check with the lock held */ 537 res = MHD_SC_TOO_LATE; 538 else if (NULL != mhd_DLINKEDL_GET_FIRST (response, auth_d_hdrs)) 539 res = MHD_SC_RESP_HEADERS_CONFLICT; 540 else 541 res = response_add_auth_digest_challenge_int (response, 542 realm, 543 opaque, 544 domain, 545 indicate_stale, 546 mqop, 547 malgo, 548 userhash_support, 549 prefer_utf8); 550 551 if (need_unlock) 552 mhd_mutex_unlock_chk (&(response->reuse.settings_lock)); 553 554 return res; 555 }