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 *
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)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
http_auth_cache_entry_free(void * data)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
http_auth_cache_free(http_auth_cache * ac)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 *
http_auth_cache_init(const array * opts)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, 600); /* 10 min if invalid num */
116 }
117 return ac;
118 }
119
120 static int
http_auth_cache_hash(const struct http_auth_require_t * const require,const char * username,const uint32_t ulen)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 *
http_auth_cache_query(splay_tree ** const sptree,const int ndx)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
http_auth_cache_insert(splay_tree ** const sptree,const int ndx,void * const data,void (data_free_fn)(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
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)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
mod_auth_periodic_cleanup(splay_tree ** sptree_ptr,const time_t max_age,const unix_time64_t cur_ts)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
TRIGGER_FUNC(mod_auth_periodic)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
INIT_FUNC(mod_auth_init)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
FREE_FUNC(mod_auth_free)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
data_auth_free(data_unset * d)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
data_auth_init(void)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
mod_auth_algorithm_parse(http_auth_info_t * ai,const char * s,size_t len)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
mod_auth_algorithms_parse(int * algorithm,buffer * algos)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
mod_auth_require_parse(http_auth_require_t * const require,const buffer * b,log_error_st * errh)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
mod_auth_require_parse_array(const array * value,array * const auth_require,log_error_st * errh)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
mod_auth_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)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
mod_auth_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)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
mod_auth_patch_config(request_st * const r,plugin_data * const p)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
SETDEFAULTS_FUNC(mod_auth_set_defaults)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
mod_auth_uri_handler(request_st * const r,void * p_d)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);
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
mod_auth_send_400_bad_request(request_st * const r)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
mod_auth_send_401_unauthorized_basic(request_st * const r,const buffer * const realm)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
mod_auth_basic_misconfigured(request_st * const r,const struct http_auth_backend_t * const backend)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
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)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->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
mod_auth_digest_mutate(http_auth_info_t * const ai,const http_auth_digest_params_t * const dp,const buffer * const method)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
mod_auth_append_nonce(buffer * b,unix_time64_t cur_ts,const struct http_auth_require_t * require,int dalgo,int * rndptr)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
mod_auth_digest_www_authenticate(buffer * b,unix_time64_t cur_ts,const struct http_auth_require_t * require,int nonce_stale)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
mod_auth_send_401_unauthorized_digest(request_st * const r,const struct http_auth_require_t * const require,int nonce_stale)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
mod_auth_digest_authentication_info(buffer * b,unix_time64_t cur_ts,const struct http_auth_require_t * require,int dalgo)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
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)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
mod_auth_digest_misconfigured(request_st * const r,const struct http_auth_backend_t * const backend)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
mod_auth_digest_parse_authorization(http_auth_digest_params_t * const dp,const char * c)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
mod_auth_digest_validate_userstar(request_st * const r,http_auth_digest_params_t * const dp,http_auth_info_t * const ai)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
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)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->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
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)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
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)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->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
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)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