/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "apr_strings.h" #include "apr_lib.h" /* for apr_isspace */ #include "apr_base64.h" /* for apr_base64_decode et al */ #define APR_WANT_STRFUNC /* for strcasecmp */ #include "apr_want.h" #include "ap_config.h" #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" #include "util_md5.h" #include "ap_provider.h" #include "ap_expr.h" #include "mod_auth.h" typedef struct { authn_provider_list *providers; char *dir; /* unused variable */ int authoritative; ap_expr_info_t *fakeuser; ap_expr_info_t *fakepass; const char *use_digest_algorithm; int fake_set:1; int use_digest_algorithm_set:1; int authoritative_set:1; } auth_basic_config_rec; static void *create_auth_basic_dir_config(apr_pool_t *p, char *d) { auth_basic_config_rec *conf = apr_pcalloc(p, sizeof(*conf)); /* Any failures are fatal. */ conf->authoritative = 1; return conf; } static void *merge_auth_basic_dir_config(apr_pool_t *p, void *basev, void *overridesv) { auth_basic_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf)); auth_basic_config_rec *base = basev; auth_basic_config_rec *overrides = overridesv; newconf->authoritative = overrides->authoritative_set ? overrides->authoritative : base->authoritative; newconf->authoritative_set = overrides->authoritative_set || base->authoritative_set; newconf->fakeuser = overrides->fake_set ? overrides->fakeuser : base->fakeuser; newconf->fakepass = overrides->fake_set ? overrides->fakepass : base->fakepass; newconf->fake_set = overrides->fake_set || base->fake_set; newconf->use_digest_algorithm = overrides->use_digest_algorithm_set ? overrides->use_digest_algorithm : base->use_digest_algorithm; newconf->use_digest_algorithm_set = overrides->use_digest_algorithm_set || base->use_digest_algorithm_set; newconf->providers = overrides->providers ? overrides->providers : base->providers; return newconf; } static const char *add_authn_provider(cmd_parms *cmd, void *config, const char *arg) { auth_basic_config_rec *conf = (auth_basic_config_rec*)config; authn_provider_list *newp; newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list)); newp->provider_name = arg; /* lookup and cache the actual provider now */ newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, newp->provider_name, AUTHN_PROVIDER_VERSION); if (newp->provider == NULL) { /* by the time they use it, the provider should be loaded and registered with us. */ return apr_psprintf(cmd->pool, "Unknown Authn provider: %s", newp->provider_name); } if (!newp->provider->check_password) { /* if it doesn't provide the appropriate function, reject it */ return apr_psprintf(cmd->pool, "The '%s' Authn provider doesn't support " "Basic Authentication", newp->provider_name); } /* Add it to the list now. */ if (!conf->providers) { conf->providers = newp; } else { authn_provider_list *last = conf->providers; while (last->next) { last = last->next; } last->next = newp; } return NULL; } static const char *set_authoritative(cmd_parms * cmd, void *config, int flag) { auth_basic_config_rec *conf = (auth_basic_config_rec *) config; conf->authoritative = flag; conf->authoritative_set = 1; return NULL; } static const char *add_basic_fake(cmd_parms * cmd, void *config, const char *user, const char *pass) { auth_basic_config_rec *conf = (auth_basic_config_rec *) config; const char *err; if (!strcasecmp(user, "off")) { conf->fakeuser = NULL; conf->fakepass = NULL; conf->fake_set = 1; } else { /* if password is unspecified, set it to the fixed string "password" to * be compatible with the behaviour of mod_ssl. */ if (!pass) { pass = "password"; } conf->fakeuser = ap_expr_parse_cmd(cmd, user, AP_EXPR_FLAG_STRING_RESULT, &err, NULL); if (err) { return apr_psprintf(cmd->pool, "Could not parse fake username expression '%s': %s", user, err); } conf->fakepass = ap_expr_parse_cmd(cmd, pass, AP_EXPR_FLAG_STRING_RESULT, &err, NULL); if (err) { return apr_psprintf(cmd->pool, "Could not parse fake password expression '%s': %s", user, err); } conf->fake_set = 1; } return NULL; } static const char *set_use_digest_algorithm(cmd_parms *cmd, void *config, const char *alg) { auth_basic_config_rec *conf = (auth_basic_config_rec *)config; if (strcasecmp(alg, "Off") && strcasecmp(alg, "MD5")) { return apr_pstrcat(cmd->pool, "Invalid algorithm in " "AuthBasicUseDigestAlgorithm: ", alg, NULL); } conf->use_digest_algorithm = alg; conf->use_digest_algorithm_set = 1; return NULL; } static const command_rec auth_basic_cmds[] = { AP_INIT_ITERATE("AuthBasicProvider", add_authn_provider, NULL, OR_AUTHCFG, "specify the auth providers for a directory or location"), AP_INIT_FLAG("AuthBasicAuthoritative", set_authoritative, NULL, OR_AUTHCFG, "Set to 'Off' to allow access control to be passed along to " "lower modules if the UserID is not known to this module"), AP_INIT_TAKE12("AuthBasicFake", add_basic_fake, NULL, OR_AUTHCFG, "Fake basic authentication using the given expressions for " "username and password, 'off' to disable. Password defaults " "to 'password' if missing."), AP_INIT_TAKE1("AuthBasicUseDigestAlgorithm", set_use_digest_algorithm, NULL, OR_AUTHCFG, "Set to 'MD5' to use the auth provider's authentication " "check for digest auth, using a hash of 'user:realm:pass'"), {NULL} }; module AP_MODULE_DECLARE_DATA auth_basic_module; /* These functions return 0 if client is OK, and proper error status * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we * couldn't figure out how to tell if the client is authorized or not. * * If they return DECLINED, and all other modules also decline, that's * treated by the server core as a configuration error, logged and * reported as such. */ static void note_basic_auth_failure(request_rec *r) { apr_table_setn(r->err_headers_out, (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate" : "WWW-Authenticate", apr_pstrcat(r->pool, "Basic realm=\"", ap_auth_name(r), "\"", NULL)); } static int hook_note_basic_auth_failure(request_rec *r, const char *auth_type) { if (strcasecmp(auth_type, "Basic")) return DECLINED; note_basic_auth_failure(r); return OK; } static int get_basic_auth(request_rec *r, const char **user, const char **pw) { const char *auth_line; char *decoded_line; /* Get the appropriate header */ auth_line = apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authorization" : "Authorization"); if (!auth_line) { note_basic_auth_failure(r); return HTTP_UNAUTHORIZED; } if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) { /* Client tried to authenticate using wrong auth scheme */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01614) "client used wrong authentication scheme: %s", r->uri); note_basic_auth_failure(r); return HTTP_UNAUTHORIZED; } /* Skip leading spaces. */ while (apr_isspace(*auth_line)) { auth_line++; } decoded_line = ap_pbase64decode(r->pool, auth_line); *user = ap_getword_nulls(r->pool, (const char**)&decoded_line, ':'); *pw = decoded_line; /* set the user, even though the user is unauthenticated at this point */ r->user = (char *) *user; return OK; } /* Determine user ID, and check if it really is that user, for HTTP * basic authentication... */ static int authenticate_basic_user(request_rec *r) { auth_basic_config_rec *conf = ap_get_module_config(r->per_dir_config, &auth_basic_module); const char *sent_user, *sent_pw, *current_auth; const char *realm = NULL; const char *digest = NULL; int res; authn_status auth_result; authn_provider_list *current_provider; /* Are we configured to be Basic auth? */ current_auth = ap_auth_type(r); if (!current_auth || strcasecmp(current_auth, "Basic")) { return DECLINED; } /* We need an authentication realm. */ if (!ap_auth_name(r)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01615) "need AuthName: %s", r->uri); return HTTP_INTERNAL_SERVER_ERROR; } r->ap_auth_type = (char*)current_auth; res = get_basic_auth(r, &sent_user, &sent_pw); if (res) { return res; } if (conf->use_digest_algorithm && !strcasecmp(conf->use_digest_algorithm, "MD5")) { realm = ap_auth_name(r); digest = ap_md5(r->pool, (unsigned char *)apr_pstrcat(r->pool, sent_user, ":", realm, ":", sent_pw, NULL)); } current_provider = conf->providers; do { const authn_provider *provider; /* For now, if a provider isn't set, we'll be nice and use the file * provider. */ if (!current_provider) { provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, AUTHN_DEFAULT_PROVIDER, AUTHN_PROVIDER_VERSION); if (!provider || !provider->check_password) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01616) "No Authn provider configured"); auth_result = AUTH_GENERAL_ERROR; break; } apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER); } else { provider = current_provider->provider; apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name); } if (digest) { char *password; if (!provider->get_realm_hash) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02493) "Authn provider does not support " "AuthBasicUseDigestAlgorithm"); auth_result = AUTH_GENERAL_ERROR; break; } /* We expect the password to be hash of user:realm:password */ auth_result = provider->get_realm_hash(r, sent_user, realm, &password); if (auth_result == AUTH_USER_FOUND) { auth_result = strcmp(digest, password) ? AUTH_DENIED : AUTH_GRANTED; } } else { auth_result = provider->check_password(r, sent_user, sent_pw); } apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE); /* Something occurred. Stop checking. */ if (auth_result != AUTH_USER_NOT_FOUND) { break; } /* If we're not really configured for providers, stop now. */ if (!conf->providers) { break; } current_provider = current_provider->next; } while (current_provider); if (auth_result != AUTH_GRANTED) { int return_code; /* If we're not authoritative, then any error is ignored. */ if (!(conf->authoritative) && auth_result != AUTH_DENIED) { return DECLINED; } switch (auth_result) { case AUTH_DENIED: ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01617) "user %s: authentication failure for \"%s\": " "Password Mismatch", sent_user, r->uri); return_code = HTTP_UNAUTHORIZED; break; case AUTH_USER_NOT_FOUND: ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01618) "user %s not found: %s", sent_user, r->uri); return_code = HTTP_UNAUTHORIZED; break; case AUTH_GENERAL_ERROR: default: /* We'll assume that the module has already said what its error * was in the logs. */ return_code = HTTP_INTERNAL_SERVER_ERROR; break; } /* If we're returning 401, tell them to try again. */ if (return_code == HTTP_UNAUTHORIZED) { note_basic_auth_failure(r); } return return_code; } return OK; } /* If requested, create a fake basic authentication header for the benefit * of a proxy or application running behind this server. */ static int authenticate_basic_fake(request_rec *r) { const char *auth_line, *user, *pass, *err; auth_basic_config_rec *conf = ap_get_module_config(r->per_dir_config, &auth_basic_module); if (!conf->fakeuser) { return DECLINED; } user = ap_expr_str_exec(r, conf->fakeuser, &err); if (err) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02455) "AuthBasicFake: could not evaluate user expression for URI '%s': %s", r->uri, err); return HTTP_INTERNAL_SERVER_ERROR; } if (!user || !*user) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02458) "AuthBasicFake: empty username expression for URI '%s', ignoring", r->uri); apr_table_unset(r->headers_in, "Authorization"); return DECLINED; } pass = ap_expr_str_exec(r, conf->fakepass, &err); if (err) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02456) "AuthBasicFake: could not evaluate password expression for URI '%s': %s", r->uri, err); return HTTP_INTERNAL_SERVER_ERROR; } if (!pass || !*pass) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02459) "AuthBasicFake: empty password expression for URI '%s', ignoring", r->uri); apr_table_unset(r->headers_in, "Authorization"); return DECLINED; } auth_line = apr_pstrcat(r->pool, "Basic ", ap_pbase64encode(r->pool, apr_pstrcat(r->pool, user, ":", pass, NULL)), NULL); apr_table_setn(r->headers_in, "Authorization", auth_line); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02457) "AuthBasicFake: \"Authorization: %s\"", auth_line); return OK; } static void register_hooks(apr_pool_t *p) { ap_hook_check_authn(authenticate_basic_user, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF); ap_hook_fixups(authenticate_basic_fake, NULL, NULL, APR_HOOK_LAST); ap_hook_note_auth_failure(hook_note_basic_auth_failure, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(auth_basic) = { STANDARD20_MODULE_STUFF, create_auth_basic_dir_config, /* dir config creater */ merge_auth_basic_dir_config, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ auth_basic_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };