| /* 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. |
| */ |
| |
| /* |
| * Security options etc. |
| * |
| * Module derived from code originally written by Rob McCool |
| * |
| */ |
| |
| #include "apr_strings.h" |
| #include "apr_network_io.h" |
| #include "apr_md5.h" |
| |
| #define APR_WANT_STRFUNC |
| #define APR_WANT_BYTEFUNC |
| #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_request.h" |
| #include "http_protocol.h" |
| #include "ap_provider.h" |
| #include "ap_expr.h" |
| |
| #include "mod_auth.h" |
| |
| #if APR_HAVE_NETINET_IN_H |
| #include <netinet/in.h> |
| #endif |
| |
| #undef AUTHZ_EXTRA_CONFIGS |
| |
| typedef struct provider_alias_rec { |
| char *provider_name; |
| char *provider_alias; |
| char *provider_args; |
| const void *provider_parsed_args; |
| ap_conf_vector_t *sec_auth; |
| const authz_provider *provider; |
| } provider_alias_rec; |
| |
| typedef enum { |
| AUTHZ_LOGIC_AND, |
| AUTHZ_LOGIC_OR, |
| AUTHZ_LOGIC_OFF, |
| AUTHZ_LOGIC_UNSET |
| } authz_logic_op; |
| |
| typedef struct authz_section_conf authz_section_conf; |
| |
| struct authz_section_conf { |
| const char *provider_name; |
| const char *provider_args; |
| const void *provider_parsed_args; |
| const authz_provider *provider; |
| ap_method_mask_t limited; |
| authz_logic_op op; |
| int negate; |
| /** true if this is not a real container but produced by AuthMerging; |
| * only used for logging */ |
| int is_merged; |
| authz_section_conf *first; |
| authz_section_conf *next; |
| }; |
| |
| typedef struct authz_core_dir_conf authz_core_dir_conf; |
| |
| struct authz_core_dir_conf { |
| authz_section_conf *section; |
| authz_core_dir_conf *next; |
| authz_logic_op op; |
| signed char authz_forbidden_on_fail; |
| }; |
| |
| #define UNSET -1 |
| |
| typedef struct authz_core_srv_conf { |
| apr_hash_t *alias_rec; |
| } authz_core_srv_conf; |
| |
| module AP_MODULE_DECLARE_DATA authz_core_module; |
| |
| static authz_core_dir_conf *authz_core_first_dir_conf; |
| |
| static void *create_authz_core_dir_config(apr_pool_t *p, char *dummy) |
| { |
| authz_core_dir_conf *conf = apr_pcalloc(p, sizeof(*conf)); |
| |
| conf->op = AUTHZ_LOGIC_UNSET; |
| conf->authz_forbidden_on_fail = UNSET; |
| |
| conf->next = authz_core_first_dir_conf; |
| authz_core_first_dir_conf = conf; |
| |
| return (void *)conf; |
| } |
| |
| static void *merge_authz_core_dir_config(apr_pool_t *p, |
| void *basev, void *newv) |
| { |
| authz_core_dir_conf *base = (authz_core_dir_conf *)basev; |
| authz_core_dir_conf *new = (authz_core_dir_conf *)newv; |
| authz_core_dir_conf *conf; |
| |
| if (new->op == AUTHZ_LOGIC_UNSET && !new->section && base->section ) { |
| /* Only authz_forbidden_on_fail has been set in new. Don't treat |
| * it as a new auth config w.r.t. AuthMerging */ |
| conf = apr_pmemdup(p, base, sizeof(*base)); |
| } |
| else if (new->op == AUTHZ_LOGIC_OFF || new->op == AUTHZ_LOGIC_UNSET || |
| !(base->section || new->section)) { |
| conf = apr_pmemdup(p, new, sizeof(*new)); |
| } |
| else { |
| authz_section_conf *section; |
| |
| if (base->section) { |
| if (new->section) { |
| section = apr_pcalloc(p, sizeof(*section)); |
| |
| section->limited = |
| base->section->limited | new->section->limited; |
| |
| section->op = new->op; |
| section->is_merged = 1; |
| |
| section->first = apr_pmemdup(p, base->section, |
| sizeof(*base->section)); |
| section->first->next = apr_pmemdup(p, new->section, |
| sizeof(*new->section)); |
| } else { |
| section = apr_pmemdup(p, base->section, |
| sizeof(*base->section)); |
| } |
| } |
| else { |
| section = apr_pmemdup(p, new->section, sizeof(*new->section)); |
| } |
| |
| conf = apr_pcalloc(p, sizeof(*conf)); |
| |
| conf->section = section; |
| conf->op = new->op; |
| } |
| |
| if (new->authz_forbidden_on_fail == UNSET) |
| conf->authz_forbidden_on_fail = base->authz_forbidden_on_fail; |
| else |
| conf->authz_forbidden_on_fail = new->authz_forbidden_on_fail; |
| |
| return (void*)conf; |
| } |
| |
| /* Only per-server directive we have is GLOBAL_ONLY */ |
| static void *merge_authz_core_svr_config(apr_pool_t *p, |
| void *basev, void *newv) |
| { |
| return basev; |
| } |
| |
| static void *create_authz_core_svr_config(apr_pool_t *p, server_rec *s) |
| { |
| authz_core_srv_conf *authcfg; |
| |
| authcfg = apr_pcalloc(p, sizeof(*authcfg)); |
| authcfg->alias_rec = apr_hash_make(p); |
| |
| return (void *)authcfg; |
| } |
| |
| /* This is a fake authz provider that really merges various authz alias |
| * configurations and then invokes them. |
| */ |
| static authz_status authz_alias_check_authorization(request_rec *r, |
| const char *require_args, |
| const void *parsed_require_args) |
| { |
| const char *provider_name; |
| |
| /* Look up the provider alias in the alias list. |
| * Get the dir_config and call ap_merge_per_dir_configs() |
| * Call the real provider->check_authorization() function |
| * Return the result of the above function call |
| */ |
| |
| provider_name = apr_table_get(r->notes, AUTHZ_PROVIDER_NAME_NOTE); |
| |
| if (provider_name) { |
| authz_core_srv_conf *authcfg; |
| provider_alias_rec *prvdraliasrec; |
| |
| authcfg = ap_get_module_config(r->server->module_config, |
| &authz_core_module); |
| |
| prvdraliasrec = apr_hash_get(authcfg->alias_rec, provider_name, |
| APR_HASH_KEY_STRING); |
| |
| /* If we found the alias provider in the list, then merge the directory |
| configurations and call the real provider */ |
| if (prvdraliasrec) { |
| ap_conf_vector_t *orig_dir_config = r->per_dir_config; |
| authz_status ret; |
| |
| r->per_dir_config = |
| ap_merge_per_dir_configs(r->pool, orig_dir_config, |
| prvdraliasrec->sec_auth); |
| |
| ret = prvdraliasrec->provider-> |
| check_authorization(r, prvdraliasrec->provider_args, |
| prvdraliasrec->provider_parsed_args); |
| |
| r->per_dir_config = orig_dir_config; |
| |
| return ret; |
| } |
| } |
| |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02305) |
| "no alias provider found for '%s' (BUG?)", |
| provider_name ? provider_name : "n/a"); |
| |
| return AUTHZ_DENIED; |
| } |
| |
| static const authz_provider authz_alias_provider = |
| { |
| &authz_alias_check_authorization, |
| NULL, |
| }; |
| |
| static const char *authz_require_alias_section(cmd_parms *cmd, void *mconfig, |
| const char *args) |
| { |
| const char *endp = ap_strrchr_c(args, '>'); |
| char *provider_name; |
| char *provider_alias; |
| char *provider_args, *extra_args; |
| ap_conf_vector_t *new_authz_config; |
| int old_overrides = cmd->override; |
| const char *errmsg; |
| |
| const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); |
| if (err != NULL) { |
| return err; |
| } |
| |
| if (endp == NULL) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive missing closing '>'", NULL); |
| } |
| |
| args = apr_pstrndup(cmd->temp_pool, args, endp - args); |
| |
| if (!args[0]) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive requires additional arguments", NULL); |
| } |
| |
| /* Pull the real provider name and the alias name from the block header */ |
| provider_name = ap_getword_conf(cmd->pool, &args); |
| provider_alias = ap_getword_conf(cmd->pool, &args); |
| provider_args = ap_getword_conf(cmd->pool, &args); |
| extra_args = ap_getword_conf(cmd->pool, &args); |
| |
| if (!provider_name[0] || !provider_alias[0]) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive requires additional arguments", NULL); |
| } |
| |
| /* We only handle one "Require-Parameters" parameter. If several parameters |
| are needed, they must be enclosed between quotes */ |
| if (extra_args && *extra_args) { |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(10142) |
| "When several arguments (%s %s...) are passed to a %s directive, " |
| "they must be enclosed in quotation marks. Otherwise, only the " |
| "first one is taken into account", |
| provider_args, extra_args, cmd->cmd->name); |
| } |
| |
| new_authz_config = ap_create_per_dir_config(cmd->pool); |
| |
| /* Walk the subsection configuration to get the per_dir config that we will |
| * merge just before the real provider is called. |
| */ |
| cmd->override = OR_AUTHCFG | ACCESS_CONF; |
| errmsg = ap_walk_config(cmd->directive->first_child, cmd, |
| new_authz_config); |
| cmd->override = old_overrides; |
| |
| if (!errmsg) { |
| provider_alias_rec *prvdraliasrec; |
| authz_core_srv_conf *authcfg; |
| |
| prvdraliasrec = apr_pcalloc(cmd->pool, sizeof(*prvdraliasrec)); |
| |
| /* Save off the new directory config along with the original |
| * provider name and function pointer data |
| */ |
| prvdraliasrec->provider_name = provider_name; |
| prvdraliasrec->provider_alias = provider_alias; |
| prvdraliasrec->provider_args = provider_args; |
| prvdraliasrec->sec_auth = new_authz_config; |
| prvdraliasrec->provider = |
| ap_lookup_provider(AUTHZ_PROVIDER_GROUP, provider_name, |
| AUTHZ_PROVIDER_VERSION); |
| |
| /* by the time the config file is used, the provider should be loaded |
| * and registered with us. |
| */ |
| if (!prvdraliasrec->provider) { |
| return apr_psprintf(cmd->pool, |
| "Unknown Authz provider: %s", |
| provider_name); |
| } |
| if (prvdraliasrec->provider->parse_require_line) { |
| err = prvdraliasrec->provider->parse_require_line(cmd, |
| provider_args, &prvdraliasrec->provider_parsed_args); |
| if (err) |
| return apr_psprintf(cmd->pool, |
| "Can't parse 'Require %s %s': %s", |
| provider_name, provider_args, err); |
| } |
| |
| authcfg = ap_get_module_config(cmd->server->module_config, |
| &authz_core_module); |
| |
| apr_hash_set(authcfg->alias_rec, provider_alias, |
| APR_HASH_KEY_STRING, prvdraliasrec); |
| |
| /* Register the fake provider so that we get called first */ |
| ap_register_auth_provider(cmd->pool, AUTHZ_PROVIDER_GROUP, |
| provider_alias, AUTHZ_PROVIDER_VERSION, |
| &authz_alias_provider, |
| AP_AUTH_INTERNAL_PER_CONF); |
| } |
| |
| return errmsg; |
| } |
| |
| static const char* format_authz_result(authz_status result) |
| { |
| return ((result == AUTHZ_DENIED) |
| ? "denied" |
| : ((result == AUTHZ_GRANTED) |
| ? "granted" |
| : ((result == AUTHZ_DENIED_NO_USER) |
| ? "denied (no authenticated user yet)" |
| : "neutral"))); |
| } |
| |
| static const char* format_authz_command(apr_pool_t *p, |
| authz_section_conf *section) |
| { |
| return (section->provider |
| ? apr_pstrcat(p, "Require ", (section->negate ? "not " : ""), |
| section->provider_name, " ", |
| section->provider_args, NULL) |
| : apr_pstrcat(p, section->is_merged ? "AuthMerging " : "<Require", |
| ((section->op == AUTHZ_LOGIC_AND) |
| ? (section->negate ? "NotAll" : "All") |
| : (section->negate ? "None" : "Any")), |
| section->is_merged ? "" : ">", NULL)); |
| } |
| |
| static authz_section_conf* create_default_section(apr_pool_t *p) |
| { |
| authz_section_conf *section = apr_pcalloc(p, sizeof(*section)); |
| |
| section->op = AUTHZ_LOGIC_OR; |
| |
| return section; |
| } |
| |
| static const char *add_authz_provider(cmd_parms *cmd, void *config, |
| const char *args) |
| { |
| authz_core_dir_conf *conf = (authz_core_dir_conf*)config; |
| authz_section_conf *section = apr_pcalloc(cmd->pool, sizeof(*section)); |
| authz_section_conf *child; |
| |
| section->provider_name = ap_getword_conf(cmd->pool, &args); |
| |
| if (!strcasecmp(section->provider_name, "not")) { |
| section->provider_name = ap_getword_conf(cmd->pool, &args); |
| section->negate = 1; |
| } |
| |
| section->provider_args = args; |
| |
| /* lookup and cache the actual provider now */ |
| section->provider = ap_lookup_provider(AUTHZ_PROVIDER_GROUP, |
| section->provider_name, |
| AUTHZ_PROVIDER_VERSION); |
| |
| /* by the time the config file is used, the provider should be loaded |
| * and registered with us. |
| */ |
| if (!section->provider) { |
| return apr_psprintf(cmd->pool, |
| "Unknown Authz provider: %s", |
| section->provider_name); |
| } |
| |
| /* if the provider doesn't provide the appropriate function, reject it */ |
| if (!section->provider->check_authorization) { |
| return apr_psprintf(cmd->pool, |
| "The '%s' Authz provider is not supported by any " |
| "of the loaded authorization modules", |
| section->provider_name); |
| } |
| |
| section->limited = cmd->limited; |
| |
| if (section->provider->parse_require_line) { |
| const char *err; |
| apr_pool_userdata_setn(section->provider_name, |
| AUTHZ_PROVIDER_NAME_NOTE, |
| apr_pool_cleanup_null, |
| cmd->temp_pool); |
| err = section->provider->parse_require_line(cmd, args, |
| §ion->provider_parsed_args); |
| |
| if (err) |
| return err; |
| } |
| |
| if (!conf->section) { |
| conf->section = create_default_section(cmd->pool); |
| } |
| |
| if (section->negate && conf->section->op == AUTHZ_LOGIC_OR) { |
| return apr_psprintf(cmd->pool, "negative %s directive has no effect " |
| "in %s directive", |
| cmd->cmd->name, |
| format_authz_command(cmd->pool, conf->section)); |
| } |
| |
| conf->section->limited |= section->limited; |
| |
| child = conf->section->first; |
| |
| if (child) { |
| while (child->next) { |
| child = child->next; |
| } |
| |
| child->next = section; |
| } |
| else { |
| conf->section->first = section; |
| } |
| |
| return NULL; |
| } |
| |
| static const char *add_authz_section(cmd_parms *cmd, void *mconfig, |
| const char *args) |
| { |
| authz_core_dir_conf *conf = mconfig; |
| const char *endp = ap_strrchr_c(args, '>'); |
| authz_section_conf *old_section = conf->section; |
| authz_section_conf *section; |
| int old_overrides = cmd->override; |
| ap_method_mask_t old_limited = cmd->limited; |
| const char *errmsg; |
| |
| if (endp == NULL) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive missing closing '>'", NULL); |
| } |
| |
| args = apr_pstrndup(cmd->temp_pool, args, endp - args); |
| |
| if (args[0]) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive doesn't take additional arguments", |
| NULL); |
| } |
| |
| section = apr_pcalloc(cmd->pool, sizeof(*section)); |
| |
| if (!strcasecmp(cmd->cmd->name, "<RequireAll")) { |
| section->op = AUTHZ_LOGIC_AND; |
| } |
| else if (!strcasecmp(cmd->cmd->name, "<RequireAny")) { |
| section->op = AUTHZ_LOGIC_OR; |
| } |
| else if (!strcasecmp(cmd->cmd->name, "<RequireNotAll")) { |
| section->op = AUTHZ_LOGIC_AND; |
| section->negate = 1; |
| } |
| else { |
| section->op = AUTHZ_LOGIC_OR; |
| section->negate = 1; |
| } |
| |
| conf->section = section; |
| |
| /* trigger NOT_IN_LIMIT errors as if this were a <Limit> directive */ |
| cmd->limited &= ~(AP_METHOD_BIT << (METHODS - 1)); |
| |
| cmd->override = OR_AUTHCFG; |
| errmsg = ap_walk_config(cmd->directive->first_child, cmd, cmd->context); |
| cmd->override = old_overrides; |
| |
| cmd->limited = old_limited; |
| |
| conf->section = old_section; |
| |
| if (errmsg) { |
| return errmsg; |
| } |
| |
| if (section->first) { |
| authz_section_conf *child; |
| |
| if (!old_section) { |
| old_section = conf->section = create_default_section(cmd->pool); |
| } |
| |
| if (section->negate && old_section->op == AUTHZ_LOGIC_OR) { |
| return apr_psprintf(cmd->pool, "%s directive has " |
| "no effect in %s directive", |
| format_authz_command(cmd->pool, section), |
| format_authz_command(cmd->pool, old_section)); |
| } |
| |
| old_section->limited |= section->limited; |
| |
| if (!section->negate && section->op == old_section->op) { |
| /* be associative */ |
| section = section->first; |
| } |
| |
| child = old_section->first; |
| |
| if (child) { |
| while (child->next) { |
| child = child->next; |
| } |
| |
| child->next = section; |
| } |
| else { |
| old_section->first = section; |
| } |
| } |
| else { |
| return apr_pstrcat(cmd->pool, |
| format_authz_command(cmd->pool, section), |
| " directive contains no authorization directives", |
| NULL); |
| } |
| |
| return NULL; |
| } |
| |
| static const char *authz_merge_sections(cmd_parms *cmd, void *mconfig, |
| const char *arg) |
| { |
| authz_core_dir_conf *conf = mconfig; |
| |
| if (!strcasecmp(arg, "Off")) { |
| conf->op = AUTHZ_LOGIC_OFF; |
| } |
| else if (!strcasecmp(arg, "And")) { |
| conf->op = AUTHZ_LOGIC_AND; |
| } |
| else if (!strcasecmp(arg, "Or")) { |
| conf->op = AUTHZ_LOGIC_OR; |
| } |
| else { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, " must be one of: " |
| "Off | And | Or", NULL); |
| } |
| |
| return NULL; |
| } |
| |
| static int authz_core_check_section(apr_pool_t *p, server_rec *s, |
| authz_section_conf *section, int is_conf) |
| { |
| authz_section_conf *prev = NULL; |
| authz_section_conf *child = section->first; |
| int ret = !OK; |
| |
| while (child) { |
| if (child->first) { |
| if (authz_core_check_section(p, s, child, 0) != OK) { |
| return !OK; |
| } |
| |
| if (child->negate && child->op != section->op) { |
| authz_section_conf *next = child->next; |
| |
| /* avoid one level of recursion when De Morgan permits */ |
| child = child->first; |
| |
| if (prev) { |
| prev->next = child; |
| } |
| else { |
| section->first = child; |
| } |
| |
| do { |
| child->negate = !child->negate; |
| } while (child->next && (child = child->next)); |
| |
| child->next = next; |
| } |
| } |
| |
| prev = child; |
| child = child->next; |
| } |
| |
| child = section->first; |
| |
| while (child) { |
| if (!child->negate) { |
| ret = OK; |
| break; |
| } |
| |
| child = child->next; |
| } |
| |
| if (ret != OK) { |
| ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, APR_SUCCESS, s, APLOGNO(01624) |
| "%s directive contains only negative authorization directives", |
| is_conf ? "<Directory>, <Location>, or similar" |
| : format_authz_command(p, section)); |
| } |
| |
| return ret; |
| } |
| |
| static int authz_core_pre_config(apr_pool_t *p, apr_pool_t *plog, |
| apr_pool_t *ptemp) |
| { |
| authz_core_first_dir_conf = NULL; |
| |
| return OK; |
| } |
| |
| static int authz_core_check_config(apr_pool_t *p, apr_pool_t *plog, |
| apr_pool_t *ptemp, server_rec *s) |
| { |
| authz_core_dir_conf *conf = authz_core_first_dir_conf; |
| |
| while (conf) { |
| if (conf->section) { |
| if (authz_core_check_section(p, s, conf->section, 1) != OK) { |
| return !OK; |
| } |
| } |
| |
| conf = conf->next; |
| } |
| |
| return OK; |
| } |
| |
| static const command_rec authz_cmds[] = |
| { |
| AP_INIT_RAW_ARGS("<AuthzProviderAlias", authz_require_alias_section, |
| NULL, RSRC_CONF, |
| "container for grouping an authorization provider's " |
| "directives under a provider alias"), |
| AP_INIT_RAW_ARGS("Require", add_authz_provider, NULL, OR_AUTHCFG, |
| "specifies authorization directives " |
| "which one must pass (or not) for a request to suceeed"), |
| AP_INIT_RAW_ARGS("<RequireAll", add_authz_section, NULL, OR_AUTHCFG, |
| "container for grouping authorization directives " |
| "of which none must fail and at least one must pass " |
| "for a request to succeed"), |
| AP_INIT_RAW_ARGS("<RequireAny", add_authz_section, NULL, OR_AUTHCFG, |
| "container for grouping authorization directives " |
| "of which one must pass " |
| "for a request to succeed"), |
| #ifdef AUTHZ_EXTRA_CONFIGS |
| AP_INIT_RAW_ARGS("<RequireNotAll", add_authz_section, NULL, OR_AUTHCFG, |
| "container for grouping authorization directives " |
| "of which some must fail or none must pass " |
| "for a request to succeed"), |
| #endif |
| AP_INIT_RAW_ARGS("<RequireNone", add_authz_section, NULL, OR_AUTHCFG, |
| "container for grouping authorization directives " |
| "of which none must pass " |
| "for a request to succeed"), |
| AP_INIT_TAKE1("AuthMerging", authz_merge_sections, NULL, OR_AUTHCFG, |
| "controls how a <Directory>, <Location>, or similar " |
| "directive's authorization directives are combined with " |
| "those of its predecessor"), |
| AP_INIT_FLAG("AuthzSendForbiddenOnFailure", ap_set_flag_slot_char, |
| (void *)APR_OFFSETOF(authz_core_dir_conf, authz_forbidden_on_fail), |
| OR_AUTHCFG, |
| "Controls if an authorization failure should result in a " |
| "'403 FORBIDDEN' response instead of the HTTP-conforming " |
| "'401 UNAUTHORIZED'"), |
| {NULL} |
| }; |
| |
| static authz_status apply_authz_sections(request_rec *r, |
| authz_section_conf *section, |
| authz_logic_op parent_op) |
| { |
| authz_status auth_result; |
| |
| /* check to make sure that the request method requires authorization */ |
| if (!(section->limited & (AP_METHOD_BIT << r->method_number))) { |
| auth_result = |
| (parent_op == AUTHZ_LOGIC_AND) ? AUTHZ_GRANTED : AUTHZ_NEUTRAL; |
| |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01625) |
| "authorization result of %s: %s " |
| "(directive limited to other methods)", |
| format_authz_command(r->pool, section), |
| format_authz_result(auth_result)); |
| |
| return auth_result; |
| } |
| |
| if (section->provider) { |
| apr_table_setn(r->notes, AUTHZ_PROVIDER_NAME_NOTE, |
| section->provider_name); |
| |
| auth_result = |
| section->provider->check_authorization(r, section->provider_args, |
| section->provider_parsed_args); |
| |
| apr_table_unset(r->notes, AUTHZ_PROVIDER_NAME_NOTE); |
| } |
| else { |
| authz_section_conf *child = section->first; |
| |
| auth_result = AUTHZ_NEUTRAL; |
| |
| while (child) { |
| authz_status child_result; |
| |
| child_result = apply_authz_sections(r, child, section->op); |
| |
| if (child_result == AUTHZ_GENERAL_ERROR) { |
| return AUTHZ_GENERAL_ERROR; |
| } |
| |
| if (child_result != AUTHZ_NEUTRAL) { |
| /* |
| * Handling of AUTHZ_DENIED/AUTHZ_DENIED_NO_USER: Return |
| * AUTHZ_DENIED_NO_USER if providing a user may change the |
| * result, AUTHZ_DENIED otherwise. |
| */ |
| if (section->op == AUTHZ_LOGIC_AND) { |
| if (child_result == AUTHZ_DENIED) { |
| auth_result = child_result; |
| break; |
| } |
| if ((child_result == AUTHZ_DENIED_NO_USER |
| && auth_result != AUTHZ_DENIED) |
| || (auth_result == AUTHZ_NEUTRAL)) { |
| auth_result = child_result; |
| } |
| } |
| else { |
| /* AUTHZ_LOGIC_OR */ |
| if (child_result == AUTHZ_GRANTED) { |
| auth_result = child_result; |
| break; |
| } |
| if ((child_result == AUTHZ_DENIED_NO_USER |
| && auth_result == AUTHZ_DENIED) |
| || (auth_result == AUTHZ_NEUTRAL)) { |
| auth_result = child_result; |
| } |
| } |
| } |
| |
| child = child->next; |
| } |
| } |
| |
| if (section->negate) { |
| if (auth_result == AUTHZ_GRANTED) { |
| auth_result = AUTHZ_DENIED; |
| } |
| else if (auth_result == AUTHZ_DENIED || |
| auth_result == AUTHZ_DENIED_NO_USER) { |
| /* For negated directives, if the original result was denied |
| * then the new result is neutral since we can not grant |
| * access simply because authorization was not rejected. |
| */ |
| auth_result = AUTHZ_NEUTRAL; |
| } |
| } |
| |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01626) |
| "authorization result of %s: %s", |
| format_authz_command(r->pool, section), |
| format_authz_result(auth_result)); |
| |
| return auth_result; |
| } |
| |
| static int authorize_user_core(request_rec *r, int after_authn) |
| { |
| authz_core_dir_conf *conf; |
| authz_status auth_result; |
| |
| conf = ap_get_module_config(r->per_dir_config, &authz_core_module); |
| |
| if (!conf->section) { |
| if (ap_auth_type(r)) { |
| /* there's an AuthType configured, but no authorization |
| * directives applied to support it |
| */ |
| |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(01627) |
| "AuthType configured with no corresponding " |
| "authorization directives"); |
| |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01628) |
| "authorization result: granted (no directives)"); |
| |
| return OK; |
| } |
| |
| auth_result = apply_authz_sections(r, conf->section, AUTHZ_LOGIC_AND); |
| |
| if (auth_result == AUTHZ_GRANTED) { |
| return OK; |
| } |
| else if (auth_result == AUTHZ_DENIED_NO_USER) { |
| if (after_authn) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(01629) |
| "authorization failure (no authenticated user): %s", |
| r->uri); |
| /* |
| * If we're returning 401 to an authenticated user, tell them to |
| * try again. If unauthenticated, note_auth_failure has already |
| * been called during auth. |
| */ |
| if (r->user) |
| ap_note_auth_failure(r); |
| |
| return HTTP_UNAUTHORIZED; |
| } |
| else { |
| /* |
| * We need a user before we can decide what to do. |
| * Get out of the way and proceed with authentication. |
| */ |
| return DECLINED; |
| } |
| } |
| else if (auth_result == AUTHZ_DENIED || auth_result == AUTHZ_NEUTRAL) { |
| if (!after_authn || ap_auth_type(r) == NULL) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(01630) |
| "client denied by server configuration: %s%s", |
| r->filename ? "" : "uri ", |
| r->filename ? r->filename : r->uri); |
| |
| return HTTP_FORBIDDEN; |
| } |
| else { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(01631) |
| "user %s: authorization failure for \"%s\": ", |
| r->user, r->uri); |
| |
| if (conf->authz_forbidden_on_fail > 0) { |
| return HTTP_FORBIDDEN; |
| } |
| else { |
| /* |
| * If we're returning 401 to an authenticated user, tell them to |
| * try again. If unauthenticated, note_auth_failure has already |
| * been called during auth. |
| */ |
| if (r->user) |
| ap_note_auth_failure(r); |
| return HTTP_UNAUTHORIZED; |
| } |
| } |
| } |
| else { |
| /* We'll assume that the module has already said what its |
| * error was in the logs. |
| */ |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| } |
| |
| static int authorize_userless(request_rec *r) |
| { |
| return authorize_user_core(r, 0); |
| } |
| |
| static int authorize_user(request_rec *r) |
| { |
| return authorize_user_core(r, 1); |
| } |
| |
| static int authz_some_auth_required(request_rec *r) |
| { |
| authz_core_dir_conf *conf; |
| |
| conf = ap_get_module_config(r->per_dir_config, &authz_core_module); |
| |
| if (conf->section |
| && (conf->section->limited & (AP_METHOD_BIT << r->method_number))) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * env authz provider |
| */ |
| |
| static authz_status env_check_authorization(request_rec *r, |
| const char *require_line, |
| const void *parsed_require_line) |
| { |
| const char *t, *w; |
| |
| /* The 'env' provider will allow the configuration to specify a list of |
| env variables to check rather than a single variable. This is different |
| from the previous host based syntax. */ |
| t = require_line; |
| while ((w = ap_getword_conf(r->pool, &t)) && w[0]) { |
| if (apr_table_get(r->subprocess_env, w)) { |
| return AUTHZ_GRANTED; |
| } |
| } |
| |
| return AUTHZ_DENIED; |
| } |
| |
| static const authz_provider authz_env_provider = |
| { |
| &env_check_authorization, |
| NULL, |
| }; |
| |
| |
| /* |
| * all authz provider |
| */ |
| |
| static authz_status all_check_authorization(request_rec *r, |
| const char *require_line, |
| const void *parsed_require_line) |
| { |
| if (parsed_require_line) { |
| return AUTHZ_GRANTED; |
| } |
| return AUTHZ_DENIED; |
| } |
| |
| static const char *all_parse_config(cmd_parms *cmd, const char *require_line, |
| const void **parsed_require_line) |
| { |
| /* |
| * If the argument to the 'all' provider is 'granted' then just let |
| * everybody in. This would be equivalent to the previous syntax of |
| * 'allow from all'. If the argument is 'denied' we reject everybody, |
| * which is equivalent to 'deny from all'. |
| */ |
| if (strcasecmp(require_line, "granted") == 0) { |
| *parsed_require_line = (void *)1; |
| return NULL; |
| } |
| else if (strcasecmp(require_line, "denied") == 0) { |
| /* *parsed_require_line is already NULL */ |
| return NULL; |
| } |
| else { |
| return "Argument for 'Require all' must be 'granted' or 'denied'"; |
| } |
| } |
| |
| static const authz_provider authz_all_provider = |
| { |
| &all_check_authorization, |
| &all_parse_config, |
| }; |
| |
| |
| /* |
| * method authz provider |
| */ |
| |
| static authz_status method_check_authorization(request_rec *r, |
| const char *require_line, |
| const void *parsed_require_line) |
| { |
| const ap_method_mask_t *allowed = parsed_require_line; |
| if (*allowed & (AP_METHOD_BIT << r->method_number)) |
| return AUTHZ_GRANTED; |
| else |
| return AUTHZ_DENIED; |
| } |
| |
| static const char *method_parse_config(cmd_parms *cmd, const char *require_line, |
| const void **parsed_require_line) |
| { |
| const char *w, *t; |
| ap_method_mask_t *allowed = apr_pcalloc(cmd->pool, sizeof *allowed); |
| |
| t = require_line; |
| |
| while ((w = ap_getword_conf(cmd->temp_pool, &t)) && w[0]) { |
| int m = ap_method_number_of(w); |
| if (m == M_INVALID) { |
| return apr_pstrcat(cmd->pool, "Invalid Method '", w, "'", NULL); |
| } |
| |
| *allowed |= (AP_METHOD_BIT << m); |
| } |
| |
| *parsed_require_line = allowed; |
| return NULL; |
| } |
| |
| static const authz_provider authz_method_provider = |
| { |
| &method_check_authorization, |
| &method_parse_config, |
| }; |
| |
| /* |
| * expr authz provider |
| */ |
| |
| #define REQUIRE_EXPR_NOTE "Require_expr_info" |
| struct require_expr_info { |
| ap_expr_info_t *expr; |
| int want_user; |
| }; |
| |
| static int expr_lookup_fn(ap_expr_lookup_parms *parms) |
| { |
| if (parms->type == AP_EXPR_FUNC_VAR |
| && strcasecmp(parms->name, "REMOTE_USER") == 0) { |
| struct require_expr_info *info; |
| apr_pool_userdata_get((void**)&info, REQUIRE_EXPR_NOTE, parms->ptemp); |
| AP_DEBUG_ASSERT(info != NULL); |
| info->want_user = 1; |
| } |
| return ap_expr_lookup_default(parms); |
| } |
| |
| static const char *expr_parse_config(cmd_parms *cmd, const char *require_line, |
| const void **parsed_require_line) |
| { |
| const char *expr_err = NULL; |
| struct require_expr_info *info = apr_pcalloc(cmd->pool, sizeof(*info)); |
| |
| /* if the expression happens to be surrounded by quotes, skip them */ |
| if (require_line[0] == '"') { |
| apr_size_t len = strlen(require_line); |
| |
| if (require_line[len-1] == '"') |
| require_line = apr_pstrndup(cmd->temp_pool, |
| require_line + 1, |
| len - 2); |
| } |
| |
| apr_pool_userdata_setn(info, REQUIRE_EXPR_NOTE, apr_pool_cleanup_null, |
| cmd->temp_pool); |
| info->expr = ap_expr_parse_cmd(cmd, require_line, 0, &expr_err, |
| expr_lookup_fn); |
| |
| if (expr_err) |
| return apr_pstrcat(cmd->temp_pool, |
| "Cannot parse expression in require line: ", |
| expr_err, NULL); |
| |
| *parsed_require_line = info; |
| |
| return NULL; |
| } |
| |
| static authz_status expr_check_authorization(request_rec *r, |
| const char *require_line, |
| const void *parsed_require_line) |
| { |
| const char *err = NULL; |
| const struct require_expr_info *info = parsed_require_line; |
| int rc = ap_expr_exec(r, info->expr, &err); |
| |
| if (rc < 0) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02320) |
| "Error evaluating expression in 'Require expr': %s", |
| err); |
| return AUTHZ_GENERAL_ERROR; |
| } |
| else if (rc == 0) { |
| if (info->want_user) |
| return AUTHZ_DENIED_NO_USER; |
| else |
| return AUTHZ_DENIED; |
| } |
| else { |
| return AUTHZ_GRANTED; |
| } |
| } |
| |
| static const authz_provider authz_expr_provider = |
| { |
| &expr_check_authorization, |
| &expr_parse_config, |
| }; |
| |
| |
| static void register_hooks(apr_pool_t *p) |
| { |
| APR_REGISTER_OPTIONAL_FN(authz_some_auth_required); |
| |
| ap_hook_pre_config(authz_core_pre_config, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_check_config(authz_core_check_config, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_check_authz(authorize_user, NULL, NULL, APR_HOOK_LAST, |
| AP_AUTH_INTERNAL_PER_CONF); |
| ap_hook_check_access_ex(authorize_userless, NULL, NULL, APR_HOOK_LAST, |
| AP_AUTH_INTERNAL_PER_CONF); |
| |
| ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "env", |
| AUTHZ_PROVIDER_VERSION, |
| &authz_env_provider, AP_AUTH_INTERNAL_PER_CONF); |
| ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "all", |
| AUTHZ_PROVIDER_VERSION, |
| &authz_all_provider, AP_AUTH_INTERNAL_PER_CONF); |
| ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "method", |
| AUTHZ_PROVIDER_VERSION, |
| &authz_method_provider, AP_AUTH_INTERNAL_PER_CONF); |
| ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "expr", |
| AUTHZ_PROVIDER_VERSION, |
| &authz_expr_provider, AP_AUTH_INTERNAL_PER_CONF); |
| } |
| |
| AP_DECLARE_MODULE(authz_core) = |
| { |
| STANDARD20_MODULE_STUFF, |
| create_authz_core_dir_config, /* dir config creater */ |
| merge_authz_core_dir_config, /* dir merger */ |
| create_authz_core_svr_config, /* server config */ |
| merge_authz_core_svr_config , /* merge server config */ |
| authz_cmds, |
| register_hooks /* register hooks */ |
| }; |
| |