mod_setenvif.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. /* Licensed to the Apache Software Foundation (ASF) under one or more
  2. * contributor license agreements. See the NOTICE file distributed with
  3. * this work for additional information regarding copyright ownership.
  4. * The ASF licenses this file to You under the Apache License, Version 2.0
  5. * (the "License"); you may not use this file except in compliance with
  6. * the License. You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /*
  17. * mod_setenvif.c
  18. * Set environment variables based on matching request headers or
  19. * attributes against regex strings
  20. *
  21. * Paul Sutton <paul@ukweb.com> 27 Oct 1996
  22. * Based on mod_browser by Alexei Kosut <akosut@organic.com>
  23. */
  24. /*
  25. * Used to set environment variables based on the incoming request headers,
  26. * or some selected other attributes of the request (e.g., the remote host
  27. * name).
  28. *
  29. * Usage:
  30. *
  31. * SetEnvIf name regex var ...
  32. *
  33. * where name is either a HTTP request header name, or one of the
  34. * special values (see below). 'name' may be a regex when it is used
  35. * to specify an HTTP request header name. The 'value' of the header
  36. & (or the value of the special value from below) are compared against
  37. * the regex argument. If this is a simple string, a simple sub-string
  38. * match is performed. Otherwise, a request expression match is
  39. * done. If the value matches the string or regular expression, the
  40. * environment variables listed as var ... are set. Each var can
  41. * be in one of three formats: var, which sets the named variable
  42. * (the value value "1"); var=value, which sets the variable to
  43. * the given value; or !var, which unsets the variable is it has
  44. * been previously set.
  45. *
  46. * Normally the strings are compared with regard to case. To ignore
  47. * case, use the directive SetEnvIfNoCase instead.
  48. *
  49. * Special values for 'name' are:
  50. *
  51. * server_addr IP address of interface on which request arrived
  52. * (analogous to SERVER_ADDR set in ap_add_common_vars())
  53. * remote_host Remote host name (if available)
  54. * remote_addr Remote IP address
  55. * request_method Request method (GET, POST, etc)
  56. * request_uri Requested URI
  57. *
  58. * Examples:
  59. *
  60. * To set the enviroment variable LOCALHOST if the client is the local
  61. * machine:
  62. *
  63. * SetEnvIf remote_addr 127.0.0.1 LOCALHOST
  64. *
  65. * To set LOCAL if the client is the local host, or within our company's
  66. * domain (192.168.10):
  67. *
  68. * SetEnvIf remote_addr 192.168.10. LOCAL
  69. * SetEnvIf remote_addr 127.0.0.1 LOCALHOST
  70. *
  71. * This could be written as:
  72. *
  73. * SetEnvIf remote_addr (127.0.0.1|192.168.10.) LOCAL
  74. *
  75. * To set HAVE_TS if the client request contains any header beginning
  76. * with "TS" with a value beginning with a lower case alphabet:
  77. *
  78. * SetEnvIf ^TS* ^[a-z].* HAVE_TS
  79. */
  80. #include "apr.h"
  81. #include "apr_strings.h"
  82. #include "apr_strmatch.h"
  83. #define APR_WANT_STRFUNC
  84. #include "apr_want.h"
  85. #include "ap_config.h"
  86. #include "httpd.h"
  87. #include "http_config.h"
  88. #include "http_core.h"
  89. #include "http_log.h"
  90. #include "http_protocol.h"
  91. enum special {
  92. SPECIAL_NOT,
  93. SPECIAL_REMOTE_ADDR,
  94. SPECIAL_REMOTE_HOST,
  95. SPECIAL_REQUEST_URI,
  96. SPECIAL_REQUEST_METHOD,
  97. SPECIAL_REQUEST_PROTOCOL,
  98. SPECIAL_SERVER_ADDR
  99. };
  100. typedef struct {
  101. char *name; /* header name */
  102. ap_regex_t *pnamereg; /* compiled header name regex */
  103. char *regex; /* regex to match against */
  104. ap_regex_t *preg; /* compiled regex */
  105. const apr_strmatch_pattern *pattern; /* non-regex pattern to match */
  106. apr_table_t *features; /* env vars to set (or unset) */
  107. enum special special_type; /* is it a "special" header ? */
  108. int icase; /* ignoring case? */
  109. } sei_entry;
  110. typedef struct {
  111. apr_array_header_t *conditionals;
  112. } sei_cfg_rec;
  113. module AP_MODULE_DECLARE_DATA setenvif_module;
  114. /*
  115. * These routines, the create- and merge-config functions, are called
  116. * for both the server-wide and the per-directory contexts. This is
  117. * because the different definitions are used at different times; the
  118. * server-wide ones are used in the post-read-request phase, and the
  119. * per-directory ones are used during the header-parse phase (after
  120. * the URI has been mapped to a file and we have anything from the
  121. * .htaccess file and <Directory> and <Files> containers).
  122. */
  123. static void *create_setenvif_config(apr_pool_t *p)
  124. {
  125. sei_cfg_rec *new = (sei_cfg_rec *) apr_palloc(p, sizeof(sei_cfg_rec));
  126. new->conditionals = apr_array_make(p, 20, sizeof(sei_entry));
  127. return (void *) new;
  128. }
  129. static void *create_setenvif_config_svr(apr_pool_t *p, server_rec *dummy)
  130. {
  131. return create_setenvif_config(p);
  132. }
  133. static void *create_setenvif_config_dir(apr_pool_t *p, char *dummy)
  134. {
  135. return create_setenvif_config(p);
  136. }
  137. static void *merge_setenvif_config(apr_pool_t *p, void *basev, void *overridesv)
  138. {
  139. sei_cfg_rec *a = apr_pcalloc(p, sizeof(sei_cfg_rec));
  140. sei_cfg_rec *base = basev, *overrides = overridesv;
  141. a->conditionals = apr_array_append(p, base->conditionals,
  142. overrides->conditionals);
  143. return a;
  144. }
  145. /*
  146. * any non-NULL magic constant will do... used to indicate if AP_REG_ICASE should
  147. * be used
  148. */
  149. #define ICASE_MAGIC ((void *)(&setenvif_module))
  150. #define SEI_MAGIC_HEIRLOOM "setenvif-phase-flag"
  151. static int is_header_regex(apr_pool_t *p, const char* name)
  152. {
  153. /* If a Header name contains characters other than:
  154. * -,_,[A-Z\, [a-z] and [0-9].
  155. * assume the header name is a regular expression.
  156. */
  157. ap_regex_t *preg = ap_pregcomp(p, "^[-A-Za-z0-9_]*$",
  158. (AP_REG_EXTENDED | AP_REG_NOSUB ));
  159. ap_assert(preg != NULL);
  160. if (ap_regexec(preg, name, 0, NULL, 0)) {
  161. return 1;
  162. }
  163. return 0;
  164. }
  165. /* If the input string does not take advantage of regular
  166. * expression metacharacters, return a pointer to an equivalent
  167. * string that can be searched using apr_strmatch(). (The
  168. * returned string will often be the input string. But if
  169. * the input string contains escaped characters, the returned
  170. * string will be a copy with the escapes removed.)
  171. */
  172. static const char *non_regex_pattern(apr_pool_t *p, const char *s)
  173. {
  174. const char *src = s;
  175. int escapes_found = 0;
  176. int in_escape = 0;
  177. while (*src) {
  178. switch (*src) {
  179. case '^':
  180. case '.':
  181. case '$':
  182. case '|':
  183. case '(':
  184. case ')':
  185. case '[':
  186. case ']':
  187. case '*':
  188. case '+':
  189. case '?':
  190. case '{':
  191. case '}':
  192. if (!in_escape) {
  193. return NULL;
  194. }
  195. in_escape = 0;
  196. break;
  197. case '\\':
  198. if (!in_escape) {
  199. in_escape = 1;
  200. escapes_found = 1;
  201. }
  202. else {
  203. in_escape = 0;
  204. }
  205. break;
  206. default:
  207. if (in_escape) {
  208. return NULL;
  209. }
  210. break;
  211. }
  212. src++;
  213. }
  214. if (!escapes_found) {
  215. return s;
  216. }
  217. else {
  218. char *unescaped = (char *)apr_palloc(p, src - s + 1);
  219. char *dst = unescaped;
  220. src = s;
  221. do {
  222. if (*src == '\\') {
  223. src++;
  224. }
  225. } while ((*dst++ = *src++));
  226. return unescaped;
  227. }
  228. }
  229. static const char *add_setenvif_core(cmd_parms *cmd, void *mconfig,
  230. char *fname, const char *args)
  231. {
  232. char *regex;
  233. const char *simple_pattern;
  234. const char *feature;
  235. sei_cfg_rec *sconf;
  236. sei_entry *new;
  237. sei_entry *entries;
  238. char *var;
  239. int i;
  240. int beenhere = 0;
  241. int icase;
  242. /*
  243. * Determine from our context into which record to put the entry.
  244. * cmd->path == NULL means we're in server-wide context; otherwise,
  245. * we're dealing with a per-directory setting.
  246. */
  247. sconf = (cmd->path != NULL)
  248. ? (sei_cfg_rec *) mconfig
  249. : (sei_cfg_rec *) ap_get_module_config(cmd->server->module_config,
  250. &setenvif_module);
  251. entries = (sei_entry *) sconf->conditionals->elts;
  252. /* get regex */
  253. regex = ap_getword_conf(cmd->pool, &args);
  254. if (!*regex) {
  255. return apr_pstrcat(cmd->pool, "Missing regular expression for ",
  256. cmd->cmd->name, NULL);
  257. }
  258. /*
  259. * If we've already got a sei_entry with the same name we want to
  260. * just copy the name pointer... so that later on we can compare
  261. * two header names just by comparing the pointers.
  262. */
  263. for (i = 0; i < sconf->conditionals->nelts; ++i) {
  264. new = &entries[i];
  265. if (!strcasecmp(new->name, fname)) {
  266. fname = new->name;
  267. break;
  268. }
  269. }
  270. /* if the last entry has an identical headername and regex then
  271. * merge with it
  272. */
  273. i = sconf->conditionals->nelts - 1;
  274. icase = cmd->info == ICASE_MAGIC;
  275. if (i < 0
  276. || entries[i].name != fname
  277. || entries[i].icase != icase
  278. || strcmp(entries[i].regex, regex)) {
  279. /* no match, create a new entry */
  280. new = apr_array_push(sconf->conditionals);
  281. new->name = fname;
  282. new->regex = regex;
  283. new->icase = icase;
  284. if ((simple_pattern = non_regex_pattern(cmd->pool, regex))) {
  285. new->pattern = apr_strmatch_precompile(cmd->pool,
  286. simple_pattern, !icase);
  287. if (new->pattern == NULL) {
  288. return apr_pstrcat(cmd->pool, cmd->cmd->name,
  289. " pattern could not be compiled.", NULL);
  290. }
  291. new->preg = NULL;
  292. }
  293. else {
  294. new->preg = ap_pregcomp(cmd->pool, regex,
  295. (AP_REG_EXTENDED | (icase ? AP_REG_ICASE : 0)));
  296. if (new->preg == NULL) {
  297. return apr_pstrcat(cmd->pool, cmd->cmd->name,
  298. " regex could not be compiled.", NULL);
  299. }
  300. new->pattern = NULL;
  301. }
  302. new->features = apr_table_make(cmd->pool, 2);
  303. if (!strcasecmp(fname, "remote_addr")) {
  304. new->special_type = SPECIAL_REMOTE_ADDR;
  305. }
  306. else if (!strcasecmp(fname, "remote_host")) {
  307. new->special_type = SPECIAL_REMOTE_HOST;
  308. }
  309. else if (!strcasecmp(fname, "request_uri")) {
  310. new->special_type = SPECIAL_REQUEST_URI;
  311. }
  312. else if (!strcasecmp(fname, "request_method")) {
  313. new->special_type = SPECIAL_REQUEST_METHOD;
  314. }
  315. else if (!strcasecmp(fname, "request_protocol")) {
  316. new->special_type = SPECIAL_REQUEST_PROTOCOL;
  317. }
  318. else if (!strcasecmp(fname, "server_addr")) {
  319. new->special_type = SPECIAL_SERVER_ADDR;
  320. }
  321. else {
  322. new->special_type = SPECIAL_NOT;
  323. /* Handle fname as a regular expression.
  324. * If fname a simple header string, identify as such
  325. * (new->pnamereg = NULL) to avoid the overhead of searching
  326. * through headers_in for a regex match.
  327. */
  328. if (is_header_regex(cmd->pool, fname)) {
  329. new->pnamereg = ap_pregcomp(cmd->pool, fname,
  330. (AP_REG_EXTENDED | AP_REG_NOSUB
  331. | (icase ? AP_REG_ICASE : 0)));
  332. if (new->pnamereg == NULL)
  333. return apr_pstrcat(cmd->pool, cmd->cmd->name,
  334. "Header name regex could not be "
  335. "compiled.", NULL);
  336. }
  337. else {
  338. new->pnamereg = NULL;
  339. }
  340. }
  341. }
  342. else {
  343. new = &entries[i];
  344. }
  345. for ( ; ; ) {
  346. feature = ap_getword_conf(cmd->pool, &args);
  347. if (!*feature) {
  348. break;
  349. }
  350. beenhere++;
  351. var = ap_getword(cmd->pool, &feature, '=');
  352. if (*feature) {
  353. apr_table_setn(new->features, var, feature);
  354. }
  355. else if (*var == '!') {
  356. apr_table_setn(new->features, var + 1, "!");
  357. }
  358. else {
  359. apr_table_setn(new->features, var, "1");
  360. }
  361. }
  362. if (!beenhere) {
  363. return apr_pstrcat(cmd->pool, "Missing envariable expression for ",
  364. cmd->cmd->name, NULL);
  365. }
  366. return NULL;
  367. }
  368. static const char *add_setenvif(cmd_parms *cmd, void *mconfig,
  369. const char *args)
  370. {
  371. char *fname;
  372. /* get header name */
  373. fname = ap_getword_conf(cmd->pool, &args);
  374. if (!*fname) {
  375. return apr_pstrcat(cmd->pool, "Missing header-field name for ",
  376. cmd->cmd->name, NULL);
  377. }
  378. return add_setenvif_core(cmd, mconfig, fname, args);
  379. }
  380. /*
  381. * This routine handles the BrowserMatch* directives. It simply turns around
  382. * and feeds them, with the appropriate embellishments, to the general-purpose
  383. * command handler.
  384. */
  385. static const char *add_browser(cmd_parms *cmd, void *mconfig, const char *args)
  386. {
  387. return add_setenvif_core(cmd, mconfig, "User-Agent", args);
  388. }
  389. static const command_rec setenvif_module_cmds[] =
  390. {
  391. AP_INIT_RAW_ARGS("SetEnvIf", add_setenvif, NULL, OR_FILEINFO,
  392. "A header-name, regex and a list of variables."),
  393. AP_INIT_RAW_ARGS("SetEnvIfNoCase", add_setenvif, ICASE_MAGIC, OR_FILEINFO,
  394. "a header-name, regex and a list of variables."),
  395. AP_INIT_RAW_ARGS("BrowserMatch", add_browser, NULL, OR_FILEINFO,
  396. "A browser regex and a list of variables."),
  397. AP_INIT_RAW_ARGS("BrowserMatchNoCase", add_browser, ICASE_MAGIC,
  398. OR_FILEINFO,
  399. "A browser regex and a list of variables."),
  400. { NULL },
  401. };
  402. /*
  403. * This routine gets called at two different points in request processing:
  404. * once before the URI has been translated (during the post-read-request
  405. * phase) and once after (during the header-parse phase). We use different
  406. * config records for the two different calls to reduce overhead (by not
  407. * re-doing the server-wide settings during directory processing), and
  408. * signal which call it is by having the earlier one pass a flag to the
  409. * later one.
  410. */
  411. static int match_headers(request_rec *r)
  412. {
  413. sei_cfg_rec *sconf;
  414. sei_entry *entries;
  415. const apr_table_entry_t *elts;
  416. const char *val;
  417. apr_size_t val_len = 0;
  418. int i, j;
  419. char *last_name;
  420. ap_regmatch_t regm[AP_MAX_REG_MATCH];
  421. if (!ap_get_module_config(r->request_config, &setenvif_module)) {
  422. ap_set_module_config(r->request_config, &setenvif_module,
  423. SEI_MAGIC_HEIRLOOM);
  424. sconf = (sei_cfg_rec *) ap_get_module_config(r->server->module_config,
  425. &setenvif_module);
  426. }
  427. else {
  428. sconf = (sei_cfg_rec *) ap_get_module_config(r->per_dir_config,
  429. &setenvif_module);
  430. }
  431. entries = (sei_entry *) sconf->conditionals->elts;
  432. last_name = NULL;
  433. val = NULL;
  434. for (i = 0; i < sconf->conditionals->nelts; ++i) {
  435. sei_entry *b = &entries[i];
  436. /* Optimize the case where a bunch of directives in a row use the
  437. * same header. Remember we don't need to strcmp the two header
  438. * names because we made sure the pointers were equal during
  439. * configuration.
  440. */
  441. if (b->name != last_name) {
  442. last_name = b->name;
  443. switch (b->special_type) {
  444. case SPECIAL_REMOTE_ADDR:
  445. val = r->connection->remote_ip;
  446. break;
  447. case SPECIAL_SERVER_ADDR:
  448. val = r->connection->local_ip;
  449. break;
  450. case SPECIAL_REMOTE_HOST:
  451. val = ap_get_remote_host(r->connection, r->per_dir_config,
  452. REMOTE_NAME, NULL);
  453. break;
  454. case SPECIAL_REQUEST_URI:
  455. val = r->uri;
  456. break;
  457. case SPECIAL_REQUEST_METHOD:
  458. val = r->method;
  459. break;
  460. case SPECIAL_REQUEST_PROTOCOL:
  461. val = r->protocol;
  462. break;
  463. case SPECIAL_NOT:
  464. if (b->pnamereg) {
  465. /* Matching headers_in against a regex. Iterate through
  466. * the headers_in until we find a match or run out of
  467. * headers.
  468. */
  469. const apr_array_header_t
  470. *arr = apr_table_elts(r->headers_in);
  471. elts = (const apr_table_entry_t *) arr->elts;
  472. val = NULL;
  473. for (j = 0; j < arr->nelts; ++j) {
  474. if (!ap_regexec(b->pnamereg, elts[j].key, 0, NULL, 0)) {
  475. val = elts[j].val;
  476. }
  477. }
  478. }
  479. else {
  480. /* Not matching against a regex */
  481. val = apr_table_get(r->headers_in, b->name);
  482. if (val == NULL) {
  483. val = apr_table_get(r->subprocess_env, b->name);
  484. }
  485. }
  486. }
  487. val_len = val ? strlen(val) : 0;
  488. }
  489. /*
  490. * A NULL value indicates that the header field or special entity
  491. * wasn't present or is undefined. Represent that as an empty string
  492. * so that REs like "^$" will work and allow envariable setting
  493. * based on missing or empty field.
  494. */
  495. if (val == NULL) {
  496. val = "";
  497. val_len = 0;
  498. }
  499. if ((b->pattern && apr_strmatch(b->pattern, val, val_len)) ||
  500. (!b->pattern && !ap_regexec(b->preg, val, AP_MAX_REG_MATCH, regm,
  501. 0))) {
  502. const apr_array_header_t *arr = apr_table_elts(b->features);
  503. elts = (const apr_table_entry_t *) arr->elts;
  504. for (j = 0; j < arr->nelts; ++j) {
  505. if (*(elts[j].val) == '!') {
  506. apr_table_unset(r->subprocess_env, elts[j].key);
  507. }
  508. else {
  509. if (!b->pattern) {
  510. char *replaced = ap_pregsub(r->pool, elts[j].val, val,
  511. AP_MAX_REG_MATCH, regm);
  512. if (replaced) {
  513. apr_table_setn(r->subprocess_env, elts[j].key,
  514. replaced);
  515. }
  516. }
  517. else {
  518. apr_table_setn(r->subprocess_env, elts[j].key,
  519. elts[j].val);
  520. }
  521. }
  522. }
  523. }
  524. }
  525. return DECLINED;
  526. }
  527. static void register_hooks(apr_pool_t *p)
  528. {
  529. ap_hook_header_parser(match_headers, NULL, NULL, APR_HOOK_MIDDLE);
  530. ap_hook_post_read_request(match_headers, NULL, NULL, APR_HOOK_MIDDLE);
  531. }
  532. module AP_MODULE_DECLARE_DATA setenvif_module =
  533. {
  534. STANDARD20_MODULE_STUFF,
  535. create_setenvif_config_dir, /* dir config creater */
  536. merge_setenvif_config, /* dir merger --- default is to override */
  537. create_setenvif_config_svr, /* server config */
  538. merge_setenvif_config, /* merge server configs */
  539. setenvif_module_cmds, /* command apr_table_t */
  540. register_hooks /* register hooks */
  541. };