]> granicus.if.org Git - apache/blobdiff - modules/aaa/mod_authz_host.c
hostname: Test and log useragent_host per-request across various modules,
[apache] / modules / aaa / mod_authz_host.c
index 5e15d9cc1f2bc6f400e382d1807f704ab9997d75..15f453996e02c24af53920ff4706ce573b91e106 100644 (file)
-/* ====================================================================
- * The Apache Software License, Version 1.1
+/* 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
  *
- * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
- * reserved.
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * 3. The end-user documentation included with the redistribution,
- *    if any, must include the following acknowledgment:
- *       "This product includes software developed by the
- *        Apache Software Foundation (http://www.apache.org/)."
- *    Alternately, this acknowledgment may appear in the software itself,
- *    if and wherever such third-party acknowledgments normally appear.
- *
- * 4. The names "Apache" and "Apache Software Foundation" must
- *    not be used to endorse or promote products derived from this
- *    software without prior written permission. For written
- *    permission, please contact apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache",
- *    nor may "Apache" appear in their name, without prior written
- *    permission of the Apache Software Foundation.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- * Portions of this software are based upon public domain software
- * originally written at the National Center for Supercomputing Applications,
- * University of Illinois, Urbana-Champaign.
+ * 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"
+#include "apr_hash.h"
 
 #define APR_WANT_STRFUNC
 #define APR_WANT_BYTEFUNC
 #include "apr_want.h"
 
 #include "ap_config.h"
+#include "ap_provider.h"
 #include "httpd.h"
 #include "http_core.h"
 #include "http_config.h"
 #include "http_log.h"
+#include "http_protocol.h"
 #include "http_request.h"
 
+#include "mod_auth.h"
+
 #if APR_HAVE_NETINET_IN_H
 #include <netinet/in.h>
 #endif
 
-enum allowdeny_type {
-    T_ENV,
-    T_ALL,
-    T_IP,
-    T_HOST,
-    T_FAIL
-};
-
-typedef struct {
-    apr_int64_t limited;
-    union {
-        char *from;
-        apr_ipsubnet_t *ip;
-    } x;
-    enum allowdeny_type type;
-} allowdeny;
-
-/* things in the 'order' array */
-#define DENY_THEN_ALLOW 0
-#define ALLOW_THEN_DENY 1
-#define MUTUAL_FAILURE 2
-
-typedef struct {
-    int order[METHODS];
-    apr_array_header_t *allows;
-    apr_array_header_t *denys;
-} authz_host_dir_conf;
-
-module AP_MODULE_DECLARE_DATA authz_host_module;
-
-static void *create_authz_host_dir_config(apr_pool_t *p, char *dummy)
-{
-    int i;
-    authz_host_dir_conf *conf =
-        (authz_host_dir_conf *)apr_pcalloc(p, sizeof(authz_host_dir_conf));
-
-    for (i = 0; i < METHODS; ++i) {
-        conf->order[i] = DENY_THEN_ALLOW;
-    }
-    conf->allows = apr_array_make(p, 1, sizeof(allowdeny));
-    conf->denys = apr_array_make(p, 1, sizeof(allowdeny));
-
-    return (void *)conf;
-}
-
-static const char *order(cmd_parms *cmd, void *dv, const char *arg)
-{
-    authz_host_dir_conf *d = (authz_host_dir_conf *) dv;
-    int i, o;
-
-    if (!strcasecmp(arg, "allow,deny"))
-        o = ALLOW_THEN_DENY;
-    else if (!strcasecmp(arg, "deny,allow"))
-        o = DENY_THEN_ALLOW;
-    else if (!strcasecmp(arg, "mutual-failure"))
-        o = MUTUAL_FAILURE;
-    else
-        return "unknown order";
-
-    for (i = 0; i < METHODS; ++i)
-        if (cmd->limited & (AP_METHOD_BIT << i))
-            d->order[i] = o;
-
-    return NULL;
-}
-
-static const char *allow_cmd(cmd_parms *cmd, void *dv, const char *from, 
-                             const char *where_c)
-{
-    authz_host_dir_conf *d = (authz_host_dir_conf *) dv;
-    allowdeny *a;
-    char *where = apr_pstrdup(cmd->pool, where_c);
-    char *s;
-    char msgbuf[120];
-    apr_status_t rv;
-
-    if (strcasecmp(from, "from"))
-        return "allow and deny must be followed by 'from'";
-
-    a = (allowdeny *) apr_array_push(cmd->info ? d->allows : d->denys);
-    a->x.from = where;
-    a->limited = cmd->limited;
-
-    if (!strncasecmp(where, "env=", 4)) {
-        a->type = T_ENV;
-        a->x.from += 4;
-
-    }
-    else if (!strcasecmp(where, "all")) {
-        a->type = T_ALL;
-    }
-    else if ((s = strchr(where, '/'))) {
-        *s++ = '\0';
-        rv = apr_ipsubnet_create(&a->x.ip, where, s, cmd->pool);
-        if(APR_STATUS_IS_EINVAL(rv)) {
-            /* looked nothing like an IP address */
-            return "An IP address was expected";
-        }
-        else if (rv != APR_SUCCESS) {
-            apr_strerror(rv, msgbuf, sizeof msgbuf);
-            return apr_pstrdup(cmd->pool, msgbuf);
-        }
-        a->type = T_IP;
-    }
-    else if (!APR_STATUS_IS_EINVAL(rv = apr_ipsubnet_create(&a->x.ip, where, NULL, cmd->pool))) {
-        if (rv != APR_SUCCESS) {
-            apr_strerror(rv, msgbuf, sizeof msgbuf);
-            return apr_pstrdup(cmd->pool, msgbuf);
-        }
-        a->type = T_IP;
-    }
-    else { /* no slash, didn't look like an IP address => must be a host */
-        a->type = T_HOST;
-    }
-
-    return NULL;
-}
-
-static char its_an_allow;
+/*
+ * To save memory if the same subnets are used in hundres of vhosts, we store
+ * each subnet only once and use this temporary hash to find it again.
+ */
+static apr_hash_t *parsed_subnets;
 
