/* Copyright 2002-2006 The Apache Software Foundation or its licensors, as * applicable. * * Licensed 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. */ /* This product includes software developed by * The Apache Software Foundation (http://www.apache.org/). * * Portions of this software were developed at the National Center * for Supercomputing Applications (NCSA) at the University of * Illinois at Urbana-Champaign. */ /* * Security options etc. * * Module derived from code originally written by Rob McCool * */ #include "apr_errno.h" #include "apr_file_io.h" #include "apr_general.h" #include "apr_mmap.h" #include "apr_network_io.h" #include "apr_strings.h" #include "apr_thread_mutex.h" #include "apr_thread_rwlock.h" #include "apr_time.h" #define APR_WANT_BYTEFUNC #define APR_WANT_STRFUNC #include "apr_want.h" #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" #if !AP_MODULE_MAGIC_AT_LEAST(20050127, 0) #define AP_REG_EXTENDED REG_EXTENDED #define AP_REG_NOSUB REG_NOSUB typedef regex_t ap_regex_t; #endif #if !APR_HAS_THREADS typedef void apr_thread_mutex_t; typedef void apr_thread_rwlock_t; #define apr_thread_mutex_create(mutex, flags, pool) \ (*mutex = (apr_thread_mutex_t *)1, APR_SUCCESS) #define apr_thread_mutex_lock(mutex) (void)APR_SUCCESS #define apr_thread_mutex_unlock(mutex) (void)APR_SUCCESS #define apr_thread_rwlock_create(rwlock, pool) APR_SUCCESS #define apr_thread_rwlock_rdlock(rwlock) (void)APR_SUCCESS #define apr_thread_rwlock_wrlock(rwlock) (void)APR_SUCCESS #define apr_thread_rwlock_unlock(rwlock) (void)APR_SUCCESS #endif /* !APR_HAS_THREADS */ #if !APR_HAVE_IPV6 #define IN6_IS_ADDR_V4MAPPED(addr) FALSE typedef struct in6_addr in6_addr_t; #elif !defined __sun || !defined __SVR4 #define in6_addr_t struct in6_addr #endif struct apr_ipsubnet_t { /* not defined in apr_network_io.h ... */ int family; #if APR_HAVE_IPV6 apr_uint32_t sub[4]; /* big enough for IPv4 and IPv6 addresses */ apr_uint32_t mask[4]; #else apr_uint32_t sub[1]; apr_uint32_t mask[1]; #endif }; typedef enum { LIST_TYPE_BLACK, LIST_TYPE_WHITE, LIST_TYPE_UNKNOWN } list_type_t; typedef struct { apr_int64_t limited; list_type_t type; char *file; } authz_iplist_dir_conf; typedef struct { int term; apr_array_header_t *list; apr_hash_t *subs; } iplist_impl_t; typedef struct { apr_thread_rwlock_t *rwlock; volatile apr_time_t mtime; apr_pool_t *pool; iplist_impl_t *ips, *hosts; } iplist_t; module AP_MODULE_DECLARE_DATA authz_iplist_module; #define HANDLER_NAME "authz-iplist" static const char REGEX_SPECIALS[] = ".\\[]()*+?{}^$|"; #define IS_REGEX_SPECIAL(c) (ap_strchr_c(REGEX_SPECIALS, c) != NULL) static apr_thread_mutex_t *iplists_mutex; static apr_hash_t *iplists; static void *create_authz_iplist_dir_config(apr_pool_t *p, char *dummy) { authz_iplist_dir_conf *conf = apr_pcalloc(p, sizeof *conf); return conf; } static const char *config_iplist(cmd_parms *cmd, void *dv, const char *type, const char *file) { authz_iplist_dir_conf *d = dv; d->limited = cmd->limited; d->type = !strcasecmp(type, "Black") ? LIST_TYPE_BLACK : !strcasecmp(type, "White") ? LIST_TYPE_WHITE : LIST_TYPE_UNKNOWN; if (d->type == LIST_TYPE_UNKNOWN) return "unknown list type"; if (!(d->file = ap_server_root_relative(cmd->pool, file))) return "bad file path"; return NULL; } static const command_rec authz_iplist_cmds[] = { AP_INIT_TAKE2("AuthzIPList", config_iplist, NULL, OR_LIMIT, "args: type file\n" "(type: \"Black\" or \"White\")\n" "(file: name of the file containing IP addrs / subnets / hosts)"), {NULL} }; static apr_status_t regist_ip(iplist_impl_t *list, const apr_ipsubnet_t *ip, apr_size_t i, apr_size_t n, apr_pool_t *p) { if (i + i >= n) list->term = ip->family; else if (((apr_uint16_t *)ip->mask)[i] == 0xFFFF) { iplist_impl_t *sub; if (!list->subs) list->subs = apr_hash_make(p); if (!(sub = apr_hash_get(list->subs, (apr_uint16_t *)ip->sub + i, 2))) { sub = apr_pcalloc(p, sizeof *sub); apr_hash_set(list->subs, (apr_uint16_t *)ip->sub + i, 2, sub); } return regist_ip(sub, ip, ++i, n, p); } else { if (!list->list) list->list = apr_array_make(p, 4, sizeof ip); *(const apr_ipsubnet_t **)apr_array_push(list->list) = ip; } return APR_SUCCESS; } static int is_hit_ip(const iplist_impl_t *list, apr_sockaddr_t *sa, apr_size_t i, apr_size_t n) { iplist_impl_t *sub; if (list->term == (sa->family != APR_INET && IN6_IS_ADDR_V4MAPPED((in6_addr_t *)sa->ipaddr_ptr) ? APR_INET : sa->family)) return TRUE; if (list->list) { int j; for (j = 0; j < list->list->nelts; j++) if (apr_ipsubnet_test(((apr_ipsubnet_t **)list->list->elts)[j], sa)) return TRUE; } return i + i < n && list->subs && (sub = apr_hash_get(list->subs, (apr_uint16_t *)sa->ipaddr_ptr + (sa->family != APR_INET && IN6_IS_ADDR_V4MAPPED((in6_addr_t *)sa->ipaddr_ptr) ? 3 * 2 : 0) + i, 2)) ? is_hit_ip(sub, sa, ++i, n) : FALSE; } static apr_status_t regist_host(iplist_impl_t *list, const apr_byte_t *host, const apr_byte_t *ptr, apr_pool_t *p) { const apr_byte_t *dom; for (dom = ptr; --dom > host && !IS_REGEX_SPECIAL(*dom); ) ; if (host == ptr) list->term = TRUE; else if (*dom++ == '.' || (--dom == host && !IS_REGEX_SPECIAL(*dom))) { iplist_impl_t *sub; if (!list->subs) list->subs = apr_hash_make(p); if (!(sub = apr_hash_get(list->subs, dom, ptr - dom))) { sub = apr_pcalloc(p, sizeof *sub); apr_hash_set(list->subs, apr_pstrmemdup(p, dom, ptr - dom), ptr - dom, sub); } return regist_host(sub, host, dom - (dom == host ? 0 : dom - 2 >= host && dom[-2] == '\\' ? 2 : 1), p); } else { ap_regex_t *regex; if (!(regex = ap_pregcomp(p, host, AP_REG_EXTENDED | AP_REG_NOSUB))) return APR_EINVAL; if (!list->list) list->list = apr_array_make(p, 4, sizeof regex); *(ap_regex_t **)apr_array_push(list->list) = regex; } return APR_SUCCESS; } static int is_hit_host(const iplist_impl_t *list, const apr_byte_t *host, const apr_byte_t *ptr) { const apr_byte_t *dom; iplist_impl_t *sub; if (list->term) return TRUE; if (list->list) { int i; for (i = 0; i < list->list->nelts; i++) if (!ap_regexec(((ap_regex_t **)list->list->elts)[i], host, 0, NULL, 0)) return TRUE; } for (dom = ptr; --dom > host && *dom != '.'; ) ; return host < ptr && (*dom++ == '.' || --dom) && list->subs && (sub = apr_hash_get(list->subs, dom, ptr - dom)) ? is_hit_host(sub, host, dom == host ? dom : --dom) : FALSE; } static apr_status_t create_list(iplist_t **list, const char *file, apr_pool_t *p) { apr_status_t rv; *list = apr_palloc(p, sizeof **list); (*list)->mtime = 0; if ((rv = apr_thread_rwlock_create(&(*list)->rwlock, p)) || (rv = apr_pool_create(&(*list)->pool, p))) return rv; apr_hash_set(iplists, apr_pstrdup(p, file), APR_HASH_KEY_STRING, *list); return APR_SUCCESS; } static apr_status_t update_list(iplist_t *list, const char *file, request_rec *r) { apr_status_t rv; apr_byte_t *s, *eol; apr_file_t *f; apr_finfo_t finfo; apr_mmap_t *m; apr_pool_clear(list->pool); /* these resources will be deallocated when r->pool is destroyed */ if ((rv = apr_file_open(&f, file, APR_READ, APR_OS_DEFAULT, r->pool)) || (rv = apr_file_lock(f, APR_FLOCK_SHARED)) || (rv = apr_file_info_get(&finfo, APR_FINFO_MIN, f)) || (finfo.size && (rv = apr_mmap_create(&m, f, 0, finfo.size, APR_MMAP_READ, r->pool)))) { list->mtime = 0; return rv; } list->mtime = finfo.mtime; list->ips = apr_pcalloc(list->pool, sizeof *list->ips); list->hosts = apr_pcalloc(list->pool, sizeof *list->hosts); if (!finfo.size) return APR_SUCCESS; for (s = m->mm; s < (apr_byte_t *)m->mm + m->size; s = eol + 1) { apr_byte_t *ipstr, *substr; apr_ipsubnet_t *subnet; for (eol = s, rv = APR_SUCCESS; eol < (apr_byte_t *)m->mm + m->size && *eol != '\n'; eol++) if (!rv && *eol != '.' && IS_REGEX_SPECIAL(*eol)) rv = APR_EINVAL; if (s == eol || *s == '#') continue; ipstr = apr_pstrmemdup(r->pool, s, eol - s); substr = ap_strchr(ipstr, '/'); if (substr) *substr++ = '\0'; if (!rv && !(rv = apr_ipsubnet_create(&subnet, ipstr, substr, list->pool))) regist_ip(list->ips, subnet, 0, subnet->family == APR_INET ? 4 : 16/*IPv6*/, list->pool); else if (APR_STATUS_IS_EINVAL(rv)) { if (substr) *--substr = '/'; ap_str_tolower(ipstr); regist_host(list->hosts, ipstr, ipstr + strlen(ipstr), list->pool); } else if (!APR_STATUS_IS_EBADIP(rv)) { list->mtime = 0; apr_pool_clear(list->pool); return rv; } } return APR_SUCCESS; } static void display_ips(iplist_impl_t *list, apr_size_t n, request_rec *r) { apr_hash_index_t *hi; ap_rprintf(r, "%*s
%s", 4 * n, "", !list->term ? "" : list->term == APR_INET ? "IPv4 terminator" : "IPv6 terminator"); if (list->list) ap_rprintf(r, " (containing %d subnet%s)", list->list->nelts, list->list->nelts > 1 ? "s" : ""); if (list->subs) { ap_rputs("
\n", r); n++; for (hi = apr_hash_first(r->pool, list->subs); hi; hi = apr_hash_next(hi)) { union { const apr_byte_t *b; const apr_uint16_t *u16; const void *v; } key; union { iplist_impl_t *i; void *v; } sub; apr_hash_this(hi, &key.v, NULL, &sub.v); ap_rprintf(r, "%*s
Key: %d.%d %.4X
\n", 4 * n, "", *key.b, key.b[1], ntohs(*key.u16)); display_ips(sub.i, n, r); } ap_rprintf(r, "%*s
", 4 * --n, ""); } ap_rputs("
\n", r); } static void display_hosts(iplist_impl_t *list, apr_size_t n, request_rec *r) { apr_hash_index_t *hi; ap_rprintf(r, "%*s
%s", 4 * n, "", !list->term ? "" : "domain terminator"); if (list->list) ap_rprintf(r, " (containing %d regex%s)", list->list->nelts, list->list->nelts > 1 ? "'s" : ""); if (list->subs) { ap_rputs("
\n", r); n++; for (hi = apr_hash_first(r->pool, list->subs); hi; hi = apr_hash_next(hi)) { union { const char *c; const void *v; } key; union { iplist_impl_t *i; void *v; } sub; apr_hash_this(hi, &key.v, NULL, &sub.v); ap_rprintf(r, "%*s
Key: %s
\n", 4 * n, "", key.c); display_hosts(sub.i, n, r); } ap_rprintf(r, "%*s
", 4 * --n, ""); } ap_rputs("
\n", r); } static void iplist_child_init(apr_pool_t *p, server_rec *s) { int rv; if ((rv = apr_thread_mutex_create(&iplists_mutex, APR_THREAD_MUTEX_DEFAULT, p))) { iplists_mutex = NULL; ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "mod_authz_iplist: apr_thread_mutex_create"); } else iplists = apr_hash_make(p); } static int iplist_access_checker(request_rec *r) { int retcode; apr_status_t rv = APR_SUCCESS; apr_finfo_t finfo; iplist_t *list; authz_iplist_dir_conf *cf = ap_get_module_config(r->per_dir_config, &authz_iplist_module); if (!(cf->limited & AP_METHOD_BIT << r->method_number) || !iplists_mutex) return OK; list = apr_hash_get(iplists, cf->file, APR_HASH_KEY_STRING); if (!list) { apr_thread_mutex_lock(iplists_mutex); list = apr_hash_get(iplists, cf->file, APR_HASH_KEY_STRING); if (!list && (rv = create_list(&list, cf->file, r->server->process->pool))) ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_authz_iplist: create_list"); apr_thread_mutex_unlock(iplists_mutex); } if (rv) return OK; if ((rv = apr_stat(&finfo, cf->file, APR_FINFO_MIN, r->pool))) { apr_thread_rwlock_wrlock(list->rwlock); list->mtime = 0; apr_pool_clear(list->pool); apr_thread_rwlock_unlock(list->rwlock); ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_authz_iplist: apr_stat: %s", cf->file); return OK; } if (finfo.mtime > list->mtime && apr_time_now() - finfo.mtime >= APR_USEC_PER_SEC) { apr_thread_rwlock_wrlock(list->rwlock); if (finfo.mtime > list->mtime && apr_time_now() - finfo.mtime >= APR_USEC_PER_SEC && (rv = update_list(list, cf->file, r))) ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_authz_iplist: update_list"); apr_thread_rwlock_unlock(list->rwlock); } if (rv) return OK; retcode = cf->type == LIST_TYPE_BLACK ? OK : HTTP_FORBIDDEN; apr_thread_rwlock_rdlock(list->rwlock); if (is_hit_ip(list->ips, r->connection->remote_addr, 0, r->connection->remote_addr->family != APR_INET && IN6_IS_ADDR_V4MAPPED((in6_addr_t *)r->connection ->remote_addr->ipaddr_ptr) ? sizeof(apr_uint32_t) : r->connection->remote_addr->ipaddr_len)) retcode = cf->type == LIST_TYPE_BLACK ? HTTP_FORBIDDEN : OK; else if (list->hosts->term || list->hosts->list || list->hosts->subs) { const char *host = ap_get_remote_host(r->connection, NULL, REMOTE_DOUBLE_REV, NULL); if (host && is_hit_host(list->hosts, host, host + strlen(host))) retcode = cf->type == LIST_TYPE_BLACK ? HTTP_FORBIDDEN : OK; } apr_thread_rwlock_unlock(list->rwlock); if (retcode == 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); return retcode; } static int iplist_handler(request_rec *r) { apr_hash_index_t *hi; if (strcmp(r->handler, HANDLER_NAME)) return DECLINED; if (r->method_number == M_OPTIONS) { r->allowed |= AP_METHOD_BIT << M_GET; return DECLINED; } else if (r->method_number != M_GET) return DECLINED; ap_set_content_type(r, "text/html"); ap_rputs("\n" "\n" "\n" "loaded lists - mod_authz_iplist\n" "\n\n" "

loaded lists - mod_authz_iplist

\n\n", r); for (hi = apr_hash_first(r->pool, iplists); hi; hi = apr_hash_next(hi)) { union { const char *c; const void *v; } file; union { iplist_t *i; void *v; } list; apr_size_t lmlen; apr_time_exp_t tm; char lastmod[APR_RFC822_DATE_LEN]; apr_hash_this(hi, &file.v, NULL, &list.v); apr_thread_rwlock_rdlock(list.i->rwlock); apr_time_exp_lt(&tm, list.i->mtime); apr_strftime(lastmod, &lmlen, sizeof lastmod, "%a, %d %b %Y %T %Z", &tm); ap_rprintf(r, "

File: %s

\n" "

Last-Mod: %s

\n
\n", file.c, lastmod); if (list.i->mtime) { ap_rputs("
[IPs]
\n", r); display_ips(list.i->ips, 1, r); ap_rputs("
[hosts]
\n", r); display_hosts(list.i->hosts, 1, r); } else ap_rputs("
invalid list
\n", r); apr_thread_rwlock_unlock(list.i->rwlock); ap_rputs("
\n\n", r); } ap_rputs("\n", r); return OK; } static void register_hooks(apr_pool_t *p) { ap_hook_child_init(iplist_child_init, NULL, NULL, APR_HOOK_MIDDLE); /* This can be access checker since we don't require r->user to be set. */ ap_hook_access_checker(iplist_access_checker, NULL, NULL, APR_HOOK_MIDDLE); /* for debugging purpose */ ap_hook_handler(iplist_handler, NULL, NULL, APR_HOOK_MIDDLE); } module AP_MODULE_DECLARE_DATA authz_iplist_module = { STANDARD20_MODULE_STUFF, create_authz_iplist_dir_config, /* dir config creater */ NULL, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ authz_iplist_cmds, register_hooks /* register hooks */ };