/* ====================================================================
* 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 */
};