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