httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Brian Behlendorf <br...@organic.com>
Subject New mod_access (fwd)
Date Thu, 21 Nov 1996 05:24:13 GMT

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



Mime
View raw message