Main Page | Modules | Namespace List | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals | Related Pages | Examples

mod_setenvif.c

Go to the documentation of this file.
00001 /* Copyright 1999-2005 The Apache Software Foundation or its licensors, as
00002  * applicable.
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 /*
00018  * mod_setenvif.c
00019  * Set environment variables based on matching request headers or
00020  * attributes against regex strings
00021  * 
00022  * Paul Sutton <paul@ukweb.com> 27 Oct 1996
00023  * Based on mod_browser by Alexei Kosut <akosut@organic.com>
00024  */
00025 
00026 /*
00027  * Used to set environment variables based on the incoming request headers,
00028  * or some selected other attributes of the request (e.g., the remote host
00029  * name).
00030  *
00031  * Usage:
00032  *
00033  *   SetEnvIf name regex var ...
00034  *
00035  * where name is either a HTTP request header name, or one of the
00036  * special values (see below). 'name' may be a regex when it is used
00037  * to specify an HTTP request header name. The 'value' of the header 
00038  & (or the value of the special value from below) are compared against
00039  * the regex argument. If this is a simple string, a simple sub-string
00040  * match is performed. Otherwise, a request expression match is
00041  * done. If the value matches the string or regular expression, the
00042  * environment variables listed as var ... are set. Each var can 
00043  * be in one of three formats: var, which sets the named variable
00044  * (the value value "1"); var=value, which sets the variable to
00045  * the given value; or !var, which unsets the variable is it has
00046  * been previously set.
00047  *
00048  * Normally the strings are compared with regard to case. To ignore
00049  * case, use the directive SetEnvIfNoCase instead.
00050  *
00051  * Special values for 'name' are:
00052  *
00053  *   server_addr        IP address of interface on which request arrived
00054  *                      (analogous to SERVER_ADDR set in ap_add_common_vars())
00055  *   remote_host        Remote host name (if available)
00056  *   remote_addr        Remote IP address
00057  *   request_method     Request method (GET, POST, etc)
00058  *   request_uri        Requested URI
00059  *
00060  * Examples:
00061  *
00062  * To set the enviroment variable LOCALHOST if the client is the local
00063  * machine:
00064  *
00065  *    SetEnvIf remote_addr 127.0.0.1 LOCALHOST
00066  *
00067  * To set LOCAL if the client is the local host, or within our company's
00068  * domain (192.168.10):
00069  *
00070  *    SetEnvIf remote_addr 192.168.10. LOCAL
00071  *    SetEnvIf remote_addr 127.0.0.1   LOCALHOST
00072  *
00073  * This could be written as:
00074  *
00075  *    SetEnvIf remote_addr (127.0.0.1|192.168.10.) LOCAL
00076  *
00077  * To set HAVE_TS if the client request contains any header beginning
00078  * with "TS" with a value beginning with a lower case alphabet:
00079  *
00080  *    SetEnvIf ^TS* ^[a-z].* HAVE_TS
00081  */
00082 
00083 #include "apr.h"
00084 #include "apr_strings.h"
00085 #include "apr_strmatch.h"
00086 
00087 #define APR_WANT_STRFUNC
00088 #include "apr_want.h"
00089 
00090 #include "ap_config.h"
00091 #include "httpd.h"
00092 #include "http_config.h"
00093 #include "http_core.h"
00094 #include "http_log.h"
00095 #include "http_protocol.h"
00096 
00097 
00098 enum special {
00099     SPECIAL_NOT,
00100     SPECIAL_REMOTE_ADDR,
00101     SPECIAL_REMOTE_HOST,
00102     SPECIAL_REQUEST_URI,
00103     SPECIAL_REQUEST_METHOD,
00104     SPECIAL_REQUEST_PROTOCOL,
00105     SPECIAL_SERVER_ADDR
00106 };
00107 typedef struct {
00108     char *name;                 /* header name */
00109     regex_t *pnamereg;          /* compiled header name regex */
00110     char *regex;                /* regex to match against */
00111     regex_t *preg;              /* compiled regex */
00112     const apr_strmatch_pattern *pattern; /* non-regex pattern to match */
00113     apr_table_t *features;      /* env vars to set (or unset) */
00114     enum special special_type;  /* is it a "special" header ? */
00115     int icase;                  /* ignoring case? */
00116 } sei_entry;
00117 
00118 typedef struct {
00119     apr_array_header_t *conditionals;
00120 } sei_cfg_rec;
00121 
00122 module AP_MODULE_DECLARE_DATA setenvif_module;
00123 
00124 /*
00125  * These routines, the create- and merge-config functions, are called
00126  * for both the server-wide and the per-directory contexts.  This is
00127  * because the different definitions are used at different times; the
00128  * server-wide ones are used in the post-read-request phase, and the
00129  * per-directory ones are used during the header-parse phase (after
00130  * the URI has been mapped to a file and we have anything from the
00131  * .htaccess file and <Directory> and <Files> containers).
00132  */
00133 static void *create_setenvif_config(apr_pool_t *p)
00134 {
00135     sei_cfg_rec *new = (sei_cfg_rec *) apr_palloc(p, sizeof(sei_cfg_rec));
00136 
00137     new->conditionals = apr_array_make(p, 20, sizeof(sei_entry));
00138     return (void *) new;
00139 }
00140 
00141 static void *create_setenvif_config_svr(apr_pool_t *p, server_rec *dummy)
00142 {
00143     return create_setenvif_config(p);
00144 }
00145 
00146 static void *create_setenvif_config_dir(apr_pool_t *p, char *dummy)
00147 {
00148     return create_setenvif_config(p);
00149 }
00150 
00151 static void *merge_setenvif_config(apr_pool_t *p, void *basev, void *overridesv)
00152 {
00153     sei_cfg_rec *a = apr_pcalloc(p, sizeof(sei_cfg_rec));
00154     sei_cfg_rec *base = basev, *overrides = overridesv;
00155 
00156     a->conditionals = apr_array_append(p, base->conditionals,
00157                                        overrides->conditionals);
00158     return a;
00159 }
00160 
00161 /*
00162  * any non-NULL magic constant will do... used to indicate if REG_ICASE should
00163  * be used
00164  */
00165 #define ICASE_MAGIC     ((void *)(&setenvif_module))
00166 #define SEI_MAGIC_HEIRLOOM "setenvif-phase-flag"
00167 
00168 static int is_header_regex(apr_pool_t *p, const char* name) 
00169 {
00170     /* If a Header name contains characters other than:
00171      *    -,_,[A-Z\, [a-z] and [0-9].
00172      * assume the header name is a regular expression.
00173      */
00174     regex_t *preg = ap_pregcomp(p, "^[-A-Za-z0-9_]*$",
00175                                 (REG_EXTENDED | REG_NOSUB ));
00176     ap_assert(preg != NULL);
00177 
00178     if (ap_regexec(preg, name, 0, NULL, 0)) {
00179         return 1;
00180     }
00181 
00182     return 0;
00183 }
00184 
00185 /* If the input string does not take advantage of regular
00186  * expression metacharacters, return a pointer to an equivalent
00187  * string that can be searched using apr_strmatch().  (The
00188  * returned string will often be the input string.  But if
00189  * the input string contains escaped characters, the returned
00190  * string will be a copy with the escapes removed.)
00191  */
00192 static const char *non_regex_pattern(apr_pool_t *p, const char *s)
00193 {
00194     const char *src = s;
00195     int escapes_found = 0;
00196     int in_escape = 0;
00197 
00198     while (*src) {
00199         switch (*src) {
00200         case '^':
00201         case '.':
00202         case '$':
00203         case '|':
00204         case '(':
00205         case ')':
00206         case '[':
00207         case ']':
00208         case '*':
00209         case '+':
00210         case '?':
00211         case '{':
00212         case '}':
00213             if (!in_escape) {
00214                 return NULL;
00215             }
00216             in_escape = 0;
00217             break;
00218         case '\\':
00219             if (!in_escape) {
00220                 in_escape = 1;
00221                 escapes_found = 1;
00222             }
00223             else {
00224                 in_escape = 0;
00225             }
00226             break;
00227         default:
00228             if (in_escape) {
00229                 return NULL;
00230             }
00231             break;
00232         }
00233         src++;
00234     }
00235     if (!escapes_found) {
00236         return s;
00237     }
00238     else {
00239         char *unescaped = (char *)apr_palloc(p, src - s + 1);
00240         char *dst = unescaped;
00241         src = s;
00242         do {
00243             if (*src == '\\') {
00244                 src++;
00245             }
00246         } while ((*dst++ = *src++));
00247         return unescaped;
00248     }
00249 }
00250 
00251 static const char *add_setenvif_core(cmd_parms *cmd, void *mconfig,
00252                                      char *fname, const char *args)
00253 {
00254     char *regex;
00255     const char *simple_pattern;
00256     const char *feature;
00257     sei_cfg_rec *sconf;
00258     sei_entry *new;
00259     sei_entry *entries;
00260     char *var;
00261     int i;
00262     int beenhere = 0;
00263     int icase;
00264 
00265     /*
00266      * Determine from our context into which record to put the entry.
00267      * cmd->path == NULL means we're in server-wide context; otherwise,
00268      * we're dealing with a per-directory setting.
00269      */
00270     sconf = (cmd->path != NULL)
00271       ? (sei_cfg_rec *) mconfig
00272       : (sei_cfg_rec *) ap_get_module_config(cmd->server->module_config,
00273                                                &setenvif_module);
00274     entries = (sei_entry *) sconf->conditionals->elts;
00275     /* get regex */
00276     regex = ap_getword_conf(cmd->pool, &args);
00277     if (!*regex) {
00278         return apr_pstrcat(cmd->pool, "Missing regular expression for ",
00279                            cmd->cmd->name, NULL);
00280     }
00281 
00282     /*
00283      * If we've already got a sei_entry with the same name we want to
00284      * just copy the name pointer... so that later on we can compare
00285      * two header names just by comparing the pointers.
00286      */
00287     for (i = 0; i < sconf->conditionals->nelts; ++i) {
00288         new = &entries[i];
00289         if (!strcasecmp(new->name, fname)) {
00290             fname = new->name;
00291             break;
00292         }
00293     }
00294 
00295     /* if the last entry has an identical headername and regex then
00296      * merge with it
00297      */
00298     i = sconf->conditionals->nelts - 1;
00299     icase = cmd->info == ICASE_MAGIC;
00300     if (i < 0
00301         || entries[i].name != fname
00302         || entries[i].icase != icase
00303         || strcmp(entries[i].regex, regex)) {
00304 
00305         /* no match, create a new entry */
00306         new = apr_array_push(sconf->conditionals);
00307         new->name = fname;
00308         new->regex = regex;
00309         new->icase = icase;
00310         if ((simple_pattern = non_regex_pattern(cmd->pool, regex))) {
00311             new->pattern = apr_strmatch_precompile(cmd->pool,
00312                                                    simple_pattern, !icase);
00313             if (new->pattern == NULL) {
00314                 return apr_pstrcat(cmd->pool, cmd->cmd->name,
00315                                    " pattern could not be compiled.", NULL);
00316             }
00317             new->preg = NULL;
00318         }
00319         else {
00320             new->preg = ap_pregcomp(cmd->pool, regex,
00321                                     (REG_EXTENDED | (icase ? REG_ICASE : 0)));
00322             if (new->preg == NULL) {
00323                 return apr_pstrcat(cmd->pool, cmd->cmd->name,
00324                                    " regex could not be compiled.", NULL);
00325             }
00326             new->pattern = NULL;
00327         }
00328         new->features = apr_table_make(cmd->pool, 2);
00329 
00330         if (!strcasecmp(fname, "remote_addr")) {
00331             new->special_type = SPECIAL_REMOTE_ADDR;
00332         }
00333         else if (!strcasecmp(fname, "remote_host")) {
00334             new->special_type = SPECIAL_REMOTE_HOST;
00335         }
00336         else if (!strcasecmp(fname, "request_uri")) {
00337             new->special_type = SPECIAL_REQUEST_URI;
00338         }
00339         else if (!strcasecmp(fname, "request_method")) {
00340             new->special_type = SPECIAL_REQUEST_METHOD;
00341         }
00342         else if (!strcasecmp(fname, "request_protocol")) {
00343             new->special_type = SPECIAL_REQUEST_PROTOCOL;
00344         }
00345         else if (!strcasecmp(fname, "server_addr")) {
00346             new->special_type = SPECIAL_SERVER_ADDR;
00347         }
00348         else {
00349             new->special_type = SPECIAL_NOT;
00350             /* Handle fname as a regular expression.
00351              * If fname a simple header string, identify as such
00352              * (new->pnamereg = NULL) to avoid the overhead of searching
00353              * through headers_in for a regex match.
00354              */
00355             if (is_header_regex(cmd->pool, fname)) {
00356                 new->pnamereg = ap_pregcomp(cmd->pool, fname,
00357                                             (REG_EXTENDED | REG_NOSUB
00358                                              | (icase ? REG_ICASE : 0)));
00359                 if (new->pnamereg == NULL)
00360                     return apr_pstrcat(cmd->pool, cmd->cmd->name,
00361                                        "Header name regex could not be "
00362                                        "compiled.", NULL);
00363             }
00364             else {
00365                 new->pnamereg = NULL;
00366             }
00367         }
00368     }
00369     else {
00370         new = &entries[i];
00371     }
00372 
00373     for ( ; ; ) {
00374         feature = ap_getword_conf(cmd->pool, &args);
00375         if (!*feature) {
00376             break;
00377         }
00378         beenhere++;
00379 
00380         var = ap_getword(cmd->pool, &feature, '=');
00381         if (*feature) {
00382             apr_table_setn(new->features, var, feature);
00383         }
00384         else if (*var == '!') {
00385             apr_table_setn(new->features, var + 1, "!");
00386         }
00387         else {
00388             apr_table_setn(new->features, var, "1");
00389         }
00390     }
00391 
00392     if (!beenhere) {
00393         return apr_pstrcat(cmd->pool, "Missing envariable expression for ",
00394                            cmd->cmd->name, NULL);
00395     }
00396 
00397     return NULL;
00398 }
00399 
00400 static const char *add_setenvif(cmd_parms *cmd, void *mconfig,
00401                                 const char *args)
00402 {
00403     char *fname;
00404 
00405     /* get header name */
00406     fname = ap_getword_conf(cmd->pool, &args);
00407     if (!*fname) {
00408         return apr_pstrcat(cmd->pool, "Missing header-field name for ",
00409                            cmd->cmd->name, NULL);
00410     }
00411     return add_setenvif_core(cmd, mconfig, fname, args);
00412 }
00413 
00414 /*
00415  * This routine handles the BrowserMatch* directives.  It simply turns around
00416  * and feeds them, with the appropriate embellishments, to the general-purpose
00417  * command handler.
00418  */
00419 static const char *add_browser(cmd_parms *cmd, void *mconfig, const char *args)
00420 {
00421     return add_setenvif_core(cmd, mconfig, "User-Agent", args);
00422 }
00423 
00424 static const command_rec setenvif_module_cmds[] =
00425 {
00426     AP_INIT_RAW_ARGS("SetEnvIf", add_setenvif, NULL, OR_FILEINFO,
00427                      "A header-name, regex and a list of variables."),
00428     AP_INIT_RAW_ARGS("SetEnvIfNoCase", add_setenvif, ICASE_MAGIC, OR_FILEINFO,
00429                      "a header-name, regex and a list of variables."),
00430     AP_INIT_RAW_ARGS("BrowserMatch", add_browser, NULL, OR_FILEINFO,
00431                      "A browser regex and a list of variables."),
00432     AP_INIT_RAW_ARGS("BrowserMatchNoCase", add_browser, ICASE_MAGIC,
00433                      OR_FILEINFO,
00434                      "A browser regex and a list of variables."),
00435     { NULL },
00436 };
00437 
00438 /*
00439  * This routine gets called at two different points in request processing:
00440  * once before the URI has been translated (during the post-read-request
00441  * phase) and once after (during the header-parse phase).  We use different
00442  * config records for the two different calls to reduce overhead (by not
00443  * re-doing the server-wide settings during directory processing), and
00444  * signal which call it is by having the earlier one pass a flag to the
00445  * later one.
00446  */
00447 static int match_headers(request_rec *r)
00448 {
00449     sei_cfg_rec *sconf;
00450     sei_entry *entries;
00451     const apr_table_entry_t *elts;
00452     const char *val;
00453     apr_size_t val_len = 0;
00454     int i, j;
00455     char *last_name;
00456     regmatch_t regm[AP_MAX_REG_MATCH];
00457 
00458     if (!ap_get_module_config(r->request_config, &setenvif_module)) {
00459         ap_set_module_config(r->request_config, &setenvif_module,
00460                              SEI_MAGIC_HEIRLOOM);
00461         sconf  = (sei_cfg_rec *) ap_get_module_config(r->server->module_config,
00462                                                       &setenvif_module);
00463     }
00464     else {
00465         sconf = (sei_cfg_rec *) ap_get_module_config(r->per_dir_config,
00466                                                      &setenvif_module);
00467     }
00468     entries = (sei_entry *) sconf->conditionals->elts;
00469     last_name = NULL;
00470     val = NULL;
00471     for (i = 0; i < sconf->conditionals->nelts; ++i) {
00472         sei_entry *b = &entries[i];
00473 
00474         /* Optimize the case where a bunch of directives in a row use the
00475          * same header.  Remember we don't need to strcmp the two header
00476          * names because we made sure the pointers were equal during
00477          * configuration.
00478          */
00479         if (b->name != last_name) {
00480             last_name = b->name;
00481             switch (b->special_type) {
00482             case SPECIAL_REMOTE_ADDR:
00483                 val = r->connection->remote_ip;
00484                 break;
00485             case SPECIAL_SERVER_ADDR:
00486                 val = r->connection->local_ip;
00487                 break;
00488             case SPECIAL_REMOTE_HOST:
00489                 val =  ap_get_remote_host(r->connection, r->per_dir_config,
00490                                           REMOTE_NAME, NULL);
00491                 break;
00492             case SPECIAL_REQUEST_URI:
00493                 val = r->uri;
00494                 break;
00495             case SPECIAL_REQUEST_METHOD:
00496                 val = r->method;
00497                 break;
00498             case SPECIAL_REQUEST_PROTOCOL:
00499                 val = r->protocol;
00500                 break;
00501             case SPECIAL_NOT:
00502                 if (b->pnamereg) {
00503                     /* Matching headers_in against a regex. Iterate through
00504                      * the headers_in until we find a match or run out of
00505                      * headers.
00506                      */
00507                     const apr_array_header_t
00508                         *arr = apr_table_elts(r->headers_in);
00509 
00510                     elts = (const apr_table_entry_t *) arr->elts;
00511                     val = NULL;
00512                     for (j = 0; j < arr->nelts; ++j) {
00513                         if (!ap_regexec(b->pnamereg, elts[j].key, 0, NULL, 0)) { 
00514                             val = elts[j].val;
00515                         }
00516                     }
00517                 }
00518                 else {
00519                     /* Not matching against a regex */
00520                     val = apr_table_get(r->headers_in, b->name);
00521                     if (val == NULL) {
00522                         val = apr_table_get(r->subprocess_env, b->name);
00523                     }
00524                 }
00525             }
00526             val_len = val ? strlen(val) : 0;
00527         }
00528 
00529         /*
00530          * A NULL value indicates that the header field or special entity
00531          * wasn't present or is undefined.  Represent that as an empty string
00532          * so that REs like "^$" will work and allow envariable setting
00533          * based on missing or empty field.
00534          */
00535         if (val == NULL) {
00536             val = "";
00537             val_len = 0;
00538         }
00539 
00540         if ((b->pattern && apr_strmatch(b->pattern, val, val_len)) ||
00541             (!b->pattern && !ap_regexec(b->preg, val, AP_MAX_REG_MATCH, regm,
00542                                         0))) {
00543             const apr_array_header_t *arr = apr_table_elts(b->features);
00544             elts = (const apr_table_entry_t *) arr->elts;
00545 
00546             for (j = 0; j < arr->nelts; ++j) {
00547                 if (*(elts[j].val) == '!') {
00548                     apr_table_unset(r->subprocess_env, elts[j].key);
00549                 }
00550                 else {
00551                     if (!b->pattern) {
00552                         char *replaced = ap_pregsub(r->pool, elts[j].val, val,
00553                                                     AP_MAX_REG_MATCH, regm);
00554                         if (replaced) {
00555                             apr_table_setn(r->subprocess_env, elts[j].key,
00556                                            replaced);
00557                         }
00558                     }
00559                     else {
00560                         apr_table_setn(r->subprocess_env, elts[j].key,
00561                                        elts[j].val);
00562                     }
00563                 }
00564             }
00565         }
00566     }
00567 
00568     return DECLINED;
00569 }
00570 
00571 static void register_hooks(apr_pool_t *p)
00572 {
00573     ap_hook_header_parser(match_headers, NULL, NULL, APR_HOOK_MIDDLE);
00574     ap_hook_post_read_request(match_headers, NULL, NULL, APR_HOOK_MIDDLE);
00575 }
00576 
00577 module AP_MODULE_DECLARE_DATA setenvif_module =
00578 {
00579     STANDARD20_MODULE_STUFF,
00580     create_setenvif_config_dir, /* dir config creater */
00581     merge_setenvif_config,      /* dir merger --- default is to override */
00582     create_setenvif_config_svr, /* server config */
00583     merge_setenvif_config,      /* merge server configs */
00584     setenvif_module_cmds,       /* command apr_table_t */
00585     register_hooks              /* register hooks */
00586 };