/* ==================================================================== * Copyright (c) 2000 The Apache Group. All rights reserved. * * 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. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * 4. The names "Apache Server" and "Apache Group" 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 names without prior written * permission of the Apache Group. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``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 GROUP 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 Group and was originally based * on public domain software written at the National Center for * Supercomputing Applications, University of Illinois, Urbana-Champaign. * For more information on the Apache Group and the Apache HTTP server * project, please see . * */ /* * Module derived from mod_access.c in Apache HTTP Server Version 1.3 * * Original author: Roberto Arturo Tena Sánchez * * Original Web site: http://accessreferer.sourceforge.net/ */ /* XX patch AllowOverride directive on order to accept mod_access_referer directives ? */ /* XX There can be absolute or relative URIs: absolute URIs always have ':' ("Uniform Resource Identifiers (URI): Generic Syntax and Semantics," RFC 2396) */ #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_request.h" enum allowdeny_type { /* T_ENV, XX Not enviroment variable check implementation needed */ T_ALL, T_IP, T_HOST, T_FAIL }; typedef struct { int limited; union { char *from; struct { unsigned long net; unsigned long mask; } ip; } x; enum allowdeny_type type; } allowdeny; typedef struct { int order[METHODS]; int default_access_referer[METHODS]; array_header * allows; array_header * denys; int allows_empty; int denys_empty; } access_referer_dir_conf; /* values in order */ #define OMITTED_ORDER 0 #define DENY_THEN_ALLOW 1 #define ALLOW_THEN_DENY 2 #define MUTUAL_FAILURE 3 /* values in default_access_referer */ #define OMITTED_DEFAULT 0 #define DEFAULT_DENY_REFERER 1 #define DEFAULT_ALLOW_REFERER 2 module MODULE_VAR_EXPORT access_referer_module; static int is_ip (const char *host); static int in_domain (const char *domain, const char *what); static int find_allowdeny (request_rec *r, array_header *a, int method); static const char * order_referer_cmd (cmd_parms *cmd, void *dv, char *arg); static const char * default_referer_cmd (cmd_parms *cmd, void *dv, char *arg); static const char * allow_referer_cmd (cmd_parms *cmd, void *dv, char *from, char *where); static void * create_access_referer_dir_config_mod (pool *p, char *dummy); static int check_dir_access_referer_mod (request_rec *r); static void *merge_access_referer_dir_config_mod (pool *p, void *basev, void *addv); static void * create_access_referer_dir_config_mod (pool *p, char *dummy) { int i; access_referer_dir_conf *conf; conf = (access_referer_dir_conf *) ap_pcalloc (p, sizeof (access_referer_dir_conf)); for (i = 0; i < METHODS; ++i) { conf->order[i] = OMITTED_ORDER; conf->default_access_referer[i] = OMITTED_DEFAULT; } conf->allows = ap_make_array (p, 1, sizeof (allowdeny)); conf->denys = ap_make_array (p, 1, sizeof (allowdeny)); conf->allows_empty = 1; conf->denys_empty = 1; return conf; } static void * merge_access_referer_dir_config_mod (pool *p, void *basev, void *addv) { int i; access_referer_dir_conf * base; access_referer_dir_conf * add; access_referer_dir_conf * new; base = (access_referer_dir_conf *) basev; add = (access_referer_dir_conf *) addv; new = (access_referer_dir_conf *) ap_pcalloc (p, sizeof (access_referer_dir_conf)); for (i = 0; i < METHODS; ++i) { if (add->order[i] == OMITTED_ORDER) { new->order[i] = base->order[i]; } else { new->order[i] = add->order[i]; } if (add->default_access_referer[i] == OMITTED_ORDER) { new->default_access_referer[i] = base->default_access_referer[i]; } else { new->default_access_referer[i] = add->default_access_referer[i]; } } /* XX wouldn't it be better to check add->allows->nelts ? */ if (add->allows_empty) { new->allows = base->allows; } else { new->allows = add->allows; } if (add->denys_empty) { new->denys = base->denys; } else { new->denys = add->denys; } return new; } static const char * order_referer_cmd (cmd_parms *cmd, void *dv, char *arg) { int i; int o; access_referer_dir_conf *d; d = (access_referer_dir_conf *) dv; if (!strcasecmp (arg, "allow_referer,deny_referer")) o = ALLOW_THEN_DENY; else if (!strcasecmp (arg, "deny_referer,allow_referer")) o = DENY_THEN_ALLOW; else if (!strcasecmp (arg, "mutual-failure")) o = MUTUAL_FAILURE; else return "unknown order_referer"; for (i = 0; i < METHODS; ++i) if (cmd->limited & (1 << i)) d->order[i] = o; return NULL; } static const char * default_referer_cmd (cmd_parms *cmd, void *dv, char *arg) { int i; int o; access_referer_dir_conf *d; d = (access_referer_dir_conf *) dv; if (!strcasecmp (arg, "allow_referer")) o = DEFAULT_ALLOW_REFERER; else if (!strcasecmp (arg, "deny_referer")) o = DEFAULT_DENY_REFERER; else return "unknown default_referer"; for (i = 0; i < METHODS; ++i) if (cmd->limited & (1 << i)) d->default_access_referer[i] = o; return NULL; } static const char * allow_referer_cmd (cmd_parms *cmd, void *dv, char *from, char *where) { char *s; allowdeny *a; access_referer_dir_conf *d; d = (access_referer_dir_conf *) dv; if (strcasecmp (from, "from")) return "allow_request and deny_request must be followed by 'from'"; if (cmd->info != NULL) { a = (allowdeny *) ap_push_array (d->allows); d->allows_empty = 0; } else { a = (allowdeny *) ap_push_array (d->denys); d->denys_empty = 0; } a->x.from = where; a->limited = cmd->limited; /* XX Not enviroment variable check implementation needed 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, '/'))) { /* network plus netmask */ unsigned long mask; a->type = T_IP; /* split netmask from network */ *s = '\0'; s++; /* get network */ if (!is_ip (where) || (a->x.ip.net = ap_inet_addr (where)) == INADDR_NONE) { a->type = T_FAIL; return "syntax error in network portion of network/netmask"; } /* get netmask */ if (!is_ip (s)) { a->type = T_FAIL; return "syntax error in mask portion of network/netmask"; } if (strchr(s, '.')) { /* netmask is in /a.b.c.d form */ mask = ap_inet_addr (s); if (mask == INADDR_NONE) { a->type = T_FAIL; return "syntax error in mask portion of network/netmask"; } } else { /* assume netmask is in /nnn form */ mask = atoi (s); if (mask > 32 || mask <= 0) { a->type = T_FAIL; return "invalid mask in network/netmask"; } mask = 0xFFFFFFFFUL << (32 - mask); mask = htonl (mask); } a->x.ip.mask = mask; /* recalculate network from netmask */ a->x.ip.net = (a->x.ip.net & mask); } else if (ap_isdigit (*where) && is_ip (where)) { /* legacy syntax for ip addrs: a.b.c. ==> a.b.c.0/24 for example */ char *t; int shift; int octet; a->type = T_IP; /* parse components */ /* XX check */ s = where; a->x.ip.net = 0; a->x.ip.mask = 0; shift = 24; while (*s) { t = s; if (!ap_isdigit (*t)) { a->type = T_FAIL; return "invalid ip address"; } while (ap_isdigit (*t)) { ++t; } if (*t == '.') { *t++ = 0; } else if (*t) { a->type = T_FAIL; return "invalid ip address"; } if (shift < 0) { return "invalid ip address, only 4 octets allowed"; } octet = atoi(s); if (octet < 0 || octet > 255) { a->type = T_FAIL; return "each octet must be between 0 and 255 inclusive"; } a->x.ip.net |= octet << shift; a->x.ip.mask |= 0xFFUL << shift; s = t; shift -= 8; } a->x.ip.net = ntohl (a->x.ip.net); a->x.ip.mask = ntohl (a->x.ip.mask); } else { a->type = T_HOST; } return NULL; } static int check_dir_access_referer_mod (request_rec *r) { int ret; int method; access_referer_dir_conf *a; ret = OK; method = r->method_number; a = (access_referer_dir_conf *) ap_get_module_config (r->per_dir_config, &access_referer_module); if (ap_table_get (r->headers_in, "Referer") == NULL) { /* The "Referer" HTTP header was not sent */ if (a->default_access_referer[method] == DEFAULT_ALLOW_REFERER || a->default_access_referer[method] == OMITTED_DEFAULT) ret = OK; else ret = FORBIDDEN; } else if (a->order[method] == ALLOW_THEN_DENY) { ret = FORBIDDEN; if (find_allowdeny (r, a->allows, method)) ret = OK; if (find_allowdeny (r, a->denys, method)) ret = FORBIDDEN; } else if (a->order[method] == DENY_THEN_ALLOW || a->order[method] == OMITTED_ORDER) { if (find_allowdeny (r, a->denys, method)) ret = FORBIDDEN; if (find_allowdeny (r, a->allows, method)) ret = OK; } else /* a->order[method] == MUTUAL_FAILURE */ { if (find_allowdeny (r, a->allows, method) && !find_allowdeny (r, a->denys, method)) ret = OK; else ret = FORBIDDEN; } if (ret == FORBIDDEN && (ap_satisfies (r) != SATISFY_ANY || !ap_some_auth_required (r))) { ap_log_rerror (APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, /* XX print IP and/or hostname, and path */ "client denied by server configuration: %s", r->filename); } return ret; } static int find_allowdeny (request_rec *r, array_header *a, int method) { int i; int mmask; allowdeny * ap; int got_refererhost_name; int got_refererhost_ip; const char * refererhost_name; unsigned long refererhost_ip; uri_components uptr; mmask = (1 << method); ap = (allowdeny *) a->elts; got_refererhost_name = 0; got_refererhost_ip = 0; refererhost_name = NULL; refererhost_ip = (unsigned long) -1; /* XX is INADDR_NONE better? */ /* XX allow/deny referer by the following fields from Referer header: uptr.scheme uptr.hostinfo uptr.path uptr.query uptr.fragment uptr.user uptr.password uptr.port_str uptr.port */ for (i = 0; i < a->nelts; i++) { /* enter here only if there are items in deny_referer or allow_referer */ if (!(mmask & ap[i].limited)) continue; switch (ap[i].type) { /* XX Not enviroment variable check implementation needed case T_ENV: if (ap_table_get (r->subprocess_env, ap[i].x.from)) { return 1; } break; */ case T_ALL: return 1; case T_IP: if (!got_refererhost_ip) { ap_parse_uri_components (r->pool, ap_table_get (r->headers_in, "Referer"), &uptr); if(uptr.hostname == NULL) { return 0; } if (!is_ip (uptr.hostname)) { /* XX resolv the domain name */ got_refererhost_ip = 1; } else { refererhost_ip = ap_inet_addr (uptr.hostname); if (refererhost_ip == INADDR_NONE) { got_refererhost_ip = 1; } else { got_refererhost_ip = 2; } } } if ((got_refererhost_ip == 2) && (ap[i].x.ip.net != INADDR_NONE) && (refererhost_ip & ap[i].x.ip.mask) == ap[i].x.ip.net) return 1; break; case T_HOST: if (!got_refererhost_name) { ap_parse_uri_components (r->pool, ap_table_get (r->headers_in, "Referer"), &uptr); refererhost_name = uptr.hostname; if (refererhost_name == NULL) got_refererhost_name = 1; else if (is_ip (refererhost_name)) /* XX reverse the domain name */ got_refererhost_name = 1; else got_refererhost_name = 2; } if ((got_refererhost_name == 2) && in_domain (ap[i].x.from, refererhost_name)) return 1; break; case T_FAIL: /* XX do nothing? */ break; } } return 0; } static int is_ip (const char *host) { /* this just tests if it matches [\d.]* */ /* XX is a better test needed? */ while ((*host == '.') || ap_isdigit (*host)) host++; return (*host == '\0'); } static int in_domain (const char *domain, const char *what) { int dl; int wl; dl = strlen (domain); wl = strlen (what); if ((wl - dl) >= 0) { if (strcasecmp (domain, &what[wl - dl])) return 0; /* Make sure we matched an *entire* subdomain --- if the user said 'allow from good.com', we don't want people from nogood.com to be able to get in. */ if (wl == dl) return 1; /* matched whole thing */ else return (domain[0] == '.' || what[wl - dl - 1] == '.'); } else return 0; } static char it_is_an_allow; static const command_rec access_referer_cmds_mod[] = { { "allow_referer", allow_referer_cmd, &it_is_an_allow, OR_LIMIT, ITERATE2, "'from' followed by hostnames or IP-address wildcards" }, { "deny_referer", allow_referer_cmd, NULL, /* it is not an allow */ OR_LIMIT, ITERATE2, "'from' followed by hostnames or IP-address wildcards" }, { "order_referer", order_referer_cmd, NULL, OR_LIMIT, TAKE1, "'allow_referer,deny_referer', 'deny_referer,allow_referer', or 'mutual-failure'" }, { "default_referer", default_referer_cmd, NULL, OR_LIMIT, TAKE1, "'allow_referer' or 'deny_referer'" }, { NULL } }; module MODULE_VAR_EXPORT access_referer_module = { STANDARD_MODULE_STUFF, NULL, /* initializer */ create_access_referer_dir_config_mod, /* dir config creater */ merge_access_referer_dir_config_mod, /* dir merger (default is to override) */ NULL, /* server config */ NULL, /* merge server config */ access_referer_cmds_mod, /* directives */ NULL, /* handlers */ NULL, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ check_dir_access_referer_mod, /* check access by referer */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ NULL, /* header parser */ NULL, /* child_init */ NULL, /* child_exit */ NULL /* post read-request */ };