-static const command_rec authz_host_cmds[] =
-{
-    AP_INIT_TAKE1("order", order, NULL, OR_LIMIT,
-                  "'allow,deny', 'deny,allow', or 'mutual-failure'"),
-    AP_INIT_ITERATE2("allow", allow_cmd, &its_an_allow, OR_LIMIT,
-                     "'from' followed by hostnames or IP-address wildcards"),
-    AP_INIT_ITERATE2("deny", allow_cmd, NULL, OR_LIMIT,
-                     "'from' followed by hostnames or IP-address wildcards"),
-    {NULL}
-};
+static apr_ipsubnet_t *localhost_v4;
+#if APR_HAVE_IPV6
+static apr_ipsubnet_t *localhost_v6;
+#endif
 
 static int in_domain(const char *domain, const char *what)
 {
@@ -240,124 +83,244 @@ static int in_domain(const char *domain, const char *what)
     }
 }
 
-static int find_allowdeny(request_rec *r, apr_array_header_t *a, int method)
+static const char *ip_parse_config(cmd_parms *cmd,
+                                   const char *require_line,
+                                   const void **parsed_require_line)
 {
-
-    allowdeny *ap = (allowdeny *) a->elts;
-    apr_int64_t mmask = (AP_METHOD_BIT << method);
-    int i;
-    int gothost = 0;
-    const char *remotehost = NULL;
-
-    for (i = 0; i < a->nelts; ++i) {
-        if (!(mmask & ap[i].limited)) {
+    const char *t, *w;
+    int count = 0;
+    apr_ipsubnet_t **ip;
+    apr_pool_t *ptemp = cmd->temp_pool;
+    apr_pool_t *p = cmd->pool;
+
+    /* The 'ip' provider will allow the configuration to specify a list of
+        ip addresses to check rather than a single address.  This is different
+        from the previous host based syntax. */
+
+    t = require_line;
+    while ((w = ap_getword_conf(ptemp, &t)) && w[0])
+        count++;
+
+    if (count == 0)
+        return "'require ip' requires an argument";
+
+    ip = apr_pcalloc(p, sizeof(apr_ipsubnet_t *) * (count + 1));
+    *parsed_require_line = ip;
+
+    t = require_line;
+    while ((w = ap_getword_conf(ptemp, &t)) && w[0]) {
+        char *addr = apr_pstrdup(ptemp, w);
+        char *mask;
+        apr_status_t rv;
+
+        if (parsed_subnets &&
+            (*ip = apr_hash_get(parsed_subnets, w, APR_HASH_KEY_STRING)) != NULL)
+        {
+            /* we already have parsed this subnet */
+            ip++;
             continue;
         }
 
-        switch (ap[i].type) {
-        case T_ENV:
-            if (apr_table_get(r->subprocess_env, ap[i].x.from)) {
-                return 1;
-            }
-            break;
-
-        case T_ALL:
-            return 1;
+        if ((mask = ap_strchr(addr, '/')))
+            *mask++ = '\0';
 
-        case T_IP:
-            if (apr_ipsubnet_test(ap[i].x.ip, r->connection->remote_addr)) {
-                return 1;
-            }
-            break;
+        rv = apr_ipsubnet_create(ip, addr, mask, p);
 
-        case T_HOST:
-            if (!gothost) {
-                int remotehost_is_ip;
+        if(APR_STATUS_IS_EINVAL(rv)) {
+            /* looked nothing like an IP address */
+            return apr_psprintf(p, "ip address '%s' appears to be invalid", w);
+        }
+        else if (rv != APR_SUCCESS) {
+            return apr_psprintf(p, "ip address '%s' appears to be invalid: %pm",
+                                w, &rv);
+        }
 
-                remotehost = ap_get_remote_host(r->connection,
-                                                r->per_dir_config,
-                                                REMOTE_DOUBLE_REV,
-                                                &remotehost_is_ip);
+        if (parsed_subnets)
+            apr_hash_set(parsed_subnets, w, APR_HASH_KEY_STRING, *ip);
+        ip++;
+    }
 
-                if ((remotehost == NULL) || remotehost_is_ip) {
-                    gothost = 1;
-                }
-                else {
-                    gothost = 2;
-                }
-            }
+    return NULL;
+}
 
-            if ((gothost == 2) && in_domain(ap[i].x.from, remotehost)) {
-                return 1;
-            }
-            break;
+static authz_status ip_check_authorization(request_rec *r,
+                                           const char *require_line,
+                                           const void *parsed_require_line)
+{
+    /* apr_ipsubnet_test should accept const but doesn't */
+    apr_ipsubnet_t **ip = (apr_ipsubnet_t **)parsed_require_line;
 
-        case T_FAIL:
-            /* do nothing? */
-            break;
-        }
+    while (*ip) {
+        if (apr_ipsubnet_test(*ip, r->useragent_addr))
+            return AUTHZ_GRANTED;
+        ip++;
     }
 
-    return 0;
+    /* authz_core will log the require line and the result at DEBUG */
+    return AUTHZ_DENIED;
 }
 
-static int check_dir_access(request_rec *r)
+static authz_status host_check_authorization(request_rec *r,
+                                             const char *require_line,
+                                             const void *parsed_require_line)
 {
-    int method = r->method_number;
-    int ret = OK;
-    authz_host_dir_conf *a = (authz_host_dir_conf *)
-        ap_get_module_config(r->per_dir_config, &authz_host_module);
-
-    if (a->order[method] == ALLOW_THEN_DENY) {
-        ret = HTTP_FORBIDDEN;
-        if (find_allowdeny(r, a->allows, method)) {
-            ret = OK;
-        }
-        if (find_allowdeny(r, a->denys, method)) {
-            ret = HTTP_FORBIDDEN;
-        }
-    }
-    else if (a->order[method] == DENY_THEN_ALLOW) {
-        if (find_allowdeny(r, a->denys, method)) {
-            ret = HTTP_FORBIDDEN;
-        }
-        if (find_allowdeny(r, a->allows, method)) {
-            ret = OK;
-        }
+    const char *t;
+    char *w, *hash_ptr;
+    const char *remotehost = NULL;
+    int remotehost_is_ip;
+
+    remotehost = ap_get_useragent_host(r, REMOTE_DOUBLE_REV, &remotehost_is_ip);
+
+    if ((remotehost == NULL) || remotehost_is_ip) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01753)
+                      "access check of '%s' to %s failed, reason: unable to get the "
+                      "remote host name", require_line, r->uri);
     }
     else {
-        if (find_allowdeny(r, a->allows, method)
-            && !find_allowdeny(r, a->denys, method)) {
-            ret = OK;
+        const char *err = NULL;
+        const ap_expr_info_t *expr = parsed_require_line;
+        const char *require;
+
+        require = ap_expr_str_exec(r, expr, &err);
+        if (err) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02593)
+                          "authz_host authorize: require host: Can't "
+                          "evaluate require expression: %s", err);
+            return AUTHZ_DENIED;
         }
