--- mod_setenvif.c.2.2 2006-08-07 12:05:17.000000000 +0200 +++ mod_setenvif.c.2.2-patched 2008-02-24 16:03:07.000000000 +0100 @@ -17,7 +17,7 @@ /* * mod_setenvif.c * Set environment variables based on matching request headers or - * attributes against regex strings + * attributes against regex strings or IP networks * * Paul Sutton 27 Oct 1996 * Based on mod_browser by Alexei Kosut @@ -35,7 +35,7 @@ * where name is either a HTTP request header name, or one of the * special values (see below). 'name' may be a regex when it is used * to specify an HTTP request header name. The 'value' of the header - & (or the value of the special value from below) are compared against + * (or the value of the special value from below) are compared against * the regex argument. If this is a simple string, a simple sub-string * match is performed. Otherwise, a request expression match is * done. If the value matches the string or regular expression, the @@ -48,6 +48,22 @@ * Normally the strings are compared with regard to case. To ignore * case, use the directive SetEnvIfNoCase instead. * + * SetEnvIfIP name network var ... + * + * where name is either a HTTP request header name, or one of the + * special values (see below). 'name' may be a regex when it is used + * to specify an HTTP request header name. The 'value' of the header + * (or the value of the special value from below) are compared against + * the network argument. This can be expressed as + * + * a (partial) domain-name + * a full IP address + * a partial IP address + * a network/netmask pair + * a network/nnn CIDR specification + * + * in the same way, as in an Allow or Deny directive. + * * Special values for 'name' are: * * server_addr IP address of interface on which request arrived @@ -81,10 +97,12 @@ */ #include "apr.h" +#include "apr_network_io.h" #include "apr_strings.h" #include "apr_strmatch.h" #define APR_WANT_STRFUNC +#define APR_WANT_BYTEFUNC #include "apr_want.h" #include "ap_config.h" @@ -104,15 +122,29 @@ SPECIAL_REQUEST_PROTOCOL, SPECIAL_SERVER_ADDR }; + +enum sei_type { + T_REGEXP, + T_SIMPLE, + T_IP, + T_HOST +}; + typedef struct { char *name; /* header name */ ap_regex_t *pnamereg; /* compiled header name regex */ - char *regex; /* regex to match against */ - ap_regex_t *preg; /* compiled regex */ - const apr_strmatch_pattern *pattern; /* non-regex pattern to match */ + char *compare; /* original pattern to match against */ + enum sei_type type; + union { + ap_regex_t *preg; /* compiled pattern */ + const apr_strmatch_pattern *pattern; /* non-regex pattern to match */ + char *from; + apr_ipsubnet_t *ip; + } x; apr_table_t *features; /* env vars to set (or unset) */ enum special special_type; /* is it a "special" header ? */ int icase; /* ignoring case? */ + int ip_match; /* ip match? */ } sei_entry; typedef struct { @@ -163,8 +195,41 @@ * be used */ #define ICASE_MAGIC ((void *)(&setenvif_module)) +/* + * any non-NULL magic constant will do... used to indicate if an IP network match + * should be used + */ +static int ip_match_marker; +#define IPMATCH_MAGIC ((void *)(&ip_match_marker)) #define SEI_MAGIC_HEIRLOOM "setenvif-phase-flag" +static 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; + } +} + static int is_header_regex(apr_pool_t *p, const char* name) { /* If a Header name contains characters other than: @@ -251,7 +316,7 @@ static const char *add_setenvif_core(cmd_parms *cmd, void *mconfig, char *fname, const char *args) { - char *regex; + char *compare; const char *simple_pattern; const char *feature; sei_cfg_rec *sconf; @@ -261,6 +326,7 @@ int i; int beenhere = 0; int icase; + int ip_match; /* * Determine from our context into which record to put the entry. @@ -272,9 +338,9 @@ : (sei_cfg_rec *) ap_get_module_config(cmd->server->module_config, &setenvif_module); entries = (sei_entry *) sconf->conditionals->elts; - /* get regex */ - regex = ap_getword_conf(cmd->pool, &args); - if (!*regex) { + /* get pattern */ + compare = ap_getword_conf(cmd->pool, &args); + if (!*compare) { return apr_pstrcat(cmd->pool, "Missing regular expression for ", cmd->cmd->name, NULL); } @@ -292,39 +358,78 @@ } } - /* if the last entry has an identical headername and regex then + /* if the last entry has an identical headername and pattern then * merge with it */ i = sconf->conditionals->nelts - 1; icase = cmd->info == ICASE_MAGIC; + ip_match = cmd->info == IPMATCH_MAGIC; if (i < 0 || entries[i].name != fname || entries[i].icase != icase - || strcmp(entries[i].regex, regex)) { + || entries[i].ip_match != ip_match + || strcmp(entries[i].compare, compare)) { /* no match, create a new entry */ new = apr_array_push(sconf->conditionals); new->name = fname; - new->regex = regex; + new->compare = compare; new->icase = icase; - if ((simple_pattern = non_regex_pattern(cmd->pool, regex))) { - new->pattern = apr_strmatch_precompile(cmd->pool, - simple_pattern, !icase); - if (new->pattern == NULL) { - return apr_pstrcat(cmd->pool, cmd->cmd->name, - " pattern could not be compiled.", NULL); + new->ip_match = ip_match; + if (!ip_match) { + if ((simple_pattern = non_regex_pattern(cmd->pool, compare))) { + new->type = T_SIMPLE; + new->x.pattern = apr_strmatch_precompile(cmd->pool, + simple_pattern, !icase); + if (new->x.pattern == NULL) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + " regex could not be compiled.", NULL); + } + } + else { + new->type = T_REGEXP; + new->x.preg = ap_pregcomp(cmd->pool, compare, + (AP_REG_EXTENDED | (icase ? AP_REG_ICASE : 0))); + if (new->x.preg == NULL) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + " regex could not be compiled.", NULL); + } } - new->preg = NULL; } else { - new->preg = ap_pregcomp(cmd->pool, regex, - (AP_REG_EXTENDED | (icase ? AP_REG_ICASE : 0))); - if (new->preg == NULL) { - return apr_pstrcat(cmd->pool, cmd->cmd->name, - " regex could not be compiled.", NULL); + char *where = apr_pstrdup(cmd->pool, compare); + char *s; + char msgbuf[120]; + apr_status_t rv; + + new->x.from = where; + + if ((s = ap_strchr(where, '/'))) { + *s++ = '\0'; + rv = apr_ipsubnet_create(&new->x.ip, where, s, cmd->pool); + if(APR_STATUS_IS_EINVAL(rv)) { + /* looked nothing like an IP address */ + return "An IP address was expected"; + } + else if (rv != APR_SUCCESS) { + apr_strerror(rv, msgbuf, sizeof msgbuf); + return apr_pstrdup(cmd->pool, msgbuf); + } + new->type = T_IP; + } + else if (!APR_STATUS_IS_EINVAL(rv = apr_ipsubnet_create(&new->x.ip, where, + NULL, cmd->pool))) { + if (rv != APR_SUCCESS) { + apr_strerror(rv, msgbuf, sizeof msgbuf); + return apr_pstrdup(cmd->pool, msgbuf); + } + new->type = T_IP; + } + else { /* no slash, didn't look like an IP address => must be a host */ + new->type = T_HOST; } - new->pattern = NULL; } + new->features = apr_table_make(cmd->pool, 2); if (!strcasecmp(fname, "remote_addr")) { @@ -365,6 +470,13 @@ new->pnamereg = NULL; } } + if (new->type == T_IP && + new->special_type != SPECIAL_REMOTE_ADDR && + new->special_type != SPECIAL_SERVER_ADDR) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + "IP match only allowed against 'REMOTE_ADDR'" + " and 'SERVER_ADDR'.", NULL); + } } else { new = &entries[i]; @@ -427,6 +539,8 @@ "A header-name, regex and a list of variables."), AP_INIT_RAW_ARGS("SetEnvIfNoCase", add_setenvif, ICASE_MAGIC, OR_FILEINFO, "a header-name, regex and a list of variables."), + AP_INIT_RAW_ARGS("SetEnvIfIP", add_setenvif, IPMATCH_MAGIC, OR_FILEINFO, + "a header-name, IP pattern and a list of variables."), AP_INIT_RAW_ARGS("BrowserMatch", add_browser, NULL, OR_FILEINFO, "A browser regex and a list of variables."), AP_INIT_RAW_ARGS("BrowserMatchNoCase", add_browser, ICASE_MAGIC, @@ -450,6 +564,7 @@ sei_entry *entries; const apr_table_entry_t *elts; const char *val; + apr_sockaddr_t *ip = NULL; apr_size_t val_len = 0; int i, j; char *last_name; @@ -537,9 +652,31 @@ val_len = 0; } - if ((b->pattern && apr_strmatch(b->pattern, val, val_len)) || - (!b->pattern && !ap_regexec(b->preg, val, AP_MAX_REG_MATCH, regm, - 0))) { + if (b->type == T_IP) { + if (b->special_type == SPECIAL_REMOTE_ADDR) { + ip = r->connection->remote_addr; + } + else if (b->special_type == SPECIAL_SERVER_ADDR) { + ip = r->connection->local_addr; + } + } + else if (b->type == T_HOST) { + if (b->special_type == SPECIAL_REMOTE_HOST) { + const char *host; + host = ap_get_remote_host(r->connection, + r->per_dir_config, + REMOTE_DOUBLE_REV, + NULL); + if (host != NULL) { + val = host; + } + } + } + + if ((b->type == T_SIMPLE && b->x.pattern && apr_strmatch(b->x.pattern, val, val_len)) || + (b->type == T_REGEXP && b->x.preg && !ap_regexec(b->x.preg, val, AP_MAX_REG_MATCH, regm, 0)) || + (b->type == T_HOST && b->x.from && val && in_domain(b->x.from, val)) || + (b->type == T_IP && b->x.ip && ip && apr_ipsubnet_test(b->x.ip, ip))) { const apr_array_header_t *arr = apr_table_elts(b->features); elts = (const apr_table_entry_t *) arr->elts; @@ -548,7 +685,7 @@ apr_table_unset(r->subprocess_env, elts[j].key); } else { - if (!b->pattern) { + if (b->type == T_REGEXP) { char *replaced = ap_pregsub(r->pool, elts[j].val, val, AP_MAX_REG_MATCH, regm); if (replaced) {