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