-        else {
-            ret = HTTP_FORBIDDEN;
+
+        /* The 'host' provider will allow the configuration to specify a list of
+            host names to check rather than a single name.  This is different
+            from the previous host based syntax. */
+        t = require;
+        while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
+            /* '#' is not valid hostname character and admin could specify
+             * 'Require host localhost# Add example.com later'. We should not
+             * grant access to 'example.com' in that case. */
+            if ((hash_ptr = ap_strchr(w, '#'))) {
+                if (hash_ptr == w) {
+                    break;
+                }
+                *hash_ptr = '\0';
+            }
+            if (in_domain(w, remotehost)) {
+                return AUTHZ_GRANTED;
+            }
+            if (hash_ptr) {
+                break;
+            }
         }
     }
 
-    if (ret == HTTP_FORBIDDEN
-        && (ap_satisfies(r) != SATISFY_ANY || !ap_some_auth_required(r))) {
-        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-            "client denied by server configuration: %s",
-            r->filename);
-    }
+    /* authz_core will log the require line and the result at DEBUG */
+    return AUTHZ_DENIED;
+}
+
+static authz_status local_check_authorization(request_rec *r,
+                                              const char *require_line,
+                                              const void *parsed_require_line)
+{
+     if (   apr_sockaddr_equal(r->connection->local_addr,
+                               r->useragent_addr)
+         || apr_ipsubnet_test(localhost_v4, r->useragent_addr)
+#if APR_HAVE_IPV6
+         || apr_ipsubnet_test(localhost_v6, r->useragent_addr)
+#endif
+        )
+     {
+        return AUTHZ_GRANTED;
+     }
+
+     return AUTHZ_DENIED;
+}
+
+static const char *host_parse_config(cmd_parms *cmd, const char *require_line,
+                                     const void **parsed_require_line)
+{
+    const char *expr_err = NULL;
+    ap_expr_info_t *expr;
+
+    expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT,
+            &expr_err, NULL);
+
+    if (expr_err)
+        return apr_pstrcat(cmd->temp_pool,
+                           "Cannot parse expression in require line: ",
+                           expr_err, NULL);
+
+    *parsed_require_line = expr;
+
+    return NULL;
+}
+
+static const authz_provider authz_ip_provider =
+{
+    &ip_check_authorization,
+    &ip_parse_config,
+};
+
+static const authz_provider authz_host_provider =
+{
+    &host_check_authorization,
+    &host_parse_config,
+};
+
+static const authz_provider authz_local_provider =
+{
+    &local_check_authorization,
+    NULL,
+};
+
+
+static int authz_host_pre_config(apr_pool_t *p, apr_pool_t *plog,
+                                 apr_pool_t *ptemp)
+{
+    /* we only use this hash in the parse config phase, ptemp is enough */
+    parsed_subnets = apr_hash_make(ptemp);
+
+    apr_ipsubnet_create(&localhost_v4, "127.0.0.0", "8", p);
+    apr_hash_set(parsed_subnets, "127.0.0.0/8", APR_HASH_KEY_STRING, localhost_v4);
+
+#if APR_HAVE_IPV6
+    apr_ipsubnet_create(&localhost_v6, "::1", NULL, p);
+    apr_hash_set(parsed_subnets, "::1", APR_HASH_KEY_STRING, localhost_v6);
+#endif
+
+    return OK;
+}
+
+static int authz_host_post_config(apr_pool_t *p, apr_pool_t *plog,
+                                  apr_pool_t *ptemp, server_rec *s)
+{
+    /* make sure we don't use this during .htaccess parsing */
+    parsed_subnets = NULL;
 
-    return ret;
+    return OK;
 }
 
 static void register_hooks(apr_pool_t *p)
 {
-    /* This can be access checker since we don't require r->user to be set. */
-    ap_hook_access_checker(check_dir_access,NULL,NULL,APR_HOOK_MIDDLE);
+    ap_hook_pre_config(authz_host_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_post_config(authz_host_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+
+    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ip",
+                              AUTHZ_PROVIDER_VERSION,
+                              &authz_ip_provider, AP_AUTH_INTERNAL_PER_CONF);
+    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "host",
+                              AUTHZ_PROVIDER_VERSION,
+                              &authz_host_provider, AP_AUTH_INTERNAL_PER_CONF);
+    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "local",
+                              AUTHZ_PROVIDER_VERSION,
+                              &authz_local_provider, AP_AUTH_INTERNAL_PER_CONF);
 }
 
-module AP_MODULE_DECLARE_DATA authz_host_module =
+AP_DECLARE_MODULE(authz_host) =
 {
     STANDARD20_MODULE_STUFF,
-    create_authz_host_dir_config,   /* dir config creater */
+    NULL,                           /* dir config creater */
     NULL,                           /* dir merger --- default is to override */
     NULL,                           /* server config */
     NULL,                           /* merge server config */
-    authz_host_cmds,
+    NULL,
     register_hooks                  /* register hooks */
 };