xref: /lighttpd1.4/src/mod_auth.c (revision 94edf7e8)
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