1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * Security options etc.
19 *
20 * Module derived from code originally written by Rob McCool
21 *
22 */
23
24#include "apr_strings.h"
25#include "apr_network_io.h"
26#include "apr_md5.h"
27#include "apr_hash.h"
28
29#define APR_WANT_STRFUNC
30#define APR_WANT_BYTEFUNC
31#include "apr_want.h"
32
33#include "ap_config.h"
34#include "ap_provider.h"
35#include "httpd.h"
36#include "http_core.h"
37#include "http_config.h"
38#include "http_log.h"
39#include "http_protocol.h"
40#include "http_request.h"
41
42#include "mod_auth.h"
43
44#if APR_HAVE_NETINET_IN_H
45#include <netinet/in.h>
46#endif
47
48/*
49 * To save memory if the same subnets are used in hundres of vhosts, we store
50 * each subnet only once and use this temporary hash to find it again.
51 */
52static apr_hash_t *parsed_subnets;
53
54static apr_ipsubnet_t *localhost_v4;
55#if APR_HAVE_IPV6
56static apr_ipsubnet_t *localhost_v6;
57#endif
58
59static int in_domain(const char *domain, const char *what)
60{
61    int dl = strlen(domain);
62    int wl = strlen(what);
63
64    if ((wl - dl) >= 0) {
65        if (strcasecmp(domain, &what[wl - dl]) != 0) {
66            return 0;
67        }
68
69        /* Make sure we matched an *entire* subdomain --- if the user
70         * said 'allow from good.com', we don't want people from nogood.com
71         * to be able to get in.
72         */
73
74        if (wl == dl) {
75            return 1;                /* matched whole thing */
76        }
77        else {
78            return (domain[0] == '.' || what[wl - dl - 1] == '.');
79        }
80    }
81    else {
82        return 0;
83    }
84}
85
86static const char *ip_parse_config(cmd_parms *cmd,
87                                   const char *require_line,
88                                   const void **parsed_require_line)
89{
90    const char *t, *w;
91    int count = 0;
92    apr_ipsubnet_t **ip;
93    apr_pool_t *ptemp = cmd->temp_pool;
94    apr_pool_t *p = cmd->pool;
95
96    /* The 'ip' provider will allow the configuration to specify a list of
97        ip addresses to check rather than a single address.  This is different
98        from the previous host based syntax. */
99
100    t = require_line;
101    while ((w = ap_getword_conf(ptemp, &t)) && w[0])
102        count++;
103
104    if (count == 0)
105        return "'require ip' requires an argument";
106
107    ip = apr_pcalloc(p, sizeof(apr_ipsubnet_t *) * (count + 1));
108    *parsed_require_line = ip;
109
110    t = require_line;
111    while ((w = ap_getword_conf(ptemp, &t)) && w[0]) {
112        char *addr = apr_pstrdup(ptemp, w);
113        char *mask;
114        apr_status_t rv;
115
116        if (parsed_subnets &&
117            (*ip = apr_hash_get(parsed_subnets, w, APR_HASH_KEY_STRING)) != NULL)
118        {
119            /* we already have parsed this subnet */
120            ip++;
121            continue;
122        }
123
124        if ((mask = ap_strchr(addr, '/')))
125            *mask++ = '\0';
126
127        rv = apr_ipsubnet_create(ip, addr, mask, p);
128
129        if(APR_STATUS_IS_EINVAL(rv)) {
130            /* looked nothing like an IP address */
131            return apr_psprintf(p, "ip address '%s' appears to be invalid", w);
132        }
133        else if (rv != APR_SUCCESS) {
134            return apr_psprintf(p, "ip address '%s' appears to be invalid: %pm",
135                                w, &rv);
136        }
137
138        if (parsed_subnets)
139            apr_hash_set(parsed_subnets, w, APR_HASH_KEY_STRING, *ip);
140        ip++;
141    }
142
143    return NULL;
144}
145
146static authz_status ip_check_authorization(request_rec *r,
147                                           const char *require_line,
148                                           const void *parsed_require_line)
149{
150    /* apr_ipsubnet_test should accept const but doesn't */
151    apr_ipsubnet_t **ip = (apr_ipsubnet_t **)parsed_require_line;
152
153    while (*ip) {
154        if (apr_ipsubnet_test(*ip, r->useragent_addr))
155            return AUTHZ_GRANTED;
156        ip++;
157    }
158
159    /* authz_core will log the require line and the result at DEBUG */
160    return AUTHZ_DENIED;
161}
162
163static authz_status host_check_authorization(request_rec *r,
164                                             const char *require_line,
165                                             const void *parsed_require_line)
166{
167    const char *t, *w;
168    const char *remotehost = NULL;
169    int remotehost_is_ip;
170
171    remotehost = ap_get_remote_host(r->connection,
172                                    r->per_dir_config,
173                                    REMOTE_DOUBLE_REV,
174                                    &remotehost_is_ip);
175
176    if ((remotehost == NULL) || remotehost_is_ip) {
177        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01753)
178                      "access check of '%s' to %s failed, reason: unable to get the "
179                      "remote host name", require_line, r->uri);
180    }
181    else {
182        const char *err = NULL;
183        const ap_expr_info_t *expr = parsed_require_line;
184        const char *require;
185
186        require = ap_expr_str_exec(r, expr, &err);
187        if (err) {
188            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02593)
189                          "authz_host authorize: require host: Can't "
190                          "evaluate require expression: %s", err);
191            return AUTHZ_DENIED;
192        }
193
194        /* The 'host' provider will allow the configuration to specify a list of
195            host names to check rather than a single name.  This is different
196            from the previous host based syntax. */
197        t = require;
198        while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
199            if (in_domain(w, remotehost)) {
200                return AUTHZ_GRANTED;
201            }
202        }
203    }
204
205    /* authz_core will log the require line and the result at DEBUG */
206    return AUTHZ_DENIED;
207}
208
209static authz_status local_check_authorization(request_rec *r,
210                                              const char *require_line,
211                                              const void *parsed_require_line)
212{
213     if (   apr_sockaddr_equal(r->connection->local_addr,
214                               r->useragent_addr)
215         || apr_ipsubnet_test(localhost_v4, r->useragent_addr)
216#if APR_HAVE_IPV6
217         || apr_ipsubnet_test(localhost_v6, r->useragent_addr)
218#endif
219        )
220     {
221        return AUTHZ_GRANTED;
222     }
223
224     return AUTHZ_DENIED;
225}
226
227static const char *host_parse_config(cmd_parms *cmd, const char *require_line,
228                                     const void **parsed_require_line)
229{
230    const char *expr_err = NULL;
231    ap_expr_info_t *expr;
232
233    expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT,
234            &expr_err, NULL);
235
236    if (expr_err)
237        return apr_pstrcat(cmd->temp_pool,
238                           "Cannot parse expression in require line: ",
239                           expr_err, NULL);
240
241    *parsed_require_line = expr;
242
243    return NULL;
244}
245
246static const authz_provider authz_ip_provider =
247{
248    &ip_check_authorization,
249    &ip_parse_config,
250};
251
252static const authz_provider authz_host_provider =
253{
254    &host_check_authorization,
255    &host_parse_config,
256};
257
258static const authz_provider authz_local_provider =
259{
260    &local_check_authorization,
261    NULL,
262};
263
264
265static int authz_host_pre_config(apr_pool_t *p, apr_pool_t *plog,
266                                 apr_pool_t *ptemp)
267{
268    /* we only use this hash in the parse config phase, ptemp is enough */
269    parsed_subnets = apr_hash_make(ptemp);
270
271    apr_ipsubnet_create(&localhost_v4, "127.0.0.0", "8", p);
272    apr_hash_set(parsed_subnets, "127.0.0.0/8", APR_HASH_KEY_STRING, localhost_v4);
273
274#if APR_HAVE_IPV6
275    apr_ipsubnet_create(&localhost_v6, "::1", NULL, p);
276    apr_hash_set(parsed_subnets, "::1", APR_HASH_KEY_STRING, localhost_v6);
277#endif
278
279    return OK;
280}
281
282static int authz_host_post_config(apr_pool_t *p, apr_pool_t *plog,
283                                  apr_pool_t *ptemp, server_rec *s)
284{
285    /* make sure we don't use this during .htaccess parsing */
286    parsed_subnets = NULL;
287
288    return OK;
289}
290
291static void register_hooks(apr_pool_t *p)
292{
293    ap_hook_pre_config(authz_host_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
294    ap_hook_post_config(authz_host_post_config, NULL, NULL, APR_HOOK_MIDDLE);
295
296    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ip",
297                              AUTHZ_PROVIDER_VERSION,
298                              &authz_ip_provider, AP_AUTH_INTERNAL_PER_CONF);
299    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "host",
300                              AUTHZ_PROVIDER_VERSION,
301                              &authz_host_provider, AP_AUTH_INTERNAL_PER_CONF);
302    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "local",
303                              AUTHZ_PROVIDER_VERSION,
304                              &authz_local_provider, AP_AUTH_INTERNAL_PER_CONF);
305}
306
307AP_DECLARE_MODULE(authz_host) =
308{
309    STANDARD20_MODULE_STUFF,
310    NULL,                           /* dir config creater */
311    NULL,                           /* dir merger --- default is to override */
312    NULL,                           /* server config */
313    NULL,                           /* merge server config */
314    NULL,
315    register_hooks                  /* register hooks */
316};
317