I already told him this probably wouldn't be in 1.2 due to the feature freeze,
but one thing he brought up did raise an interesting point. He looked at the
"deny|allow from user-agents" bit and decided that leaving "from" in there was
a logical problem. Indeed, I wonder what would happen if I meant "the host
named user-agents", but more importantly, is there a potential security hole
here? The other thing he did was allow for
deny from 204.152.144.255/32
where the 32 can mean the number of significant bits in the IP number being
compared. Seems pretty useful - anyone want to make that its own patch to put
in /contrib/patches?
Brian
--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
brian@organic.com www.apache.org hyperreal.com http://www.organic.com/JOBS
---------- Forwarded message ----------
Date: Wed, 20 Nov 1996 15:06:48 -0600
From: Huver <huver@amgraf.com>
To: brian@organic.com
Subject: New mod_access complete
Hi,
I got the 1.2dev lastnight (961119200035.tgz) from the
ftp.apache.org/httpd/from-cvs directory. Hope this is what you meant,
I could not access http://dev.apache.org/from-cvs/ URL.
I'm not sure how to send the mod_access.c out there; after reading the
notes I'm not a member and so can I present an issue/code to discuss
and ask for testing/votes?
I presume you are a dev team member, so I'm sending the mod_access.c
in its entirety to you (the diff would be 70% of the thing anyway).
A quick synopsis:
allow user-agents agent-name agent-name ...
allow from domain-name domain-name ...
(domain-name requires 'HostnameLookup On' directive)
allow from IP[/nbits] IP[/nbits] ...
The "user-agents" line does not use 'from' keyword because it keeps
the same parsing rule. User-agent name checks are done caseless, as is
'domain-name'. I kept the checking logic in the original so that if
"user-agents" is given, it is checked first (before checking the
"allow from all" clause). It's a good feature and the logic stands.
The IP[/nbits] part I already explained yesterday (0 <= nbits <= 32,
how many significant bits must match).
Please pass it on for the crew to review/test/comment/whatever. Thanks.
I speak on behalf of my employer here that Amgraf, Inc. disclaims all
resonsiblities of what I did to mod_access.
-huver huver@amgraf.com
==========================================================================
/* ====================================================================
* Copyright (c) 1995 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.
*
* 5. 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 <http://www.apache.org/>.
*
*/
/*
* Security options etc.
*
* Module derived from code originally written by Rob McCool
*
*/
/* heavily modified from version 1.1.1 and 1.2dev 961119220035.tgz
* package by huver@amgraf.com. 96/11/20
*/
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_request.h"
typedef struct {
char *from;
int limited;
unsigned int type;
unsigned long bitmask;
} allowdeny;
/* for 'type' above */
#define CHECK_TYPE_NAME 1 /* domain name */
#define CHECK_TYPE_IP 2 /* ip address */
#define CHECK_TYPE_AGENT 3 /* user agent */
/* to keep compiler happy */
void strtolower();
/* things in the 'order' array */
#define DENY_THEN_ALLOW 0
#define ALLOW_THEN_DENY 1
#define MUTUAL_FAILURE 2
typedef struct {
int order[METHODS];
array_header *allows;
array_header *denys;
} access_dir_conf;
module access_module;
void *create_access_dir_config (pool *p, char *dummy)
{
access_dir_conf *conf =
(access_dir_conf *)pcalloc(p, sizeof(access_dir_conf));
int i;
for (i = 0; i < METHODS; ++i) conf->order[i] = DENY_THEN_ALLOW;
conf->allows = make_array (p, 1, sizeof (allowdeny));
conf->denys = make_array (p, 1, sizeof (allowdeny));
return (void *)conf;
}
const char *order (cmd_parms *cmd, void *dv, char *arg)
{
access_dir_conf *d = (access_dir_conf *)dv;
int i, order;
if (!strcasecmp (arg, "allow,deny")) order = ALLOW_THEN_DENY;
else if (!strcasecmp (arg, "deny,allow")) order = DENY_THEN_ALLOW;
else if (!strcasecmp (arg, "mutual-failure")) order = MUTUAL_FAILURE;
else return "unknown order";
for (i = 0; i < METHODS; ++i)
if (cmd->limited & (1 << i))
d->order[i] = order;
return NULL;
}
const char *allow_cmd (cmd_parms *cmd, void *dv, char *from, char *where)
{
access_dir_conf *d = (access_dir_conf *)dv;
allowdeny *a;
char *ip, *p;
int is_addr, is_agent, n;
unsigned long bmask;
struct in_addr s_in;
/*
if (strcasecmp (from, "from"))
return "allow and deny must be followed by 'from'";
*/
/* to allow "user-agents" specifications, this is changed so a
* line like:
*
* allow user-agents agent-name agent-name ...
*
* would work. That is, "from" is not used in the case of user-agents.
*/
n = is_agent = 0;
if (strcasecmp(from, "from") == 0) n = 1;
if (strcasecmp(from, "user-agents") == 0) { n = 1; is_agent = 1; }
if (n == 0)
return "allow and deny must be followed by 'from' or 'user-agents'";
/* figure out what the given pattern is, all digits or not */
ip = (char *) malloc (strlen(where) + 1);
strcpy (ip, where);
/* note if "IP/bits" is used */
if ((p = strchr(ip, '/')) != NULL) *p = 0;
is_addr = 1;
for (p = ip; *p != 0; p++)
if (*p != '.' && ! isdigit(*p)) {
is_addr = 0;
break;
}
if (is_addr) {
if (inet_aton(ip, &s_in) == 0) {
free (ip);
return "bad IP address pattern";
}
}
free (ip);
a = (allowdeny *)push_array (cmd->info ? d->allows : d->denys);
/* default bitmask and check type */
a->bitmask = 0;
a->type = CHECK_TYPE_NAME;
if (is_agent) {
/* agent names are checked caseless */
a->type = CHECK_TYPE_AGENT;
ip = (char *) malloc (strlen(where) + 1);
strcpy (ip, where);
strtolower (ip);
strcpy (where, ip);
free (ip);
}
else if (is_addr) {
/* set allowed bitmask. default is 0 which allows all; bitmask
* is only used for iP address matching.
*/
a->type = CHECK_TYPE_IP;
if ((p = strchr (where, '/')) != NULL) {
a->bitmask = 0xffffffff;
*p++ = '\0';
n = atoi(p);
if (n >= 0 && n <= 32) a->bitmask = 0xffffffff << (32-n);
}
else { /* work on IP pattern if '/n' not given */
n = 0;
for (p = where; *p != 0; p++) /* count dots */
if (*p == '.') n++;
ip = malloc(16); /* max len for aaa.bbb.ccc.ddd plus null */
strcpy (ip, where);
if (n == 3) {
if (*(p-1) == '.') strcat (ip, "0");
}
else { /* less than 3 dots */
if (n == 2) {
if (*(p-1) == '.') strcat (ip, "0.0");
else strcat (ip, ".0");
}
else {
if (*(p-1) == '.') strcat (ip, "0.0.0");
else strcat (ip, ".0.0");
}
}
/* figure out proper mask -- walk back along the IP string
* for each dot
*/
bmask = 0xffffffff;
p = ip + strlen(ip) - 1;
while (p >= ip) {
if (*p == '.') {
if (atoi(p+1) == 0) bmask <<= 8;
*p = '\0';
}
p--;
}
free (ip);
a->bitmask = bmask;
}
}
/* preset bitmask to network order (for later comparison) */
a->bitmask = htonl(a->bitmask);
a->from = pstrdup (cmd->pool, where);
a->limited = cmd->limited;
return NULL;
}
static char its_an_allow;
command_rec access_cmds[] = {
{ "order", order, NULL, OR_LIMIT, TAKE1,
"'allow,deny', 'deny,allow', or 'mutual-failure'" },
{ "allow", allow_cmd, &its_an_allow, OR_LIMIT, ITERATE2,
"'from' followed by hostnames or IP-address wildcards" },
{ "deny", allow_cmd, NULL, OR_LIMIT, ITERATE2,
"'from' followed by hostnames or IP-address wildcards" },
{NULL}
};
int in_domain(const char *domain, const char *what) {
int dl=strlen(domain);
int wl=strlen(what);
if((wl-dl) >= 0) {
if (strcasecmp(domain,&what[wl-dl]) != 0) 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;
}
int in_ip (char *allowdeny, char *client, unsigned long allowdeny_mask)
{
struct in_addr allow_in, his_in;
unsigned long we_allowdeny;
/* good allowdeny IP pattern? */
if (inet_aton(allowdeny, &allow_in) == 0) return 0;
/* is the given "IP address to check" an IP pattern? */
if (inet_aton(client, &his_in) == 0) return 0;
/* remember, we are called AFTER the "all" has been checked,
* so we must be particular here. everything is in network
* byteorder.
*/
we_allowdeny = allow_in.s_addr & allowdeny_mask;
if ((his_in.s_addr & allowdeny_mask) == we_allowdeny) return 1;
return 0;
}
int find_allowdeny (request_rec *r, array_header *a, int method)
{
allowdeny *ap = (allowdeny *)a->elts;
int mmask = (1 << method);
int i, gothost=0;
const char *remotehost=NULL;
for (i = 0; i < a->nelts; ++i) {
if (!(mmask & ap[i].limited))
continue;
/* this order of checking says 'user-agents' spec takes
* precedence over anything else.
*/
if (ap[i].type == CHECK_TYPE_AGENT) {
char * this_agent = table_get(r->headers_in, "User-Agent");
if (!this_agent) return 0;
strtolower(this_agent);
if (strstr(this_agent, ap[i].from)) return 1;
}
if (!strcmp (ap[i].from, "all"))
return 1;
if (!gothost)
{
remotehost = get_remote_host(r->connection, r->per_dir_config,
REMOTE_HOST);
gothost = 1;
}
/* Do symbolic domain name matching only if we know the
* remote name. So for "allow from name" to work,
* "HostnameLookup On" directive must be set.
*/
if (remotehost != NULL) {
if (ap[i].type == CHECK_TYPE_NAME &&
in_domain(ap[i].from, remotehost)) return 1;
}
if (ap[i].type == CHECK_TYPE_IP &&
in_ip (ap[i].from, r->connection->remote_ip, ap[i].bitmask))
return 1;
}
return 0;
}
int check_dir_access (request_rec *r)
{
int method = r->method_number;
access_dir_conf *a =
(access_dir_conf *)
get_module_config (r->per_dir_config, &access_module);
int ret = OK;
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) {
if (find_allowdeny (r, a->denys, method))
ret = FORBIDDEN;
if (find_allowdeny (r, a->allows, method))
ret = OK;
}
else {
if (find_allowdeny(r, a->allows, method)
&& !find_allowdeny(r, a->denys, method))
ret = OK;
else
ret = FORBIDDEN;
}
if (ret == FORBIDDEN && (
satisfies(r) != SATISFY_ANY || !some_auth_required(r)
)) {
log_reason ("Client denied by server configuration", r->filename, r);
}
return ret;
}
void strtolower (char *p)
{
char c;
while ((c = *p) != '\0') {
if (isupper(c)) *p = tolower(c);
p++;
}
}
module access_module = {
STANDARD_MODULE_STUFF,
NULL, /* initializer */
create_access_dir_config, /* dir config creater */
NULL, /* dir merger --- default is to override */
NULL, /* server config */
NULL, /* merge server config */
access_cmds,
NULL, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
check_dir_access, /* check access */
NULL, /* type_checker */
NULL, /* fixups */
NULL /* logger */
};
|