| /* 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; | |
| unsigned int fake_set:1, | |
| use_digest_algorithm_set:1, | |
| 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 associated to user '%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 (ap_cstr_casecmp(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 (ap_cstr_casecmp(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 (*auth_line == ' ' || *auth_line == '\t') { | |
| 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 || ap_cstr_casecmp(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 | |
| && !ap_cstr_casecmp(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_HANDLED: | |
| return_code = r->status; | |
| 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 */ | |
| }; |