1 /* 2 * http_auth - HTTP authentication and authorization 3 * 4 * Largely-rewritten from original 5 * Copyright(c) 2016,2021 Glenn Strauss gstrauss()gluelogic.com All rights reserved 6 * License: BSD 3-clause (same as lighttpd) 7 */ 8 #include "first.h" 9 10 #include <stdlib.h> 11 #include <string.h> 12 13 #include "mod_auth_api.h" 14 #include "sys-crypto-md.h" /* USE_LIB_CRYPTO */ 15 16 #include "base.h" 17 #include "ck.h" 18 #include "http_header.h" 19 #include "log.h" 20 #include "algo_splaytree.h" 21 #include "plugin.h" 22 #include "plugin_config.h" 23 24 /** 25 * auth framework 26 */ 27 28 typedef struct { 29 splay_tree *sptree; /* data in nodes of tree are (http_auth_cache_entry *)*/ 30 time_t max_age; 31 } http_auth_cache; 32 33 typedef struct { 34 const http_auth_backend_t *auth_backend; 35 const array *auth_require; 36 http_auth_cache *auth_cache; 37 unsigned int auth_extern_authn; 38 } plugin_config; 39 40 typedef struct { 41 PLUGIN_DATA; 42 plugin_config defaults; 43 plugin_config conf; 44 } plugin_data; 45 46 typedef struct { 47 const struct http_auth_require_t *require; 48 unix_time64_t ctime; 49 int dalgo; 50 uint32_t dlen; 51 uint32_t ulen; 52 uint32_t klen; 53 char *k; 54 char *username; 55 char *pwdigest; 56 } http_auth_cache_entry; 57 58 static http_auth_cache_entry * 59 http_auth_cache_entry_init (const struct http_auth_require_t * const require, const int dalgo, const char *k, const uint32_t klen, const char *username, const uint32_t ulen, const char *pw, const uint32_t pwlen) 60 { 61 /*(similar to buffer_copy_string_len() for each element, 62 * but allocate exact lengths in single chunk of memory 63 * for cache to avoid wasting space and for memory locality)*/ 64 /* http_auth_require_t is stored instead of copying realm 65 *(store pointer to http_auth_require_t, which is persistent 66 * and will be different for each realm + permissions combo)*/ 67 http_auth_cache_entry * const ae = 68 malloc(sizeof(http_auth_cache_entry) + ulen + pwlen 69 + (k == username ? 0 : klen)); 70 force_assert(ae); 71 ae->require = require; 72 ae->ctime = log_monotonic_secs; 73 ae->dalgo = dalgo; 74 ae->ulen = ulen; 75 ae->dlen = pwlen; 76 ae->klen = klen; 77 ae->username = (char *)(ae + 1); 78 ae->pwdigest = ae->username + ulen; 79 ae->k = (k == username) 80 ? ae->username 81 : memcpy(ae->pwdigest + pwlen, k, klen); 82 memcpy(ae->username, username, ulen); 83 memcpy(ae->pwdigest, pw, pwlen); 84 return ae; 85 } 86 87 static void 88 http_auth_cache_entry_free (void *data) 89 { 90 http_auth_cache_entry * const ae = data; 91 ck_memzero(ae->pwdigest, ae->dlen); 92 free(ae); 93 } 94 95 static void 96 http_auth_cache_free (http_auth_cache *ac) 97 { 98 splay_tree *sptree = ac->sptree; 99 while (sptree) { 100 http_auth_cache_entry_free(sptree->data); 101 sptree = splaytree_delete(sptree, sptree->key); 102 } 103 free(ac); 104 } 105 106 static http_auth_cache * 107 http_auth_cache_init (const array *opts) 108 { 109 http_auth_cache *ac = malloc(sizeof(http_auth_cache)); 110 force_assert(ac); 111 ac->sptree = NULL; 112 ac->max_age = 600; /* 10 mins */ 113 for (uint32_t i = 0, used = opts->used; i < used; ++i) { 114 data_unset *du = opts->data[i]; 115 if (buffer_is_equal_string(&du->key, CONST_STR_LEN("max-age"))) 116 ac->max_age = (time_t)config_plugin_value_to_int32(du, ac->max_age); 117 } 118 return ac; 119 } 120 121 static int 122 http_auth_cache_hash (const struct http_auth_require_t * const require, const char *username, const uint32_t ulen) 123 { 124 uint32_t h = /*(hash pointer value, which includes realm and permissions)*/ 125 djbhash((char *)(intptr_t)require, sizeof(intptr_t), DJBHASH_INIT); 126 h = djbhash(username, ulen, h); 127 /* strip highest bit of hash value for splaytree (see splaytree_djbhash())*/ 128 return (int32_t)(h & ~(((uint32_t)1) << 31)); 129 } 130 131 static http_auth_cache_entry * 132 http_auth_cache_query (splay_tree ** const sptree, const int ndx) 133 { 134 *sptree = splaytree_splay(*sptree, ndx); 135 return (*sptree && (*sptree)->key == ndx) ? (*sptree)->data : NULL; 136 } 137 138 static void 139 http_auth_cache_insert (splay_tree ** const sptree, const int ndx, void * const data, void(data_free_fn)(void *)) 140 { 141 /*(not necessary to re-splay (with current usage) since single-threaded 142 * and splaytree has not been modified since http_auth_cache_query())*/ 143 /* *sptree = splaytree_splay(*sptree, ndx); */ 144 if (NULL == *sptree || (*sptree)->key != ndx) 145 *sptree = splaytree_insert(*sptree, ndx, data); 146 else { /* collision; replace old entry */ 147 data_free_fn((*sptree)->data); 148 (*sptree)->data = data; 149 } 150 } 151 152 /* walk though cache, collect expired ids, and remove them in a second loop */ 153 static void 154 mod_auth_tag_old_entries (splay_tree * const t, int * const keys, int * const ndx, const time_t max_age, const unix_time64_t cur_ts) 155 { 156 if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ 157 if (t->left) 158 mod_auth_tag_old_entries(t->left, keys, ndx, max_age, cur_ts); 159 if (t->right) 160 mod_auth_tag_old_entries(t->right, keys, ndx, max_age, cur_ts); 161 if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ 162 163 const http_auth_cache_entry * const ae = t->data; 164 if (cur_ts - ae->ctime > max_age) 165 keys[(*ndx)++] = t->key; 166 } 167 168 __attribute_noinline__ 169 static void 170 mod_auth_periodic_cleanup(splay_tree **sptree_ptr, const time_t max_age, const unix_time64_t cur_ts) 171 { 172 splay_tree *sptree = *sptree_ptr; 173 int max_ndx, i; 174 int keys[8192]; /* 32k size on stack */ 175 do { 176 if (!sptree) break; 177 max_ndx = 0; 178 mod_auth_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts); 179 for (i = 0; i < max_ndx; ++i) { 180 int ndx = keys[i]; 181 sptree = splaytree_splay(sptree, ndx); 182 if (sptree && sptree->key == ndx) { 183 http_auth_cache_entry_free(sptree->data); 184 sptree = splaytree_delete(sptree, ndx); 185 } 186 } 187 } while (max_ndx == sizeof(keys)/sizeof(int)); 188 *sptree_ptr = sptree; 189 } 190 191 TRIGGER_FUNC(mod_auth_periodic) 192 { 193 const plugin_data * const p = p_d; 194 const unix_time64_t cur_ts = log_monotonic_secs; 195 if (cur_ts & 0x7) return HANDLER_GO_ON; /*(continue once each 8 sec)*/ 196 UNUSED(srv); 197 198 /* future: might construct array of (http_auth_cache *) at startup 199 * to avoid the need to search for them here */ 200 /* (init i to 0 if global context; to 1 to skip empty global context) */ 201 if (NULL == p->cvlist) return HANDLER_GO_ON; 202 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 203 const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 204 for (; cpv->k_id != -1; ++cpv) { 205 if (cpv->k_id != 3) continue; /* k_id == 3 for auth.cache */ 206 if (cpv->vtype != T_CONFIG_LOCAL) continue; 207 http_auth_cache *ac = cpv->v.v; 208 mod_auth_periodic_cleanup(&ac->sptree, ac->max_age, cur_ts); 209 } 210 } 211 212 return HANDLER_GO_ON; 213 } 214 215 216 217 218 static handler_t mod_auth_check_basic(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend); 219 static handler_t mod_auth_check_digest(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend); 220 static handler_t mod_auth_check_extern(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend); 221 222 INIT_FUNC(mod_auth_init) { 223 static http_auth_scheme_t http_auth_scheme_basic = { "basic", mod_auth_check_basic, NULL }; 224 static http_auth_scheme_t http_auth_scheme_digest = { "digest", mod_auth_check_digest, NULL }; 225 static const http_auth_scheme_t http_auth_scheme_extern = { "extern", mod_auth_check_extern, NULL }; 226 plugin_data *p = calloc(1, sizeof(*p)); 227 force_assert(p); 228 229 /* register http_auth_scheme_* */ 230 http_auth_scheme_basic.p_d = p; 231 http_auth_scheme_set(&http_auth_scheme_basic); 232 http_auth_scheme_digest.p_d = p; 233 http_auth_scheme_set(&http_auth_scheme_digest); 234 http_auth_scheme_set(&http_auth_scheme_extern); 235 236 return p; 237 } 238 239 FREE_FUNC(mod_auth_free) { 240 plugin_data * const p = p_d; 241 if (NULL == p->cvlist) return; 242 /* (init i to 0 if global context; to 1 to skip empty global context) */ 243 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 244 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 245 for (; -1 != cpv->k_id; ++cpv) { 246 if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue; 247 switch (cpv->k_id) { 248 case 1: /* auth.require */ 249 array_free(cpv->v.v); 250 break; 251 case 3: /* auth.cache */ 252 http_auth_cache_free(cpv->v.v); 253 break; 254 default: 255 break; 256 } 257 } 258 } 259 260 http_auth_dumbdata_reset(); 261 } 262 263 /* data type for mod_auth structured data 264 * (parsed from auth.require array of strings) */ 265 typedef struct { 266 DATA_UNSET; 267 http_auth_require_t *require; 268 } data_auth; 269 270 static void data_auth_free(data_unset *d) 271 { 272 data_auth * const dauth = (data_auth *)d; 273 free(dauth->key.ptr); 274 http_auth_require_free(dauth->require); 275 free(dauth); 276 } 277 278 static data_auth *data_auth_init(void) 279 { 280 static const struct data_methods fn = { 281 NULL, /* copy must not be called on this data */ 282 data_auth_free, 283 NULL, /* insert_dup must not be called on this data */ 284 }; 285 data_auth * const dauth = calloc(1, sizeof(*dauth)); 286 force_assert(NULL != dauth); 287 dauth->type = TYPE_OTHER; 288 dauth->fn = &fn; 289 290 dauth->require = http_auth_require_init(); 291 292 return dauth; 293 } 294 295 static int mod_auth_algorithm_parse(http_auth_info_t *ai, const char *s, size_t len) { 296 if (0 == len) { 297 ai->dalgo = HTTP_AUTH_DIGEST_MD5; 298 ai->dlen = HTTP_AUTH_DIGEST_MD5_BINLEN; 299 return 1; 300 } 301 302 if (len > 5 303 && (s[len-5] ) == '-' 304 && (s[len-4] | 0x20) == 's' 305 && (s[len-3] | 0x20) == 'e' 306 && (s[len-2] | 0x20) == 's' 307 && (s[len-1] | 0x20) == 's') { 308 ai->dalgo = HTTP_AUTH_DIGEST_SESS; 309 len -= 5; 310 } 311 else { 312 ai->dalgo = HTTP_AUTH_DIGEST_NONE; 313 } 314 315 if (3 == len 316 && 'm' == (s[0] | 0x20) 317 && 'd' == (s[1] | 0x20) 318 && '5' == (s[2] )) { 319 ai->dalgo |= HTTP_AUTH_DIGEST_MD5; 320 ai->dlen = HTTP_AUTH_DIGEST_MD5_BINLEN; 321 return 1; 322 } 323 #ifdef USE_LIB_CRYPTO 324 else if (len >= 7 325 && 's' == (s[0] | 0x20) 326 && 'h' == (s[1] | 0x20) 327 && 'a' == (s[2] | 0x20) 328 && '-' == (s[3] )) { 329 if (len == 7 && s[4] == '2' && s[5] == '5' && s[6] == '6') { 330 ai->dalgo |= HTTP_AUTH_DIGEST_SHA256; 331 ai->dlen = HTTP_AUTH_DIGEST_SHA256_BINLEN; 332 return 1; 333 } 334 #ifdef USE_LIB_CRYPTO_SHA512_256 335 if (len == 11 && 0 == memcmp(s+4, "512-256", 7)) { 336 ai->dalgo |= HTTP_AUTH_DIGEST_SHA512_256; 337 ai->dlen = HTTP_AUTH_DIGEST_SHA512_256_BINLEN; 338 return 1; 339 } 340 #endif 341 } 342 #endif 343 return 0; /*(error)*/ 344 } 345 346 static int mod_auth_algorithms_parse(int *algorithm, buffer *algos) { 347 for (const char *s = algos->ptr, *p; s; s = p ? p+1 : NULL) { 348 http_auth_info_t ai; 349 p = strchr(s, '|'); 350 if (!mod_auth_algorithm_parse(&ai, s, p ? (size_t)(p - s) : strlen(s))) 351 return 0; 352 *algorithm |= ai.dalgo; 353 } 354 return 1; 355 } 356 357 static int mod_auth_require_parse (http_auth_require_t * const require, const buffer *b, log_error_st *errh) 358 { 359 /* user=name1|user=name2|group=name3|host=name4 */ 360 361 const char *str = b->ptr; 362 const char *p; 363 364 if (buffer_is_equal_string(b, CONST_STR_LEN("valid-user"))) { 365 require->valid_user = 1; 366 return 1; /* success */ 367 } 368 369 do { 370 const char *eq; 371 size_t len; 372 p = strchr(str, '|'); 373 len = NULL != p ? (size_t)(p - str) : strlen(str); 374 eq = memchr(str, '=', len); 375 if (NULL == eq) { 376 log_error(errh, __FILE__, __LINE__, 377 "error parsing auth.require 'require' field: missing '=' " 378 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\"). " 379 "error value: %s error near: %s", b->ptr, str); 380 return 0; 381 } 382 if (eq[1] == '|' || eq[1] == '\0') { 383 log_error(errh, __FILE__, __LINE__, 384 "error parsing auth.require 'require' field: " 385 "missing token after '=' " 386 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\"). " 387 "error value: %s error near: %s", b->ptr, str); 388 return 0; 389 } 390 391 switch ((int)(eq - str)) { 392 case 4: 393 if (0 == memcmp(str, CONST_STR_LEN("user"))) { 394 /*("user=" is 5)*/ 395 array_insert_value(&require->user, str+5, len-5); 396 continue; 397 } 398 else if (0 == memcmp(str, CONST_STR_LEN("host"))) { 399 /*("host=" is 5)*/ 400 array_insert_value(&require->host, str+5, len-5); 401 log_error(errh, __FILE__, __LINE__, 402 "warning parsing auth.require 'require' field: " 403 "'host' not implemented; field value: %s", b->ptr); 404 continue; 405 } 406 break; /* to error */ 407 case 5: 408 if (0 == memcmp(str, CONST_STR_LEN("group"))) { 409 /*("group=" is 6)*/ 410 array_insert_value(&require->group, str+6, len-6); 411 #if 0/*(supported by mod_authn_ldap, but not all other backends)*/ 412 log_error(errh, __FILE__, __LINE__, 413 "warning parsing auth.require 'require' field: " 414 "'group' not implemented; field value: %s", b->ptr); 415 #endif 416 continue; 417 } 418 break; /* to error */ 419 case 10: 420 if (0 == memcmp(str, CONST_STR_LEN("valid-user"))) { 421 log_error(errh, __FILE__, __LINE__, 422 "error parsing auth.require 'require' field: " 423 "valid user can not be combined with other require rules " 424 "(expecting \"valid-user\" or " 425 "\"user=a|user=b|group=g|host=h\"). error value: %s", b->ptr); 426 return 0; 427 } 428 break; /* to error */ 429 default: 430 break; /* to error */ 431 } 432 433 log_error(errh, __FILE__, __LINE__, 434 "error parsing auth.require 'require' field: " 435 "invalid/unsupported token " 436 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\"). " 437 "error value: %s error near: %s", b->ptr, str); 438 return 0; 439 440 } while (p && *((str = p+1))); 441 442 return 1; /* success */ 443 } 444 445 static handler_t mod_auth_require_parse_array(const array *value, array * const auth_require, log_error_st *errh) 446 { 447 for (uint32_t n = 0; n < value->used; ++n) { 448 size_t m; 449 data_array *da_file = (data_array *)value->data[n]; 450 const buffer *method = NULL, *realm = NULL, *require = NULL; 451 const buffer *nonce_secret = NULL; 452 data_unset *userhash = NULL; 453 const http_auth_scheme_t *auth_scheme; 454 buffer *algos = NULL; 455 int algorithm = HTTP_AUTH_DIGEST_SESS; 456 457 if (!array_is_kvstring(&da_file->value)) { 458 log_error(errh, __FILE__, __LINE__, 459 "unexpected value for auth.require; expected " 460 "auth.require = ( \"urlpath\" => ( \"option\" => \"value\" ) )"); 461 462 return HANDLER_ERROR; 463 } 464 465 for (m = 0; m < da_file->value.used; m++) { 466 if (da_file->value.data[m]->type == TYPE_STRING) { 467 data_string *ds = (data_string *)da_file->value.data[m]; 468 if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("method"))) { 469 method = &ds->value; 470 } else if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("realm"))) { 471 realm = &ds->value; 472 } else if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("require"))) { 473 require = &ds->value; 474 } else if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("algorithm"))) { 475 algos = &ds->value; 476 } else if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("nonce_secret")) 477 || buffer_is_equal_string(&ds->key, CONST_STR_LEN("nonce-secret"))) { 478 nonce_secret = &ds->value; 479 } else if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("userhash"))) { 480 userhash = (data_unset *)ds; 481 } else { 482 log_error(errh, __FILE__, __LINE__, 483 "the field is unknown in: " 484 "auth.require = ( \"...\" => ( ..., -> \"%s\" <- => \"...\" ) )", 485 da_file->value.data[m]->key.ptr); 486 487 return HANDLER_ERROR; 488 } 489 } else { 490 log_error(errh, __FILE__, __LINE__, 491 "a string was expected for: " 492 "auth.require = ( \"...\" => ( ..., -> \"%s\" <- => \"...\" ) )", 493 da_file->value.data[m]->key.ptr); 494 495 return HANDLER_ERROR; 496 } 497 } 498 499 if (!method || buffer_is_blank(method)) { 500 log_error(errh, __FILE__, __LINE__, 501 "the method field is missing or blank in: " 502 "auth.require = ( \"...\" => ( ..., \"method\" => \"...\" ) )"); 503 return HANDLER_ERROR; 504 } else { 505 auth_scheme = http_auth_scheme_get(method); 506 if (NULL == auth_scheme) { 507 log_error(errh, __FILE__, __LINE__, 508 "unknown method %s (e.g. \"basic\", \"digest\" or \"extern\") in " 509 "auth.require = ( \"...\" => ( ..., \"method\" => \"...\") )", method->ptr); 510 return HANDLER_ERROR; 511 } 512 } 513 514 if (!realm) { 515 log_error(errh, __FILE__, __LINE__, 516 "the realm field is missing in: " 517 "auth.require = ( \"...\" => ( ..., \"realm\" => \"...\" ) )"); 518 return HANDLER_ERROR; 519 } 520 521 if (!require || buffer_is_blank(require)) { 522 log_error(errh, __FILE__, __LINE__, 523 "the require field is missing or blank in: " 524 "auth.require = ( \"...\" => ( ..., \"require\" => \"...\" ) )"); 525 return HANDLER_ERROR; 526 } 527 528 if (!algos || buffer_is_blank(algos)) { 529 algorithm |= HTTP_AUTH_DIGEST_MD5; 530 } else if (!mod_auth_algorithms_parse(&algorithm, algos)) { 531 log_error(errh, __FILE__, __LINE__, 532 "invalid algorithm in: " 533 "auth.require = ( \"...\" => ( ..., \"algorithm\" => \"...\" ) )"); 534 return HANDLER_ERROR; 535 } 536 537 if (require) { /*(always true at this point)*/ 538 data_auth * const dauth = data_auth_init(); 539 buffer_copy_buffer(&dauth->key, &da_file->key); 540 dauth->require->scheme = auth_scheme; 541 dauth->require->algorithm = algorithm; 542 dauth->require->realm = realm; 543 dauth->require->nonce_secret = nonce_secret; /*(NULL is ok)*/ 544 dauth->require->userhash = config_plugin_value_tobool(userhash, 0); 545 if (!mod_auth_require_parse(dauth->require, require, errh)) { 546 dauth->fn->free((data_unset *)dauth); 547 return HANDLER_ERROR; 548 } 549 array_insert_unique(auth_require, (data_unset *)dauth); 550 } 551 } 552 553 return HANDLER_GO_ON; 554 } 555 556 static void mod_auth_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 557 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 558 case 0: /* auth.backend */ 559 if (cpv->vtype == T_CONFIG_LOCAL) 560 pconf->auth_backend = cpv->v.v; 561 break; 562 case 1: /* auth.require */ 563 if (cpv->vtype == T_CONFIG_LOCAL) 564 pconf->auth_require = cpv->v.v; 565 break; 566 case 2: /* auth.extern-authn */ 567 pconf->auth_extern_authn = cpv->v.u; 568 break; 569 case 3: /* auth.cache */ 570 if (cpv->vtype == T_CONFIG_LOCAL) 571 pconf->auth_cache = cpv->v.v; 572 break; 573 default:/* should not happen */ 574 return; 575 } 576 } 577 578 static void mod_auth_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 579 do { 580 mod_auth_merge_config_cpv(pconf, cpv); 581 } while ((++cpv)->k_id != -1); 582 } 583 584 static void mod_auth_patch_config(request_st * const r, plugin_data * const p) { 585 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 586 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 587 for (int i = 1, used = p->nconfig; i < used; ++i) { 588 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 589 mod_auth_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]); 590 } 591 } 592 593 SETDEFAULTS_FUNC(mod_auth_set_defaults) { 594 static const config_plugin_keys_t cpk[] = { 595 { CONST_STR_LEN("auth.backend"), 596 T_CONFIG_STRING, 597 T_CONFIG_SCOPE_CONNECTION } 598 ,{ CONST_STR_LEN("auth.require"), 599 T_CONFIG_ARRAY_KVARRAY, 600 T_CONFIG_SCOPE_CONNECTION } 601 ,{ CONST_STR_LEN("auth.extern-authn"), 602 T_CONFIG_BOOL, 603 T_CONFIG_SCOPE_CONNECTION } 604 ,{ CONST_STR_LEN("auth.cache"), 605 T_CONFIG_ARRAY, 606 T_CONFIG_SCOPE_CONNECTION } 607 ,{ NULL, 0, 608 T_CONFIG_UNSET, 609 T_CONFIG_SCOPE_UNSET } 610 }; 611 612 plugin_data * const p = p_d; 613 if (!config_plugin_values_init(srv, p, cpk, "mod_auth")) 614 return HANDLER_ERROR; 615 616 /* process and validate config directives 617 * (init i to 0 if global context; to 1 to skip empty global context) */ 618 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 619 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 620 for (; -1 != cpv->k_id; ++cpv) { 621 switch (cpv->k_id) { 622 case 0: /* auth.backend */ 623 if (!buffer_is_blank(cpv->v.b)) { 624 const http_auth_backend_t * const auth_backend = 625 http_auth_backend_get(cpv->v.b); 626 if (NULL == auth_backend) { 627 log_error(srv->errh, __FILE__, __LINE__, 628 "auth.backend not supported: %s", cpv->v.b->ptr); 629 return HANDLER_ERROR; 630 } 631 *(const http_auth_backend_t **)&cpv->v.v = auth_backend; 632 cpv->vtype = T_CONFIG_LOCAL; 633 } 634 break; 635 case 1: /* auth.require */ 636 { 637 array * const a = array_init(4); 638 if (HANDLER_GO_ON != 639 mod_auth_require_parse_array(cpv->v.a, a, srv->errh)) { 640 array_free(a); 641 return HANDLER_ERROR; 642 } 643 cpv->v.a = a; 644 cpv->vtype = T_CONFIG_LOCAL; 645 } 646 break; 647 case 2: /* auth.extern-authn */ 648 break; 649 case 3: /* auth.cache */ 650 cpv->v.v = http_auth_cache_init(cpv->v.a); 651 cpv->vtype = T_CONFIG_LOCAL; 652 break; 653 default:/* should not happen */ 654 break; 655 } 656 } 657 } 658 659 /* initialize p->defaults from global config context */ 660 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 661 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 662 if (-1 != cpv->k_id) 663 mod_auth_merge_config(&p->defaults, cpv); 664 } 665 666 return HANDLER_GO_ON; 667 } 668 669 static handler_t mod_auth_uri_handler(request_st * const r, void *p_d) { 670 plugin_data *p = p_d; 671 data_auth *dauth; 672 673 mod_auth_patch_config(r, p); 674 675 if (p->conf.auth_require == NULL) return HANDLER_GO_ON; 676 677 /* search auth directives for first prefix match against URL path */ 678 /* if we have a case-insensitive FS we have to lower-case the URI here too */ 679 dauth = (!r->conf.force_lowercase_filenames) 680 ? (data_auth *)array_match_key_prefix(p->conf.auth_require, &r->uri.path) 681 : (data_auth *)array_match_key_prefix_nc(p->conf.auth_require, &r->uri.path); 682 if (NULL == dauth) return HANDLER_GO_ON; 683 684 { 685 const http_auth_scheme_t * const scheme = dauth->require->scheme; 686 if (p->conf.auth_extern_authn) { 687 const buffer *vb = http_header_env_get(r, CONST_STR_LEN("REMOTE_USER")); 688 if (NULL != vb && http_auth_match_rules(dauth->require, vb->ptr, NULL, NULL)) { 689 return HANDLER_GO_ON; 690 } 691 } 692 return scheme->checkfn(r, scheme->p_d, dauth->require, p->conf.auth_backend); 693 } 694 } 695 696 697 int mod_auth_plugin_init(plugin *p); 698 int mod_auth_plugin_init(plugin *p) { 699 p->version = LIGHTTPD_VERSION_ID; 700 p->name = "auth"; 701 p->init = mod_auth_init; 702 p->set_defaults = mod_auth_set_defaults; 703 p->handle_trigger = mod_auth_periodic; 704 p->handle_uri_clean = mod_auth_uri_handler; 705 p->cleanup = mod_auth_free; 706 707 return 0; 708 } 709 710 711 712 713 /* 714 * auth schemes (basic, digest, extern) 715 * 716 * (could be in separate file from mod_auth.c as long as registration occurs) 717 */ 718 719 #include "sys-crypto-md.h" 720 #include "base64.h" 721 #include "rand.h" 722 #include "http_header.h" 723 724 __attribute_cold__ 725 __attribute_noinline__ 726 static handler_t 727 mod_auth_send_400_bad_request (request_st * const r) 728 { 729 /* a field was missing or invalid */ 730 r->http_status = 400; /* Bad Request */ 731 r->handler_module = NULL; 732 return HANDLER_FINISHED; 733 } 734 735 736 737 __attribute_noinline__ 738 static handler_t 739 mod_auth_send_401_unauthorized_basic (request_st * const r, const buffer * const realm) 740 { 741 r->http_status = 401; 742 r->handler_module = NULL; 743 buffer_append_str3( 744 http_header_response_set_ptr(r, HTTP_HEADER_WWW_AUTHENTICATE, 745 CONST_STR_LEN("WWW-Authenticate")), 746 CONST_STR_LEN("Basic realm=\""), 747 BUF_PTR_LEN(realm), 748 CONST_STR_LEN("\", charset=\"UTF-8\"")); 749 return HANDLER_FINISHED; 750 } 751 752 753 __attribute_cold__ 754 static handler_t 755 mod_auth_basic_misconfigured (request_st * const r, const struct http_auth_backend_t * const backend) 756 { 757 if (NULL == backend) 758 log_error(r->conf.errh, __FILE__, __LINE__, 759 "auth.backend not configured for %s", r->uri.path.ptr); 760 else 761 log_error(r->conf.errh, __FILE__, __LINE__, 762 "auth.require \"method\" => \"basic\" invalid " 763 "(try \"digest\"?) for %s", r->uri.path.ptr); 764 765 r->http_status = 500; 766 r->handler_module = NULL; 767 return HANDLER_FINISHED; 768 } 769 770 771 static handler_t 772 mod_auth_check_basic(request_st * const r, void *p_d, const struct http_auth_require_t * const require, const struct http_auth_backend_t * const backend) 773 { 774 if (NULL == backend || NULL == backend->basic) 775 return mod_auth_basic_misconfigured(r, backend); 776 777 const buffer * const vb = 778 http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, 779 CONST_STR_LEN("Authorization")); 780 if (NULL == vb || !buffer_eq_icase_ssn(vb->ptr, CONST_STR_LEN("Basic "))) 781 return mod_auth_send_401_unauthorized_basic(r, require->realm); 782 #ifdef __COVERITY__ 783 if (buffer_clen(vb) < sizeof("Basic ")-1) 784 return mod_auth_send_400_bad_request(r); 785 #endif 786 787 size_t ulen = buffer_clen(vb) - (sizeof("Basic ")-1); 788 size_t pwlen; 789 char *pw; 790 char user[1024]; 791 792 /* base64-decode Authorization into username:password string; 793 * limit base64-decoded username:password string to fit into 1k buf */ 794 if (ulen > 1363) /*(1363/4*3+3 = 1023)*/ 795 return mod_auth_send_401_unauthorized_basic(r, require->realm); 796 /* coverity[overflow_sink : FALSE] */ 797 ulen = li_base64_dec((unsigned char *)user, sizeof(user), 798 vb->ptr+sizeof("Basic ")-1, ulen, BASE64_STANDARD); 799 if (0 == ulen) { 800 log_error(r->conf.errh, __FILE__, __LINE__, 801 "decoding base64-string failed %s", vb->ptr+sizeof("Basic ")-1); 802 return mod_auth_send_400_bad_request(r); 803 } 804 user[ulen] = '\0'; 805 pw = memchr(user, ':', ulen); 806 if (NULL == pw) { 807 log_error(r->conf.errh, __FILE__, __LINE__, "missing ':' in %s", user); 808 return mod_auth_send_400_bad_request(r); 809 } 810 *pw++ = '\0'; 811 pwlen = (size_t)(user + ulen - pw); 812 ulen = (size_t)(pw - 1 - user); 813 814 plugin_data * const p = p_d; 815 splay_tree ** sptree = p->conf.auth_cache 816 ? &p->conf.auth_cache->sptree 817 : NULL; 818 http_auth_cache_entry *ae = NULL; 819 handler_t rc = HANDLER_ERROR; 820 int ndx = -1; 821 if (sptree) { 822 ndx = http_auth_cache_hash(require, user, ulen); 823 ae = http_auth_cache_query(sptree, ndx); 824 if (ae && ae->require == require 825 && ulen == ae->ulen && 0 == memcmp(user, ae->username, ulen)) 826 rc = ck_memeq_const_time(ae->pwdigest, ae->dlen, pw, pwlen) 827 ? HANDLER_GO_ON 828 : HANDLER_ERROR; 829 else /*(not found or hash collision)*/ 830 ae = NULL; 831 } 832 833 if (NULL == ae) { 834 const buffer userb = { user, ulen+1, 0 }; 835 rc = backend->basic(r, backend->p_d, require, &userb, pw); 836 } 837 838 switch (rc) { 839 case HANDLER_GO_ON: 840 http_auth_setenv(r, user, ulen, CONST_STR_LEN("Basic")); 841 if (sptree && NULL == ae) { /*(cache (new) successful result)*/ 842 ae = http_auth_cache_entry_init(require, 0, user, ulen, user, ulen, 843 pw, pwlen); 844 http_auth_cache_insert(sptree, ndx, ae, http_auth_cache_entry_free); 845 } 846 break; 847 case HANDLER_WAIT_FOR_EVENT: 848 case HANDLER_FINISHED: 849 break; 850 case HANDLER_ERROR: 851 default: 852 log_error(r->conf.errh, __FILE__, __LINE__, 853 "password doesn't match for %s username: %s IP: %s", 854 r->uri.path.ptr, user, r->con->dst_addr_buf.ptr); 855 r->keep_alive = -1; /*(disable keep-alive if bad password)*/ 856 rc = mod_auth_send_401_unauthorized_basic(r, require->realm); 857 break; 858 } 859 860 ck_memzero(pw, pwlen); 861 return rc; 862 } 863 864 865 866 enum http_auth_digest_params_e { 867 e_username = 0 868 ,e_realm 869 ,e_nonce 870 ,e_uri 871 ,e_algorithm 872 ,e_qop 873 ,e_cnonce 874 ,e_nc 875 ,e_response 876 ,e_userstar 877 ,e_userhash 878 ,http_auth_digest_params_sz /*(last item)*/ 879 }; 880 881 typedef struct http_auth_digest_params_t { 882 const char *ptr[http_auth_digest_params_sz]; 883 uint16_t len[http_auth_digest_params_sz]; 884 unix_time64_t send_nextnonce_ts; 885 unsigned char rdigest[MD_DIGEST_LENGTH_MAX];/*(earlier members get 0-init)*/ 886 } http_auth_digest_params_t; 887 888 889 static void 890 mod_auth_digest_mutate (http_auth_info_t * const ai, const http_auth_digest_params_t * const dp, const buffer * const method) 891 { 892 force_assert(method); 893 li_md_iov_fn digest_iov = MD5_iov; 894 /* (ai->dalgo & HTTP_AUTH_DIGEST_MD5) default */ 895 #ifdef USE_LIB_CRYPTO 896 if (ai->dalgo & HTTP_AUTH_DIGEST_SHA256) 897 digest_iov = SHA256_iov; 898 #ifdef USE_LIB_CRYPTO_SHA512_256 899 else if (ai->dalgo & HTTP_AUTH_DIGEST_SHA512_256) 900 digest_iov = SHA512_256_iov; 901 #endif 902 #endif 903 size_t n; 904 struct const_iovec iov[11]; 905 char a1[MD_DIGEST_LENGTH_MAX*2+8]; /*(+1 for li_tohex(); +8 for align)*/ 906 char a2[MD_DIGEST_LENGTH_MAX*2+8]; /*(+1 for li_tohex(); +8 for align)*/ 907 908 li_tohex(a1, sizeof(a1), (const char *)ai->digest, ai->dlen); 909 910 if (ai->dalgo & HTTP_AUTH_DIGEST_SESS) { 911 /* http://www.rfc-editor.org/errata_search.php?rfc=2617 912 * Errata ID: 1649 */ 913 iov[0].iov_base = a1; 914 iov[0].iov_len = ai->dlen*2; 915 iov[1].iov_base = ":"; 916 iov[1].iov_len = 1; 917 iov[2].iov_base = dp->ptr[e_nonce]; 918 iov[2].iov_len = dp->len[e_nonce]; 919 iov[3].iov_base = ":"; 920 iov[3].iov_len = 1; 921 iov[4].iov_base = dp->ptr[e_cnonce]; 922 iov[4].iov_len = dp->len[e_cnonce]; 923 digest_iov(ai->digest, iov, 5); 924 li_tohex(a1, sizeof(a1), (const char *)ai->digest, ai->dlen); 925 } 926 927 /* calculate H(A2) */ 928 iov[0].iov_base = method->ptr; 929 iov[0].iov_len = buffer_clen(method); 930 iov[1].iov_base = ":"; 931 iov[1].iov_len = 1; 932 iov[2].iov_base = dp->ptr[e_uri]; 933 iov[2].iov_len = dp->len[e_uri]; 934 n = 3; 935 #if 0 936 /* qop=auth-int not supported, already checked in caller */ 937 if (dp->ptr[e_qop] && buffer_eq_icase_ss(dp->ptr[e_qop], dp->len[e_qop], 938 CONST_STR_LEN("auth-int"))) { 939 iov[3].iov_base = ":"; 940 iov[3].iov_len = 1; 941 iov[4].iov_base = [body checksum]; 942 iov[4].iov_len = ai->dlen*2; 943 n = 5; 944 } 945 #endif 946 digest_iov(ai->digest, iov, n); 947 li_tohex(a2, sizeof(a2), (const char *)ai->digest, ai->dlen); 948 949 /* calculate response */ 950 iov[0].iov_base = a1; 951 iov[0].iov_len = ai->dlen*2; 952 iov[1].iov_base = ":"; 953 iov[1].iov_len = 1; 954 iov[2].iov_base = dp->ptr[e_nonce]; 955 iov[2].iov_len = dp->len[e_nonce]; 956 iov[3].iov_base = ":"; 957 iov[3].iov_len = 1; 958 n = 4; 959 if (dp->len[e_qop]) { 960 iov[4].iov_base = dp->ptr[e_nc]; 961 iov[4].iov_len = dp->len[e_nc]; 962 iov[5].iov_base = ":"; 963 iov[5].iov_len = 1; 964 iov[6].iov_base = dp->ptr[e_cnonce]; 965 iov[6].iov_len = dp->len[e_cnonce]; 966 iov[7].iov_base = ":"; 967 iov[7].iov_len = 1; 968 iov[8].iov_base = dp->ptr[e_qop]; 969 iov[8].iov_len = dp->len[e_qop]; 970 iov[9].iov_base = ":"; 971 iov[9].iov_len = 1; 972 n = 10; 973 } 974 iov[n].iov_base = a2; 975 iov[n].iov_len = ai->dlen*2; 976 digest_iov(ai->digest, iov, n+1); 977 } 978 979 980 static void 981 mod_auth_append_nonce (buffer *b, unix_time64_t cur_ts, const struct http_auth_require_t *require, int dalgo, int *rndptr) 982 { 983 buffer_append_uint_hex(b, (uintmax_t)cur_ts); 984 buffer_append_char(b, ':'); 985 const buffer * const nonce_secret = require->nonce_secret; 986 int rnd; 987 if (NULL == nonce_secret) 988 rnd = rndptr ? *rndptr : li_rand_pseudo(); 989 else { /*(do not directly expose random number generator single value)*/ 990 rndptr 991 ? (void)(rnd = *rndptr) 992 : li_rand_pseudo_bytes((unsigned char *)&rnd, sizeof(rnd)); 993 buffer_append_uint_hex(b, (uintmax_t)rnd); 994 buffer_append_char(b, ':'); 995 } 996 997 size_t n; 998 struct const_iovec iov[3]; 999 1000 #if 0 1001 char a1[LI_ITOSTRING_LENGTH]; 1002 char a2[LI_ITOSTRING_LENGTH]; 1003 iov[0].iov_base = a1; 1004 iov[0].iov_len = li_itostrn(a1, sizeof(a1), cur_ts); 1005 iov[1].iov_base = a2; 1006 iov[1].iov_len = li_itostrn(a2, sizeof(a2), rnd); 1007 #else 1008 iov[0].iov_base = &cur_ts; 1009 iov[0].iov_len = sizeof(cur_ts); 1010 iov[1].iov_base = &rnd; 1011 iov[1].iov_len = sizeof(rnd); 1012 #endif 1013 n = 2; 1014 if (nonce_secret) { 1015 iov[2].iov_base = nonce_secret->ptr; 1016 iov[2].iov_len = buffer_clen(nonce_secret); 1017 n = 3; 1018 } 1019 1020 unsigned char h[MD_DIGEST_LENGTH_MAX]; 1021 switch (dalgo) { 1022 #ifdef USE_LIB_CRYPTO 1023 #ifdef USE_LIB_CRYPTO_SHA512_256 1024 case HTTP_AUTH_DIGEST_SHA512_256: 1025 SHA512_256_iov(h, iov, n); 1026 n = HTTP_AUTH_DIGEST_SHA512_256_BINLEN; 1027 break; 1028 #endif 1029 case HTTP_AUTH_DIGEST_SHA256: 1030 SHA256_iov(h, iov, n); 1031 n = HTTP_AUTH_DIGEST_SHA256_BINLEN; 1032 break; 1033 #endif 1034 /*case HTTP_AUTH_DIGEST_MD5:*/ 1035 default: 1036 MD5_iov(h, iov, n); 1037 n = HTTP_AUTH_DIGEST_MD5_BINLEN; 1038 break; 1039 } 1040 li_tohex(buffer_extend(b, n*2), n*2+1, (const char *)h, n); 1041 } 1042 1043 1044 static void 1045 mod_auth_digest_www_authenticate (buffer *b, unix_time64_t cur_ts, const struct http_auth_require_t *require, int nonce_stale) 1046 { 1047 int algos = nonce_stale ? nonce_stale : require->algorithm; 1048 int n = 0; 1049 int algoid[3]; 1050 unsigned int algolen[3]; 1051 const char *algoname[3]; 1052 #ifdef USE_LIB_CRYPTO 1053 #ifdef USE_LIB_CRYPTO_SHA512_256 1054 if (algos & HTTP_AUTH_DIGEST_SHA512_256) { 1055 algoid[n] = HTTP_AUTH_DIGEST_SHA512_256; 1056 algoname[n] = "SHA-512-256"; 1057 algolen[n] = sizeof("SHA-512-256")-1; 1058 ++n; 1059 } 1060 #endif 1061 if (algos & HTTP_AUTH_DIGEST_SHA256) { 1062 algoid[n] = HTTP_AUTH_DIGEST_SHA256; 1063 algoname[n] = "SHA-256"; 1064 algolen[n] = sizeof("SHA-256")-1; 1065 ++n; 1066 } 1067 #endif 1068 if (algos & HTTP_AUTH_DIGEST_MD5) { 1069 algoid[n] = HTTP_AUTH_DIGEST_MD5; 1070 algoname[n] = "MD5"; 1071 algolen[n] = sizeof("MD5")-1; 1072 ++n; 1073 } 1074 1075 buffer_clear(b); 1076 for (int i = 0; i < n; ++i) { 1077 struct const_iovec iov[] = { 1078 { CONST_STR_LEN("\r\nWWW-Authenticate: ") } 1079 ,{ CONST_STR_LEN("Digest realm=\"") } 1080 ,{ BUF_PTR_LEN(require->realm) } 1081 ,{ CONST_STR_LEN("\", charset=\"UTF-8\", algorithm=") } 1082 ,{ algoname[i], algolen[i] } 1083 ,{ CONST_STR_LEN(", nonce=\"") } 1084 }; 1085 buffer_append_iovec(b, iov+(0==i), sizeof(iov)/sizeof(*iov)-(0==i)); 1086 mod_auth_append_nonce(b, cur_ts, require, algoid[i], NULL); 1087 buffer_append_string_len(b, CONST_STR_LEN("\", qop=\"auth\"")); 1088 if (require->userhash) { 1089 buffer_append_string_len(b, CONST_STR_LEN(", userhash=true")); 1090 } 1091 if (nonce_stale) { 1092 buffer_append_string_len(b, CONST_STR_LEN(", stale=true")); 1093 } 1094 } 1095 } 1096 1097 1098 __attribute_noinline__ 1099 static handler_t 1100 mod_auth_send_401_unauthorized_digest(request_st * const r, const struct http_auth_require_t * const require, int nonce_stale) 1101 { 1102 r->http_status = 401; 1103 r->handler_module = NULL; 1104 mod_auth_digest_www_authenticate( 1105 http_header_response_set_ptr(r, HTTP_HEADER_WWW_AUTHENTICATE, 1106 CONST_STR_LEN("WWW-Authenticate")), 1107 log_epoch_secs, require, nonce_stale); 1108 return HANDLER_FINISHED; 1109 } 1110 1111 1112 static void 1113 mod_auth_digest_authentication_info (buffer *b, unix_time64_t cur_ts, const struct http_auth_require_t *require, int dalgo) 1114 { 1115 buffer_clear(b); 1116 buffer_append_string_len(b, CONST_STR_LEN("nextnonce=\"")); 1117 mod_auth_append_nonce(b, cur_ts, require, dalgo, NULL); 1118 buffer_append_char(b, '"'); 1119 } 1120 1121 1122 static handler_t 1123 mod_auth_digest_get (request_st * const r, void *p_d, const struct http_auth_require_t * const require, const struct http_auth_backend_t * const backend, http_auth_info_t * const ai) 1124 { 1125 plugin_data * const p = p_d; 1126 splay_tree **sptree = p->conf.auth_cache 1127 ? &p->conf.auth_cache->sptree 1128 : NULL; 1129 http_auth_cache_entry *ae = NULL; 1130 handler_t rc = HANDLER_GO_ON; 1131 int ndx = -1; 1132 1133 const char *user = ai->username; 1134 const uint32_t ulen = ai->ulen; 1135 char userbuf[sizeof(ai->userbuf)]; 1136 if (ai->userhash && ulen <= sizeof(userbuf)) { 1137 /*(lowercase hex in userhash for consistency)*/ 1138 const char * const restrict s = ai->username; 1139 for (uint_fast32_t i = 0; i < ulen; ++i) 1140 userbuf[i] = !light_isupper(s[i]) ? s[i] : (s[i] | 0x20); 1141 user = userbuf; 1142 } 1143 1144 if (sptree) { 1145 ndx = http_auth_cache_hash(require, user, ulen); 1146 ae = http_auth_cache_query(sptree, ndx); 1147 if (ae && ae->require == require 1148 && ae->dalgo == ai->dalgo 1149 && ae->dlen == ai->dlen 1150 && ae->klen == ulen 1151 && 0 == memcmp(ae->k, user, ulen) 1152 && (ae->k == ae->username || ai->userhash)) { 1153 memcpy(ai->digest, ae->pwdigest, ai->dlen); 1154 if (ae->k != ae->username) { /*(userhash was key; copy username)*/ 1155 if (__builtin_expect( (ae->ulen <= sizeof(ai->userbuf)), 1)) { 1156 ai->ulen = ae->ulen; 1157 ai->username = memcpy(ai->userbuf, ae->username, ae->ulen); 1158 } 1159 } 1160 } 1161 else /*(not found or hash collision)*/ 1162 ae = NULL; 1163 } 1164 1165 if (NULL == ae) { 1166 if (ai->userhash && ulen <= sizeof(ai->userbuf)) 1167 ai->username = memcpy(ai->userbuf, userbuf, ulen); 1168 /* ai->username (lowercase userhash) will be replaced by username */ 1169 rc = backend->digest(r, backend->p_d, ai); 1170 } 1171 1172 switch (rc) { 1173 case HANDLER_GO_ON: 1174 break; 1175 case HANDLER_WAIT_FOR_EVENT: 1176 return HANDLER_WAIT_FOR_EVENT; 1177 case HANDLER_FINISHED: 1178 return HANDLER_FINISHED; 1179 case HANDLER_ERROR: 1180 default: 1181 r->keep_alive = -1; /*(disable keep-alive if unknown user)*/ 1182 return mod_auth_send_401_unauthorized_digest(r, require, 0); 1183 } 1184 1185 if (sptree && NULL == ae) { /*(cache digest from backend)*/ 1186 ae = http_auth_cache_entry_init(require, ai->dalgo, user, ulen, 1187 ai->username, ai->ulen, 1188 (char *)ai->digest, ai->dlen); 1189 http_auth_cache_insert(sptree, ndx, ae, http_auth_cache_entry_free); 1190 } 1191 1192 return rc; 1193 } 1194 1195 1196 __attribute_cold__ 1197 static handler_t 1198 mod_auth_digest_misconfigured (request_st * const r, const struct http_auth_backend_t * const backend) 1199 { 1200 if (NULL == backend) 1201 log_error(r->conf.errh, __FILE__, __LINE__, 1202 "auth.backend not configured for %s", r->uri.path.ptr); 1203 else 1204 log_error(r->conf.errh, __FILE__, __LINE__, 1205 "auth.require \"method\" => \"digest\" invalid " 1206 "(try \"basic\"?) for %s", r->uri.path.ptr); 1207 1208 r->http_status = 500; 1209 r->handler_module = NULL; 1210 return HANDLER_FINISHED; 1211 } 1212 1213 1214 static void 1215 mod_auth_digest_parse_authorization (http_auth_digest_params_t * const dp, const char *c) 1216 { 1217 struct digest_kv { 1218 const char *key; 1219 uint32_t klen; 1220 enum http_auth_digest_params_e id; 1221 }; 1222 1223 static const struct digest_kv dkv[] = { 1224 { CONST_STR_LEN("username"), e_username }, 1225 { CONST_STR_LEN("realm"), e_realm }, 1226 { CONST_STR_LEN("nonce"), e_nonce }, 1227 { CONST_STR_LEN("uri"), e_uri }, 1228 { CONST_STR_LEN("algorithm"), e_algorithm }, 1229 { CONST_STR_LEN("qop"), e_qop }, 1230 { CONST_STR_LEN("cnonce"), e_cnonce }, 1231 { CONST_STR_LEN("nc"), e_nc }, 1232 { CONST_STR_LEN("response"), e_response }, 1233 { CONST_STR_LEN("username*"), e_userstar }, 1234 { CONST_STR_LEN("userhash"), e_userhash }, 1235 1236 { NULL, 0, http_auth_digest_params_sz } 1237 }; 1238 1239 /* parse credentials from client */ 1240 /* (caller must pass c pointing to string after "Digest ") */ 1241 for (const char *e; *c; c++) { 1242 /* skip whitespaces */ 1243 while (*c == ' ' || *c == '\t' || *c == ',') ++c; 1244 if (!*c) break; 1245 for (e = c; *e!='=' && *e!=' ' && *e!='\t' && *e!='\0'; ++e) ; 1246 const uint32_t tlen = (uint32_t)(e - c); 1247 1248 for (int i = 0; dkv[i].key; ++i) { 1249 if (tlen != dkv[i].klen || 0 != memcmp(c, dkv[i].key, tlen)) 1250 continue; 1251 c += tlen; 1252 /* detect and step over '='; ignore BWS (bad whitespace) */ 1253 if (__builtin_expect( (*c != '='), 0)) { 1254 while (*c == ' ' || *c == '\t') ++c; 1255 if (*c != '=') return; /*(including '\0')*/ 1256 } 1257 do { ++c; } while (*c == ' ' || *c == '\t'); 1258 1259 if (*c == '"') { 1260 for (e = ++c; *e != '"' && *e != '\0'; ++e) { 1261 if (*e == '\\' && *++e == '\0') return; 1262 } 1263 if (*e != '"') return; 1264 /* value with "..." *//*(XXX: quoted value not unescaped)*/ 1265 } 1266 else { 1267 for (e = c; *e!=',' && *e!=' ' && *e!='\t' && *e!='\0'; ++e) ; 1268 /* value without "..." */ 1269 } 1270 dp->ptr[dkv[i].id] = c; 1271 dp->len[dkv[i].id] = (uint16_t)(e - c); 1272 c = e; 1273 if (*c != ',') { 1274 /*(could more strictly check for linear whitespace)*/ 1275 c = strchr(c, ','); 1276 if (!c) return; 1277 } 1278 break; 1279 } 1280 } 1281 } 1282 1283 1284 static handler_t 1285 mod_auth_digest_validate_userstar (request_st * const r, http_auth_digest_params_t * const dp, http_auth_info_t * const ai) 1286 { 1287 /*assert(dp->ptr[e_userstar]);*/ 1288 1289 if (dp->len[e_userhash] == 4) { /*("true")*/ 1290 log_error(r->conf.errh, __FILE__, __LINE__, 1291 "digest: invalid \"username*\" with \"userhash\" = true"); 1292 return mod_auth_send_400_bad_request(r); 1293 } 1294 1295 /* "username*" RFC5987 ext-value 1296 * ext-value = charset "'" [ language ] "'" value-chars */ 1297 const char *ptr = dp->ptr[e_userstar]; 1298 uint32_t len = dp->len[e_userstar]; 1299 /* validate and step over charset... */ 1300 if ((*ptr | 0x20) == 'u' && len > 5 1301 && buffer_eq_icase_ssn(ptr, "utf-8", 5)) 1302 ptr += 5; 1303 else if ((*ptr | 0x20) == 'i' && len > 10 1304 && buffer_eq_icase_ssn(ptr, "iso-8859-1", 10)) 1305 ptr += 10; 1306 else 1307 ptr = "\n"; /*(invalid char; (not '\''); error below)*/ 1308 /* step over ...'language'... */ 1309 if (*ptr++ != '\'' 1310 || !(ptr = memchr(ptr, '\'', 1311 len - (uint32_t)(ptr - dp->ptr[e_userstar])))) { 1312 log_error(r->conf.errh, __FILE__, __LINE__, 1313 "digest: invalid \"username*\" ext-value"); 1314 return mod_auth_send_400_bad_request(r); 1315 } 1316 ++ptr; 1317 1318 /* decode %XX encodings (could be more efficient by combining tests) */ 1319 buffer * const tb = r->tmp_buf; 1320 buffer_copy_string_len(tb, ptr, len-(uint32_t)(ptr - dp->ptr[e_userstar])); 1321 buffer_urldecode_path(tb); 1322 if (dp->ptr[e_userstar][0] == 'u' && !buffer_is_valid_UTF8(tb)) { 1323 log_error(r->conf.errh, __FILE__, __LINE__, 1324 "digest: invalid \"username*\" invalid UTF-8"); 1325 return mod_auth_send_400_bad_request(r); 1326 } 1327 len = buffer_clen(tb); 1328 if (len > sizeof(ai->userbuf)) { 1329 log_error(r->conf.errh, __FILE__, __LINE__, 1330 "digest: invalid \"username*\" too long"); 1331 return mod_auth_send_400_bad_request(r); 1332 } 1333 for (ptr = tb->ptr; *ptr; ++ptr) { 1334 /* prohibit decoded control chars, including '\0','\r','\n' */ 1335 /* (theoretically could permit '\t', but not currently done) */ 1336 if (*(unsigned char *)ptr < 0x20 || *ptr == 127) { /* iscntrl() */ 1337 log_error(r->conf.errh, __FILE__, __LINE__, 1338 "digest: invalid \"username*\" contains ctrl chars"); 1339 return mod_auth_send_400_bad_request(r); 1340 } 1341 } 1342 1343 ai->ulen = len; 1344 ai->username = memcpy(ai->userbuf, tb->ptr, len); 1345 return HANDLER_GO_ON; 1346 } 1347 1348 1349 static handler_t 1350 mod_auth_digest_validate_params (request_st * const r, const struct http_auth_require_t * const require, http_auth_digest_params_t * const dp, http_auth_info_t * const ai) 1351 { 1352 /* check for required parameters */ 1353 if ((!dp->ptr[e_qop] || (dp->ptr[e_nc] && dp->ptr[e_cnonce])) 1354 && ((NULL != dp->ptr[e_username]) ^ (NULL != dp->ptr[e_userstar])) 1355 && dp->ptr[e_realm] 1356 && dp->ptr[e_nonce] 1357 && dp->ptr[e_uri] 1358 && dp->ptr[e_response]) { 1359 ai->username = dp->ptr[e_username]; 1360 ai->ulen = dp->len[e_username]; 1361 ai->realm = dp->ptr[e_realm]; 1362 ai->rlen = dp->len[e_realm]; 1363 ai->userhash = (dp->len[e_userhash]==4);/*("true", not "false",absent)*/ 1364 if (!ai->username) { /* (dp->ptr[e_userstar]) */ 1365 if (HANDLER_GO_ON != mod_auth_digest_validate_userstar(r, dp, ai)) 1366 return HANDLER_FINISHED; 1367 } 1368 } 1369 else { 1370 log_error(r->conf.errh, __FILE__, __LINE__, 1371 "digest: missing field"); 1372 return mod_auth_send_400_bad_request(r); 1373 } 1374 1375 if (!buffer_eq_slen(require->realm, ai->realm, ai->rlen)) { 1376 log_error(r->conf.errh, __FILE__, __LINE__, 1377 "digest: realm mismatch"); 1378 return mod_auth_send_401_unauthorized_digest(r, require, 0); 1379 } 1380 1381 if (!mod_auth_algorithm_parse(ai,dp->ptr[e_algorithm],dp->len[e_algorithm]) 1382 || !(require->algorithm & ai->dalgo & ~HTTP_AUTH_DIGEST_SESS)) { 1383 log_error(r->conf.errh, __FILE__, __LINE__, 1384 "digest: (%.*s): invalid", 1385 (int)dp->len[e_algorithm], dp->ptr[e_algorithm]); 1386 return mod_auth_send_401_unauthorized_digest(r, require, 0); 1387 } 1388 1389 /* *-sess requires nonce and cnonce */ 1390 if ((ai->dalgo & HTTP_AUTH_DIGEST_SESS) 1391 && (!dp->ptr[e_nonce] || !dp->ptr[e_cnonce])) { 1392 log_error(r->conf.errh, __FILE__, __LINE__, 1393 "digest: (%.*s): missing field", 1394 (int)dp->len[e_algorithm], dp->ptr[e_algorithm]); 1395 return mod_auth_send_400_bad_request(r); 1396 } 1397 1398 if (0 != li_hex2bin(dp->rdigest, sizeof(dp->rdigest), 1399 dp->ptr[e_response], dp->len[e_response]) 1400 || dp->len[e_response] != (ai->dlen << 1)) { 1401 log_error(r->conf.errh, __FILE__, __LINE__, 1402 "digest: (%s): invalid format", dp->ptr[e_response]); 1403 return mod_auth_send_400_bad_request(r); 1404 } 1405 1406 if (dp->ptr[e_qop]&& buffer_eq_icase_ss(dp->ptr[e_qop], dp->len[e_qop], 1407 CONST_STR_LEN("auth-int"))) { 1408 log_error(r->conf.errh, __FILE__, __LINE__, 1409 "digest: qop=auth-int not supported"); 1410 return mod_auth_send_400_bad_request(r); 1411 } 1412 1413 /* detect if attacker is attempting to reuse valid digest for one uri 1414 * on a different request uri. Might also happen if intermediate proxy 1415 * altered client request line. (Altered request would not result in 1416 * the same digest as that calculated by the client.) 1417 * Internal redirects such as with mod_rewrite will modify request uri. 1418 * Reauthentication is done to detect crossing auth realms, but this 1419 * uri validation step is bypassed. r->target_orig is 1420 * original request-target sent in client request. */ 1421 if (!buffer_eq_slen(&r->target_orig, dp->ptr[e_uri], dp->len[e_uri])) { 1422 log_error(r->conf.errh, __FILE__, __LINE__, 1423 "digest: auth failed: uri mismatch (%s != %.*s), IP: %s", 1424 r->target_orig.ptr, (int)dp->len[e_uri], dp->ptr[e_uri], 1425 r->con->dst_addr_buf.ptr); 1426 return mod_auth_send_400_bad_request(r); 1427 } 1428 1429 return HANDLER_GO_ON; 1430 } 1431 1432 1433 static handler_t 1434 mod_auth_digest_validate_nonce (request_st * const r, const struct http_auth_require_t * const require, http_auth_digest_params_t * const dp, http_auth_info_t * const ai) 1435 { 1436 /* check age of nonce. Note, random data is used in nonce generation 1437 * in mod_auth_send_401_unauthorized_digest(). If that were replaced 1438 * with nanosecond time, then nonce secret would remain unique enough 1439 * for the purposes of Digest auth, and would be reproducible (and 1440 * verifiable) if nanoseconds were included with seconds as part of the 1441 * nonce "timestamp:secret". However, doing so would expose a high 1442 * precision timestamp of the system to attackers. timestamp in nonce 1443 * could theoretically be modified and still produce same md5sum, but 1444 * that is highly unlikely within a 10 min (moving) window of valid 1445 * time relative to current time (now). 1446 * If it is desired to validate that nonces were generated by server, 1447 * then specify auth.require = ( ... => ( "secret" => "..." ) ) 1448 * When secret is specified, then instead of nanoseconds, the random 1449 * data value (included for unique nonces) will be exposed in the nonce 1450 * along with the timestamp, and the additional secret will be used to 1451 * validate that the server generated the nonce using that secret. */ 1452 unix_time64_t ts = 0; 1453 const unsigned char * const nonce = (unsigned char *)dp->ptr[e_nonce]; 1454 int i; 1455 for (i = 0; i < 16 && light_isxdigit(nonce[i]); ++i) 1456 ts = (unix_time64_t)((uint64_t)ts << 4) | hex2int(nonce[i]); 1457 1458 const unix_time64_t cur_ts = log_epoch_secs; 1459 if (nonce[i] != ':' || ts > cur_ts || cur_ts - ts > 600) { /*(10 mins)*/ 1460 /* nonce is stale; have client regenerate digest */ 1461 return mod_auth_send_401_unauthorized_digest(r, require, ai->dalgo); 1462 } 1463 1464 if (cur_ts - ts > 540) /*(9 mins)*/ 1465 dp->send_nextnonce_ts = cur_ts; 1466 1467 if (require->nonce_secret) { 1468 unsigned int rnd = 0; 1469 for (int j = i+8; i < j && light_isxdigit(nonce[i]); ++i) { 1470 rnd = (rnd << 4) + hex2int(nonce[i]); 1471 } 1472 if (nonce[i] != ':') { 1473 /* nonce is invalid; 1474 * expect extra field w/ require->nonce_secret */ 1475 log_error(r->conf.errh, __FILE__, __LINE__, 1476 "digest: nonce invalid"); 1477 return mod_auth_send_400_bad_request(r); 1478 } 1479 buffer * const tb = r->tmp_buf; 1480 buffer_clear(tb); 1481 mod_auth_append_nonce(tb, cur_ts, require, ai->dalgo, (int *)&rnd); 1482 if (!buffer_eq_slen(tb, dp->ptr[e_nonce], dp->len[e_nonce])) { 1483 /* nonce not generated using current require->nonce_secret */ 1484 log_error(r->conf.errh, __FILE__, __LINE__, 1485 "digest: nonce mismatch"); 1486 return mod_auth_send_401_unauthorized_digest(r, require, 0); 1487 } 1488 } 1489 1490 return HANDLER_GO_ON; 1491 } 1492 1493 1494 static handler_t 1495 mod_auth_check_digest (request_st * const r, void *p_d, const struct http_auth_require_t * const require, const struct http_auth_backend_t * const backend) 1496 { 1497 if (NULL == backend || NULL == backend->digest) 1498 return mod_auth_digest_misconfigured(r, backend); 1499 1500 const buffer * const vb = 1501 http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, 1502 CONST_STR_LEN("Authorization")); 1503 if (NULL == vb || !buffer_eq_icase_ssn(vb->ptr, CONST_STR_LEN("Digest "))) 1504 return mod_auth_send_401_unauthorized_digest(r, require, 0); 1505 #ifdef __COVERITY__ 1506 if (buffer_clen(vb) < sizeof("Digest ")-1) 1507 return mod_auth_send_400_bad_request(r); 1508 #endif 1509 1510 http_auth_digest_params_t dp; 1511 http_auth_info_t ai; 1512 handler_t rc; 1513 1514 /* XXX: should use offsetof() (if portable enough) */ 1515 memset(&dp, 0, sizeof(dp) - sizeof(dp.rdigest)); 1516 1517 mod_auth_digest_parse_authorization(&dp, vb->ptr + sizeof("Digest ")-1); 1518 1519 rc = mod_auth_digest_validate_params(r, require, &dp, &ai); 1520 if (__builtin_expect( (HANDLER_GO_ON != rc), 0)) 1521 return rc; 1522 1523 rc = mod_auth_digest_validate_nonce(r, require, &dp, &ai); 1524 if (__builtin_expect( (HANDLER_GO_ON != rc), 0)) 1525 return rc; 1526 1527 rc = mod_auth_digest_get(r, p_d, require, backend, &ai); 1528 if (__builtin_expect( (HANDLER_GO_ON != rc), 0)) 1529 return rc; 1530 1531 mod_auth_digest_mutate(&ai, &dp, http_method_buf(r->http_method)); 1532 1533 if (!ck_memeq_const_time_fixed_len(dp.rdigest, ai.digest, ai.dlen)) { 1534 /*ck_memzero(ai.digest, ai.dlen);*//*skip clear since mutated*/ 1535 /* digest not ok */ 1536 log_error(r->conf.errh, __FILE__, __LINE__, 1537 "digest: auth failed for %.*s: wrong password, IP: %s", 1538 (int)ai.ulen, ai.username, r->con->dst_addr_buf.ptr); 1539 r->keep_alive = -1; /*(disable keep-alive if bad password)*/ 1540 return mod_auth_send_401_unauthorized_digest(r, require, 0); 1541 } 1542 /*ck_memzero(ai.digest, ai.dlen);*//* skip clear since mutated */ 1543 1544 /* check authorization (authz); string args must be '\0'-terminated) */ 1545 buffer * const tb = r->tmp_buf; 1546 buffer_copy_string_len(tb, ai.username, ai.ulen); 1547 if (!http_auth_match_rules(require, tb->ptr, NULL, NULL)) 1548 return mod_auth_send_401_unauthorized_digest(r, require, 0); 1549 1550 if (dp.send_nextnonce_ts) { 1551 /*(send nextnonce when expiration is approaching)*/ 1552 mod_auth_digest_authentication_info( 1553 http_header_response_set_ptr(r, HTTP_HEADER_OTHER, 1554 CONST_STR_LEN("Authentication-Info")), 1555 dp.send_nextnonce_ts /*(cur_ts)*/, require, ai.dalgo); 1556 } 1557 1558 http_auth_setenv(r, ai.username, ai.ulen, CONST_STR_LEN("Digest")); 1559 return HANDLER_GO_ON; 1560 } 1561 1562 1563 1564 static handler_t mod_auth_check_extern(request_st * const r, void *p_d, const struct http_auth_require_t * const require, const struct http_auth_backend_t * const backend) { 1565 /* require REMOTE_USER already set */ 1566 const buffer *vb = http_header_env_get(r, CONST_STR_LEN("REMOTE_USER")); 1567 UNUSED(p_d); 1568 UNUSED(backend); 1569 if (NULL != vb && http_auth_match_rules(require, vb->ptr, NULL, NULL)) { 1570 return HANDLER_GO_ON; 1571 } else { 1572 r->http_status = 401; 1573 r->handler_module = NULL; 1574 return HANDLER_FINISHED; 1575 } 1576 } 1577