summaryrefslogtreecommitdiff
path: root/data/lighttpd/lighttpd-1.4.53/src/mod_auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'data/lighttpd/lighttpd-1.4.53/src/mod_auth.c')
-rw-r--r--data/lighttpd/lighttpd-1.4.53/src/mod_auth.c888
1 files changed, 888 insertions, 0 deletions
diff --git a/data/lighttpd/lighttpd-1.4.53/src/mod_auth.c b/data/lighttpd/lighttpd-1.4.53/src/mod_auth.c
new file mode 100644
index 000000000..977b5c2dd
--- /dev/null
+++ b/data/lighttpd/lighttpd-1.4.53/src/mod_auth.c
@@ -0,0 +1,888 @@
+#include "first.h"
+
+#include "base.h"
+#include "plugin.h"
+#include "http_auth.h"
+#include "http_header.h"
+#include "log.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * auth framework
+ */
+
+typedef struct {
+ /* auth */
+ array *auth_require;
+ buffer *auth_backend_conf;
+ unsigned short auth_extern_authn;
+
+ /* generated */
+ const http_auth_backend_t *auth_backend;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+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);
+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);
+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);
+
+INIT_FUNC(mod_auth_init) {
+ static const http_auth_scheme_t http_auth_scheme_basic = { "basic", mod_auth_check_basic, NULL };
+ static const http_auth_scheme_t http_auth_scheme_digest = { "digest", mod_auth_check_digest, NULL };
+ static const http_auth_scheme_t http_auth_scheme_extern = { "extern", mod_auth_check_extern, NULL };
+ plugin_data *p;
+
+ /* register http_auth_scheme_* */
+ http_auth_scheme_set(&http_auth_scheme_basic);
+ http_auth_scheme_set(&http_auth_scheme_digest);
+ http_auth_scheme_set(&http_auth_scheme_extern);
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+FREE_FUNC(mod_auth_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (NULL == s) continue;
+
+ array_free(s->auth_require);
+ buffer_free(s->auth_backend_conf);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* data type for mod_auth structured data
+ * (parsed from auth.require array of strings) */
+typedef struct {
+ DATA_UNSET;
+ http_auth_require_t *require;
+} data_auth;
+
+static void data_auth_free(data_unset *d)
+{
+ data_auth * const dauth = (data_auth *)d;
+ buffer_free(dauth->key);
+ http_auth_require_free(dauth->require);
+ free(dauth);
+}
+
+static data_auth *data_auth_init(void)
+{
+ static const struct data_methods fn = {
+ NULL, /* reset must not be called on this data */
+ NULL, /* copy must not be called on this data */
+ data_auth_free,
+ NULL, /* insert_dup must not be called on this data */
+ NULL /* print must not be called on this data */
+ };
+ data_auth * const dauth = calloc(1, sizeof(*dauth));
+ force_assert(NULL != dauth);
+ dauth->type = TYPE_OTHER;
+ dauth->fn = &fn;
+
+ dauth->key = buffer_init();
+ dauth->require = http_auth_require_init();
+
+ return dauth;
+}
+
+static int mod_auth_require_parse (server *srv, http_auth_require_t * const require, const buffer *b)
+{
+ /* user=name1|user=name2|group=name3|host=name4 */
+
+ const char *str = b->ptr;
+ const char *p;
+
+ if (buffer_is_equal_string(b, CONST_STR_LEN("valid-user"))) {
+ require->valid_user = 1;
+ return 1; /* success */
+ }
+
+ do {
+ const char *eq;
+ size_t len;
+ p = strchr(str, '|');
+ len = NULL != p ? (size_t)(p - str) : strlen(str);
+ eq = memchr(str, '=', len);
+ if (NULL == eq) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbss",
+ "error parsing auth.require 'require' field: missing '='",
+ "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
+ "error value:", b, "error near:", str);
+ return 0;
+ }
+ if (p-1 == eq) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbss",
+ "error parsing auth.require 'require' field: missing token after '='",
+ "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
+ "error value:", b, "error near:", str);
+ return 0;
+ }
+
+ switch ((int)(eq - str)) {
+ case 4:
+ if (0 == memcmp(str, CONST_STR_LEN("user"))) {
+ /*("user=" is 5)*/
+ array_set_key_value(require->user, str+5, len-5, CONST_STR_LEN(""));
+ continue;
+ }
+ else if (0 == memcmp(str, CONST_STR_LEN("host"))) {
+ /*("host=" is 5)*/
+ array_set_key_value(require->host, str+5, len-5, CONST_STR_LEN(""));
+ log_error_write(srv, __FILE__, __LINE__, "ssb",
+ "warning parsing auth.require 'require' field: 'host' not implemented;",
+ "field value:", b);
+ continue;
+ }
+ break; /* to error */
+ case 5:
+ if (0 == memcmp(str, CONST_STR_LEN("group"))) {
+ /*("group=" is 6)*/
+ array_set_key_value(require->group, str+6, len-6, CONST_STR_LEN(""));
+ #if 0/*(supported by mod_authn_ldap, but not all other backends)*/
+ log_error_write(srv, __FILE__, __LINE__, "ssb",
+ "warning parsing auth.require 'require' field: 'group' not implemented;",
+ "field value:", b);
+ #endif
+ continue;
+ }
+ break; /* to error */
+ case 10:
+ if (0 == memcmp(str, CONST_STR_LEN("valid-user"))) {
+ log_error_write(srv, __FILE__, __LINE__, "sssb",
+ "error parsing auth.require 'require' field: valid user can not be combined with other require rules",
+ "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
+ "error value:", b);
+ return 0;
+ }
+ break; /* to error */
+ default:
+ break; /* to error */
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sssbss",
+ "error parsing auth.require 'require' field: invalid/unsupported token",
+ "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
+ "error value:", b, "error near:", str);
+ return 0;
+
+ } while (p && *((str = p+1)));
+
+ return 1; /* success */
+}
+
+SETDEFAULTS_FUNC(mod_auth_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "auth.backend", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "auth.require", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "auth.extern-authn", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },/* 2 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ data_config const* config = (data_config const*)srv->config_context->data[i];
+ plugin_config *s;
+ size_t n;
+ data_array *da;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->auth_backend_conf = buffer_init();
+
+ s->auth_require = array_init();
+
+ cv[0].destination = s->auth_backend_conf;
+ cv[1].destination = s->auth_require; /* T_CONFIG_LOCAL; not modified by config_insert_values_global() */
+ cv[2].destination = &s->auth_extern_authn;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
+ return HANDLER_ERROR;
+ }
+
+ if (!buffer_string_is_empty(s->auth_backend_conf)) {
+ s->auth_backend = http_auth_backend_get(s->auth_backend_conf);
+ if (NULL == s->auth_backend) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not supported:", s->auth_backend_conf);
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ /* no auth.require for this section */
+ if (NULL == (da = (data_array *)array_get_element(config->value, "auth.require"))) continue;
+
+ if (da->type != TYPE_ARRAY || !array_is_kvarray(da->value)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "unexpected value for auth.require; expected ",
+ "auth.require = ( \"urlpath\" => ( \"option\" => \"value\" ) )");
+ return HANDLER_ERROR;
+ }
+
+
+ for (n = 0; n < da->value->used; n++) {
+ size_t m;
+ data_array *da_file = (data_array *)da->value->data[n];
+ const buffer *method = NULL, *realm = NULL, *require = NULL;
+ const http_auth_scheme_t *auth_scheme;
+
+ if (!array_is_kvstring(da_file->value)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "unexpected value for auth.require; expected ",
+ "auth.require = ( \"urlpath\" => ( \"option\" => \"value\" ) )");
+
+ return HANDLER_ERROR;
+ }
+
+ for (m = 0; m < da_file->value->used; m++) {
+ if (da_file->value->data[m]->type == TYPE_STRING) {
+ data_string *ds = (data_string *)da_file->value->data[m];
+ if (buffer_is_equal_string(ds->key, CONST_STR_LEN("method"))) {
+ method = ds->value;
+ } else if (buffer_is_equal_string(ds->key, CONST_STR_LEN("realm"))) {
+ realm = ds->value;
+ } else if (buffer_is_equal_string(ds->key, CONST_STR_LEN("require"))) {
+ require = ds->value;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssbs",
+ "the field is unknown in:",
+ "auth.require = ( \"...\" => ( ..., -> \"",
+ da_file->value->data[m]->key,
+ "\" <- => \"...\" ) )");
+
+ return HANDLER_ERROR;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssbs",
+ "a string was expected for:",
+ "auth.require = ( \"...\" => ( ..., -> \"",
+ da_file->value->data[m]->key,
+ "\" <- => \"...\" ) )");
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (buffer_string_is_empty(method)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "the method field is missing or blank in:",
+ "auth.require = ( \"...\" => ( ..., \"method\" => \"...\" ) )");
+ return HANDLER_ERROR;
+ } else {
+ auth_scheme = http_auth_scheme_get(method);
+ if (NULL == auth_scheme) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "unknown method", method, "(e.g. \"basic\", \"digest\" or \"extern\") in",
+ "auth.require = ( \"...\" => ( ..., \"method\" => \"...\") )");
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (buffer_is_empty(realm)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "the realm field is missing in:",
+ "auth.require = ( \"...\" => ( ..., \"realm\" => \"...\" ) )");
+ return HANDLER_ERROR;
+ }
+
+ if (buffer_string_is_empty(require)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "the require field is missing or blank in:",
+ "auth.require = ( \"...\" => ( ..., \"require\" => \"...\" ) )");
+ return HANDLER_ERROR;
+ }
+
+ if (require) { /*(always true at this point)*/
+ data_auth * const dauth = data_auth_init();
+ buffer_copy_buffer(dauth->key, da_file->key);
+ dauth->require->scheme = auth_scheme;
+ buffer_copy_buffer(dauth->require->realm, realm);
+ if (!mod_auth_require_parse(srv, dauth->require, require)) {
+ dauth->fn->free((data_unset *)dauth);
+ return HANDLER_ERROR;
+ }
+ array_insert_unique(s->auth_require, (data_unset *)dauth);
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_auth_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(auth_backend);
+ PATCH(auth_require);
+ PATCH(auth_extern_authn);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend"))) {
+ PATCH(auth_backend);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.require"))) {
+ PATCH(auth_require);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.extern-authn"))) {
+ PATCH(auth_extern_authn);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+static handler_t mod_auth_uri_handler(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ data_auth *dauth;
+
+ mod_auth_patch_connection(srv, con, p);
+
+ if (p->conf.auth_require == NULL) return HANDLER_GO_ON;
+
+ /* search auth directives for first prefix match against URL path */
+ /* if we have a case-insensitive FS we have to lower-case the URI here too */
+ dauth = (!con->conf.force_lowercase_filenames)
+ ? (data_auth *)array_match_key_prefix(p->conf.auth_require, con->uri.path)
+ : (data_auth *)array_match_key_prefix_nc(p->conf.auth_require, con->uri.path);
+ if (NULL == dauth) return HANDLER_GO_ON;
+
+ {
+ const http_auth_scheme_t * const scheme = dauth->require->scheme;
+ if (p->conf.auth_extern_authn) {
+ buffer *vb = http_header_env_get(con, CONST_STR_LEN("REMOTE_USER"));
+ if (NULL != vb && http_auth_match_rules(dauth->require, vb->ptr, NULL, NULL)) {
+ return HANDLER_GO_ON;
+ }
+ }
+ return scheme->checkfn(srv, con, scheme->p_d, dauth->require, p->conf.auth_backend);
+ }
+}
+
+int mod_auth_plugin_init(plugin *p);
+int mod_auth_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("auth");
+ p->init = mod_auth_init;
+ p->set_defaults = mod_auth_set_defaults;
+ p->handle_uri_clean = mod_auth_uri_handler;
+ p->cleanup = mod_auth_free;
+
+ p->data = NULL;
+
+ return 0;
+}
+
+
+
+
+/*
+ * auth schemes (basic, digest, extern)
+ *
+ * (could be in separate file from mod_auth.c as long as registration occurs)
+ */
+
+#include "base64.h"
+#include "md5.h"
+#include "rand.h"
+#include "http_header.h"
+
+static handler_t mod_auth_send_400_bad_request(server *srv, connection *con) {
+ UNUSED(srv);
+
+ /* a field was missing or invalid */
+ con->http_status = 400; /* Bad Request */
+ con->mode = DIRECT;
+
+ return HANDLER_FINISHED;
+}
+
+static handler_t mod_auth_send_401_unauthorized_basic(server *srv, connection *con, buffer *realm) {
+ con->http_status = 401;
+ con->mode = DIRECT;
+
+ buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("Basic realm=\""));
+ buffer_append_string_buffer(srv->tmp_buf, realm);
+ buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("\", charset=\"UTF-8\""));
+
+ http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv->tmp_buf));
+
+ return HANDLER_FINISHED;
+}
+
+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) {
+ buffer *b = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
+ buffer *username;
+ char *pw;
+ handler_t rc = HANDLER_UNSET;
+
+ UNUSED(p_d);
+
+ if (NULL == backend) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not configured for", con->uri.path);
+ con->http_status = 500;
+ con->mode = DIRECT;
+ return HANDLER_FINISHED;
+ }
+
+ if (NULL == b) {
+ return mod_auth_send_401_unauthorized_basic(srv, con, require->realm);
+ }
+
+ if (0 != strncasecmp(b->ptr, "Basic ", sizeof("Basic ")-1)) {
+ return mod_auth_send_401_unauthorized_basic(srv, con, require->realm);
+ }
+ #ifdef __COVERITY__
+ if (buffer_string_length(b) < sizeof("Basic ")-1) {
+ return mod_auth_send_400_bad_request(srv, con);
+ }
+ #endif
+
+ username = buffer_init();
+
+ /* coverity[overflow_sink : FALSE] */
+ if (!buffer_append_base64_decode(username, b->ptr+sizeof("Basic ")-1, buffer_string_length(b)-(sizeof("Basic ")-1), BASE64_STANDARD)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "decoding base64-string failed", username);
+
+ buffer_free(username);
+ return mod_auth_send_400_bad_request(srv, con);
+ }
+
+ /* r2 == user:password */
+ if (NULL == (pw = strchr(username->ptr, ':'))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "missing ':' in", username);
+
+ buffer_free(username);
+ return mod_auth_send_400_bad_request(srv, con);
+ }
+
+ buffer_string_set_length(username, pw - username->ptr);
+ pw++;
+
+ rc = backend->basic(srv, con, backend->p_d, require, username, pw);
+ switch (rc) {
+ case HANDLER_GO_ON:
+ http_auth_setenv(con, CONST_BUF_LEN(username), CONST_STR_LEN("Basic"));
+ break;
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_FINISHED:
+ break;
+ case HANDLER_ERROR:
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sbsBsB", "password doesn't match for", con->uri.path, "username:", username, ", IP:", con->dst_addr_buf);
+ rc = HANDLER_UNSET;
+ break;
+ }
+
+ buffer_free(username);
+ return (HANDLER_UNSET != rc) ? rc : mod_auth_send_401_unauthorized_basic(srv, con, require->realm);
+}
+
+#define HASHLEN 16
+#define HASHHEXLEN 32
+typedef unsigned char HASH[HASHLEN];
+typedef char HASHHEX[HASHHEXLEN+1];
+
+static void CvtHex(const HASH Bin, char (*Hex)[33]) {
+ li_tohex(*Hex, sizeof(*Hex), (const char*) Bin, 16);
+}
+
+typedef struct {
+ const char *key;
+ int key_len;
+ char **ptr;
+} digest_kv;
+
+static handler_t mod_auth_send_401_unauthorized_digest(server *srv, connection *con, buffer *realm, int nonce_stale);
+
+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) {
+ buffer *vb = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
+
+ char a1[33];
+ char a2[33];
+
+ char *username = NULL;
+ char *realm = NULL;
+ char *nonce = NULL;
+ char *uri = NULL;
+ char *algorithm = NULL;
+ char *qop = NULL;
+ char *cnonce = NULL;
+ char *nc = NULL;
+ char *respons = NULL;
+
+ char *e, *c;
+ const char *m = NULL;
+ int i;
+ buffer *b;
+
+ li_MD5_CTX Md5Ctx;
+ HASH HA1;
+ HASH HA2;
+ HASH RespHash;
+ HASHHEX HA2Hex;
+
+
+ /* init pointers */
+#define S(x) \
+ x, sizeof(x)-1, NULL
+ digest_kv dkv[10] = {
+ { S("username=") },
+ { S("realm=") },
+ { S("nonce=") },
+ { S("uri=") },
+ { S("algorithm=") },
+ { S("qop=") },
+ { S("cnonce=") },
+ { S("nc=") },
+ { S("response=") },
+
+ { NULL, 0, NULL }
+ };
+#undef S
+
+ dkv[0].ptr = &username;
+ dkv[1].ptr = &realm;
+ dkv[2].ptr = &nonce;
+ dkv[3].ptr = &uri;
+ dkv[4].ptr = &algorithm;
+ dkv[5].ptr = &qop;
+ dkv[6].ptr = &cnonce;
+ dkv[7].ptr = &nc;
+ dkv[8].ptr = &respons;
+
+ UNUSED(p_d);
+
+ if (NULL == backend) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not configured for", con->uri.path);
+ con->http_status = 500;
+ con->mode = DIRECT;
+ return HANDLER_FINISHED;
+ }
+
+ if (NULL == vb) {
+ return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
+ }
+
+ if (0 != strncasecmp(vb->ptr, "Digest ", sizeof("Digest ")-1)) {
+ return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
+ } else {
+ size_t n = buffer_string_length(vb);
+ #ifdef __COVERITY__
+ if (n < sizeof("Digest ")-1) {
+ return mod_auth_send_400_bad_request(srv, con);
+ }
+ #endif
+ n -= (sizeof("Digest ")-1);
+ b = buffer_init();
+ buffer_copy_string_len(b,vb->ptr+sizeof("Digest ")-1,n);
+ }
+
+ /* parse credentials from client */
+ for (c = b->ptr; *c; c++) {
+ /* skip whitespaces */
+ while (*c == ' ' || *c == '\t') c++;
+ if (!*c) break;
+
+ for (i = 0; dkv[i].key; i++) {
+ if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) {
+ if ((c[dkv[i].key_len] == '"') &&
+ (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) {
+ /* value with "..." */
+ *(dkv[i].ptr) = c + dkv[i].key_len + 1;
+ c = e;
+
+ *e = '\0';
+ } else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) {
+ /* value without "...", terminated by ',' */
+ *(dkv[i].ptr) = c + dkv[i].key_len;
+ c = e;
+
+ *e = '\0';
+ } else {
+ /* value without "...", terminated by EOL */
+ *(dkv[i].ptr) = c + dkv[i].key_len;
+ c += strlen(c) - 1;
+ }
+ break;
+ }
+ }
+ }
+
+ /* check if everything is transmitted */
+ if (!username ||
+ !realm ||
+ !nonce ||
+ !uri ||
+ (qop && (!nc || !cnonce)) ||
+ !respons ) {
+ /* missing field */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: missing field");
+
+ buffer_free(b);
+ return mod_auth_send_400_bad_request(srv, con);
+ }
+
+ if (!buffer_is_equal_string(require->realm, realm, strlen(realm))) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: realm mismatch");
+ buffer_free(b);
+ return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
+ }
+
+ /**
+ * protect the md5-sess against missing cnonce and nonce
+ */
+ if (algorithm &&
+ 0 == strcasecmp(algorithm, "md5-sess") &&
+ (!nonce || !cnonce)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: (md5-sess: missing field");
+
+ buffer_free(b);
+ return mod_auth_send_400_bad_request(srv, con);
+ }
+
+ if (qop && strcasecmp(qop, "auth-int") == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: qop=auth-int not supported");
+
+ buffer_free(b);
+ return mod_auth_send_400_bad_request(srv, con);
+ }
+
+ m = get_http_method_name(con->request.http_method);
+ force_assert(m);
+
+ /* detect if attacker is attempting to reuse valid digest for one uri
+ * on a different request uri. Might also happen if intermediate proxy
+ * altered client request line. (Altered request would not result in
+ * the same digest as that calculated by the client.)
+ * Internal redirects such as with mod_rewrite will modify request uri.
+ * Reauthentication is done to detect crossing auth realms, but this
+ * uri validation step is bypassed. con->request.orig_uri is original
+ * uri sent in client request. */
+ {
+ const size_t ulen = strlen(uri);
+ const size_t rlen = buffer_string_length(con->request.orig_uri);
+ if (!buffer_is_equal_string(con->request.orig_uri, uri, ulen)
+ && !(rlen < ulen && 0 == memcmp(con->request.orig_uri->ptr, uri, rlen) && uri[rlen] == '?')) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsssB",
+ "digest: auth failed: uri mismatch (", con->request.orig_uri, "!=", uri, "), IP:", con->dst_addr_buf);
+ buffer_free(b);
+ return mod_auth_send_400_bad_request(srv, con);
+ }
+ }
+
+ /* password-string == HA1 */
+ switch (backend->digest(srv, con, backend->p_d, username, realm, HA1)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_WAIT_FOR_EVENT:
+ buffer_free(b);
+ return HANDLER_WAIT_FOR_EVENT;
+ case HANDLER_FINISHED:
+ buffer_free(b);
+ return HANDLER_FINISHED;
+ case HANDLER_ERROR:
+ default:
+ buffer_free(b);
+ return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
+ }
+
+ if (algorithm &&
+ strcasecmp(algorithm, "md5-sess") == 0) {
+ li_MD5_Init(&Md5Ctx);
+ /* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
+ CvtHex(HA1, &a1);
+ li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
+ li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
+ li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
+ li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
+ li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
+ li_MD5_Final(HA1, &Md5Ctx);
+ }
+
+ CvtHex(HA1, &a1);
+
+ /* calculate H(A2) */
+ li_MD5_Init(&Md5Ctx);
+ li_MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m));
+ li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
+ li_MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri));
+ /* qop=auth-int not supported, already checked above */
+/*
+ if (qop && strcasecmp(qop, "auth-int") == 0) {
+ li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
+ li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
+ }
+*/
+ li_MD5_Final(HA2, &Md5Ctx);
+ CvtHex(HA2, &HA2Hex);
+
+ /* calculate response */
+ li_MD5_Init(&Md5Ctx);
+ li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
+ li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
+ li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
+ li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
+ if (qop && *qop) {
+ li_MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc));
+ li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
+ li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
+ li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
+ li_MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop));
+ li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
+ };
+ li_MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
+ li_MD5_Final(RespHash, &Md5Ctx);
+ CvtHex(RespHash, &a2);
+
+ if (0 != strcmp(a2, respons)) {
+ /* digest not ok */
+ log_error_write(srv, __FILE__, __LINE__, "sssB",
+ "digest: auth failed for ", username, ": wrong password, IP:", con->dst_addr_buf);
+
+ buffer_free(b);
+ return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
+ }
+
+ /* value is our allow-rules */
+ if (!http_auth_match_rules(require, username, NULL, NULL)) {
+ buffer_free(b);
+ return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0);
+ }
+
+ /* check age of nonce. Note, random data is used in nonce generation
+ * in mod_auth_send_401_unauthorized_digest(). If that were replaced
+ * with nanosecond time, then nonce secret would remain unique enough
+ * for the purposes of Digest auth, and would be reproducible (and
+ * verifiable) if nanoseconds were inclued with seconds as part of the
+ * nonce "timestamp:secret". Since that is not done, timestamp in
+ * nonce could theoretically be modified and still produce same md5sum,
+ * but that is highly unlikely within a 10 min (moving) window of valid
+ * time relative to current time (now) */
+ {
+ time_t ts = 0;
+ const unsigned char * const nonce_uns = (unsigned char *)nonce;
+ for (i = 0; i < 8 && light_isxdigit(nonce_uns[i]); ++i) {
+ ts = (ts << 4) + hex2int(nonce_uns[i]);
+ }
+ if (nonce[i] != ':'
+ || ts > srv->cur_ts || srv->cur_ts - ts > 600) { /*(10 mins)*/
+ /* nonce is stale; have client regenerate digest */
+ buffer_free(b);
+ return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 1);
+ } /*(future: might send nextnonce when expiration is imminent)*/
+ }
+
+ http_auth_setenv(con, username, strlen(username), CONST_STR_LEN("Digest"));
+
+ buffer_free(b);
+
+ return HANDLER_GO_ON;
+}
+
+static handler_t mod_auth_send_401_unauthorized_digest(server *srv, connection *con, buffer *realm, int nonce_stale) {
+ li_MD5_CTX Md5Ctx;
+ HASH h;
+ char hh[33];
+
+ force_assert(33 >= LI_ITOSTRING_LENGTH); /*(buffer used for both li_itostrn() and CvtHex())*/
+
+ /* generate nonce */
+
+ /* generate shared-secret */
+ li_MD5_Init(&Md5Ctx);
+
+ li_itostrn(hh, sizeof(hh), srv->cur_ts);
+ li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+ li_itostrn(hh, sizeof(hh), li_rand_pseudo());
+ li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+
+ li_MD5_Final(h, &Md5Ctx);
+
+ CvtHex(h, &hh);
+
+ /* generate WWW-Authenticate */
+
+ con->http_status = 401;
+ con->mode = DIRECT;
+
+ buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("Digest realm=\""));
+ buffer_append_string_buffer(srv->tmp_buf, realm);
+ buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("\", charset=\"UTF-8\", nonce=\""));
+ buffer_append_uint_hex(srv->tmp_buf, (uintmax_t)srv->cur_ts);
+ buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN(":"));
+ buffer_append_string_len(srv->tmp_buf, hh, HASHHEXLEN);
+ buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("\", qop=\"auth\""));
+ if (nonce_stale) {
+ buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN(", stale=true"));
+ }
+
+ http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv->tmp_buf));
+
+ return HANDLER_FINISHED;
+}
+
+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) {
+ /* require REMOTE_USER already set */
+ buffer *vb = http_header_env_get(con, CONST_STR_LEN("REMOTE_USER"));
+ UNUSED(srv);
+ UNUSED(p_d);
+ UNUSED(backend);
+ if (NULL != vb && http_auth_match_rules(require, vb->ptr, NULL, NULL)) {
+ return HANDLER_GO_ON;
+ } else {
+ con->http_status = 401;
+ con->mode = DIRECT;
+ return HANDLER_FINISHED;
+ }
+}