xref: /lighttpd1.4/src/mod_auth.c (revision 3dd3cde9)
18abd06a7SGlenn Strauss #include "first.h"
28abd06a7SGlenn Strauss 
304d76e7aSGlenn Strauss #include "base.h"
422e8b456SStefan Bühler #include "plugin.h"
522e8b456SStefan Bühler #include "http_auth.h"
6*3dd3cde9SGlenn Strauss #include "http_header.h"
722e8b456SStefan Bühler #include "log.h"
8bcdc6a3bSJan Kneschke 
9bcdc6a3bSJan Kneschke #include <stdlib.h>
10bcdc6a3bSJan Kneschke #include <string.h>
11bcdc6a3bSJan Kneschke 
12bcdc6a3bSJan Kneschke /**
137ba06c71SGlenn Strauss  * auth framework
14bcdc6a3bSJan Kneschke  */
15bcdc6a3bSJan Kneschke 
164b3a91e6SGlenn Strauss typedef struct {
174b3a91e6SGlenn Strauss 	/* auth */
184b3a91e6SGlenn Strauss 	array  *auth_require;
194b3a91e6SGlenn Strauss 	buffer *auth_backend_conf;
20f54d628cSGlenn Strauss 	unsigned short auth_extern_authn;
214b3a91e6SGlenn Strauss 
224b3a91e6SGlenn Strauss 	/* generated */
234b3a91e6SGlenn Strauss 	const http_auth_backend_t *auth_backend;
247ba06c71SGlenn Strauss } plugin_config;
254b3a91e6SGlenn Strauss 
264b3a91e6SGlenn Strauss typedef struct {
274b3a91e6SGlenn Strauss 	PLUGIN_DATA;
284b3a91e6SGlenn Strauss 
297ba06c71SGlenn Strauss 	plugin_config **config_storage;
304b3a91e6SGlenn Strauss 
317ba06c71SGlenn Strauss 	plugin_config conf;
327ba06c71SGlenn Strauss } plugin_data;
337ba06c71SGlenn Strauss 
347ba06c71SGlenn Strauss static handler_t mod_auth_check_basic(server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend);
357ba06c71SGlenn Strauss static handler_t mod_auth_check_digest(server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend);
367ba06c71SGlenn Strauss static handler_t mod_auth_check_extern(server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend);
374b3a91e6SGlenn Strauss 
38bcdc6a3bSJan Kneschke INIT_FUNC(mod_auth_init) {
397ba06c71SGlenn Strauss 	static const http_auth_scheme_t http_auth_scheme_basic  = { "basic",  mod_auth_check_basic,  NULL };
407ba06c71SGlenn Strauss 	static const http_auth_scheme_t http_auth_scheme_digest = { "digest", mod_auth_check_digest, NULL };
417ba06c71SGlenn Strauss 	static const http_auth_scheme_t http_auth_scheme_extern = { "extern", mod_auth_check_extern, NULL };
427ba06c71SGlenn Strauss 	plugin_data *p;
437ba06c71SGlenn Strauss 
447ba06c71SGlenn Strauss 	/* register http_auth_scheme_* */
457ba06c71SGlenn Strauss 	http_auth_scheme_set(&http_auth_scheme_basic);
467ba06c71SGlenn Strauss 	http_auth_scheme_set(&http_auth_scheme_digest);
477ba06c71SGlenn Strauss 	http_auth_scheme_set(&http_auth_scheme_extern);
48bcdc6a3bSJan Kneschke 
49bcdc6a3bSJan Kneschke 	p = calloc(1, sizeof(*p));
50bcdc6a3bSJan Kneschke 
51bcdc6a3bSJan Kneschke 	return p;
52bcdc6a3bSJan Kneschke }
53bcdc6a3bSJan Kneschke 
54bcdc6a3bSJan Kneschke FREE_FUNC(mod_auth_free) {
557ba06c71SGlenn Strauss 	plugin_data *p = p_d;
56bcdc6a3bSJan Kneschke 
57bcdc6a3bSJan Kneschke 	UNUSED(srv);
58bcdc6a3bSJan Kneschke 
59bcdc6a3bSJan Kneschke 	if (!p) return HANDLER_GO_ON;
60bcdc6a3bSJan Kneschke 
61bcdc6a3bSJan Kneschke 	if (p->config_storage) {
62bcdc6a3bSJan Kneschke 		size_t i;
63bcdc6a3bSJan Kneschke 		for (i = 0; i < srv->config_context->used; i++) {
647ba06c71SGlenn Strauss 			plugin_config *s = p->config_storage[i];
65bcdc6a3bSJan Kneschke 
6633cebeb0SStefan Bühler 			if (NULL == s) continue;
674a6ce4aeSJan Kneschke 
68bcdc6a3bSJan Kneschke 			array_free(s->auth_require);
69bcdc6a3bSJan Kneschke 			buffer_free(s->auth_backend_conf);
70bcdc6a3bSJan Kneschke 
71bcdc6a3bSJan Kneschke 			free(s);
72bcdc6a3bSJan Kneschke 		}
73bcdc6a3bSJan Kneschke 		free(p->config_storage);
74bcdc6a3bSJan Kneschke 	}
75bcdc6a3bSJan Kneschke 
76bcdc6a3bSJan Kneschke 	free(p);
77bcdc6a3bSJan Kneschke 
78bcdc6a3bSJan Kneschke 	return HANDLER_GO_ON;
79bcdc6a3bSJan Kneschke }
80bcdc6a3bSJan Kneschke 
817ba06c71SGlenn Strauss /* data type for mod_auth structured data
827ba06c71SGlenn Strauss  * (parsed from auth.require array of strings) */
837ba06c71SGlenn Strauss typedef struct {
847ba06c71SGlenn Strauss     DATA_UNSET;
857ba06c71SGlenn Strauss     http_auth_require_t *require;
867ba06c71SGlenn Strauss } data_auth;
877ba06c71SGlenn Strauss 
887ba06c71SGlenn Strauss static void data_auth_free(data_unset *d)
897ba06c71SGlenn Strauss {
907ba06c71SGlenn Strauss     data_auth * const dauth = (data_auth *)d;
917ba06c71SGlenn Strauss     buffer_free(dauth->key);
927ba06c71SGlenn Strauss     http_auth_require_free(dauth->require);
937ba06c71SGlenn Strauss     free(dauth);
947ba06c71SGlenn Strauss }
957ba06c71SGlenn Strauss 
967ba06c71SGlenn Strauss static data_auth *data_auth_init(void)
977ba06c71SGlenn Strauss {
987ba06c71SGlenn Strauss     data_auth * const dauth = calloc(1, sizeof(*dauth));
997ba06c71SGlenn Strauss     force_assert(NULL != dauth);
1007ba06c71SGlenn Strauss     dauth->copy       = NULL; /* must not be called on this data */
1017ba06c71SGlenn Strauss     dauth->free       = data_auth_free;
1027ba06c71SGlenn Strauss     dauth->reset      = NULL; /* must not be called on this data */
1037ba06c71SGlenn Strauss     dauth->insert_dup = NULL; /* must not be called on this data */
1047ba06c71SGlenn Strauss     dauth->print      = NULL; /* must not be called on this data */
1057ba06c71SGlenn Strauss     dauth->type       = TYPE_OTHER;
1067ba06c71SGlenn Strauss 
1077ba06c71SGlenn Strauss     dauth->key = buffer_init();
1087ba06c71SGlenn Strauss     dauth->require = http_auth_require_init();
1097ba06c71SGlenn Strauss 
1107ba06c71SGlenn Strauss     return dauth;
1117ba06c71SGlenn Strauss }
1127ba06c71SGlenn Strauss 
1137ba06c71SGlenn Strauss static int mod_auth_require_parse (server *srv, http_auth_require_t * const require, const buffer *b)
1147ba06c71SGlenn Strauss {
1157ba06c71SGlenn Strauss     /* user=name1|user=name2|group=name3|host=name4 */
1167ba06c71SGlenn Strauss 
1177ba06c71SGlenn Strauss     const char *str = b->ptr;
1187ba06c71SGlenn Strauss     const char *p;
1197ba06c71SGlenn Strauss 
1207ba06c71SGlenn Strauss     if (buffer_is_equal_string(b, CONST_STR_LEN("valid-user"))) {
1217ba06c71SGlenn Strauss         require->valid_user = 1;
1227ba06c71SGlenn Strauss         return 1; /* success */
1237ba06c71SGlenn Strauss     }
1247ba06c71SGlenn Strauss 
1257ba06c71SGlenn Strauss     do {
1267ba06c71SGlenn Strauss         const char *eq;
1277ba06c71SGlenn Strauss         size_t len;
1287ba06c71SGlenn Strauss         p = strchr(str, '|');
1297ba06c71SGlenn Strauss         len = NULL != p ? (size_t)(p - str) : strlen(str);
1307ba06c71SGlenn Strauss         eq = memchr(str, '=', len);
1317ba06c71SGlenn Strauss         if (NULL == eq) {
1327ba06c71SGlenn Strauss             log_error_write(srv, __FILE__, __LINE__, "sssbss",
1337ba06c71SGlenn Strauss                             "error parsing auth.require 'require' field: missing '='",
1347ba06c71SGlenn Strauss                             "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
1357ba06c71SGlenn Strauss                             "error value:", b, "error near:", str);
1367ba06c71SGlenn Strauss             return 0;
1377ba06c71SGlenn Strauss         }
1387ba06c71SGlenn Strauss         if (p-1  == eq) {
1397ba06c71SGlenn Strauss             log_error_write(srv, __FILE__, __LINE__, "sssbss",
1407ba06c71SGlenn Strauss                             "error parsing auth.require 'require' field: missing token after '='",
1417ba06c71SGlenn Strauss                             "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
1427ba06c71SGlenn Strauss                             "error value:", b, "error near:", str);
1437ba06c71SGlenn Strauss             return 0;
1447ba06c71SGlenn Strauss         }
1457ba06c71SGlenn Strauss 
1467ba06c71SGlenn Strauss         switch ((int)(eq - str)) {
1477ba06c71SGlenn Strauss           case 4:
1487ba06c71SGlenn Strauss             if (0 == memcmp(str, CONST_STR_LEN("user"))) {
1497ba06c71SGlenn Strauss                 data_string *ds = data_string_init();
1507ba06c71SGlenn Strauss                 buffer_copy_string_len(ds->key,str+5,len-5); /*("user=" is 5)*/
1517ba06c71SGlenn Strauss                 array_insert_unique(require->user, (data_unset *)ds);
1527ba06c71SGlenn Strauss                 continue;
1537ba06c71SGlenn Strauss             }
1547ba06c71SGlenn Strauss             else if (0 == memcmp(str, CONST_STR_LEN("host"))) {
1557ba06c71SGlenn Strauss                 data_string *ds = data_string_init();
1567ba06c71SGlenn Strauss                 buffer_copy_string_len(ds->key,str+5,len-5); /*("host=" is 5)*/
1577ba06c71SGlenn Strauss                 array_insert_unique(require->host, (data_unset *)ds);
1587ba06c71SGlenn Strauss                 log_error_write(srv, __FILE__, __LINE__, "ssb",
1597ba06c71SGlenn Strauss                                 "warning parsing auth.require 'require' field: 'host' not implemented;",
1607ba06c71SGlenn Strauss                                 "field value:", b);
1617ba06c71SGlenn Strauss                 continue;
1627ba06c71SGlenn Strauss             }
1637ba06c71SGlenn Strauss             break; /* to error */
1647ba06c71SGlenn Strauss           case 5:
1657ba06c71SGlenn Strauss             if (0 == memcmp(str, CONST_STR_LEN("group"))) {
1667ba06c71SGlenn Strauss                 data_string *ds = data_string_init();
1677ba06c71SGlenn Strauss                 buffer_copy_string_len(ds->key,str+6,len-6); /*("group=" is 6)*/
1687ba06c71SGlenn Strauss                 array_insert_unique(require->group, (data_unset *)ds);
1699c91af0cSGlenn Strauss               #if 0/*(supported by mod_authn_ldap, but not all other backends)*/
1707ba06c71SGlenn Strauss                 log_error_write(srv, __FILE__, __LINE__, "ssb",
1717ba06c71SGlenn Strauss                                 "warning parsing auth.require 'require' field: 'group' not implemented;",
1727ba06c71SGlenn Strauss                                 "field value:", b);
1739c91af0cSGlenn Strauss               #endif
1747ba06c71SGlenn Strauss                 continue;
1757ba06c71SGlenn Strauss             }
1767ba06c71SGlenn Strauss             break; /* to error */
1777ba06c71SGlenn Strauss           case 10:
1787ba06c71SGlenn Strauss             if (0 == memcmp(str, CONST_STR_LEN("valid-user"))) {
1797ba06c71SGlenn Strauss                 log_error_write(srv, __FILE__, __LINE__, "sssb",
1807ba06c71SGlenn Strauss                                 "error parsing auth.require 'require' field: valid user can not be combined with other require rules",
1817ba06c71SGlenn Strauss                                 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
1827ba06c71SGlenn Strauss                                 "error value:", b);
1837ba06c71SGlenn Strauss                 return 0;
1847ba06c71SGlenn Strauss             }
1857ba06c71SGlenn Strauss             break; /* to error */
1867ba06c71SGlenn Strauss           default:
1877ba06c71SGlenn Strauss             break; /* to error */
1887ba06c71SGlenn Strauss         }
1897ba06c71SGlenn Strauss 
1907ba06c71SGlenn Strauss         log_error_write(srv, __FILE__, __LINE__, "sssbss",
1917ba06c71SGlenn Strauss                         "error parsing auth.require 'require' field: invalid/unsupported token",
1927ba06c71SGlenn Strauss                         "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
1937ba06c71SGlenn Strauss                         "error value:", b, "error near:", str);
1947ba06c71SGlenn Strauss         return 0;
1957ba06c71SGlenn Strauss 
1967ba06c71SGlenn Strauss     } while (p && *((str = p+1)));
1977ba06c71SGlenn Strauss 
1987ba06c71SGlenn Strauss     return 1; /* success */
1997ba06c71SGlenn Strauss }
2007ba06c71SGlenn Strauss 
2017ba06c71SGlenn Strauss SETDEFAULTS_FUNC(mod_auth_set_defaults) {
2027ba06c71SGlenn Strauss 	plugin_data *p = p_d;
2037ba06c71SGlenn Strauss 	size_t i;
2047ba06c71SGlenn Strauss 
2057ba06c71SGlenn Strauss 	config_values_t cv[] = {
2067ba06c71SGlenn Strauss 		{ "auth.backend",                   NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
2077ba06c71SGlenn Strauss 		{ "auth.require",                   NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION },  /* 1 */
208f54d628cSGlenn Strauss 		{ "auth.extern-authn",              NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },/* 2 */
2097ba06c71SGlenn Strauss 		{ NULL,                             NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
2107ba06c71SGlenn Strauss 	};
2117ba06c71SGlenn Strauss 
2127ba06c71SGlenn Strauss 	p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
2137ba06c71SGlenn Strauss 
2147ba06c71SGlenn Strauss 	for (i = 0; i < srv->config_context->used; i++) {
2157ba06c71SGlenn Strauss 		data_config const* config = (data_config const*)srv->config_context->data[i];
2167ba06c71SGlenn Strauss 		plugin_config *s;
2177ba06c71SGlenn Strauss 		size_t n;
2187ba06c71SGlenn Strauss 		data_array *da;
2197ba06c71SGlenn Strauss 
2207ba06c71SGlenn Strauss 		s = calloc(1, sizeof(plugin_config));
2217ba06c71SGlenn Strauss 		s->auth_backend_conf = buffer_init();
2227ba06c71SGlenn Strauss 
2237ba06c71SGlenn Strauss 		s->auth_require = array_init();
2247ba06c71SGlenn Strauss 
2257ba06c71SGlenn Strauss 		cv[0].destination = s->auth_backend_conf;
2267ba06c71SGlenn Strauss 		cv[1].destination = s->auth_require; /* T_CONFIG_LOCAL; not modified by config_insert_values_global() */
227f54d628cSGlenn Strauss 		cv[2].destination = &s->auth_extern_authn;
2287ba06c71SGlenn Strauss 
2297ba06c71SGlenn Strauss 		p->config_storage[i] = s;
2307ba06c71SGlenn Strauss 
2317ba06c71SGlenn Strauss 		if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
2327ba06c71SGlenn Strauss 			return HANDLER_ERROR;
2337ba06c71SGlenn Strauss 		}
2347ba06c71SGlenn Strauss 
2357ba06c71SGlenn Strauss 		if (!buffer_string_is_empty(s->auth_backend_conf)) {
2367ba06c71SGlenn Strauss 			s->auth_backend = http_auth_backend_get(s->auth_backend_conf);
2377ba06c71SGlenn Strauss 			if (NULL == s->auth_backend) {
2387ba06c71SGlenn Strauss 				log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not supported:", s->auth_backend_conf);
2397ba06c71SGlenn Strauss 
2407ba06c71SGlenn Strauss 				return HANDLER_ERROR;
2417ba06c71SGlenn Strauss 			}
2427ba06c71SGlenn Strauss 		}
2437ba06c71SGlenn Strauss 
2447ba06c71SGlenn Strauss 		/* no auth.require for this section */
2457ba06c71SGlenn Strauss 		if (NULL == (da = (data_array *)array_get_element(config->value, "auth.require"))) continue;
2467ba06c71SGlenn Strauss 
247bd77abe0SGlenn Strauss 		if (da->type != TYPE_ARRAY || !array_is_kvarray(da->value)) {
248bd77abe0SGlenn Strauss 			log_error_write(srv, __FILE__, __LINE__, "ss",
249bd77abe0SGlenn Strauss 					"unexpected value for auth.require; expected ",
250bd77abe0SGlenn Strauss 					"auth.require = ( \"urlpath\" => ( \"option\" => \"value\" ) )");
251bd77abe0SGlenn Strauss 			return HANDLER_ERROR;
252bd77abe0SGlenn Strauss 		}
253bd77abe0SGlenn Strauss 
2547ba06c71SGlenn Strauss 
2557ba06c71SGlenn Strauss 		for (n = 0; n < da->value->used; n++) {
2567ba06c71SGlenn Strauss 			size_t m;
2577ba06c71SGlenn Strauss 			data_array *da_file = (data_array *)da->value->data[n];
2587ba06c71SGlenn Strauss 			const buffer *method = NULL, *realm = NULL, *require = NULL;
2597ba06c71SGlenn Strauss 			const http_auth_scheme_t *auth_scheme;
2607ba06c71SGlenn Strauss 
261bd77abe0SGlenn Strauss 			if (!array_is_kvstring(da_file->value)) {
2627ba06c71SGlenn Strauss 				log_error_write(srv, __FILE__, __LINE__, "ss",
263bd77abe0SGlenn Strauss 						"unexpected value for auth.require; expected ",
264bd77abe0SGlenn Strauss 						"auth.require = ( \"urlpath\" => ( \"option\" => \"value\" ) )");
2657ba06c71SGlenn Strauss 
2667ba06c71SGlenn Strauss 				return HANDLER_ERROR;
2677ba06c71SGlenn Strauss 			}
2687ba06c71SGlenn Strauss 
2697ba06c71SGlenn Strauss 			for (m = 0; m < da_file->value->used; m++) {
2707ba06c71SGlenn Strauss 				if (da_file->value->data[m]->type == TYPE_STRING) {
2717ba06c71SGlenn Strauss 					data_string *ds = (data_string *)da_file->value->data[m];
2727ba06c71SGlenn Strauss 					if (buffer_is_equal_string(ds->key, CONST_STR_LEN("method"))) {
2737ba06c71SGlenn Strauss 						method = ds->value;
2747ba06c71SGlenn Strauss 					} else if (buffer_is_equal_string(ds->key, CONST_STR_LEN("realm"))) {
2757ba06c71SGlenn Strauss 						realm = ds->value;
2767ba06c71SGlenn Strauss 					} else if (buffer_is_equal_string(ds->key, CONST_STR_LEN("require"))) {
2777ba06c71SGlenn Strauss 						require = ds->value;
2787ba06c71SGlenn Strauss 					} else {
2797ba06c71SGlenn Strauss 						log_error_write(srv, __FILE__, __LINE__, "ssbs",
2807ba06c71SGlenn Strauss 							"the field is unknown in:",
2817ba06c71SGlenn Strauss 							"auth.require = ( \"...\" => ( ..., -> \"",
2827ba06c71SGlenn Strauss 							da_file->value->data[m]->key,
2837ba06c71SGlenn Strauss 							"\" <- => \"...\" ) )");
2847ba06c71SGlenn Strauss 
2857ba06c71SGlenn Strauss 						return HANDLER_ERROR;
2867ba06c71SGlenn Strauss 					}
2877ba06c71SGlenn Strauss 				} else {
2887ba06c71SGlenn Strauss 					log_error_write(srv, __FILE__, __LINE__, "ssbs",
2897ba06c71SGlenn Strauss 						"a string was expected for:",
2907ba06c71SGlenn Strauss 						"auth.require = ( \"...\" => ( ..., -> \"",
2917ba06c71SGlenn Strauss 						da_file->value->data[m]->key,
2927ba06c71SGlenn Strauss 						"\" <- => \"...\" ) )");
2937ba06c71SGlenn Strauss 
2947ba06c71SGlenn Strauss 					return HANDLER_ERROR;
2957ba06c71SGlenn Strauss 				}
2967ba06c71SGlenn Strauss 			}
2977ba06c71SGlenn Strauss 
2987ba06c71SGlenn Strauss 			if (buffer_string_is_empty(method)) {
2997ba06c71SGlenn Strauss 				log_error_write(srv, __FILE__, __LINE__, "ss",
3007ba06c71SGlenn Strauss 						"the method field is missing or blank in:",
3017ba06c71SGlenn Strauss 						"auth.require = ( \"...\" => ( ..., \"method\" => \"...\" ) )");
3027ba06c71SGlenn Strauss 				return HANDLER_ERROR;
3037ba06c71SGlenn Strauss 			} else {
3047ba06c71SGlenn Strauss 				auth_scheme = http_auth_scheme_get(method);
3057ba06c71SGlenn Strauss 				if (NULL == auth_scheme) {
3067ba06c71SGlenn Strauss 					log_error_write(srv, __FILE__, __LINE__, "sbss",
3077ba06c71SGlenn Strauss 							"unknown method", method, "(e.g. \"basic\", \"digest\" or \"extern\") in",
3087ba06c71SGlenn Strauss 							"auth.require = ( \"...\" => ( ..., \"method\" => \"...\") )");
3097ba06c71SGlenn Strauss 					return HANDLER_ERROR;
3107ba06c71SGlenn Strauss 				}
3117ba06c71SGlenn Strauss 			}
3127ba06c71SGlenn Strauss 
3137ba06c71SGlenn Strauss 			if (buffer_is_empty(realm)) {
3147ba06c71SGlenn Strauss 				log_error_write(srv, __FILE__, __LINE__, "ss",
3157ba06c71SGlenn Strauss 						"the realm field is missing in:",
3167ba06c71SGlenn Strauss 						"auth.require = ( \"...\" => ( ..., \"realm\" => \"...\" ) )");
3177ba06c71SGlenn Strauss 				return HANDLER_ERROR;
3187ba06c71SGlenn Strauss 			}
3197ba06c71SGlenn Strauss 
3207ba06c71SGlenn Strauss 			if (buffer_string_is_empty(require)) {
3217ba06c71SGlenn Strauss 				log_error_write(srv, __FILE__, __LINE__, "ss",
3227ba06c71SGlenn Strauss 						"the require field is missing or blank in:",
3237ba06c71SGlenn Strauss 						"auth.require = ( \"...\" => ( ..., \"require\" => \"...\" ) )");
3247ba06c71SGlenn Strauss 				return HANDLER_ERROR;
3257ba06c71SGlenn Strauss 			}
3267ba06c71SGlenn Strauss 
327609e9a50SGlenn Strauss 			if (require) { /*(always true at this point)*/
3287ba06c71SGlenn Strauss 				data_auth * const dauth = data_auth_init();
3297ba06c71SGlenn Strauss 				buffer_copy_buffer(dauth->key, da_file->key);
3307ba06c71SGlenn Strauss 				dauth->require->scheme = auth_scheme;
3317ba06c71SGlenn Strauss 				buffer_copy_buffer(dauth->require->realm, realm);
3327ba06c71SGlenn Strauss 				if (!mod_auth_require_parse(srv, dauth->require, require)) {
3337ba06c71SGlenn Strauss 					dauth->free((data_unset *)dauth);
3347ba06c71SGlenn Strauss 					return HANDLER_ERROR;
3357ba06c71SGlenn Strauss 				}
3367ba06c71SGlenn Strauss 				array_insert_unique(s->auth_require, (data_unset *)dauth);
3377ba06c71SGlenn Strauss 			}
3387ba06c71SGlenn Strauss 		}
3397ba06c71SGlenn Strauss 	}
3407ba06c71SGlenn Strauss 
3417ba06c71SGlenn Strauss 	return HANDLER_GO_ON;
3427ba06c71SGlenn Strauss }
3437ba06c71SGlenn Strauss 
344bcdc6a3bSJan Kneschke #define PATCH(x) \
345bcdc6a3bSJan Kneschke 	p->conf.x = s->x;
3467ba06c71SGlenn Strauss static int mod_auth_patch_connection(server *srv, connection *con, plugin_data *p) {
347bcdc6a3bSJan Kneschke 	size_t i, j;
3487ba06c71SGlenn Strauss 	plugin_config *s = p->config_storage[0];
3494a6ce4aeSJan Kneschke 
3504a6ce4aeSJan Kneschke 	PATCH(auth_backend);
3514a6ce4aeSJan Kneschke 	PATCH(auth_require);
352f54d628cSGlenn Strauss 	PATCH(auth_extern_authn);
353bcdc6a3bSJan Kneschke 
354bcdc6a3bSJan Kneschke 	/* skip the first, the global context */
355bcdc6a3bSJan Kneschke 	for (i = 1; i < srv->config_context->used; i++) {
356bcdc6a3bSJan Kneschke 		data_config *dc = (data_config *)srv->config_context->data[i];
3574a6ce4aeSJan Kneschke 		s = p->config_storage[i];
358bcdc6a3bSJan Kneschke 
359bcdc6a3bSJan Kneschke 		/* condition didn't match */
360bcdc6a3bSJan Kneschke 		if (!config_check_cond(srv, con, dc)) continue;
361bcdc6a3bSJan Kneschke 
362bcdc6a3bSJan Kneschke 		/* merge config */
363bcdc6a3bSJan Kneschke 		for (j = 0; j < dc->value->used; j++) {
364bcdc6a3bSJan Kneschke 			data_unset *du = dc->value->data[j];
365bcdc6a3bSJan Kneschke 
366bcdc6a3bSJan Kneschke 			if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend"))) {
367bcdc6a3bSJan Kneschke 				PATCH(auth_backend);
368bcdc6a3bSJan Kneschke 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.require"))) {
369bcdc6a3bSJan Kneschke 				PATCH(auth_require);
370f54d628cSGlenn Strauss 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.extern-authn"))) {
371f54d628cSGlenn Strauss 				PATCH(auth_extern_authn);
372bcdc6a3bSJan Kneschke 			}
373bcdc6a3bSJan Kneschke 		}
374bcdc6a3bSJan Kneschke 	}
375bcdc6a3bSJan Kneschke 
376bcdc6a3bSJan Kneschke 	return 0;
377bcdc6a3bSJan Kneschke }
378bcdc6a3bSJan Kneschke #undef PATCH
379bcdc6a3bSJan Kneschke 
3807ba06c71SGlenn Strauss static handler_t mod_auth_uri_handler(server *srv, connection *con, void *p_d) {
3817ba06c71SGlenn Strauss 	size_t k;
3827ba06c71SGlenn Strauss 	plugin_data *p = p_d;
38381b2d1f0SGlenn Strauss 
3847ba06c71SGlenn Strauss 	mod_auth_patch_connection(srv, con, p);
38581b2d1f0SGlenn Strauss 
3867ba06c71SGlenn Strauss 	if (p->conf.auth_require == NULL) return HANDLER_GO_ON;
38781b2d1f0SGlenn Strauss 
3887ba06c71SGlenn Strauss 	/* search auth directives for first prefix match against URL path */
3897ba06c71SGlenn Strauss 	for (k = 0; k < p->conf.auth_require->used; k++) {
3907ba06c71SGlenn Strauss 		const data_auth * const dauth = (data_auth *)p->conf.auth_require->data[k];
3917ba06c71SGlenn Strauss 		const buffer *path = dauth->key;
3927ba06c71SGlenn Strauss 
3937ba06c71SGlenn Strauss 		if (buffer_string_length(con->uri.path) < buffer_string_length(path)) continue;
3947ba06c71SGlenn Strauss 
3957ba06c71SGlenn Strauss 		/* if we have a case-insensitive FS we have to lower-case the URI here too */
3967ba06c71SGlenn Strauss 
3977ba06c71SGlenn Strauss 		if (!con->conf.force_lowercase_filenames
3987ba06c71SGlenn Strauss 		    ? 0 == strncmp(con->uri.path->ptr, path->ptr, buffer_string_length(path))
3997ba06c71SGlenn Strauss 		    : 0 == strncasecmp(con->uri.path->ptr, path->ptr, buffer_string_length(path))) {
4007ba06c71SGlenn Strauss 			const http_auth_scheme_t * const scheme = dauth->require->scheme;
401f54d628cSGlenn Strauss 			if (p->conf.auth_extern_authn) {
402*3dd3cde9SGlenn Strauss 				buffer *vb = http_header_env_get(con, CONST_STR_LEN("REMOTE_USER"));
403*3dd3cde9SGlenn Strauss 				if (NULL != vb && http_auth_match_rules(dauth->require, vb->ptr, NULL, NULL)) {
404f54d628cSGlenn Strauss 					return HANDLER_GO_ON;
405f54d628cSGlenn Strauss 				}
406f54d628cSGlenn Strauss 			}
4077ba06c71SGlenn Strauss 			return scheme->checkfn(srv, con, scheme->p_d, dauth->require, p->conf.auth_backend);
4087ba06c71SGlenn Strauss 		}
4097ba06c71SGlenn Strauss 	}
4107ba06c71SGlenn Strauss 
4117ba06c71SGlenn Strauss 	/* nothing to do for us */
4127ba06c71SGlenn Strauss 	return HANDLER_GO_ON;
4137ba06c71SGlenn Strauss }
4147ba06c71SGlenn Strauss 
4157ba06c71SGlenn Strauss int mod_auth_plugin_init(plugin *p);
4167ba06c71SGlenn Strauss int mod_auth_plugin_init(plugin *p) {
4177ba06c71SGlenn Strauss 	p->version     = LIGHTTPD_VERSION_ID;
4187ba06c71SGlenn Strauss 	p->name        = buffer_init_string("auth");
4197ba06c71SGlenn Strauss 	p->init        = mod_auth_init;
4207ba06c71SGlenn Strauss 	p->set_defaults = mod_auth_set_defaults;
4217ba06c71SGlenn Strauss 	p->handle_uri_clean = mod_auth_uri_handler;
4227ba06c71SGlenn Strauss 	p->cleanup     = mod_auth_free;
4237ba06c71SGlenn Strauss 
4247ba06c71SGlenn Strauss 	p->data        = NULL;
4257ba06c71SGlenn Strauss 
42681b2d1f0SGlenn Strauss 	return 0;
42781b2d1f0SGlenn Strauss }
42881b2d1f0SGlenn Strauss 
42981b2d1f0SGlenn Strauss 
43081b2d1f0SGlenn Strauss 
43181b2d1f0SGlenn Strauss 
4327ba06c71SGlenn Strauss /*
4337ba06c71SGlenn Strauss  * auth schemes (basic, digest, extern)
4347ba06c71SGlenn Strauss  *
4357ba06c71SGlenn Strauss  * (could be in separate file from mod_auth.c as long as registration occurs)
4367ba06c71SGlenn Strauss  */
43781b2d1f0SGlenn Strauss 
4387ba06c71SGlenn Strauss #include "base64.h"
4397ba06c71SGlenn Strauss #include "md5.h"
4407f4e156eSGlenn Strauss #include "rand.h"
441*3dd3cde9SGlenn Strauss #include "http_header.h"
44281b2d1f0SGlenn Strauss 
4434b3a91e6SGlenn Strauss static handler_t mod_auth_send_400_bad_request(server *srv, connection *con) {
4444b3a91e6SGlenn Strauss 	UNUSED(srv);
4454b3a91e6SGlenn Strauss 
4464b3a91e6SGlenn Strauss 	/* a field was missing or invalid */
4474b3a91e6SGlenn Strauss 	con->http_status = 400; /* Bad Request */
4484b3a91e6SGlenn Strauss 	con->mode = DIRECT;
4494b3a91e6SGlenn Strauss 
4504b3a91e6SGlenn Strauss 	return HANDLER_FINISHED;
4514b3a91e6SGlenn Strauss }
4524b3a91e6SGlenn Strauss 
4537ba06c71SGlenn Strauss static handler_t mod_auth_send_401_unauthorized_basic(server *srv, connection *con, buffer *realm) {
4544b3a91e6SGlenn Strauss 	con->http_status = 401;
4554b3a91e6SGlenn Strauss 	con->mode = DIRECT;
4564b3a91e6SGlenn Strauss 
4577ba06c71SGlenn Strauss 	buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("Basic realm=\""));
4587ba06c71SGlenn Strauss 	buffer_append_string_buffer(srv->tmp_buf, realm);
4597ba06c71SGlenn Strauss 	buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("\", charset=\"UTF-8\""));
4604b3a91e6SGlenn Strauss 
461*3dd3cde9SGlenn Strauss 	http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv->tmp_buf));
4624b3a91e6SGlenn Strauss 
4634b3a91e6SGlenn Strauss 	return HANDLER_FINISHED;
4644b3a91e6SGlenn Strauss }
4654b3a91e6SGlenn Strauss 
4667ba06c71SGlenn Strauss static handler_t mod_auth_check_basic(server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend) {
467*3dd3cde9SGlenn Strauss 	buffer *b = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
46881b2d1f0SGlenn Strauss 	buffer *username;
46981b2d1f0SGlenn Strauss 	char *pw;
4707ba06c71SGlenn Strauss 	handler_t rc = HANDLER_UNSET;
47181b2d1f0SGlenn Strauss 
4727ba06c71SGlenn Strauss 	UNUSED(p_d);
47381b2d1f0SGlenn Strauss 
4747ba06c71SGlenn Strauss 	if (NULL == backend) {
4757ba06c71SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not configured for", con->uri.path);
4767ba06c71SGlenn Strauss 		con->http_status = 500;
4777ba06c71SGlenn Strauss 		con->mode = DIRECT;
4787ba06c71SGlenn Strauss 		return HANDLER_FINISHED;
4797ba06c71SGlenn Strauss 	}
4807ba06c71SGlenn Strauss 
481*3dd3cde9SGlenn Strauss 	if (NULL == b) {
4827ba06c71SGlenn Strauss 		return mod_auth_send_401_unauthorized_basic(srv, con, require->realm);
4837ba06c71SGlenn Strauss 	}
4847ba06c71SGlenn Strauss 
485*3dd3cde9SGlenn Strauss 	if (0 != strncasecmp(b->ptr, "Basic ", sizeof("Basic ")-1)) {
4864b3a91e6SGlenn Strauss 		return mod_auth_send_400_bad_request(srv, con);
4874b3a91e6SGlenn Strauss 	}
4889bc61f16SGlenn Strauss       #ifdef __COVERITY__
489*3dd3cde9SGlenn Strauss 	if (buffer_string_length(b) < sizeof("Basic ")-1) {
4909bc61f16SGlenn Strauss 		return mod_auth_send_400_bad_request(srv, con);
4919bc61f16SGlenn Strauss 	}
4929bc61f16SGlenn Strauss       #endif
49381b2d1f0SGlenn Strauss 
49481b2d1f0SGlenn Strauss 	username = buffer_init();
49581b2d1f0SGlenn Strauss 
4968047c2f4SGlenn Strauss 	/* coverity[overflow_sink : FALSE] */
4977ba06c71SGlenn Strauss 	if (!buffer_append_base64_decode(username, b->ptr+sizeof("Basic ")-1, buffer_string_length(b)-(sizeof("Basic ")-1), BASE64_STANDARD)) {
4987ba06c71SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "sb", "decoding base64-string failed", username);
49981b2d1f0SGlenn Strauss 
50081b2d1f0SGlenn Strauss 		buffer_free(username);
5014b3a91e6SGlenn Strauss 		return mod_auth_send_400_bad_request(srv, con);
50281b2d1f0SGlenn Strauss 	}
50381b2d1f0SGlenn Strauss 
50481b2d1f0SGlenn Strauss 	/* r2 == user:password */
50581b2d1f0SGlenn Strauss 	if (NULL == (pw = strchr(username->ptr, ':'))) {
5067ba06c71SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "sb", "missing ':' in", username);
50781b2d1f0SGlenn Strauss 
50881b2d1f0SGlenn Strauss 		buffer_free(username);
5094b3a91e6SGlenn Strauss 		return mod_auth_send_400_bad_request(srv, con);
51081b2d1f0SGlenn Strauss 	}
51181b2d1f0SGlenn Strauss 
51281b2d1f0SGlenn Strauss 	buffer_string_set_length(username, pw - username->ptr);
51381b2d1f0SGlenn Strauss 	pw++;
51481b2d1f0SGlenn Strauss 
515a401c946SGlenn Strauss 	rc = backend->basic(srv, con, backend->p_d, require, username, pw);
5167ba06c71SGlenn Strauss 	switch (rc) {
5174b3a91e6SGlenn Strauss 	case HANDLER_GO_ON:
518*3dd3cde9SGlenn Strauss 		http_auth_setenv(con, CONST_BUF_LEN(username), CONST_STR_LEN("Basic"));
5194b3a91e6SGlenn Strauss 		break;
5204b3a91e6SGlenn Strauss 	case HANDLER_WAIT_FOR_EVENT:
5214b3a91e6SGlenn Strauss 	case HANDLER_FINISHED:
5227ba06c71SGlenn Strauss 		break;
5234b3a91e6SGlenn Strauss 	case HANDLER_ERROR:
5244b3a91e6SGlenn Strauss 	default:
52528d12134SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "sbsBsB", "password doesn't match for", con->uri.path, "username:", username, ", IP:", con->dst_addr_buf);
5267ba06c71SGlenn Strauss 		rc = HANDLER_UNSET;
5277ba06c71SGlenn Strauss 		break;
52881b2d1f0SGlenn Strauss 	}
52981b2d1f0SGlenn Strauss 
5304b3a91e6SGlenn Strauss 	buffer_free(username);
5317ba06c71SGlenn Strauss 	return (HANDLER_UNSET != rc) ? rc : mod_auth_send_401_unauthorized_basic(srv, con, require->realm);
53281b2d1f0SGlenn Strauss }
53381b2d1f0SGlenn Strauss 
53481b2d1f0SGlenn Strauss #define HASHLEN 16
53581b2d1f0SGlenn Strauss #define HASHHEXLEN 32
53681b2d1f0SGlenn Strauss typedef unsigned char HASH[HASHLEN];
53781b2d1f0SGlenn Strauss typedef char HASHHEX[HASHHEXLEN+1];
53881b2d1f0SGlenn Strauss 
53981b2d1f0SGlenn Strauss static void CvtHex(const HASH Bin, char (*Hex)[33]) {
54081b2d1f0SGlenn Strauss 	li_tohex(*Hex, sizeof(*Hex), (const char*) Bin, 16);
54181b2d1f0SGlenn Strauss }
54281b2d1f0SGlenn Strauss 
54381b2d1f0SGlenn Strauss typedef struct {
54481b2d1f0SGlenn Strauss 	const char *key;
54581b2d1f0SGlenn Strauss 	int key_len;
54681b2d1f0SGlenn Strauss 	char **ptr;
54781b2d1f0SGlenn Strauss } digest_kv;
54881b2d1f0SGlenn Strauss 
5497ba06c71SGlenn Strauss static handler_t mod_auth_send_401_unauthorized_digest(server *srv, connection *con, buffer *realm, int nonce_stale);
5507ba06c71SGlenn Strauss 
5517ba06c71SGlenn Strauss static handler_t mod_auth_check_digest(server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend) {
552*3dd3cde9SGlenn Strauss 	buffer *vb = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
5537ba06c71SGlenn Strauss 
55481b2d1f0SGlenn Strauss 	char a1[33];
55581b2d1f0SGlenn Strauss 	char a2[33];
55681b2d1f0SGlenn Strauss 
55781b2d1f0SGlenn Strauss 	char *username = NULL;
55881b2d1f0SGlenn Strauss 	char *realm = NULL;
55981b2d1f0SGlenn Strauss 	char *nonce = NULL;
56081b2d1f0SGlenn Strauss 	char *uri = NULL;
56181b2d1f0SGlenn Strauss 	char *algorithm = NULL;
56281b2d1f0SGlenn Strauss 	char *qop = NULL;
56381b2d1f0SGlenn Strauss 	char *cnonce = NULL;
56481b2d1f0SGlenn Strauss 	char *nc = NULL;
56581b2d1f0SGlenn Strauss 	char *respons = NULL;
56681b2d1f0SGlenn Strauss 
56781b2d1f0SGlenn Strauss 	char *e, *c;
56881b2d1f0SGlenn Strauss 	const char *m = NULL;
56981b2d1f0SGlenn Strauss 	int i;
57081b2d1f0SGlenn Strauss 	buffer *b;
57181b2d1f0SGlenn Strauss 
57281b2d1f0SGlenn Strauss 	li_MD5_CTX Md5Ctx;
57381b2d1f0SGlenn Strauss 	HASH HA1;
57481b2d1f0SGlenn Strauss 	HASH HA2;
57581b2d1f0SGlenn Strauss 	HASH RespHash;
57681b2d1f0SGlenn Strauss 	HASHHEX HA2Hex;
57781b2d1f0SGlenn Strauss 
57881b2d1f0SGlenn Strauss 
57981b2d1f0SGlenn Strauss 	/* init pointers */
58081b2d1f0SGlenn Strauss #define S(x) \
58181b2d1f0SGlenn Strauss 	x, sizeof(x)-1, NULL
58281b2d1f0SGlenn Strauss 	digest_kv dkv[10] = {
58381b2d1f0SGlenn Strauss 		{ S("username=") },
58481b2d1f0SGlenn Strauss 		{ S("realm=") },
58581b2d1f0SGlenn Strauss 		{ S("nonce=") },
58681b2d1f0SGlenn Strauss 		{ S("uri=") },
58781b2d1f0SGlenn Strauss 		{ S("algorithm=") },
58881b2d1f0SGlenn Strauss 		{ S("qop=") },
58981b2d1f0SGlenn Strauss 		{ S("cnonce=") },
59081b2d1f0SGlenn Strauss 		{ S("nc=") },
59181b2d1f0SGlenn Strauss 		{ S("response=") },
59281b2d1f0SGlenn Strauss 
59381b2d1f0SGlenn Strauss 		{ NULL, 0, NULL }
59481b2d1f0SGlenn Strauss 	};
59581b2d1f0SGlenn Strauss #undef S
59681b2d1f0SGlenn Strauss 
59781b2d1f0SGlenn Strauss 	dkv[0].ptr = &username;
59881b2d1f0SGlenn Strauss 	dkv[1].ptr = &realm;
59981b2d1f0SGlenn Strauss 	dkv[2].ptr = &nonce;
60081b2d1f0SGlenn Strauss 	dkv[3].ptr = &uri;
60181b2d1f0SGlenn Strauss 	dkv[4].ptr = &algorithm;
60281b2d1f0SGlenn Strauss 	dkv[5].ptr = &qop;
60381b2d1f0SGlenn Strauss 	dkv[6].ptr = &cnonce;
60481b2d1f0SGlenn Strauss 	dkv[7].ptr = &nc;
60581b2d1f0SGlenn Strauss 	dkv[8].ptr = &respons;
60681b2d1f0SGlenn Strauss 
6077ba06c71SGlenn Strauss 	UNUSED(p_d);
6087ba06c71SGlenn Strauss 
6097ba06c71SGlenn Strauss 	if (NULL == backend) {
6107ba06c71SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not configured for", con->uri.path);
6117ba06c71SGlenn Strauss 		con->http_status = 500;
6127ba06c71SGlenn Strauss 		con->mode = DIRECT;
6137ba06c71SGlenn Strauss 		return HANDLER_FINISHED;
6147ba06c71SGlenn Strauss 	}
6157ba06c71SGlenn Strauss 
616*3dd3cde9SGlenn Strauss 	if (NULL == vb) {
6177ba06c71SGlenn Strauss 		return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
6187ba06c71SGlenn Strauss 	}
6197ba06c71SGlenn Strauss 
620*3dd3cde9SGlenn Strauss 	if (0 != strncasecmp(vb->ptr, "Digest ", sizeof("Digest ")-1)) {
6217ba06c71SGlenn Strauss 		return mod_auth_send_400_bad_request(srv, con);
622dadfb5fcSGlenn Strauss 	} else {
623*3dd3cde9SGlenn Strauss 		size_t n = buffer_string_length(vb);
6249bc61f16SGlenn Strauss 	      #ifdef __COVERITY__
625dadfb5fcSGlenn Strauss 		if (n < sizeof("Digest ")-1) {
6269bc61f16SGlenn Strauss 			return mod_auth_send_400_bad_request(srv, con);
6279bc61f16SGlenn Strauss 		}
6289bc61f16SGlenn Strauss 	      #endif
629dadfb5fcSGlenn Strauss 		n -= (sizeof("Digest ")-1);
6307ba06c71SGlenn Strauss 		b = buffer_init();
631*3dd3cde9SGlenn Strauss 		buffer_copy_string_len(b,vb->ptr+sizeof("Digest ")-1,n);
632dadfb5fcSGlenn Strauss 	}
63381b2d1f0SGlenn Strauss 
63481b2d1f0SGlenn Strauss 	/* parse credentials from client */
63581b2d1f0SGlenn Strauss 	for (c = b->ptr; *c; c++) {
63681b2d1f0SGlenn Strauss 		/* skip whitespaces */
63781b2d1f0SGlenn Strauss 		while (*c == ' ' || *c == '\t') c++;
63881b2d1f0SGlenn Strauss 		if (!*c) break;
63981b2d1f0SGlenn Strauss 
64081b2d1f0SGlenn Strauss 		for (i = 0; dkv[i].key; i++) {
64181b2d1f0SGlenn Strauss 			if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) {
64281b2d1f0SGlenn Strauss 				if ((c[dkv[i].key_len] == '"') &&
64381b2d1f0SGlenn Strauss 				    (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) {
64481b2d1f0SGlenn Strauss 					/* value with "..." */
64581b2d1f0SGlenn Strauss 					*(dkv[i].ptr) = c + dkv[i].key_len + 1;
64681b2d1f0SGlenn Strauss 					c = e;
64781b2d1f0SGlenn Strauss 
64881b2d1f0SGlenn Strauss 					*e = '\0';
64981b2d1f0SGlenn Strauss 				} else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) {
65081b2d1f0SGlenn Strauss 					/* value without "...", terminated by ',' */
65181b2d1f0SGlenn Strauss 					*(dkv[i].ptr) = c + dkv[i].key_len;
65281b2d1f0SGlenn Strauss 					c = e;
65381b2d1f0SGlenn Strauss 
65481b2d1f0SGlenn Strauss 					*e = '\0';
65581b2d1f0SGlenn Strauss 				} else {
65681b2d1f0SGlenn Strauss 					/* value without "...", terminated by EOL */
65781b2d1f0SGlenn Strauss 					*(dkv[i].ptr) = c + dkv[i].key_len;
65881b2d1f0SGlenn Strauss 					c += strlen(c) - 1;
65981b2d1f0SGlenn Strauss 				}
66081b2d1f0SGlenn Strauss 				break;
66181b2d1f0SGlenn Strauss 			}
66281b2d1f0SGlenn Strauss 		}
66381b2d1f0SGlenn Strauss 	}
66481b2d1f0SGlenn Strauss 
66581b2d1f0SGlenn Strauss 	/* check if everything is transmitted */
66681b2d1f0SGlenn Strauss 	if (!username ||
66781b2d1f0SGlenn Strauss 	    !realm ||
66881b2d1f0SGlenn Strauss 	    !nonce ||
66981b2d1f0SGlenn Strauss 	    !uri ||
67081b2d1f0SGlenn Strauss 	    (qop && (!nc || !cnonce)) ||
67181b2d1f0SGlenn Strauss 	    !respons ) {
67281b2d1f0SGlenn Strauss 		/* missing field */
67381b2d1f0SGlenn Strauss 
67481b2d1f0SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "s",
67581b2d1f0SGlenn Strauss 				"digest: missing field");
67681b2d1f0SGlenn Strauss 
67781b2d1f0SGlenn Strauss 		buffer_free(b);
6784b3a91e6SGlenn Strauss 		return mod_auth_send_400_bad_request(srv, con);
67981b2d1f0SGlenn Strauss 	}
68081b2d1f0SGlenn Strauss 
6815a6e4df8SGlenn Strauss 	if (!buffer_is_equal_string(require->realm, realm, strlen(realm))) {
6825a6e4df8SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "s",
6835a6e4df8SGlenn Strauss 				"digest: realm mismatch");
6845a6e4df8SGlenn Strauss 		buffer_free(b);
6855a6e4df8SGlenn Strauss 		return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
6865a6e4df8SGlenn Strauss 	}
6875a6e4df8SGlenn Strauss 
68881b2d1f0SGlenn Strauss 	/**
68981b2d1f0SGlenn Strauss 	 * protect the md5-sess against missing cnonce and nonce
69081b2d1f0SGlenn Strauss 	 */
69181b2d1f0SGlenn Strauss 	if (algorithm &&
69281b2d1f0SGlenn Strauss 	    0 == strcasecmp(algorithm, "md5-sess") &&
69381b2d1f0SGlenn Strauss 	    (!nonce || !cnonce)) {
69481b2d1f0SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "s",
69581b2d1f0SGlenn Strauss 				"digest: (md5-sess: missing field");
69681b2d1f0SGlenn Strauss 
69781b2d1f0SGlenn Strauss 		buffer_free(b);
6984b3a91e6SGlenn Strauss 		return mod_auth_send_400_bad_request(srv, con);
69981b2d1f0SGlenn Strauss 	}
70081b2d1f0SGlenn Strauss 
70181b2d1f0SGlenn Strauss 	if (qop && strcasecmp(qop, "auth-int") == 0) {
70281b2d1f0SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "s",
70381b2d1f0SGlenn Strauss 				"digest: qop=auth-int not supported");
70481b2d1f0SGlenn Strauss 
70581b2d1f0SGlenn Strauss 		buffer_free(b);
7064b3a91e6SGlenn Strauss 		return mod_auth_send_400_bad_request(srv, con);
70781b2d1f0SGlenn Strauss 	}
70881b2d1f0SGlenn Strauss 
70981b2d1f0SGlenn Strauss 	m = get_http_method_name(con->request.http_method);
71081b2d1f0SGlenn Strauss 	force_assert(m);
71181b2d1f0SGlenn Strauss 
71281b2d1f0SGlenn Strauss 	/* detect if attacker is attempting to reuse valid digest for one uri
71381b2d1f0SGlenn Strauss 	 * on a different request uri.  Might also happen if intermediate proxy
71481b2d1f0SGlenn Strauss 	 * altered client request line.  (Altered request would not result in
71581b2d1f0SGlenn Strauss 	 * the same digest as that calculated by the client.)
71681b2d1f0SGlenn Strauss 	 * Internal redirects such as with mod_rewrite will modify request uri.
71781b2d1f0SGlenn Strauss 	 * Reauthentication is done to detect crossing auth realms, but this
71881b2d1f0SGlenn Strauss 	 * uri validation step is bypassed.  con->request.orig_uri is original
71981b2d1f0SGlenn Strauss 	 * uri sent in client request. */
72081b2d1f0SGlenn Strauss 	{
72181b2d1f0SGlenn Strauss 		const size_t ulen = strlen(uri);
72281b2d1f0SGlenn Strauss 		const size_t rlen = buffer_string_length(con->request.orig_uri);
72381b2d1f0SGlenn Strauss 		if (!buffer_is_equal_string(con->request.orig_uri, uri, ulen)
72481b2d1f0SGlenn Strauss 		    && !(rlen < ulen && 0 == memcmp(con->request.orig_uri->ptr, uri, rlen) && uri[rlen] == '?')) {
72528d12134SGlenn Strauss 			log_error_write(srv, __FILE__, __LINE__, "sbsssB",
7267ba06c71SGlenn Strauss 					"digest: auth failed: uri mismatch (", con->request.orig_uri, "!=", uri, "), IP:", con->dst_addr_buf);
72781b2d1f0SGlenn Strauss 			buffer_free(b);
7284b3a91e6SGlenn Strauss 			return mod_auth_send_400_bad_request(srv, con);
72981b2d1f0SGlenn Strauss 		}
73081b2d1f0SGlenn Strauss 	}
73181b2d1f0SGlenn Strauss 
73281b2d1f0SGlenn Strauss 	/* password-string == HA1 */
7337ba06c71SGlenn Strauss 	switch (backend->digest(srv, con, backend->p_d, username, realm, HA1)) {
7344b3a91e6SGlenn Strauss 	case HANDLER_GO_ON:
7354b3a91e6SGlenn Strauss 		break;
7364b3a91e6SGlenn Strauss 	case HANDLER_WAIT_FOR_EVENT:
73781b2d1f0SGlenn Strauss 		buffer_free(b);
7384b3a91e6SGlenn Strauss 		return HANDLER_WAIT_FOR_EVENT;
7394b3a91e6SGlenn Strauss 	case HANDLER_FINISHED:
7404b3a91e6SGlenn Strauss 		buffer_free(b);
7414b3a91e6SGlenn Strauss 		return HANDLER_FINISHED;
7424b3a91e6SGlenn Strauss 	case HANDLER_ERROR:
7434b3a91e6SGlenn Strauss 	default:
7444b3a91e6SGlenn Strauss 		buffer_free(b);
7457ba06c71SGlenn Strauss 		return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
74681b2d1f0SGlenn Strauss 	}
74781b2d1f0SGlenn Strauss 
74881b2d1f0SGlenn Strauss 	if (algorithm &&
74981b2d1f0SGlenn Strauss 	    strcasecmp(algorithm, "md5-sess") == 0) {
75081b2d1f0SGlenn Strauss 		li_MD5_Init(&Md5Ctx);
75181b2d1f0SGlenn Strauss 		/* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
75281b2d1f0SGlenn Strauss 		CvtHex(HA1, &a1);
75381b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
75481b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
75581b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
75681b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
75781b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
75881b2d1f0SGlenn Strauss 		li_MD5_Final(HA1, &Md5Ctx);
75981b2d1f0SGlenn Strauss 	}
76081b2d1f0SGlenn Strauss 
76181b2d1f0SGlenn Strauss 	CvtHex(HA1, &a1);
76281b2d1f0SGlenn Strauss 
76381b2d1f0SGlenn Strauss 	/* calculate H(A2) */
76481b2d1f0SGlenn Strauss 	li_MD5_Init(&Md5Ctx);
76581b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m));
76681b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
76781b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri));
76881b2d1f0SGlenn Strauss 	/* qop=auth-int not supported, already checked above */
76981b2d1f0SGlenn Strauss /*
77081b2d1f0SGlenn Strauss 	if (qop && strcasecmp(qop, "auth-int") == 0) {
77181b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
77281b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
77381b2d1f0SGlenn Strauss 	}
77481b2d1f0SGlenn Strauss */
77581b2d1f0SGlenn Strauss 	li_MD5_Final(HA2, &Md5Ctx);
77681b2d1f0SGlenn Strauss 	CvtHex(HA2, &HA2Hex);
77781b2d1f0SGlenn Strauss 
77881b2d1f0SGlenn Strauss 	/* calculate response */
77981b2d1f0SGlenn Strauss 	li_MD5_Init(&Md5Ctx);
78081b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
78181b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
78281b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
78381b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
78481b2d1f0SGlenn Strauss 	if (qop && *qop) {
78581b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc));
78681b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
78781b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
78881b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
78981b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop));
79081b2d1f0SGlenn Strauss 		li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
79181b2d1f0SGlenn Strauss 	};
79281b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
79381b2d1f0SGlenn Strauss 	li_MD5_Final(RespHash, &Md5Ctx);
79481b2d1f0SGlenn Strauss 	CvtHex(RespHash, &a2);
79581b2d1f0SGlenn Strauss 
79681b2d1f0SGlenn Strauss 	if (0 != strcmp(a2, respons)) {
79781b2d1f0SGlenn Strauss 		/* digest not ok */
79828d12134SGlenn Strauss 		log_error_write(srv, __FILE__, __LINE__, "sssB",
7997ba06c71SGlenn Strauss 				"digest: auth failed for ", username, ": wrong password, IP:", con->dst_addr_buf);
80081b2d1f0SGlenn Strauss 
80181b2d1f0SGlenn Strauss 		buffer_free(b);
8027ba06c71SGlenn Strauss 		return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
80381b2d1f0SGlenn Strauss 	}
80481b2d1f0SGlenn Strauss 
80581b2d1f0SGlenn Strauss 	/* value is our allow-rules */
8067ba06c71SGlenn Strauss 	if (!http_auth_match_rules(require, username, NULL, NULL)) {
8074b3a91e6SGlenn Strauss 		buffer_free(b);
8087ba06c71SGlenn Strauss 		return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
80981b2d1f0SGlenn Strauss 	}
81081b2d1f0SGlenn Strauss 
8117f4e156eSGlenn Strauss 	/* check age of nonce.  Note, random data is used in nonce generation
8127ba06c71SGlenn Strauss 	 * in mod_auth_send_401_unauthorized_digest().  If that were replaced
81381b2d1f0SGlenn Strauss 	 * with nanosecond time, then nonce secret would remain unique enough
81481b2d1f0SGlenn Strauss 	 * for the purposes of Digest auth, and would be reproducible (and
81581b2d1f0SGlenn Strauss 	 * verifiable) if nanoseconds were inclued with seconds as part of the
81681b2d1f0SGlenn Strauss 	 * nonce "timestamp:secret".  Since that is not done, timestamp in
81781b2d1f0SGlenn Strauss 	 * nonce could theoretically be modified and still produce same md5sum,
81881b2d1f0SGlenn Strauss 	 * but that is highly unlikely within a 10 min (moving) window of valid
81981b2d1f0SGlenn Strauss 	 * time relative to current time (now) */
82081b2d1f0SGlenn Strauss 	{
82181b2d1f0SGlenn Strauss 		time_t ts = 0;
82281b2d1f0SGlenn Strauss 		const unsigned char * const nonce_uns = (unsigned char *)nonce;
82381b2d1f0SGlenn Strauss 		for (i = 0; i < 8 && light_isxdigit(nonce_uns[i]); ++i) {
82481b2d1f0SGlenn Strauss 			ts = (ts << 4) + hex2int(nonce_uns[i]);
82581b2d1f0SGlenn Strauss 		}
82683759aedSGlenn Strauss 		if (nonce[i] != ':'
82781b2d1f0SGlenn Strauss 		    || ts > srv->cur_ts || srv->cur_ts - ts > 600) { /*(10 mins)*/
8284b3a91e6SGlenn Strauss 			/* nonce is stale; have client regenerate digest */
82981b2d1f0SGlenn Strauss 			buffer_free(b);
8307ba06c71SGlenn Strauss 			return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 1);
83181b2d1f0SGlenn Strauss 		} /*(future: might send nextnonce when expiration is imminent)*/
83281b2d1f0SGlenn Strauss 	}
83381b2d1f0SGlenn Strauss 
834*3dd3cde9SGlenn Strauss 	http_auth_setenv(con, username, strlen(username), CONST_STR_LEN("Digest"));
83581b2d1f0SGlenn Strauss 
83681b2d1f0SGlenn Strauss 	buffer_free(b);
83781b2d1f0SGlenn Strauss 
8384b3a91e6SGlenn Strauss 	return HANDLER_GO_ON;
83981b2d1f0SGlenn Strauss }
84081b2d1f0SGlenn Strauss 
8417ba06c71SGlenn Strauss static handler_t mod_auth_send_401_unauthorized_digest(server *srv, connection *con, buffer *realm, int nonce_stale) {
84281b2d1f0SGlenn Strauss 	li_MD5_CTX Md5Ctx;
8437ba06c71SGlenn Strauss 	HASH h;
8447ba06c71SGlenn Strauss 	char hh[33];
84581b2d1f0SGlenn Strauss 
8467ba06c71SGlenn Strauss 	force_assert(33 >= LI_ITOSTRING_LENGTH); /*(buffer used for both li_itostrn() and CvtHex())*/
8477ba06c71SGlenn Strauss 
8487ba06c71SGlenn Strauss 	/* generate nonce */
8497ba06c71SGlenn Strauss 
85081b2d1f0SGlenn Strauss 	/* generate shared-secret */
85181b2d1f0SGlenn Strauss 	li_MD5_Init(&Md5Ctx);
85281b2d1f0SGlenn Strauss 
85381b2d1f0SGlenn Strauss 	li_itostrn(hh, sizeof(hh), srv->cur_ts);
85481b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
8556e171bd4SGlenn Strauss 	li_itostrn(hh, sizeof(hh), li_rand_pseudo());
85681b2d1f0SGlenn Strauss 	li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
85781b2d1f0SGlenn Strauss 
85881b2d1f0SGlenn Strauss 	li_MD5_Final(h, &Md5Ctx);
85981b2d1f0SGlenn Strauss 
8607ba06c71SGlenn Strauss 	CvtHex(h, &hh);
86181b2d1f0SGlenn Strauss 
8627ba06c71SGlenn Strauss 	/* generate WWW-Authenticate */
86381b2d1f0SGlenn Strauss 
864f9d58670SStefan Bühler 	con->http_status = 401;
865f9d58670SStefan Bühler 	con->mode = DIRECT;
8667ba06c71SGlenn Strauss 
8677ba06c71SGlenn Strauss 	buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("Digest realm=\""));
8687ba06c71SGlenn Strauss 	buffer_append_string_buffer(srv->tmp_buf, realm);
8697ba06c71SGlenn Strauss 	buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("\", charset=\"UTF-8\", nonce=\""));
8707ba06c71SGlenn Strauss 	buffer_append_uint_hex(srv->tmp_buf, (uintmax_t)srv->cur_ts);
8717ba06c71SGlenn Strauss 	buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN(":"));
8727ba06c71SGlenn Strauss 	buffer_append_string(srv->tmp_buf, hh);
8737ba06c71SGlenn Strauss 	buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("\", qop=\"auth\""));
8747ba06c71SGlenn Strauss 	if (nonce_stale) {
8757ba06c71SGlenn Strauss 		buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN(", stale=true"));
8767ba06c71SGlenn Strauss 	}
8777ba06c71SGlenn Strauss 
878*3dd3cde9SGlenn Strauss 	http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv->tmp_buf));
8797ba06c71SGlenn Strauss 
880f9d58670SStefan Bühler 	return HANDLER_FINISHED;
8817ba06c71SGlenn Strauss }
8827ba06c71SGlenn Strauss 
8837ba06c71SGlenn Strauss static handler_t mod_auth_check_extern(server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend) {
8847ba06c71SGlenn Strauss 	/* require REMOTE_USER already set */
885*3dd3cde9SGlenn Strauss 	buffer *vb = http_header_env_get(con, CONST_STR_LEN("REMOTE_USER"));
8867ba06c71SGlenn Strauss 	UNUSED(srv);
8877ba06c71SGlenn Strauss 	UNUSED(p_d);
8887ba06c71SGlenn Strauss 	UNUSED(backend);
889*3dd3cde9SGlenn Strauss 	if (NULL != vb && http_auth_match_rules(require, vb->ptr, NULL, NULL)) {
890f9d58670SStefan Bühler 		return HANDLER_GO_ON;
891bcdc6a3bSJan Kneschke 	} else {
8924b3a91e6SGlenn Strauss 		con->http_status = 401;
8934b3a91e6SGlenn Strauss 		con->mode = DIRECT;
894bcdc6a3bSJan Kneschke 		return HANDLER_FINISHED;
8957187271fSStefan Bühler 	}
896bcdc6a3bSJan Kneschke }
897