I am running a 3-tier mod_perl setup as advised by the Guide: Lightweight apache <-> mod_perl apache <-> db server. One of the problems with this setup is that the mod_perl apache no longer knows some details about the client request, like the client IP. I used Ask Bjoern Hansen's module proxy_add_forward, which adds a "X-Forwarded-For" header to to the request as it is forwarded to the mod_perl apache server. After I added SSL to my lightweight (but gaining!) apache, I found I needed some SSL connection information on the mod_perl server as well. I recently fell in love with mod_rewrite, and so I ported some of its capabilities to mod_headers The attached patch (cd apache_1.3.XX; patch -p1 < ProxyHeaderRewrite.patch; make install) will add two commands to mod_headers: HeaderRewrite - dynamically set headers for the client ProxyHeaderRewrite - dynamically set headers for the upstream proxy server While the original mod_headers would allow you so say: Header append Foo "burfl" Now you can say HeaderRewrite append Foo "%{ENV:BURFL}", using the full RewriteCond syntax My current lightweight apache server config looks something like: ProxyHeaderRewrite append X-Forwarded-For "%{REMOTE_ADDR}" ProxyHeaderRewrite append X-Frontend-Host "%{HTTP_HOST}" <VirtualHost _default_:443> ... SSLOptions StdEnvVars ProxyHeaderRewrite append X-SSL-Cipher "%{ENV:SSL_PROTOCOL} %{ENV:SSL_CIPHER}" </VirtualHost> Did I miss another way to do this? Is this patch useful? BTW, the Guide on server architecture: http://perl.apache.org/guide/strategy.html Ask Bjoern Hansen's module proxy_add_forward http://www.cpan.org/authors/id/ABH/mod_proxy_add_forward.c -Tim
--- apache_1.3.12.dist/src/modules/standard/mod_headers.c Wed Oct 27 09:26:53 1999 +++ apache_1.3.12/src/modules/standard/mod_headers.c Tue Jul 11 00:38:26 2000 @@ -99,9 +99,52 @@ * To remove a header: * Header unset Author * + * + * Non-standard Additions: + * + * Most code is from mod_rewrite, by + * Ralf S. Engelschall + * [EMAIL PROTECTED] + * Assembled by Tim Bishop <[EMAIL PROTECTED]> + * + * + * HeaderRewrite (set headers to client using RewriteCond syntax) + * + * Syntax: HeaderRewrite action header rewriteValue + * + * This works the same as the header directive, except that full + * mod_rewrite RewriteCond interpolation is performed on the rewriteValue + * string. See http://www.apache.org/docs/mod/mod_rewrite.html#RewriteCond + * (Of course, back-references (%N, $N) have no meaning) + * + * + * ProxyHeaderRewrite (set headers sent to upstream servers (if proxying)) + * + * Syntax: ProxyHeaderRewrite action header rewriteValue + * + * ProxyHeaderRewrite allows you to rewrite headers sent to upstream + * servers when your server is functioning as a proxy server. + * This is useful when you want to send additional header information + * to upstream servers. + * + * Bugs: Cannot rewrite the Host header with ProxyHeaderRewrite + * + * Examples: + * + * # tell upstream server the ip of the request + * ProxyHeaderRewrite append X-Forwarded-For "%{REMOTE_ADDR}" + * # tell upstream server info on SSL status + * <VirtualHost _default_:443> + * SSLOptions StdEnvVars + * ProxyHeaderRewrite append X-SSL-Cipher "%{ENV:SSL_PROTOCOL} +%{ENV:SSL_CIPHER}" + * </VirtualHost> + * # tell upstream server the virtual host used + * ProxyHeaderRewrite append X-Frontend-Host "%{HTTP:Host}" + * */ #include "httpd.h" +#include "http_log.h" #include "http_config.h" typedef enum { @@ -111,12 +154,50 @@ hdr_unset = 'u' /* unset header */ } hdr_actions; +typedef enum { + hdr_string = 's', /* header is a string */ + hdr_env_var = 'v', /* set header from env var */ + hdr_interpolate = 'i' /* header needs to be interpolated (not yet!) */ +} hdr_value_type; + +typedef enum { + hdr_client = 'c', /* modify headers for client */ + hdr_upstream = 'u' /* modify headers for upstream server */ +} hdr_header_target; + typedef struct { - hdr_actions action; - char *header; - char *value; + hdr_actions action; + char *header; + char *value; + hdr_value_type value_type; + hdr_header_target header_target; /* one of hdr_client | hdr_upstream */ } header_entry; + +/* env variable interpolation support */ +static void expand_variables_inbuffer(request_rec *r, char *buf, int buf_len); +static char *expand_variables(request_rec *r, char *str); +static char *lookup_variable(request_rec *r, char *var); +static char *lookup_header(request_rec *r, const char *name); + +#ifndef LONG_STRING_LEN +#define LONG_STRING_LEN 2048 +#endif + +/* REMOTE_NAME returns the hostname, or the dotted quad if the + * hostname lookup fails. It will force a DNS lookup according + * to the HostnameLookups setting. + * from httd_core.h + */ +#define REMOTE_NAME (1) + + /* + * The key in the r->notes table wherein we store our accumulated + * Vary values, and the one used for per-condition checks in a chain. + */ +#define VARY_KEY "headers-rewrite-Vary" +#define VARY_KEY_THIS "headers-rewrite-Vary-this" + /* * headers_conf is our per-module configuration. This is used as both * a per-dir and per-server config @@ -168,6 +249,56 @@ new = (header_entry *) ap_push_array(serverconf->headers); } + new->value_type = hdr_string; + new->header_target = hdr_client; /* modify "outgoing" (to client) headers */ + + if (!strcasecmp(action, "set")) + new->action = hdr_set; + else if (!strcasecmp(action, "add")) + new->action = hdr_add; + else if (!strcasecmp(action, "append")) + new->action = hdr_append; + else if (!strcasecmp(action, "unset")) + new->action = hdr_unset; + else + return "first argument must be add, set, append or unset."; + + if (new->action == hdr_unset) { + if (value) + return "Header unset takes two arguments"; + } + else if (!value) + return "Header requires three arguments"; + + if ((colon = strchr(hdr, ':'))) + *colon = '\0'; + + new->header = hdr; + new->value = value; + + return NULL; +} + +static const char * +header_rewrite_cmd(cmd_parms *cmd, headers_conf * dirconf, + char *action, char *hdr, char *value) +{ + header_entry *new; + server_rec *s = cmd->server; + headers_conf *serverconf = + (headers_conf *) ap_get_module_config(s->module_config, &headers_module); + char *colon; + + if (cmd->path) { + new = (header_entry *) ap_push_array(dirconf->headers); + } + else { + new = (header_entry *) ap_push_array(serverconf->headers); + } + + new->value_type = hdr_interpolate; + new->header_target = hdr_client; + if (!strcasecmp(action, "set")) new->action = hdr_set; else if (!strcasecmp(action, "add")) @@ -195,36 +326,493 @@ return NULL; } +static const char * +proxy_header_rewrite_cmd(cmd_parms *cmd, headers_conf * dirconf, + char *action, char *hdr, char *value) +{ + header_entry *new; + server_rec *s = cmd->server; + headers_conf *serverconf = + (headers_conf *) ap_get_module_config(s->module_config, &headers_module); + char *colon; + + if (cmd->path) { + new = (header_entry *) ap_push_array(dirconf->headers); + } + else { + new = (header_entry *) ap_push_array(serverconf->headers); + } + + new->value_type = hdr_interpolate; + new->header_target = hdr_upstream; /* send these headers only to upstream servers +(that we are proxying for) */ + + if (!strcasecmp(action, "set")) + new->action = hdr_set; + else if (!strcasecmp(action, "add")) + new->action = hdr_add; + else if (!strcasecmp(action, "append")) + new->action = hdr_append; + else if (!strcasecmp(action, "unset")) + new->action = hdr_unset; + else + return "first argument must be add, set, append or unset."; + + if (new->action == hdr_unset) { + if (value) + return "Header unset takes two arguments"; + } + else if (!value) + return "Header requires three arguments"; + + if ((colon = strchr(hdr, ':'))) + *colon = '\0'; + + new->header = hdr; + new->value = value; + + return NULL; +} + + static const command_rec headers_cmds[] = { {"Header", header_cmd, NULL, OR_FILEINFO, TAKE23, "an action, header and value"}, + {"HeaderRewrite", header_rewrite_cmd, NULL, OR_FILEINFO, TAKE23, + "an action, header and environment var from which to get header value"}, + {"ProxyHeaderRewrite", proxy_header_rewrite_cmd, NULL, OR_FILEINFO, TAKE23, + "an action, header and environment var from which to get header value"}, {NULL} }; static void do_headers_fixup(request_rec *r, array_header *headers) { int i; + table *target_hdrs; + const char *value; for (i = 0; i < headers->nelts; ++i) { header_entry *hdr = &((header_entry *) (headers->elts))[i]; - switch (hdr->action) { - case hdr_add: - ap_table_addn(r->headers_out, hdr->header, hdr->value); - break; - case hdr_append: - ap_table_mergen(r->headers_out, hdr->header, hdr->value); - break; - case hdr_set: - ap_table_setn(r->headers_out, hdr->header, hdr->value); - break; - case hdr_unset: - ap_table_unset(r->headers_out, hdr->header); - break; + + /* + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r, + "mod_header: processing header: %c %s: %s %c %c", + hdr->action, hdr->header, hdr->value, + hdr->value_type, hdr->header_target); + */ + + if ( hdr->header_target == hdr_upstream ) { + // skip to next header_entry if not a proxy request + if ( r->proxyreq != PROXY_PASS) { continue; } + target_hdrs = r->headers_in; + } else { + target_hdrs = r->headers_out; + } + + if ( hdr->value_type == hdr_string ) { + value = hdr->value; + + } else if ( hdr->value_type == hdr_env_var ) { + char *varname = hdr->value; + + /* first try the internal Apache notes structure */ + value = ap_table_get(r->notes, varname); + /* if (value) ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_header: found %s in notes table",varname); */ + /* second try the internal Apache env structure */ + if (value == NULL) { + value = ap_table_get(r->subprocess_env, varname); + /* if (value) ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_header: found %s in subprocess env",varname); */ + } + /* third try the external OS env */ + if (value == NULL) { + value = getenv(varname); + /* if (value) ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_header: found %s using getenv",varname); */ + } + + if (value == NULL) { + value = ""; + /* ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_header: %s not found, using ''",varname); */ + } + + } else if ( hdr->value_type == hdr_interpolate ) { + value = expand_variables(r, hdr->value); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r, + "mod_header: interpolated %s -> %s", + hdr->value,value); + } else { + /* not handled */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_header: invalid ProxyHeaderFromEnv coding!"); + continue; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r, + "mod_header: setting %s %s: %s", + ( target_hdrs == r->headers_out ) + ? "client header" : "upstream header", + hdr->header, value); + + + switch (hdr->action) { + case hdr_add: + ap_table_addn(target_hdrs, hdr->header, value); + break; + case hdr_append: + ap_table_mergen(target_hdrs, hdr->header, value); + break; + case hdr_set: + ap_table_setn(target_hdrs, hdr->header, value); + break; + case hdr_unset: + ap_table_unset(target_hdrs, hdr->header); + break; + } + + } + +} + +/* +** This section pilfered from mod_rewrite +** +** Ralf S. Engelschall +** [EMAIL PROTECTED] +** www.engelschall.com +*/ + +/* +** +-------------------------------------------------------+ +** | | +** | environment variable support +** | | +** +-------------------------------------------------------+ +*/ + + + + +static void expand_variables_inbuffer(request_rec *r, char *buf, int buf_len) +{ + char *newbuf; + newbuf = expand_variables(r, buf); + if (strcmp(newbuf, buf) != 0) { + ap_cpystrn(buf, newbuf, buf_len); + } + return; +} + +static char *expand_variables(request_rec *r, char *str) +{ + char output[MAX_STRING_LEN]; + char input[MAX_STRING_LEN]; + char *cp; + char *cp2; + char *cp3; + int expanded; + char *outp; + char *endp; + + ap_cpystrn(input, str, sizeof(input)); + output[0] = '\0'; + outp = output; + endp = output + sizeof(output); + expanded = 0; + for (cp = input; cp < input+MAX_STRING_LEN; ) { + if ((cp2 = strstr(cp, "%{")) != NULL) { + if ((cp3 = strstr(cp2, "}")) != NULL) { + *cp2 = '\0'; + outp = ap_cpystrn(outp, cp, endp - outp); + + cp2 += 2; + *cp3 = '\0'; + outp = ap_cpystrn(outp, lookup_variable(r, cp2), endp - outp); + + cp = cp3+1; + expanded = 1; + continue; + } } + outp = ap_cpystrn(outp, cp, endp - outp); + break; + } + return expanded ? ap_pstrdup(r->pool, output) : str; +} + +static char *lookup_variable(request_rec *r, char *var) +{ + const char *result; + char resultbuf[LONG_STRING_LEN]; + time_t tc; + struct tm *tm; + request_rec *rsub; +#ifndef WIN32 + struct passwd *pw; + struct group *gr; + struct stat finfo; +#endif + + result = NULL; + + /* HTTP headers */ + if (strcasecmp(var, "HTTP_USER_AGENT") == 0) { + result = lookup_header(r, "User-Agent"); + } + else if (strcasecmp(var, "HTTP_REFERER") == 0) { + result = lookup_header(r, "Referer"); + } + else if (strcasecmp(var, "HTTP_COOKIE") == 0) { + result = lookup_header(r, "Cookie"); + } + else if (strcasecmp(var, "HTTP_FORWARDED") == 0) { + result = lookup_header(r, "Forwarded"); + } + else if (strcasecmp(var, "HTTP_HOST") == 0) { + result = lookup_header(r, "Host"); + } + else if (strcasecmp(var, "HTTP_PROXY_CONNECTION") == 0) { + result = lookup_header(r, "Proxy-Connection"); + } + else if (strcasecmp(var, "HTTP_ACCEPT") == 0) { + result = lookup_header(r, "Accept"); + } + /* all other headers from which we are still not know about */ + else if (strlen(var) > 5 && strncasecmp(var, "HTTP:", 5) == 0) { + result = lookup_header(r, var+5); + } + + /* connection stuff */ + else if (strcasecmp(var, "REMOTE_ADDR") == 0) { + result = r->connection->remote_ip; + } + else if (strcasecmp(var, "REMOTE_HOST") == 0) { + result = (char *)ap_get_remote_host(r->connection, + r->per_dir_config, REMOTE_NAME); + } + else if (strcasecmp(var, "REMOTE_USER") == 0) { + result = r->connection->user; + } + else if (strcasecmp(var, "REMOTE_IDENT") == 0) { + result = (char *)ap_get_remote_logname(r); } + /* request stuff */ + else if (strcasecmp(var, "THE_REQUEST") == 0) { /* non-standard */ + result = r->the_request; + } + else if (strcasecmp(var, "REQUEST_METHOD") == 0) { + result = r->method; + } + else if (strcasecmp(var, "REQUEST_URI") == 0) { /* non-standard */ + result = r->uri; + } + else if (strcasecmp(var, "SCRIPT_FILENAME") == 0 || + strcasecmp(var, "REQUEST_FILENAME") == 0 ) { + result = r->filename; + } + else if (strcasecmp(var, "PATH_INFO") == 0) { + result = r->path_info; + } + else if (strcasecmp(var, "QUERY_STRING") == 0) { + result = r->args; + } + else if (strcasecmp(var, "AUTH_TYPE") == 0) { + result = r->connection->ap_auth_type; + } + else if (strcasecmp(var, "IS_SUBREQ") == 0) { /* non-standard */ + result = (r->main != NULL ? "true" : "false"); + } + + /* internal server stuff */ + else if (strcasecmp(var, "DOCUMENT_ROOT") == 0) { + result = (const char *) ap_document_root(r); + } + else if (strcasecmp(var, "SERVER_ADMIN") == 0) { + result = r->server->server_admin; + } + else if (strcasecmp(var, "SERVER_NAME") == 0) { + result = (const char *) ap_get_server_name(r); + } + else if (strcasecmp(var, "SERVER_ADDR") == 0) { /* non-standard */ + result = r->connection->local_ip; + } + else if (strcasecmp(var, "SERVER_PORT") == 0) { + ap_snprintf(resultbuf, sizeof(resultbuf), "%u", ap_get_server_port(r)); + result = resultbuf; + } + else if (strcasecmp(var, "SERVER_PROTOCOL") == 0) { + result = r->protocol; + } + else if (strcasecmp(var, "SERVER_SOFTWARE") == 0) { + result = ap_get_server_version(); + } + else if (strcasecmp(var, "API_VERSION") == 0) { /* non-standard */ + ap_snprintf(resultbuf, sizeof(resultbuf), "%d:%d", + MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR); + result = resultbuf; + } + + /* underlaying Unix system stuff */ + else if (strcasecmp(var, "TIME_YEAR") == 0) { + tc = time(NULL); + tm = localtime(&tc); + ap_snprintf(resultbuf, sizeof(resultbuf), "%02d%02d", + (tm->tm_year / 100) + 19, tm->tm_year % 100); + result = resultbuf; + } +#define MKTIMESTR(format, tmfield) \ + tc = time(NULL); \ + tm = localtime(&tc); \ + ap_snprintf(resultbuf, sizeof(resultbuf), format, tm->tmfield); \ + result = resultbuf; + else if (strcasecmp(var, "TIME_MON") == 0) { + MKTIMESTR("%02d", tm_mon+1) + } + else if (strcasecmp(var, "TIME_DAY") == 0) { + MKTIMESTR("%02d", tm_mday) + } + else if (strcasecmp(var, "TIME_HOUR") == 0) { + MKTIMESTR("%02d", tm_hour) + } + else if (strcasecmp(var, "TIME_MIN") == 0) { + MKTIMESTR("%02d", tm_min) + } + else if (strcasecmp(var, "TIME_SEC") == 0) { + MKTIMESTR("%02d", tm_sec) + } + else if (strcasecmp(var, "TIME_WDAY") == 0) { + MKTIMESTR("%d", tm_wday) + } + else if (strcasecmp(var, "TIME") == 0) { + tc = time(NULL); + tm = localtime(&tc); + ap_snprintf(resultbuf, sizeof(resultbuf), + "%02d%02d%02d%02d%02d%02d%02d", (tm->tm_year / 100) + 19, + (tm->tm_year % 100), tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + result = resultbuf; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r, "mod_headers: interpolate: +RESULT='%s'", result); + } + + /* all other env-variables from the parent Apache process */ + else if (strlen(var) > 4 && strncasecmp(var, "ENV:", 4) == 0) { + /* first try the internal Apache notes structure */ + result = ap_table_get(r->notes, var+4); + /* second try the internal Apache env structure */ + if (result == NULL) { + result = ap_table_get(r->subprocess_env, var+4); + } + /* third try the external OS env */ + if (result == NULL) { + result = getenv(var+4); + } + } + +#define LOOKAHEAD(subrecfunc) \ + if ( \ + /* filename is safe to use */ \ + r->filename != NULL \ + /* - and we're either not in a subrequest */ \ + && ( r->main == NULL \ + /* - or in a subrequest where paths are non-NULL... */ \ + || ( r->main->uri != NULL && r->uri != NULL \ + /* ...and sub and main paths differ */ \ + && strcmp(r->main->uri, r->uri) != 0))) { \ + /* process a file-based subrequest */ \ + rsub = subrecfunc(r->filename, r); \ + /* now recursively lookup the variable in the sub_req */ \ + result = lookup_variable(rsub, var+5); \ + /* copy it up to our scope before we destroy sub_req's pool */ \ + result = ap_pstrdup(r->pool, result); \ + /* cleanup by destroying the subrequest */ \ + ap_destroy_sub_req(rsub); \ + /* log it */ \ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r,"mod_headers: interpolate: +lookahead: path=%s var=%s -> val=%s", \ + r->filename, var+5, result); \ + /* return ourself to prevent re-pstrdup */ \ + return (char *)result; \ + } + + /* look-ahead for parameter through URI-based sub-request */ + else if (strlen(var) > 5 && strncasecmp(var, "LA-U:", 5) == 0) { + LOOKAHEAD(ap_sub_req_lookup_uri) + } + /* look-ahead for parameter through file-based sub-request */ + else if (strlen(var) > 5 && strncasecmp(var, "LA-F:", 5) == 0) { + LOOKAHEAD(ap_sub_req_lookup_file) + } + +#if !defined(WIN32) && !defined(NETWARE) + /* Win32 has a rather different view of file ownerships. + For now, just forget it */ + + /* file stuff */ + else if (strcasecmp(var, "SCRIPT_USER") == 0) { + result = "<unknown>"; + if (r->finfo.st_mode != 0) { + if ((pw = getpwuid(r->finfo.st_uid)) != NULL) { + result = pw->pw_name; + } + } + else { + if (stat(r->filename, &finfo) == 0) { + if ((pw = getpwuid(finfo.st_uid)) != NULL) { + result = pw->pw_name; + } + } + } + } + else if (strcasecmp(var, "SCRIPT_GROUP") == 0) { + result = "<unknown>"; + if (r->finfo.st_mode != 0) { + if ((gr = getgrgid(r->finfo.st_gid)) != NULL) { + result = gr->gr_name; + } + } + else { + if (stat(r->filename, &finfo) == 0) { + if ((gr = getgrgid(finfo.st_gid)) != NULL) { + result = gr->gr_name; + } + } + } + } +#endif /* ndef WIN32 && NETWARE*/ + + if (result == NULL) { + return ap_pstrdup(r->pool, ""); + } + else { + return ap_pstrdup(r->pool, result); + } +} + +static char *lookup_header(request_rec *r, const char *name) +{ + array_header *hdrs_arr; + table_entry *hdrs; + int i; + + hdrs_arr = ap_table_elts(r->headers_in); + hdrs = (table_entry *)hdrs_arr->elts; + for (i = 0; i < hdrs_arr->nelts; ++i) { + if (hdrs[i].key == NULL) { + continue; + } + if (strcasecmp(hdrs[i].key, name) == 0) { + ap_table_merge(r->notes, VARY_KEY_THIS, name); + return hdrs[i].val; + } + } + return NULL; } + + + + static int fixup_headers(request_rec *r) {