diff options
author | Colm MacCarthaigh <colm@apache.org> | 2010-01-08 11:45:43 +0000 |
---|---|---|
committer | Colm MacCarthaigh <colm@apache.org> | 2010-01-08 11:45:43 +0000 |
commit | 396931c93e46f5d130f0df7044a9c0e63fd12ebd (patch) | |
tree | 9ff5247f604985caa9581ebe4bfa6aa5164db5ce /APACHE_1_3_42/src/modules | |
parent | 7d344b579813528064a6711a91f675b7f47e4926 (diff) | |
download | httpd-1.3.tar.gz |
Tag 1.3.421.3
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/tags/1.3@897175 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'APACHE_1_3_42/src/modules')
90 files changed, 44185 insertions, 0 deletions
diff --git a/APACHE_1_3_42/src/modules/NWGNUmakefile b/APACHE_1_3_42/src/modules/NWGNUmakefile new file mode 100644 index 0000000000..3f416d3c16 --- /dev/null +++ b/APACHE_1_3_42/src/modules/NWGNUmakefile @@ -0,0 +1,29 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + standard \ + proxy \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)\NWGNUhead.inc + +# +# build this level's files + +ifeq "$(wildcard NWGNUmakefile.mak)" "NWGNUmakefile.mak" +include NWGNUmakefile.mak +endif + +# +# You can use this target if all that is needed is to copy files to the +# installation area +# +install :: nlms FORCE + diff --git a/APACHE_1_3_42/src/modules/README b/APACHE_1_3_42/src/modules/README new file mode 100644 index 0000000000..df25f051f2 --- /dev/null +++ b/APACHE_1_3_42/src/modules/README @@ -0,0 +1,34 @@ +The directory structure for this level is as follows: + +standard/ + + In this directory are the standard supported modules for + Apache. Not all are compiled by default. + +proxy/ + + This houses the code for the proxy module for Apache. + +experimental/ + + In this directory we've placed some modules which we think + provide some pretty interesting functionality, but which + are still in the early stages of development and could + evolve radically in the future. This code isn't supported + officially. + +extra/ + + This is the directory for third-party modules, such as mod_jserv. + +test/ + + This directory houses modules which test various components + of Apache. You should not compile these into a production + server. + +example/ + + This directory houses example modules, to help module authors + figure their way around the Apache API and module concept. + diff --git a/APACHE_1_3_42/src/modules/example/.indent.pro b/APACHE_1_3_42/src/modules/example/.indent.pro new file mode 100644 index 0000000000..a9fbe9f9a1 --- /dev/null +++ b/APACHE_1_3_42/src/modules/example/.indent.pro @@ -0,0 +1,54 @@ +-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1 +-TBUFF +-TFILE +-TTRANS +-TUINT4 +-T_trans +-Tallow_options_t +-Tapache_sfio +-Tarray_header +-Tbool_int +-Tbuf_area +-Tbuff_struct +-Tbuffy +-Tcmd_how +-Tcmd_parms +-Tcommand_rec +-Tcommand_struct +-Tconn_rec +-Tcore_dir_config +-Tcore_server_config +-Tdir_maker_func +-Tevent +-Tglobals_s +-Thandler_func +-Thandler_rec +-Tjoblist_s +-Tlisten_rec +-Tmerger_func +-Tmode_t +-Tmodule +-Tmodule_struct +-Tmutex +-Tn_long +-Tother_child_rec +-Toverrides_t +-Tparent_score +-Tpid_t +-Tpiped_log +-Tpool +-Trequest_rec +-Trequire_line +-Trlim_t +-Tscoreboard +-Tsemaphore +-Tserver_addr_rec +-Tserver_rec +-Tserver_rec_chain +-Tshort_score +-Ttable +-Ttable_entry +-Tthread +-Tu_wide_int +-Tvtime_t +-Twide_int diff --git a/APACHE_1_3_42/src/modules/example/Makefile.tmpl b/APACHE_1_3_42/src/modules/example/Makefile.tmpl new file mode 100644 index 0000000000..c99bdf4661 --- /dev/null +++ b/APACHE_1_3_42/src/modules/example/Makefile.tmpl @@ -0,0 +1,15 @@ + +#Dependencies + +$(OBJS) $(OBJS_PIC): Makefile + +# DO NOT REMOVE +mod_example.o: mod_example.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_main.h $(INCDIR)/http_protocol.h \ + $(INCDIR)/util_script.h diff --git a/APACHE_1_3_42/src/modules/example/README b/APACHE_1_3_42/src/modules/example/README new file mode 100644 index 0000000000..77abc097c0 --- /dev/null +++ b/APACHE_1_3_42/src/modules/example/README @@ -0,0 +1,53 @@ +README for Apache 1.2 Example Module +[April, 1997] + +The files in the src/modules/example directory under the Apache +distribution directory tree are provided as an example to those that +wish to write modules that use the Apache API. + +The main file is mod_example.c, which illustrates all the different +callback mechanisms and call syntaces. By no means does an add-on +module need to include routines for all of the callbacks - quite the +contrary! + +The example module is an actual working module. If you link it into +your server, enable the "example-handler" handler for a location, and then +browse to that location, you will see a display of some of the tracing +the example module did as the various callbacks were made. + +To include the example module in your server, follow the steps below: + + 1. Uncomment the "Module example_module" line near the bottom of + the src/Configuration file. If there isn't one, add it; it + should look like this: + + Module example_module modules/example/mod_example.o + + 2. Run the src/Configure script ("cd src; ./Configure"). This will + build the Makefile for the server itself, and update the + src/modules/Makefile for any additional modules you have + requested from beneath that subdirectory. + + 3. Make the server (run "make" in the src directory). + +To add another module of your own: + + A. mkdir src/modules/mymodule + B. cp src/modules/example/* src/modules/mymodule + C. Modify the files in the new directory + D. Follow steps [1] through [3] above, with appropriate changes. + +To activate the example module, include a block similar to the +following in your srm.conf file: + + <Location /example-info> + SetHandler example-handler + </Location> + +As an alternative, you can put the following into a .htaccess file and +then request the file "test.example" from that location: + + AddHandler example-handler .example + +After reloading/restarting your server, you should be able to browse +to this location and see the brief display mentioned earlier. diff --git a/APACHE_1_3_42/src/modules/example/mod_example.c b/APACHE_1_3_42/src/modules/example/mod_example.c new file mode 100644 index 0000000000..79ae87206a --- /dev/null +++ b/APACHE_1_3_42/src/modules/example/mod_example.c @@ -0,0 +1,1116 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Apache example module. Provide demonstrations of how modules do things. + * + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_main.h" +#include "http_protocol.h" +#include "util_script.h" + +#include <stdio.h> + +/*--------------------------------------------------------------------------*/ +/* */ +/* Data declarations. */ +/* */ +/* Here are the static cells and structure declarations private to our */ +/* module. */ +/* */ +/*--------------------------------------------------------------------------*/ + +/* + * Sample configuration record. Used for both per-directory and per-server + * configuration data. + * + * It's perfectly reasonable to have two different structures for the two + * different environments. The same command handlers will be called for + * both, though, so the handlers need to be able to tell them apart. One + * possibility is for both structures to start with an int which is zero for + * one and 1 for the other. + * + * Note that while the per-directory and per-server configuration records are + * available to most of the module handlers, they should be treated as + * READ-ONLY by all except the command and merge handlers. Sometimes handlers + * are handed a record that applies to the current location by implication or + * inheritance, and modifying it will change the rules for other locations. + */ +typedef struct excfg { + int cmode; /* Environment to which record applies (directory, + * server, or combination). + */ +#define CONFIG_MODE_SERVER 1 +#define CONFIG_MODE_DIRECTORY 2 +#define CONFIG_MODE_COMBO 3 /* Shouldn't ever happen. */ + int local; /* Boolean: "Example" directive declared here? */ + int congenital; /* Boolean: did we inherit an "Example"? */ + char *trace; /* Pointer to trace string. */ + char *loc; /* Location to which this record applies. */ +} excfg; + +/* + * Let's set up a module-local static cell to point to the accreting callback + * trace. As each API callback is made to us, we'll tack on the particulars + * to whatever we've already recorded. To avoid massive memory bloat as + * directories are walked again and again, we record the routine/environment + * the first time (non-request context only), and ignore subsequent calls for + * the same routine/environment. + */ +static const char *trace = NULL; +static table *static_calls_made = NULL; + +/* + * To avoid leaking memory from pools other than the per-request one, we + * allocate a module-private pool, and then use a sub-pool of that which gets + * freed each time we modify the trace. That way previous layers of trace + * data don't get lost. + */ +static pool *example_pool = NULL; +static pool *example_subpool = NULL; + +/* + * Declare ourselves so the configuration routines can find and know us. + * We'll fill it in at the end of the module. + */ +module MODULE_VAR_EXPORT example_module; + +/*--------------------------------------------------------------------------*/ +/* */ +/* The following pseudo-prototype declarations illustrate the parameters */ +/* passed to command handlers for the different types of directive */ +/* syntax. If an argument was specified in the directive definition */ +/* (look for "command_rec" below), it's available to the command handler */ +/* via the (void *) info field in the cmd_parms argument passed to the */ +/* handler (cmd->info for the examples below). */ +/* */ +/*--------------------------------------------------------------------------*/ + +/* + * Command handler for a NO_ARGS directive. + * + * static const char *handle_NO_ARGS(cmd_parms *cmd, void *mconfig); + */ + +/* + * Command handler for a RAW_ARGS directive. The "args" argument is the text + * of the commandline following the directive itself. + * + * static const char *handle_RAW_ARGS(cmd_parms *cmd, void *mconfig, + * const char *args); + */ + +/* + * Command handler for a FLAG directive. The single parameter is passed in + * "bool", which is either zero or not for Off or On respectively. + * + * static const char *handle_FLAG(cmd_parms *cmd, void *mconfig, int bool); + */ + +/* + * Command handler for a TAKE1 directive. The single parameter is passed in + * "word1". + * + * static const char *handle_TAKE1(cmd_parms *cmd, void *mconfig, + * char *word1); + */ + +/* + * Command handler for a TAKE2 directive. TAKE2 commands must always have + * exactly two arguments. + * + * static const char *handle_TAKE2(cmd_parms *cmd, void *mconfig, + * char *word1, char *word2); + */ + +/* + * Command handler for a TAKE3 directive. Like TAKE2, these must have exactly + * three arguments, or the parser complains and doesn't bother calling us. + * + * static const char *handle_TAKE3(cmd_parms *cmd, void *mconfig, + * char *word1, char *word2, char *word3); + */ + +/* + * Command handler for a TAKE12 directive. These can take either one or two + * arguments. + * - word2 is a NULL pointer if no second argument was specified. + * + * static const char *handle_TAKE12(cmd_parms *cmd, void *mconfig, + * char *word1, char *word2); + */ + +/* + * Command handler for a TAKE123 directive. A TAKE123 directive can be given, + * as might be expected, one, two, or three arguments. + * - word2 is a NULL pointer if no second argument was specified. + * - word3 is a NULL pointer if no third argument was specified. + * + * static const char *handle_TAKE123(cmd_parms *cmd, void *mconfig, + * char *word1, char *word2, char *word3); + */ + +/* + * Command handler for a TAKE13 directive. Either one or three arguments are + * permitted - no two-parameters-only syntax is allowed. + * - word2 and word3 are NULL pointers if only one argument was specified. + * + * static const char *handle_TAKE13(cmd_parms *cmd, void *mconfig, + * char *word1, char *word2, char *word3); + */ + +/* + * Command handler for a TAKE23 directive. At least two and as many as three + * arguments must be specified. + * - word3 is a NULL pointer if no third argument was specified. + * + * static const char *handle_TAKE23(cmd_parms *cmd, void *mconfig, + * char *word1, char *word2, char *word3); + */ + +/* + * Command handler for a ITERATE directive. + * - Handler is called once for each of n arguments given to the directive. + * - word1 points to each argument in turn. + * + * static const char *handle_ITERATE(cmd_parms *cmd, void *mconfig, + * char *word1); + */ + +/* + * Command handler for a ITERATE2 directive. + * - Handler is called once for each of the second and subsequent arguments + * given to the directive. + * - word1 is the same for each call for a particular directive instance (the + * first argument). + * - word2 points to each of the second and subsequent arguments in turn. + * + * static const char *handle_ITERATE2(cmd_parms *cmd, void *mconfig, + * char *word1, char *word2); + */ + +/*--------------------------------------------------------------------------*/ +/* */ +/* These routines are strictly internal to this module, and support its */ +/* operation. They are not referenced by any external portion of the */ +/* server. */ +/* */ +/*--------------------------------------------------------------------------*/ + +/* + * Locate our directory configuration record for the current request. + */ +static excfg *our_dconfig(request_rec *r) +{ + + return (excfg *) ap_get_module_config(r->per_dir_config, &example_module); +} + +#if 0 +/* + * Locate our server configuration record for the specified server. + */ +static excfg *our_sconfig(server_rec *s) +{ + + return (excfg *) ap_get_module_config(s->module_config, &example_module); +} + +/* + * Likewise for our configuration record for the specified request. + */ +static excfg *our_rconfig(request_rec *r) +{ + + return (excfg *) ap_get_module_config(r->request_config, &example_module); +} +#endif + +/* + * This routine sets up some module-wide cells if they haven't been already. + */ +static void setup_module_cells() +{ + /* + * If we haven't already allocated our module-private pool, do so now. + */ + if (example_pool == NULL) { + example_pool = ap_make_sub_pool(NULL); + }; + /* + * Likewise for the table of routine/environment pairs we visit outside of + * request context. + */ + if (static_calls_made == NULL) { + static_calls_made = ap_make_table(example_pool, 16); + }; +} + +/* + * This routine is used to add a trace of a callback to the list. We're + * passed the server record (if available), the request record (if available), + * a pointer to our private configuration record (if available) for the + * environment to which the callback is supposed to apply, and some text. We + * turn this into a textual representation and add it to the tail of the list. + * The list can be displayed by the example_handler() routine. + * + * If the call occurs within a request context (i.e., we're passed a request + * record), we put the trace into the request pool and attach it to the + * request via the notes mechanism. Otherwise, the trace gets added + * to the static (non-request-specific) list. + * + * Note that the r->notes table is only for storing strings; if you need to + * maintain per-request data of any other type, you need to use another + * mechanism. + */ + +#define TRACE_NOTE "example-trace" + +static void trace_add(server_rec *s, request_rec *r, excfg *mconfig, + const char *note) +{ + + const char *sofar; + char *addon; + char *where; + pool *p; + const char *trace_copy; + + /* + * Make sure our pools and tables are set up - we need 'em. + */ + setup_module_cells(); + /* + * Now, if we're in request-context, we use the request pool. + */ + if (r != NULL) { + p = r->pool; + if ((trace_copy = ap_table_get(r->notes, TRACE_NOTE)) == NULL) { + trace_copy = ""; + } + } + else { + /* + * We're not in request context, so the trace gets attached to our + * module-wide pool. We do the create/destroy every time we're called + * in non-request context; this avoids leaking memory in some of + * the subsequent calls that allocate memory only once (such as the + * key formation below). + * + * Make a new sub-pool and copy any existing trace to it. Point the + * trace cell at the copied value. + */ + p = ap_make_sub_pool(example_pool); + if (trace != NULL) { + trace = ap_pstrdup(p, trace); + } + /* + * Now, if we have a sub-pool from before, nuke it and replace with + * the one we just allocated. + */ + if (example_subpool != NULL) { + ap_destroy_pool(example_subpool); + } + example_subpool = p; + trace_copy = trace; + } + /* + * If we weren't passed a configuration record, we can't figure out to + * what location this call applies. This only happens for co-routines + * that don't operate in a particular directory or server context. If we + * got a valid record, extract the location (directory or server) to which + * it applies. + */ + where = (mconfig != NULL) ? mconfig->loc : "nowhere"; + where = (where != NULL) ? where : ""; + /* + * Now, if we're not in request context, see if we've been called with + * this particular combination before. The table is allocated in the + * module's private pool, which doesn't get destroyed. + */ + if (r == NULL) { + char *key; + + key = ap_pstrcat(p, note, ":", where, NULL); + if (ap_table_get(static_calls_made, key) != NULL) { + /* + * Been here, done this. + */ + return; + } + else { + /* + * First time for this combination of routine and environment - + * log it so we don't do it again. + */ + ap_table_set(static_calls_made, key, "been here"); + } + } + addon = ap_pstrcat(p, " <LI>\n", " <DL>\n", " <DT><SAMP>", + note, "</SAMP>\n", " </DT>\n", " <DD><SAMP>[", + where, "]</SAMP>\n", " </DD>\n", " </DL>\n", + " </LI>\n", NULL); + sofar = (trace_copy == NULL) ? "" : trace_copy; + trace_copy = ap_pstrcat(p, sofar, addon, NULL); + if (r != NULL) { + ap_table_set(r->notes, TRACE_NOTE, trace_copy); + } + else { + trace = trace_copy; + } + /* + * You *could* change the following if you wanted to see the calling + * sequence reported in the server's error_log, but beware - almost all of + * these co-routines are called for every single request, and the impact + * on the size (and readability) of the error_log is considerable. + */ +#define EXAMPLE_LOG_EACH 0 +#if EXAMPLE_LOG_EACH + if (s != NULL) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, s, "mod_example: %s", note); + } +#endif +} + +/*--------------------------------------------------------------------------*/ +/* We prototyped the various syntax for command handlers (routines that */ +/* are called when the configuration parser detects a directive declared */ +/* by our module) earlier. Now we actually declare a "real" routine that */ +/* will be invoked by the parser when our "real" directive is */ +/* encountered. */ +/* */ +/* If a command handler encounters a problem processing the directive, it */ +/* signals this fact by returning a non-NULL pointer to a string */ +/* describing the problem. */ +/* */ +/* The magic return value DECLINE_CMD is used to deal with directives */ +/* that might be declared by multiple modules. If the command handler */ +/* returns NULL, the directive was processed; if it returns DECLINE_CMD, */ +/* the next module (if any) that declares the directive is given a chance */ +/* at it. If it returns any other value, it's treated as the text of an */ +/* error message. */ +/*--------------------------------------------------------------------------*/ +/* + * Command handler for the NO_ARGS "Example" directive. All we do is mark the + * call in the trace log, and flag the applicability of the directive to the + * current location in that location's configuration record. + */ +static const char *cmd_example(cmd_parms *cmd, void *mconfig) +{ + + excfg *cfg = (excfg *) mconfig; + + /* + * "Example Wuz Here" + */ + cfg->local = 1; + trace_add(cmd->server, NULL, cfg, "cmd_example()"); + return NULL; +} + +/*--------------------------------------------------------------------------*/ +/* */ +/* Now we declare our content handlers, which are invoked when the server */ +/* encounters a document which our module is supposed to have a chance to */ +/* see. (See mod_mime's SetHandler and AddHandler directives, and the */ +/* mod_info and mod_status examples, for more details.) */ +/* */ +/* Since content handlers are dumping data directly into the connexion */ +/* (using the r*() routines, such as rputs() and rprintf()) without */ +/* intervention by other parts of the server, they need to make */ +/* sure any accumulated HTTP headers are sent first. This is done by */ +/* calling send_http_header(). Otherwise, no header will be sent at all, */ +/* and the output sent to the client will actually be HTTP-uncompliant. */ +/*--------------------------------------------------------------------------*/ +/* + * Sample content handler. All this does is display the call list that has + * been built up so far. + * + * The return value instructs the caller concerning what happened and what to + * do next: + * OK ("we did our thing") + * DECLINED ("this isn't something with which we want to get involved") + * HTTP_mumble ("an error status should be reported") + */ +static int example_handler(request_rec *r) +{ + + excfg *dcfg; + + dcfg = our_dconfig(r); + trace_add(r->server, r, dcfg, "example_handler()"); + /* + * We're about to start sending content, so we need to force the HTTP + * headers to be sent at this point. Otherwise, no headers will be sent + * at all. We can set any we like first, of course. **NOTE** Here's + * where you set the "Content-type" header, and you do so by putting it in + * r->content_type, *not* r->headers_out("Content-type"). If you don't + * set it, it will be filled in with the server's default type (typically + * "text/plain"). You *must* also ensure that r->content_type is lower + * case. + * + * We also need to start a timer so the server can know if the connexion + * is broken. + */ + r->content_type = "text/html"; + + ap_soft_timeout("send example call trace", r); + ap_send_http_header(r); +#ifdef CHARSET_EBCDIC + /* Server-generated response, converted */ + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1); +#endif + + /* + * If we're only supposed to send header information (HEAD request), we're + * already there. + */ + if (r->header_only) { + ap_kill_timeout(r); + return OK; + } + + /* + * Now send our actual output. Since we tagged this as being + * "text/html", we need to embed any HTML. + */ + ap_rputs(DOCTYPE_HTML_3_2, r); + ap_rputs("<HTML>\n", r); + ap_rputs(" <HEAD>\n", r); + ap_rputs(" <TITLE>mod_example Module Content-Handler Output\n", r); + ap_rputs(" </TITLE>\n", r); + ap_rputs(" </HEAD>\n", r); + ap_rputs(" <BODY>\n", r); + ap_rputs(" <H1><SAMP>mod_example</SAMP> Module Content-Handler Output\n", r); + ap_rputs(" </H1>\n", r); + ap_rputs(" <P>\n", r); + ap_rprintf(r, " Apache HTTP Server version: \"%s\"\n", + ap_get_server_version()); + ap_rputs(" <BR>\n", r); + ap_rprintf(r, " Server built: \"%s\"\n", ap_get_server_built()); + ap_rputs(" </P>\n", r);; + ap_rputs(" <P>\n", r); + ap_rputs(" The format for the callback trace is:\n", r); + ap_rputs(" </P>\n", r); + ap_rputs(" <DL>\n", r); + ap_rputs(" <DT><EM>n</EM>.<SAMP><routine-name>", r); + ap_rputs("(<routine-data>)</SAMP>\n", r); + ap_rputs(" </DT>\n", r); + ap_rputs(" <DD><SAMP>[<applies-to>]</SAMP>\n", r); + ap_rputs(" </DD>\n", r); + ap_rputs(" </DL>\n", r); + ap_rputs(" <P>\n", r); + ap_rputs(" The <SAMP><routine-data></SAMP> is supplied by\n", r); + ap_rputs(" the routine when it requests the trace,\n", r); + ap_rputs(" and the <SAMP><applies-to></SAMP> is extracted\n", r); + ap_rputs(" from the configuration record at the time of the trace.\n", r); + ap_rputs(" <STRONG>SVR()</STRONG> indicates a server environment\n", r); + ap_rputs(" (blank means the main or default server, otherwise it's\n", r); + ap_rputs(" the name of the VirtualHost); <STRONG>DIR()</STRONG>\n", r); + ap_rputs(" indicates a location in the URL or filesystem\n", r); + ap_rputs(" namespace.\n", r); + ap_rputs(" </P>\n", r); + ap_rprintf(r, " <H2>Static callbacks so far:</H2>\n <OL>\n%s </OL>\n", + trace); + ap_rputs(" <H2>Request-specific callbacks so far:</H2>\n", r); + ap_rprintf(r, " <OL>\n%s </OL>\n", ap_table_get(r->notes, TRACE_NOTE)); + ap_rputs(" <H2>Environment for <EM>this</EM> call:</H2>\n", r); + ap_rputs(" <UL>\n", r); + ap_rprintf(r, " <LI>Applies-to: <SAMP>%s</SAMP>\n </LI>\n", dcfg->loc); + ap_rprintf(r, " <LI>\"Example\" directive declared here: %s\n </LI>\n", + (dcfg->local ? "YES" : "NO")); + ap_rprintf(r, " <LI>\"Example\" inherited: %s\n </LI>\n", + (dcfg->congenital ? "YES" : "NO")); + ap_rputs(" </UL>\n", r); + ap_rputs(" </BODY>\n", r); + ap_rputs("</HTML>\n", r); + /* + * We're all done, so cancel the timeout we set. Since this is probably + * the end of the request we *could* assume this would be done during + * post-processing - but it's possible that another handler might be + * called and inherit our outstanding timer. Not good; to each its own. + */ + ap_kill_timeout(r); + /* + * We did what we wanted to do, so tell the rest of the server we + * succeeded. + */ + return OK; +} + +/*--------------------------------------------------------------------------*/ +/* */ +/* Now let's declare routines for each of the callback phase in order. */ +/* (That's the order in which they're listed in the callback list, *not */ +/* the order in which the server calls them! See the command_rec */ +/* declaration near the bottom of this file.) Note that these may be */ +/* called for situations that don't relate primarily to our function - in */ +/* other words, the fixup handler shouldn't assume that the request has */ +/* to do with "example" stuff. */ +/* */ +/* With the exception of the content handler, all of our routines will be */ +/* called for each request, unless an earlier handler from another module */ +/* aborted the sequence. */ +/* */ +/* Handlers that are declared as "int" can return the following: */ +/* */ +/* OK Handler accepted the request and did its thing with it. */ +/* DECLINED Handler took no action. */ +/* HTTP_mumble Handler looked at request and found it wanting. */ +/* */ +/* What the server does after calling a module handler depends upon the */ +/* handler's return value. In all cases, if the handler returns */ +/* DECLINED, the server will continue to the next module with an handler */ +/* for the current phase. However, if the handler return a non-OK, */ +/* non-DECLINED status, the server aborts the request right there. If */ +/* the handler returns OK, the server's next action is phase-specific; */ +/* see the individual handler comments below for details. */ +/* */ +/*--------------------------------------------------------------------------*/ +/* + * This function is called during server initialisation. Any information + * that needs to be recorded must be in static cells, since there's no + * configuration record. + * + * There is no return value. + */ + +/* + * All our module-initialiser does is add its trace to the log. + */ +static void example_init(server_rec *s, pool *p) +{ + + char *note; + char *sname = s->server_hostname; + + /* + * Set up any module cells that ought to be initialised. + */ + setup_module_cells(); + /* + * The arbitrary text we add to our trace entry indicates for which server + * we're being called. + */ + sname = (sname != NULL) ? sname : ""; + note = ap_pstrcat(p, "example_init(", sname, ")", NULL); + trace_add(s, NULL, NULL, note); +} + +/* + * This function is called during server initialisation when an heavy-weight + * process (such as a child) is being initialised. As with the + * module-initialisation function, any information that needs to be recorded + * must be in static cells, since there's no configuration record. + * + * There is no return value. + */ + +/* + * All our process-initialiser does is add its trace to the log. + */ +static void example_child_init(server_rec *s, pool *p) +{ + + char *note; + char *sname = s->server_hostname; + + /* + * Set up any module cells that ought to be initialised. + */ + setup_module_cells(); + /* + * The arbitrary text we add to our trace entry indicates for which server + * we're being called. + */ + sname = (sname != NULL) ? sname : ""; + note = ap_pstrcat(p, "example_child_init(", sname, ")", NULL); + trace_add(s, NULL, NULL, note); +} + +/* + * This function is called when an heavy-weight process (such as a child) is + * being run down or destroyed. As with the child-initialisation function, + * any information that needs to be recorded must be in static cells, since + * there's no configuration record. + * + * There is no return value. + */ + +/* + * All our process-death routine does is add its trace to the log. + */ +static void example_child_exit(server_rec *s, pool *p) +{ + + char *note; + char *sname = s->server_hostname; + + /* + * The arbitrary text we add to our trace entry indicates for which server + * we're being called. + */ + sname = (sname != NULL) ? sname : ""; + note = ap_pstrcat(p, "example_child_exit(", sname, ")", NULL); + trace_add(s, NULL, NULL, note); +} + +/* + * This function gets called to create a per-directory configuration + * record. This will be called for the "default" server environment, and for + * each directory for which the parser finds any of our directives applicable. + * If a directory doesn't have any of our directives involved (i.e., they + * aren't in the .htaccess file, or a <Location>, <Directory>, or related + * block), this routine will *not* be called - the configuration for the + * closest ancestor is used. + * + * The return value is a pointer to the created module-specific + * structure. + */ +static void *example_create_dir_config(pool *p, char *dirspec) +{ + + excfg *cfg; + char *dname = dirspec; + + /* + * Allocate the space for our record from the pool supplied. + */ + cfg = (excfg *) ap_pcalloc(p, sizeof(excfg)); + /* + * Now fill in the defaults. If there are any `parent' configuration + * records, they'll get merged as part of a separate callback. + */ + cfg->local = 0; + cfg->congenital = 0; + cfg->cmode = CONFIG_MODE_DIRECTORY; + /* + * Finally, add our trace to the callback list. + */ + dname = (dname != NULL) ? dname : ""; + cfg->loc = ap_pstrcat(p, "DIR(", dname, ")", NULL); + trace_add(NULL, NULL, cfg, "example_create_dir_config()"); + return (void *) cfg; +} + +/* + * This function gets called to merge two per-directory configuration + * records. This is typically done to cope with things like .htaccess files + * or <Location> directives for directories that are beneath one for which a + * configuration record was already created. The routine has the + * responsibility of creating a new record and merging the contents of the + * other two into it appropriately. If the module doesn't declare a merge + * routine, the record for the closest ancestor location (that has one) is + * used exclusively. + * + * The routine MUST NOT modify any of its arguments! + * + * The return value is a pointer to the created module-specific structure + * containing the merged values. + */ +static void *example_merge_dir_config(pool *p, void *parent_conf, + void *newloc_conf) +{ + + excfg *merged_config = (excfg *) ap_pcalloc(p, sizeof(excfg)); + excfg *pconf = (excfg *) parent_conf; + excfg *nconf = (excfg *) newloc_conf; + char *note; + + /* + * Some things get copied directly from the more-specific record, rather + * than getting merged. + */ + merged_config->local = nconf->local; + merged_config->loc = ap_pstrdup(p, nconf->loc); + /* + * Others, like the setting of the `congenital' flag, get ORed in. The + * setting of that particular flag, for instance, is TRUE if it was ever + * true anywhere in the upstream configuration. + */ + merged_config->congenital = (pconf->congenital | pconf->local); + /* + * If we're merging records for two different types of environment (server + * and directory), mark the new record appropriately. Otherwise, inherit + * the current value. + */ + merged_config->cmode = + (pconf->cmode == nconf->cmode) ? pconf->cmode : CONFIG_MODE_COMBO; + /* + * Now just record our being called in the trace list. Include the + * locations we were asked to merge. + */ + note = ap_pstrcat(p, "example_merge_dir_config(\"", pconf->loc, "\",\"", + nconf->loc, "\")", NULL); + trace_add(NULL, NULL, merged_config, note); + return (void *) merged_config; +} + +/* + * This function gets called to create a per-server configuration + * record. It will always be called for the "default" server. + * + * The return value is a pointer to the created module-specific + * structure. + */ +static void *example_create_server_config(pool *p, server_rec *s) +{ + + excfg *cfg; + char *sname = s->server_hostname; + + /* + * As with the example_create_dir_config() reoutine, we allocate and fill + * in an empty record. + */ + cfg = (excfg *) ap_pcalloc(p, sizeof(excfg)); + cfg->local = 0; + cfg->congenital = 0; + cfg->cmode = CONFIG_MODE_SERVER; + /* + * Note that we were called in the trace list. + */ + sname = (sname != NULL) ? sname : ""; + cfg->loc = ap_pstrcat(p, "SVR(", sname, ")", NULL); + trace_add(s, NULL, cfg, "example_create_server_config()"); + return (void *) cfg; +} + +/* + * This function gets called to merge two per-server configuration + * records. This is typically done to cope with things like virtual hosts and + * the default server configuration The routine has the responsibility of + * creating a new record and merging the contents of the other two into it + * appropriately. If the module doesn't declare a merge routine, the more + * specific existing record is used exclusively. + * + * The routine MUST NOT modify any of its arguments! + * + * The return value is a pointer to the created module-specific structure + * containing the merged values. + */ +static void *example_merge_server_config(pool *p, void *server1_conf, + void *server2_conf) +{ + + excfg *merged_config = (excfg *) ap_pcalloc(p, sizeof(excfg)); + excfg *s1conf = (excfg *) server1_conf; + excfg *s2conf = (excfg *) server2_conf; + char *note; + + /* + * Our inheritance rules are our own, and part of our module's semantics. + * Basically, just note whence we came. + */ + merged_config->cmode = + (s1conf->cmode == s2conf->cmode) ? s1conf->cmode : CONFIG_MODE_COMBO; + merged_config->local = s2conf->local; + merged_config->congenital = (s1conf->congenital | s1conf->local); + merged_config->loc = ap_pstrdup(p, s2conf->loc); + /* + * Trace our call, including what we were asked to merge. + */ + note = ap_pstrcat(p, "example_merge_server_config(\"", s1conf->loc, "\",\"", + s2conf->loc, "\")", NULL); + trace_add(NULL, NULL, merged_config, note); + return (void *) merged_config; +} + +/* + * This routine is called after the request has been read but before any other + * phases have been processed. This allows us to make decisions based upon + * the input header fields. + * + * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no + * further modules are called for this phase. + */ +static int example_post_read_request(request_rec *r) +{ + + excfg *cfg; + + cfg = our_dconfig(r); + /* + * We don't actually *do* anything here, except note the fact that we were + * called. + */ + trace_add(r->server, r, cfg, "example_post_read_request()"); + return DECLINED; +} + +/* + * This routine gives our module an opportunity to translate the URI into an + * actual filename. If we don't do anything special, the server's default + * rules (Alias directives and the like) will continue to be followed. + * + * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no + * further modules are called for this phase. + */ +static int example_translate_handler(request_rec *r) +{ + + excfg *cfg; + + cfg = our_dconfig(r); + /* + * We don't actually *do* anything here, except note the fact that we were + * called. + */ + trace_add(r->server, r, cfg, "example_translate_handler()"); + return DECLINED; +} + +/* + * This routine is called to check the authentication information sent with + * the request (such as looking up the user in a database and verifying that + * the [encrypted] password sent matches the one in the database). + * + * The return value is OK, DECLINED, or some HTTP_mumble error (typically + * HTTP_UNAUTHORIZED). If we return OK, no other modules are given a chance + * at the request during this phase. + */ +static int example_check_user_id(request_rec *r) +{ + + excfg *cfg; + + cfg = our_dconfig(r); + /* + * Don't do anything except log the call. + */ + trace_add(r->server, r, cfg, "example_check_user_id()"); + return DECLINED; +} + +/* + * This routine is called to check to see if the resource being requested + * requires authorisation. + * + * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no + * other modules are called during this phase. + * + * If *all* modules return DECLINED, the request is aborted with a server + * error. + */ +static int example_auth_checker(request_rec *r) +{ + + excfg *cfg; + + cfg = our_dconfig(r); + /* + * Log the call and return OK, or access will be denied (even though we + * didn't actually do anything). + */ + trace_add(r->server, r, cfg, "example_auth_checker()"); + return DECLINED; +} + +/* + * This routine is called to check for any module-specific restrictions placed + * upon the requested resource. (See the mod_access module for an example.) + * + * The return value is OK, DECLINED, or HTTP_mumble. All modules with an + * handler for this phase are called regardless of whether their predecessors + * return OK or DECLINED. The first one to return any other status, however, + * will abort the sequence (and the request) as usual. + */ +static int example_access_checker(request_rec *r) +{ + + excfg *cfg; + + cfg = our_dconfig(r); + trace_add(r->server, r, cfg, "example_access_checker()"); + return DECLINED; +} + +/* + * This routine is called to determine and/or set the various document type + * information bits, like Content-type (via r->content_type), language, et + * cetera. + * + * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no + * further modules are given a chance at the request for this phase. + */ +static int example_type_checker(request_rec *r) +{ + + excfg *cfg; + + cfg = our_dconfig(r); + /* + * Log the call, but don't do anything else - and report truthfully that + * we didn't do anything. + */ + trace_add(r->server, r, cfg, "example_type_checker()"); + return DECLINED; +} + +/* + * This routine is called to perform any module-specific fixing of header + * fields, et cetera. It is invoked just before any content-handler. + * + * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, the + * server will still call any remaining modules with an handler for this + * phase. + */ +static int example_fixer_upper(request_rec *r) +{ + + excfg *cfg; + + cfg = our_dconfig(r); + /* + * Log the call and exit. + */ + trace_add(r->server, r, cfg, "example_fixer_upper()"); + return OK; +} + +/* + * This routine is called to perform any module-specific logging activities + * over and above the normal server things. + * + * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, any + * remaining modules with an handler for this phase will still be called. + */ +static int example_logger(request_rec *r) +{ + + excfg *cfg; + + cfg = our_dconfig(r); + trace_add(r->server, r, cfg, "example_logger()"); + return DECLINED; +} + +/* + * This routine is called to give the module a chance to look at the request + * headers and take any appropriate specific actions early in the processing + * sequence. + * + * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, any + * remaining modules with handlers for this phase will still be called. + */ +static int example_header_parser(request_rec *r) +{ + + excfg *cfg; + + cfg = our_dconfig(r); + trace_add(r->server, r, cfg, "example_header_parser()"); + return DECLINED; +} + +/*--------------------------------------------------------------------------*/ +/* */ +/* All of the routines have been declared now. Here's the list of */ +/* directives specific to our module, and information about where they */ +/* may appear and how the command parser should pass them to us for */ +/* processing. Note that care must be taken to ensure that there are NO */ +/* collisions of directive names between modules. */ +/* */ +/*--------------------------------------------------------------------------*/ +/* + * List of directives specific to our module. + */ +static const command_rec example_cmds[] = +{ + { + "Example", /* directive name */ + cmd_example, /* config action routine */ + NULL, /* argument to include in call */ + OR_OPTIONS, /* where available */ + NO_ARGS, /* arguments */ + "Example directive - no arguments" + /* directive description */ + }, + {NULL} +}; + +/*--------------------------------------------------------------------------*/ +/* */ +/* Now the list of content handlers available from this module. */ +/* */ +/*--------------------------------------------------------------------------*/ +/* + * List of content handlers our module supplies. Each handler is defined by + * two parts: a name by which it can be referenced (such as by + * {Add,Set}Handler), and the actual routine name. The list is terminated by + * a NULL block, since it can be of variable length. + * + * Note that content-handlers are invoked on a most-specific to least-specific + * basis; that is, a handler that is declared for "text/plain" will be + * invoked before one that was declared for "text / *". Note also that + * if a content-handler returns anything except DECLINED, no other + * content-handlers will be called. + */ +static const handler_rec example_handlers[] = +{ + {"example-handler", example_handler}, + {NULL} +}; + +/*--------------------------------------------------------------------------*/ +/* */ +/* Finally, the list of callback routines and data structures that */ +/* provide the hooks into our module from the other parts of the server. */ +/* */ +/*--------------------------------------------------------------------------*/ +/* + * Module definition for configuration. If a particular callback is not + * needed, replace its routine name below with the word NULL. + * + * The number in brackets indicates the order in which the routine is called + * during request processing. Note that not all routines are necessarily + * called (such as if a resource doesn't have access restrictions). + */ +module MODULE_VAR_EXPORT example_module = +{ + STANDARD_MODULE_STUFF, + example_init, /* module initializer */ + example_create_dir_config, /* per-directory config creator */ + example_merge_dir_config, /* dir config merger */ + example_create_server_config, /* server config creator */ + example_merge_server_config, /* server config merger */ + example_cmds, /* command table */ + example_handlers, /* [9] list of handlers */ + example_translate_handler, /* [2] filename-to-URI translation */ + example_check_user_id, /* [5] check/validate user_id */ + example_auth_checker, /* [6] check user_id is valid *here* */ + example_access_checker, /* [4] check access by host address */ + example_type_checker, /* [7] MIME type checker/setter */ + example_fixer_upper, /* [8] fixups */ + example_logger, /* [10] logger */ +#if MODULE_MAGIC_NUMBER >= 19970103 + example_header_parser, /* [3] header parser */ +#endif +#if MODULE_MAGIC_NUMBER >= 19970719 + example_child_init, /* process initializer */ +#endif +#if MODULE_MAGIC_NUMBER >= 19970728 + example_child_exit, /* process exit/cleanup */ +#endif +#if MODULE_MAGIC_NUMBER >= 19970902 + example_post_read_request /* [1] post read_request handling */ +#endif +}; diff --git a/APACHE_1_3_42/src/modules/experimental/.indent.pro b/APACHE_1_3_42/src/modules/experimental/.indent.pro new file mode 100644 index 0000000000..a9fbe9f9a1 --- /dev/null +++ b/APACHE_1_3_42/src/modules/experimental/.indent.pro @@ -0,0 +1,54 @@ +-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1 +-TBUFF +-TFILE +-TTRANS +-TUINT4 +-T_trans +-Tallow_options_t +-Tapache_sfio +-Tarray_header +-Tbool_int +-Tbuf_area +-Tbuff_struct +-Tbuffy +-Tcmd_how +-Tcmd_parms +-Tcommand_rec +-Tcommand_struct +-Tconn_rec +-Tcore_dir_config +-Tcore_server_config +-Tdir_maker_func +-Tevent +-Tglobals_s +-Thandler_func +-Thandler_rec +-Tjoblist_s +-Tlisten_rec +-Tmerger_func +-Tmode_t +-Tmodule +-Tmodule_struct +-Tmutex +-Tn_long +-Tother_child_rec +-Toverrides_t +-Tparent_score +-Tpid_t +-Tpiped_log +-Tpool +-Trequest_rec +-Trequire_line +-Trlim_t +-Tscoreboard +-Tsemaphore +-Tserver_addr_rec +-Tserver_rec +-Tserver_rec_chain +-Tshort_score +-Ttable +-Ttable_entry +-Tthread +-Tu_wide_int +-Tvtime_t +-Twide_int diff --git a/APACHE_1_3_42/src/modules/experimental/Makefile.tmpl b/APACHE_1_3_42/src/modules/experimental/Makefile.tmpl new file mode 100644 index 0000000000..6760311f15 --- /dev/null +++ b/APACHE_1_3_42/src/modules/experimental/Makefile.tmpl @@ -0,0 +1,23 @@ + +#Dependencies + +$(OBJS) $(OBJS_PIC): Makefile + +# DO NOT REMOVE +mod_mmap_static.o: mod_mmap_static.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_log.h $(INCDIR)/http_protocol.h \ + $(INCDIR)/http_request.h $(INCDIR)/http_core.h +mod_auth_digest.o: mod_auth_digest.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/util_md5.h \ + $(INCDIR)/ap_md5.h diff --git a/APACHE_1_3_42/src/modules/experimental/mod_auth_digest.c b/APACHE_1_3_42/src/modules/experimental/mod_auth_digest.c new file mode 100644 index 0000000000..a43b68bf52 --- /dev/null +++ b/APACHE_1_3_42/src/modules/experimental/mod_auth_digest.c @@ -0,0 +1,2007 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_auth_digest: MD5 digest authentication + * + * Originally by Alexei Kosut <akosut@nueva.pvt.k12.ca.us> + * Updated to RFC-2617 by Ronald Tschalär <ronald@innovation.ch> + * based on mod_auth, by Rob McCool and Robert S. Thau + * + * This module an updated version of modules/standard/mod_digest.c + * However, it has not been extensively tested yet, and is therefore + * currently marked experimental. Send problem reports to me + * (ronald@innovation.ch) + * + * Requires either /dev/random (or equivalent) or the truerand library, + * available for instance from + * ftp://research.att.com/dist/mab/librand.shar + * + * Open Issues: + * - qop=auth-int (when streams and trailer support available) + * - nonce-format configurability + * - Proxy-Authorization-Info header is set by this module, but is + * currently ignored by mod_proxy (needs patch to mod_proxy) + * - generating the secret takes a while (~ 8 seconds) if using the + * truerand library + * - The source of the secret should be run-time directive (with server + * scope: RSRC_CONF). However, that could be tricky when trying to + * choose truerand vs. file... + * - shared-mem not completely tested yet. Seems to work ok for me, + * but... (definitely won't work on Windoze) + * - Sharing a realm among multiple servers has following problems: + * o Server name and port can't be included in nonce-hash + * (we need two nonce formats, which must be configured explicitly) + * o Nonce-count check can't be for equal, or then nonce-count checking + * must be disabled. What we could do is the following: + * (expected < received) ? set expected = received : issue error + * The only problem is that it allows replay attacks when somebody + * captures a packet sent to one server and sends it to another + * one. Should we add "AuthDigestNcCheck Strict"? + */ + +/* The section for the Configure script: + * MODULE-DEFINITION-START + * Name: digest_auth_module + * ConfigStart + + RULE_DEV_RANDOM=`./helpers/CutRule DEV_RANDOM $file` + if [ "$RULE_DEV_RANDOM" = "default" ]; then + if [ -r "/dev/random" ]; then + RULE_DEV_RANDOM="/dev/random" + elif [ -r "/dev/urandom" ]; then + RULE_DEV_RANDOM="/dev/urandom" + else + RULE_DEV_RANDOM="truerand" + if helpers/TestCompile func randbyte; then + : + elif helpers/TestCompile lib rand randbyte; then + : + else + echo " (mod_auth_digest) truerand library missing!" + echo "** This will most probably defeat successful compilation." + echo "** See Rule DEV_RANDOM in src/Configuration.tmpl for more information." + fi + fi + fi + if [ "$RULE_DEV_RANDOM" = "truerand" ]; then + echo " using truerand library (-lrand) for the random seed" + LIBS="$LIBS -L/usr/local/lib -lrand" + else + echo " using $RULE_DEV_RANDOM for the random seed" + CFLAGS="$CFLAGS -DDEV_RANDOM=$RULE_DEV_RANDOM" + fi + + * ConfigEnd + * MODULE-DEFINITION-END + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_conf_globals.h" +#include "http_core.h" +#include "http_request.h" +#include "http_log.h" +#include "http_protocol.h" +#include "ap_config.h" +#include "ap_ctype.h" +#include "util_uri.h" +#include "util_md5.h" +#include "ap_sha1.h" + +#ifdef WIN32 +/* Crypt APIs are available on Win95 with OSR 2 */ +#include <wincrypt.h> +#endif + +#ifdef HAVE_SHMEM_MM +#include "mm.h" +#endif /* HAVE_SHMEM_MM */ + + +/* struct to hold the configuration info */ + +typedef struct digest_config_struct { + const char *dir_name; + const char *pwfile; + const char *grpfile; + const char *realm; + const char **qop_list; + AP_SHA1_CTX nonce_ctx; + long nonce_lifetime; + const char *nonce_format; + int check_nc; + const char *algorithm; + char *uri_list; + const char *ha1; +} digest_config_rec; + + +#define DFLT_ALGORITHM "MD5" + +#define DFLT_NONCE_LIFE 300L +#define NEXTNONCE_DELTA 30 + + +#define NONCE_TIME_LEN (((sizeof(time_t)+2)/3)*4) +#define NONCE_HASH_LEN (2*SHA_DIGESTSIZE) +#define NONCE_LEN (NONCE_TIME_LEN + NONCE_HASH_LEN) + +#define SECRET_LEN 20 + + +/* client list definitions */ + +typedef struct hash_entry { + unsigned long key; /* the key for this entry */ + struct hash_entry *next; /* next entry in the bucket */ + unsigned long nonce_count; /* for nonce-count checking */ + char ha1[2*MD5_DIGESTSIZE+1]; /* for algorithm=MD5-sess */ + char last_nonce[NONCE_LEN+1]; /* for one-time nonce's */ +} client_entry; + +static struct hash_table { + client_entry **table; + unsigned long tbl_len; + unsigned long num_entries; + unsigned long num_created; + unsigned long num_removed; + unsigned long num_renewed; +} *client_list; + + +/* struct to hold a parsed Authorization header */ + +enum hdr_sts { NO_HEADER, NOT_DIGEST, INVALID, VALID }; + +typedef struct digest_header_struct { + const char *scheme; + const char *realm; + const char *username; + char *nonce; + const char *uri; + const char *digest; + const char *algorithm; + const char *cnonce; + const char *opaque; + unsigned long opaque_num; + const char *message_qop; + const char *nonce_count; + /* the following fields are not (directly) from the header */ + time_t nonce_time; + enum hdr_sts auth_hdr_sts; + const char *raw_request_uri; + uri_components *psd_request_uri; + int needed_auth; + client_entry *client; +} digest_header_rec; + + +/* (mostly) nonce stuff */ + +typedef union time_union { + time_t time; + unsigned char arr[sizeof(time_t)]; +} time_rec; + + +static unsigned char secret[SECRET_LEN]; +static int call_cnt = 0; + + +#ifdef HAVE_SHMEM_MM +/* opaque stuff */ + +static MM *opaque_mm; +static unsigned long *opaque_cntr; + +static MM *client_mm; + +static MM *otn_count_mm; +static time_t *otn_counter; /* one-time-nonce counter */ + +#define SHMEM_SIZE 1000 /* ~ 12 entries */ +#define NUM_BUCKETS 15UL + +#else /* HAVE_SHMEM_MM */ +static void *client_mm = NULL; +#endif /* HAVE_SHMEM_MM */ + +module MODULE_VAR_EXPORT digest_auth_module; + +/* + * initialization code + */ + +#ifdef HAVE_SHMEM_MM +static void cleanup_tables(void *not_used) +{ + fprintf(stderr, "Digest: cleaning up shared memory\n"); + fflush(stderr); + + if (client_mm) { + mm_destroy(client_mm); + client_mm = NULL; + } + + if (opaque_mm) { + mm_destroy(opaque_mm); + opaque_mm = NULL; + } + + if (otn_count_mm) { + mm_destroy(otn_count_mm); + otn_count_mm = NULL; + } +} +#endif /* HAVE_SHMEM_MM */ + +#ifdef __OpenBSD__ +static void initialize_secret(server_rec *s) +{ + u_int32_t rnd = 0, i; + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, + "Digest: generating secret for digest authentication ..."); + + for (i = 0; i < sizeof(secret); i++) { + if (i % 4 == 0) + rnd = arc4random(); + secret[i] = rnd; + rnd >>= 8; + } + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, + "Digest: done"); +} +#elif defined(WIN32) +/* TODO: abstract out the random number generation. APR? */ +static void initialize_secret(server_rec *s) +{ + HCRYPTPROV hProv; + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, + "Digest: generating secret for digest authentication ..."); + if (!CryptAcquireContext(&hProv,NULL,NULL,PROV_RSA_FULL,0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, s, + "Digest: Error acquiring context. Errno = %d", + GetLastError()); + exit(EXIT_FAILURE); + } + if (!CryptGenRandom(hProv,sizeof(secret),secret)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, s, + "Digest: Error generating secret. Errno = %d", + GetLastError()); + exit(EXIT_FAILURE); + } + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, "Digest: done"); +} +#else +static void initialize_secret(server_rec *s) +{ +#ifdef DEV_RANDOM + int rnd; + ssize_t got; + size_t tot; +#else + extern int randbyte(void); /* from the truerand library */ + unsigned int idx; +#endif + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, + "Digest: generating secret for digest authentication ..."); + +#ifdef DEV_RANDOM +#define XSTR(x) #x +#define STR(x) XSTR(x) + if ((rnd = open(STR(DEV_RANDOM), O_RDONLY)) == -1) { + ap_log_error(APLOG_MARK, APLOG_CRIT, s, + "Digest: Couldn't open " STR(DEV_RANDOM)); + exit(EXIT_FAILURE); + } + for (tot=0; tot<sizeof(secret); tot += got) { + if ((got = read(rnd, secret+tot, sizeof(secret)-tot)) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, s, + "Digest: Error reading " STR(DEV_RANDOM)); + exit(EXIT_FAILURE); + } + } + close(rnd); +#undef STR +#undef XSTR +#else /* use truerand */ + /* this will increase the startup time of the server, unfortunately... + * (generating 20 bytes takes about 8 seconds) + */ + for (idx=0; idx<sizeof(secret); idx++) + secret[idx] = (unsigned char) randbyte(); +#endif /* DEV_RANDOM */ + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, "Digest: done"); +} +#endif + +#ifdef HAVE_SHMEM_MM +static void initialize_tables(server_rec *s) +{ + unsigned long idx; + + /* set up client list */ + + client_mm = mm_create(SHMEM_SIZE, tmpnam(NULL)); + if (client_mm == NULL) + goto failed; +#ifdef MPE + if (geteuid() == 1) { +#else + if (geteuid() == 0) { +#endif + if (mm_permission(client_mm, 0600, ap_user_id, ap_group_id)) + goto failed; + } + client_list = mm_malloc(client_mm, sizeof(*client_list) + + sizeof(client_entry*)*NUM_BUCKETS); + if (!client_list) goto failed; + client_list->table = (client_entry**) (client_list + 1); + for (idx=0; idx<NUM_BUCKETS; idx++) + client_list->table[idx] = NULL; + client_list->tbl_len = NUM_BUCKETS; + client_list->num_entries = 0; + + + /* setup opaque */ + + opaque_mm = mm_create(sizeof(*opaque_cntr), tmpnam(NULL)); + if (opaque_mm == NULL) + goto failed; +#ifdef MPE + if (geteuid() == 1) { +#else + if (geteuid() == 0) { +#endif + if (mm_permission(opaque_mm, 0600, ap_user_id, ap_group_id)) + goto failed; + } + opaque_cntr = mm_malloc(opaque_mm, sizeof(*opaque_cntr)); + if (opaque_cntr == NULL) + goto failed; + *opaque_cntr = 1UL; + + + /* setup one-time-nonce counter */ + + otn_count_mm = mm_create(sizeof(*otn_counter), tmpnam(NULL)); + if (otn_count_mm == NULL) + goto failed; +#ifdef MPE + if (geteuid() == 1) { +#else + if (geteuid() == 0) { +#endif + if (mm_permission(otn_count_mm, 0600, ap_user_id, ap_group_id)) + goto failed; + } + otn_counter = mm_malloc(otn_count_mm, sizeof(*otn_counter)); + if (otn_counter == NULL) + goto failed; + *otn_counter = 0; + + + /* success */ + return; + +failed: + if (!client_mm || (client_list && client_list->table && !opaque_mm) + || (opaque_cntr && !otn_count_mm)) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, + "Digest: failed to create shared memory segments; reason " + "was `%s' - all nonce-count checking, one-time nonces, " + "and MD5-sess algorithm disabled", mm_error()); + else + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, + "Digest: failed to allocate shared mem; reason was `%s' " + "- all nonce-count checking, one-time nonces, and " + "MD5-sess algorithm disabled", mm_error()); + + cleanup_tables(NULL); +} +#endif /* HAVE_SHMEM_MM */ + +static void initialize_module(server_rec *s, pool *p) +{ + /* keep from doing the init more than once at startup, and delay + * the init until the second round + */ + if (++call_cnt < 2) + return; + + /* only initialize the secret on startup, not on restarts */ + if (call_cnt == 2) + initialize_secret(s); + +#ifdef HAVE_SHMEM_MM + /* Note: this stuff is currently fixed for the lifetime of the server, + * i.e. even across restarts. This means that A) any shmem-size + * configuration changes are ignored, and B) certain optimizations, + * such as only allocating the smallest necessary entry for each + * client, can't be done. However, the alternative is a nightmare: + * we can't call mm_destroy on a graceful restart because there will + * be children using the tables, and we also don't know when the + * last child dies. Therefore we can never clean up the old stuff, + * creating a creeping memory leak. + */ + initialize_tables(s); + /* atexit(cleanup_tables); */ + ap_register_cleanup(p, NULL, cleanup_tables, ap_null_cleanup); +#endif /* HAVE_SHMEM_MM */ +} + + +/* + * configuration code + */ + +static void *create_digest_dir_config(pool *p, char *dir) +{ + digest_config_rec *conf; + + if (dir == NULL) return NULL; + + conf = (digest_config_rec *) ap_pcalloc(p, sizeof(digest_config_rec)); + if (conf) { + conf->qop_list = ap_palloc(p, sizeof(char*)); + conf->qop_list[0] = NULL; + conf->nonce_lifetime = DFLT_NONCE_LIFE; + conf->dir_name = ap_pstrdup(p, dir); + conf->algorithm = DFLT_ALGORITHM; + } + + return conf; +} + +static const char *set_realm(cmd_parms *cmd, void *config, const char *realm) +{ + digest_config_rec *conf = (digest_config_rec *) config; + + /* The core already handles the realm, but it's just too convenient to + * grab it ourselves too and cache some setups. However, we need to + * let the core get at it too, which is why we decline at the end - + * this relies on the fact that http_core is last in the list. + */ + conf->realm = realm; + + /* we precompute the part of the nonce hash that is constant (well, + * the host:port would be too, but that varies for .htaccess files + * and directives outside a virtual host section) + */ + ap_SHA1Init(&conf->nonce_ctx); + ap_SHA1Update_binary(&conf->nonce_ctx, secret, sizeof(secret)); + ap_SHA1Update_binary(&conf->nonce_ctx, (const unsigned char *) realm, + strlen(realm)); + + return DECLINE_CMD; +} + +static const char *set_digest_file(cmd_parms *cmd, void *config, + const char *file) +{ + ((digest_config_rec *) config)->pwfile = file; + return NULL; +} + +static const char *set_group_file(cmd_parms *cmd, void *config, + const char *file) +{ + ((digest_config_rec *) config)->grpfile = file; + return NULL; +} + +static const char *set_qop(cmd_parms *cmd, void *config, const char *op) +{ + digest_config_rec *conf = (digest_config_rec *) config; + char **tmp; + int cnt; + + if (!strcasecmp(op, "none")) { + if (conf->qop_list[0] == NULL) { + conf->qop_list = ap_palloc(cmd->pool, 2 * sizeof(char*)); + conf->qop_list[1] = NULL; + } + conf->qop_list[0] = "none"; + return NULL; + } + + if (!strcasecmp(op, "auth-int")) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server, + "Digest: WARNING: qop `auth-int' currently only works " + "correctly for responses with no entity"); + else if (strcasecmp(op, "auth")) + return ap_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL); + + for (cnt=0; conf->qop_list[cnt] != NULL; cnt++) + ; + tmp = ap_palloc(cmd->pool, (cnt+2)*sizeof(char*)); + memcpy(tmp, conf->qop_list, cnt*sizeof(char*)); + tmp[cnt] = ap_pstrdup(cmd->pool, op); + tmp[cnt+1] = NULL; + conf->qop_list = (const char **)tmp; + + return NULL; +} + +static const char *set_nonce_lifetime(cmd_parms *cmd, void *config, + const char *t) +{ + char *endptr; + long lifetime; + + lifetime = ap_strtol(t, &endptr, 10); + if (endptr < (t+strlen(t)) && !ap_isspace(*endptr)) + return ap_pstrcat(cmd->pool, "Invalid time in AuthDigestNonceLifetime: ", t, NULL); + + ((digest_config_rec *) config)->nonce_lifetime = lifetime; + return NULL; +} + +static const char *set_nonce_format(cmd_parms *cmd, void *config, + const char *fmt) +{ + ((digest_config_rec *) config)->nonce_format = fmt; + return "AuthDigestNonceFormat is not implemented (yet)"; +} + +static const char *set_nc_check(cmd_parms *cmd, void *config, int flag) +{ + ((digest_config_rec *) config)->check_nc = flag; + return NULL; +} + +static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg) +{ + if (!strcasecmp(alg, "MD5-sess")) +#ifdef HAVE_SHMEM_MM + ; +#else /* HAVE_SHMEM_MM */ + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server, + "Digest: WARNING: algorithm `MD5-sess' is currently not " + "correctly implemented"); +#endif /* HAVE_SHMEM_MM */ + else if (strcasecmp(alg, "MD5")) + return ap_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL); + + ((digest_config_rec *) config)->algorithm = alg; + return NULL; +} + +static const char *set_uri_list(cmd_parms *cmd, void *config, const char *uri) +{ + digest_config_rec *c = (digest_config_rec *) config; + if (c->uri_list) { + c->uri_list[strlen(c->uri_list)-1] = '\0'; + c->uri_list = ap_pstrcat(cmd->pool, c->uri_list, " ", uri, "\"", NULL); + } + else + c->uri_list = ap_pstrcat(cmd->pool, ", domain=\"", uri, "\"", NULL); + return NULL; +} + +static const command_rec digest_cmds[] = +{ + {"AuthName", set_realm, NULL, OR_AUTHCFG, TAKE1, + "The authentication realm (e.g. \"Members Only\")"}, + {"AuthDigestFile", set_digest_file, NULL, OR_AUTHCFG, TAKE1, + "The name of the file containing the usernames and password hashes"}, + {"AuthDigestGroupFile", set_group_file, NULL, OR_AUTHCFG, TAKE1, + "The name of the file containing the group names and members"}, + {"AuthDigestQop", set_qop, NULL, OR_AUTHCFG, ITERATE, + "A list of quality-of-protection options"}, + {"AuthDigestNonceLifetime", set_nonce_lifetime, NULL, OR_AUTHCFG, TAKE1, + "Maximum lifetime of the server nonce (seconds)"}, + {"AuthDigestNonceFormat", set_nonce_format, NULL, OR_AUTHCFG, TAKE1, + "The format to use when generating the server nonce"}, + {"AuthDigestNcCheck", set_nc_check, NULL, OR_AUTHCFG, FLAG, + "Whether or not to check the nonce-count sent by the client"}, + {"AuthDigestAlgorithm", set_algorithm, NULL, OR_AUTHCFG, TAKE1, + "The algorithm used for the hash calculation"}, + {"AuthDigestDomain", set_uri_list, NULL, OR_AUTHCFG, ITERATE, + "A list of URI's which belong to the same protection space as the current URI"}, + {NULL, NULL, NULL, 0, 0, NULL} +}; + + +#ifdef HAVE_SHMEM_MM +/* + * client list code + * + * Each client is assigned a number, which is transferred in the opaque + * field of the WWW-Authenticate and Authorization headers. The number + * is just a simple counter which is incremented for each new client. + * Clients can't forge this number because it is hashed up into the + * server nonce, and that is checked. + * + * The clients are kept in a simple hash table, which consists of an + * array of client_entry's, each with a linked list of entries hanging + * off it. The client's number modulo the size of the array gives the + * bucket number. + * + * The clients are garbage collected whenever a new client is allocated + * but there is not enough space left in the shared memory segment. A + * simple semi-LRU is used for this: whenever a client entry is accessed + * it is moved to the beginning of the linked list in its bucket (this + * also makes for faster lookups for current clients). The garbage + * collecter then just removes the oldest entry (i.e. the one at the + * end of the list) in each bucket. + * + * The main advantages of the above scheme are that it's easy to implement + * and it keeps the hash table evenly balanced (i.e. same number of entries + * in each bucket). The major disadvantage is that you may be throwing + * entries out which are in active use. This is not tragic, as these + * clients will just be sent a new client id (opaque field) and nonce + * with a stale=true (i.e. it will just look like the nonce expired, + * thereby forcing an extra round trip). If the shared memory segment + * has enough headroom over the current client set size then this should + * not occur too often. + * + * To help tune the size of the shared memory segment (and see if the + * above algorithm is really sufficient) a set of counters is kept + * indicating the number of clients held, the number of garbage collected + * clients, and the number of erroneously purged clients. These are printed + * out at each garbage collection run. Note that access to the counters is + * not synchronized because they are just indicaters, and whether they are + * off by a few doesn't matter; and for the same reason no attempt is made + * to guarantee the num_renewed is correct in the face of clients spoofing + * the opaque field. + */ + +/* + * Get the client given its client number (the key). Returns the entry, + * or NULL if its not found. + * + * Access to the list itself is synchronized via locks. However, access + * to the entry returned by get_client() is NOT synchronized. This means + * that there are potentially problems if a client uses multiple, + * simultaneous connections to access url's within the same protection + * space. However, these problems are not new: when using multiple + * connections you have no guarantee of the order the requests are + * processed anyway, so you have problems with the nonce-count and + * one-time nonces anyway. + */ +static client_entry *get_client(unsigned long key, const request_rec *r) +{ + int bucket; + client_entry *entry, *prev = NULL; + + + if (!key || !client_mm) return NULL; + + bucket = key % client_list->tbl_len; + entry = client_list->table[bucket]; + + mm_lock(client_mm, MM_LOCK_RD); + + while(entry && key != entry->key) { + prev = entry; + entry = entry->next; + } + + if (entry && prev) { /* move entry to front of list */ + prev->next = entry->next; + entry->next = client_list->table[bucket]; + client_list->table[bucket] = entry; + } + + mm_unlock(client_mm); + + if (entry) + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "get_client(): client %lu found", key); + else + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "get_client(): client %lu not found", key); + + return entry; +} + + +/* A simple garbage-collecter to remove unused clients. It removes the + * last entry in each bucket and updates the counters. Returns the + * number of removed entries. + */ +static long gc(void) +{ + client_entry *entry, *prev; + unsigned long num_removed = 0, idx; + + /* garbage collect all last entries */ + + for (idx=0; idx<client_list->tbl_len; idx++) { + entry = client_list->table[idx]; + prev = NULL; + while (entry->next) { /* find last entry */ + prev = entry; + entry = entry->next; + } + if (prev) prev->next = NULL; /* cut list */ + else client_list->table[idx] = NULL; + if (entry) { /* remove entry */ + mm_free(client_mm, entry); + num_removed++; + } + } + + /* update counters and log */ + + client_list->num_entries -= num_removed; + client_list->num_removed += num_removed; + + return num_removed; +} + + +/* + * Add a new client to the list. Returns the entry if successful, NULL + * otherwise. This triggers the garbage collection if memory is low. + */ +static client_entry *add_client(unsigned long key, client_entry *info, + server_rec *s) +{ + int bucket; + client_entry *entry; + + + if (!key || !client_mm) return NULL; + + bucket = key % client_list->tbl_len; + entry = client_list->table[bucket]; + + mm_lock(client_mm, MM_LOCK_RW); + + /* try to allocate a new entry */ + + entry = mm_malloc(client_mm, sizeof(client_entry)); + if (!entry) { + long num_removed = gc(); + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, s, + "Digest: gc'd %ld client entries. Total new clients: " + "%ld; Total removed clients: %ld; Total renewed clients: " + "%ld", num_removed, + client_list->num_created - client_list->num_renewed, + client_list->num_removed, client_list->num_renewed); + entry = mm_malloc(client_mm, sizeof(client_entry)); + if (!entry) return NULL; /* give up */ + } + + /* now add the entry */ + + memcpy(entry, info, sizeof(client_entry)); + entry->key = key; + entry->next = client_list->table[bucket]; + client_list->table[bucket] = entry; + client_list->num_created++; + client_list->num_entries++; + + mm_unlock(client_mm); + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, s, + "allocated new client %lu", key); + + return entry; +} +#else /* HAVE_SHMEM_MM */ +static client_entry *get_client(unsigned long key, const request_rec *r) +{ + return NULL; +} +#endif /* HAVE_SHMEM_MM */ + + +/* + * Authorization header parser code + */ + +/* Parse the Authorization header, if it exists */ +static int get_digest_rec(request_rec *r, digest_header_rec *resp) +{ + const char *auth_line; + size_t l; + int vk = 0, vv = 0; + char *key, *value; + + auth_line = ap_table_get(r->headers_in, + r->proxyreq == STD_PROXY ? "Proxy-Authorization" + : "Authorization"); + if (!auth_line) { + resp->auth_hdr_sts = NO_HEADER; + return !OK; + } + + resp->scheme = ap_getword_white(r->pool, &auth_line); + if (strcasecmp(resp->scheme, "Digest")) { + resp->auth_hdr_sts = NOT_DIGEST; + return !OK; + } + + l = strlen(auth_line); + + key = ap_palloc(r->pool, l+1); + value = ap_palloc(r->pool, l+1); + + while (auth_line[0] != '\0') { + + /* find key */ + + while (ap_isspace(auth_line[0])) auth_line++; + vk = 0; + while (auth_line[0] != '=' && auth_line[0] != ',' + && auth_line[0] != '\0' && !ap_isspace(auth_line[0])) + key[vk++] = *auth_line++; + key[vk] = '\0'; + while (ap_isspace(auth_line[0])) auth_line++; + + /* find value */ + + if (auth_line[0] == '=') { + auth_line++; + while (ap_isspace(auth_line[0])) auth_line++; + + vv = 0; + if (auth_line[0] == '\"') { /* quoted string */ + auth_line++; + while (auth_line[0] != '\"' && auth_line[0] != '\0') { + if (auth_line[0] == '\\' && auth_line[1] != '\0') + auth_line++; /* escaped char */ + value[vv++] = *auth_line++; + } + if (auth_line[0] != '\0') auth_line++; + } + else { /* token */ + while (auth_line[0] != ',' && auth_line[0] != '\0' + && !ap_isspace(auth_line[0])) + value[vv++] = *auth_line++; + } + value[vv] = '\0'; + } + + while (auth_line[0] != ',' && auth_line[0] != '\0') auth_line++; + if (auth_line[0] != '\0') auth_line++; + + if (!strcasecmp(key, "username")) + resp->username = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "realm")) + resp->realm = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "nonce")) + resp->nonce = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "uri")) + resp->uri = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "response")) + resp->digest = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "algorithm")) + resp->algorithm = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "cnonce")) + resp->cnonce = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "opaque")) + resp->opaque = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "qop")) + resp->message_qop = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "nc")) + resp->nonce_count = ap_pstrdup(r->pool, value); + } + + if (!resp->username || !resp->realm || !resp->nonce || !resp->uri + || !resp->digest + || (resp->message_qop && (!resp->cnonce || !resp->nonce_count))) { + resp->auth_hdr_sts = INVALID; + return !OK; + } + + if (resp->opaque) + resp->opaque_num = (unsigned long) ap_strtol(resp->opaque, NULL, 16); + + resp->auth_hdr_sts = VALID; + return OK; +} + + +/* Because the browser may preemptively send auth info, incrementing the + * nonce-count when it does, and because the client does not get notified + * if the URI didn't need authentication after all, we need to be sure to + * update the nonce-count each time we receive an Authorization header no + * matter what the final outcome of the request. Furthermore this is a + * convenient place to get the request-uri (before any subrequests etc + * are initiated) and to initialize the request_config. + * + * Note that this must be called after mod_proxy had its go so that + * r->proxyreq is set correctly. + */ +static int update_nonce_count(request_rec *r) +{ + digest_header_rec *resp; + int res; + + if (!ap_is_initial_req(r)) + return DECLINED; + + resp = ap_pcalloc(r->pool, sizeof(digest_header_rec)); + resp->raw_request_uri = r->unparsed_uri; + resp->psd_request_uri = &r->parsed_uri; + resp->needed_auth = 0; + ap_set_module_config(r->request_config, &digest_auth_module, resp); + + res = get_digest_rec(r, resp); + resp->client = get_client(resp->opaque_num, r); + if (res == OK && resp->client) + resp->client->nonce_count++; + + return DECLINED; +} + + +/* + * Nonce generation code + */ + +/* The hash part of the nonce is a SHA-1 hash of the time, realm, server host + * and port, opaque, and our secret. + */ +static void gen_nonce_hash(char *hash, const char *timestr, const char *opaque, + const server_rec *server, + const digest_config_rec *conf) +{ + const char *hex = "0123456789abcdef"; + unsigned char sha1[SHA_DIGESTSIZE]; + AP_SHA1_CTX ctx; + int idx; + + memcpy(&ctx, &conf->nonce_ctx, sizeof(ctx)); + /* + ap_SHA1Update_binary(&ctx, (const unsigned char *) server->server_hostname, + strlen(server->server_hostname)); + ap_SHA1Update_binary(&ctx, (const unsigned char *) &server->port, + sizeof(server->port)); + */ + ap_SHA1Update_binary(&ctx, (const unsigned char *) timestr, strlen(timestr)); + if (opaque) + ap_SHA1Update_binary(&ctx, (const unsigned char *) opaque, + strlen(opaque)); + ap_SHA1Final(sha1, &ctx); + + for (idx=0; idx<SHA_DIGESTSIZE; idx++) { + *hash++ = hex[sha1[idx] >> 4]; + *hash++ = hex[sha1[idx] & 0xF]; + } + + *hash++ = '\0'; +} + + +/* The nonce has the format b64(time)+hash . + */ +static const char *gen_nonce(pool *p, time_t now, const char *opaque, + const server_rec *server, + const digest_config_rec *conf) +{ + char *nonce = ap_palloc(p, NONCE_LEN+1); + time_rec t; + + if (conf->nonce_lifetime != 0) + t.time = now; + else +#ifdef HAVE_SHMEM_MM + /* this counter is not synch'd, because it doesn't really matter + * if it counts exactly. + */ + t.time = (*otn_counter)++; +#else /* HAVE_SHMEM_MM */ + t.time = 42; +#endif /* HAVE_SHMEM_MM */ + ap_base64encode_binary(nonce, t.arr, sizeof(t.arr)); + gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, opaque, server, conf); + + return nonce; +} + + +/* + * Opaque and hash-table management + */ + +#ifdef HAVE_SHMEM_MM +/* + * Generate a new client entry, add it to the list, and return the + * entry. Returns NULL if failed. + */ +static client_entry *gen_client(const request_rec *r) +{ + unsigned long op; + client_entry new_entry = { 0, NULL, 0, "", "" }, *entry; + + if (!opaque_mm) return 0; + + mm_lock(opaque_mm, MM_LOCK_RW); + op = (*opaque_cntr)++; + mm_unlock(opaque_mm); + + if (!(entry = add_client(op, &new_entry, r->server))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "Digest: failed to allocate client entry - ignoring " + "client"); + return NULL; + } + + return entry; +} +#else /* HAVE_SHMEM_MM */ +static client_entry *gen_client(const request_rec *r) { return NULL; } +#endif /* HAVE_SHMEM_MM */ + + + +/* + * MD5-sess code. + * + * If you want to use algorithm=MD5-sess you must write get_userpw_hash() + * yourself (see below). The dummy provided here just uses the hash from + * the auth-file, i.e. it is only useful for testing client implementations + * of MD5-sess . + */ + +/* + * get_userpw_hash() will be called each time a new session needs to be + * generated and is expected to return the equivalent of + * + * h_urp = ap_md5(r->pool, + * ap_pstrcat(r->pool, username, ":", ap_auth_name(r), ":", passwd)) + * ap_md5(r->pool, + * (unsigned char *) ap_pstrcat(r->pool, h_urp, ":", resp->nonce, ":", + * resp->cnonce, NULL)); + * + * or put differently, it must return + * + * MD5(MD5(username ":" realm ":" password) ":" nonce ":" cnonce) + * + * If something goes wrong, the failure must be logged and NULL returned. + * + * You must implement this yourself, which will probably consist of code + * contacting the password server with the necessary information (typically + * the username, realm, nonce, and cnonce) and receiving the hash from it. + * + * TBD: This function should probably be in a seperate source file so that + * people need not modify mod_auth_digest.c each time they install a new + * version of apache. + */ +static const char *get_userpw_hash(const request_rec *r, + const digest_header_rec *resp, + const digest_config_rec *conf) +{ + return ap_md5(r->pool, + (unsigned char *) ap_pstrcat(r->pool, conf->ha1, ":", resp->nonce, + ":", resp->cnonce, NULL)); +} + + +/* Retrieve current session H(A1). If there is none and "generate" is + * true then a new session for MD5-sess is generated and stored in the + * client struct; if generate is false, or a new session could not be + * generated then NULL is returned (in case of failure to generate the + * failure reason will have been logged already). + */ +static const char *get_session_HA1(const request_rec *r, + digest_header_rec *resp, + const digest_config_rec *conf, + int generate) +{ + const char *ha1 = NULL; + + /* return the current sessions if there is one */ + if (resp->opaque && resp->client && resp->client->ha1[0]) + return resp->client->ha1; + else if (!generate) + return NULL; + + /* generate a new session */ + if (!resp->client) + resp->client = gen_client(r); + if (resp->client) { + ha1 = get_userpw_hash(r, resp, conf); + if (ha1) + memcpy(resp->client->ha1, ha1, sizeof(resp->client->ha1)); + } + + return ha1; +} + + +static void clear_session(const digest_header_rec *resp) +{ + if (resp->client) + resp->client->ha1[0] = '\0'; +} + +/* + * Authorization challenge generation code (for WWW-Authenticate) + */ + +static const char *ltox(pool *p, unsigned long num) +{ + if (num != 0) + return ap_psprintf(p, "%lx", num); + else + return ""; +} + +static void note_digest_auth_failure(request_rec *r, + const digest_config_rec *conf, + digest_header_rec *resp, int stale) +{ + const char *qop, *opaque, *opaque_param, *domain, *nonce; + int cnt; + + + /* Setup qop */ + + if (conf->qop_list[0] == NULL) + qop = ", qop=\"auth\""; + else if (!strcasecmp(conf->qop_list[0], "none")) + qop = ""; + else { + qop = ap_pstrcat(r->pool, ", qop=\"", conf->qop_list[0], NULL); + for (cnt=1; conf->qop_list[cnt] != NULL; cnt++) + qop = ap_pstrcat(r->pool, qop, ",", conf->qop_list[cnt], NULL); + qop = ap_pstrcat(r->pool, qop, "\"", NULL); + } + + /* Setup opaque */ + + if (resp->opaque == NULL) { + /* new client */ + if ((conf->check_nc || conf->nonce_lifetime == 0 + || !strcasecmp(conf->algorithm, "MD5-sess")) + && (resp->client = gen_client(r)) != NULL) + opaque = ltox(r->pool, resp->client->key); + else + opaque = ""; /* opaque not needed */ + } + else if (resp->client == NULL) { + /* client info was gc'd */ + resp->client = gen_client(r); + if (resp->client != NULL) { + opaque = ltox(r->pool, resp->client->key); + stale = 1; + client_list->num_renewed++; + } + else + opaque = ""; /* ??? */ + } + else { + opaque = resp->opaque; + /* we're generating a new nonce, so reset the nonce-count */ + resp->client->nonce_count = 0; + } + + if (opaque[0]) + opaque_param = ap_pstrcat(r->pool, ", opaque=\"", opaque, "\"", NULL); + else + opaque_param = NULL; + + /* Setup nonce */ + + nonce = gen_nonce(r->pool, r->request_time, opaque, r->server, conf); + if (resp->client && conf->nonce_lifetime == 0) + memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1); + + /* Setup MD5-sess stuff. Note that we just clear out the session + * info here, since we can't generate a new session until the request + * from the client comes in with the cnonce. + */ + + if (!strcasecmp(conf->algorithm, "MD5-sess")) + clear_session(resp); + + /* setup domain attribute. We want to send this attribute wherever + * possible so that the client won't send the Authorization header + * unneccessarily (it's usually > 200 bytes!). + */ + + /* don't send domain + * - for proxy requests + * - if it's no specified + */ + if (r->proxyreq || !conf->uri_list) { + domain = NULL; + } + else { + domain = conf->uri_list; + } + + ap_table_mergen(r->err_headers_out, + r->proxyreq == STD_PROXY ? "Proxy-Authenticate" + : "WWW-Authenticate", + ap_psprintf(r->pool, "Digest realm=\"%s\", nonce=\"%s\", " + "algorithm=%s%s%s%s%s", + ap_auth_name(r), nonce, conf->algorithm, + opaque_param ? opaque_param : "", + domain ? domain : "", + stale ? ", stale=true" : "", qop)); +} + + +/* + * Authorization header verification code + */ + +static const char *get_hash(request_rec *r, const char *user, + const char *realm, const char *auth_pwfile) +{ + configfile_t *f; + char l[MAX_STRING_LEN]; + const char *rpw; + char *w, *x; + + if (!(f = ap_pcfg_openfile(r->pool, auth_pwfile))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "Digest: Could not open password file: %s", auth_pwfile); + return NULL; + } + while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { + if ((l[0] == '#') || (!l[0])) + continue; + rpw = l; + w = ap_getword(r->pool, &rpw, ':'); + x = ap_getword(r->pool, &rpw, ':'); + + if (x && w && !strcmp(user, w) && !strcmp(realm, x)) { + ap_cfg_closefile(f); + return ap_pstrdup(r->pool, rpw); + } + } + ap_cfg_closefile(f); + return NULL; +} + +static int check_nc(const request_rec *r, const digest_header_rec *resp, + const digest_config_rec *conf) +{ + unsigned long nc; + const char *snc = resp->nonce_count; + char *endptr; + + if (!conf->check_nc || !client_mm) + return OK; + + nc = ap_strtol(snc, &endptr, 16); + if (endptr < (snc+strlen(snc)) && !ap_isspace(*endptr)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: invalid nc %s received - not a number", snc); + return !OK; + } + + if (!resp->client) + return !OK; + + if (nc != resp->client->nonce_count) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: Warning, possible replay attack: nonce-count " + "check failed: %lu != %lu", nc, + resp->client->nonce_count); + return !OK; + } + + return OK; +} + +static int check_nonce(request_rec *r, digest_header_rec *resp, + const digest_config_rec *conf) +{ + double dt; + time_rec nonce_time; + char tmp, hash[NONCE_HASH_LEN+1]; + + if (strlen(resp->nonce) != NONCE_LEN) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: invalid nonce %s received - length is not %d", + resp->nonce, NONCE_LEN); + note_digest_auth_failure(r, conf, resp, 1); + return AUTH_REQUIRED; + } + + tmp = resp->nonce[NONCE_TIME_LEN]; + resp->nonce[NONCE_TIME_LEN] = '\0'; + ap_base64decode_binary(nonce_time.arr, resp->nonce); + gen_nonce_hash(hash, resp->nonce, resp->opaque, r->server, conf); + resp->nonce[NONCE_TIME_LEN] = tmp; + resp->nonce_time = nonce_time.time; + + if (strcmp(hash, resp->nonce+NONCE_TIME_LEN)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: invalid nonce %s received - hash is not %s", + resp->nonce, hash); + note_digest_auth_failure(r, conf, resp, 1); + return AUTH_REQUIRED; + } + + dt = difftime(r->request_time, nonce_time.time); + if (conf->nonce_lifetime > 0 && dt < 0) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: invalid nonce %s received - user attempted " + "time travel", resp->nonce); + note_digest_auth_failure(r, conf, resp, 1); + return AUTH_REQUIRED; + } + + if (conf->nonce_lifetime > 0) { + if (dt > conf->nonce_lifetime) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r, + "Digest: user %s: nonce expired - sending new nonce", + r->connection->user); + note_digest_auth_failure(r, conf, resp, 1); + return AUTH_REQUIRED; + } + } + else if (conf->nonce_lifetime == 0 && resp->client) { + if (memcmp(resp->client->last_nonce, resp->nonce, NONCE_LEN)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r, + "Digest: user %s: one-time-nonce mismatch - sending " + "new nonce", r->connection->user); + note_digest_auth_failure(r, conf, resp, 1); + return AUTH_REQUIRED; + } + } + /* else (lifetime < 0) => never expires */ + + return OK; +} + +/* The actual MD5 code... whee */ + +/* RFC-2069 */ +static const char *old_digest(const request_rec *r, + const digest_header_rec *resp, const char *ha1) +{ + const char *ha2; + + ha2 = ap_md5(r->pool, (unsigned char *)ap_pstrcat(r->pool, r->method, ":", + resp->uri, NULL)); + return ap_md5(r->pool, + (unsigned char *)ap_pstrcat(r->pool, ha1, ":", resp->nonce, + ":", ha2, NULL)); +} + +/* RFC-2617 */ +static const char *new_digest(const request_rec *r, + digest_header_rec *resp, + const digest_config_rec *conf) +{ + const char *ha1, *ha2, *a2; + + if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess")) { + ha1 = get_session_HA1(r, resp, conf, 1); + if (!ha1) + return NULL; + } + else + ha1 = conf->ha1; + + if (resp->message_qop && !strcasecmp(resp->message_qop, "auth-int")) + a2 = ap_pstrcat(r->pool, r->method, ":", resp->uri, ":", + ap_md5(r->pool, (const unsigned char*) ""), NULL); /* TBD */ + else + a2 = ap_pstrcat(r->pool, r->method, ":", resp->uri, NULL); + ha2 = ap_md5(r->pool, (const unsigned char *)a2); + + return ap_md5(r->pool, + (unsigned char *)ap_pstrcat(r->pool, ha1, ":", resp->nonce, + ":", resp->nonce_count, ":", + resp->cnonce, ":", + resp->message_qop, ":", ha2, + NULL)); +} + + +static void copy_uri_components(uri_components *dst, uri_components *src, + request_rec *r) +{ + if (src->scheme && src->scheme[0] != '\0') + dst->scheme = src->scheme; + else + dst->scheme = (char *) "http"; + + if (src->hostname && src->hostname[0] != '\0') { + dst->hostname = ap_pstrdup(r->pool, src->hostname); + ap_unescape_url(dst->hostname); + } + else + dst->hostname = (char *) ap_get_server_name(r); + + if (src->port_str && src->port_str[0] != '\0') + dst->port = src->port; + else + dst->port = ap_get_server_port(r); + + if (src->path && src->path[0] != '\0') { + dst->path = ap_pstrdup(r->pool, src->path); + ap_unescape_url(dst->path); + } + else + dst->path = src->path; + + if (src->query && src->query[0] != '\0') { + dst->query = ap_pstrdup(r->pool, src->query); + ap_unescape_url(dst->query); + } + else + dst->query = src->query; +} + +/* This handles non-FQDN's. If h1 is empty, the comparison succeeds. Else + * if h1 is a FQDN (i.e. contains a '.') then normal strcasecmp() is done. + * Else only the first part of h2 (up to the first '.') is compared. + */ +static int compare_hostnames(const char *h1, const char *h2) +{ + const char *dot; + + /* if no hostname given, then ok */ + if (!h1 || h1[0] == '\0') + return 1; + + /* handle FQDN's in h1 */ + dot = strchr(h1, '.'); + if (dot != NULL) + return !strcasecmp(h1, h2); + + /* handle non-FQDN's in h1 */ + dot = strchr(h2, '.'); + if (dot == NULL) + return !strcasecmp(h1, h2); + else + return (strlen(h1) == (size_t) (dot - h2)) && !strncasecmp(h1, h2, dot-h2); +} + +/* These functions return 0 if client is OK, and proper error status + * if not... either AUTH_REQUIRED, if we made a check, and it failed, or + * SERVER_ERROR, if things are so totally confused that we couldn't + * figure out how to tell if the client is authorized or not. + * + * If they return DECLINED, and all other modules also decline, that's + * treated by the server core as a configuration error, logged and + * reported as such. + */ + +/* Determine user ID, and check if the attributes are correct, if it + * really is that user, if the nonce is correct, etc. + */ + +static int authenticate_digest_user(request_rec *r) +{ + digest_config_rec *conf; + digest_header_rec *resp; + request_rec *mainreq; + conn_rec *conn = r->connection; + const char *t; + int res; + + + /* do we require Digest auth for this URI? */ + + if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest")) + return DECLINED; + + if (!ap_auth_name(r)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: need AuthName: %s", r->uri); + return SERVER_ERROR; + } + + + /* get the client response and mark */ + + mainreq = r; + while (mainreq->main != NULL) mainreq = mainreq->main; + while (mainreq->prev != NULL) mainreq = mainreq->prev; + resp = (digest_header_rec *) ap_get_module_config(mainreq->request_config, + &digest_auth_module); + resp->needed_auth = 1; + + + /* get our conf */ + + conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config, + &digest_auth_module); + + + /* check for existence and syntax of Auth header */ + + if (resp->auth_hdr_sts != VALID) { + if (resp->auth_hdr_sts == NOT_DIGEST) + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: client used wrong authentication scheme " + "`%s': %s", resp->scheme, r->uri); + else if (resp->auth_hdr_sts == INVALID) + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: missing user, realm, nonce, uri, digest, " + "cnonce, or nonce_count in authorization header: %s", + r->uri); + /* else (resp->auth_hdr_sts == NO_HEADER) */ + note_digest_auth_failure(r, conf, resp, 0); + return AUTH_REQUIRED; + } + + r->connection->user = (char *) resp->username; + r->connection->ap_auth_type = (char *) "Digest"; + + + /* check the auth attributes */ + + if (strcmp(resp->uri, resp->raw_request_uri)) { + /* Hmm, the simple match didn't work (probably a proxy modified the + * request-uri), so lets do a more sophisticated match + */ + uri_components r_uri, d_uri; + + copy_uri_components(&r_uri, resp->psd_request_uri, r); + if (ap_parse_uri_components(r->pool, resp->uri, &d_uri) != HTTP_OK) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: invalid uri <%s> in Authorization header", + resp->uri); + return BAD_REQUEST; + } + + if (d_uri.hostname) + ap_unescape_url(d_uri.hostname); + if (d_uri.path) + ap_unescape_url(d_uri.path); + if (d_uri.query) + ap_unescape_url(d_uri.query); + + if (r->method_number == M_CONNECT) { + if (strcmp(resp->uri, r_uri.hostinfo)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: uri mismatch - <%s> does not match " + "request-uri <%s>", resp->uri, r_uri.hostinfo); + return BAD_REQUEST; + } + } + else if ( + /* check hostname matches, if present */ + !compare_hostnames(d_uri.hostname, r_uri.hostname) + /* check port matches, if present */ + || (d_uri.port_str && d_uri.port != r_uri.port) + /* check that server-port is default port if no port present */ + || (d_uri.hostname && d_uri.hostname[0] != '\0' + && !d_uri.port_str && r_uri.port != ap_default_port(r)) + /* check that path matches */ + || (d_uri.path != r_uri.path + /* either exact match */ + && (!d_uri.path || !r_uri.path + || strcmp(d_uri.path, r_uri.path)) + /* or '*' matches empty path in scheme://host */ + && !(d_uri.path && !r_uri.path && resp->psd_request_uri->hostname + && d_uri.path[0] == '*' && d_uri.path[1] == '\0')) + /* check that query matches */ + || (d_uri.query != r_uri.query + && (!d_uri.query || !r_uri.query + || strcmp(d_uri.query, r_uri.query))) + ) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: uri mismatch - <%s> does not match " + "request-uri <%s>", resp->uri, resp->raw_request_uri); + return BAD_REQUEST; + } + } + + if (resp->opaque && resp->opaque_num == 0) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: received invalid opaque - got `%s'", + resp->opaque); + note_digest_auth_failure(r, conf, resp, 0); + return AUTH_REQUIRED; + } + + if (strcmp(resp->realm, conf->realm)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: realm mismatch - got `%s' but expected `%s'", + resp->realm, conf->realm); + note_digest_auth_failure(r, conf, resp, 0); + return AUTH_REQUIRED; + } + + if (resp->algorithm != NULL + && strcasecmp(resp->algorithm, "MD5") + && strcasecmp(resp->algorithm, "MD5-sess")) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: unknown algorithm `%s' received: %s", + resp->algorithm, r->uri); + note_digest_auth_failure(r, conf, resp, 0); + return AUTH_REQUIRED; + } + + if (!conf->pwfile) + return DECLINED; + + if (!(conf->ha1 = get_hash(r, conn->user, conf->realm, conf->pwfile))) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: user `%s' in realm `%s' not found: %s", + conn->user, conf->realm, r->uri); + note_digest_auth_failure(r, conf, resp, 0); + return AUTH_REQUIRED; + } + + if (resp->message_qop == NULL) { + /* old (rfc-2069) style digest */ + if (strcmp(resp->digest, old_digest(r, resp, conf->ha1))) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: user %s: password mismatch: %s", conn->user, + r->uri); + note_digest_auth_failure(r, conf, resp, 0); + return AUTH_REQUIRED; + } + } + else { + const char *exp_digest; + int match = 0, idx; + for (idx=0; conf->qop_list[idx] != NULL; idx++) { + if (!strcasecmp(conf->qop_list[idx], resp->message_qop)) { + match = 1; + break; + } + } + + if (!match + && !(conf->qop_list[0] == NULL + && !strcasecmp(resp->message_qop, "auth"))) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: invalid qop `%s' received: %s", + resp->message_qop, r->uri); + note_digest_auth_failure(r, conf, resp, 0); + return AUTH_REQUIRED; + } + + if (check_nc(r, resp, conf) != OK) { + note_digest_auth_failure(r, conf, resp, 0); + return AUTH_REQUIRED; + } + + exp_digest = new_digest(r, resp, conf); + if (!exp_digest) { + /* we failed to allocate a client struct */ + return SERVER_ERROR; + } + if (strcmp(resp->digest, exp_digest)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: user %s: password mismatch: %s", conn->user, + r->uri); + note_digest_auth_failure(r, conf, resp, 0); + return AUTH_REQUIRED; + } + } + + /* Note: this check is done last so that a "stale=true" can be + generated if the nonce is old */ + if ((res = check_nonce(r, resp, conf))) + return res; + + return OK; +} + + +/* + * Checking ID + */ + +static table *groups_for_user(request_rec *r, const char *user, + const char *grpfile) +{ + configfile_t *f; + table *grps = ap_make_table(r->pool, 15); + pool *sp; + char l[MAX_STRING_LEN]; + const char *group_name, *ll, *w; + + if (!(f = ap_pcfg_openfile(r->pool, grpfile))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "Digest: Could not open group file: %s", grpfile); + return NULL; + } + + sp = ap_make_sub_pool(r->pool); + + while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { + if ((l[0] == '#') || (!l[0])) + continue; + ll = l; + ap_clear_pool(sp); + + group_name = ap_getword(sp, &ll, ':'); + + while (ll[0]) { + w = ap_getword_conf(sp, &ll); + if (!strcmp(w, user)) { + ap_table_setn(grps, ap_pstrdup(r->pool, group_name), "in"); + break; + } + } + } + + ap_cfg_closefile(f); + ap_destroy_pool(sp); + return grps; +} + + +static int digest_check_auth(request_rec *r) +{ + const digest_config_rec *conf = + (digest_config_rec *) ap_get_module_config(r->per_dir_config, + &digest_auth_module); + const char *user = r->connection->user; + int m = r->method_number; + int method_restricted = 0; + register int x; + const char *t, *w; + table *grpstatus; + const array_header *reqs_arr; + require_line *reqs; + + if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest")) + return DECLINED; + + reqs_arr = ap_requires(r); + /* If there is no "requires" directive, then any user will do. + */ + if (!reqs_arr) + return OK; + reqs = (require_line *) reqs_arr->elts; + + if (conf->grpfile) + grpstatus = groups_for_user(r, user, conf->grpfile); + else + grpstatus = NULL; + + for (x = 0; x < reqs_arr->nelts; x++) { + + if (!(reqs[x].method_mask & (1 << m))) + continue; + + method_restricted = 1; + + t = reqs[x].requirement; + w = ap_getword_white(r->pool, &t); + if (!strcasecmp(w, "valid-user")) + return OK; + else if (!strcasecmp(w, "user")) { + while (t[0]) { + w = ap_getword_conf(r->pool, &t); + if (!strcmp(user, w)) + return OK; + } + } + else if (!strcasecmp(w, "group")) { + if (!grpstatus) + return DECLINED; + + while (t[0]) { + w = ap_getword_conf(r->pool, &t); + if (ap_table_get(grpstatus, w)) + return OK; + } + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: access to %s failed, reason: unknown require " + "directive \"%s\"", r->uri, reqs[x].requirement); + return DECLINED; + } + } + + if (!method_restricted) + return OK; + + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: access to %s failed, reason: user %s not allowed access", + r->uri, user); + + note_digest_auth_failure(r, conf, + (digest_header_rec *) ap_get_module_config(r->request_config, + &digest_auth_module), + 0); + return AUTH_REQUIRED; +} + + +/* + * Authorization-Info header code + */ + +#ifdef SEND_DIGEST +static const char *hdr(const table *tbl, const char *name) +{ + const char *val = ap_table_get(tbl, name); + if (val) + return val; + else + return ""; +} +#endif + +static int add_auth_info(request_rec *r) +{ + const digest_config_rec *conf = + (digest_config_rec *) ap_get_module_config(r->per_dir_config, + &digest_auth_module); + digest_header_rec *resp = + (digest_header_rec *) ap_get_module_config(r->request_config, + &digest_auth_module); + const char *ai = NULL, *digest = NULL, *nextnonce = ""; + + if (resp == NULL || !resp->needed_auth || conf == NULL) + return OK; + + + /* rfc-2069 digest + */ + if (resp->message_qop == NULL) { + /* old client, so calc rfc-2069 digest */ + +#ifdef SEND_DIGEST + /* most of this totally bogus because the handlers don't set the + * headers until the final handler phase (I wonder why this phase + * is called fixup when there's almost nothing you can fix up...) + * + * Because it's basically impossible to get this right (e.g. the + * Content-length is never set yet when we get here, and we can't + * calc the entity hash) it's best to just leave this #def'd out. + */ + char *entity_info = + ap_md5(r->pool, + (unsigned char *) ap_pstrcat(r->pool, resp->raw_request_uri, + ":", + r->content_type ? r->content_type : ap_default_type(r), ":", + hdr(r->headers_out, "Content-Length"), ":", + r->content_encoding ? r->content_encoding : "", ":", + hdr(r->headers_out, "Last-Modified"), ":", + r->no_cache && !ap_table_get(r->headers_out, "Expires") ? + ap_gm_timestr_822(r->pool, r->request_time) : + hdr(r->headers_out, "Expires"), + NULL)); + digest = + ap_md5(r->pool, + (unsigned char *)ap_pstrcat(r->pool, conf->ha1, ":", + resp->nonce, ":", + r->method, ":", + ap_gm_timestr_822(r->pool, r->request_time), ":", + entity_info, ":", + ap_md5(r->pool, (unsigned char *) ""), /* H(entity) - TBD */ + NULL)); +#endif + } + + + /* setup nextnonce + */ + if (conf->nonce_lifetime > 0) { + /* send nextnonce if current nonce will expire in less than 30 secs */ + if (difftime(r->request_time, resp->nonce_time) > (conf->nonce_lifetime-NEXTNONCE_DELTA)) { + nextnonce = ap_pstrcat(r->pool, ", nextnonce=\"", + gen_nonce(r->pool, r->request_time, + resp->opaque, r->server, conf), + "\"", NULL); + if (resp->client) + resp->client->nonce_count = 0; + } + } + else if (conf->nonce_lifetime == 0 && resp->client) { + const char *nonce = gen_nonce(r->pool, 0, resp->opaque, r->server, + conf); + nextnonce = ap_pstrcat(r->pool, ", nextnonce=\"", nonce, "\"", NULL); + memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1); + } + /* else nonce never expires, hence no nextnonce */ + + + /* do rfc-2069 digest + */ + if (conf->qop_list[0] && !strcasecmp(conf->qop_list[0], "none") + && resp->message_qop == NULL) { + /* use only RFC-2069 format */ + if (digest) + ai = ap_pstrcat(r->pool, "digest=\"", digest, "\"", nextnonce,NULL); + else + ai = nextnonce; + } + else { + const char *resp_dig, *ha1, *a2, *ha2; + + /* calculate rspauth attribute + */ + if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess")) { + ha1 = get_session_HA1(r, resp, conf, 0); + if (!ha1) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Digest: internal error: couldn't find session " + "info for user %s", resp->username); + return !OK; + } + } + else + ha1 = conf->ha1; + + if (resp->message_qop && !strcasecmp(resp->message_qop, "auth-int")) + a2 = ap_pstrcat(r->pool, ":", resp->uri, ":", + ap_md5(r->pool, (const unsigned char *) ""), NULL); /* TBD */ + else + a2 = ap_pstrcat(r->pool, ":", resp->uri, NULL); + ha2 = ap_md5(r->pool, (const unsigned char *)a2); + + resp_dig = ap_md5(r->pool, + (unsigned char *)ap_pstrcat(r->pool, ha1, ":", + resp->nonce, ":", + resp->nonce_count, ":", + resp->cnonce, ":", + resp->message_qop ? + resp->message_qop : "", + ":", ha2, NULL)); + + /* assemble Authentication-Info header + */ + ai = ap_pstrcat(r->pool, + "rspauth=\"", resp_dig, "\"", + nextnonce, + resp->cnonce ? ", cnonce=\"" : "", + resp->cnonce ? ap_escape_quotes(r->pool, resp->cnonce) : + "", + resp->cnonce ? "\"" : "", + resp->nonce_count ? ", nc=" : "", + resp->nonce_count ? resp->nonce_count : "", + resp->message_qop ? ", qop=" : "", + resp->message_qop ? resp->message_qop : "", + digest ? "digest=\"" : "", + digest ? digest : "", + digest ? "\"" : "", + NULL); + } + + if (ai && ai[0]) + ap_table_mergen(r->headers_out, + r->proxyreq == STD_PROXY ? "Proxy-Authentication-Info" + : "Authentication-Info", + ai); + return OK; +} + + +module MODULE_VAR_EXPORT digest_auth_module = +{ + STANDARD_MODULE_STUFF, + initialize_module, /* initializer */ + create_digest_dir_config, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + digest_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + authenticate_digest_user, /* check_user_id */ + digest_check_auth, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + add_auth_info, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + update_nonce_count /* post read-request */ +}; + diff --git a/APACHE_1_3_42/src/modules/experimental/mod_backtrace.c b/APACHE_1_3_42/src/modules/experimental/mod_backtrace.c new file mode 100644 index 0000000000..fc61773957 --- /dev/null +++ b/APACHE_1_3_42/src/modules/experimental/mod_backtrace.c @@ -0,0 +1,204 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined(__linux__) && !defined(__FreeBSD__) +#error This module is currently only implemented for Linux and FreeBSD. +#endif + +/* + * Documentation: + * + * mod_backtrace is an experimental module for Apache httpd 1.3 which + * collects backtraces when a child process crashes. Currently it is + * implemented only on Linux and FreeBSD, but other platforms could be + * supported in the future. You should verify that it works reasonably + * on your system before putting it in production. + * + * It implements a fatal exception hook that will be called when a child + * process crashes. In the exception hook it uses system library routines + * to obtain information about the call stack, and it writes the call + * stack to a log file or the web server error log. The backtrace is a + * critical piece of information when determining the failing software + * component that caused the crash. Note that the backtrace written by + * mod_backtrace may not have as much information as a debugger can + * display from a core dump. + * + * Apache httpd requirements for mod_backtrace: + * + * Apache httpd >= 1.3.30 must be built with the AP_ENABLE_EXCEPTION_HOOK + * symbol defined and mod_so enabled. AP_ENABLE_EXCEPTION_HOOK is already + * defined in ap_config.h for some platforms, including AIX, Linux, + * Solaris, and HP-UX. It can be enabled for other platforms by including + * -DAP_ENABLE_EXCEPTION_HOOK in CFLAGS when the configure script is + * invoked. + * + * Compiling mod_backtrace: + * + * Linux: + * apxs -ci mod_backtrace.c + * + * FreeBSD: + * install libexecinfo from the Ports system then + * apxs -ci -L/usr/local/lib -lexecinfo mod_backtrace.c + * + * Activating mod_backtrace: + * + * 1. Load it like any other DSO: + * LoadModule backtrace_module libexec/mod_backtrace.so + * ... + * AddModule mod_backtrace.c + * + * 2. Enable exception hooks for modules like mod_backtrace: + * EnableExceptionHook On + * + * 3. Choose where backtrace information should be written. + * If you want backtraces from crashes to be reported some place other + * than the error log, use the BacktraceLog directive to specify a + * fully-qualified filename for the log to which backtraces will be + * written. Note that the web server user id (e.g., "nobody") must + * be able to create or append to this log file, as the log file is + * not opened until a crash occurs. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" + +#include <fcntl.h> +#include <unistd.h> +#include <execinfo.h> + +static char *log_fname; + +static void bt_show_backtrace(int sig) +{ + char msgbuf[128]; + size_t size; + void *array[20]; + extern int main(); + int logfd; + time_t now; + char msg_prefix[60]; + char *newline; + int using_errorlog = 1; + + time(&now); + ap_snprintf(msg_prefix, sizeof msg_prefix, + "[%s pid %ld mod_backtrace", + asctime(localtime(&now)), + (long)getpid()); + newline = strchr(msg_prefix, '\n'); /* dang asctime() */ + if (newline) { /* silly we are */ + *newline = ']'; + } + + if (log_fname) { + logfd = open(log_fname, O_WRONLY|O_APPEND|O_CREAT, 0644); + if (logfd == -1) { + logfd = 2; /* unix, so fd 2 is the web server error log */ + ap_snprintf(msgbuf, sizeof msgbuf, + "%s error %d opening %s\n", + msg_prefix, errno, log_fname); + write(logfd, msgbuf, strlen(msgbuf)); + } + else { + using_errorlog = 0; + } + } + else { + logfd = 2; + } + + ap_snprintf(msgbuf, sizeof msgbuf, + "%s backtrace for signal %d\n", + msg_prefix, sig); + write(logfd, msgbuf, strlen(msgbuf)); + + /* the address of main() can be useful if we're on old + * glibc and get only addresses for stack frames... knowing + * where main() is then is a useful clue + */ + ap_snprintf(msgbuf, sizeof msgbuf, + "%s main() is at %pp\n", + msg_prefix, + main);/* don't you DARE put parens after "main" */ + write(logfd, msgbuf, strlen(msgbuf)); + + size = backtrace(array, sizeof array / sizeof array[0]); + backtrace_symbols_fd(array, size, logfd); + ap_snprintf(msgbuf, sizeof msgbuf, + "%s end of report\n", + msg_prefix); + write(logfd, msgbuf, strlen(msgbuf)); + if (!using_errorlog) { + close(logfd); + } +} + +static void bt_exception_hook(ap_exception_info_t *ei) +{ + bt_show_backtrace(ei->sig); +} + +static void bt_init(server_rec *s, pool *p) +{ + int rc = ap_add_fatal_exception_hook(bt_exception_hook); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, s, + "fatal exception hooks are not enabled; please " + "enable them with the EnableExceptionHook directive " + "or disable mod_backtrace"); + } +} + +static const char *bt_cmd_file(cmd_parms *cmd, void *dconf, char *fname) +{ + log_fname = ap_pstrdup(cmd->pool, fname); + return NULL; +} + +static const command_rec bt_command_table[] = { + { + "BacktraceLog", bt_cmd_file, NULL, RSRC_CONF, TAKE1, "the fully-qualified filename of the mod_backtrace logfile" + } + , + { + NULL + } +}; + +module MODULE_VAR_EXPORT backtrace_module = { + STANDARD_MODULE_STUFF, + bt_init, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + bt_command_table, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child init */ + NULL, /* child exit */ + NULL /* post read request */ +}; diff --git a/APACHE_1_3_42/src/modules/experimental/mod_mmap_static.c b/APACHE_1_3_42/src/modules/experimental/mod_mmap_static.c new file mode 100644 index 0000000000..10af90537b --- /dev/null +++ b/APACHE_1_3_42/src/modules/experimental/mod_mmap_static.c @@ -0,0 +1,358 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_mmap_static: mmap a config-time list of files for faster serving + * + * v0.04 + * + * Author: Dean Gaudet <dgaudet@arctic.org> + * + * v0.01: initial implementation + * v0.02: get rid of the extra stat() in the core by filling in what we know + * v0.03: get rid of the cached match from the xlat routine since there are + * many cases where the request is modified between it and the + * handler... so we do the binary search twice, but the second time + * we can use st_ino and st_dev to speed it up. + * v0.04: work around mod_rewrite, which sets r->filename to the uri first + */ + +/* + Documentation: + + The concept is simple. Some sites have a set of static files that are + really busy, and change infrequently (or even on a regular schedule). + Save time by mmap()ing these files into memory and avoid a lot of the + crap required to do normal file serving. Place directives such as: + + mmapfile /path/to/file1 + mmapfile /path/to/file2 + ... + + into your configuration. These files are only mmap()d when the server + is restarted, so if you change the list, or if the files are changed, + then you'll need to restart the server. + + To reiterate that point: if the files are modified *in place* + without restarting the server you may end up serving requests that + are completely bogus. You should update files by unlinking the old + copy and putting a new copy in place. Most tools such as rdist and + mv do this. + + There's no such thing as inheriting these files across vhosts or + whatever... place the directives in the main server only. + + Known problems: + + Don't use Alias or RewriteRule to move these files around... unless + you feel like paying for an extra stat() on each request. This is + a deficiency in the Apache API that will hopefully be solved some day. + The file will be served out of the mmap cache, but there will be + an extra stat() that's a waste. +*/ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <sys/mman.h> + +#define CORE_PRIVATE + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_core.h" + +module MODULE_VAR_EXPORT mmap_static_module; + +typedef struct { + char *filename; + void *mm; + struct stat finfo; +} a_file; + +typedef struct { + array_header *files; + array_header *inode_sorted; +} a_server_config; + + +static void *create_server_config(pool *p, server_rec *s) +{ + a_server_config *sconf = ap_palloc(p, sizeof(*sconf)); + + sconf->files = ap_make_array(p, 20, sizeof(a_file)); + sconf->inode_sorted = NULL; + return sconf; +} + +static void cleanup_mmap(void *sconfv) +{ + a_server_config *sconf = sconfv; + size_t n; + a_file *file; + + n = sconf->files->nelts; + file = (a_file *)sconf->files->elts; + while(n) { + munmap(file->mm, file->finfo.st_size); + ++file; + --n; + } +} + +static const char *mmapfile(cmd_parms *cmd, void *dummy, char *filename) +{ + a_server_config *sconf; + a_file *new_file; + a_file tmp; + int fd; + caddr_t mm; + + if (stat(filename, &tmp.finfo) == -1) { + ap_log_error(APLOG_MARK, APLOG_WARNING, cmd->server, + "mmap_static: unable to stat(%s), skipping", filename); + return NULL; + } + if ((tmp.finfo.st_mode & S_IFMT) != S_IFREG) { + ap_log_error(APLOG_MARK, APLOG_WARNING, cmd->server, + "mmap_static: %s isn't a regular file, skipping", filename); + return NULL; + } + ap_block_alarms(); + fd = open(filename, O_RDONLY, 0); + if (fd == -1) { + ap_log_error(APLOG_MARK, APLOG_WARNING, cmd->server, + "mmap_static: unable to open(%s, O_RDONLY), skipping", filename); + return NULL; + } + mm = mmap(NULL, tmp.finfo.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (mm == (caddr_t)-1) { + int save_errno = errno; + close(fd); + ap_unblock_alarms(); + errno = save_errno; + ap_log_error(APLOG_MARK, APLOG_WARNING, cmd->server, + "mmap_static: unable to mmap %s, skipping", filename); + return NULL; + } + close(fd); + tmp.mm = mm; + tmp.filename = ap_pstrdup(cmd->pool, filename); + sconf = ap_get_module_config(cmd->server->module_config, &mmap_static_module); + new_file = ap_push_array(sconf->files); + *new_file = tmp; + if (sconf->files->nelts == 1) { + /* first one, register the cleanup */ + ap_register_cleanup(cmd->pool, sconf, cleanup_mmap, ap_null_cleanup); + } + ap_unblock_alarms(); + return NULL; +} + +static command_rec mmap_static_cmds[] = +{ + { + "mmapfile", mmapfile, NULL, RSRC_CONF, ITERATE, + "A space separated list of files to mmap at config time" + }, + { + NULL + } +}; + +static int file_compare(const void *av, const void *bv) +{ + const a_file *a = av; + const a_file *b = bv; + + return strcmp(a->filename, b->filename); +} + +static int inode_compare(const void *av, const void *bv) +{ + const a_file *a = *(a_file **)av; + const a_file *b = *(a_file **)bv; + long c; + + c = a->finfo.st_ino - b->finfo.st_ino; + if (c == 0) { + return a->finfo.st_dev - b->finfo.st_dev; + } + return c; +} + +static void mmap_init(server_rec *s, pool *p) +{ + a_server_config *sconf; + array_header *inodes; + a_file *elts; + int nelts; + int i; + + /* sort the elements of the main_server, by filename */ + sconf = ap_get_module_config(s->module_config, &mmap_static_module); + elts = (a_file *)sconf->files->elts; + nelts = sconf->files->nelts; + qsort(elts, nelts, sizeof(a_file), file_compare); + + /* build an index by inode as well, speeds up the search in the handler */ + inodes = ap_make_array(p, nelts, sizeof(a_file *)); + sconf->inode_sorted = inodes; + for (i = 0; i < nelts; ++i) { + *(a_file **)ap_push_array(inodes) = &elts[i]; + } + qsort(inodes->elts, nelts, sizeof(a_file *), inode_compare); + + /* and make the virtualhosts share the same thing */ + for (s = s->next; s; s = s->next) { + ap_set_module_config(s->module_config, &mmap_static_module, sconf); + } +} + +/* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a + * bit of a kludge, because we really want to run after core_translate runs. + */ + +static int mmap_static_xlat(request_rec *r) +{ + a_server_config *sconf; + a_file tmp; + a_file *match; + int res; + + sconf = ap_get_module_config(r->server->module_config, &mmap_static_module); + + /* we only operate when at least one mmapfile directive was used */ + if (ap_is_empty_table(sconf->files)) + return DECLINED; + + /* we require other modules to first set up a filename */ + res = core_module.translate_handler(r); + if (res == DECLINED || !r->filename) { + return res; + } + tmp.filename = r->filename; + match = (a_file *)bsearch(&tmp, sconf->files->elts, sconf->files->nelts, + sizeof(a_file), file_compare); + if (match == NULL) { + return DECLINED; + } + + /* shortcircuit the get_path_info() stat() calls and stuff */ + r->finfo = match->finfo; + return OK; +} + + +static int mmap_static_handler(request_rec *r) +{ + a_server_config *sconf; + a_file tmp; + a_file *ptmp; + a_file **pmatch; + a_file *match; + int rangestatus, errstatus; + + /* we don't handle anything but GET */ + if (r->method_number != M_GET) return DECLINED; + + /* file doesn't exist, we won't be dealing with it */ + if (r->finfo.st_mode == 0) return DECLINED; + + sconf = ap_get_module_config(r->server->module_config, &mmap_static_module); + tmp.finfo.st_dev = r->finfo.st_dev; + tmp.finfo.st_ino = r->finfo.st_ino; + ptmp = &tmp; + pmatch = (a_file **)bsearch(&ptmp, sconf->inode_sorted->elts, + sconf->inode_sorted->nelts, sizeof(a_file *), inode_compare); + if (pmatch == NULL) { + return DECLINED; + } + match = *pmatch; + + /* note that we would handle GET on this resource */ + r->allowed |= (1 << M_GET); + + /* This handler has no use for a request body (yet), but we still + * need to read and discard it if the client sent one. + */ + if ((errstatus = ap_discard_request_body(r)) != OK) + return errstatus; + + ap_update_mtime(r, match->finfo.st_mtime); + ap_set_last_modified(r); + ap_set_etag(r); + if (((errstatus = ap_meets_conditions(r)) != OK) + || (errstatus = ap_set_content_length (r, match->finfo.st_size))) { + return errstatus; + } + +#ifdef CHARSET_EBCDIC + /* check Content Type to see if ebcdic conversion is appropriate */ + ap_checkconv(r); +#endif + rangestatus = ap_set_byterange(r); + ap_send_http_header(r); + + if (!r->header_only) { + if (!rangestatus) { + ap_send_mmap (match->mm, r, 0, match->finfo.st_size); + } + else { + long offset, length; + while (ap_each_byterange(r, &offset, &length)) { + ap_send_mmap(match->mm, r, offset, length); + } + } + } + return OK; +} + + +static const handler_rec mmap_static_handlers[] = +{ + { "*/*", mmap_static_handler }, + { NULL } +}; + +module MODULE_VAR_EXPORT mmap_static_module = +{ + STANDARD_MODULE_STUFF, + mmap_init, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + create_server_config, /* server config */ + NULL, /* merge server config */ + mmap_static_cmds, /* command handlers */ + mmap_static_handlers, /* handlers */ + mmap_static_xlat, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/experimental/mod_whatkilledus.c b/APACHE_1_3_42/src/modules/experimental/mod_whatkilledus.c new file mode 100644 index 0000000000..bc861885df --- /dev/null +++ b/APACHE_1_3_42/src/modules/experimental/mod_whatkilledus.c @@ -0,0 +1,335 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Documentation: + * + * mod_whatkilledus is an experimental module for Apache httpd 1.3 which + * tracks the current request and logs a report of the active request + * when a child process crashes. You should verify that it works reasonably + * on your system before putting it in production. + * + * mod_whatkilledus is called during request processing to save information + * about the current request. It also implements a fatal exception hook + * that will be called when a child process crashes. + * + * Apache httpd requirements for mod_whatkilledus: + * + * Apache httpd >= 1.3.30 must be built with the AP_ENABLE_EXCEPTION_HOOK + * symbol defined and mod_so enabled. AP_ENABLE_EXCEPTION_HOOK is already + * defined in ap_config.h for some platforms, including AIX, Linux, + * Solaris, and HP-UX. It can be enabled for other platforms by including + * -DAP_ENABLE_EXCEPTION_HOOK in CFLAGS when the configure script is + * invoked. + * + * Compiling mod_whatkilledus: + * + * AIX: + * apxs -ci -I/path/to/apache/src/main -Wl,-bE:mod_whatkilledus.exp mod_whatkilledus.c + * + * other: + * apxs -ci -I/path/to/apache/src/main mod_whatkilledus.c + * + * Activating mod_whatkilledus: + * + * 1. Load it like any other DSO, but the AddModule should come + * last so that if another module causes a crash early in + * request processing mod_whatkilledus will have already + * had a chance to save information about the request. + * + * LoadModule whatkilledus_module libexec/mod_whatkilledus.so + * ... + * AddModule mod_whatkilledus.c + * + * 2. Enable exception hooks for modules like mod_whatkilledus: + * EnableExceptionHook On + * + * 3. Choose where the report on current activity should be written. If + * you want it reported to some place other than the error log, use the + * WhatKilledUsLog directive to specify a fully-qualified filename for + * the log. Note that the web server user id (e.g., "nobody") must + * be able to create or append to this log file, as the log file is + * not opened until a crash occurs. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" + +#include "test_char.h" /* an odd one since it is not installed */ + +/* this module is not thread-safe; it is intended for Apache 1.3 on + * platforms that use single-threaded child processes for handling + * client connections + */ +static char *log_fname; +static char *local_addr; +static char *remote_addr; +static char *request_plus_headers; +static char buffer[2048]; + +static void exception_hook(ap_exception_info_t *ei) +{ + int msg_len; + int logfd; + char msg_prefix[60]; + time_t now; + char *newline; + int using_errorlog = 1; + + time(&now); + ap_snprintf(msg_prefix, sizeof msg_prefix, + "[%s pid %ld mod_whatkilledus", + asctime(localtime(&now)), + (long)getpid()); + newline = strchr(msg_prefix, '\n'); /* dang asctime() */ + if (newline) { /* silly we are */ + *newline = ']'; + } + + if (log_fname) { + logfd = open(log_fname, O_WRONLY|O_APPEND|O_CREAT, 0644); + if (logfd == -1) { + logfd = 2; /* unix, so fd 2 is the web server error log */ + ap_snprintf(buffer, sizeof buffer, + "%s error %d opening %s\n", + msg_prefix, errno, log_fname); + write(logfd, buffer, strlen(buffer)); + } + else { + using_errorlog = 0; + } + } + else { + logfd = 2; + } + + msg_len = ap_snprintf(buffer, sizeof buffer, + "%s sig %d crash\n", + msg_prefix, ei->sig); + write(logfd, buffer, msg_len); + + if (local_addr) { + msg_len = ap_snprintf(buffer, sizeof buffer, + "%s active connection: %s->%s\n", + msg_prefix, remote_addr, local_addr); + } + else { + msg_len = ap_snprintf(buffer, sizeof buffer, + "%s no active connection at crash\n", + msg_prefix); + } + + write(logfd, buffer, msg_len); + + if (request_plus_headers) { + msg_len = ap_snprintf(buffer, sizeof buffer, + "%s active request:\n", + msg_prefix); + write(logfd, buffer, msg_len); + write(logfd, request_plus_headers, strlen(request_plus_headers)); + } + else { + msg_len = ap_snprintf(buffer, sizeof buffer, + "%s no request active at crash\n", + msg_prefix); + write(logfd, buffer, msg_len); + } + msg_len = ap_snprintf(buffer, sizeof buffer, + "%s end of report\n", + msg_prefix); + write(logfd, buffer, msg_len); + if (!using_errorlog) { + close(logfd); + } +} + +static void init(server_rec *s, pool *p) +{ + int rc = ap_add_fatal_exception_hook(exception_hook); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, s, + "fatal exception hooks are not enabled; please " + "enable them with the EnableExceptionHook directive " + "or disable mod_whatkilledus"); + } +} + +static void clear_conn_info(void *ignored) +{ + local_addr = remote_addr = NULL; +} + +static void save_conn_info(request_rec *r) +{ + conn_rec *c = r->connection; + local_addr = ap_psprintf(c->pool, "%pI", &c->local_addr); + remote_addr = ap_psprintf(c->pool, "%pI", &c->remote_addr); + + ap_register_cleanup(c->pool, NULL, clear_conn_info, ap_null_cleanup); +} + +static void clear_req_info(void *ignored) +{ + request_plus_headers = NULL; +} + +#define FIELD_SEPARATOR "|" +#define KEYVAL_SEPARATOR ":" + +/* graciously lifted from mod_log_forensic */ + +static int count_string(const char *p) +{ + int n; + + for (n = 0; *p; ++p, ++n) { + if (test_char_table[*(unsigned char *)p] & T_ESCAPE_FORENSIC) { + n += 2; + } + } + return n; +} + +static int count_headers(void *in_len, const char *key, const char *value) +{ + int *len = in_len; + + *len += strlen(FIELD_SEPARATOR); + *len += count_string(key); + *len += strlen(KEYVAL_SEPARATOR); + *len += count_string(value); + + return 1; +} + +static char *copy_and_escape(char *loc, const char *str) { + /* mod_log_forensic will SIGABRT here if it messed up the count + * and overflowed; mod_whatkilledus will segfault here or will + * SIGABRT back in the caller if that happens + */ + for ( ; *str; ++str) { + if (test_char_table[*(unsigned char *)str] & T_ESCAPE_FORENSIC) { + *loc++ = '%'; + sprintf(loc, "%02x", *(unsigned char *)str); + loc += 2; + } + else { + *loc++ = *str; + } + } + *loc = '\0'; + return loc; +} + +static int copy_headers(void *in_ch, const char *key, const char *value) +{ + char **ch = in_ch; + + strcpy(*ch, FIELD_SEPARATOR); + *ch += strlen(FIELD_SEPARATOR); + + *ch = copy_and_escape(*ch, key); + + strcpy(*ch, KEYVAL_SEPARATOR); + *ch += strlen(KEYVAL_SEPARATOR); + + *ch = copy_and_escape(*ch, value); + + return 1; +} + +static void save_req_info(request_rec *r) +{ + /* to save for the request: + * r->the_request + + * foreach header: + * '|' + header field + */ + int len = strlen(r->the_request); + char *ch; + ap_table_do(count_headers, &len, r->headers_in, NULL); + + request_plus_headers = ap_palloc(r->pool, len + 2 /* 2 for the '\n' + '\0' at end */); + ch = request_plus_headers; + strcpy(ch, r->the_request); + ch += strlen(ch); + + ap_table_do(copy_headers, &ch, r->headers_in, NULL); + *ch = '\n'; + *(ch + 1) = '\0'; + + ap_assert(ch == request_plus_headers + len); + + ap_register_cleanup(r->pool, NULL, clear_req_info, ap_null_cleanup); +} + +static int post_read(request_rec *r) +{ + if (r->prev) { /* we were already called for this internal redirect */ + return DECLINED; + } + + /* save whatever info, like client, which vhost, which port + * (to know SSL or not), etc. + */ + if (!local_addr) { /* first request on this connection */ + save_conn_info(r); + } + + save_req_info(r); + + return DECLINED; +} + +static const char *cmd_file(cmd_parms *cmd, void *dconf, char *fname) +{ + log_fname = ap_pstrdup(cmd->pool, fname); + return NULL; +} + +static const command_rec command_table[] = { + { + "WhatKilledUsLog", cmd_file, NULL, RSRC_CONF, TAKE1, "the fully-qualified filename of the mod_whatkilledus logfile" + } + , + { + NULL + } +}; + +module MODULE_VAR_EXPORT whatkilledus_module = { + STANDARD_MODULE_STUFF, + init, /* initializer */ + NULL, /* create per-dir config */ + NULL, /* merge per-dir config */ + NULL, /* server config */ + NULL, /* merge server config */ + command_table, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + post_read /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/experimental/mod_whatkilledus.exp b/APACHE_1_3_42/src/modules/experimental/mod_whatkilledus.exp new file mode 100644 index 0000000000..f17ce67664 --- /dev/null +++ b/APACHE_1_3_42/src/modules/experimental/mod_whatkilledus.exp @@ -0,0 +1 @@ +whatkilledus_module diff --git a/APACHE_1_3_42/src/modules/extra/.indent.pro b/APACHE_1_3_42/src/modules/extra/.indent.pro new file mode 100644 index 0000000000..a9fbe9f9a1 --- /dev/null +++ b/APACHE_1_3_42/src/modules/extra/.indent.pro @@ -0,0 +1,54 @@ +-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1 +-TBUFF +-TFILE +-TTRANS +-TUINT4 +-T_trans +-Tallow_options_t +-Tapache_sfio +-Tarray_header +-Tbool_int +-Tbuf_area +-Tbuff_struct +-Tbuffy +-Tcmd_how +-Tcmd_parms +-Tcommand_rec +-Tcommand_struct +-Tconn_rec +-Tcore_dir_config +-Tcore_server_config +-Tdir_maker_func +-Tevent +-Tglobals_s +-Thandler_func +-Thandler_rec +-Tjoblist_s +-Tlisten_rec +-Tmerger_func +-Tmode_t +-Tmodule +-Tmodule_struct +-Tmutex +-Tn_long +-Tother_child_rec +-Toverrides_t +-Tparent_score +-Tpid_t +-Tpiped_log +-Tpool +-Trequest_rec +-Trequire_line +-Trlim_t +-Tscoreboard +-Tsemaphore +-Tserver_addr_rec +-Tserver_rec +-Tserver_rec_chain +-Tshort_score +-Ttable +-Ttable_entry +-Tthread +-Tu_wide_int +-Tvtime_t +-Twide_int diff --git a/APACHE_1_3_42/src/modules/extra/Makefile.tmpl b/APACHE_1_3_42/src/modules/extra/Makefile.tmpl new file mode 100644 index 0000000000..67c70fab94 --- /dev/null +++ b/APACHE_1_3_42/src/modules/extra/Makefile.tmpl @@ -0,0 +1,6 @@ + +#Dependencies + +$(OBJS) $(OBJS_PIC): Makefile + +# DO NOT REMOVE diff --git a/APACHE_1_3_42/src/modules/proxy/.indent.pro b/APACHE_1_3_42/src/modules/proxy/.indent.pro new file mode 100644 index 0000000000..64099d767f --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/.indent.pro @@ -0,0 +1,64 @@ +-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1 +-TBUFF +-TFILE +-TTRANS +-TUINT4 +-T_trans +-Tallow_options_t +-Tapache_sfio +-Tarray_header +-Tbool_int +-Tbuf_area +-Tbuff_struct +-Tbuffy +-Tcache_req +-Tcmd_how +-Tcmd_parms +-Tcommand_rec +-Tcommand_struct +-Tconn_rec +-Tcore_dir_config +-Tcore_server_config +-Tdir_maker_func +-Tdirconn_entry +-Tevent +-Tglobals_s +-Thandler_func +-Thandler_rec +-Tin_addr +-Tjoblist_s +-Tlisten_rec +-Tlong61_t +-Tmerger_func +-Tmode_t +-Tmodule +-Tmodule_struct +-Tmutex +-Tn_long +-Toff_t +-Tother_child_rec +-Toverrides_t +-Tparent_score +-Tper_thread_data +-Tpid_t +-Tpiped_log +-Tpool +-Tproxy_alias +-Trequest_rec +-Trequire_line +-Trlim_t +-Tscoreboard +-Tsemaphore +-Tserver_addr_rec +-Tserver_rec +-Tserver_rec_chain +-Tshort_score +-Tsockaddr +-Tsockaddr_in +-Ttable +-Ttable_entry +-Tthread +-Tu_wide_int +-Tvtime_t +-Twide_int +-Tproxy_server_conf diff --git a/APACHE_1_3_42/src/modules/proxy/Makefile.OS2 b/APACHE_1_3_42/src/modules/proxy/Makefile.OS2 new file mode 100644 index 0000000000..260f005080 --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/Makefile.OS2 @@ -0,0 +1,6 @@ +# Extra rules for making DLLs for OS/2 + +%.def : %.c + echo "LIBRARY $* INITINSTANCE" > $@ + echo "EXPORTS" >> $@ + grep "^module .*=" $< | sed "s/module .* \(.*\) =/ \1/" >> $@ diff --git a/APACHE_1_3_42/src/modules/proxy/Makefile.libdir b/APACHE_1_3_42/src/modules/proxy/Makefile.libdir new file mode 100644 index 0000000000..7b5254013a --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/Makefile.libdir @@ -0,0 +1,4 @@ +This is a place-holder which indicates to Configure that it shouldn't +provide the default targets when building the Makefile in this directory. +Instead it'll just prepend all the important variable definitions, and +copy the Makefile.tmpl onto the end. diff --git a/APACHE_1_3_42/src/modules/proxy/Makefile.tmpl b/APACHE_1_3_42/src/modules/proxy/Makefile.tmpl new file mode 100644 index 0000000000..f721914b57 --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/Makefile.tmpl @@ -0,0 +1,121 @@ + +LIB=libproxy.$(LIBEXT) + +OBJS=\ + mod_proxy.o \ + proxy_cache.o proxy_connect.o proxy_ftp.o proxy_http.o proxy_util.o +OBJS_PIC=\ + mod_proxy.lo \ + proxy_cache.lo proxy_connect.lo proxy_ftp.lo proxy_http.lo proxy_util.lo + +all: lib + +lib: $(LIB) + +libproxy.a: $(OBJS) + rm -f $@ + ar cr $@ $(OBJS) + $(RANLIB) $@ + +libproxy.so: $(OBJS_PIC) + rm -f $@ + $(LD_SHLIB) $(LDFLAGS_SHLIB) -o $@ $(OBJS_PIC) $(LIBS_SHLIB) + +libproxy.dll: $(OBJS_PIC) mod_proxy.def + if [ "x$(OS)" = "xCygwin" ]; then \ + rm -f $@; \ + if [ -f "$(SRCDIR)/$(SHCORE_IMPLIB)" ]; then \ + $(LD_SHLIB) $(LDFLAGS_SHLIB) -o $*.dll $(OBJS_PIC) $(LIBS_SHLIB) \ + $(SRCDIR)/$(SHCORE_IMPLIB) $(LIBS1); \ + fi \ + else \ + $(LD_SHLIB) $(LDFLAGS_SHLIB) -o $* $(OBJS_PIC) $(LIBS_SHLIB); \ + emxbind -b -q -s -h0 -dmod_proxy.def $* && \ + rm $*; \ + fi + +.SUFFIXES: .o .lo .dll + +.c.o: + $(CC) -c $(INCLUDES) $(CFLAGS) $< + +.c.lo: + $(CC) -c $(INCLUDES) $(CFLAGS) $(CFLAGS_SHLIB) $< && mv $*.o $*.lo + +clean: + rm -f $(OBJS) $(OBJS_PIC) $(LIB) + +distclean: clean + -rm -f Makefile + +# We really don't expect end users to use this rule. It works only with +# gcc, and rebuilds Makefile.tmpl. You have to re-run Configure after +# using it. +depend: + cp Makefile.tmpl Makefile.tmpl.bak \ + && sed -ne '1,/^# DO NOT REMOVE/p' Makefile.tmpl > Makefile.new \ + && gcc -MM $(INCLUDES) $(CFLAGS) *.c >> Makefile.new \ + && sed -e '1,$$s: $(INCDIR)/: $$(INCDIR)/:g' \ + -e '1,$$s: $(OSDIR)/: $$(OSDIR)/:g' Makefile.new \ + > Makefile.tmpl \ + && rm Makefile.new + +#Dependencies + +$(OBJS) $(OBJS_PIC): Makefile + +# DO NOT REMOVE +mod_proxy.o: mod_proxy.c mod_proxy.h $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/explain.h \ + $(INCDIR)/http_log.h $(INCDIR)/http_vhost.h \ + $(INCDIR)/http_request.h +proxy_cache.o: proxy_cache.c mod_proxy.h $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/explain.h \ + $(INCDIR)/http_log.h $(INCDIR)/http_main.h \ + $(INCDIR)/util_date.h $(INCDIR)/multithread.h \ + $(INCDIR)/ap_md5.h +proxy_connect.o: proxy_connect.c mod_proxy.h $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/explain.h \ + $(INCDIR)/http_log.h $(INCDIR)/http_main.h +proxy_ftp.o: proxy_ftp.c mod_proxy.h $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/explain.h \ + $(INCDIR)/http_main.h $(INCDIR)/http_log.h +proxy_http.o: proxy_http.c mod_proxy.h $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/explain.h \ + $(INCDIR)/http_log.h $(INCDIR)/http_main.h \ + $(INCDIR)/http_core.h $(INCDIR)/util_date.h +proxy_util.o: proxy_util.c mod_proxy.h $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/explain.h \ + $(INCDIR)/http_main.h $(INCDIR)/ap_md5.h \ + $(INCDIR)/multithread.h $(INCDIR)/http_log.h \ + $(INCDIR)/util_date.h diff --git a/APACHE_1_3_42/src/modules/proxy/NWGNUmakefile b/APACHE_1_3_42/src/modules/proxy/NWGNUmakefile new file mode 100644 index 0000000000..3625360b63 --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/NWGNUmakefile @@ -0,0 +1,27 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)\NWGNUhead.inc + +# +# build this level's files + +ifeq "$(wildcard NWGNUmakefile.mak)" "NWGNUmakefile.mak" +include NWGNUmakefile.mak +endif + +# +# You can use this target if all that is needed is to copy files to the +# installation area +# +install :: nlms FORCE + diff --git a/APACHE_1_3_42/src/modules/proxy/NWGNUmakefile.mak b/APACHE_1_3_42/src/modules/proxy/NWGNUmakefile.mak new file mode 100644 index 0000000000..9b861bc6a3 --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/NWGNUmakefile.mak @@ -0,0 +1,243 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = Proxy + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Proxy Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Proxy Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start_ws + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop_ws + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/Proxy.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/proxy_util.o \ + $(OBJDIR)/proxy_cache.o \ + $(OBJDIR)/proxy_connect.o \ + $(OBJDIR)/proxy_ftp.o \ + $(OBJDIR)/proxy_http.o \ + $(OBJDIR)/mod_proxy.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libprews.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + @nlmlib.imp \ + @ws2nlm.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + proxy_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + copy $(OBJDIR)\Proxy.nlm $(INSTALL)\APache\modules + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/proxy/mod_proxy.c b/APACHE_1_3_42/src/modules/proxy/mod_proxy.c new file mode 100644 index 0000000000..03433ac04a --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/mod_proxy.c @@ -0,0 +1,988 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mod_proxy.h" + +#define CORE_PRIVATE + +#include "http_log.h" +#include "http_core.h" +#include "http_vhost.h" +#include "http_request.h" + +/* Some WWW schemes and their default ports; this is basically /etc/services */ +/* This will become global when the protocol abstraction comes */ +static struct proxy_services defports[] = +{ + {"http", DEFAULT_HTTP_PORT}, + {"ftp", DEFAULT_FTP_PORT}, + {"https", DEFAULT_HTTPS_PORT}, + {"gopher", DEFAULT_GOPHER_PORT}, + {"nntp", DEFAULT_NNTP_PORT}, + {"wais", DEFAULT_WAIS_PORT}, + {"snews", DEFAULT_SNEWS_PORT}, + {"prospero", DEFAULT_PROSPERO_PORT}, + {NULL, -1} /* unknown port */ +}; + +/* + * A Web proxy module. Stages: + * + * translate_name: set filename to proxy:<URL> + * type_checker: set type to PROXY_MAGIC_TYPE if filename begins proxy: + * fix_ups: convert the URL stored in the filename to the + * canonical form. + * handler: handle proxy requests + */ + +/* -------------------------------------------------------------- */ +/* Translate the URL into a 'filename' */ + +static int alias_match(const char *uri, const char *alias_fakename) +{ + const char *end_fakename = alias_fakename + strlen(alias_fakename); + const char *aliasp = alias_fakename, *urip = uri; + + while (aliasp < end_fakename) { + if (*aliasp == '/') { + /* + * any number of '/' in the alias matches any number in the + * supplied URI, but there must be at least one... + */ + if (*urip != '/') + return 0; + + while (*aliasp == '/') + ++aliasp; + while (*urip == '/') + ++urip; + } + else { + /* Other characters are compared literally */ + if (*urip++ != *aliasp++) + return 0; + } + } + + /* Check last alias path component matched all the way */ + + if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/') + return 0; + + /* + * Return number of characters from URI which matched (may be greater + * than length of alias, since we may have matched doubled slashes) + */ + + return urip - uri; +} + +/* Detect if an absoluteURI should be proxied or not. Note that we + * have to do this during this phase because later phases are + * "short-circuiting"... i.e. translate_names will end when the first + * module returns OK. So for example, if the request is something like: + * + * GET http://othervhost/cgi-bin/printenv HTTP/1.0 + * + * mod_alias will notice the /cgi-bin part and ScriptAlias it and + * short-circuit the proxy... just because of the ordering in the + * configuration file. + */ +static int proxy_detect(request_rec *r) +{ + void *sconf = r->server->module_config; + proxy_server_conf *conf; + + conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + + if (conf->req && r->parsed_uri.scheme) { + /* but it might be something vhosted */ + if (!(r->parsed_uri.hostname + && !strcasecmp(r->parsed_uri.scheme, ap_http_method(r)) + && ap_matches_request_vhost(r, r->parsed_uri.hostname, + r->parsed_uri.port_str ? r->parsed_uri.port : ap_default_port(r)))) { + r->proxyreq = STD_PROXY; + r->uri = r->unparsed_uri; + r->filename = ap_pstrcat(r->pool, "proxy:", r->uri, NULL); + r->handler = "proxy-server"; + } + } + /* We need special treatment for CONNECT proxying: it has no scheme part */ + else if (conf->req && r->method_number == M_CONNECT + && r->parsed_uri.hostname + && r->parsed_uri.port_str) { + r->proxyreq = STD_PROXY; + r->uri = r->unparsed_uri; + r->filename = ap_pstrcat(r->pool, "proxy:", r->uri, NULL); + r->handler = "proxy-server"; + } + return DECLINED; +} + +static int proxy_trans(request_rec *r) +{ + void *sconf = r->server->module_config; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + int i, len; + struct proxy_alias *ent = (struct proxy_alias *)conf->aliases->elts; + + if (r->proxyreq != NOT_PROXY) { + /* + * someone has already set up the proxy, it was possibly ourselves in + * proxy_detect + */ + return OK; + } + + /* + * XXX: since r->uri has been manipulated already we're not really + * compliant with RFC1945 at this point. But this probably isn't an + * issue because this is a hybrid proxy/origin server. + */ + + for (i = 0; i < conf->aliases->nelts; i++) { + len = alias_match(r->uri, ent[i].fake); + + if (len > 0) { + r->filename = ap_pstrcat(r->pool, "proxy:", ent[i].real, + r->uri + len, NULL); + r->handler = "proxy-server"; + r->proxyreq = PROXY_PASS; + return OK; + } + } + return DECLINED; +} + +/* -------------------------------------------------------------- */ +/* Fixup the filename */ + +/* + * Canonicalise the URL + */ +static int proxy_fixup(request_rec *r) +{ + char *url, *p; + + if (r->proxyreq == NOT_PROXY || strncmp(r->filename, "proxy:", 6) != 0) + return DECLINED; + + url = &r->filename[6]; + +/* canonicalise each specific scheme */ + if (strncasecmp(url, "http:", 5) == 0) + return ap_proxy_http_canon(r, url + 5, "http", DEFAULT_HTTP_PORT); + else if (strncasecmp(url, "ftp:", 4) == 0) + return ap_proxy_ftp_canon(r, url + 4); + + p = strchr(url, ':'); + if (p == NULL || p == url) + return HTTP_BAD_REQUEST; + + return OK; /* otherwise; we've done the best we can */ +} + +static void proxy_init(server_rec *r, pool *p) +{ + ap_proxy_garbage_init(r, p); +} + + + +/* Send a redirection if the request contains a hostname which is not */ +/* fully qualified, i.e. doesn't have a domain name appended. Some proxy */ +/* servers like Netscape's allow this and access hosts from the local */ +/* domain in this case. I think it is better to redirect to a FQDN, since */ +/* these will later be found in the bookmarks files. */ +/* The "ProxyDomain" directive determines what domain will be appended */ +static int proxy_needsdomain(request_rec *r, const char *url, const char *domain) +{ + char *nuri; + const char *ref; + + /* We only want to worry about GETs */ + if (r->proxyreq == NOT_PROXY || r->method_number != M_GET || !r->parsed_uri.hostname) + return DECLINED; + + /* If host does contain a dot already, or it is "localhost", decline */ + if (strchr(r->parsed_uri.hostname, '.') != NULL + || strcasecmp(r->parsed_uri.hostname, "localhost") == 0) + return DECLINED; /* host name has a dot already */ + + ref = ap_table_get(r->headers_in, "Referer"); + + /* Reassemble the request, but insert the domain after the host name */ + /* Note that the domain name always starts with a dot */ + r->parsed_uri.hostname = ap_pstrcat(r->pool, r->parsed_uri.hostname, + domain, NULL); + nuri = ap_unparse_uri_components(r->pool, + &r->parsed_uri, + UNP_REVEALPASSWORD); + + ap_table_set(r->headers_out, "Location", nuri); + ap_log_rerror(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, r, + "Domain missing: %s sent to %s%s%s", r->uri, + ap_unparse_uri_components(r->pool, &r->parsed_uri, + UNP_OMITUSERINFO), + ref ? " from " : "", ref ? ref : ""); + + return HTTP_MOVED_PERMANENTLY; +} + +/* -------------------------------------------------------------- */ +/* Invoke handler */ + +static int proxy_handler(request_rec *r) +{ + char *url, *scheme, *p; + void *sconf = r->server->module_config; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + array_header *proxies = conf->proxies; + struct proxy_remote *ents = (struct proxy_remote *) proxies->elts; + int i, rc; + cache_req *cr; + int direct_connect = 0; + const char *maxfwd_str; + + if (r->proxyreq == NOT_PROXY || strncmp(r->filename, "proxy:", 6) != 0) + return DECLINED; + + if (r->method_number == M_TRACE && + (maxfwd_str = ap_table_get(r->headers_in, "Max-Forwards")) != NULL) { + long maxfwd = ap_strtol(maxfwd_str, NULL, 10); + if (maxfwd < 1) { + int access_status; + r->proxyreq = NOT_PROXY; + if ((access_status = ap_send_http_trace(r))) + ap_die(access_status, r); + else + ap_finalize_request_protocol(r); + return OK; + } + ap_table_setn(r->headers_in, "Max-Forwards", + ap_psprintf(r->pool, "%ld", (maxfwd > 0) ? maxfwd - 1 : 0)); + } + + if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) + return rc; + + if (r->method_number == M_TRACE) { + core_server_config *coreconf = (core_server_config *) + ap_get_module_config(r->server->module_config, &core_module); + + if (coreconf->trace_enable == AP_TRACE_DISABLE) + { + /* Allow "error-notes" string to be printed by ap_send_error_response() + * Note; this goes nowhere, canned error response need an overhaul. + */ + ap_table_setn(r->notes, "error-notes", + "TRACE forbidden by server configuration"); + ap_table_setn(r->notes, "verbose-error-to", "*"); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, + "proxy: TRACE forbidden by server configuration"); + return HTTP_FORBIDDEN; + } + + if (coreconf->trace_enable != AP_TRACE_EXTENDED + && (r->read_length || r->read_chunked || r->remaining)) + { + /* Allow "error-notes" string to be printed by ap_send_error_response() + * Note; this goes nowhere, canned error response need an overhaul. + */ + ap_table_setn(r->notes, "error-notes", + "TRACE with request body is not allowed"); + ap_table_setn(r->notes, "verbose-error-to", "*"); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, + "proxy: TRACE with request body is not allowed"); + return HTTP_REQUEST_ENTITY_TOO_LARGE; + } + } + + url = r->filename + 6; + p = strchr(url, ':'); + if (p == NULL) + return HTTP_BAD_REQUEST; + + /* Try serve the request from the cache. If we succeed, we leave. */ + rc = ap_proxy_cache_check(r, url, &conf->cache, &cr); + if (rc != DECLINED) + return rc; + + /* If the host doesn't have a domain name, add one and redirect. */ + if (conf->domain != NULL) { + rc = proxy_needsdomain(r, url, conf->domain); + if (ap_is_HTTP_REDIRECT(rc)) + return HTTP_MOVED_PERMANENTLY; + } + + *p = '\0'; + scheme = ap_pstrdup(r->pool, url); + *p = ':'; + + /* Check URI's destination host against NoProxy hosts */ + /* Bypass ProxyRemote server lookup if configured as NoProxy */ + /* we only know how to handle communication to a proxy via http */ + /* if (strcasecmp(scheme, "http") == 0) */ + { + int ii; + struct dirconn_entry *list = (struct dirconn_entry *)conf->dirconn->elts; + + for (direct_connect = ii = 0; ii < conf->dirconn->nelts && !direct_connect; ii++) { + direct_connect = list[ii].matcher(&list[ii], r); + } +#if DEBUGGING + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r, + (direct_connect) ? "NoProxy for %s" : "UseProxy for %s", + r->uri); +#endif + } + +/* firstly, try a proxy, unless a NoProxy directive is active */ + + if (!direct_connect) + for (i = 0; i < proxies->nelts; i++) { + p = strchr(ents[i].scheme, ':'); /* is it a partial URL? */ + if (strcmp(ents[i].scheme, "*") == 0 || + (p == NULL && strcasecmp(scheme, ents[i].scheme) == 0) || + (p != NULL && + strncasecmp(url, ents[i].scheme, strlen(ents[i].scheme)) == 0)) { + /* + * CONNECT is a special method that bypasses the normal proxy + * code. + */ + if (r->method_number == M_CONNECT) + rc = ap_proxy_connect_handler(r, cr, url, ents[i].hostname, + ents[i].port); +/* we only know how to handle communication to a proxy via http */ + else if (strcasecmp(ents[i].protocol, "http") == 0) + rc = ap_proxy_http_handler(r, cr, url, ents[i].hostname, + ents[i].port); + else + rc = DECLINED; + + /* an error or success */ + if (rc != DECLINED && rc != HTTP_BAD_GATEWAY) + return rc; + /* we failed to talk to the upstream proxy */ + } + } + + /* otherwise, try it direct */ + /* N.B. what if we're behind a firewall, where we must use a proxy or + * give up?? + */ + + /* handle the scheme */ + if (r->method_number == M_CONNECT) { + return ap_proxy_connect_handler(r, cr, url, NULL, 0); + } + if (strcasecmp(scheme, "http") == 0) { + return ap_proxy_http_handler(r, cr, url, NULL, 0); + } + if (strcasecmp(scheme, "ftp") == 0) { + return ap_proxy_ftp_handler(r, cr, url); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, r, + "proxy: No protocol handler was valid for the URL %s. " + "If you are using a DSO version of mod_proxy, make sure " + "the proxy submodules are included in the configuration " + "using LoadModule.", r->uri); + return HTTP_FORBIDDEN; + } +} + +/* -------------------------------------------------------------- */ +/* Setup configurable data */ + +static void * + create_proxy_config(pool *p, server_rec *s) +{ + proxy_server_conf *ps = ap_pcalloc(p, sizeof(proxy_server_conf)); + + ps->proxies = ap_make_array(p, 10, sizeof(struct proxy_remote)); + ps->aliases = ap_make_array(p, 10, sizeof(struct proxy_alias)); + ps->raliases = ap_make_array(p, 10, sizeof(struct proxy_alias)); + ps->noproxies = ap_make_array(p, 10, sizeof(struct noproxy_entry)); + ps->dirconn = ap_make_array(p, 10, sizeof(struct dirconn_entry)); + ps->nocaches = ap_make_array(p, 10, sizeof(struct nocache_entry)); + ps->allowed_connect_ports = ap_make_array(p, 10, sizeof(int)); + ps->domain = NULL; + ps->viaopt = via_off; /* initially backward compatible with 1.3.1 */ + ps->viaopt_set = 0; /* 0 means default */ + ps->req = 0; + ps->req_set = 0; + ps->recv_buffer_size = 0; /* this default was left unset for some + * reason */ + ps->recv_buffer_size_set = 0; + ps->io_buffer_size = IOBUFSIZE; + ps->io_buffer_size_set = 0; + + ps->cache.root = NULL; + ps->cache.space = DEFAULT_CACHE_SPACE; + ps->cache.space_set = 0; + ps->cache.maxexpire = DEFAULT_CACHE_MAXEXPIRE; + ps->cache.maxexpire_set = 0; + ps->cache.defaultexpire = DEFAULT_CACHE_EXPIRE; + ps->cache.defaultexpire_set = 0; + ps->cache.lmfactor = DEFAULT_CACHE_LMFACTOR; + ps->cache.lmfactor_set = 0; + ps->cache.gcinterval = DEFAULT_CACHE_GCINTERVAL; + ps->cache.gcinterval_set = 1; + /* at these levels, the cache can have 2^18 directories (256,000) */ + ps->cache.dirlevels = 3; + ps->cache.dirlevels_set = 0; + ps->cache.dirlength = 1; + ps->cache.dirlength_set = 0; + ps->cache.cache_completion = (float)DEFAULT_CACHE_COMPLETION; + ps->cache.cache_completion_set = 0; + + return ps; +} + +static void * + merge_proxy_config(pool *p, void *basev, + void *overridesv) +{ + proxy_server_conf *ps = ap_pcalloc(p, sizeof(proxy_server_conf)); + proxy_server_conf *base = (proxy_server_conf *)basev; + proxy_server_conf *overrides = (proxy_server_conf *)overridesv; + + ps->proxies = ap_append_arrays(p, base->proxies, overrides->proxies); + ps->aliases = ap_append_arrays(p, base->aliases, overrides->aliases); + ps->raliases = ap_append_arrays(p, base->raliases, overrides->raliases); + ps->noproxies = ap_append_arrays(p, base->noproxies, overrides->noproxies); + ps->dirconn = ap_append_arrays(p, base->dirconn, overrides->dirconn); + ps->nocaches = ap_append_arrays(p, base->nocaches, overrides->nocaches); + ps->allowed_connect_ports = ap_append_arrays(p, base->allowed_connect_ports, overrides->allowed_connect_ports); + + ps->domain = (overrides->domain == NULL) ? base->domain : overrides->domain; + ps->viaopt = (overrides->viaopt_set == 0) ? base->viaopt : overrides->viaopt; + ps->req = (overrides->req_set == 0) ? base->req : overrides->req; + ps->recv_buffer_size = (overrides->recv_buffer_size_set == 0) ? base->recv_buffer_size : overrides->recv_buffer_size; + ps->io_buffer_size = (overrides->io_buffer_size_set == 0) ? base->io_buffer_size : overrides->io_buffer_size; + + ps->cache.root = (overrides->cache.root == NULL) ? base->cache.root : overrides->cache.root; + ps->cache.space = (overrides->cache.space_set == 0) ? base->cache.space : overrides->cache.space; + ps->cache.maxexpire = (overrides->cache.maxexpire_set == 0) ? base->cache.maxexpire : overrides->cache.maxexpire; + ps->cache.defaultexpire = (overrides->cache.defaultexpire_set == 0) ? base->cache.defaultexpire : overrides->cache.defaultexpire; + ps->cache.lmfactor = (overrides->cache.lmfactor_set == 0) ? base->cache.lmfactor : overrides->cache.lmfactor; + ps->cache.gcinterval = (overrides->cache.gcinterval_set == 0) ? base->cache.gcinterval : overrides->cache.gcinterval; + /* at these levels, the cache can have 2^18 directories (256,000) */ + ps->cache.dirlevels = (overrides->cache.dirlevels_set == 0) ? base->cache.dirlevels : overrides->cache.dirlevels; + ps->cache.dirlength = (overrides->cache.dirlength_set == 0) ? base->cache.dirlength : overrides->cache.dirlength; + ps->cache.cache_completion = (overrides->cache.cache_completion_set == 0) ? base->cache.cache_completion : overrides->cache.cache_completion; + + return ps; +} + +static const char * + add_proxy(cmd_parms *cmd, void *dummy, char *f, char *r) +{ + server_rec *s = cmd->server; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(s->module_config, &proxy_module); + struct proxy_remote *new; + char *p, *q; + int port; + + p = strchr(r, ':'); + if (p == NULL || p[1] != '/' || p[2] != '/' || p[3] == '\0') + return "ProxyRemote: Bad syntax for a remote proxy server"; + q = strchr(p + 3, ':'); + if (q != NULL) { + if (sscanf(q + 1, "%u", &port) != 1 || port > 65535) + return "ProxyRemote: Bad syntax for a remote proxy server (bad port number)"; + *q = '\0'; + } + else + port = -1; + *p = '\0'; + if (strchr(f, ':') == NULL) + ap_str_tolower(f); /* lowercase scheme */ + ap_str_tolower(p + 3); /* lowercase hostname */ + + if (port == -1) { + int i; + for (i = 0; defports[i].scheme != NULL; i++) + if (strcasecmp(defports[i].scheme, r) == 0) + break; + port = defports[i].port; + } + + new = ap_push_array(conf->proxies); + new->scheme = f; + new->protocol = r; + new->hostname = p + 3; + new->port = port; + return NULL; +} + +static const char * + add_pass(cmd_parms *cmd, void *dummy, char *f, char *r) +{ + server_rec *s = cmd->server; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(s->module_config, &proxy_module); + struct proxy_alias *new; + + new = ap_push_array(conf->aliases); + new->fake = f; + new->real = r; + return NULL; +} + +static const char * + add_pass_reverse(cmd_parms *cmd, void *dummy, char *f, char *r) +{ + server_rec *s = cmd->server; + proxy_server_conf *conf; + struct proxy_alias *new; + + conf = (proxy_server_conf *)ap_get_module_config(s->module_config, + &proxy_module); + new = ap_push_array(conf->raliases); + new->fake = f; + new->real = r; + return NULL; +} + +static const char * + set_proxy_exclude(cmd_parms *parms, void *dummy, char *arg) +{ + server_rec *s = parms->server; + proxy_server_conf *conf = + ap_get_module_config(s->module_config, &proxy_module); + struct noproxy_entry *new; + struct noproxy_entry *list = (struct noproxy_entry *) conf->noproxies->elts; + struct hostent hp; + int found = 0; + int i; + + /* Don't duplicate entries */ + for (i = 0; i < conf->noproxies->nelts; i++) { + if (strcasecmp(arg, list[i].name) == 0) /* ignore case for host names */ + found = 1; + } + + if (!found) { + new = ap_push_array(conf->noproxies); + new->name = arg; + /* Don't do name lookups on things that aren't dotted */ + if (strchr(arg, '.') != NULL && ap_proxy_host2addr(new->name, &hp) == NULL) + /* + * @@@FIXME: This copies only the first of (possibly many) IP + * addrs + */ + memcpy(&new->addr, hp.h_addr, sizeof(struct in_addr)); + else + new->addr.s_addr = 0; + } + return NULL; +} + +/* + * Set the ports CONNECT can use + */ +static const char * + set_allowed_ports(cmd_parms *parms, void *dummy, char *arg) +{ + server_rec *s = parms->server; + proxy_server_conf *conf = + ap_get_module_config(s->module_config, &proxy_module); + int *New; + + if (!ap_isdigit(arg[0])) + return "AllowCONNECT: port number must be numeric"; + + New = ap_push_array(conf->allowed_connect_ports); + *New = atoi(arg); + return NULL; +} + +/* Similar to set_proxy_exclude(), but defining directly connected hosts, + * which should never be accessed via the configured ProxyRemote servers + */ +static const char * + set_proxy_dirconn(cmd_parms *parms, void *dummy, char *arg) +{ + server_rec *s = parms->server; + proxy_server_conf *conf = + ap_get_module_config(s->module_config, &proxy_module); + struct dirconn_entry *New; + struct dirconn_entry *list = (struct dirconn_entry *)conf->dirconn->elts; + int found = 0; + int i; + + /* Don't duplicate entries */ + for (i = 0; i < conf->dirconn->nelts; i++) { + if (strcasecmp(arg, list[i].name) == 0) + found = 1; + } + + if (!found) { + New = ap_push_array(conf->dirconn); + New->name = arg; + New->hostentry = NULL; + + if (ap_proxy_is_ipaddr(New, parms->pool)) { +#if DEBUGGING + fprintf(stderr, "Parsed addr %s\n", inet_ntoa(New->addr)); + fprintf(stderr, "Parsed mask %s\n", inet_ntoa(New->mask)); +#endif + } + else if (ap_proxy_is_domainname(New, parms->pool)) { + ap_str_tolower(New->name); +#if DEBUGGING + fprintf(stderr, "Parsed domain %s\n", New->name); +#endif + } + else if (ap_proxy_is_hostname(New, parms->pool)) { + ap_str_tolower(New->name); +#if DEBUGGING + fprintf(stderr, "Parsed host %s\n", New->name); +#endif + } + else { + ap_proxy_is_word(New, parms->pool); +#if DEBUGGING + fprintf(stderr, "Parsed word %s\n", New->name); +#endif + } + } + return NULL; +} + +static const char * + set_proxy_domain(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + + if (arg[0] != '.') + return "ProxyDomain: domain name must start with a dot."; + + psf->domain = arg; + return NULL; +} + +static const char * + set_proxy_req(cmd_parms *parms, void *dummy, int flag) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + + psf->req = flag; + psf->req_set = 1; + return NULL; +} + + +static const char * + set_cache_size(cmd_parms *parms, char *struct_ptr, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + int val; + + if (sscanf(arg, "%d", &val) != 1) + return "CacheSize value must be an integer (kBytes)"; + psf->cache.space = val; + psf->cache.space_set = 1; + return NULL; +} + +static const char * + set_cache_root(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + + psf->cache.root = arg; + + return NULL; +} + +static const char * + set_cache_factor(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + double val; + + if (sscanf(arg, "%lg", &val) != 1) + return "CacheLastModifiedFactor value must be a float"; + psf->cache.lmfactor = val; + psf->cache.lmfactor_set = 1; + + return NULL; +} + +static const char * + set_cache_maxex(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + double val; + + if (sscanf(arg, "%lg", &val) != 1) + return "CacheMaxExpire value must be a float"; + psf->cache.maxexpire = (int)(val * (double)SEC_ONE_HR); + psf->cache.maxexpire_set = 1; + return NULL; +} + +static const char * + set_cache_defex(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + double val; + + if (sscanf(arg, "%lg", &val) != 1) + return "CacheDefaultExpire value must be a float"; + psf->cache.defaultexpire = (int)(val * (double)SEC_ONE_HR); + psf->cache.defaultexpire_set = 1; + return NULL; +} + +static const char * + set_cache_gcint(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + double val; + + if (sscanf(arg, "%lg", &val) != 1) + return "CacheGcInterval value must be a float"; + psf->cache.gcinterval = (int)(val * (double)SEC_ONE_HR); + psf->cache.gcinterval_set = 1; + return NULL; +} + +static const char * + set_cache_dirlevels(cmd_parms *parms, char *struct_ptr, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + int val; + + val = atoi(arg); + if (val < 1) + return "CacheDirLevels value must be an integer greater than 0"; + if (val * psf->cache.dirlength > CACHEFILE_LEN) + return "CacheDirLevels*CacheDirLength value must not be higher than 20"; + psf->cache.dirlevels = val; + psf->cache.dirlevels_set = 1; + return NULL; +} + +static const char * + set_cache_dirlength(cmd_parms *parms, char *struct_ptr, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + int val; + + val = atoi(arg); + if (val < 1) + return "CacheDirLength value must be an integer greater than 0"; + if (val * psf->cache.dirlevels > CACHEFILE_LEN) + return "CacheDirLevels*CacheDirLength value must not be higher than 20"; + psf->cache.dirlength = val; + psf->cache.dirlength_set = 1; + return NULL; +} + +static const char * + set_cache_exclude(cmd_parms *parms, void *dummy, char *arg) +{ + server_rec *s = parms->server; + proxy_server_conf *conf = + ap_get_module_config(s->module_config, &proxy_module); + struct nocache_entry *new; + struct nocache_entry *list = (struct nocache_entry *) conf->nocaches->elts; + struct hostent hp; + int found = 0; + int i; + + /* Don't duplicate entries */ + for (i = 0; i < conf->nocaches->nelts; i++) { + if (strcasecmp(arg, list[i].name) == 0) /* ignore case for host names */ + found = 1; + } + + if (!found) { + new = ap_push_array(conf->nocaches); + new->name = arg; + /* Don't do name lookups on things that aren't dotted */ + if (strchr(arg, '.') != NULL && ap_proxy_host2addr(new->name, &hp) == NULL) + /* + * @@@FIXME: This copies only the first of (possibly many) IP + * addrs + */ + memcpy(&new->addr, hp.h_addr, sizeof(struct in_addr)); + else + new->addr.s_addr = 0; + } + return NULL; +} + +static const char * + set_recv_buffer_size(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + int s = atoi(arg); + if (s < 512 && s != 0) { + return "ProxyReceiveBufferSize must be >= 512 bytes, or 0 for system default."; + } + + psf->recv_buffer_size = s; + psf->recv_buffer_size_set = 1; + return NULL; +} + +static const char * + set_io_buffer_size(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + long s = atol(arg); + + psf->io_buffer_size = ((s > IOBUFSIZE) ? s : IOBUFSIZE); + psf->io_buffer_size_set = 1; + return NULL; +} + +static const char * + set_cache_completion(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + int s = atoi(arg); + if (s > 100 || s < 0) { + return "CacheForceCompletion must be <= 100 percent, " + "or 0 for system default."; + } + + if (s > 0) + psf->cache.cache_completion = ((float)s / 100); + + psf->cache.cache_completion_set = 1; + return NULL; +} + +static const char * + set_via_opt(cmd_parms *parms, void *dummy, char *arg) +{ + proxy_server_conf *psf = + ap_get_module_config(parms->server->module_config, &proxy_module); + + if (strcasecmp(arg, "Off") == 0) + psf->viaopt = via_off; + else if (strcasecmp(arg, "On") == 0) + psf->viaopt = via_on; + else if (strcasecmp(arg, "Block") == 0) + psf->viaopt = via_block; + else if (strcasecmp(arg, "Full") == 0) + psf->viaopt = via_full; + else { + return "ProxyVia must be one of: " + "off | on | full | block"; + } + + psf->viaopt_set = 1; + return NULL; +} + +static const handler_rec proxy_handlers[] = +{ + {"proxy-server", proxy_handler}, + {NULL} +}; + +static const command_rec proxy_cmds[] = +{ + {"ProxyRequests", set_proxy_req, NULL, RSRC_CONF, FLAG, + "on if the true proxy requests should be accepted"}, + {"ProxyRemote", add_proxy, NULL, RSRC_CONF, TAKE2, + "a scheme, partial URL or '*' and a proxy server"}, + {"ProxyPass", add_pass, NULL, RSRC_CONF, TAKE2, + "a virtual path and a URL"}, + {"ProxyPassReverse", add_pass_reverse, NULL, RSRC_CONF, TAKE2, + "a virtual path and a URL for reverse proxy behaviour"}, + {"ProxyBlock", set_proxy_exclude, NULL, RSRC_CONF, ITERATE, + "A list of names, hosts or domains to which the proxy will not connect"}, + {"ProxyReceiveBufferSize", set_recv_buffer_size, NULL, RSRC_CONF, TAKE1, + "Receive buffer size for outgoing HTTP and FTP connections in bytes"}, + {"ProxyIOBufferSize", set_io_buffer_size, NULL, RSRC_CONF, TAKE1, + "IO buffer size for outgoing HTTP and FTP connections in bytes"}, + {"NoProxy", set_proxy_dirconn, NULL, RSRC_CONF, ITERATE, + "A list of domains, hosts, or subnets to which the proxy will connect directly"}, + {"ProxyDomain", set_proxy_domain, NULL, RSRC_CONF, TAKE1, + "The default intranet domain name (in absence of a domain in the URL)"}, + {"AllowCONNECT", set_allowed_ports, NULL, RSRC_CONF, ITERATE, + "A list of ports which CONNECT may connect to"}, + {"CacheRoot", set_cache_root, NULL, RSRC_CONF, TAKE1, + "The directory to store cache files"}, + {"CacheSize", set_cache_size, NULL, RSRC_CONF, TAKE1, + "The maximum disk space used by the cache in Kb"}, + {"CacheMaxExpire", set_cache_maxex, NULL, RSRC_CONF, TAKE1, + "The maximum time in hours to cache a document"}, + {"CacheDefaultExpire", set_cache_defex, NULL, RSRC_CONF, TAKE1, + "The default time in hours to cache a document"}, + {"CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF, TAKE1, + "The factor used to estimate Expires date from LastModified date"}, + {"CacheGcInterval", set_cache_gcint, NULL, RSRC_CONF, TAKE1, + "The interval between garbage collections, in hours"}, + {"CacheDirLevels", set_cache_dirlevels, NULL, RSRC_CONF, TAKE1, + "The number of levels of subdirectories in the cache"}, + {"CacheDirLength", set_cache_dirlength, NULL, RSRC_CONF, TAKE1, + "The number of characters in subdirectory names"}, + {"NoCache", set_cache_exclude, NULL, RSRC_CONF, ITERATE, + "A list of names, hosts or domains for which caching is *not* provided"}, + {"CacheForceCompletion", set_cache_completion, NULL, RSRC_CONF, TAKE1, + "Force a http cache completion after this percentage is loaded"}, + {"ProxyVia", set_via_opt, NULL, RSRC_CONF, TAKE1, + "Configure Via: proxy header header to one of: on | off | block | full"}, + {NULL} +}; + +module MODULE_VAR_EXPORT proxy_module = +{ + STANDARD_MODULE_STUFF, + proxy_init, /* initializer */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + create_proxy_config, /* create per-server config structure */ + merge_proxy_config, /* merge per-server config structures */ + proxy_cmds, /* command table */ + proxy_handlers, /* handlers */ + proxy_trans, /* translate_handler */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + proxy_fixup, /* pre-run fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + proxy_detect /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/proxy/mod_proxy.h b/APACHE_1_3_42/src/modules/proxy/mod_proxy.h new file mode 100644 index 0000000000..8d6f41745a --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/mod_proxy.h @@ -0,0 +1,308 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MOD_PROXY_H +#define MOD_PROXY_H + +/* + * Main include file for the Apache proxy + */ + +/* + + Note numerous FIXMEs and CHECKMEs which should be eliminated. + + If TESTING is set, then garbage collection doesn't delete ... probably a good + idea when hacking. + + */ + +#define TESTING 0 + +#include "httpd.h" +#include "http_config.h" +#include "http_protocol.h" + +#include "explain.h" + +extern module MODULE_VAR_EXPORT proxy_module; + + +/* for proxy_canonenc() */ +enum enctype { + enc_path, enc_search, enc_user, enc_fpath, enc_parm +}; + +#define HDR_APP (0) /* append header, for proxy_add_header() */ +#define HDR_REP (1) /* replace header, for proxy_add_header() */ + +/* number of characters in the hash */ +#define HASH_LEN (22*2) + +/* maximum 'CacheDirLevels*CacheDirLength' value */ +#define CACHEFILE_LEN 20 /* must be less than HASH_LEN/2 */ + +#define SEC_ONE_DAY 86400 /* one day, in seconds */ +#define SEC_ONE_HR 3600 /* one hour, in seconds */ + +#define DEFAULT_FTP_DATA_PORT 20 +#define DEFAULT_FTP_PORT 21 +#define DEFAULT_GOPHER_PORT 70 +#define DEFAULT_NNTP_PORT 119 +#define DEFAULT_WAIS_PORT 210 +#define DEFAULT_HTTPS_PORT 443 +#define DEFAULT_SNEWS_PORT 563 +#define DEFAULT_PROSPERO_PORT 1525 /* WARNING: conflict w/Oracle */ + +/* Some WWW schemes and their default ports; this is basically /etc/services */ +struct proxy_services { + const char *scheme; + int port; +}; + +/* static information about a remote proxy */ +struct proxy_remote { + const char *scheme; /* the schemes handled by this proxy, or '*' */ + const char *protocol; /* the scheme used to talk to this proxy */ + const char *hostname; /* the hostname of this proxy */ + int port; /* the port for this proxy */ +}; + +struct proxy_alias { + char *real; + char *fake; +}; + +struct dirconn_entry { + char *name; + struct in_addr addr, mask; + struct hostent *hostentry; + int (*matcher) (struct dirconn_entry * This, request_rec *r); +}; + +struct noproxy_entry { + char *name; + struct in_addr addr; +}; + +struct nocache_entry { + char *name; + struct in_addr addr; +}; + +#define DEFAULT_CACHE_SPACE 5 +#define DEFAULT_CACHE_MAXEXPIRE SEC_ONE_DAY +#define DEFAULT_CACHE_EXPIRE SEC_ONE_HR +#define DEFAULT_CACHE_LMFACTOR (0.1) +#define DEFAULT_CACHE_COMPLETION (0.9) +#define DEFAULT_CACHE_GCINTERVAL SEC_ONE_HR + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +/* static information about the local cache */ +struct cache_conf { + const char *root; /* the location of the cache directory */ + off_t space; /* Maximum cache size (in 1024 bytes) */ + char space_set; + time_t maxexpire; /* Maximum time to keep cached files in secs */ + char maxexpire_set; + time_t defaultexpire; /* default time to keep cached file in secs */ + char defaultexpire_set; + double lmfactor; /* factor for estimating expires date */ + char lmfactor_set; + time_t gcinterval; /* garbage collection interval, in seconds */ + char gcinterval_set; + int dirlevels; /* Number of levels of subdirectories */ + char dirlevels_set; + int dirlength; /* Length of subdirectory names */ + char dirlength_set; + float cache_completion; /* Force cache completion after this point */ + char cache_completion_set; +}; + +typedef struct { + struct cache_conf cache; /* cache configuration */ + array_header *proxies; + array_header *aliases; + array_header *raliases; + array_header *noproxies; + array_header *dirconn; + array_header *nocaches; + array_header *allowed_connect_ports; + char *domain; /* domain name to use in absence of a domain name in the request */ + int req; /* true if proxy requests are enabled */ + char req_set; + enum { + via_off, + via_on, + via_block, + via_full + } viaopt; /* how to deal with proxy Via: headers */ + char viaopt_set; + size_t recv_buffer_size; + char recv_buffer_size_set; + size_t io_buffer_size; + char io_buffer_size_set; +} proxy_server_conf; + +struct hdr_entry { + const char *field; + const char *value; +}; + +/* caching information about a request */ +typedef struct { + request_rec *req; /* the request */ + char *url; /* the URL requested */ + char *filename; /* name of the cache file, + or NULL if no cache */ + char *tempfile; /* name of the temporary file, + or NULL if not caching */ + time_t ims; /* if-Modified-Since date of request, + -1 if no header */ + time_t ius; /* if-Unmodified-Since date of request, + -1 if no header */ + const char *im; /* if-Match etag of request, + NULL if no header */ + const char *inm; /* if-None-Match etag of request, + NULL if no header */ + BUFF *fp; /* the cache file descriptor if the file + is cached and may be returned, + or NULL if the file is not cached + (or must be reloaded) */ + BUFF *origfp; /* the old cache file descriptor if the file has + been revalidated and is being rewritten to + disk */ + time_t expire; /* calculated expire date of cached entity */ + time_t lmod; /* last-modified date of cached entity */ + time_t date; /* the date the cached file was last touched */ + time_t req_time; /* the time the request started */ + time_t resp_time; /* the time the response was received */ + int version; /* update count of the file */ + off_t len; /* content length */ + char *protocol; /* Protocol, and major/minor number, + e.g. HTTP/1.1 */ + int status; /* the status of the cached file */ + unsigned int written; /* total *content* bytes written to cache */ + float cache_completion; /* specific to this request */ + char *resp_line; /* the whole status line + (protocol, code + message) */ + table *req_hdrs; /* the original request headers */ + table *hdrs; /* the original HTTP response headers + of the file */ + char *xcache; /* the X-Cache header value + to be sent to client */ +} cache_req; + +struct per_thread_data { + struct hostent hpbuf; +#ifdef TPF64BIT + in_addr_t ipaddr; +#else + u_long ipaddr; +#endif + char *charpbuf[2]; +}; +/* Function prototypes */ + +/* proxy_cache.c */ + +void ap_proxy_cache_tidy(cache_req *c); +int ap_proxy_cache_check(request_rec *r, char *url, struct cache_conf *conf, + cache_req **cr); +int ap_proxy_cache_update(cache_req *c, table *resp_hdrs, + const int is_HTTP1, int nocache); +void ap_proxy_garbage_coll(request_rec *r); + +/* proxy_connect.c */ + +int ap_proxy_connect_handler(request_rec *r, cache_req *c, char *url, + const char *proxyhost, int proxyport); + +/* proxy_ftp.c */ + +int ap_proxy_ftp_canon(request_rec *r, char *url); +int ap_proxy_ftp_handler(request_rec *r, cache_req *c, char *url); + +/* proxy_http.c */ + +int ap_proxy_http_canon(request_rec *r, char *url, const char *scheme, + int def_port); +int ap_proxy_http_handler(request_rec *r, cache_req *c, char *url, + const char *proxyhost, int proxyport); + +/* proxy_util.c */ + +int ap_proxy_hex2c(const char *x); +void ap_proxy_c2hex(int ch, char *x); +char *ap_proxy_canonenc(pool *p, const char *x, int len, enum enctype t, + enum proxyreqtype isenc); +char *ap_proxy_canon_netloc(pool *p, char **const urlp, char **userp, + char **passwordp, char **hostp, int *port); +const char *ap_proxy_date_canon(pool *p, const char *x); +table *ap_proxy_read_headers(request_rec *r, char *buffer, int size, BUFF *f); +long int ap_proxy_send_fb(BUFF *f, request_rec *r, cache_req *c, off_t len, int nowrite, int chunked, size_t recv_buffer_size); +void ap_proxy_write_headers(cache_req *c, const char *respline, table *t); +int ap_proxy_liststr(const char *list, const char *key, char **val); +void ap_proxy_hash(const char *it, char *val, int ndepth, int nlength); +int ap_proxy_hex2sec(const char *x); +void ap_proxy_sec2hex(int t, char *y); +cache_req *ap_proxy_cache_error(cache_req *r); +int ap_proxyerror(request_rec *r, int statuscode, const char *message); +const char *ap_proxy_host2addr(const char *host, struct hostent *reqhp); +int ap_proxy_is_ipaddr(struct dirconn_entry *This, pool *p); +int ap_proxy_is_domainname(struct dirconn_entry *This, pool *p); +int ap_proxy_is_hostname(struct dirconn_entry *This, pool *p); +int ap_proxy_is_word(struct dirconn_entry *This, pool *p); +int ap_proxy_doconnect(int sock, struct sockaddr_in *addr, request_rec *r); +int ap_proxy_garbage_init(server_rec *, pool *); +/* This function is called by ap_table_do() for all header lines */ +int ap_proxy_send_hdr_line(void *p, const char *key, const char *value); +unsigned ap_proxy_bputs2(const char *data, BUFF *client, cache_req *cache); +time_t ap_proxy_current_age(cache_req *c, const time_t age_value); +BUFF *ap_proxy_open_cachefile(request_rec *r, char *filename); +BUFF *ap_proxy_create_cachefile(request_rec *r, char *filename); +void ap_proxy_clear_connection(pool *p, table *headers); +int ap_proxy_table_replace(table *base, table *overlay); +void ap_proxy_table_unmerge(pool *p, table *t, char *key); +int ap_proxy_read_response_line(BUFF *f, request_rec *r, char *buffer, int size, int *backasswards, int *major, int *minor); + +/* WARNING - PRIVATE DEFINITION BELOW */ + +/* XXX: if you tweak this you should look at is_empty_table() and table_elts() + * in ap_alloc.h + * + * NOTE: this private definition is a duplicate of the one in alloc.c + * It's here for ap_proxy_table_replace() to avoid breaking binary compat + */ +struct table { + /* This has to be first to promote backwards compatibility with + * older modules which cast a table * to an array_header *... + * they should use the table_elts() function for most of the + * cases they do this for. + */ + array_header a; +#ifdef MAKE_TABLE_PROFILE + void *creator; +#endif +}; + +#endif /*MOD_PROXY_H*/ diff --git a/APACHE_1_3_42/src/modules/proxy/proxy_cache.c b/APACHE_1_3_42/src/modules/proxy/proxy_cache.c new file mode 100644 index 0000000000..5a65e9d643 --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/proxy_cache.c @@ -0,0 +1,1768 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Cache and garbage collection routines for Apache proxy */ + +#include "mod_proxy.h" +#include "http_conf_globals.h" +#include "http_log.h" +#include "http_main.h" +#include "http_core.h" +#include "util_date.h" +#ifdef WIN32 +#include <sys/utime.h> +#else +#include <utime.h> +#endif /* WIN32 */ +#include "multithread.h" +#include "ap_md5.h" +#ifdef __TANDEM +#include <sys/types.h> +#include <sys/stat.h> +#endif +#ifdef TPF +#include "os.h" +#endif + +struct gc_ent { + unsigned long int len; + time_t expire; + char file[HASH_LEN + 1]; +}; + +/* Poor man's 61 bit arithmetic */ +typedef struct { + long lower; /* lower 30 bits of result */ + long upper; /* upper 31 bits of result */ +} long61_t; + +/* FIXME: The block size can be different on a `per file system' base. + * This would make automatic detection highly OS specific. + * In the GNU fileutils code for du(1), you can see how complicated it can + * become to detect the block size. And, with BSD-4.x fragments, it + * it even more difficult to get precise results. + * As a compromise (and to improve on the incorrect counting of cache + * size on byte level, omitting directory sizes entirely, which was + * used up to apache-1.3b7) we're rounding to multiples of 512 here. + * Your file system may be using larger blocks (I certainly hope so!) + * but it will hardly use smaller blocks. + * (So this approximation is still closer to reality than the old behavior). + * The best solution would be automatic detection, the next best solution + * IMHO is a sensible default and the possibility to override it. + */ + +#define ROUNDUP2BLOCKS(_bytes) (((_bytes)+block_size-1) & ~(block_size-1)) +static long block_size = 512; /* this must be a power of 2 */ +static long61_t curbytes, cachesize; +static time_t garbage_now, garbage_expire; +static mutex *garbage_mutex = NULL; + + +int ap_proxy_garbage_init(server_rec *r, pool *p) +{ + if (!garbage_mutex) + garbage_mutex = ap_create_mutex(NULL); + + return (0); +} + + +static int sub_garbage_coll(request_rec *r, array_header *files, + const char *cachedir, const char *cachesubdir); +static void help_proxy_garbage_coll(request_rec *r); +static int should_proxy_garbage_coll(request_rec *r); +#if !defined(WIN32) && !defined(MPE) && !defined(OS2) && !defined(NETWARE) && !defined(TPF) +static void detached_proxy_garbage_coll(request_rec *r); +#endif + + +void ap_proxy_garbage_coll(request_rec *r) +{ + static int inside = 0; + + (void)ap_acquire_mutex(garbage_mutex); + if (inside == 1) { + (void)ap_release_mutex(garbage_mutex); + return; + } + else + inside = 1; + (void)ap_release_mutex(garbage_mutex); + + ap_block_alarms(); /* avoid SIGALRM on big cache cleanup */ + if (should_proxy_garbage_coll(r)) +#if !defined(WIN32) && !defined(MPE) && !defined(OS2) && !defined(NETWARE) && !defined(TPF) + detached_proxy_garbage_coll(r); +#else + help_proxy_garbage_coll(r); +#endif + ap_unblock_alarms(); + + (void)ap_acquire_mutex(garbage_mutex); + inside = 0; + (void)ap_release_mutex(garbage_mutex); +} + + +static void add_long61(long61_t *accu, long val) +{ + /* Add in lower 30 bits */ + accu->lower += (val & 0x3FFFFFFFL); + /* add in upper bits, and carry */ + accu->upper += (val >> 30) + ((accu->lower & ~0x3FFFFFFFL) != 0L); + /* Clear carry */ + accu->lower &= 0x3FFFFFFFL; +} + +static void sub_long61(long61_t *accu, long val) +{ + int carry = (val & 0x3FFFFFFFL) > accu->lower; + /* Subtract lower 30 bits */ + accu->lower = accu->lower - (val & 0x3FFFFFFFL) + ((carry) ? 0x40000000 : 0); + /* add in upper bits, and carry */ + accu->upper -= (val >> 30) + carry; +} + +/* Compare two long61's: + * return <0 when left < right + * return 0 when left == right + * return >0 when left > right + */ +static long cmp_long61(long61_t *left, long61_t *right) +{ + return (left->upper == right->upper) ? (left->lower - right->lower) + : (left->upper - right->upper); +} + +/* Compare two gc_ent's, sort them by expiration date */ +static int gcdiff(const void *ap, const void *bp) +{ + const struct gc_ent *a = (const struct gc_ent *) ap; + const struct gc_ent *b = (const struct gc_ent *) bp; + + if (a->expire > b->expire) + return 1; + else if (a->expire < b->expire) + return -1; + else + return 0; +} + +#if !defined(WIN32) && !defined(MPE) && !defined(OS2) && !defined(NETWARE) && !defined(TPF) +static void detached_proxy_garbage_coll(request_rec *r) +{ + pid_t pid; + int status; + pid_t pgrp; + +#if 0 + ap_log_error(APLOG_MARK, APLOG_DEBUG, r->server, + "proxy: Guess what; we fork() again..."); +#endif + switch (pid = fork()) { + case -1: + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy: fork() for cache cleanup failed"); + return; + + case 0: /* Child */ + + /* close all sorts of things, including the socket fd */ + ap_cleanup_for_exec(); + + /* Fork twice to disassociate from the child */ + switch (pid = fork()) { + case -1: + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy: fork(2nd) for cache cleanup failed"); + exit(1); + + case 0: /* Child */ + /* The setpgrp() stuff was snarfed from http_main.c */ +#ifndef NO_SETSID + if ((pgrp = setsid()) == -1) { + perror("setsid"); + fprintf(stderr, "%s: setsid failed\n", + ap_server_argv0); + exit(1); + } +#elif defined(NEXT) || defined(NEWSOS) + if (setpgrp(0, getpid()) == -1 || (pgrp = getpgrp(0)) == -1) { + perror("setpgrp"); + fprintf(stderr, "%S: setpgrp or getpgrp failed\n", + ap_server_argv0); + exit(1); + } +#elif defined(CYGWIN) + /* Cygwin does not take any argument for setpgrp() */ + if ((pgrp = setpgrp()) == -1) { + perror("setpgrp"); + fprintf(stderr, "%S: setpgrp failed\n", + ap_server_argv0); + exit(1); + } +#else + if ((pgrp = setpgrp(getpid(), 0)) == -1) { + perror("setpgrp"); + fprintf(stderr, "%s: setpgrp failed\n", + ap_server_argv0); + exit(1); + } +#endif + help_proxy_garbage_coll(r); + exit(0); + + default: /* Father */ + /* After grandson has been forked off, */ + /* there's nothing else to do. */ + exit(0); + } + default: + /* Wait until grandson has been forked off */ + /* (without wait we'd leave a zombie) */ + waitpid(pid, &status, 0); + return; + } +} +#endif /* ndef WIN32 */ + +#define DOT_TIME "/.time" /* marker */ + +static int should_proxy_garbage_coll(request_rec *r) +{ + void *sconf = r->server->module_config; + proxy_server_conf *pconf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + const struct cache_conf *conf = &pconf->cache; + + const char *cachedir = conf->root; + char *filename; + struct stat buf; + int timefd; + time_t every = conf->gcinterval; + static time_t lastcheck = BAD_DATE; /* static (per-process) data!!! */ + + if (cachedir == NULL || every == -1) + return 0; + + filename = ap_palloc(r->pool, strlen(cachedir) + strlen(DOT_TIME) + 1); + + garbage_now = time(NULL); + /* + * Usually, the modification time of <cachedir>/.time can only increase. + * Thus, even with several child processes having their own copy of + * lastcheck, if time(NULL) still < lastcheck then it's not time for GC + * yet. + */ + if (garbage_now != -1 && lastcheck != BAD_DATE && garbage_now < lastcheck + every) + return 0; + + strcpy(filename, cachedir); + strcat(filename, DOT_TIME); + + /* + * At this point we have a bit of an engineering compromise. We could + * either create and/or mark the .time file (prior to the fork which + * might fail on a resource issue) or wait until we are safely forked. + * The advantage of doing it now in this process is that we get some + * usefull live out of the global last check variable. (XXX which should + * go scoreboard IMHO.) Note that the actual counting is at a later + * moment. + */ + if (stat(filename, &buf) == -1) { /* does not exist */ + if (errno != ENOENT) { + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy: stat(%s)", filename); + return 0; + } + if ((timefd = creat(filename, 0666)) == -1) { + if (errno != EEXIST) + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy: creat(%s)", filename); + else + lastcheck = garbage_now; /* someone else got in there */ + return 0; + } + close(timefd); + } + else { + lastcheck = buf.st_mtime; /* save the time */ + if (garbage_now < lastcheck + every) { + return 0; + } + if (utime(filename, NULL) == -1) + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy: utimes(%s)", filename); + } + + return 1; +} + +static void help_proxy_garbage_coll(request_rec *r) +{ + const char *cachedir; + void *sconf = r->server->module_config; + proxy_server_conf *pconf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + const struct cache_conf *conf = &pconf->cache; + array_header *files; + struct gc_ent *fent; + char *filename; + int i; + + cachedir = conf->root; + filename = ap_palloc(r->pool, strlen(cachedir) + HASH_LEN + 2); + /* configured size is given in kB. Make it bytes, convert to long61_t: */ + cachesize.lower = cachesize.upper = 0; + add_long61(&cachesize, conf->space << 10); + + ap_block_alarms(); /* avoid SIGALRM on big cache cleanup */ + + files = ap_make_array(r->pool, 100, sizeof(struct gc_ent)); + curbytes.upper = curbytes.lower = 0L; + + sub_garbage_coll(r, files, cachedir, "/"); + + if (cmp_long61(&curbytes, &cachesize) < 0L) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "proxy GC: Cache is %ld%% full (nothing deleted)", + (long)(((curbytes.upper << 20) | (curbytes.lower >> 10)) * 100 / conf->space)); + ap_unblock_alarms(); + return; + } + + /* sort the files we found by expiration date */ + qsort(files->elts, files->nelts, sizeof(struct gc_ent), gcdiff); + + for (i = 0; i < files->nelts; i++) { + fent = &((struct gc_ent *) files->elts)[i]; + sprintf(filename, "%s%s", cachedir, fent->file); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "GC Unlinking %s (expiry %ld, garbage_now %ld)", filename, (long)fent->expire, (long)garbage_now); +#if TESTING + fprintf(stderr, "Would unlink %s\n", filename); +#else + if (unlink(filename) == -1) { + if (errno != ENOENT) + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy gc: unlink(%s)", filename); + } + else +#endif + { + sub_long61(&curbytes, ROUNDUP2BLOCKS(fent->len)); + if (cmp_long61(&curbytes, &cachesize) < 0) + break; + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "proxy GC: Cache is %ld%% full (%d deleted)", + (long)(((curbytes.upper << 20) | (curbytes.lower >> 10)) * 100 / conf->space), i); + ap_unblock_alarms(); +} + +static int sub_garbage_coll(request_rec *r, array_header *files, + const char *cachebasedir, const char *cachesubdir) +{ + char line[17 * (3)]; + char cachedir[HUGE_STRING_LEN]; + struct stat buf; + int fd, i; + DIR *dir; +#if defined(NEXT) || defined(WIN32) + struct DIR_TYPE *ent; +#else + struct dirent *ent; +#endif + struct gc_ent *fent; + int nfiles = 0; + char *filename; + + ap_snprintf(cachedir, sizeof(cachedir), "%s%s", cachebasedir, cachesubdir); + filename = ap_palloc(r->pool, strlen(cachedir) + HASH_LEN + 2); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "GC Examining directory %s", cachedir); + dir = opendir(cachedir); + if (dir == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy gc: opendir(%s)", cachedir); + return 0; + } + + while ((ent = readdir(dir)) != NULL) { + if (ent->d_name[0] == '.') + continue; + sprintf(filename, "%s%s", cachedir, ent->d_name); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "GC Examining file %s", filename); +/* is it a temporary file? */ + if (strncmp(ent->d_name, "tmp", 3) == 0) { +/* then stat it to see how old it is; delete temporary files > 1 day old */ + if (stat(filename, &buf) == -1) { + if (errno != ENOENT) + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy gc: stat(%s)", filename); + } + else if (garbage_now != -1 && buf.st_atime < garbage_now - SEC_ONE_DAY && + buf.st_mtime < garbage_now - SEC_ONE_DAY) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "GC unlink %s", filename); + ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, r->server, + "proxy gc: deleting orphaned cache file %s", filename); +#if TESTING + fprintf(stderr, "Would unlink %s\n", filename); +#else + unlink(filename); +#endif + } + continue; + } + ++nfiles; +/* is it another file? */ + /* FIXME: Shouldn't any unexpected files be deleted? */ + /* if (strlen(ent->d_name) != HASH_LEN) continue; */ + +/* under OS/2 use dirent's d_attr to identify a diretory */ +/* under TPF use stat to identify a directory */ +#if defined(OS2) || defined(TPF) +/* is it a directory? */ +#ifdef OS2 + if (ent->d_attr & A_DIR) +#elif defined(TPF) + if (stat(filename, &buf) == -1) { + if (errno != ENOENT) + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy gc: stat(%s)", filename); + } + if (S_ISDIR(buf.st_mode)) +#endif + { + char newcachedir[HUGE_STRING_LEN]; + ap_snprintf(newcachedir, sizeof(newcachedir), + "%s%s/", cachesubdir, ent->d_name); + if (!sub_garbage_coll(r, files, cachebasedir, newcachedir)) { + ap_snprintf(newcachedir, sizeof(newcachedir), + "%s%s", cachedir, ent->d_name); +#if TESTING + fprintf(stderr, "Would remove directory %s\n", newcachedir); +#else + rmdir(newcachedir); +#endif + --nfiles; + } + continue; + } +#endif + +/* read the file */ +#if defined(WIN32) + /* + * On WIN32 open does not work for directories, so we us stat instead + * of fstat to determine if the file is a directory + */ + if (stat(filename, &buf) == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy gc: stat(%s)", filename); + continue; + } + fd = -1; +#else + fd = open(filename, O_RDONLY | O_BINARY); + if (fd == -1) { + if (errno != ENOENT) + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy gc: open(%s)", filename); + continue; + } + if (fstat(fd, &buf) == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy gc: fstat(%s)", filename); + close(fd); + continue; + } +#endif + +/* In OS/2 and TPF this has already been done above */ +#if !defined(OS2) && !defined(TPF) + if (S_ISDIR(buf.st_mode)) { + char newcachedir[HUGE_STRING_LEN]; +#if !defined(WIN32) + /* Win32 used stat, no file to close */ + close(fd); +#endif + ap_snprintf(newcachedir, sizeof(newcachedir), + "%s%s/", cachesubdir, ent->d_name); + if (!sub_garbage_coll(r, files, cachebasedir, newcachedir)) { + ap_snprintf(newcachedir, sizeof(newcachedir), + "%s%s", cachedir, ent->d_name); +#if TESTING + fprintf(stderr, "Would remove directory %s\n", newcachedir); +#else + rmdir(newcachedir); +#endif + --nfiles; + } + else { + /* Directory is not empty. Account for its size: */ + add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size)); + } + continue; + } +#endif + +#if defined(WIN32) + /* + * Since we have determined above that the file is not a directory, + * it should be safe to open it now + */ + fd = open(filename, O_RDONLY | O_BINARY); + if (fd == -1) { + if (errno != ENOENT) + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy gc: open(%s) = %d", filename, errno); + continue; + } +#endif + + i = read(fd, line, 17 * (3) - 1); + close(fd); + if (i == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, r->server, + "proxy gc: read(%s)", filename); + continue; + } + line[i] = '\0'; + garbage_expire = ap_proxy_hex2sec(line + 17 * (2)); + if (!ap_checkmask(line, "&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&") || + garbage_expire == BAD_DATE) { + /* bad file */ + if (garbage_now != -1 && buf.st_atime > garbage_now + SEC_ONE_DAY && + buf.st_mtime > garbage_now + SEC_ONE_DAY) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, r->server, + "proxy: deleting bad cache file with future date: %s", filename); +#if TESTING + fprintf(stderr, "Would unlink bad file %s\n", filename); +#else + unlink(filename); +#endif + } + continue; + } + +/* + * we need to calculate an 'old' factor, and remove the 'oldest' files + * so that the space requirement is met; sort by the expires date of the + * file. + * + */ + fent = (struct gc_ent *) ap_push_array(files); + fent->len = buf.st_size; + fent->expire = garbage_expire; + strcpy(fent->file, cachesubdir); + strcat(fent->file, ent->d_name); + +/* accumulate in blocks, to cope with directories > 4Gb */ + add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size)); + } + + closedir(dir); + + return nfiles; + +} + + +/* + * Read a cache file; + * returns 1 on success, + * 0 on failure (bad file or wrong URL) + * -1 on UNIX error + * + * We read the cache hex header, then the message response line and + * response headers, and finally we return with the filepointer + * pointing at the start of the message body itself, ready to be + * shipped to the client later on, if appropriate. + */ +static int rdcache(request_rec *r, BUFF *cachefp, cache_req *c) +{ + char urlbuff[HUGE_STRING_LEN], *strp; + int len; + + /* read the data from the cache file */ + + /* + * Format: + * + * The cache needs to keep track of the following information: - Date, + * LastMod, Version, ReqTime, RespTime, ContentLength - The original + * request headers (for Vary) - The original response headers (for + * returning with a cached response) - The body of the message + * + * date SP lastmod SP expire SP count SP request-time SP response-time SP + * content-lengthCRLF (dates are stored as hex seconds since 1970) + * Original URLCRLF Original Request Headers CRLF Original Response + * Headers CRLF Body + * + */ + + /* retrieve cachefile information values */ + len = ap_bgets(urlbuff, sizeof urlbuff, cachefp); + if (len == -1) { + /* Delete broken cache file */ + unlink(c->filename); + return -1; + } + if (len == 0 || urlbuff[len - 1] != '\n') + return 0; + urlbuff[len - 1] = '\0'; + + if (!ap_checkmask(urlbuff, + "&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&")) + return 0; + + c->date = ap_proxy_hex2sec(urlbuff + 17 * (0)); + c->lmod = ap_proxy_hex2sec(urlbuff + 17 * (1)); + c->expire = ap_proxy_hex2sec(urlbuff + 17 * (2)); + c->version = ap_proxy_hex2sec(urlbuff + 17 * (3)); + c->req_time = ap_proxy_hex2sec(urlbuff + 17 * (4)); + c->resp_time = ap_proxy_hex2sec(urlbuff + 17 * (5)); + c->len = ap_proxy_hex2sec(urlbuff + 17 * (6)); + + /* check that we have the same URL */ + len = ap_bgets(urlbuff, sizeof urlbuff, cachefp); + if (len == -1) { + /* Delete broken cache file */ + unlink(c->filename); + return -1; + } + if (len == 0 || strncmp(urlbuff, "X-URL: ", 7) != 0 || + urlbuff[len - 1] != '\n') + return 0; + urlbuff[len - 1] = '\0'; + if (strcmp(urlbuff + 7, c->url) != 0) + return 0; + + /* then the original request headers */ + c->req_hdrs = ap_proxy_read_headers(r, urlbuff, sizeof urlbuff, cachefp); + if (c->req_hdrs == NULL) { + /* Delete broken cache file */ + unlink(c->filename); + return -1; + } + + /* then the original response headers */ + len = ap_bgets(urlbuff, sizeof urlbuff, cachefp); + if (len == -1) { + /* Delete broken cache file */ + unlink(c->filename); + return -1; + } + if (len == 0 || urlbuff[len - 1] != '\n') + return 0; + urlbuff[--len] = '\0'; + + c->resp_line = ap_pstrdup(r->pool, urlbuff); + strp = strchr(urlbuff, ' '); + if (strp == NULL) + return 0; + + c->status = atoi(strp); + c->hdrs = ap_proxy_read_headers(r, urlbuff, sizeof urlbuff, cachefp); + if (c->hdrs == NULL) { + /* Delete broken cache file */ + unlink(c->filename); + return -1; + } + if (c->len != -1) /* add a content-length header */ + if (ap_table_get(c->hdrs, "Content-Length") == NULL) { + ap_table_set(c->hdrs, "Content-Length", + ap_psprintf(r->pool, "%lu", (unsigned long)c->len)); + } + + + return 1; +} + +/* + * Call this to check the possible conditional status of + * the client request, and return the response from the cache + * + * Conditionals include If-Modified-Since, If-Match, If-Unmodified-Since + * and If-None-Match. + * + * We don't yet understand If-Range, but we will... + */ +int ap_proxy_cache_conditional(request_rec *r, cache_req *c, BUFF *cachefp) +{ + const char *etag, *wetag = NULL; + + /* get etag */ + if ((etag = ap_table_get(c->hdrs, "Etag"))) { + wetag = ap_pstrcat(r->pool, "W/", etag, NULL); + } + + /* check for If-Match, If-Unmodified-Since */ + while (1) { + + /* + * check If-Match and If-Unmodified-Since exist + * + * If neither of these exist, the request is not conditional, and we + * serve it normally + */ + if (!c->im && BAD_DATE == c->ius) { + break; + } + + /* + * check If-Match + * + * we check if the Etag on the cached file is in the list of Etags in + * the If-Match field. The comparison must be a strong comparison, so + * the Etag cannot be marked as weak. If the comparision fails we + * return 412 Precondition Failed. + * + * if If-Match is specified AND If-Match is not a "*" AND Etag is + * missing or weak or not in the list THEN return 412 Precondition + * Failed + */ + + if (c->im) { + if (strcmp(c->im, "*") && + (!etag || (strlen(etag) > 1 && 'W' == etag[0] && '/' == etag[1]) || !ap_proxy_liststr(c->im, etag, NULL))) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "If-Match specified, and it didn't - return 412"); + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "If-Match specified, and it matched"); + break; + } + } + + /* + * check If-Unmodified-Since + * + * if If-Unmodified-Since is specified AND Last-Modified is specified + * somewhere AND If-Unmodified-Since is in the past compared to + * Last-Modified THEN return 412 Precondition Failed + */ + if (BAD_DATE != c->ius && BAD_DATE != c->lmod) { + if (c->ius < c->lmod) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "If-Unmodified-Since specified, but it wasn't - return 412"); + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "If-Unmodified-Since specified, and it was unmodified"); + break; + } + } + + /* if cache file is being updated */ + if (c->origfp) { + ap_proxy_write_headers(c, c->resp_line, c->hdrs); + ap_proxy_send_fb(c->origfp, r, c, c->len, 1, 0, IOBUFSIZE); + ap_proxy_cache_tidy(c); + } + else + ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR)); + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Use your cached copy, conditional precondition failed."); + return HTTP_PRECONDITION_FAILED; + } + + + /* check for If-None-Match, If-Modified-Since */ + while (1) { + + /* + * check for existance of If-None-Match and If-Modified-Since + * + * if neither of these headers have been set, then the request is not + * conditional, and we just send the cached response and be done with + * it. + */ + if (!c->inm && BAD_DATE == c->ims) { + break; + } + + /* + * check If-None-Match + * + * we check if the Etag on the cached file is in the list of Etags in + * the If-None-Match field. The comparison must be a strong + * comparison, so the Etag cannot be marked as weak. If the + * comparision fails we return 412 Precondition Failed. + * + * if If-None-Match is specified: if If-None-Match is a "*" THEN 304 + * else if Etag is specified AND we get a match THEN 304 else if Weak + * Etag is specified AND we get a match THEN 304 else sent the + * original object + */ + if (c->inm) { + if (!strcmp(c->inm, "*")) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "If-None-Match: * specified, return 304"); + } + else if (etag && ap_proxy_liststr(c->inm, etag, NULL)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "If-None-Match: specified and we got a strong match - return 304"); + } + else if (wetag && ap_proxy_liststr(c->inm, wetag, NULL)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "If-None-Match specified, and we got a weak match - return 304"); + } + else + break; + } + + /* + * check If-Modified-Since + * + * if If-Modified-Since is specified AND Last-Modified is specified + * somewhere: if last modification date is earlier than + * If-Modified-Since THEN 304 else send the original object + */ + if (BAD_DATE != c->ims && BAD_DATE != c->lmod) { + if (c->ims >= c->lmod) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "If-Modified-Since specified and not modified, try return 304"); + } + else + break; + } + + + /* are we updating the cache file? */ + if (c->origfp) { + ap_proxy_write_headers(c, c->resp_line, c->hdrs); + ap_proxy_send_fb(c->origfp, r, c, c->len, 1, 0, IOBUFSIZE); + ap_proxy_cache_tidy(c); + } + else + ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR)); + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Use local copy, cached file hasn't changed"); + return HTTP_NOT_MODIFIED; + } + + + /* No conditional - just send it cousin! */ + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Local copy modified, send it"); + r->status_line = strchr(c->resp_line, ' ') + 1; + r->status = c->status; + + /* Prepare and send headers to client */ + ap_proxy_table_replace(r->headers_out, c->hdrs); + /* make sure our X-Cache header does not stomp on a previous header */ + ap_table_mergen(r->headers_out, "X-Cache", c->xcache); + + /* content type is already set in the headers */ + r->content_type = ap_table_get(r->headers_out, "Content-Type"); + + ap_send_http_header(r); + + /* are we rewriting the cache file? */ + if (c->origfp) { + ap_proxy_write_headers(c, c->resp_line, c->hdrs); + ap_proxy_send_fb(c->origfp, r, c, c->len, r->header_only, 0, IOBUFSIZE); + ap_proxy_cache_tidy(c); + return OK; + } + + /* no, we not */ + if (!r->header_only) { + ap_proxy_send_fb(cachefp, r, NULL, c->len, 0, 0, IOBUFSIZE); + } + else { + ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR)); + } + + return OK; +} + + +/* + * Call this to test for a resource in the cache + * Returns DECLINED if we need to check the remote host + * or an HTTP status code if successful + * + * Functions: + * if URL is cached then + * if cached file is not expired then + * if last modified after if-modified-since then send body + * else send 304 Not modified + * else if cached file is expired then + * if last modified after if-modified-since then add + * last modified date to request + */ +int ap_proxy_cache_check(request_rec *r, char *url, struct cache_conf * conf, + cache_req **cr) +{ + const char *datestr, *pragma_req = NULL, *pragma_cresp = NULL, *cc_req = NULL, + *cc_cresp = NULL; + cache_req *c; + BUFF *cachefp; + int i; + void *sconf = r->server->module_config; + proxy_server_conf *pconf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + const char *agestr = NULL; + char *val; + time_t age_c = 0; + time_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale, minfresh; + + c = ap_pcalloc(r->pool, sizeof(cache_req)); + *cr = c; + c->req = r; + c->url = ap_pstrdup(r->pool, url); + c->filename = NULL; + c->tempfile = NULL; + c->fp = NULL; + c->origfp = NULL; + c->version = 0; + c->len = -1; + c->req_hdrs = NULL; + c->hdrs = NULL; + c->xcache = NULL; + + /* get the If-Modified-Since date of the request, if it exists */ + c->ims = BAD_DATE; + datestr = ap_table_get(r->headers_in, "If-Modified-Since"); + if (datestr != NULL) { + /* this may modify the value in the original table */ + datestr = ap_proxy_date_canon(r->pool, datestr); + c->ims = ap_parseHTTPdate(datestr); + if (c->ims == BAD_DATE) /* bad or out of range date; remove it */ + ap_table_unset(r->headers_in, "If-Modified-Since"); + } + +/* get the If-Unmodified-Since date of the request, if it exists */ + c->ius = BAD_DATE; + datestr = ap_table_get(r->headers_in, "If-Unmodified-Since"); + if (datestr != NULL) { + /* this may modify the value in the original table */ + datestr = ap_proxy_date_canon(r->pool, datestr); + c->ius = ap_parseHTTPdate(datestr); + if (c->ius == BAD_DATE) /* bad or out of range date; remove it */ + ap_table_unset(r->headers_in, "If-Unmodified-Since"); + } + +/* get the If-Match of the request, if it exists */ + c->im = ap_table_get(r->headers_in, "If-Match"); + +/* get the If-None-Match of the request, if it exists */ + c->inm = ap_table_get(r->headers_in, "If-None-Match"); + +/* find the filename for this cache entry */ + if (conf->root != NULL) { + char hashfile[66]; + ap_proxy_hash(url, hashfile, pconf->cache.dirlevels, pconf->cache.dirlength); + c->filename = ap_pstrcat(r->pool, conf->root, "/", hashfile, NULL); + } + else { + c->filename = NULL; + c->fp = NULL; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "No CacheRoot, so no caching. Declining."); + return DECLINED; + } + +/* find certain cache controlling headers */ + pragma_req = ap_table_get(r->headers_in, "Pragma"); + cc_req = ap_table_get(r->headers_in, "Cache-Control"); + +/* first things first - does the request allow us to return + * cached information at all? If not, just decline the request. + * + * Note that there is a big difference between not being allowed + * to cache a request (no-store) and not being allowed to return + * a cached request without revalidation (max-age=0). + * + * Caching is forbidden under the following circumstances: + * + * - RFC2616 14.9.2 Cache-Control: no-store + * we are not supposed to store this request at all. Behave as a tunnel. + * + */ + if (ap_proxy_liststr(cc_req, "no-store", NULL)) { + +/* delete the previously cached file */ + if (c->filename) + unlink(c->filename); + c->fp = NULL; + c->filename = NULL; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "no-store forbids caching. Declining."); + return DECLINED; + } + +/* if the cache file exists, open it */ + cachefp = NULL; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Request for %s, pragma_req=%s, ims=%ld", url, + (pragma_req == NULL) ? "(unset)" : pragma_req, c->ims); +/* find out about whether the request can access the cache */ + if (c->filename != NULL && r->method_number == M_GET && + strlen(url) < 1024) { + cachefp = ap_proxy_open_cachefile(r, c->filename); + } + + + /* + * if a cache file exists, try reading body and headers from cache file + */ + if (cachefp != NULL) { + i = rdcache(r, cachefp, c); + if (i == -1) + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: error reading cache file %s", + c->filename); + else if (i == 0) + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, + "proxy: bad (short?) cache file: %s", c->filename); + if (i != 1) { + ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR)); + cachefp = NULL; + } + if (c->hdrs) { + cc_cresp = ap_table_get(c->hdrs, "Cache-Control"); + pragma_cresp = ap_table_get(c->hdrs, "Pragma"); + if ((agestr = ap_table_get(c->hdrs, "Age"))) { + age_c = atoi(agestr); + } + } + } + + /* if a cache file does not exist, create empty header array */ +/* fixed? in this case, we want to get the headers from the remote server + it will be handled later if we don't do this (I hope ;-) + + if (cachefp == NULL) + c->hdrs = ap_make_table(r->pool, 20); +*/ + /* FIXME: Shouldn't we check the URL somewhere? */ + + /* + * Check Content-Negotiation - Vary + * + * At this point we need to make sure that the object we found in the cache + * is the same object that would be delivered to the client, when the + * effects of content negotiation are taken into effect. + * + * In plain english, we want to make sure that a language-negotiated + * document in one language is not given to a client asking for a + * language negotiated document in a different language by mistake. + * + * RFC2616 13.6 and 14.44 describe the Vary mechanism. + */ + if (c->hdrs && c->req_hdrs) { + char *vary = ap_pstrdup(r->pool, ap_table_get(c->hdrs, "Vary")); + + while (vary && *vary) { + char *name = vary; + const char *h1, *h2; + + /* isolate header name */ + while (*vary && !ap_isspace(*vary) && (*vary != ',')) + ++vary; + while (*vary && (ap_isspace(*vary) || (*vary == ','))) { + *vary = '\0'; + ++vary; + } + + /* + * is this header in the request and the header in the cached + * request identical? If not, we give up and do a straight get + */ + h1 = ap_table_get(r->headers_in, name); + h2 = ap_table_get(c->req_hdrs, name); + if (h1 == h2) { + /* both headers NULL, so a match - do nothing */ + } + else if (h1 && h2 && !strcmp(h1, h2)) { + /* both headers exist and are equal - do nothing */ + } + else { + + /* headers do not match, so Vary failed */ + c->fp = cachefp; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Vary header mismatch - object must be fetched from scratch. Declining."); + return DECLINED; + } + } + } + + + /* + * We now want to check if our cached data is still fresh. This depends + * on a few things, in this order: + * + * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache no-cache in + * either the request or the cached response means that we must + * revalidate the request unconditionally, overriding any expiration + * mechanism. It's equivalent to max-age=0,must-revalidate. + * + * - RFC2616 14.32 Pragma: no-cache This is treated the same as + * Cache-Control: no-cache. + * + * - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate, + * proxy-revalidate if the max-stale request header exists, modify the + * stale calculations below so that an object can be at most <max-stale> + * seconds stale before we request a revalidation, _UNLESS_ a + * must-revalidate or proxy-revalidate cached response header exists to + * stop us doing this. + * + * - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the + * maximum age an object can be before it is considered stale. This + * directive has the effect of proxy|must revalidate, which in turn means + * simple ignore any max-stale setting. + * + * - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both + * requests and responses. If both are specified, the smaller of the two + * takes priority. + * + * - RFC2616 14.21 Expires: if this request header exists in the cached + * entity, and it's value is in the past, it has expired. + * + */ + + /* calculate age of object */ + age = ap_proxy_current_age(c, age_c); + + /* extract s-maxage */ + if (cc_cresp && ap_proxy_liststr(cc_cresp, "s-maxage", &val)) + smaxage = atoi(val); + else + smaxage = -1; + + /* extract max-age from request */ + if (cc_req && ap_proxy_liststr(cc_req, "max-age", &val)) + maxage_req = atoi(val); + else + maxage_req = -1; + + /* extract max-age from response */ + if (cc_cresp && ap_proxy_liststr(cc_cresp, "max-age", &val)) + maxage_cresp = atoi(val); + else + maxage_cresp = -1; + + /* + * if both maxage request and response, the smaller one takes priority + */ + if (-1 == maxage_req) + maxage = maxage_cresp; + else if (-1 == maxage_cresp) + maxage = maxage_req; + else + maxage = MIN(maxage_req, maxage_cresp); + + /* extract max-stale */ + if (cc_req && ap_proxy_liststr(cc_req, "max-stale", &val)) + maxstale = atoi(val); + else + maxstale = 0; + + /* extract min-fresh */ + if (cc_req && ap_proxy_liststr(cc_req, "min-fresh", &val)) + minfresh = atoi(val); + else + minfresh = 0; + + /* override maxstale if must-revalidate or proxy-revalidate */ + if (maxstale && ((cc_cresp && ap_proxy_liststr(cc_cresp, "must-revalidate", NULL)) || (cc_cresp && ap_proxy_liststr(cc_cresp, "proxy-revalidate", NULL)))) + maxstale = 0; + + if (cachefp != NULL && + + /* handle no-cache */ + !((cc_req && ap_proxy_liststr(cc_req, "no-cache", NULL)) || + (pragma_req && ap_proxy_liststr(pragma_req, "no-cache", NULL)) || + (cc_cresp && ap_proxy_liststr(cc_cresp, "no-cache", NULL)) || + (pragma_cresp && ap_proxy_liststr(pragma_cresp, "no-cache", NULL))) && + + /* handle expiration */ + ((-1 < smaxage && age < (smaxage - minfresh)) || + (-1 < maxage && age < (maxage + maxstale - minfresh)) || + (c->expire != BAD_DATE && age < (c->expire - c->date + maxstale - minfresh))) + ) { + + /* it's fresh darlings... */ + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Unexpired data available"); + + /* set age header on response */ + ap_table_set(c->hdrs, "Age", + ap_psprintf(r->pool, "%lu", (unsigned long)age)); + + /* add warning if maxstale overrode freshness calculation */ + if (!((-1 < smaxage && age < smaxage) || + (-1 < maxage && age < maxage) || + (c->expire != BAD_DATE && (c->expire - c->date) > age))) { + /* make sure we don't stomp on a previous warning */ + ap_table_merge(c->hdrs, "Warning", "110 Response is stale"); + } + + /* check conditionals (If-Modified-Since, etc) */ + c->xcache = ap_pstrcat(r->pool, "HIT from ", ap_get_server_name(r), NULL); + return ap_proxy_cache_conditional(r, c, cachefp); + + + } + + /* + * at this point we have determined our cached data needs revalidation + * but first - we check 1 thing: + * + * RFC2616 14.9.4 - if "only-if-cached" specified, send a 504 Gateway + * Timeout - we're not allowed to revalidate the object + */ + if (ap_proxy_liststr(cc_req, "only-if-cached", NULL)) { + if (cachefp) + ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR)); + return HTTP_GATEWAY_TIME_OUT; + } + + + /* + * If we already have cached data and a last-modified date, and it is not + * a head request, then add an If-Modified-Since. + * + * If we also have an Etag, then the object must have come from an HTTP/1.1 + * server. Add an If-None-Match as well. + * + * See RFC2616 13.3.4 + */ + + if (cachefp != NULL && !r->header_only) { + + const char *etag = ap_table_get(c->hdrs, "Etag"); + + /* If-Modified-Since */ + if (c->lmod != BAD_DATE) { + /* + * use the later of the one from the request and the + * last-modified date from the cache + */ + if (c->ims == BAD_DATE || c->ims < c->lmod) { + const char *q; + + if ((q = ap_table_get(c->hdrs, "Last-Modified")) != NULL) + ap_table_set(r->headers_in, "If-Modified-Since", (char *)q); + } + } + + /* If-None-Match */ + if (etag) { + ap_table_set(r->headers_in, "If-None-Match", etag); + } + + } + + + c->fp = cachefp; + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Local copy not present or expired. Declining."); + + return DECLINED; +} + +/* + * Having read the response from the client, decide what to do + * If the response is not cachable, then delete any previously cached + * response, and copy data from remote server to client. + * Functions: + * parse dates + * check for an uncachable response + * calculate an expiry date, if one is not provided + * if the remote file has not been modified, then return the document + * from the cache, maybe updating the header line + * otherwise, delete the old cached file and open a new temporary file + */ +int ap_proxy_cache_update(cache_req *c, table *resp_hdrs, + const int is_HTTP1, int nocache) +{ +#if defined(ULTRIX_BRAIN_DEATH) || defined(SINIX_D_RESOLVER_BUG) + extern char *mktemp(char *template); +#endif + request_rec *r = c->req; + char *p; + const char *expire, *lmods, *dates, *clen; + time_t expc, date, lmod, now; + char buff[17 * 7 + 1]; + void *sconf = r->server->module_config; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + const char *cc_resp; + table *req_hdrs; + + cc_resp = ap_table_get(resp_hdrs, "Cache-Control"); + + c->tempfile = NULL; + + /* we've received the response from the origin server */ + + /* + * read expiry date; if a bad date, then leave it so the client can read + * it + */ + expire = ap_table_get(resp_hdrs, "Expires"); + if (expire != NULL) + expc = ap_parseHTTPdate(expire); + else + expc = BAD_DATE; + + /* read the last-modified date; if the date is bad, then delete it */ + lmods = ap_table_get(resp_hdrs, "Last-Modified"); + if (lmods != NULL) { + lmod = ap_parseHTTPdate(lmods); + if (lmod == BAD_DATE) { + /* kill last modified date */ + lmods = NULL; + } + } + else + lmod = BAD_DATE; + + + /* + * what responses should we not cache? + * + * At this point we decide based on the response headers whether it is + * appropriate _NOT_ to cache the data from the server. There are a whole + * lot of conditions that prevent us from caching this data. They are + * tested here one by one to be clear and unambiguous. + */ + + /* + * RFC2616 13.4 we are allowed to cache 200, 203, 206, 300, 301 or 410 We + * don't cache 206, because we don't (yet) cache partial responses. We + * include 304 Not Modified here too as this is the origin server telling + * us to serve the cached copy. + */ + if ((r->status != HTTP_OK && r->status != HTTP_NON_AUTHORITATIVE && r->status != HTTP_MULTIPLE_CHOICES && r->status != HTTP_MOVED_PERMANENTLY && r->status != HTTP_NOT_MODIFIED) || + + /* if a broken Expires header is present, don't cache it */ + (expire != NULL && expc == BAD_DATE) || + + /* + * if the server said 304 Not Modified but we have no cache file - pass + * this untouched to the user agent, it's not for us. + */ + (r->status == HTTP_NOT_MODIFIED && (c == NULL || c->fp == NULL)) || + + /* + * 200 OK response from HTTP/1.0 and up without a Last-Modified header + */ + (r->status == HTTP_OK && lmods == NULL && is_HTTP1) || + + /* HEAD requests */ + r->header_only || + + /* + * RFC2616 14.9.2 Cache-Control: no-store response indicating do not + * cache, or stop now if you are trying to cache it + */ + ap_proxy_liststr(cc_resp, "no-store", NULL) || + + /* + * RFC2616 14.9.1 Cache-Control: private this object is marked for this + * user's eyes only. Behave as a tunnel. + */ + ap_proxy_liststr(cc_resp, "private", NULL) || + + /* + * RFC2616 14.8 Authorisation: if authorisation is included in the + * request, we don't cache, but we can cache if the following exceptions + * are true: 1) If Cache-Control: s-maxage is included 2) If + * Cache-Control: must-revalidate is included 3) If Cache-Control: public + * is included + */ + (ap_table_get(r->headers_in, "Authorization") != NULL + + && !(ap_proxy_liststr(cc_resp, "s-maxage", NULL) || ap_proxy_liststr(cc_resp, "must-revalidate", NULL) || ap_proxy_liststr(cc_resp, "public", NULL)) + ) || + + /* or we've been asked not to cache it above */ + nocache) { + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Response is not cacheable, unlinking %s", c->filename); + + /* close the file */ + if (c->fp != NULL) { + ap_pclosef(r->pool, ap_bfileno(c->fp, B_WR)); + c->fp = NULL; + } + + /* delete the previously cached file */ + if (c->filename) + unlink(c->filename); + return DECLINED; /* send data to client but not cache */ + } + + + /* + * It's safe to cache the response. + * + * We now want to update the cache file header information with the new + * date, last modified, expire and content length and write it away to + * our cache file. First, we determine these values from the response, + * using heuristics if appropriate. + * + * In addition, we make HTTP/1.1 age calculations and write them away too. + */ + + /* Read the date. Generate one if one is not supplied */ + dates = ap_table_get(resp_hdrs, "Date"); + if (dates != NULL) + date = ap_parseHTTPdate(dates); + else + date = BAD_DATE; + + now = time(NULL); + + if (date == BAD_DATE) { /* No, or bad date */ +/* no date header! */ +/* add one; N.B. use the time _now_ rather than when we were checking the cache + */ + date = now; + dates = ap_gm_timestr_822(r->pool, now); + ap_table_set(resp_hdrs, "Date", dates); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Added date header"); + } + +/* set response_time for HTTP/1.1 age calculations */ + c->resp_time = now; + +/* check last-modified date */ + if (lmod != BAD_DATE && lmod > date) +/* if its in the future, then replace by date */ + { + lmod = date; + lmods = dates; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Last modified is in the future, replacing with now"); + } +/* if the response did not contain the header, then use the cached version */ + if (lmod == BAD_DATE && c->fp != NULL) { + lmod = c->lmod; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Reusing cached last modified"); + } + +/* we now need to calculate the expire data for the object. */ + if (expire == NULL && c->fp != NULL) { /* no expiry data sent in + * response */ + expire = ap_table_get(c->hdrs, "Expires"); + if (expire != NULL) + expc = ap_parseHTTPdate(expire); + } +/* so we now have the expiry date */ +/* if no expiry date then + * if lastmod + * expiry date = now + min((date - lastmod) * factor, maxexpire) + * else + * expire date = now + defaultexpire + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Expiry date is %ld", (long)expc); + if (expc == BAD_DATE) { + if (lmod != BAD_DATE) { + double x = (double)(date - lmod) * conf->cache.lmfactor; + double maxex = conf->cache.maxexpire; + if (x > maxex) + x = maxex; + expc = now + (int)x; + } + else + expc = now + conf->cache.defaultexpire; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Expiry date calculated %ld", (long)expc); + } + +/* get the content-length header */ + clen = ap_table_get(resp_hdrs, "Content-Length"); + if (clen == NULL) + c->len = -1; + else + c->len = ap_strtol(clen, NULL, 10); + +/* we have all the header information we need - write it to the cache file */ + c->version++; + ap_proxy_sec2hex(date, buff + 17 * (0)); + buff[17 * (1) - 1] = ' '; + ap_proxy_sec2hex(lmod, buff + 17 * (1)); + buff[17 * (2) - 1] = ' '; + ap_proxy_sec2hex(expc, buff + 17 * (2)); + buff[17 * (3) - 1] = ' '; + ap_proxy_sec2hex(c->version, buff + 17 * (3)); + buff[17 * (4) - 1] = ' '; + ap_proxy_sec2hex(c->req_time, buff + 17 * (4)); + buff[17 * (5) - 1] = ' '; + ap_proxy_sec2hex(c->resp_time, buff + 17 * (5)); + buff[17 * (6) - 1] = ' '; + ap_proxy_sec2hex(c->len, buff + 17 * (6)); + buff[17 * (7) - 1] = '\n'; + buff[17 * (7)] = '\0'; + +/* Was the server response a 304 Not Modified? + * + * If it was, it means that we requested a revalidation, and that + * the result of that revalidation was that the object was fresh. + * + */ + +/* if response from server 304 not modified */ + if (r->status == HTTP_NOT_MODIFIED) { + +/* Have the headers changed? + * + * if not - we fulfil the request and return now. + */ + + if (c->hdrs) { + /* recall at this point that c->len is already set from resp_hdrs. + If Content-Length was NULL, then c->len is -1, otherwise it's + set to whatever the value was. */ + if (c->len == 0 || c->len == -1) { + const char *c_clen_str; + off_t c_clen; + if ( (c_clen_str = ap_table_get(c->hdrs, "Content-Length")) && + ( (c_clen = ap_strtol(c_clen_str, NULL, 10)) > 0) ) { + ap_table_set(resp_hdrs, "Content-Length", c_clen_str); + c->len = c_clen; + ap_proxy_sec2hex(c->len, buff + 17 * (6)); + buff[17 * (7) - 1] = '\n'; + buff[17 * (7)] = '\0'; + } + } + if (!ap_proxy_table_replace(c->hdrs, resp_hdrs)) { + c->xcache = ap_pstrcat(r->pool, "HIT from ", ap_get_server_name(r), " (with revalidation)", NULL); + return ap_proxy_cache_conditional(r, c, c->fp); + } + } + else + c->hdrs = resp_hdrs; +/* if we get here - the headers have changed. Go through the motions + * of creating a new temporary cache file below, we'll then serve + * the request like we would have in ap_proxy_cache_conditional() + * above, and at the same time we will also rewrite the contents + * to the new temporary file. + */ + } + +/* + * Ok - lets prepare and open the cached file + * + * If a cached file (in c->fp) is already open, then we want to + * update that cached file. Copy the c->fp to c->origfp and open + * up a new one. + * + * If the cached file (in c->fp) is NULL, we must open a new cached + * file from scratch. + * + * The new cache file will be moved to it's final location in the + * directory tree later, overwriting the old cache file should it exist. + */ + +/* if a cache file was already open */ + if (c->fp != NULL) { + c->origfp = c->fp; + } + + while (1) { +/* create temporary filename */ +#ifndef TPF +#define TMPFILESTR "/tmpXXXXXX" + if (conf->cache.root == NULL) { + c = ap_proxy_cache_error(c); + break; + } + c->tempfile = ap_palloc(r->pool, strlen(conf->cache.root) + sizeof(TMPFILESTR)); + strcpy(c->tempfile, conf->cache.root); + strcat(c->tempfile, TMPFILESTR); +#undef TMPFILESTR + p = mktemp(c->tempfile); +#else + if (conf->cache.root == NULL) { + c = ap_proxy_cache_error(c); + break; + } + c->tempfile = ap_palloc(r->pool, strlen(conf->cache.root) + 1 + L_tmpnam); + strcpy(c->tempfile, conf->cache.root); + strcat(c->tempfile, "/"); + p = tmpnam(NULL); + strcat(c->tempfile, p); +#endif + if (p == NULL) { + c = ap_proxy_cache_error(c); + break; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Create temporary file %s", c->tempfile); + +/* create the new file */ + c->fp = ap_proxy_create_cachefile(r, c->tempfile); + if (NULL == c->fp) { + c = ap_proxy_cache_error(c); + break; + } + +/* write away the cache header and the URL */ + if (ap_bvputs(c->fp, buff, "X-URL: ", c->url, "\n", NULL) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: error writing cache file(%s)", c->tempfile); + c = ap_proxy_cache_error(c); + break; + } + +/* get original request headers */ + if (c->req_hdrs) + req_hdrs = ap_copy_table(r->pool, c->req_hdrs); + else + req_hdrs = ap_copy_table(r->pool, r->headers_in); + +/* remove hop-by-hop headers */ + ap_proxy_clear_connection(r->pool, req_hdrs); + +/* save original request headers */ + if (c->req_hdrs) + ap_table_do(ap_proxy_send_hdr_line, c, c->req_hdrs, NULL); + else + ap_table_do(ap_proxy_send_hdr_line, c, r->headers_in, NULL); + if (ap_bputs(CRLF, c->fp) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req, + "proxy: error writing request headers terminating CRLF to %s", c->tempfile); + c = ap_proxy_cache_error(c); + break; + } + break; + } + +/* Was the server response a 304 Not Modified? + * + * If so, we have some work to do that we didn't do when we first + * checked above. We need to fulfil the request, and we need to + * copy the body from the old object to the new one. + */ + +/* if response from server 304 not modified */ + if (r->status == HTTP_NOT_MODIFIED) { + +/* fulfil the request */ + c->xcache = ap_pstrcat(r->pool, "HIT from ", ap_get_server_name(r), " (with revalidation)", NULL); + return ap_proxy_cache_conditional(r, c, c->fp); + + } + return DECLINED; +} + +void ap_proxy_cache_tidy(cache_req *c) +{ + server_rec *s; + long int bc; + + if (!c || !c->fp) + return; + + s = c->req->server; + +/* don't care how much was sent, but rather how much was written to cache + ap_bgetopt(c->req->connection->client, BO_BYTECT, &bc); + */ + bc = c->written; + + if (c->len != -1) { +/* file lengths don't match; don't cache it */ + if (bc != c->len) { + ap_pclosef(c->req->pool, ap_bfileno(c->fp, B_WR)); /* no need to flush */ + unlink(c->tempfile); + return; + } + } +/* don't care if aborted, cache it if fully retrieved from host! + else if (c->req->connection->aborted) { + ap_pclosef(c->req->pool, c->fp->fd); / no need to flush / + unlink(c->tempfile); + return; + } +*/ + else { +/* update content-length of file */ + char buff[17]; + off_t curpos; + + c->len = bc; + ap_bflush(c->fp); + ap_proxy_sec2hex(c->len, buff); + curpos = lseek(ap_bfileno(c->fp, B_WR), 17 * 6, SEEK_SET); + if (curpos == -1) + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "proxy: error seeking on cache file %s", c->tempfile); + else if (write(ap_bfileno(c->fp, B_WR), buff, sizeof(buff) - 1) == -1) + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "proxy: error updating cache file %s", c->tempfile); + } + + if (ap_bflush(c->fp) == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "proxy: error writing to cache file %s", + c->tempfile); + ap_pclosef(c->req->pool, ap_bfileno(c->fp, B_WR)); + unlink(c->tempfile); + return; + } + + if (ap_pclosef(c->req->pool, ap_bfileno(c->fp, B_WR))== -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "proxy: error closing cache file %s", c->tempfile); + unlink(c->tempfile); + return; + } + + if (unlink(c->filename) == -1 && errno != ENOENT) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "proxy: error deleting old cache file %s", + c->filename); + (void)unlink(c->tempfile); + } + else { + char *p; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(s->module_config, &proxy_module); + + for (p = c->filename + strlen(conf->cache.root) + 1;;) { + p = strchr(p, '/'); + if (!p) + break; + *p = '\0'; +#if defined(WIN32) || defined(NETWARE) + if (mkdir(c->filename) < 0 && errno != EEXIST) +#elif defined(__TANDEM) + if (mkdir(c->filename, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST) +#else + if (mkdir(c->filename, S_IREAD | S_IWRITE | S_IEXEC) < 0 && errno != EEXIST) +#endif /* WIN32 */ + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "proxy: error creating cache directory %s", + c->filename); + *p = '/'; + ++p; + } +#if defined(OS2) || defined(WIN32) || defined(NETWARE) || defined(MPE) + /* Under OS/2 use rename. */ + if (rename(c->tempfile, c->filename) == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "proxy: error renaming cache file %s to %s", + c->tempfile, c->filename); + (void)unlink(c->tempfile); + } +#else + + if (link(c->tempfile, c->filename) == -1) + ap_log_error(APLOG_MARK, APLOG_INFO, s, + "proxy: error linking cache file %s to %s", + c->tempfile, c->filename); + if (unlink(c->tempfile) == -1) + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "proxy: error deleting temp file %s", c->tempfile); +#endif + } +} diff --git a/APACHE_1_3_42/src/modules/proxy/proxy_connect.c b/APACHE_1_3_42/src/modules/proxy/proxy_connect.c new file mode 100644 index 0000000000..d316fd9196 --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/proxy_connect.c @@ -0,0 +1,257 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* CONNECT method for Apache proxy */ + +#include "mod_proxy.h" +#include "http_log.h" +#include "http_main.h" + +#ifdef HAVE_BSTRING_H +#include <bstring.h> /* for IRIX, FD_SET calls bzero() */ +#endif + +/* + * This handles Netscape CONNECT method secure proxy requests. + * A connection is opened to the specified host and data is + * passed through between the WWW site and the browser. + * + * This code is based on the INTERNET-DRAFT document + * "Tunneling SSL Through a WWW Proxy" currently at + * http://www.mcom.com/newsref/std/tunneling_ssl.html. + * + * If proxyhost and proxyport are set, we send a CONNECT to + * the specified proxy.. + * + * FIXME: this is bad, because it does its own socket I/O + * instead of using the I/O in buff.c. However, + * the I/O in buff.c blocks on reads, and because + * this function doesn't know how much data will + * be sent either way (or when) it can't use blocking + * I/O. This may be very implementation-specific + * (to Linux). Any suggestions? + * FIXME: this doesn't log the number of bytes sent, but + * that may be okay, since the data is supposed to + * be transparent. In fact, this doesn't log at all + * yet. 8^) + * FIXME: doesn't check any headers initally sent from the + * client. + * FIXME: should allow authentication, but hopefully the + * generic proxy authentication is good enough. + * FIXME: no check for r->assbackwards, whatever that is. + */ + +static int allowed_port(proxy_server_conf *conf, int port) +{ + int i; + int *list = (int *)conf->allowed_connect_ports->elts; + + for (i = 0; i < conf->allowed_connect_ports->nelts; i++) { + if (port == list[i]) + return 1; + } + return 0; +} + + +int ap_proxy_connect_handler(request_rec *r, cache_req *c, char *url, + const char *proxyhost, int proxyport) +{ + struct sockaddr_in server; + struct in_addr destaddr; + struct hostent server_hp; + const char *host, *err; + char *p; + int port, sock; + char buffer[HUGE_STRING_LEN]; + int nbytes, i, j; + fd_set fds; + + void *sconf = r->server->module_config; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + struct noproxy_entry *npent = (struct noproxy_entry *) conf->noproxies->elts; + + memset(&server, '\0', sizeof(server)); + server.sin_family = AF_INET; + + /* Break the URL into host:port pairs */ + + host = url; + p = strchr(url, ':'); + if (p == NULL) + port = DEFAULT_HTTPS_PORT; + else { + port = atoi(p + 1); + *p = '\0'; + } + +/* check if ProxyBlock directive on this host */ + destaddr.s_addr = ap_inet_addr(host); + for (i = 0; i < conf->noproxies->nelts; i++) { + if ((npent[i].name != NULL && strstr(host, npent[i].name) != NULL) + || destaddr.s_addr == npent[i].addr.s_addr + || npent[i].name[0] == '*') + return ap_proxyerror(r, HTTP_FORBIDDEN, + "Connect to remote machine blocked"); + } + + /* Check if it is an allowed port */ + if (conf->allowed_connect_ports->nelts == 0) { + /* Default setting if not overridden by AllowCONNECT */ + switch (port) { + case DEFAULT_HTTPS_PORT: + case DEFAULT_SNEWS_PORT: + break; + default: + return HTTP_FORBIDDEN; + } + } + else if (!allowed_port(conf, port)) + return HTTP_FORBIDDEN; + + if (proxyhost) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "CONNECT to remote proxy %s on port %d", proxyhost, proxyport); + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "CONNECT to %s on port %d", host, port); + } + + /* Nasty cast to work around broken terniary expressions on MSVC */ + server.sin_port = htons((unsigned short)(proxyport ? proxyport : port)); + err = ap_proxy_host2addr(proxyhost ? proxyhost : host, &server_hp); + + if (err != NULL) + return ap_proxyerror(r, + proxyhost ? HTTP_BAD_GATEWAY : HTTP_INTERNAL_SERVER_ERROR, err); + + sock = ap_psocket_ex(r->pool, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + if (sock == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "proxy: error creating socket"); + return HTTP_INTERNAL_SERVER_ERROR; + } + +#ifdef CHECK_FD_SETSIZE + if (sock >= FD_SETSIZE) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_WARNING, NULL, + "proxy_connect_handler: filedescriptor (%u) " + "larger than FD_SETSIZE (%u) " + "found, you probably need to rebuild Apache with a " + "larger FD_SETSIZE", sock, FD_SETSIZE); + ap_pclosesocket(r->pool, sock); + return HTTP_INTERNAL_SERVER_ERROR; + } +#endif + + j = 0; + while (server_hp.h_addr_list[j] != NULL) { + memcpy(&server.sin_addr, server_hp.h_addr_list[j], + sizeof(struct in_addr)); + i = ap_proxy_doconnect(sock, &server, r); + if (i == 0) + break; + j++; + } + if (i == -1) { + ap_pclosesocket(r->pool, sock); + return ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, ap_pstrcat(r->pool, + "Could not connect to remote machine:<br>", strerror(errno), NULL)); + } + + /* + * If we are connecting through a remote proxy, we need to pass the + * CONNECT request on to it. + */ + if (proxyport) { + /* + * FIXME: We should not be calling write() directly, but we currently + * have no alternative. Error checking ignored. Also, we force a + * HTTP/1.0 request to keep things simple. + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "Sending the CONNECT request to the remote proxy"); + ap_snprintf(buffer, sizeof(buffer), "CONNECT %s HTTP/1.0" CRLF, r->uri); + send(sock, buffer, strlen(buffer), 0); + ap_snprintf(buffer, sizeof(buffer), + "Proxy-agent: %s" CRLF CRLF, ap_get_server_version()); + send(sock, buffer, strlen(buffer), 0); + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "Returning 200 OK Status"); + ap_rvputs(r, "HTTP/1.0 200 Connection established" CRLF, NULL); + ap_rvputs(r, "Proxy-agent: ", ap_get_server_version(), CRLF CRLF, NULL); + ap_bflush(r->connection->client); + } + + while (1) { /* Infinite loop until error (one side closes + * the connection) */ + FD_ZERO(&fds); + FD_SET(sock, &fds); + FD_SET(ap_bfileno(r->connection->client, B_WR), &fds); + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "Going to sleep (select)"); + i = ap_select((ap_bfileno(r->connection->client, B_WR) > sock ? + ap_bfileno(r->connection->client, B_WR) + 1 : + sock + 1), &fds, NULL, NULL, NULL); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "Woke from select(), i=%d", i); + + if (i) { + if (FD_ISSET(sock, &fds)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "sock was set"); + if ((nbytes = recv(sock, buffer, HUGE_STRING_LEN, 0)) != 0) { + if (nbytes == -1) + break; + if (send(ap_bfileno(r->connection->client, B_WR), buffer, + nbytes, 0) == EOF) + break; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, + r->server, "Wrote %d bytes to client", nbytes); + } + else + break; + } + else if (FD_ISSET(ap_bfileno(r->connection->client, B_WR), &fds)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, + "client->fd was set"); + if ((nbytes = recv(ap_bfileno(r->connection->client, B_WR), + buffer, HUGE_STRING_LEN, 0)) != 0) { + if (nbytes == -1) + break; + if (send(sock, buffer, nbytes, 0) == EOF) + break; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, + r->server, "Wrote %d bytes to server", nbytes); + } + else + break; + } + else + break; /* Must be done waiting */ + } + else + break; + } + + ap_pclosesocket(r->pool, sock); + + return OK; +} diff --git a/APACHE_1_3_42/src/modules/proxy/proxy_ftp.c b/APACHE_1_3_42/src/modules/proxy/proxy_ftp.c new file mode 100644 index 0000000000..01a6a0112e --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/proxy_ftp.c @@ -0,0 +1,1393 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* FTP routines for Apache proxy */ + +#include "mod_proxy.h" +#include "http_main.h" +#include "http_log.h" +#include "http_core.h" + +#define AUTODETECT_PWD + +/* + * Decodes a '%' escaped string, and returns the number of characters + */ +static int decodeenc(char *x) +{ + int i, j, ch; + + if (x[0] == '\0') + return 0; /* special case for no characters */ + for (i = 0, j = 0; x[i] != '\0'; i++, j++) { +/* decode it if not already done */ + ch = x[i]; + if (ch == '%' && ap_isxdigit(x[i + 1]) && ap_isxdigit(x[i + 2])) { + ch = ap_proxy_hex2c(&x[i + 1]); + i += 2; + } + x[j] = ch; + } + x[j] = '\0'; + return j; +} + +/* + * checks an encoded ftp string for bad characters, namely, CR, LF or + * non-ascii character + */ +static int ftp_check_string(const char *x) +{ + int i, ch; + + for (i = 0; x[i] != '\0'; i++) { + ch = x[i]; + if (ch == '%' && ap_isxdigit(x[i + 1]) && ap_isxdigit(x[i + 2])) { + ch = ap_proxy_hex2c(&x[i + 1]); + i += 2; + } + if (ch == CR || ch == LF || (OS_ASC(ch) & 0x80)) + return 0; + } + return 1; +} + +/* + * Canonicalise ftp URLs. + */ +int ap_proxy_ftp_canon(request_rec *r, char *url) +{ + char *user, *password, *host, *path, *parms, *strp, sport[7]; + pool *p = r->pool; + const char *err; + int port; + + port = DEFAULT_FTP_PORT; + err = ap_proxy_canon_netloc(p, &url, &user, &password, &host, &port); + if (err) + return HTTP_BAD_REQUEST; + if (user != NULL && !ftp_check_string(user)) + return HTTP_BAD_REQUEST; + if (password != NULL && !ftp_check_string(password)) + return HTTP_BAD_REQUEST; + +/* now parse path/parameters args, according to rfc1738 */ +/* N.B. if this isn't a true proxy request, then the URL path + * (but not query args) has already been decoded. + * This gives rise to the problem of a ; being decoded into the + * path. + */ + strp = strchr(url, ';'); + if (strp != NULL) { + *(strp++) = '\0'; + parms = ap_proxy_canonenc(p, strp, strlen(strp), enc_parm, + r->proxyreq); + if (parms == NULL) + return HTTP_BAD_REQUEST; + } + else + parms = ""; + + path = ap_proxy_canonenc(p, url, strlen(url), enc_path, r->proxyreq); + if (path == NULL) + return HTTP_BAD_REQUEST; + if (!ftp_check_string(path)) + return HTTP_BAD_REQUEST; + + if (r->proxyreq == NOT_PROXY && r->args != NULL) { + if (strp != NULL) { + strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_parm, STD_PROXY); + if (strp == NULL) + return HTTP_BAD_REQUEST; + parms = ap_pstrcat(p, parms, "?", strp, NULL); + } + else { + strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_fpath, STD_PROXY); + if (strp == NULL) + return HTTP_BAD_REQUEST; + path = ap_pstrcat(p, path, "?", strp, NULL); + } + r->args = NULL; + } + +/* now, rebuild URL */ + + if (port != DEFAULT_FTP_PORT) + ap_snprintf(sport, sizeof(sport), ":%d", port); + else + sport[0] = '\0'; + + r->filename = ap_pstrcat(p, "proxy:ftp://", (user != NULL) ? user : "", + (password != NULL) ? ":" : "", + (password != NULL) ? password : "", + (user != NULL) ? "@" : "", host, sport, "/", path, + (parms[0] != '\0') ? ";" : "", parms, NULL); + + return OK; +} + +/* + * Returns the ftp status code; + * or -1 on I/O error, 0 on data error + */ +static int ftp_getrc(BUFF *ctrl) +{ + int len, status; + char linebuff[100], buff[5]; + + len = ap_bgets(linebuff, sizeof linebuff, ctrl); + if (len == -1) + return -1; +/* check format */ + if (len < 5 || !ap_isdigit(linebuff[0]) || !ap_isdigit(linebuff[1]) || + !ap_isdigit(linebuff[2]) || (linebuff[3] != ' ' && linebuff[3] != '-')) + status = 0; + else + status = 100 * linebuff[0] + 10 * linebuff[1] + linebuff[2] - 111 * '0'; + + if (linebuff[len - 1] != '\n') { + (void)ap_bskiplf(ctrl); + } + +/* skip continuation lines */ + if (linebuff[3] == '-') { + memcpy(buff, linebuff, 3); + buff[3] = ' '; + do { + len = ap_bgets(linebuff, sizeof linebuff, ctrl); + if (len == -1) + return -1; + if (linebuff[len - 1] != '\n') { + (void)ap_bskiplf(ctrl); + } + } while (memcmp(linebuff, buff, 4) != 0); + } + + return status; +} + +/* + * Like ftp_getrc but returns both the ftp status code and + * remembers the response message in the supplied buffer + */ +static int ftp_getrc_msg(BUFF *ctrl, char *msgbuf, int msglen) +{ + int len, status; + char linebuff[100], buff[5]; + char *mb = msgbuf, *me = &msgbuf[msglen]; + + len = ap_bgets(linebuff, sizeof linebuff, ctrl); + if (len == -1) + return -1; + if (len < 5 || !ap_isdigit(linebuff[0]) || !ap_isdigit(linebuff[1]) || + !ap_isdigit(linebuff[2]) || (linebuff[3] != ' ' && linebuff[3] != '-')) + status = 0; + else + status = 100 * linebuff[0] + 10 * linebuff[1] + linebuff[2] - 111 * '0'; + + mb = ap_cpystrn(mb, linebuff + 4, me - mb); + + if (linebuff[len - 1] != '\n') + (void)ap_bskiplf(ctrl); + + if (linebuff[3] == '-') { + memcpy(buff, linebuff, 3); + buff[3] = ' '; + do { + len = ap_bgets(linebuff, sizeof linebuff, ctrl); + if (len == -1) + return -1; + if (linebuff[len - 1] != '\n') { + (void)ap_bskiplf(ctrl); + } + mb = ap_cpystrn(mb, linebuff + 4, me - mb); + } while (memcmp(linebuff, buff, 4) != 0); + } + return status; +} + +static long int send_dir(BUFF *data, request_rec *r, cache_req *c, char *cwd) +{ + char *buf, *buf2; + size_t buf_size; + char *filename; + int searchidx = 0; + char *searchptr = NULL; + int firstfile = 1; + unsigned long total_bytes_sent = 0; + register int n; + conn_rec *con = r->connection; + pool *p = r->pool; + char *dir, *path, *reldir, *site, *type = NULL; + char *basedir = ""; /* By default, path is relative to the $HOME + * dir */ + + /* create default sized buffers for the stuff below */ + buf_size = IOBUFSIZE; + buf = ap_palloc(r->pool, buf_size); + buf2 = ap_palloc(r->pool, buf_size); + + /* Save "scheme://site" prefix without password */ + site = ap_unparse_uri_components(p, &r->parsed_uri, UNP_OMITPASSWORD | UNP_OMITPATHINFO); + /* ... and path without query args */ + path = ap_unparse_uri_components(p, &r->parsed_uri, UNP_OMITSITEPART | UNP_OMITQUERY); + + /* If path began with /%2f, change the basedir */ + if (strncasecmp(path, "/%2f", 4) == 0) { + basedir = "/%2f"; + } + + /* Strip off a type qualifier. It is ignored for dir listings */ + if ((type = strstr(path, ";type=")) != NULL) + *type++ = '\0'; + + (void)decodeenc(path); + + while (path[1] == '/') /* collapse multiple leading slashes to one */ + ++path; + + /* Copy path, strip (all except the last) trailing slashes */ + /* (the trailing slash is needed for the dir component loop below) */ + path = dir = ap_pstrcat(r->pool, path, "/", NULL); + for (n = strlen(path); n > 1 && path[n - 1] == '/' && path[n - 2] == '/'; --n) + path[n - 1] = '\0'; + + /* print "ftp://host/" */ + n = ap_snprintf(buf, buf_size, DOCTYPE_HTML_3_2 + "<html><head><title>%s%s%s</title>\n" + "<base href=\"%s%s%s\"></head>\n" + "<body><h2>Directory of " + "<a href=\"/\">%s</a>/", + site, basedir, ap_escape_html(p, path), + site, basedir, ap_escape_uri(p, path), + site); + total_bytes_sent += ap_proxy_bputs2(buf, con->client, c); + + /* Add a link to the root directory (if %2f hack was used) */ + if (basedir[0] != '\0') { + total_bytes_sent += ap_proxy_bputs2("<a href=\"/%2f/\">%2f</a>/", con->client, c); + } + + for (dir = path + 1; (dir = strchr(dir, '/')) != NULL;) { + *dir = '\0'; + if ((reldir = strrchr(path + 1, '/')) == NULL) { + reldir = path + 1; + } + else + ++reldir; + /* print "path/" component */ + ap_snprintf(buf, buf_size, "<a href=\"%s%s/\">%s</a>/", + basedir, + ap_escape_uri(p, path), + ap_escape_html(p, reldir)); + total_bytes_sent += ap_proxy_bputs2(buf, con->client, c); + *dir = '/'; + while (*dir == '/') + ++dir; + } + + /* If the caller has determined the current directory, and it differs */ + /* from what the client requested, then show the real name */ + if (cwd == NULL || strncmp(cwd, path, strlen(cwd)) == 0) { + ap_snprintf(buf, buf_size, "</h2>\n<hr /><pre>"); + } + else { + ap_snprintf(buf, buf_size, "</h2>\n(%s)\n<hr /><pre>", + ap_escape_html(p, cwd)); + } + total_bytes_sent += ap_proxy_bputs2(buf, con->client, c); + + while (!con->aborted) { + n = ap_bgets(buf, buf_size, data); + if (n == -1) { /* input error */ + if (c != NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req, + "proxy: error reading from %s", c->url); + c = ap_proxy_cache_error(c); + } + break; + } + if (n == 0) + break; /* EOF */ + + if (buf[n - 1] == '\n') /* strip trailing '\n' */ + buf[--n] = '\0'; + if (buf[n - 1] == '\r') /* strip trailing '\r' if present */ + buf[--n] = '\0'; + + /* Handle unix-style symbolic link */ + if (buf[0] == 'l' && (filename = strstr(buf, " -> ")) != NULL) { + char *link_ptr = filename; + + do { + filename--; + } while (filename[0] != ' ' && filename > buf); + if (filename != buf) + *(filename++) = '\0'; + *(link_ptr++) = '\0'; + ap_snprintf(buf2, buf_size, "%s <a href=\"%s\">%s %s</a>\n", + ap_escape_html(p, buf), + ap_escape_uri(p, filename), + ap_escape_html(p, filename), + ap_escape_html(p, link_ptr)); + ap_cpystrn(buf, buf2, buf_size); + n = strlen(buf); + } + /* Handle unix style or DOS style directory */ + else if (buf[0] == 'd' || buf[0] == '-' || buf[0] == 'l' || ap_isdigit(buf[0])) { + if (ap_isdigit(buf[0])) { /* handle DOS dir */ + searchptr = strchr(buf, '<'); + if (searchptr != NULL) + *searchptr = '['; + searchptr = strchr(buf, '>'); + if (searchptr != NULL) + *searchptr = ']'; + } + + filename = strrchr(buf, ' '); + *(filename++) = 0; + + /* handle filenames with spaces in 'em */ + if (!strcmp(filename, ".") || !strcmp(filename, "..") || firstfile) { + firstfile = 0; + searchidx = filename - buf; + } + else if (searchidx != 0 && buf[searchidx] != 0) { + *(--filename) = ' '; + buf[searchidx - 1] = 0; + filename = &buf[searchidx]; + } + + /* Special handling for '.' and '..': append slash to link */ + if (!strcmp(filename, ".") || !strcmp(filename, "..") || buf[0] == 'd') { + ap_snprintf(buf2, buf_size, "%s <a href=\"%s/\">%s</a>\n", + ap_escape_html(p, buf), ap_escape_uri(p, filename), + ap_escape_html(p, filename)); + } + else { + ap_snprintf(buf2, buf_size, "%s <a href=\"%s\">%s</a>\n", + ap_escape_html(p, buf), + ap_escape_uri(p, filename), + ap_escape_html(p, filename)); + } + ap_cpystrn(buf, buf2, buf_size); + n = strlen(buf); + } + /* else??? What about other OS's output formats? */ + else { + strcat(buf, "\n"); /* re-append the newline char */ + ap_cpystrn(buf, ap_escape_html(p, buf), buf_size); + } + + total_bytes_sent += ap_proxy_bputs2(buf, con->client, c); + + ap_reset_timeout(r); /* reset timeout after successfule write */ + } + + total_bytes_sent += ap_proxy_bputs2("</pre><hr />\n", con->client, c); + total_bytes_sent += ap_proxy_bputs2(ap_psignature("", r), con->client, c); + total_bytes_sent += ap_proxy_bputs2("</body></html>\n", con->client, c); + + ap_bclose(data); + + ap_bflush(con->client); + + return total_bytes_sent; +} + +/* Common routine for failed authorization (i.e., missing or wrong password) + * to an ftp service. This causes most browsers to retry the request + * with username and password (which was presumably queried from the user) + * supplied in the Authorization: header. + * Note that we "invent" a realm name which consists of the + * ftp://user@host part of the reqest (sans password -if supplied but invalid-) + */ +static int ftp_unauthorized(request_rec *r, int log_it) +{ + r->proxyreq = NOT_PROXY; + /* + * Log failed requests if they supplied a password (log username/password + * guessing attempts) + */ + if (log_it) + ap_log_rerror(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, r, + "proxy: missing or failed auth to %s", + ap_unparse_uri_components(r->pool, + &r->parsed_uri, UNP_OMITPATHINFO)); + + ap_table_setn(r->err_headers_out, "WWW-Authenticate", + ap_pstrcat(r->pool, "Basic realm=\"", + ap_unparse_uri_components(r->pool, &r->parsed_uri, + UNP_OMITPASSWORD | UNP_OMITPATHINFO), + "\"", NULL)); + + return HTTP_UNAUTHORIZED; +} + +/* Set ftp server to TYPE {A,I,E} before transfer of a directory or file */ +static int ftp_set_TYPE(request_rec *r, BUFF *ctrl, char xfer_type) +{ + static char old_type[2] = {'A', '\0'}; /* After logon, mode is ASCII */ + int ret = HTTP_OK; + int rc; + + if (xfer_type == old_type[0]) + return ret; + + /* set desired type */ + old_type[0] = xfer_type; + ap_bvputs(ctrl, "TYPE ", old_type, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: TYPE %s", old_type); + +/* responses: 200, 421, 500, 501, 504, 530 */ + /* 200 Command okay. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 504 Command not implemented for that parameter. */ + /* 530 Not logged in. */ + rc = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", rc); + if (rc == -1 || rc == 421) { + ap_kill_timeout(r); + ret = ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server"); + } + else if (rc != 200 && rc != 504) { + ap_kill_timeout(r); + ret = ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Unable to set transfer type"); + } +/* Allow not implemented */ + else if (rc == 504) + /* ignore it silently */ ; + + return ret; +} + +/* Common cleanup routine: close open BUFFers or sockets, and return an error */ +static int ftp_cleanup_and_return(request_rec *r, BUFF *ctrl, BUFF *data, int csock, int dsock, int rc) +{ + if (ctrl != NULL) + ap_bclose(ctrl); + else if (csock != -1) + ap_pclosesocket(r->pool, csock); + + if (data != NULL) + ap_bclose(data); + else if (dsock != -1) + ap_pclosesocket(r->pool, dsock); + + ap_kill_timeout(r); + + return rc; +} + +/* + * Handles direct access of ftp:// URLs + * Original (Non-PASV) version from + * Troy Morrison <spiffnet@zoom.com> + * PASV added by Chuck + */ +int ap_proxy_ftp_handler(request_rec *r, cache_req *c, char *url) +{ + char *desthost, *path, *strp, *parms; + char *strp2; + char *cwd = NULL; + char *user = NULL; +/* char *account = NULL; how to supply an account in a URL? */ + const char *password = NULL; + const char *err; + int destport, i, j, len, rc, nocache = 0; + int csd = 0, sock = -1, dsock = -1; + struct sockaddr_in server; + struct hostent server_hp; + struct in_addr destaddr; + table *resp_hdrs; + BUFF *ctrl = NULL; + BUFF *data = NULL; + pool *p = r->pool; + char *destportstr = NULL; + const char *urlptr = NULL; + int one = 1; + NET_SIZE_T clen; + char xfer_type = 'A'; /* after ftp login, the default is ASCII */ + int get_dirlisting = 0; + + void *sconf = r->server->module_config; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + struct noproxy_entry *npent = (struct noproxy_entry *) conf->noproxies->elts; + struct nocache_entry *ncent = (struct nocache_entry *) conf->nocaches->elts; + +/* stuff for PASV mode */ + unsigned int presult, h0, h1, h2, h3, p0, p1; + unsigned int paddr; + unsigned short pport; + struct sockaddr_in data_addr; + int pasvmode = 0; + char pasv[64]; + char *pstr; + +/* stuff for responses */ + char resp[MAX_STRING_LEN]; + char *size = NULL; + +/* we only support GET and HEAD */ + + if (r->method_number != M_GET) + return HTTP_NOT_IMPLEMENTED; + +/* We break the URL into host, port, path-search */ + + urlptr = strstr(url, "://"); + if (urlptr == NULL) + return HTTP_BAD_REQUEST; + urlptr += 3; + destport = 21; + strp = strchr(urlptr, '/'); + if (strp == NULL) { + desthost = ap_pstrdup(p, urlptr); + urlptr = "/"; + } + else { + char *q = ap_palloc(p, strp - urlptr + 1); + memcpy(q, urlptr, strp - urlptr); + q[strp - urlptr] = '\0'; + urlptr = strp; + desthost = q; + } + + strp2 = strchr(desthost, ':'); + if (strp2 != NULL) { + *(strp2++) = '\0'; + if (ap_isdigit(*strp2)) { + destport = atoi(strp2); + destportstr = strp2; + } + } + path = strchr(urlptr, '/')+1; + + /* + * The "Authorization:" header must be checked first. We allow the user + * to "override" the URL-coded user [ & password ] in the Browsers' + * User&Password Dialog. NOTE that this is only marginally more secure + * than having the password travel in plain as part of the URL, because + * Basic Auth simply uuencodes the plain text password. But chances are + * still smaller that the URL is logged regularly. + */ + if ((password = ap_table_get(r->headers_in, "Authorization")) != NULL + && strcasecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0 + && (password = ap_pbase64decode(r->pool, password))[0] != ':') { + /* + * Note that this allocation has to be made from r->connection->pool + * because it has the lifetime of the connection. The other + * allocations are temporary and can be tossed away any time. + */ + user = ap_getword_nulls(r->connection->pool, &password, ':'); + r->connection->ap_auth_type = "Basic"; + r->connection->user = r->parsed_uri.user = user; + nocache = 1; /* This resource only accessible with + * username/password */ + } + else if ((user = r->parsed_uri.user) != NULL) { + user = ap_pstrdup(p, user); + decodeenc(user); + if ((password = r->parsed_uri.password) != NULL) { + char *tmp = ap_pstrdup(p, password); + decodeenc(tmp); + password = tmp; + } + nocache = 1; /* This resource only accessible with + * username/password */ + } + else { + user = "anonymous"; + password = "apache_proxy@"; + } + + /* check if ProxyBlock directive on this host */ + destaddr.s_addr = ap_inet_addr(desthost); + for (i = 0; i < conf->noproxies->nelts; i++) { + if (destaddr.s_addr == npent[i].addr.s_addr || + (npent[i].name != NULL && + (npent[i].name[0] == '*' || strstr(desthost, npent[i].name) != NULL))) + return ap_proxyerror(r, HTTP_FORBIDDEN, + "Connect to remote machine blocked"); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: connect to %s:%d", desthost, destport); + + parms = strchr(url, ';'); + if (parms != NULL) + *(parms++) = '\0'; + + memset(&server, 0, sizeof(struct sockaddr_in)); + server.sin_family = AF_INET; + server.sin_port = htons((unsigned short)destport); + err = ap_proxy_host2addr(desthost, &server_hp); + if (err != NULL) + return ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, err); + + sock = ap_psocket_ex(p, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + if (sock == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: error creating socket"); + return HTTP_INTERNAL_SERVER_ERROR; + } + +#if !defined(TPF) && !defined(BEOS) + if (conf->recv_buffer_size > 0 + && setsockopt(sock, SOL_SOCKET, SO_RCVBUF, + (const char *)&conf->recv_buffer_size, sizeof(int)) + == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "setsockopt(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default"); + } +#endif + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, + sizeof(one)) == -1) { +#ifndef _OSD_POSIX /* BS2000 has this option "always on" */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: error setting reuseaddr option: setsockopt(SO_REUSEADDR)"); + ap_pclosesocket(p, sock); + return HTTP_INTERNAL_SERVER_ERROR; +#endif /* _OSD_POSIX */ + } + +#ifdef SINIX_D_RESOLVER_BUG + { + struct in_addr *ip_addr = (struct in_addr *)*server_hp.h_addr_list; + + for (; ip_addr->s_addr != 0; ++ip_addr) { + memcpy(&server.sin_addr, ip_addr, sizeof(struct in_addr)); + i = ap_proxy_doconnect(sock, &server, r); + if (i == 0) + break; + } + } +#else + j = 0; + while (server_hp.h_addr_list[j] != NULL) { + memcpy(&server.sin_addr, server_hp.h_addr_list[j], + sizeof(struct in_addr)); + i = ap_proxy_doconnect(sock, &server, r); + if (i == 0) + break; + j++; + } +#endif + if (i == -1) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, ap_pstrcat(r->pool, + "Could not connect to remote machine: ", + strerror(errno), NULL))); + } + + /* record request_time for HTTP/1.1 age calculation */ + c->req_time = time(NULL); + + ctrl = ap_bcreate(p, B_RDWR | B_SOCKET); + ap_bpushfd(ctrl, sock, sock); +/* shouldn't we implement telnet control options here? */ + +#ifdef CHARSET_EBCDIC + ap_bsetflag(ctrl, B_ASCII2EBCDIC | B_EBCDIC2ASCII, 1); +#endif /* CHARSET_EBCDIC */ + + /* possible results: */ + /* 120 Service ready in nnn minutes. */ + /* 220 Service ready for new user. */ + /* 421 Service not available, closing control connection. */ + ap_hard_timeout("proxy ftp", r); + i = ftp_getrc_msg(ctrl, resp, sizeof resp); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + } +#if 0 + if (i == 120) { + /* + * RFC2068 states: 14.38 Retry-After + * + * The Retry-After response-header field can be used with a 503 (Service + * Unavailable) response to indicate how long the service is expected + * to be unavailable to the requesting client. The value of this + * field can be either an HTTP-date or an integer number of seconds + * (in decimal) after the time of the response. Retry-After = + * "Retry-After" ":" ( HTTP-date | delta-seconds ) + */ +/**INDENT** Error@756: Unbalanced parens */ + ap_set_header("Retry-After", ap_psprintf(p, "%u", 60 * wait_mins); + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE, resp)); + } +#endif + if (i != 220) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, resp)); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: connected."); + + ap_bvputs(ctrl, "USER ", user, CRLF, NULL); + ap_bflush(ctrl); /* capture any errors */ + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: USER %s", user); + + /* possible results; 230, 331, 332, 421, 500, 501, 530 */ + /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */ + /* 230 User logged in, proceed. */ + /* 331 User name okay, need password. */ + /* 332 Need account for login. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* (This may include errors such as command line too long.) */ + /* 501 Syntax error in parameters or arguments. */ + /* 530 Not logged in. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + } + if (i == 530) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ftp_unauthorized(r, 1)); + } + if (i != 230 && i != 331) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + + if (i == 331) { /* send password */ + if (password == NULL) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ftp_unauthorized(r, 0)); + } + ap_bvputs(ctrl, "PASS ", password, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PASS %s", password); + /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */ + /* 230 User logged in, proceed. */ + /* 332 Need account for login. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 503 Bad sequence of commands. */ + /* 530 Not logged in. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + } + if (i == 332) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_UNAUTHORIZED, + "Need account for login")); + } + /* @@@ questionable -- we might as well return a 403 Forbidden here */ + if (i == 530) /* log it: passwd guessing attempt? */ + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ftp_unauthorized(r, 1)); + if (i != 230 && i != 202) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + + /* + * Special handling for leading "%2f": this enforces a "cwd /" out of the + * $HOME directory which was the starting point after login + */ + if (strncasecmp(path, "%2f", 3) == 0) { + path += 3; + while (*path == '/') /* skip leading '/' (after root %2f) */ + ++path; + ap_bputs("CWD /" CRLF, ctrl); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: CWD /"); + + /* possible results: 250, 421, 500, 501, 502, 530, 550 */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + else if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + else if (i != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + +/* set the directory (walk directory component by component): + * this is what we must do if we don't know the OS type of the remote + * machine + */ + for (; (strp = strchr(path, '/')) != NULL; path = strp + 1) { + char *slash = strp; + + *slash = '\0'; + + /* Skip multiple '/' (or trailing '/') to avoid 500 errors */ + while (strp[1] == '/') + ++strp; + if (strp[1] == '\0') + break; + + len = decodeenc(path); /* Note! This decodes a %2f -> "/" */ + if (strchr(path, '/')) /* were there any '/' characters? */ + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_REQUEST, + "Use of %2F is only allowed at the base directory")); + + ap_bvputs(ctrl, "CWD ", path, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: CWD %s", path); + *slash = '/'; + +/* responses: 250, 421, 500, 501, 502, 530, 550 */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (i == 500 || i == 501) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_REQUEST, + "Syntax error in filename (reported by ftp server)")); + if (i != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + + if (parms != NULL && strncmp(parms, "type=", 5) == 0 + && ap_isalpha(parms[5])) { + /* + * "type=d" forces a dir listing. The other types (i|a|e) are + * directly used for the ftp TYPE command + */ + if (!(get_dirlisting = (parms[5] == 'd'))) + xfer_type = ap_toupper(parms[5]); + + /* Check valid types, rather than ignoring invalid types silently: */ + if (strchr("AEI", xfer_type) == NULL) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_REQUEST, ap_pstrcat(r->pool, + "ftp proxy supports only types 'a', 'i', or 'e': \"", + parms, "\" is invalid.", NULL))); + } + else { + /* make binary transfers the default */ + xfer_type = 'I'; + } + +/* try to set up PASV data connection first */ + dsock = ap_psocket_ex(p, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + if (dsock == -1) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: error creating PASV socket")); + } + +#if !defined (TPF) && !defined(BEOS) + if (conf->recv_buffer_size) { + if (setsockopt(dsock, SOL_SOCKET, SO_RCVBUF, + (const char *)&conf->recv_buffer_size, sizeof(int)) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "setsockopt(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default"); + } + } +#endif + + ap_bputs("PASV" CRLF, ctrl); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PASV command issued"); +/* possible results: 227, 421, 500, 501, 502, 530 */ + /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + + i = ap_bgets(pasv, sizeof(pasv), ctrl); + if (i == -1 || i == 421) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: PASV: control connection is toast")); + } + else { + pasv[i - 1] = '\0'; + pstr = strtok(pasv, " "); /* separate result code */ + if (pstr != NULL) { + presult = atoi(pstr); + if (*(pstr + strlen(pstr) + 1) == '=') + pstr += strlen(pstr) + 2; + else { + pstr = strtok(NULL, "("); /* separate address & port + * params */ + if (pstr != NULL) + pstr = strtok(NULL, ")"); + } + } + else + presult = atoi(pasv); + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", presult); + + if (presult == 227 && pstr != NULL && (sscanf(pstr, + "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) { + /* pardon the parens, but it makes gcc happy */ + paddr = (((((h3 << 8) + h2) << 8) + h1) << 8) + h0; + pport = (p1 << 8) + p0; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: contacting host %d.%d.%d.%d:%d", + h3, h2, h1, h0, pport); + data_addr.sin_family = AF_INET; + data_addr.sin_addr.s_addr = htonl(paddr); + data_addr.sin_port = htons(pport); + i = ap_proxy_doconnect(dsock, &data_addr, r); + + if (i == -1) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + ap_pstrcat(r->pool, + "Could not connect to remote machine: ", + strerror(errno), NULL))); + } + pasvmode = 1; + } + else { + ap_pclosesocket(p, dsock); /* and try the regular way */ + dsock = -1; + } + } + + if (!pasvmode) { /* set up data connection */ + clen = sizeof(struct sockaddr_in); + if (getsockname(sock, (struct sockaddr *)&server, &clen) < 0) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: error getting socket address")); + } + + dsock = ap_psocket_ex(p, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + if (dsock == -1) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: error creating socket")); + } + + if (setsockopt(dsock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, + sizeof(one)) == -1) { +#ifndef _OSD_POSIX /* BS2000 has this option "always on" */ + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: error setting reuseaddr option")); +#endif /* _OSD_POSIX */ + } + + if (bind(dsock, (struct sockaddr *)&server, + sizeof(struct sockaddr_in)) == -1) { + + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + ap_psprintf(p, "proxy: error binding to ftp data socket %s:%d", + inet_ntoa(server.sin_addr), server.sin_port))); + } + listen(dsock, 2); /* only need a short queue */ + } + +/* set request; "path" holds last path component */ + len = decodeenc(path); + if (strchr(path, '/')) /* were there any '/' characters? */ + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_REQUEST, + "Use of %2F is only allowed at the base directory")); + + /* TM - if len == 0 then it must be a directory (you can't RETR nothing) */ + + if (len == 0) { + get_dirlisting = 1; + } + else { + ap_bvputs(ctrl, "SIZE ", path, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: SIZE %s", path); + i = ftp_getrc_msg(ctrl, resp, sizeof resp); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d with response %s", i, resp); + if (i != 500) { /* Size command not recognized */ + if (i == 550) { /* Not a regular file */ + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: SIZE shows this is a directory"); + get_dirlisting = 1; + ap_bvputs(ctrl, "CWD ", path, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: CWD %s", path); + + /* possible results: 250, 421, 500, 501, 502, 530, 550 */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (i != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + path = ""; + len = 0; + } + else if (i == 213) {/* Size command ok */ + for (j = 0; j < sizeof resp && ap_isdigit(resp[j]); j++); + resp[j] = '\0'; + if (resp[0] != '\0') + size = ap_pstrdup(p, resp); + } + } + } + +#ifdef AUTODETECT_PWD + ap_bvputs(ctrl, "PWD", CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PWD"); +/* responses: 257, 500, 501, 502, 421, 550 */ + /* 257 "<directory-name>" <commentary> */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 550 Requested action not taken. */ + i = ftp_getrc_msg(ctrl, resp, sizeof resp); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PWD returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (i == 257) { + const char *dirp = resp; + cwd = ap_getword_conf(r->pool, &dirp); + } +#endif /* AUTODETECT_PWD */ + + if (get_dirlisting) { + if (len != 0) + ap_bvputs(ctrl, "LIST ", path, CRLF, NULL); + else + ap_bputs("LIST -lag" CRLF, ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: LIST %s", (len == 0 ? "" : path)); + } + else { + ftp_set_TYPE(r, ctrl, xfer_type); + ap_bvputs(ctrl, "RETR ", path, CRLF, NULL); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: RETR %s", path); + } + ap_bflush(ctrl); +/* RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530, 550 + NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502, 530 */ + /* 110 Restart marker reply. */ + /* 125 Data connection already open; transfer starting. */ + /* 150 File status okay; about to open data connection. */ + /* 226 Closing data connection. */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 425 Can't open data connection. */ + /* 426 Connection closed; transfer aborted. */ + /* 450 Requested file action not taken. */ + /* 451 Requested action aborted. Local error in processing. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + rc = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", rc); + if (rc == -1 || rc == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (rc == 550) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: RETR failed, trying LIST instead"); + get_dirlisting = 1; + ftp_set_TYPE(r, ctrl, 'A'); /* directories must be transferred in + * ASCII */ + + ap_bvputs(ctrl, "CWD ", path, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: CWD %s", path); + /* possible results: 250, 421, 500, 501, 502, 530, 550 */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + rc = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", rc); + if (rc == -1 || rc == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (rc == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (rc != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + +#ifdef AUTODETECT_PWD + ap_bvputs(ctrl, "PWD", CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PWD"); +/* responses: 257, 500, 501, 502, 421, 550 */ + /* 257 "<directory-name>" <commentary> */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 550 Requested action not taken. */ + i = ftp_getrc_msg(ctrl, resp, sizeof resp); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PWD returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (i == 257) { + const char *dirp = resp; + cwd = ap_getword_conf(r->pool, &dirp); + } +#endif /* AUTODETECT_PWD */ + + ap_bputs("LIST -lag" CRLF, ctrl); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: LIST -lag"); + rc = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", rc); + if (rc == -1 || rc == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + } + ap_kill_timeout(r); + if (rc != 125 && rc != 150 && rc != 226 && rc != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + + r->status = HTTP_OK; + r->status_line = "200 OK"; + + resp_hdrs = ap_make_table(p, 2); + c->hdrs = resp_hdrs; + + ap_table_setn(resp_hdrs, "Date", ap_gm_timestr_822(r->pool, r->request_time)); + ap_table_setn(resp_hdrs, "Server", ap_get_server_version()); + + if (get_dirlisting) { + ap_table_setn(resp_hdrs, "Content-Type", "text/html"); +#ifdef CHARSET_EBCDIC + r->ebcdic.conv_out = 1; /* server-generated */ +#endif + } + else { +#ifdef CHARSET_EBCDIC + r->ebcdic.conv_out = 0; /* do not convert what we read from the ftp + * server */ +#endif + if (r->content_type != NULL) { + ap_table_setn(resp_hdrs, "Content-Type", r->content_type); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: Content-Type set to %s", r->content_type); + } + else { + ap_table_setn(resp_hdrs, "Content-Type", ap_default_type(r)); + } + if (xfer_type != 'A' && size != NULL) { + /* We "trust" the ftp server to really serve (size) bytes... */ + ap_table_set(resp_hdrs, "Content-Length", size); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: Content-Length set to %s", size); + } + } + if (r->content_encoding != NULL && r->content_encoding[0] != '\0') { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: Content-Encoding set to %s", r->content_encoding); + ap_table_setn(resp_hdrs, "Content-Encoding", r->content_encoding); + } + +/* check if NoCache directive on this host */ + if (nocache == 0) { + for (i = 0; i < conf->nocaches->nelts; i++) { + if (destaddr.s_addr == ncent[i].addr.s_addr || + (ncent[i].name != NULL && + (ncent[i].name[0] == '*' || + strstr(desthost, ncent[i].name) != NULL))) { + nocache = 1; + break; + } + } + } + + i = ap_proxy_cache_update(c, resp_hdrs, 0, nocache); + + if (i != DECLINED) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, i); + } + + if (!pasvmode) { /* wait for connection */ + ap_hard_timeout("proxy ftp data connect", r); + clen = sizeof(struct sockaddr_in); + do + csd = accept(dsock, (struct sockaddr *)&server, &clen); + while (csd == -1 && errno == EINTR); + if (csd == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: failed to accept data connection"); + if (c != NULL) + c = ap_proxy_cache_error(c); + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + data = ap_bcreate(p, B_RDWR | B_SOCKET); + ap_bpushfd(data, csd, -1); + ap_kill_timeout(r); + } + else { + data = ap_bcreate(p, B_RDWR | B_SOCKET); + ap_bpushfd(data, dsock, dsock); + } + + ap_hard_timeout("proxy receive", r); + + /* send response */ + /* write status line and headers to the cache file */ + ap_proxy_write_headers(c, ap_pstrcat(p, "HTTP/1.1 ", r->status_line, NULL), resp_hdrs); + + /* Setup the headers for our client from upstreams response-headers */ + ap_overlap_tables(r->headers_out, resp_hdrs, AP_OVERLAP_TABLES_SET); + /* Add X-Cache header */ + ap_table_setn(r->headers_out, "X-Cache", + ap_pstrcat(r->pool, "MISS from ", + ap_get_server_name(r), NULL)); + /* The Content-Type of this response is the upstream one. */ + r->content_type = ap_table_get(r->headers_out, "Content-Type"); + /* finally output the headers to the client */ + ap_send_http_header(r); + +#ifdef CHARSET_EBCDIC + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out); +#endif +/* send body */ + if (!r->header_only) { + if (!get_dirlisting) { +/* we need to set this for ap_proxy_send_fb()... */ + if (c != NULL) + c->cache_completion = 0; + ap_proxy_send_fb(data, r, c, -1, 0, 0, conf->io_buffer_size); + } + else { + send_dir(data, r, c, cwd); + } + /* ap_proxy_send_fb() closes the socket */ + data = NULL; + dsock = -1; + + /* + * We checked for 125||150||226||250 above. See if another rc is + * pending, and fetch it: + */ + if (rc == 125 || rc == 150) + rc = ftp_getrc(ctrl); + } + else { +/* abort the transfer: we send the header only */ + ap_bputs("ABOR" CRLF, ctrl); + ap_bflush(ctrl); + if (data != NULL) { + ap_bclose(data); + data = NULL; + dsock = -1; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: ABOR"); +/* responses: 225, 226, 421, 500, 501, 502 */ + /* 225 Data connection open; no transfer in progress. */ + /* 226 Closing data connection. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + } + + ap_kill_timeout(r); + ap_proxy_cache_tidy(c); + +/* finish */ + ap_bputs("QUIT" CRLF, ctrl); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: QUIT"); +/* responses: 221, 500 */ + /* 221 Service closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: QUIT: status %d", i); + + ap_bclose(ctrl); + + ap_rflush(r); /* flush before garbage collection */ + + ap_proxy_garbage_coll(r); + + return OK; +} diff --git a/APACHE_1_3_42/src/modules/proxy/proxy_http.c b/APACHE_1_3_42/src/modules/proxy/proxy_http.c new file mode 100644 index 0000000000..f6cdae739a --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/proxy_http.c @@ -0,0 +1,646 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* HTTP routines for Apache proxy */ + +#include "mod_proxy.h" +#include "http_log.h" +#include "http_main.h" +#include "http_core.h" +#include "util_date.h" + +/* + * Canonicalise http-like URLs. + * scheme is the scheme for the URL + * url is the URL starting with the first '/' + * def_port is the default port for this scheme. + */ +int ap_proxy_http_canon(request_rec *r, char *url, const char *scheme, int def_port) +{ + char *host, *path, *search, sport[7]; + const char *err; + int port; + + /* + * do syntatic check. We break the URL into host, port, path, search + */ + port = def_port; + err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port); + if (err) + return HTTP_BAD_REQUEST; + + /* now parse path/search args, according to rfc1738 */ + /* + * N.B. if this isn't a true proxy request, then the URL _path_ has + * already been decoded. True proxy requests have r->uri == + * r->unparsed_uri, and no others have that property. + */ + if (r->uri == r->unparsed_uri) { + search = strchr(url, '?'); + if (search != NULL) + *(search++) = '\0'; + } + else + search = r->args; + + /* process path */ + path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, + r->proxyreq); + if (path == NULL) + return HTTP_BAD_REQUEST; + + if (port != def_port) + ap_snprintf(sport, sizeof(sport), ":%d", port); + else + sport[0] = '\0'; + + r->filename = ap_pstrcat(r->pool, "proxy:", scheme, "://", host, sport, "/", + path, (search) ? "?" : "", (search) ? search : "", NULL); + return OK; +} + +/* handle the conversion of URLs in the ProxyPassReverse function */ +static const char *proxy_location_reverse_map(request_rec *r, const char *url) +{ + void *sconf; + proxy_server_conf *conf; + struct proxy_alias *ent; + int i, l1, l2; + char *u; + + sconf = r->server->module_config; + conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + l1 = strlen(url); + ent = (struct proxy_alias *)conf->raliases->elts; + for (i = 0; i < conf->raliases->nelts; i++) { + l2 = strlen(ent[i].real); + if (l1 >= l2 && strncmp(ent[i].real, url, l2) == 0) { + u = ap_pstrcat(r->pool, ent[i].fake, &url[l2], NULL); + return ap_construct_url(r->pool, u, r); + } + } + return url; +} + +/* + * This handles http:// URLs, and other URLs using a remote proxy over http + * If proxyhost is NULL, then contact the server directly, otherwise + * go via the proxy. + * Note that if a proxy is used, then URLs other than http: can be accessed, + * also, if we have trouble which is clearly specific to the proxy, then + * we return DECLINED so that we can try another proxy. (Or the direct + * route.) + */ +int ap_proxy_http_handler(request_rec *r, cache_req *c, char *url, + const char *proxyhost, int proxyport) +{ + const char *strp; + char *strp2; + const char *err, *desthost; + int i, j, sock,/* len,*/ backasswards; + table *req_hdrs, *resp_hdrs; + array_header *reqhdrs_arr; + table_entry *reqhdrs_elts; + struct sockaddr_in server; + struct in_addr destaddr; + struct hostent server_hp; + BUFF *f; + char buffer[HUGE_STRING_LEN]; + char portstr[32]; + pool *p = r->pool; + int destport = 0; + const char *chunked = NULL; + char *destportstr = NULL; + const char *urlptr = NULL; + const char *datestr, *urlstr; + int result, major, minor; + const char *content_length; + + void *sconf = r->server->module_config; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + struct noproxy_entry *npent = (struct noproxy_entry *) conf->noproxies->elts; + struct nocache_entry *ncent = (struct nocache_entry *) conf->nocaches->elts; + int nocache = 0; + + if (conf->cache.root == NULL) + nocache = 1; + + memset(&server, '\0', sizeof(server)); + server.sin_family = AF_INET; + + /* We break the URL into host, port, path-search */ + + urlptr = strstr(url, "://"); + if (urlptr == NULL) + return HTTP_BAD_REQUEST; + urlptr += 3; + destport = DEFAULT_HTTP_PORT; + strp = strchr(urlptr, '/'); + if (strp == NULL) { + desthost = ap_pstrdup(p, urlptr); + urlptr = "/"; + } + else { + char *q = ap_palloc(p, strp - urlptr + 1); + memcpy(q, urlptr, strp - urlptr); + q[strp - urlptr] = '\0'; + urlptr = strp; + desthost = q; + } + + strp2 = strchr(desthost, ':'); + if (strp2 != NULL) { + *(strp2++) = '\0'; + if (ap_isdigit(*strp2)) { + destport = atoi(strp2); + destportstr = strp2; + } + } + + /* check if ProxyBlock directive on this host */ + destaddr.s_addr = ap_inet_addr(desthost); + for (i = 0; i < conf->noproxies->nelts; i++) { + if (destaddr.s_addr == npent[i].addr.s_addr || + (npent[i].name != NULL && + (npent[i].name[0] == '*' || strstr(desthost, npent[i].name) != NULL))) + return ap_proxyerror(r, HTTP_FORBIDDEN, + "Connect to remote machine blocked"); + } + + if (proxyhost != NULL) { + server.sin_port = htons((unsigned short)proxyport); + err = ap_proxy_host2addr(proxyhost, &server_hp); + if (err != NULL) + return DECLINED; /* try another */ + } + else { + server.sin_port = htons((unsigned short)destport); + err = ap_proxy_host2addr(desthost, &server_hp); + if (err != NULL) + return ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, err); + } + + + /* + * we have worked out who exactly we are going to connect to, now make + * that connection... + */ + sock = ap_psocket_ex(p, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + if (sock == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: error creating socket"); + return HTTP_INTERNAL_SERVER_ERROR; + } + +#if !defined(TPF) && !defined(BEOS) + if (conf->recv_buffer_size) { + if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, + (const char *)&conf->recv_buffer_size, sizeof(int)) + == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "setsockopt(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default"); + } + } +#endif + +#ifdef SINIX_D_RESOLVER_BUG + { + struct in_addr *ip_addr = (struct in_addr *)*server_hp.h_addr_list; + + for (; ip_addr->s_addr != 0; ++ip_addr) { + memcpy(&server.sin_addr, ip_addr, sizeof(struct in_addr)); + i = ap_proxy_doconnect(sock, &server, r); + if (i == 0) + break; + /* + * Even if the connection was unsuccesful we should + * reinit the socket + */ + sock = ap_psocket_ex(p, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + } + } +#else + j = 0; + while (server_hp.h_addr_list[j] != NULL) { + memcpy(&server.sin_addr, server_hp.h_addr_list[j], + sizeof(struct in_addr)); + i = ap_proxy_doconnect(sock, &server, r); + if (i == 0) + break; + /* + * Even if the connection was unsuccesful we should + * reinit the socket + */ + sock = ap_psocket_ex(p, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + j++; + } +#endif + if (i == -1) { + if (proxyhost != NULL) + return DECLINED; /* try again another way */ + else + return ap_proxyerror(r, HTTP_BAD_GATEWAY, ap_pstrcat(r->pool, + "Could not connect to remote machine: ", + strerror(errno), NULL)); + } + + /* record request_time for HTTP/1.1 age calculation */ + c->req_time = time(NULL); + + /* + * build upstream-request headers by stripping r->headers_in from + * connection specific headers. We must not remove the Connection: header + * from r->headers_in, we still have to react to Connection: close + */ + req_hdrs = ap_copy_table(r->pool, r->headers_in); + ap_proxy_clear_connection(r->pool, req_hdrs); + + /* + * At this point, we start sending the HTTP/1.1 request to the remote + * server (proxy or otherwise). + */ + f = ap_bcreate(p, B_RDWR | B_SOCKET); + ap_bpushfd(f, sock, sock); + + ap_hard_timeout("proxy send", r); + ap_bvputs(f, r->method, " ", proxyhost ? url : urlptr, " HTTP/1.1" CRLF, + NULL); + /* Send Host: now, adding it to req_hdrs wouldn't be much better */ + if (destportstr != NULL && destport != DEFAULT_HTTP_PORT) + ap_bvputs(f, "Host: ", desthost, ":", destportstr, CRLF, NULL); + else + ap_bvputs(f, "Host: ", desthost, CRLF, NULL); + + if (conf->viaopt == via_block) { + /* Block all outgoing Via: headers */ + ap_table_unset(req_hdrs, "Via"); + } + else if (conf->viaopt != via_off) { + /* Create a "Via:" request header entry and merge it */ + i = ap_get_server_port(r); + if (ap_is_default_port(i, r)) { + strcpy(portstr, ""); + } + else { + ap_snprintf(portstr, sizeof portstr, ":%d", i); + } + /* Generate outgoing Via: header with/without server comment: */ + ap_table_mergen(req_hdrs, "Via", + (conf->viaopt == via_full) + ? ap_psprintf(p, "%d.%d %s%s (%s)", + HTTP_VERSION_MAJOR(r->proto_num), + HTTP_VERSION_MINOR(r->proto_num), + ap_get_server_name(r), portstr, + SERVER_BASEVERSION) + : ap_psprintf(p, "%d.%d %s%s", + HTTP_VERSION_MAJOR(r->proto_num), + HTTP_VERSION_MINOR(r->proto_num), + ap_get_server_name(r), portstr) + ); + } + + /* the X-* headers are only added if we are a reverse + * proxy, otherwise we would be giving away private information. + */ + if (r->proxyreq == PROXY_PASS) { + const char *buf; + + /* + * Add X-Forwarded-For: so that the upstream has a chance to determine, + * where the original request came from. + */ + ap_table_mergen(req_hdrs, "X-Forwarded-For", r->connection->remote_ip); + + /* Add X-Forwarded-Host: so that upstream knows what the + * original request hostname was. + */ + if ((buf = ap_table_get(r->headers_in, "Host"))) { + ap_table_mergen(req_hdrs, "X-Forwarded-Host", buf); + } + + /* Add X-Forwarded-Server: so that upstream knows what the + * name of this proxy server is (if there are more than one) + * XXX: This duplicates Via: - do we strictly need it? + */ + ap_table_mergen(req_hdrs, "X-Forwarded-Server", r->server->server_hostname); + } + + /* we don't yet support keepalives - but we will soon, I promise! + * XXX: This introduces various HTTP Request vulnerabilies if not + * properly implemented. Before changing this .. be certain to + * add a hard-close of the connection if the T-E and C-L headers + * are both present, or the C-L header is malformed. + */ + ap_table_set(req_hdrs, "Connection", "close"); + + reqhdrs_arr = ap_table_elts(req_hdrs); + reqhdrs_elts = (table_entry *)reqhdrs_arr->elts; + for (i = 0; i < reqhdrs_arr->nelts; i++) { + if (reqhdrs_elts[i].key == NULL || reqhdrs_elts[i].val == NULL + + /* + * Clear out hop-by-hop request headers not to send: RFC2616 13.5.1 + * says we should strip these headers: + */ + || !strcasecmp(reqhdrs_elts[i].key, "Host") /* Already sent */ + || !strcasecmp(reqhdrs_elts[i].key, "Keep-Alive") + || !strcasecmp(reqhdrs_elts[i].key, "TE") + || !strcasecmp(reqhdrs_elts[i].key, "Trailer") + || !strcasecmp(reqhdrs_elts[i].key, "Transfer-Encoding") + || !strcasecmp(reqhdrs_elts[i].key, "Upgrade") + /* + * XXX: @@@ FIXME: "Proxy-Authorization" should *only* be suppressed + * if THIS server requested the authentication, not when a frontend + * proxy requested it! + * + * The solution to this problem is probably to strip out the + * Proxy-Authorisation header in the authorisation code itself, not + * here. This saves us having to signal somehow whether this request + * was authenticated or not. + */ + || !strcasecmp(reqhdrs_elts[i].key, "Proxy-Authorization")) + continue; + ap_bvputs(f, reqhdrs_elts[i].key, ": ", reqhdrs_elts[i].val, CRLF, NULL); + } + + /* the obligatory empty line to mark the end of the headers */ + ap_bputs(CRLF, f); + + /* and flush the above away */ + ap_bflush(f); + + /* and kill the send timeout */ + ap_kill_timeout(r); + + + /* read the request data, and pass it to the backend. + * we might encounter a stray 100-continue reponse from a PUT or POST, + * if this happens we ignore the 100 continue status line and read the + * response again. + */ + { + /* send the request data, if any. */ + ap_hard_timeout("proxy receive request data", r); + if (ap_should_client_block(r)) { + while ((i = ap_get_client_block(r, buffer, sizeof buffer)) > 0) { + ap_reset_timeout(r); + ap_bwrite(f, buffer, i); + } + } + ap_bflush(f); + ap_kill_timeout(r); + + + /* then, read a response line */ + ap_hard_timeout("proxy receive response status line", r); + result = ap_proxy_read_response_line(f, r, buffer, sizeof(buffer)-1, &backasswards, &major, &minor); + ap_kill_timeout(r); + + /* trap any errors */ + if (result != OK) { + ap_bclose(f); + return result; + } + + /* if this response was 100-continue, a stray response has been caught. + * read the line again for the real response + */ + if (r->status == 100) { + ap_hard_timeout("proxy receive response status line", r); + result = ap_proxy_read_response_line(f, r, buffer, sizeof(buffer)-1, &backasswards, &major, &minor); + ap_kill_timeout(r); + + /* trap any errors */ + if (result != OK) { + ap_bclose(f); + return result; + } + } + } + + + /* + * We have our response status line from the convoluted code above, + * now we read the headers to continue. + */ + ap_hard_timeout("proxy receive response headers", r); + + /* + * Is it an HTTP/1 response? Do some sanity checks on the response. (This + * is buggy if we ever see an HTTP/1.10) + */ + if (backasswards == 0) { + + /* read the response headers. */ + /* N.B. for HTTP/1.0 clients, we have to fold line-wrapped headers */ + /* Also, take care with headers with multiple occurences. */ + + resp_hdrs = ap_proxy_read_headers(r, buffer, sizeof(buffer), f); + if (resp_hdrs == NULL) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, r->server, + "proxy: Bad HTTP/%d.%d header returned by %s (%s)", + major, minor, r->uri, r->method); + resp_hdrs = ap_make_table(p, 20); + nocache = 1; /* do not cache this broken file */ + } + + /* handle Via header in the response */ + if (conf->viaopt != via_off && conf->viaopt != via_block) { + /* Create a "Via:" response header entry and merge it */ + i = ap_get_server_port(r); + if (ap_is_default_port(i, r)) { + strcpy(portstr, ""); + } + else { + ap_snprintf(portstr, sizeof portstr, ":%d", i); + } + ap_table_mergen((table *)resp_hdrs, "Via", + (conf->viaopt == via_full) + ? ap_psprintf(p, "%d.%d %s%s (%s)", + major, minor, + ap_get_server_name(r), portstr, + SERVER_BASEVERSION) + : ap_psprintf(p, "%d.%d %s%s", + major, minor, + ap_get_server_name(r), portstr) + ); + } + + /* is this content chunked? */ + chunked = ap_table_get(resp_hdrs, "Transfer-Encoding"); + if (chunked && (strcasecmp(chunked, "chunked") != 0)) { + ap_kill_timeout(r); + return ap_proxyerror(r, HTTP_BAD_GATEWAY, ap_pstrcat(r->pool, + "Unsupported Transfer-Encoding ", chunked, + " from remote server", NULL)); + } + + /* strip hop-by-hop headers defined by Connection and RFC2616 */ + ap_proxy_clear_connection(p, resp_hdrs); + + content_length = ap_table_get(resp_hdrs, "Content-Length"); + if (content_length != NULL) { + if (chunked) { + /* XXX: We would unset keep-alive here, to the proxy + * origin server, for safety's sake but we aren't using + * keep-alives (we force Connection: close above) + */ + nocache = 1; /* do not cache this suspect file */ + ap_table_unset(resp_hdrs, "Content-Length"); + } + else { + char *len_end; + errno = 0; + c->len = ap_strtol(content_length, &len_end, 10); + + if (errno || (c->len < 0) || (len_end && *len_end)) { + ap_kill_timeout(r); + return ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Invalid Content-Length from remote" + " server"); + } + } + } + } + else { + /* an http/0.9 response */ + + /* no headers */ + resp_hdrs = ap_make_table(p, 20); + } + + ap_kill_timeout(r); + + /* + * HTTP/1.1 requires us to accept 3 types of dates, but only generate one + * type + */ + /* + * we SET the dates here, obliterating possible multiple dates, as only + * one of each date makes sense in each response. + */ + if ((datestr = ap_table_get(resp_hdrs, "Date")) != NULL) + ap_table_set(resp_hdrs, "Date", ap_proxy_date_canon(p, datestr)); + if ((datestr = ap_table_get(resp_hdrs, "Last-Modified")) != NULL) + ap_table_set(resp_hdrs, "Last-Modified", ap_proxy_date_canon(p, datestr)); + if ((datestr = ap_table_get(resp_hdrs, "Expires")) != NULL) + ap_table_set(resp_hdrs, "Expires", ap_proxy_date_canon(p, datestr)); + + /* handle the ProxyPassReverse mappings */ + if ((urlstr = ap_table_get(resp_hdrs, "Location")) != NULL) + ap_table_set(resp_hdrs, "Location", proxy_location_reverse_map(r, urlstr)); + if ((urlstr = ap_table_get(resp_hdrs, "URI")) != NULL) + ap_table_set(resp_hdrs, "URI", proxy_location_reverse_map(r, urlstr)); + if ((urlstr = ap_table_get(resp_hdrs, "Content-Location")) != NULL) + ap_table_set(resp_hdrs, "Content-Location", proxy_location_reverse_map(r, urlstr)); + +/* check if NoCache directive on this host */ + if (nocache == 0) { + for (i = 0; i < conf->nocaches->nelts; i++) { + if (destaddr.s_addr == ncent[i].addr.s_addr || + (ncent[i].name != NULL && + (ncent[i].name[0] == '*' || + strstr(desthost, ncent[i].name) != NULL))) { + nocache = 1; + break; + } + } + + /* + * update the cache file, possibly even fulfilling the request if it + * turns out a conditional allowed us to serve the object from the + * cache... + */ + i = ap_proxy_cache_update(c, resp_hdrs, !backasswards, nocache); + if (i != DECLINED) { + ap_bclose(f); + return i; + } + + /* write status line and headers to the cache file */ + ap_proxy_write_headers(c, ap_pstrcat(p, "HTTP/1.1 ", r->status_line, NULL), resp_hdrs); + } + + /* Setup the headers for our client from upstreams response-headers */ + ap_proxy_table_replace(r->headers_out, resp_hdrs); + /* Add X-Cache header - be careful not to obliterate any upstream headers */ + ap_table_mergen(r->headers_out, "X-Cache", + ap_pstrcat(r->pool, "MISS from ", + ap_get_server_name(r), NULL)); + /* The Content-Type of this response is the upstream one. */ + r->content_type = ap_table_get(r->headers_out, "Content-Type"); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Content-Type: %s", r->content_type); + + /* finally output the headers to the client */ + ap_send_http_header(r); + + /* + * Is it an HTTP/0.9 respose? If so, send the extra data we read from + * upstream as the start of the reponse to client + */ +/* FIXME: This code is broken: we try and write a buffer and length that + * were never intelligently initialised. Rather have a bit of broken protocol + * handling for now than broken code. + */ +/* + if (backasswards) { + ap_hard_timeout("proxy send assbackward", r); + + ap_bwrite(r->connection->client, buffer, len); + if (c != NULL && c->fp != NULL && ap_bwrite(c->fp, buffer, len) != len) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req, + "proxy: error writing extra data to %s", c->tempfile); + c = ap_proxy_cache_error(c); + } + ap_kill_timeout(r); + } +*/ + +#ifdef CHARSET_EBCDIC + /* + * What we read/write after the header should not be modified (i.e., the + * cache copy is ASCII, not EBCDIC, even for text/html) + */ + r->ebcdic.conv_in = r->ebcdic.conv_out = 0; + ap_bsetflag(f, B_ASCII2EBCDIC | B_EBCDIC2ASCII, 0); + ap_bsetflag(r->connection->client, B_ASCII2EBCDIC | B_EBCDIC2ASCII, 0); +#endif + +/* send body */ +/* if header only, then cache will be NULL */ +/* HTTP/1.0 tells us to read to EOF, rather than content-length bytes */ +/* XXX CHANGEME: We want to eventually support keepalives, which means + * we must read content-length bytes... */ + if (!r->header_only) { +/* we need to set this for ap_proxy_send_fb()... */ + c->cache_completion = conf->cache.cache_completion; + +/* XXX CHECKME: c->len should be the expected content length, or -1 if the + * content length is not known. We need to make 100% sure c->len is always + * set correctly before we get here to correctly do keepalive. + */ + ap_proxy_send_fb(f, r, c, c->len, 0, chunked != NULL, + conf->io_buffer_size); + } + + /* ap_proxy_send_fb() closes the socket f for us */ + + ap_proxy_cache_tidy(c); + + ap_proxy_garbage_coll(r); + return OK; +} diff --git a/APACHE_1_3_42/src/modules/proxy/proxy_util.c b/APACHE_1_3_42/src/modules/proxy/proxy_util.c new file mode 100644 index 0000000000..ef58663da3 --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/proxy_util.c @@ -0,0 +1,1640 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Utility routines for Apache proxy */ +#include "mod_proxy.h" +#include "http_main.h" +#include "ap_md5.h" +#include "multithread.h" +#include "http_log.h" +#include "util_uri.h" +#include "util_date.h" /* get ap_checkmask() decl. */ + +static int proxy_match_ipaddr(struct dirconn_entry *This, request_rec *r); +static int proxy_match_domainname(struct dirconn_entry *This, request_rec *r); +static int proxy_match_hostname(struct dirconn_entry *This, request_rec *r); +static int proxy_match_word(struct dirconn_entry *This, request_rec *r); +static struct per_thread_data *get_per_thread_data(void); +/* already called in the knowledge that the characters are hex digits */ +int ap_proxy_hex2c(const char *x) +{ + int i; +#ifndef CHARSET_EBCDIC + int ch; + + ch = x[0]; + if (ap_isdigit(ch)) + i = ch - '0'; + else if (ap_isupper(ch)) + i = ch - ('A' - 10); + else + i = ch - ('a' - 10); + i <<= 4; + + ch = x[1]; + if (ap_isdigit(ch)) + i += ch - '0'; + else if (ap_isupper(ch)) + i += ch - ('A' - 10); + else + i += ch - ('a' - 10); + return i; +#else /* CHARSET_EBCDIC */ + return (1 == sscanf(x, "%2x", &i)) ? os_toebcdic[i & 0xFF] : 0; +#endif /* CHARSET_EBCDIC */ +} + +void ap_proxy_c2hex(int ch, char *x) +{ +#ifndef CHARSET_EBCDIC + int i; + + x[0] = '%'; + i = (ch & 0xF0) >> 4; + if (i >= 10) + x[1] = ('A' - 10) + i; + else + x[1] = '0' + i; + + i = ch & 0x0F; + if (i >= 10) + x[2] = ('A' - 10) + i; + else + x[2] = '0' + i; +#else /* CHARSET_EBCDIC */ + static const char ntoa[] = {"0123456789ABCDEF"}; + ch = os_toascii[ch & 0xFF]; + x[0] = '%'; + x[1] = ntoa[(ch >> 4) & 0x0F]; + x[2] = ntoa[ch & 0x0F]; + x[3] = '\0'; +#endif /* CHARSET_EBCDIC */ +} + +/* + * canonicalise a URL-encoded string + */ + +/* + * Convert a URL-encoded string to canonical form. + * It decodes characters which need not be encoded, + * and encodes those which must be encoded, and does not touch + * those which must not be touched. + */ +char *ap_proxy_canonenc(pool *p, const char *x, int len, enum enctype t, + enum proxyreqtype isenc) +{ + int i, j, ch; + char *y; + const char *allowed; /* characters which should not be encoded */ + const char *reserved; /* characters which much not be en/de-coded */ + +/* N.B. in addition to :@&=, this allows ';' in an http path + * and '?' in an ftp path -- this may be revised + * + * Also, it makes a '+' character in a search string reserved, as + * it may be form-encoded. (Although RFC 1738 doesn't allow this - + * it only permits ; / ? : @ = & as reserved chars.) + */ + if (t == enc_path) + allowed = "$-_.+!*'(),;:@&="; + else if (t == enc_search) + allowed = "$-_.!*'(),;:@&="; + else if (t == enc_user) + allowed = "$-_.+!*'(),;@&="; + else if (t == enc_fpath) + allowed = "$-_.+!*'(),?:@&="; + else /* if (t == enc_parm) */ + allowed = "$-_.+!*'(),?/:@&="; + + if (t == enc_path) + reserved = "/"; + else if (t == enc_search) + reserved = "+"; + else + reserved = ""; + + y = ap_palloc(p, 3 * len + 1); + + for (i = 0, j = 0; i < len; i++, j++) { +/* always handle '/' first */ + ch = x[i]; + if (strchr(reserved, ch)) { + y[j] = ch; + continue; + } +/* decode it if not already done */ + if (isenc != NOT_PROXY && ch == '%') { + if (!ap_isxdigit(x[i + 1]) || !ap_isxdigit(x[i + 2])) + return NULL; + ch = ap_proxy_hex2c(&x[i + 1]); + i += 2; + if (ch != 0 && strchr(reserved, ch)) { /* keep it encoded */ + ap_proxy_c2hex(ch, &y[j]); + j += 2; + continue; + } + } +/* recode it, if necessary */ + if (!ap_isalnum(ch) && !strchr(allowed, ch)) { + ap_proxy_c2hex(ch, &y[j]); + j += 2; + } + else + y[j] = ch; + } + y[j] = '\0'; + return y; +} + +/* + * Parses network-location. + * urlp on input the URL; on output the path, after the leading / + * user NULL if no user/password permitted + * password holder for password + * host holder for host + * port port number; only set if one is supplied. + * + * Returns an error string. + */ +char * + ap_proxy_canon_netloc(pool *p, char **const urlp, char **userp, + char **passwordp, char **hostp, int *port) +{ + int i; + char *strp, *host, *url = *urlp; + char *user = NULL, *password = NULL; + + if (url[0] != '/' || url[1] != '/') + return "Malformed URL"; + host = url + 2; + url = strchr(host, '/'); + if (url == NULL) + url = ""; + else + *(url++) = '\0'; /* skip seperating '/' */ + + /* find _last_ '@' since it might occur in user/password part */ + strp = strrchr(host, '@'); + + if (strp != NULL) { + *strp = '\0'; + user = host; + host = strp + 1; + +/* find password */ + strp = strchr(user, ':'); + if (strp != NULL) { + *strp = '\0'; + password = ap_proxy_canonenc(p, strp + 1, strlen(strp + 1), enc_user, STD_PROXY); + if (password == NULL) + return "Bad %-escape in URL (password)"; + } + + user = ap_proxy_canonenc(p, user, strlen(user), enc_user, STD_PROXY); + if (user == NULL) + return "Bad %-escape in URL (username)"; + } + if (userp != NULL) { + *userp = user; + } + if (passwordp != NULL) { + *passwordp = password; + } + + strp = strrchr(host, ':'); + if (strp != NULL) { + *(strp++) = '\0'; + + for (i = 0; strp[i] != '\0'; i++) + if (!ap_isdigit(strp[i])) + break; + + /* if (i == 0) the no port was given; keep default */ + if (strp[i] != '\0') { + return "Bad port number in URL"; + } + else if (i > 0) { + *port = atoi(strp); + if (*port > 65535) + return "Port number in URL > 65535"; + } + } + ap_str_tolower(host); /* DNS names are case-insensitive */ + if (*host == '\0') + return "Missing host in URL"; +/* check hostname syntax */ + for (i = 0; host[i] != '\0'; i++) + if (!ap_isdigit(host[i]) && host[i] != '.') + break; + /* must be an IP address */ +#if defined(WIN32) || defined(NETWARE) || defined(TPF) || defined(BEOS) + if (host[i] == '\0' && (inet_addr(host) == -1)) +#else + if (host[i] == '\0' && (ap_inet_addr(host) == -1 || inet_network(host) == -1)) +#endif + { + return "Bad IP address in URL"; + } + + *urlp = url; + *hostp = host; + + return NULL; +} + +static const char *const lwday[7] = +{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + +/* + * If the date is a valid RFC 850 date or asctime() date, then it + * is converted to the RFC 1123 format, otherwise it is not modified. + * This routine is not very fast at doing conversions, as it uses + * sscanf and sprintf. However, if the date is already correctly + * formatted, then it exits very quickly. + */ +const char * + ap_proxy_date_canon(pool *p, const char *x) +{ + int wk, mday, year, hour, min, sec, mon; + char *q, month[4], zone[4], week[4]; + + q = strchr(x, ','); + /* check for RFC 850 date */ + if (q != NULL && q - x > 3 && q[1] == ' ') { + *q = '\0'; + for (wk = 0; wk < 7; wk++) + if (strcmp(x, lwday[wk]) == 0) + break; + *q = ','; + if (wk == 7) + return x; /* not a valid date */ + if (strlen(q) != 24 || + q[4] != '-' || q[8] != '-' || q[11] != ' ' || q[14] != ':' || + q[17] != ':' || strcmp(&q[20], " GMT") != 0) + return x; + if (sscanf(q + 2, "%u-%3s-%u %u:%u:%u %3s", &mday, month, &year, + &hour, &min, &sec, zone) != 7) + return x; + if (year < 70) + year += 2000; + else + year += 1900; + } + else { +/* check for asctime() date */ + if (strlen(x) != 24 || + x[3] != ' ' || x[7] != ' ' || x[10] != ' ' || x[13] != ':' || + x[16] != ':' || x[19] != ' ' || x[24] != '\0') + return x; + if (sscanf(x, "%3s %3s %u %u:%u:%u %u", week, month, &mday, &hour, + &min, &sec, &year) != 7) + return x; + for (wk = 0; wk < 7; wk++) + if (strcmp(week, ap_day_snames[wk]) == 0) + break; + if (wk == 7) + return x; + } + +/* check date */ + for (mon = 0; mon < 12; mon++) + if (strcmp(month, ap_month_snames[mon]) == 0) + break; + if (mon == 12) + return x; + + q = ap_palloc(p, 30); + ap_snprintf(q, 30, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", ap_day_snames[wk], mday, + ap_month_snames[mon], year, hour, min, sec); + return q; +} + + +/* + * Reads headers from a buffer and returns an array of headers. + * Returns NULL on file error + * This routine tries to deal with too long lines and continuation lines. + * + * Note: Currently the headers are passed through unmerged. This has to be + * done so that headers which react badly to merging (such as Set-Cookie + * headers, which contain commas within the date field) do not get stuffed + * up. + */ +table *ap_proxy_read_headers(request_rec *r, char *buffer, int size, BUFF *f) +{ + table *resp_hdrs; + int len; + char *value, *end; + char field[MAX_STRING_LEN]; + + resp_hdrs = ap_make_table(r->pool, 20); + + /* + * Read header lines until we get the empty separator line, a read error, + * the connection closes (EOF), or we timeout. + */ + while ((len = ap_getline(buffer, size, f, 1)) > 0) { + + if (!(value = strchr(buffer, ':'))) { /* Find the colon separator */ + + /* + * Buggy MS IIS servers sometimes return invalid headers (an + * extra "HTTP/1.0 200, OK" line sprinkled in between the usual + * MIME headers). Try to deal with it in a sensible way, but log + * the fact. XXX: The mask check is buggy if we ever see an + * HTTP/1.10 + */ + + if (!ap_checkmask(buffer, "HTTP/#.# ###*")) { + /* Nope, it wasn't even an extra HTTP header. Give up. */ + return NULL; + } + + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, r->server, + "proxy: Ignoring duplicate HTTP status line " + "returned by buggy server %s (%s)", r->uri, r->method); + continue; + } + + *value = '\0'; + ++value; + /* + * XXX: RFC2068 defines only SP and HT as whitespace, this test is + * wrong... and so are many others probably. + */ + while (ap_isspace(*value)) + ++value; /* Skip to start of value */ + + /* should strip trailing whitespace as well */ + for (end = &value[strlen(value) - 1]; end > value && ap_isspace(*end); --end) + *end = '\0'; + + /* make sure we add so as not to destroy duplicated headers */ + ap_table_add(resp_hdrs, buffer, value); + + /* the header was too long; at the least we should skip extra data */ + if (len >= size - 1) { + while ((len = ap_getline(field, MAX_STRING_LEN, f, 1)) + >= MAX_STRING_LEN - 1) { + /* soak up the extra data */ + } + if (len == 0) /* time to exit the larger loop as well */ + break; + } + } + return resp_hdrs; +} + +/* read data from (socket BUFF*) f, write it to: + * - c->fp, if it is open + * - r->connection->client, if nowrite == 0 + */ + +long int ap_proxy_send_fb(BUFF *f, request_rec *r, cache_req *c, off_t len, int nowrite, int chunked, size_t recv_buffer_size) +{ + int ok, end_of_chunk; + char *buf; + size_t buf_size; + long remaining = 0; + long total_bytes_rcvd; + register int n = 0, o, w; + conn_rec *con = r->connection; + int alternate_timeouts = 1; /* 1 if we alternate between soft & hard + * timeouts */ + + /* allocate a buffer to store the bytes in */ + /* + * make sure it is at least IOBUFSIZE, as recv_buffer_size may be zero + * for system default + */ + buf_size = MAX(recv_buffer_size, IOBUFSIZE); + buf = ap_palloc(r->pool, buf_size); + + total_bytes_rcvd = 0; + if (c != NULL) + c->written = 0; + +#ifdef CHARSET_EBCDIC + /* The cache copy is ASCII, not EBCDIC, even for text/html) */ + ap_bsetflag(f, B_ASCII2EBCDIC | B_EBCDIC2ASCII, 0); + ap_bsetflag(con->client, B_ASCII2EBCDIC | B_EBCDIC2ASCII, 0); +#endif + + /* + * Since we are reading from one buffer and writing to another, it is + * unsafe to do a soft_timeout here, at least until the proxy has its own + * timeout handler which can set both buffers to EOUT. + */ + + ap_kill_timeout(r); + +#if defined(WIN32) || defined(TPF) || defined(NETWARE) + /* works fine under win32, so leave it */ + ap_hard_timeout("proxy send body", r); + alternate_timeouts = 0; +#else + /* + * CHECKME! Since hard_timeout won't work in unix on sends with partial + * cache completion, we have to alternate between hard_timeout for reads, + * and soft_timeout for send. This is because we need to get a return + * from ap_bwrite to be able to continue caching. BUT, if we *can't* + * continue anyway, just use hard_timeout. (Also, if no cache file is + * written, use hard timeouts) + */ + + if (c == NULL || c->len <= 0 || c->cache_completion == 1.0) { + ap_hard_timeout("proxy send body", r); + alternate_timeouts = 0; + } +#endif + + /* + * Loop and ap_bread() while we can successfully read and write, or + * (after the client aborted) while we can successfully read and finish + * the configured cache_completion. + */ + for (end_of_chunk = ok = 1; ok;) { + if (alternate_timeouts) + ap_hard_timeout("proxy recv body from upstream server", r); + + + /* read a chunked block */ + if (chunked) { + long chunk_start = 0; + n = 0; + + /* start of a new chunk */ + if (end_of_chunk) { + end_of_chunk = 0; + /* get the chunk size from the stream */ + chunk_start = ap_getline(buf, buf_size, f, 0); + if ((chunk_start <= 0) || ((size_t)chunk_start + 1 >= buf_size) || !ap_isxdigit(*buf)) { + n = -1; + } + /* parse the chunk size */ + else { + remaining = ap_get_chunk_size(buf); + if (remaining == 0) { /* Last chunk indicated, get footers */ + /* as we are a proxy, we discard the footers, as the headers + * have already been sent at this point. + */ + if (NULL == ap_proxy_read_headers(r, buf, buf_size, f)) { + n = -1; + } + } + else if (remaining < 0) { + n = -1; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, + "proxy: remote protocol error, invalid chunk size"); + + } + } + } + + /* read the chunk */ + if (remaining > 0) { + n = ap_bread(f, buf, (int) MIN(buf_size, remaining)); + if (n > -1) { + remaining -= n; + end_of_chunk = (remaining == 0); + } + } + + /* soak up trailing CRLF */ + if (end_of_chunk) { + int ch; /* int because it may hold an EOF */ + /* + * For EBCDIC, the proxy has configured the BUFF layer to + * transparently pass the ascii characters thru (also writing + * an ASCII copy to the cache, where appropriate). + * Therefore, we see here an ASCII-CRLF (\015\012), + * not an EBCDIC-CRLF (\r\n). + */ + if ((ch = ap_bgetc(f)) == EOF) { + /* Protocol error: EOF detected within chunk */ + n = -1; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, + "proxy: remote protocol error, eof while reading chunked from proxy"); + } + else + { + if (ch == '\015') { /* _ASCII_ CR */ + ch = ap_bgetc(f); + } + if (ch != '\012') { + n = -1; + } + } + } + } + + /* otherwise read block normally */ + else { + if (-1 == len) { + n = ap_bread(f, buf, buf_size); + } + else { + n = ap_bread(f, buf, (int) MIN(buf_size, + (len - total_bytes_rcvd))); + } + } + + + if (alternate_timeouts) + ap_kill_timeout(r); + else + ap_reset_timeout(r); + + if (n == -1) { /* input error */ + if (c != NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req, + "proxy: error reading from %s", c->url); + c = ap_proxy_cache_error(c); + } + break; + } + if (n == 0) + break; /* EOF */ + o = 0; + total_bytes_rcvd += n; + + /* if we've received everything... */ + /* + * in the case of slow frontends and expensive backends, we want to + * avoid leaving a backend connection hanging while the frontend + * takes it's time to absorb the bytes. so: if we just read the last + * block, we close the backend connection now instead of later - it's + * no longer needed. + */ + if (total_bytes_rcvd == len) { + ap_bclose(f); + f = NULL; + } + + /* Write to cache first. */ + /* + * @@@ XXX FIXME: Assuming that writing the cache file won't time + * out?!!? + */ + if (c != NULL && c->fp != NULL) { + if (ap_bwrite(c->fp, &buf[0], n) != n) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req, + "proxy: error writing to %s", c->tempfile); + c = ap_proxy_cache_error(c); + } + else { + c->written += n; + } + } + + /* Write the block to the client, detect aborted transfers */ + while (!nowrite && !con->aborted && n > 0) { + if (alternate_timeouts) + ap_soft_timeout("proxy send body", r); + + w = ap_bwrite(con->client, &buf[o], n); + + if (alternate_timeouts) + ap_kill_timeout(r); + else + ap_reset_timeout(r); + + if (w <= 0) { + if (c != NULL) { + /* + * when a send failure occurs, we need to decide whether + * to continue loading and caching the document, or to + * abort the whole thing + */ + ok = (c->len > 0) && + (c->cache_completion > 0) && + (c->len * c->cache_completion < total_bytes_rcvd); + + if (!ok) { + if (c->fp != NULL) { + ap_pclosef(c->req->pool, ap_bfileno(c->fp, B_WR)); + c->fp = NULL; + } + unlink(c->tempfile); + c = NULL; + } + } + con->aborted = 1; + break; + } + n -= w; + o += w; + } /* while client alive and more data to send */ + + /* if we've received everything, leave now */ + if (total_bytes_rcvd == len) + break; + + } /* loop and ap_bread while "ok" */ + + /* if the backend connection is still open, close it */ + if (f) { + ap_bclose(f); + } + + if (!con->aborted) { + ap_bflush(con->client); + } + + ap_kill_timeout(r); + + r->bytes_sent += total_bytes_rcvd; + + return total_bytes_rcvd; +} + +/* + * Writes response line and headers to the cache file. + * + * If respline is NULL, no response line will be written. + */ +void ap_proxy_write_headers(cache_req *c, const char *respline, table *t) +{ + /* write status line */ + if (respline && c->fp != NULL && + ap_bvputs(c->fp, respline, CRLF, NULL) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req, + "proxy: error writing status line to %s", c->tempfile); + c = ap_proxy_cache_error(c); + return; + } + + /* write response headers to the cache file */ + ap_table_do(ap_proxy_send_hdr_line, c, t, NULL); + + /* write terminating CRLF */ + if (c->fp != NULL && ap_bputs(CRLF, c->fp) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req, + "proxy: error writing CRLF to %s", c->tempfile); + c = ap_proxy_cache_error(c); + } +} + + +/* + * list is a comma-separated list of case-insensitive tokens, with + * optional whitespace around the tokens. + * The return returns 1 if the token val is found in the list, or 0 + * otherwise. + */ +int ap_proxy_liststr(const char *list, const char *key, char **val) +{ + int len, i; + const char *p; + char valbuf[HUGE_STRING_LEN]; + valbuf[sizeof(valbuf) - 1] = 0; /* safety terminating zero */ + + len = strlen(key); + + while (list != NULL) { + p = strchr(list, ','); + if (p != NULL) { + i = p - list; + do + p++; + while (ap_isspace(*p)); + } + else + i = strlen(list); + + while (i > 0 && ap_isspace(list[i - 1])) + i--; + if (i == len && strncasecmp(list, key, len) == 0) { + if (val) { + p = strchr(list, ','); + while (ap_isspace(*list)) { + list++; + } + if ('=' == list[0]) + list++; + while (ap_isspace(*list)) { + list++; + } + strncpy(valbuf, list, MIN(p - list, sizeof(valbuf) - 1)); + *val = valbuf; + } + return 1; + } + list = p; + } + return 0; +} + +#ifdef CASE_BLIND_FILESYSTEM + +/* + * On some platforms, the file system is NOT case sensitive. So, a == A + * need to map to smaller set of characters + */ +void ap_proxy_hash(const char *it, char *val, int ndepth, int nlength) +{ + AP_MD5_CTX context; + unsigned char digest[16]; + char tmp[26]; + int i, k, d; + unsigned int x; + static const char enc_table[32] = "abcdefghijklmnopqrstuvwxyz012345"; + + ap_MD5Init(&context); + ap_MD5Update(&context, (const unsigned char *)it, strlen(it)); + ap_MD5Final(digest, &context); + +/* encode 128 bits as 26 characters, using a modified uuencoding */ +/* the encoding is 5 bytes -> 8 characters + * i.e. 128 bits is 3 x 5 bytes + 1 byte -> 3 * 8 characters + 2 characters + */ + for (i = 0, k = 0; i < 15; i += 5) { + x = (digest[i] << 24) | (digest[i + 1] << 16) | (digest[i + 2] << 8) | digest[i + 3]; + tmp[k++] = enc_table[x >> 27]; + tmp[k++] = enc_table[(x >> 22) & 0x1f]; + tmp[k++] = enc_table[(x >> 17) & 0x1f]; + tmp[k++] = enc_table[(x >> 12) & 0x1f]; + tmp[k++] = enc_table[(x >> 7) & 0x1f]; + tmp[k++] = enc_table[(x >> 2) & 0x1f]; + x = ((x & 0x3) << 8) | digest[i + 4]; + tmp[k++] = enc_table[x >> 5]; + tmp[k++] = enc_table[x & 0x1f]; + } +/* one byte left */ + x = digest[15]; + tmp[k++] = enc_table[x >> 3]; /* use up 5 bits */ + tmp[k++] = enc_table[x & 0x7]; + /* now split into directory levels */ + + for (i = k = d = 0; d < ndepth; ++d) { + memcpy(&val[i], &tmp[k], nlength); + k += nlength; + val[i + nlength] = '/'; + i += nlength + 1; + } + memcpy(&val[i], &tmp[k], 26 - k); + val[i + 26 - k] = '\0'; +} + +#else + +void ap_proxy_hash(const char *it, char *val, int ndepth, int nlength) +{ + AP_MD5_CTX context; + unsigned char digest[16]; + char tmp[22]; + int i, k, d; + unsigned int x; +#if defined(MPE) || (defined(AIX) && defined(__ps2__)) + /* + * Believe it or not, AIX 1.x does not allow you to name a file '@', so + * hack around it in the encoding. + */ + static const char enc_table[64] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_%"; +#else + static const char enc_table[64] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@"; +#endif + + ap_MD5Init(&context); + ap_MD5Update(&context, (const unsigned char *)it, strlen(it)); + ap_MD5Final(digest, &context); + +/* encode 128 bits as 22 characters, using a modified uuencoding */ +/* the encoding is 3 bytes -> 4 characters + * i.e. 128 bits is 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters + */ + for (i = 0, k = 0; i < 15; i += 3) { + x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2]; + tmp[k++] = enc_table[x >> 18]; + tmp[k++] = enc_table[(x >> 12) & 0x3f]; + tmp[k++] = enc_table[(x >> 6) & 0x3f]; + tmp[k++] = enc_table[x & 0x3f]; + } +/* one byte left */ + x = digest[15]; + tmp[k++] = enc_table[x >> 2]; /* use up 6 bits */ + tmp[k++] = enc_table[(x << 4) & 0x3f]; + /* now split into directory levels */ + + for (i = k = d = 0; d < ndepth; ++d) { + memcpy(&val[i], &tmp[k], nlength); + k += nlength; + val[i + nlength] = '/'; + i += nlength + 1; + } + memcpy(&val[i], &tmp[k], 22 - k); + val[i + 22 - k] = '\0'; +} + +#endif /* CASE_BLIND_FILESYSTEM */ + +/* + * Converts 16 hex digits to a time integer + */ +int ap_proxy_hex2sec(const char *x) +{ + int i, ch; + unsigned int j; + + for (i = 0, j = 0; i < 16; i++) { + ch = x[i]; + j <<= 4; + if (ap_isdigit(ch)) + j |= ch - '0'; + else if (ap_isupper(ch)) + j |= ch - ('A' - 10); + else + j |= ch - ('a' - 10); + } +/* no longer necessary, as the source hex is 8-byte int */ +/* if (j == 0xffffffff)*/ + /* return -1;*//* so that it works with 8-byte ints */ +/* else */ + return j; +} + +/* + * Converts a time integer to 16 hex digits + */ +void ap_proxy_sec2hex(int t, char *y) +{ + int i, ch; + unsigned int j = t; + + if (-1 == t) { + strcpy(y, "FFFFFFFFFFFFFFFF"); + return; + } + + for (i = 15; i >= 0; i--) { + ch = j & 0xF; + j >>= 4; + if (ch >= 10) + y[i] = ch + ('A' - 10); + else + y[i] = ch + '0'; + } + y[16] = '\0'; +} + + +cache_req *ap_proxy_cache_error(cache_req *c) +{ + if (c != NULL) { + if (c->fp != NULL) { + ap_pclosef(c->req->pool, ap_bfileno(c->fp, B_WR)); + c->fp = NULL; + } + if (c->origfp != NULL) { + ap_pclosef(c->req->pool, ap_bfileno(c->origfp, B_WR)); + c->origfp = NULL; + } + if (c->tempfile) + unlink(c->tempfile); + } + return NULL; +} + +int ap_proxyerror(request_rec *r, int statuscode, const char *message) +{ + ap_table_setn(r->notes, "error-notes", + ap_pstrcat(r->pool, + "The proxy server could not handle the request " + "<EM><A HREF=\"", ap_escape_uri(r->pool, r->uri), + "\">", ap_escape_html(r->pool, r->method), + " ", + ap_escape_html(r->pool, r->uri), "</A></EM>.<P>\n" + "Reason: <STRONG>", + ap_escape_html(r->pool, message), + "</STRONG>", NULL)); + + /* Allow "error-notes" string to be printed by ap_send_error_response() */ + ap_table_setn(r->notes, "verbose-error-to", ap_pstrdup(r->pool, "*")); + + r->status_line = ap_psprintf(r->pool, "%3.3u Proxy Error", statuscode); + return statuscode; +} + +/* + * This routine returns its own error message + */ +const char * + ap_proxy_host2addr(const char *host, struct hostent * reqhp) +{ + int i; + struct hostent *hp; + struct per_thread_data *ptd = get_per_thread_data(); + + for (i = 0; host[i] != '\0'; i++) + if (!ap_isdigit(host[i]) && host[i] != '.') + break; + + if (host[i] != '\0') { + hp = gethostbyname(host); + if (hp == NULL) + return "Host not found"; + } + else { + ptd->ipaddr = ap_inet_addr(host); + hp = gethostbyaddr((char *)&ptd->ipaddr, sizeof(ptd->ipaddr), AF_INET); + if (hp == NULL) { + memset(&ptd->hpbuf, 0, sizeof(ptd->hpbuf)); + ptd->hpbuf.h_name = 0; + ptd->hpbuf.h_addrtype = AF_INET; + ptd->hpbuf.h_length = sizeof(ptd->ipaddr); + ptd->hpbuf.h_addr_list = ptd->charpbuf; + ptd->hpbuf.h_addr_list[0] = (char *)&ptd->ipaddr; + ptd->hpbuf.h_addr_list[1] = 0; + hp = &ptd->hpbuf; + } + } + *reqhp = *hp; + return NULL; +} + +static const char * + proxy_get_host_of_request(request_rec *r) +{ + char *url, *user = NULL, *password = NULL, *err, *host; + int port = -1; + + if (r->hostname != NULL) + return r->hostname; + + /* Set url to the first char after "scheme://" */ + if ((url = strchr(r->uri, ':')) == NULL + || url[1] != '/' || url[2] != '/') + return NULL; + + url = ap_pstrdup(r->pool, &url[1]); /* make it point to "//", which is + * what proxy_canon_netloc expects */ + + err = ap_proxy_canon_netloc(r->pool, &url, &user, &password, &host, &port); + + if (err != NULL) + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, + "%s", err); + + r->hostname = host; + + return host; /* ought to return the port, too */ +} + +/* Return TRUE if addr represents an IP address (or an IP network address) */ +int ap_proxy_is_ipaddr(struct dirconn_entry *This, pool *p) +{ + const char *addr = This->name; + long ip_addr[4]; + int i, quads; + long bits; + + /* if the address is given with an explicit netmask, use that */ + /* Due to a deficiency in ap_inet_addr(), it is impossible to parse */ + /* "partial" addresses (with less than 4 quads) correctly, i.e. */ + /* 192.168.123 is parsed as 192.168.0.123, which is not what I want. */ + /* I therefore have to parse the IP address manually: */ + /* + * if (proxy_readmask(This->name, &This->addr.s_addr, &This->mask.s_addr) + * == 0) + */ + /* addr and mask were set by proxy_readmask() */ + /* return 1; */ + + /* Parse IP addr manually, optionally allowing */ + /* abbreviated net addresses like 192.168. */ + + /* Iterate over up to 4 (dotted) quads. */ + for (quads = 0; quads < 4 && *addr != '\0'; ++quads) { + char *tmp; + + if (*addr == '/' && quads > 0) /* netmask starts here. */ + break; + + if (!ap_isdigit(*addr)) + return 0; /* no digit at start of quad */ + + ip_addr[quads] = ap_strtol(addr, &tmp, 0); + + if (tmp == addr) /* expected a digit, found something else */ + return 0; + + if (ip_addr[quads] < 0 || ip_addr[quads] > 255) { + /* invalid octet */ + return 0; + } + + addr = tmp; + + if (*addr == '.' && quads != 3) + ++addr; /* after the 4th quad, a dot would be illegal */ + } + + for (This->addr.s_addr = 0, i = 0; i < quads; ++i) + This->addr.s_addr |= htonl(ip_addr[i] << (24 - 8 * i)); + + if (addr[0] == '/' && ap_isdigit(addr[1])) { /* net mask follows: */ + char *tmp; + + ++addr; + + bits = ap_strtol(addr, &tmp, 0); + + if (tmp == addr) /* expected a digit, found something else */ + return 0; + + addr = tmp; + + if (bits < 0 || bits > 32) /* netmask must be between 0 and 32 */ + return 0; + + } + else { + /* Determine (i.e., "guess") netmask by counting the */ + /* number of trailing .0's; reduce #quads appropriately */ + /* (so that 192.168.0.0 is equivalent to 192.168.) */ + while (quads > 0 && ip_addr[quads - 1] == 0) + --quads; + + /* + * "IP Address should be given in dotted-quad form, optionally + * followed by a netmask (e.g., 192.168.111.0/24)"; + */ + if (quads < 1) + return 0; + + /* every zero-byte counts as 8 zero-bits */ + bits = 8 * quads; + + if (bits != 32) /* no warning for fully qualified IP address */ + fprintf(stderr, "Warning: NetMask not supplied with IP-Addr; guessing: %s/%ld\n", + inet_ntoa(This->addr), bits); + } + + This->mask.s_addr = htonl(INADDR_NONE << (32 - bits)); + + if (*addr == '\0' && (This->addr.s_addr & ~This->mask.s_addr) != 0) { + fprintf(stderr, "Warning: NetMask and IP-Addr disagree in %s/%ld\n", + inet_ntoa(This->addr), bits); + This->addr.s_addr &= This->mask.s_addr; + fprintf(stderr, " Set to %s/%ld\n", + inet_ntoa(This->addr), bits); + } + + if (*addr == '\0') { + This->matcher = proxy_match_ipaddr; + return 1; + } + else + return (*addr == '\0'); /* okay iff we've parsed the whole string */ +} + +/* Return TRUE if addr represents an IP address (or an IP network address) */ +static int proxy_match_ipaddr(struct dirconn_entry *This, request_rec *r) +{ + int i; + int ip_addr[4]; + struct in_addr addr; + struct in_addr *ip_list; + char **ip_listptr; + const char *found; + const char *host = proxy_get_host_of_request(r); + + if (host == NULL) /* oops! */ + return 0; + + memset(&addr, '\0', sizeof addr); + memset(ip_addr, '\0', sizeof ip_addr); + + if (4 == sscanf(host, "%d.%d.%d.%d", &ip_addr[0], &ip_addr[1], &ip_addr[2], &ip_addr[3])) { + for (addr.s_addr = 0, i = 0; i < 4; ++i) + addr.s_addr |= htonl(ip_addr[i] << (24 - 8 * i)); + + if (This->addr.s_addr == (addr.s_addr & This->mask.s_addr)) { +#if DEBUGGING + fprintf(stderr, "1)IP-Match: %s[%s] <-> ", host, inet_ntoa(addr)); + fprintf(stderr, "%s/", inet_ntoa(This->addr)); + fprintf(stderr, "%s\n", inet_ntoa(This->mask)); +#endif + return 1; + } +#if DEBUGGING + else { + fprintf(stderr, "1)IP-NoMatch: %s[%s] <-> ", host, inet_ntoa(addr)); + fprintf(stderr, "%s/", inet_ntoa(This->addr)); + fprintf(stderr, "%s\n", inet_ntoa(This->mask)); + } +#endif + } + else { + struct hostent the_host; + + memset(&the_host, '\0', sizeof the_host); + found = ap_proxy_host2addr(host, &the_host); + + if (found != NULL) { +#if DEBUGGING + fprintf(stderr, "2)IP-NoMatch: hostname=%s msg=%s\n", host, found); +#endif + return 0; + } + + if (the_host.h_name != NULL) + found = the_host.h_name; + else + found = host; + + /* Try to deal with multiple IP addr's for a host */ + for (ip_listptr = the_host.h_addr_list; *ip_listptr; ++ip_listptr) { + ip_list = (struct in_addr *)*ip_listptr; + if (This->addr.s_addr == (ip_list->s_addr & This->mask.s_addr)) { +#if DEBUGGING + fprintf(stderr, "3)IP-Match: %s[%s] <-> ", found, inet_ntoa(*ip_list)); + fprintf(stderr, "%s/", inet_ntoa(This->addr)); + fprintf(stderr, "%s\n", inet_ntoa(This->mask)); +#endif + return 1; + } +#if DEBUGGING + else { + fprintf(stderr, "3)IP-NoMatch: %s[%s] <-> ", found, inet_ntoa(*ip_list)); + fprintf(stderr, "%s/", inet_ntoa(This->addr)); + fprintf(stderr, "%s\n", inet_ntoa(This->mask)); + } +#endif + } + } + + return 0; +} + +/* Return TRUE if addr represents a domain name */ +int ap_proxy_is_domainname(struct dirconn_entry *This, pool *p) +{ + char *addr = This->name; + int i; + + /* Domain name must start with a '.' */ + if (addr[0] != '.') + return 0; + + /* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */ + for (i = 0; ap_isalnum(addr[i]) || addr[i] == '-' || addr[i] == '.'; ++i) + continue; + +#if 0 + if (addr[i] == ':') { + fprintf(stderr, "@@@@ handle optional port in proxy_is_domainname()\n"); + /* @@@@ handle optional port */ + } +#endif + + if (addr[i] != '\0') + return 0; + + /* Strip trailing dots */ + for (i = strlen(addr) - 1; i > 0 && addr[i] == '.'; --i) + addr[i] = '\0'; + + This->matcher = proxy_match_domainname; + return 1; +} + +/* Return TRUE if host "host" is in domain "domain" */ +static int proxy_match_domainname(struct dirconn_entry *This, request_rec *r) +{ + const char *host = proxy_get_host_of_request(r); + int d_len = strlen(This->name), h_len; + + if (host == NULL) /* some error was logged already */ + return 0; + + h_len = strlen(host); + + /* @@@ do this within the setup? */ + /* Ignore trailing dots in domain comparison: */ + while (d_len > 0 && This->name[d_len - 1] == '.') + --d_len; + while (h_len > 0 && host[h_len - 1] == '.') + --h_len; + return h_len > d_len + && strncasecmp(&host[h_len - d_len], This->name, d_len) == 0; +} + +/* Return TRUE if addr represents a host name */ +int ap_proxy_is_hostname(struct dirconn_entry *This, pool *p) +{ + struct hostent host; + char *addr = This->name; + int i; + + /* Host names must not start with a '.' */ + if (addr[0] == '.') + return 0; + + /* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */ + for (i = 0; ap_isalnum(addr[i]) || addr[i] == '-' || addr[i] == '.'; ++i); + +#if 0 + if (addr[i] == ':') { + fprintf(stderr, "@@@@ handle optional port in proxy_is_hostname()\n"); + /* @@@@ handle optional port */ + } +#endif + + if (addr[i] != '\0' || ap_proxy_host2addr(addr, &host) != NULL) + return 0; + + This->hostentry = ap_pduphostent(p, &host); + + /* Strip trailing dots */ + for (i = strlen(addr) - 1; i > 0 && addr[i] == '.'; --i) + addr[i] = '\0'; + + This->matcher = proxy_match_hostname; + return 1; +} + +/* Return TRUE if host "host" is equal to host2 "host2" */ +static int proxy_match_hostname(struct dirconn_entry *This, request_rec *r) +{ + char *host = This->name; + const char *host2 = proxy_get_host_of_request(r); + int h2_len; + int h1_len; + + if (host == NULL || host2 == NULL) + return 0; /* oops! */ + + h2_len = strlen(host2); + h1_len = strlen(host); + +#if 0 + unsigned long *ip_list; + + /* Try to deal with multiple IP addr's for a host */ + for (ip_list = *This->hostentry->h_addr_list; *ip_list != 0UL; ++ip_list) + if (*ip_list == ? ? ? ? ? ? ? ? ? ? ? ? ?) + return 1; +#endif + + /* Ignore trailing dots in host2 comparison: */ + while (h2_len > 0 && host2[h2_len - 1] == '.') + --h2_len; + while (h1_len > 0 && host[h1_len - 1] == '.') + --h1_len; + return h1_len == h2_len + && strncasecmp(host, host2, h1_len) == 0; +} + +/* Return TRUE if addr is to be matched as a word */ +int ap_proxy_is_word(struct dirconn_entry *This, pool *p) +{ + This->matcher = proxy_match_word; + return 1; +} + +/* Return TRUE if string "str2" occurs literally in "str1" */ +static int proxy_match_word(struct dirconn_entry *This, request_rec *r) +{ + const char *host = proxy_get_host_of_request(r); + return host != NULL && strstr(host, This->name) != NULL; +} + +int ap_proxy_doconnect(int sock, struct sockaddr_in *addr, request_rec *r) +{ + int i; + + ap_hard_timeout("proxy connect", r); + do { + i = connect(sock, (struct sockaddr *)addr, sizeof(struct sockaddr_in)); +#if defined(WIN32) || defined(NETWARE) + if (i == SOCKET_ERROR) + errno = WSAGetLastError(); +#endif /* WIN32 */ + } while (i == -1 && errno == EINTR); + if (i == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy connect to %s port %d failed", + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + } + ap_kill_timeout(r); + + return i; +} + +/* This function is called by ap_table_do() for all header lines + * (from proxy_http.c and proxy_ftp.c) + * It is passed a cache_req struct pointer and a MIME field and value pair + */ +int ap_proxy_send_hdr_line(void *p, const char *key, const char *value) +{ + cache_req *c = (cache_req *)p; + + if (key == NULL || value == NULL || value[0] == '\0') + return 1; + if (c->fp != NULL && + ap_bvputs(c->fp, key, ": ", value, CRLF, NULL) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req, + "proxy: error writing header to %s", c->tempfile); + c = ap_proxy_cache_error(c); + return 0; /* no need to continue, it failed already */ + } + return 1; /* tell ap_table_do() to continue calling us + * for more headers */ +} + +/* send a text line to one or two BUFF's; return line length */ +unsigned ap_proxy_bputs2(const char *data, BUFF *client, cache_req *cache) +{ + unsigned len = ap_bputs(data, client); + if (cache != NULL && cache->fp != NULL) + ap_bputs(data, cache->fp); + return len; +} + +/* do a HTTP/1.1 age calculation */ +time_t ap_proxy_current_age(cache_req *c, const time_t age_value) +{ + time_t apparent_age, corrected_received_age, response_delay, corrected_initial_age, + resident_time, current_age; + + /* Perform an HTTP/1.1 age calculation. (RFC2616 13.2.3) */ + + apparent_age = MAX(0, c->resp_time - c->date); + corrected_received_age = MAX(apparent_age, age_value); + response_delay = c->resp_time - c->req_time; + corrected_initial_age = corrected_received_age + response_delay; + resident_time = time(NULL) - c->resp_time; + current_age = corrected_initial_age + resident_time; + + return (current_age); +} + +/* open a cache file and return a pointer to a BUFF */ +BUFF *ap_proxy_open_cachefile(request_rec *r, char *filename) +{ + BUFF *cachefp = NULL; + int cfd; + + if (filename != NULL) { + cfd = open(filename, O_RDWR | O_BINARY); + if (cfd != -1) { + ap_note_cleanups_for_fd(r->pool, cfd); + cachefp = ap_bcreate(r->pool, B_RD | B_WR); + ap_bpushfd(cachefp, cfd, cfd); + } + else if (errno != ENOENT) + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: error opening cache file %s", + filename); + else + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "File %s not found", filename); + + } + return cachefp; +} + +/* create a cache file and return a pointer to a BUFF */ +BUFF *ap_proxy_create_cachefile(request_rec *r, char *filename) +{ + BUFF *cachefp = NULL; + int cfd; + + if (filename != NULL) { + cfd = open(filename, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0622); + if (cfd != -1) { + ap_note_cleanups_for_fd(r->pool, cfd); + cachefp = ap_bcreate(r->pool, B_WR); + ap_bpushfd(cachefp, -1, cfd); + } + else if (errno != ENOENT) + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: error creating cache file %s", + filename); + } + return cachefp; +} + +/* Clear all connection-based headers from headers table */ +void ap_proxy_clear_connection(pool *p, table *headers) +{ + const char *name; + char *next = ap_pstrdup(p, ap_table_get(headers, "Connection")); + + /* Some proxies (Squid, ICS) use the non-standard "Proxy-Connection" header. */ + ap_table_unset(headers, "Proxy-Connection"); + + if (next != NULL) { + while (*next) { + name = next; + while (*next && !ap_isspace(*next) && (*next != ',')) + ++next; + while (*next && (ap_isspace(*next) || (*next == ','))) { + *next = '\0'; + ++next; + } + ap_table_unset(headers, name); + } + ap_table_unset(headers, "Connection"); + } + + /* unset hop-by-hop headers defined in RFC2616 13.5.1 */ + ap_table_unset(headers,"Keep-Alive"); + /* + * XXX: @@@ FIXME: "Proxy-Authenticate" should IMO *not* be stripped + * because in a chain of proxies some "front" proxy might need + * proxy authentication, while a "back-end" proxy which needs none can + * simply pass the "Proxy-Authenticate" back to the client, and pass + * the client's "Proxy-Authorization" to the front-end proxy. + * (See the note in proxy_http.c for the "Proxy-Authorization" case.) + * + * MnKr 04/2002 + */ + ap_table_unset(headers,"Proxy-Authenticate"); + ap_table_unset(headers,"TE"); + ap_table_unset(headers,"Trailer"); + /* it is safe to just chop the transfer-encoding header + * here, because proxy doesn't support any other encodings + * to the backend other than chunked. + */ + ap_table_unset(headers,"Transfer-Encoding"); + ap_table_unset(headers,"Upgrade"); + +} + +/* overlay one table on another + * keys in base will be replaced by keys in overlay + * + * Note: this has to be done in a special way, due + * to some nastiness when it comes to having multiple + * headers in the overlay table. First, we remove all + * the headers in the base table that are found in the + * overlay table, then we simply concatenate the + * tables together. + * + * The base and overlay tables need not be in the same + * pool (and probably won't be). + * + * If the base table is changed in any way through + * being overlayed with the overlay table, this + * function returns a 1. + */ +int ap_proxy_table_replace(table *base, table *overlay) +{ + table_entry *elts = (table_entry *)overlay->a.elts; + int i, q = 0; + const char *val; + + /* remove overlay's keys from base */ + for (i = 0; i < overlay->a.nelts; ++i) { + val = ap_table_get(base, elts[i].key); + if (!val || strcmp(val, elts[i].val)) { + q = 1; + } + if (val) { + ap_table_unset(base, elts[i].key); + } + } + + /* add overlay to base */ + for (i = 0; i < overlay->a.nelts; ++i) { + ap_table_add(base, elts[i].key, elts[i].val); + } + + return q; +} + +/* read the response line + * This function reads a single line of response from the server, + * and returns a status code. + * It also populates the request_rec with the resultant status, and + * returns backasswards status (HTTP/0.9). + */ +int ap_proxy_read_response_line(BUFF *f, request_rec *r, char *buffer, int size, int *backasswards, int *major, int *minor) { + + long len; + + len = ap_getline(buffer, size-1, f, 0); + if (len == -1) { + ap_bclose(f); + ap_kill_timeout(r); + return ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server"); + } + else if (len == 0) { + ap_bclose(f); + ap_kill_timeout(r); + return ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Document contains no data"); + } + + /* + * Is it an HTTP/1 response? Do some sanity checks on the response. (This + * is buggy if we ever see an HTTP/1.10) + */ + if (ap_checkmask(buffer, "HTTP/#.# ###*")) { + + if (2 != sscanf(buffer, "HTTP/%u.%u", major, minor)) { + /* if no response, default to HTTP/1.1 - is this correct? */ + *major = 1; + *minor = 1; + } + + /* If not an HTTP/1 message */ + if (*major < 1) { + ap_bclose(f); + ap_kill_timeout(r); + return HTTP_BAD_GATEWAY; + } + *backasswards = 0; + + /* there need not be a reason phrase in the response, + * and ap_getline() already deleted trailing whitespace. + * But RFC2616 requires a SP after the Status-Code. Add one: + */ + if (strlen(buffer) < sizeof("HTTP/1.x 200 ")-1) + buffer = ap_pstrcat(r->pool, buffer, " ", NULL); + buffer[12] = '\0'; + r->status = atoi(&buffer[9]); + buffer[12] = ' '; + r->status_line = ap_pstrdup(r->pool, &buffer[9]); + + /* if the response was 100 continue, soak up any headers */ + if (r->status == 100) { + ap_proxy_read_headers(r, buffer, size, f); + } + + } + else { + + /* an http/0.9 response */ + *backasswards = 1; + r->status = 200; + r->status_line = "200 OK"; + *major = 0; + *minor = 9; + + } + + return OK; + +} + + +#if defined WIN32 + +static DWORD tls_index; + +BOOL WINAPI DllMain(HINSTANCE dllhandle, DWORD reason, LPVOID reserved) +{ + LPVOID memptr; + + switch (reason) { + case DLL_PROCESS_ATTACH: + tls_index = TlsAlloc(); + case DLL_THREAD_ATTACH: /* intentional no break */ + TlsSetValue(tls_index, malloc(sizeof(struct per_thread_data))); + break; + case DLL_THREAD_DETACH: + memptr = TlsGetValue(tls_index); + if (memptr) { + free(memptr); + TlsSetValue(tls_index, 0); + } + break; + } + + return TRUE; +} + +#endif + +static struct per_thread_data *get_per_thread_data(void) +{ +#if defined(WIN32) + + return (struct per_thread_data *)TlsGetValue(tls_index); + +#else + + static APACHE_TLS struct per_thread_data sptd; + return &sptd; + +#endif +} diff --git a/APACHE_1_3_42/src/modules/standard/.indent.pro b/APACHE_1_3_42/src/modules/standard/.indent.pro new file mode 100644 index 0000000000..a9fbe9f9a1 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/.indent.pro @@ -0,0 +1,54 @@ +-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1 +-TBUFF +-TFILE +-TTRANS +-TUINT4 +-T_trans +-Tallow_options_t +-Tapache_sfio +-Tarray_header +-Tbool_int +-Tbuf_area +-Tbuff_struct +-Tbuffy +-Tcmd_how +-Tcmd_parms +-Tcommand_rec +-Tcommand_struct +-Tconn_rec +-Tcore_dir_config +-Tcore_server_config +-Tdir_maker_func +-Tevent +-Tglobals_s +-Thandler_func +-Thandler_rec +-Tjoblist_s +-Tlisten_rec +-Tmerger_func +-Tmode_t +-Tmodule +-Tmodule_struct +-Tmutex +-Tn_long +-Tother_child_rec +-Toverrides_t +-Tparent_score +-Tpid_t +-Tpiped_log +-Tpool +-Trequest_rec +-Trequire_line +-Trlim_t +-Tscoreboard +-Tsemaphore +-Tserver_addr_rec +-Tserver_rec +-Tserver_rec_chain +-Tshort_score +-Ttable +-Ttable_entry +-Tthread +-Tu_wide_int +-Tvtime_t +-Twide_int diff --git a/APACHE_1_3_42/src/modules/standard/Makefile.Cygwin b/APACHE_1_3_42/src/modules/standard/Makefile.Cygwin new file mode 100644 index 0000000000..61ac32af91 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/Makefile.Cygwin @@ -0,0 +1,57 @@ +# +# Extra rule sets for making shared module DLLs for Cygwin 1.x +# +# On Cygwin OS the user needs to run twice "make" if shared modules have +# been requested using the --enable-shared=<module> configure flag. +# This is because when we pass the module mod_foo.c we have no import +# library, usually src/libhttpd.dll to link against in this case. So the +# two "make" runs do the following: +# +# 1st run: builds all static modules and links everything to the +# shared core DLL, which is also the import library for any +# further shared modules, including the apxs tool mechanism. +# +# 2nd run: builds *only* the shared module DLLs specified by the +# --enable-shared configure switch and of course updates +# the buildmark.o and hence httpd's build date. +# +# Any suggestions in getting this done in one step are highly welcome. +# Stipe Tolj <tolj@wapme-systems.de> +# + +# overwrite variables that are defined in previous makefile +LD_SHLIB=$(CC) +LDFLAGS_SHLIB=--shared + +define shared_dll + $(LD_SHLIB) $(LDFLAGS_SHLIB) -o $*.dll $(<:%.c=%.o) $(LIBS_SHLIB) \ + $(SRCDIR)/$(SHCORE_IMPLIB) $(LIBS1) +endef + +%.def : %.c + touch $*.def + +%.lo : %.c + $(CC) -c $(INCLUDES) $(CFLAGS) $(CFLAGS_SHLIB) $< && mv $*.o $*.lo + +%.dll : %.lo + @if [ -f "$(SRCDIR)/$(SHCORE_IMPLIB)" ]; then \ + rm -f $(SRCDIR)/$(SHCORE_IMPLIB).$$; \ + echo $(shared_dll); \ + $(shared_dll); \ + else \ + if [ ! -f "$(SRCDIR)/$(SHCORE_IMPLIB).$$" ]; then \ + echo "+--------------------------------------------------------+"; \ + echo "| There is no shared core 'libhttpd.dll' available! |"; \ + echo "| |"; \ + echo "| This is obviously your first 'make' run with configure |"; \ + echo "| flag SHARED_CORE enabled and shared modules. |"; \ + echo "| |"; \ + echo "| You will have to re-run 'make' after this run builds |"; \ + echo "| the required shared import library! |"; \ + echo "+--------------------------------------------------------+"; \ + sleep 10; \ + touch $(SRCDIR)/$(SHCORE_IMPLIB).$$; \ + fi; \ + fi; + diff --git a/APACHE_1_3_42/src/modules/standard/Makefile.OS2 b/APACHE_1_3_42/src/modules/standard/Makefile.OS2 new file mode 100644 index 0000000000..b72db748e3 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/Makefile.OS2 @@ -0,0 +1,118 @@ +# Extra rules for making DLLs for OS/2 + +define mkdll +$(LD_SHLIB) $(LDFLAGS_SHLIB) -o $* $(<:%.c=%.o) $(LIBS_SHLIB) $(LIBS1) $(<:%.o=%.def) && \ +emxbind -b -q -s -h0 -d$(<:%.o=%.def) $* && \ +rm $* +endef + + +%.def : %.c + echo "LIBRARY $* INITINSTANCE" > $@ + echo "EXPORTS" >> $@ + grep "^module .*=" $< | sed "s/module.* \(.*\) =.*/ \1/" >> $@ + +access.dll: mod_access.o mod_access.def + $(mkdll) + +actions.dll: mod_actions.o mod_actions.def + $(mkdll) + +alias.dll: mod_alias.o mod_alias.def + $(mkdll) + +asis.dll: mod_asis.o mod_asis.def + $(mkdll) + +auth.dll: mod_auth.o mod_auth.def + $(mkdll) + +auth_ano.dll: mod_auth_anon.o mod_auth_anon.def + $(mkdll) + +auth_db.dll: mod_auth_db.o mod_auth_db.def + $(mkdll) + +auth_dbm.dll: mod_auth_dbm.o mod_auth_dbm.def + $(mkdll) + +autoinde.dll: mod_autoindex.o mod_autoindex.def + $(mkdll) + +cern_met.dll: mod_cern_meta.o mod_cern_meta.def + $(mkdll) + +cgi.dll: mod_cgi.o mod_cgi.def + $(mkdll) + +digest.dll: mod_digest.o mod_digest.def + $(mkdll) + +dir.dll: mod_dir.o mod_dir.def + $(mkdll) + +env.dll: mod_env.o mod_env.def + $(mkdll) + +expires.dll: mod_expires.o mod_expires.def + $(mkdll) + +headers.dll: mod_headers.o mod_headers.def + $(mkdll) + +imap.dll: mod_imap.o mod_imap.def + $(mkdll) + +include.dll: mod_include.o mod_include.def + $(mkdll) + +info.dll: mod_info.o mod_info.def + $(mkdll) + +log_agen.dll: mod_log_agent.o mod_log_agent.def + $(mkdll) + +log_conf.dll: mod_log_config.o mod_log_config.def + $(mkdll) + +log_fore.dll: mod_log_forensic.o mod_log_forensic.def + $(mkdll) + +log_refe.dll: mod_log_referer.o mod_log_referer.def + $(mkdll) + +mime.dll: mod_mime.o mod_mime.def + $(mkdll) + +mime_mag.dll: mod_mime_magic.o mod_mime_magic.def + $(mkdll) + +negotiat.dll: mod_negotiation.o mod_negotiation.def + $(mkdll) + +rewrite.dll: mod_rewrite.o mod_rewrite.def + $(mkdll) + +setenvif.dll: mod_setenvif.o mod_setenvif.def + $(mkdll) + +so.dll: mod_so.o mod_so.def + $(mkdll) + +speling.dll: mod_speling.o mod_speling.def + $(mkdll) + +status.dll: mod_status.o mod_status.def + $(mkdll) + +unique_i.dll: mod_unique_id.o mod_unique_id.def + $(mkdll) + +userdir.dll: mod_userdir.o mod_userdir.def + $(mkdll) + +usertrac.dll: mod_usertrack.o mod_usertrack.def + $(mkdll) + +vhost_al.dll: mod_vhost_alias.o mod_vhost_alias.def + $(mkdll) diff --git a/APACHE_1_3_42/src/modules/standard/Makefile.tmpl b/APACHE_1_3_42/src/modules/standard/Makefile.tmpl new file mode 100644 index 0000000000..d261719a7e --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/Makefile.tmpl @@ -0,0 +1,256 @@ + +#Dependencies + +$(OBJS) $(OBJS_PIC): Makefile + +# DO NOT REMOVE +mod_access.o: mod_access.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_core.h \ + $(INCDIR)/http_config.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_request.h +mod_actions.o: mod_actions.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_request.h $(INCDIR)/http_core.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_main.h \ + $(INCDIR)/http_log.h $(INCDIR)/util_script.h +mod_alias.o: mod_alias.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h +mod_asis.o: mod_asis.c $(INCDIR)/httpd.h $(INCDIR)/ap_config.h \ + $(INCDIR)/ap_mmn.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_log.h \ + $(INCDIR)/util_script.h $(INCDIR)/http_main.h \ + $(INCDIR)/http_request.h +mod_auth.o: mod_auth.c $(INCDIR)/httpd.h $(INCDIR)/ap_config.h \ + $(INCDIR)/ap_mmn.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_protocol.h +mod_auth_anon.o: mod_auth_anon.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_request.h +mod_auth_db.o: mod_auth_db.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_protocol.h +mod_auth_dbm.o: mod_auth_dbm.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_protocol.h +mod_autoindex.o: mod_autoindex.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_request.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_main.h $(INCDIR)/util_script.h +mod_cern_meta.o: mod_cern_meta.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/util_script.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_request.h +mod_cgi.o: mod_cgi.c $(INCDIR)/httpd.h $(INCDIR)/ap_config.h \ + $(INCDIR)/ap_mmn.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_request.h $(INCDIR)/http_core.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_main.h \ + $(INCDIR)/http_log.h $(INCDIR)/util_script.h \ + $(INCDIR)/http_conf_globals.h +mod_digest.o: mod_digest.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/util_md5.h \ + $(INCDIR)/ap_md5.h +mod_dir.o: mod_dir.c $(INCDIR)/httpd.h $(INCDIR)/ap_config.h \ + $(INCDIR)/ap_mmn.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_request.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_main.h $(INCDIR)/util_script.h +mod_env.o: mod_env.c $(INCDIR)/httpd.h $(INCDIR)/ap_config.h \ + $(INCDIR)/ap_mmn.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h +mod_expires.o: mod_expires.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_log.h +mod_headers.o: mod_headers.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h +mod_imap.o: mod_imap.c $(INCDIR)/httpd.h $(INCDIR)/ap_config.h \ + $(INCDIR)/ap_mmn.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_request.h $(INCDIR)/http_core.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_main.h \ + $(INCDIR)/http_log.h $(INCDIR)/util_script.h +mod_include.o: mod_include.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_request.h $(INCDIR)/http_core.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_main.h $(INCDIR)/util_script.h +mod_info.o: mod_info.c $(INCDIR)/httpd.h $(INCDIR)/ap_config.h \ + $(INCDIR)/ap_mmn.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_main.h $(INCDIR)/http_protocol.h \ + $(INCDIR)/util_script.h $(INCDIR)/http_conf_globals.h +mod_log_agent.o: mod_log_agent.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_log.h +mod_log_config.o: mod_log_config.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h +mod_log_referer.o: mod_log_referer.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_log.h +mod_mime.o: mod_mime.c $(INCDIR)/httpd.h $(INCDIR)/ap_config.h \ + $(INCDIR)/ap_mmn.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_log.h +mod_mime_magic.o: mod_mime_magic.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_request.h $(INCDIR)/http_core.h \ + $(INCDIR)/http_log.h $(INCDIR)/http_protocol.h +mod_negotiation.o: mod_negotiation.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_request.h $(INCDIR)/http_core.h \ + $(INCDIR)/http_log.h $(INCDIR)/util_script.h +mod_rewrite.o: mod_rewrite.c mod_rewrite.h $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_conf_globals.h $(INCDIR)/http_request.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \ + $(INCDIR)/http_vhost.h +mod_setenvif.o: mod_setenvif.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_log.h +mod_so.o: mod_so.c $(INCDIR)/httpd.h $(INCDIR)/ap_config.h \ + $(INCDIR)/ap_mmn.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_log.h +mod_speling.o: mod_speling.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_core.h \ + $(INCDIR)/http_config.h $(INCDIR)/http_log.h +mod_status.o: mod_status.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_protocol.h \ + $(INCDIR)/http_conf_globals.h $(INCDIR)/http_main.h \ + $(INCDIR)/util_script.h $(INCDIR)/scoreboard.h \ + $(INCDIR)/http_log.h +mod_unique_id.o: mod_unique_id.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_log.h $(INCDIR)/multithread.h +mod_userdir.o: mod_userdir.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h +mod_usertrack.o: mod_usertrack.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \ + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \ + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUAuthAnon.mak b/APACHE_1_3_42/src/modules/standard/NWGNUAuthAnon.mak new file mode 100644 index 0000000000..47b1b09402 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUAuthAnon.mak @@ -0,0 +1,235 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = AuthAnon + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Authentication Anonymous Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = AuthAnon Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/AuthAnon.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_auth_anon.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + anon_auth_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUAuthDBM.mak b/APACHE_1_3_42/src/modules/standard/NWGNUAuthDBM.mak new file mode 100644 index 0000000000..d07d6b7a45 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUAuthDBM.mak @@ -0,0 +1,249 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(SRC)\lib\sdbm \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = AuthDBM + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Authentication DBM Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = AuthDBM Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/authdbm.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_auth_dbm.o \ + $(OBJDIR)/sdbm_lock.o \ + $(OBJDIR)/sdbm_hash.o \ + $(OBJDIR)/sdbm.o \ + $(OBJDIR)/sdbm_pair.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + dbm_auth_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + +$(OBJDIR)/%.o: $(SRC)\lib\sdbm\%.c $(OBJDIR)\cc.opt + @echo compiling $< + $(CC) $< -o=$(OBJDIR)\$(@F) @$(OBJDIR)\cc.opt + +%.d: $(SRC)\lib\sdbm\%.c $(OBJDIR)\cc.opt + @echo Creating dependancy list for $(notdir $<) + $(CC) $< -o $*.tmp -M @$(OBJDIR)\cc.opt + $(GNUTOOLS)/sed 's/$*.o[ :]*/$(OBJDIR)\/$*.o : $@ /g' $*.tmp > $@ + -$(DEL) $*.tmp + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUCERNMeta.mak b/APACHE_1_3_42/src/modules/standard/NWGNUCERNMeta.mak new file mode 100644 index 0000000000..0b61601d5c --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUCERNMeta.mak @@ -0,0 +1,235 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = CERNMeta + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache CERN Meta Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = CERNMeta Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/CERNMeta.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_cern_meta.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + cern_meta_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUDigest.mak b/APACHE_1_3_42/src/modules/standard/NWGNUDigest.mak new file mode 100644 index 0000000000..800f300142 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUDigest.mak @@ -0,0 +1,235 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = Digest + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Digest Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Digest Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/Digest.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_digest.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + digest_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUExpires.mak b/APACHE_1_3_42/src/modules/standard/NWGNUExpires.mak new file mode 100644 index 0000000000..39ba67edf8 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUExpires.mak @@ -0,0 +1,235 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = Expires + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Expires Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Expires Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/Expires.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_expires.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + expires_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUHeaders.mak b/APACHE_1_3_42/src/modules/standard/NWGNUHeaders.mak new file mode 100644 index 0000000000..ba5886bb48 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUHeaders.mak @@ -0,0 +1,235 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = Headers + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Headers Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Headers Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/Headers.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_headers.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + headers_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUInfo.mak b/APACHE_1_3_42/src/modules/standard/NWGNUInfo.mak new file mode 100644 index 0000000000..da9c4b5329 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUInfo.mak @@ -0,0 +1,236 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = Info + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Information Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Info Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/Info.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_info.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + @nlmlib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + info_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNURewrite.mak b/APACHE_1_3_42/src/modules/standard/NWGNURewrite.mak new file mode 100644 index 0000000000..762b1b405e --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNURewrite.mak @@ -0,0 +1,237 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = Rewrite + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Rewrite Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Rewrite Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start_ws + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop_ws + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/Rewrite.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_rewrite.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libprews.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + @nlmlib.imp \ + @ws2nlm.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + rewrite_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUSpeling.mak b/APACHE_1_3_42/src/modules/standard/NWGNUSpeling.mak new file mode 100644 index 0000000000..770b4dcc18 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUSpeling.mak @@ -0,0 +1,236 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = Speling + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Spelling Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Spelling Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/Speling.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_speling.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + @nlmlib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + speling_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUStatus.mak b/APACHE_1_3_42/src/modules/standard/NWGNUStatus.mak new file mode 100644 index 0000000000..30a2bc2cdd --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUStatus.mak @@ -0,0 +1,235 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = Status + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Status Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Status Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/Status.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_status.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + status_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUUsrtrack.mak b/APACHE_1_3_42/src/modules/standard/NWGNUUsrtrack.mak new file mode 100644 index 0000000000..53ae4d9fef --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUUsrtrack.mak @@ -0,0 +1,236 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = Usrtrack + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache User Track Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = UserTrack Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start_ws + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop_ws + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/Usrtrack.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_usertrack.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libprews.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + @ws2nlm.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + usertrack_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUVhost.mak b/APACHE_1_3_42/src/modules/standard/NWGNUVhost.mak new file mode 100644 index 0000000000..b6bec0236a --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUVhost.mak @@ -0,0 +1,235 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = vhost + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Mass Virtual Host Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Vhost Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/vhost.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_vhost_alias.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + vhost_alias_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUforensic.mak b/APACHE_1_3_42/src/modules/standard/NWGNUforensic.mak new file mode 100644 index 0000000000..605fedc432 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUforensic.mak @@ -0,0 +1,235 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = forensic + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Forensic Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Forensic Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/forensic.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_log_forensic.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + log_forensic_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUmakefile b/APACHE_1_3_42/src/modules/standard/NWGNUmakefile new file mode 100644 index 0000000000..3625360b63 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUmakefile @@ -0,0 +1,27 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)\NWGNUhead.inc + +# +# build this level's files + +ifeq "$(wildcard NWGNUmakefile.mak)" "NWGNUmakefile.mak" +include NWGNUmakefile.mak +endif + +# +# You can use this target if all that is needed is to copy files to the +# installation area +# +install :: nlms FORCE + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUmakefile.mak b/APACHE_1_3_42/src/modules/standard/NWGNUmakefile.mak new file mode 100644 index 0000000000..e066ae1952 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUmakefile.mak @@ -0,0 +1,290 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(SRC)\lib\sdbm \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/AuthDBM.nlm \ + $(OBJDIR)/AuthAnon.nlm \ + $(OBJDIR)/CERNMeta.nlm \ + $(OBJDIR)/Digest.nlm \ + $(OBJDIR)/Expires.nlm \ + $(OBJDIR)/forensic.nlm \ + $(OBJDIR)/Headers.nlm \ + $(OBJDIR)/Info.nlm \ + $(OBJDIR)/Rewrite.nlm \ + $(OBJDIR)/Speling.nlm \ + $(OBJDIR)/Status.nlm \ + $(OBJDIR)/uniqueid.nlm \ + $(OBJDIR)/Usrtrack.nlm \ + $(OBJDIR)/Vhost.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(OBJDIR)/stdmod.lib \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(OBJDIR)/mod_access.o \ + $(OBJDIR)/mod_actions.o \ + $(OBJDIR)/mod_alias.o \ + $(OBJDIR)/mod_asis.o \ + $(OBJDIR)/mod_auth.o \ + $(OBJDIR)/mod_autoindex.o \ + $(OBJDIR)/mod_dir.o \ + $(OBJDIR)/mod_env.o \ + $(OBJDIR)/mod_imap.o \ + $(OBJDIR)/mod_include.o \ + $(OBJDIR)/mod_log_nw.o \ + $(OBJDIR)/mod_mime.o \ + $(OBJDIR)/mod_negotiation.o \ + $(OBJDIR)/mod_setenvif.o \ + $(OBJDIR)/mod_so.o \ + $(OBJDIR)/mod_userdir.o \ + $(EOLIST) + +# Standard modules not linked statically + +# $(OBJDIR)/mod_auth_anon.obj \ dynamic +# $(OBJDIR)/mod_auth_db.obj \ +# $(OBJDIR)/mod_auth_dbm.obj \ +# $(OBJDIR)/mod_cern_meta.obj \ dynamic +# $(OBJDIR)/mod_cgi.obj \ +# $(OBJDIR)/mod_digest.obj \ dynamic +# $(OBJDIR)/mod_expires.obj \ dynamic +# $(OBJDIR)/mod_headers.obj \ dynamic +# $(OBJDIR)/mod_info.obj \ dynamic +# $(OBJDIR)/mod_log_agent.obj \ +# $(OBJDIR)/mod_log_referer.obj \ +# $(OBJDIR)/mod_mime_magic.obj \ +# $(OBJDIR)/mod_rewrite.obj \ dynamic +# $(OBJDIR)/mod_speling.obj \ dynamic +# $(OBJDIR)/mod_status.obj \ dynamic +# $(OBJDIR)/mod_unique_id.obj \ +# $(OBJDIR)/mod_usertrack.obj \ dynamic + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + copy $(OBJDIR)\*.nlm $(INSTALL)\APache\modules + +# +# Any specialized rules here +# +$(OBJDIR)/%.o: $(NWOS)\%.c $(OBJDIR)\cc.opt + @echo compiling $< + $(CC) $< -o=$(OBJDIR)\$(@F) @$(OBJDIR)\cc.opt + +%.d: $(NWOS)\%.c $(OBJDIR)\cc.opt + @echo Creating dependancy list for $(notdir $<) + $(CC) $< -o $*.tmp -M @$(OBJDIR)\cc.opt + $(GNUTOOLS)/sed 's/$*.o[ :]*/$(OBJDIR)\/$*.o : $@ /g' $*.tmp > $@ + -$(DEL) $*.tmp + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/NWGNUuniqueid.mak b/APACHE_1_3_42/src/modules/standard/NWGNUuniqueid.mak new file mode 100644 index 0000000000..41aa9a9f82 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/NWGNUuniqueid.mak @@ -0,0 +1,236 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)\NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(SRC)\include \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = uniqueid + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache Unique ID Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Unique ID Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUenvironment.inc +# +NLM_VERSION = 1,3,0 + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _lib_start_ws + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _lib_stop_ws + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/uniqueid.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_unique_id.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(NWOS)\$(OBJDIR)\libprews.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @ApacheCore.imp \ + @threads.imp \ + @clib.imp \ + @ws2nlm.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + unique_id_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\NWGNUtail.inc + diff --git a/APACHE_1_3_42/src/modules/standard/mod_access.c b/APACHE_1_3_42/src/modules/standard/mod_access.c new file mode 100644 index 0000000000..07b84ff742 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_access.c @@ -0,0 +1,372 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Security options etc. + * + * Module derived from code originally written by Rob McCool + * + */ + +#include "httpd.h" +#include "http_core.h" +#include "http_config.h" +#include "http_log.h" +#include "http_request.h" + +enum allowdeny_type { + T_ENV, + T_ALL, + T_IP, + T_HOST, + T_FAIL +}; + +typedef struct { + int limited; + union { + char *from; + struct { + struct in_addr net; + struct in_addr mask; + } ip; + } x; + enum allowdeny_type type; +} allowdeny; + +/* 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 MODULE_VAR_EXPORT access_module; + +static void *create_access_dir_config(pool *p, char *dummy) +{ + access_dir_conf *conf = + (access_dir_conf *) ap_pcalloc(p, sizeof(access_dir_conf)); + int i; + + for (i = 0; i < METHODS; ++i) + conf->order[i] = DENY_THEN_ALLOW; + conf->allows = ap_make_array(p, 1, sizeof(allowdeny)); + conf->denys = ap_make_array(p, 1, sizeof(allowdeny)); + + return (void *) conf; +} + +static const char *order(cmd_parms *cmd, void *dv, char *arg) +{ + access_dir_conf *d = (access_dir_conf *) dv; + int i, o; + + if (!strcasecmp(arg, "allow,deny")) + o = ALLOW_THEN_DENY; + else if (!strcasecmp(arg, "deny,allow")) + o = DENY_THEN_ALLOW; + else if (!strcasecmp(arg, "mutual-failure")) + o = MUTUAL_FAILURE; + else + return "unknown order"; + + for (i = 0; i < METHODS; ++i) + if (cmd->limited & (1 << i)) + d->order[i] = o; + + return NULL; +} + +static int is_ip(const char *host) +{ + while ((*host == '.') || ap_isdigit(*host)) + host++; + return (*host == '\0'); +} + +static const char *allow_cmd(cmd_parms *cmd, void *dv, char *from, char *where) +{ + access_dir_conf *d = (access_dir_conf *) dv; + allowdeny *a; + char *s; + + if (strcasecmp(from, "from")) + return "allow and deny must be followed by 'from'"; + + a = (allowdeny *) ap_push_array(cmd->info ? d->allows : d->denys); + a->x.from = where; + a->limited = cmd->limited; + + if (!strncasecmp(where, "env=", 4)) { + a->type = T_ENV; + a->x.from += 4; + + } + else if (!strcasecmp(where, "all")) { + a->type = T_ALL; + + } + else if ((s = strchr(where, '/'))) { + struct in_addr mask; + + a->type = T_IP; + /* trample on where, we won't be using it any more */ + *s++ = '\0'; + + if (!is_ip(where) + || (a->x.ip.net.s_addr = ap_inet_addr(where)) == INADDR_NONE) { + a->type = T_FAIL; + return "syntax error in network portion of network/netmask"; + } + + /* is_ip just tests if it matches [\d.]+ */ + if (!is_ip(s)) { + a->type = T_FAIL; + return "syntax error in mask portion of network/netmask"; + } + /* is it in /a.b.c.d form? */ + if (strchr(s, '.')) { + mask.s_addr = ap_inet_addr(s); + if (mask.s_addr == INADDR_NONE) { + a->type = T_FAIL; + return "syntax error in mask portion of network/netmask"; + } + } + else { + int i; + + /* assume it's in /nnn form */ + i = atoi(s); + if (i > 32 || i <= 0) { + a->type = T_FAIL; + return "invalid mask in network/netmask"; + } + mask.s_addr = 0xFFFFFFFFUL << (32 - i); + mask.s_addr = htonl(mask.s_addr); + } + a->x.ip.mask = mask; + a->x.ip.net.s_addr = (a->x.ip.net.s_addr & mask.s_addr); /* pjr - This fixes PR 4770 */ + } + else if (ap_isdigit(*where) && is_ip(where)) { + /* legacy syntax for ip addrs: a.b.c. ==> a.b.c.0/24 for example */ + int shift; + char *t; + int octet; + + a->type = T_IP; + /* parse components */ + s = where; + a->x.ip.net.s_addr = 0; + a->x.ip.mask.s_addr = 0; + shift = 24; + while (*s) { + t = s; + if (!ap_isdigit(*t)) { + a->type = T_FAIL; + return "invalid ip address"; + } + while (ap_isdigit(*t)) { + ++t; + } + if (*t == '.') { + *t++ = 0; + } + else if (*t) { + a->type = T_FAIL; + return "invalid ip address"; + } + if (shift < 0) { + a->type = T_FAIL; + return "invalid ip address, only 4 octets allowed"; + } + octet = atoi(s); + if (octet < 0 || octet > 255) { + a->type = T_FAIL; + return "each octet must be between 0 and 255 inclusive"; + } + a->x.ip.net.s_addr |= (unsigned int)octet << shift; + a->x.ip.mask.s_addr |= 0xFFUL << shift; + s = t; + shift -= 8; + } + a->x.ip.net.s_addr = ntohl(a->x.ip.net.s_addr); + a->x.ip.mask.s_addr = ntohl(a->x.ip.mask.s_addr); + } + else { + a->type = T_HOST; + } + + return NULL; +} + +static char its_an_allow; + +static const 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} +}; + +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 find_allowdeny(request_rec *r, array_header *a, int method) +{ + allowdeny *ap = (allowdeny *) a->elts; + int mmask = (1 << method); + int i; + int gothost = 0; + const char *remotehost = NULL; + + for (i = 0; i < a->nelts; ++i) { + if (!(mmask & ap[i].limited)) + continue; + + switch (ap[i].type) { + case T_ENV: + if (ap_table_get(r->subprocess_env, ap[i].x.from)) { + return 1; + } + break; + + case T_ALL: + return 1; + + case T_IP: + if (ap[i].x.ip.net.s_addr != INADDR_NONE + && (r->connection->remote_addr.sin_addr.s_addr + & ap[i].x.ip.mask.s_addr) == ap[i].x.ip.net.s_addr) { + return 1; + } + break; + + case T_HOST: + if (!gothost) { + remotehost = ap_get_remote_host(r->connection, r->per_dir_config, + REMOTE_DOUBLE_REV); + + if ((remotehost == NULL) || is_ip(remotehost)) + gothost = 1; + else + gothost = 2; + } + + if ((gothost == 2) && in_domain(ap[i].x.from, remotehost)) + return 1; + break; + + case T_FAIL: + /* do nothing? */ + break; + } + } + + return 0; +} + +static int check_dir_access(request_rec *r) +{ + int method = r->method_number; + access_dir_conf *a = + (access_dir_conf *) + ap_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 + && (ap_satisfies(r) != SATISFY_ANY || !ap_some_auth_required(r))) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "client denied by server configuration: %s", + r->filename); + } + + return ret; +} + + + +module MODULE_VAR_EXPORT 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 */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_actions.c b/APACHE_1_3_42/src/modules/standard/mod_actions.c new file mode 100644 index 0000000000..f2f3b57bd3 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_actions.c @@ -0,0 +1,249 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_actions.c: executes scripts based on MIME type or HTTP method + * + * by Alexei Kosut; based on mod_cgi.c, mod_mime.c and mod_includes.c, + * adapted by rst from original NCSA code by Rob McCool + * + * Usage instructions: + * + * Action mime/type /cgi-bin/script + * + * will activate /cgi-bin/script when a file of content type mime/type is + * requested. It sends the URL and file path of the requested document using + * the standard CGI PATH_INFO and PATH_TRANSLATED environment variables. + * + * Script PUT /cgi-bin/script + * + * will activate /cgi-bin/script when a request is received with the + * HTTP method "PUT". The available method names are defined in httpd.h. + * If the method is GET, the script will only be activated if the requested + * URI includes query information (stuff after a ?-mark). + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_main.h" +#include "http_log.h" +#include "util_script.h" + +typedef struct { + char *method; + char *script; +} xmethod_t; + +/* + * HTTP methods are case-sensitive, so we can't use a table structure to + * track extension method mappings -- table keys are case-INsensitive. + */ +typedef struct { + table *action_types; /* Added with Action... */ + char *scripted[METHODS]; /* Added with Script... */ + array_header *xmethods; /* Added with Script -- extension methods */ +} action_dir_config; + +module action_module; + +static void *create_action_dir_config(pool *p, char *dummy) +{ + action_dir_config *new = + (action_dir_config *) ap_palloc(p, sizeof(action_dir_config)); + + new->action_types = ap_make_table(p, 4); + memset(new->scripted, 0, sizeof(new->scripted)); + new->xmethods = ap_make_array(p, 4, sizeof(xmethod_t)); + return new; +} + +static void *merge_action_dir_configs(pool *p, void *basev, void *addv) +{ + action_dir_config *base = (action_dir_config *) basev; + action_dir_config *add = (action_dir_config *) addv; + action_dir_config *new = (action_dir_config *) ap_palloc(p, + sizeof(action_dir_config)); + int i; + + new->action_types = ap_overlay_tables(p, add->action_types, + base->action_types); + + for (i = 0; i < METHODS; ++i) { + new->scripted[i] = add->scripted[i] ? add->scripted[i] + : base->scripted[i]; + } + new->xmethods = ap_append_arrays(p, add->xmethods, base->xmethods); + return new; +} + +static const char *add_action(cmd_parms *cmd, action_dir_config *m, char *type, + char *script) +{ + ap_table_setn(m->action_types, type, script); + return NULL; +} + +static const char *set_script(cmd_parms *cmd, action_dir_config *m, + char *method, char *script) +{ + int methnum; + + methnum = ap_method_number_of(method); + if (methnum == M_TRACE) { + return "TRACE not allowed for Script"; + } + else if (methnum != M_INVALID) { + m->scripted[methnum] = script; + } + else { + /* + * We used to return "Unknown method type for Script" + * but now we actually handle unknown methods. + */ + xmethod_t *xm; + xmethod_t *list; + int i; + + /* + * Scan through the list; if the method already has a script + * defined, overwrite it. Otherwise, add it. + */ + list = (xmethod_t *) m->xmethods->elts; + for (i = 0; i < m->xmethods->nelts; ++i) { + xm = &list[i]; + if (strcmp(method, xm->method) == 0) { + xm->script = script; + break; + } + } + if (i <= m->xmethods->nelts) { + xm = ap_push_array(m->xmethods); + xm->method = method; + xm->script = script; + } + } + return NULL; +} + +static const command_rec action_cmds[] = +{ + {"Action", add_action, NULL, OR_FILEINFO, TAKE2, + "a media type followed by a script name"}, + {"Script", set_script, NULL, ACCESS_CONF | RSRC_CONF, TAKE2, + "a method followed by a script name"}, + {NULL} +}; + +static int action_handler(request_rec *r) +{ + action_dir_config *conf = (action_dir_config *) + ap_get_module_config(r->per_dir_config, &action_module); + const char *t, *action = r->handler ? r->handler : + ap_field_noparam(r->pool, r->content_type); + const char *script; + int i; + + /* Set allowed stuff */ + for (i = 0; i < METHODS; ++i) { + if (conf->scripted[i]) { + r->allowed |= (1 << i); + } + } + + /* First, check for the method-handling scripts */ + if (r->method_number == M_GET) { + if (r->args) { + script = conf->scripted[M_GET]; + } + else { + script = NULL; + } + } + else { + if (r->method_number != M_INVALID) { + script = conf->scripted[r->method_number]; + } + else { + int j; + xmethod_t *xm; + xmethod_t *list; + + script = NULL; + list = (xmethod_t *) conf->xmethods->elts; + for (j = 0; j < conf->xmethods->nelts; ++j) { + xm = &list[j]; + if (strcmp(r->method, xm->method) == 0) { + script = xm->script; + break; + } + } + } + } + + /* Check for looping, which can happen if the CGI script isn't */ + if (script && r->prev && r->prev->prev) { + return DECLINED; + } + + /* Second, check for actions (which override the method scripts) */ + if ((t = ap_table_get(conf->action_types, + action ? action : ap_default_type(r)))) { + script = t; + } + + if (script == NULL) { + return DECLINED; + } + + ap_internal_redirect_handler(ap_pstrcat(r->pool, script, + ap_escape_uri(r->pool, + r->uri), + r->args ? "?" : NULL, + r->args, NULL), r); + return OK; +} + +static const handler_rec action_handlers[] = +{ + {"*/*", action_handler}, + {NULL} +}; + +module action_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_action_dir_config, /* dir config creater */ + merge_action_dir_configs, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + action_cmds, /* command table */ + action_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_alias.c b/APACHE_1_3_42/src/modules/standard/mod_alias.c new file mode 100644 index 0000000000..3e81c07c75 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_alias.c @@ -0,0 +1,408 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * http_alias.c: Stuff for dealing with directory aliases + * + * Original by Rob McCool, rewritten in succession by David Robinson + * and rst. + * + */ + +#include "httpd.h" +#include "http_core.h" +#include "http_config.h" +#include "http_log.h" + +typedef struct { + char *real; + char *fake; + char *handler; + regex_t *regexp; + int redir_status; /* 301, 302, 303, 410, etc */ +} alias_entry; + +typedef struct { + array_header *aliases; + array_header *redirects; +} alias_server_conf; + +typedef struct { + array_header *redirects; +} alias_dir_conf; + +module MODULE_VAR_EXPORT alias_module; + +static void *create_alias_config(pool *p, server_rec *s) +{ + alias_server_conf *a = + (alias_server_conf *) ap_pcalloc(p, sizeof(alias_server_conf)); + + a->aliases = ap_make_array(p, 20, sizeof(alias_entry)); + a->redirects = ap_make_array(p, 20, sizeof(alias_entry)); + return a; +} + +static void *create_alias_dir_config(pool *p, char *d) +{ + alias_dir_conf *a = + (alias_dir_conf *) ap_pcalloc(p, sizeof(alias_dir_conf)); + a->redirects = ap_make_array(p, 2, sizeof(alias_entry)); + return a; +} + +static void *merge_alias_config(pool *p, void *basev, void *overridesv) +{ + alias_server_conf *a = + (alias_server_conf *) ap_pcalloc(p, sizeof(alias_server_conf)); + alias_server_conf *base = (alias_server_conf *) basev, *overrides = (alias_server_conf *) overridesv; + + a->aliases = ap_append_arrays(p, overrides->aliases, base->aliases); + a->redirects = ap_append_arrays(p, overrides->redirects, base->redirects); + return a; +} + +static void *merge_alias_dir_config(pool *p, void *basev, void *overridesv) +{ + alias_dir_conf *a = + (alias_dir_conf *) ap_pcalloc(p, sizeof(alias_dir_conf)); + alias_dir_conf *base = (alias_dir_conf *) basev, *overrides = (alias_dir_conf *) overridesv; + a->redirects = ap_append_arrays(p, overrides->redirects, base->redirects); + return a; +} + +static const char *add_alias_internal(cmd_parms *cmd, void *dummy, char *f, char *r, + int use_regex) +{ + server_rec *s = cmd->server; + alias_server_conf *conf = + (alias_server_conf *) ap_get_module_config(s->module_config, &alias_module); + alias_entry *new = ap_push_array(conf->aliases); + + /* XX r can NOT be relative to DocumentRoot here... compat bug. */ + + if (use_regex) { + new->regexp = ap_pregcomp(cmd->pool, f, REG_EXTENDED); + if (new->regexp == NULL) + return "Regular expression could not be compiled."; + new->real = r; + } +#ifndef OS2 + else + new->real = ap_os_canonical_filename(cmd->pool, r); +#else + new->real = r; +#endif + new->fake = f; + new->handler = cmd->info; + + return NULL; +} + +static const char *add_alias(cmd_parms *cmd, void *dummy, char *f, char *r) +{ + return add_alias_internal(cmd, dummy, f, r, 0); +} + +static const char *add_alias_regex(cmd_parms *cmd, void *dummy, char *f, char *r) +{ + return add_alias_internal(cmd, dummy, f, r, 1); +} + +static const char *add_redirect_internal(cmd_parms *cmd, alias_dir_conf * dirconf, + char *arg1, char *arg2, char *arg3, + int use_regex) +{ + alias_entry *new; + server_rec *s = cmd->server; + alias_server_conf *serverconf = + (alias_server_conf *) ap_get_module_config(s->module_config, &alias_module); + int status = (int) (long) cmd->info; + regex_t *r = NULL; + char *f = arg2; + char *url = arg3; + + if (!strcasecmp(arg1, "gone")) + status = HTTP_GONE; + else if (!strcasecmp(arg1, "permanent")) + status = HTTP_MOVED_PERMANENTLY; + else if (!strcasecmp(arg1, "temp")) + status = HTTP_MOVED_TEMPORARILY; + else if (!strcasecmp(arg1, "seeother")) + status = HTTP_SEE_OTHER; + else if (ap_isdigit(*arg1)) + status = atoi(arg1); + else { + f = arg1; + url = arg2; + } + + if (use_regex) { + r = ap_pregcomp(cmd->pool, f, REG_EXTENDED); + if (r == NULL) + return "Regular expression could not be compiled."; + } + + if (ap_is_HTTP_REDIRECT(status)) { + if (!url) + return "URL to redirect to is missing"; + if (!use_regex && !ap_is_url(url)) + return "Redirect to non-URL"; + } + else { + if (url) + return "Redirect URL not valid for this status"; + } + + if (cmd->path) + new = ap_push_array(dirconf->redirects); + else + new = ap_push_array(serverconf->redirects); + + new->fake = f; + new->real = url; + new->regexp = r; + new->redir_status = status; + return NULL; +} + +static const char *add_redirect(cmd_parms *cmd, alias_dir_conf * dirconf, char *arg1, + char *arg2, char *arg3) +{ + return add_redirect_internal(cmd, dirconf, arg1, arg2, arg3, 0); +} + +static const char *add_redirect_regex(cmd_parms *cmd, alias_dir_conf * dirconf, + char *arg1, char *arg2, char *arg3) +{ + return add_redirect_internal(cmd, dirconf, arg1, arg2, arg3, 1); +} + +static const command_rec alias_cmds[] = +{ + {"Alias", add_alias, NULL, RSRC_CONF, TAKE2, + "a fakename and a realname"}, + {"ScriptAlias", add_alias, "cgi-script", RSRC_CONF, TAKE2, + "a fakename and a realname"}, + {"Redirect", add_redirect, (void *) HTTP_MOVED_TEMPORARILY, + OR_FILEINFO, TAKE23, + "an optional status, then document to be redirected and destination URL"}, + {"AliasMatch", add_alias_regex, NULL, RSRC_CONF, TAKE2, + "a regular expression and a filename"}, + {"ScriptAliasMatch", add_alias_regex, "cgi-script", RSRC_CONF, TAKE2, + "a regular expression and a filename"}, + {"RedirectMatch", add_redirect_regex, (void *) HTTP_MOVED_TEMPORARILY, + OR_FILEINFO, TAKE23, + "an optional status, then a regular expression and destination URL"}, + {"RedirectTemp", add_redirect, (void *) HTTP_MOVED_TEMPORARILY, + OR_FILEINFO, TAKE2, + "a document to be redirected, then the destination URL"}, + {"RedirectPermanent", add_redirect, (void *) HTTP_MOVED_PERMANENTLY, + OR_FILEINFO, TAKE2, + "a document to be redirected, then the destination URL"}, + {NULL} +}; + +static int alias_matches(const char *uri, const char *alias_fakename) +{ + const char *end_fakename = alias_fakename + strlen(alias_fakename); + const char *aliasp = alias_fakename, *urip = uri; + + while (aliasp < end_fakename) { + if (*aliasp == '/') { + /* any number of '/' in the alias matches any number in + * the supplied URI, but there must be at least one... + */ + if (*urip != '/') + return 0; + + while (*aliasp == '/') + ++aliasp; + while (*urip == '/') + ++urip; + } + else { + /* Other characters are compared literally */ + if (*urip++ != *aliasp++) + return 0; + } + } + + /* Check last alias path component matched all the way */ + + if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/') + return 0; + + /* Return number of characters from URI which matched (may be + * greater than length of alias, since we may have matched + * doubled slashes) + */ + + return urip - uri; +} + +static char *try_alias_list(request_rec *r, array_header *aliases, int doesc, int *status) +{ + alias_entry *entries = (alias_entry *) aliases->elts; + regmatch_t regm[AP_MAX_REG_MATCH]; + char *found = NULL; + int i; + + for (i = 0; i < aliases->nelts; ++i) { + alias_entry *p = &entries[i]; + int l; + + if (p->regexp) { + if (!ap_regexec(p->regexp, r->uri, AP_MAX_REG_MATCH, regm, 0)) { + if (p->real) { + found = ap_pregsub(r->pool, p->real, r->uri, + AP_MAX_REG_MATCH, regm); + if (found && doesc) { + found = ap_escape_uri(r->pool, found); + } + } + else { + /* need something non-null */ + found = ap_pstrdup(r->pool, ""); + } + } + } + else { + l = alias_matches(r->uri, p->fake); + + if (l > 0) { + if (doesc) { + char *escurl; + escurl = ap_os_escape_path(r->pool, r->uri + l, 1); + + found = ap_pstrcat(r->pool, p->real, escurl, NULL); + } + else + found = ap_pstrcat(r->pool, p->real, r->uri + l, NULL); + } + } + + if (found) { + if (p->handler) { /* Set handler, and leave a note for mod_cgi */ + r->handler = p->handler; + ap_table_setn(r->notes, "alias-forced-type", r->handler); + } + + *status = p->redir_status; + + return found; + } + } + + return NULL; +} + +static int translate_alias_redir(request_rec *r) +{ + void *sconf = r->server->module_config; + alias_server_conf *serverconf = + (alias_server_conf *) ap_get_module_config(sconf, &alias_module); + char *ret; + int status; + + if (r->uri[0] != '/' && r->uri[0] != '\0') + return DECLINED; + + if ((ret = try_alias_list(r, serverconf->redirects, 1, &status)) != NULL) { + if (ap_is_HTTP_REDIRECT(status)) { + /* include QUERY_STRING if any */ + if (r->args) { + ret = ap_pstrcat(r->pool, ret, "?", r->args, NULL); + } + ap_table_setn(r->headers_out, "Location", ret); + } + return status; + } + + if ((ret = try_alias_list(r, serverconf->aliases, 0, &status)) != NULL) { + r->filename = ret; + return OK; + } + + return DECLINED; +} + +static int fixup_redir(request_rec *r) +{ + void *dconf = r->per_dir_config; + alias_dir_conf *dirconf = + (alias_dir_conf *) ap_get_module_config(dconf, &alias_module); + char *ret; + int status; + + /* It may have changed since last time, so try again */ + + if ((ret = try_alias_list(r, dirconf->redirects, 1, &status)) != NULL) { + if (ap_is_HTTP_REDIRECT(status)) { + if (ret[0] == '/') { + char *orig_target = ret; + + ret = ap_construct_url(r->pool, ret, r); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, + "incomplete redirection target of '%s' for " + "URI '%s' modified to '%s'", + orig_target, r->uri, ret); + } + if (!ap_is_url(ret)) { + status = HTTP_INTERNAL_SERVER_ERROR; + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, + "cannot redirect '%s' to '%s'; " + "target is not a valid absoluteURI or abs_path", + r->uri, ret); + } + else { + /* append requested query only, if the config didn't + * supply its own. + */ + if (r->args && !strchr(ret, '?')) { + ret = ap_pstrcat(r->pool, ret, "?", r->args, NULL); + } + ap_table_setn(r->headers_out, "Location", ret); + } + } + return status; + } + + return DECLINED; +} + +module MODULE_VAR_EXPORT alias_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_alias_dir_config, /* dir config creater */ + merge_alias_dir_config, /* dir merger --- default is to override */ + create_alias_config, /* server config */ + merge_alias_config, /* merge server configs */ + alias_cmds, /* command table */ + NULL, /* handlers */ + translate_alias_redir, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + fixup_redir, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_asis.c b/APACHE_1_3_42/src/modules/standard/mod_asis.c new file mode 100644 index 0000000000..6bd5d33278 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_asis.c @@ -0,0 +1,104 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_protocol.h" +#include "http_log.h" +#include "util_script.h" +#include "http_main.h" +#include "http_request.h" + +static int asis_handler(request_rec *r) +{ + FILE *f; + const char *location; + + r->allowed |= (1 << M_GET); + if (r->method_number != M_GET) + return DECLINED; + if (r->finfo.st_mode == 0) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "File does not exist: %s", r->filename); + return NOT_FOUND; + } + + f = ap_pfopen(r->pool, r->filename, "r"); + + if (f == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "file permissions deny server access: %s", r->filename); + return FORBIDDEN; + } + + ap_scan_script_header_err(r, f, NULL); + location = ap_table_get(r->headers_out, "Location"); + + if (location && location[0] == '/' && + ((r->status == HTTP_OK) || ap_is_HTTP_REDIRECT(r->status))) { + + ap_pfclose(r->pool, f); + + /* Internal redirect -- fake-up a pseudo-request */ + r->status = HTTP_OK; + + /* This redirect needs to be a GET no matter what the original + * method was. + */ + r->method = ap_pstrdup(r->pool, "GET"); + r->method_number = M_GET; + + ap_internal_redirect_handler(location, r); + return OK; + } + + ap_send_http_header(r); + if (!r->header_only) + ap_send_fd(f, r); + + ap_pfclose(r->pool, f); + return OK; +} + +static const handler_rec asis_handlers[] = +{ + {ASIS_MAGIC_TYPE, asis_handler}, + {"send-as-is", asis_handler}, + {NULL} +}; + +module MODULE_VAR_EXPORT asis_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + NULL, /* command table */ + asis_handlers, /* handlers */ + NULL, /* translate_handler */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* pre-run fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_auth.c b/APACHE_1_3_42/src/modules/standard/mod_auth.c new file mode 100644 index 0000000000..ee0e1b8a89 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_auth.c @@ -0,0 +1,396 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * http_auth: authentication + * + * Rob McCool + * + * Adapted to Apache by rst. + * + * dirkx - Added Authoritative control to allow passing on to lower + * modules if and only if the user-id is not known to this + * module. A known user with a faulty or absent password still + * causes an AuthRequired. The default is 'Authoritative', i.e. + * no control is passed along. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" + +typedef struct auth_config_struct { + char *auth_pwfile; + char *auth_grpfile; + int auth_authoritative; +} auth_config_rec; + +static void *create_auth_dir_config(pool *p, char *d) +{ + auth_config_rec *sec = + (auth_config_rec *) ap_pcalloc(p, sizeof(auth_config_rec)); + sec->auth_pwfile = NULL; /* just to illustrate the default really */ + sec->auth_grpfile = NULL; /* unless you have a broken HP cc */ + sec->auth_authoritative = 1; /* keep the fortress secure by default */ + return sec; +} + +static const char *set_auth_slot(cmd_parms *cmd, void *offset, char *f, char *t) +{ + if (t && strcmp(t, "standard")) + return ap_pstrcat(cmd->pool, "Invalid auth file type: ", t, NULL); + + return ap_set_file_slot(cmd, offset, f); +} + +static const command_rec auth_cmds[] = +{ + {"AuthUserFile", set_auth_slot, + (void *) XtOffsetOf(auth_config_rec, auth_pwfile), OR_AUTHCFG, TAKE12, + "text file containing user IDs and passwords"}, + {"AuthGroupFile", set_auth_slot, + (void *) XtOffsetOf(auth_config_rec, auth_grpfile), OR_AUTHCFG, TAKE12, + "text file containing group names and member user IDs"}, + {"AuthAuthoritative", ap_set_flag_slot, + (void *) XtOffsetOf(auth_config_rec, auth_authoritative), + OR_AUTHCFG, FLAG, + "Set to 'off' to allow access control to be passed along to " + "lower modules if the UserID is not known to this module"}, + {NULL} +}; + +module MODULE_VAR_EXPORT auth_module; + +static char *get_pw(request_rec *r, char *user, char *auth_pwfile) +{ + configfile_t *f; + char l[MAX_STRING_LEN]; + const char *rpw, *w; + + if (!(f = ap_pcfg_openfile(r->pool, auth_pwfile))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "Could not open password file: %s", auth_pwfile); + return NULL; + } + while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { + if ((l[0] == '#') || (!l[0])) + continue; + rpw = l; + w = ap_getword(r->pool, &rpw, ':'); + + if (!strcmp(user, w)) { + ap_cfg_closefile(f); + return ap_getword(r->pool, &rpw, ':'); + } + } + ap_cfg_closefile(f); + return NULL; +} + +static table *groups_for_user(pool *p, char *user, char *grpfile) +{ + configfile_t *f; + table *grps = ap_make_table(p, 15); + pool *sp; + char l[MAX_STRING_LEN]; + const char *group_name, *ll, *w; + + if (!(f = ap_pcfg_openfile(p, grpfile))) { +/*add? aplog_error(APLOG_MARK, APLOG_ERR, NULL, + "Could not open group file: %s", grpfile);*/ + return NULL; + } + + sp = ap_make_sub_pool(p); + + while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { + if ((l[0] == '#') || (!l[0])) + continue; + ll = l; + ap_clear_pool(sp); + + group_name = ap_getword(sp, &ll, ':'); + + while (ll[0]) { + w = ap_getword_conf(sp, &ll); + if (!strcmp(w, user)) { + ap_table_setn(grps, ap_pstrdup(p, group_name), "in"); + break; + } + } + } + ap_cfg_closefile(f); + ap_destroy_pool(sp); + return grps; +} + +/* These functions return 0 if client is OK, and proper error status + * if not... either AUTH_REQUIRED, if we made a check, and it failed, or + * SERVER_ERROR, if things are so totally confused that we couldn't + * figure out how to tell if the client is authorized or not. + * + * If they return DECLINED, and all other modules also decline, that's + * treated by the server core as a configuration error, logged and + * reported as such. + */ + +/* Determine user ID, and check if it really is that user, for HTTP + * basic authentication... + */ + +static int authenticate_basic_user(request_rec *r) +{ + auth_config_rec *sec = + (auth_config_rec *) ap_get_module_config(r->per_dir_config, &auth_module); + conn_rec *c = r->connection; + const char *sent_pw; + char *real_pw; + char *invalid_pw; + int res; + + if ((res = ap_get_basic_auth_pw(r, &sent_pw))) + return res; + + if (!sec->auth_pwfile) + return DECLINED; + + if (!(real_pw = get_pw(r, c->user, sec->auth_pwfile))) { + if (!(sec->auth_authoritative)) + return DECLINED; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user %s not found: %s", c->user, r->uri); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + invalid_pw = ap_validate_password(sent_pw, real_pw); + if (invalid_pw != NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user %s: authentication failure for \"%s\": %s", + c->user, r->uri, invalid_pw); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + return OK; +} + +/* Checking ID */ + +static int check_user_access(request_rec *r) +{ + auth_config_rec *sec = + (auth_config_rec *) ap_get_module_config(r->per_dir_config, &auth_module); + char *user = r->connection->user; + int m = r->method_number; + int method_restricted = 0; + register int x; + const char *t, *w; + table *grpstatus; + const array_header *reqs_arr = ap_requires(r); + require_line *reqs; + + /* BUG FIX: tadc, 11-Nov-1995. If there is no "requires" directive, + * then any user will do. + */ + if (reqs_arr == NULL) { + return (OK); + } + reqs = (require_line *) reqs_arr->elts; + + if (sec->auth_grpfile) { + grpstatus = groups_for_user(r->pool, user, sec->auth_grpfile); + } + else { + grpstatus = NULL; + } + + for (x = 0; x < reqs_arr->nelts; x++) { + + if (! (reqs[x].method_mask & (1 << m))) { + continue; + } + + method_restricted = 1; + + t = reqs[x].requirement; + w = ap_getword_white(r->pool, &t); + if (strcmp(w, "valid-user") == 0) { + return OK; + } + /* + * If requested, allow access if the user is valid and the + * owner of the document. + */ + if (strcmp(w, "file-owner") == 0) { +#if defined(WIN32) || defined(NETWARE) || defined(OS2) + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r, + "'Require file-owner' not supported " + "on this platform, ignored"); + continue; +#else + struct passwd *pwent; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "checking for 'owner' access for file '%s'", + r->filename); + if (r->finfo.st_ino == 0) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "no stat info for '%s'", r->filename); + continue; + } + pwent = getpwuid(r->finfo.st_uid); + if (pwent == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "no username for UID %d (owner of '%s')", + r->finfo.st_uid, r->filename); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "checking authenticated user '%s' " + "against owner '%s' of '%s'", + user, pwent->pw_name, r->filename); + if (strcmp(user, pwent->pw_name) == 0) { + return OK; + } + else { + continue; + } + } +#endif + } + if (strcmp(w, "file-group") == 0) { +#if defined(WIN32) || defined(NETWARE) || defined(OS2) + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r, + "'Require file-group' not supported " + "on this platform, ignored"); + continue; +#else + struct group *grent; + if (sec->auth_grpfile == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r, + "no AuthGroupFile, so 'file-group' " + "requirement cannot succeed for file '%s'", + r->filename); + continue; + } + if (grpstatus == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, r, + "authenticated user '%s' not a member of " + "any groups, so 'file-group' requirement " + "cannot succeed for file '%s'", + user, r->filename); + continue; + } + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "checking for 'group' access for file '%s'", + r->filename); + if (r->finfo.st_ino == 0) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "no stat info for '%s'", r->filename); + continue; + } + grent = getgrgid(r->finfo.st_gid); + if (grent == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "no group name for GID %d (owner of '%s')", + r->finfo.st_gid, r->filename); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, + "checking groups of authenticated user '%s' " + "against owner group '%s' of '%s'", + user, grent->gr_name, r->filename); + if (ap_table_get(grpstatus, grent->gr_name) != NULL) { + return OK; + } + else { + continue; + } + } +#endif + } + if (strcmp(w, "user") == 0) { + while (t[0] != '\0') { + w = ap_getword_conf(r->pool, &t); + if (strcmp(user, w) == 0) { + return OK; + } + } + } + else if (strcmp(w, "group") == 0) { + if (grpstatus == NULL) { + return DECLINED; /* DBM group? Something else? */ + } + + while (t[0]) { + w = ap_getword_conf(r->pool, &t); + if (ap_table_get(grpstatus, w)) { + return OK; + } + } + } + else if (sec->auth_authoritative) { + /* if we aren't authoritative, any require directive could be + * valid even if we don't grok it. However, if we are + * authoritative, we can warn the user they did something wrong. + * That something could be a missing "AuthAuthoritative off", but + * more likely is a typo in the require directive. + */ + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "access to %s failed, " + "reason: unknown require directive:" + "\"%s\"", r->uri, reqs[x].requirement); + } + } + + if (! method_restricted) { + return OK; + } + + if (! sec->auth_authoritative) { + return DECLINED; + } + + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "access to %s failed, reason: user %s not allowed access", + r->uri, user); + + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; +} + +module MODULE_VAR_EXPORT auth_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_auth_dir_config, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + auth_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + authenticate_basic_user, /* check_user_id */ + check_user_access, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_auth_anon.c b/APACHE_1_3_42/src/modules/standard/mod_auth_anon.c new file mode 100644 index 0000000000..c55de46a33 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_auth_anon.c @@ -0,0 +1,274 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * http_auth: authentication + * + * Rob McCool & Brian Behlendorf. + * + * Adapted to Apache by rst. + * + * Version 0.5 May 1996 + * + * Modified by Dirk.vanGulik@jrc.it to + * + * Adapted to allow anonymous logins, just like with Anon-FTP, when + * one gives the magic user name 'anonymous' and ones email address + * as the password. + * + * Just add the following tokes to your <directory> setup: + * + * Anonymous magic-user-id [magic-user-id]... + * + * Anonymous_MustGiveEmail [ on | off ] default = on + * Anonymous_LogEmail [ on | off ] default = on + * Anonymous_VerifyEmail [ on | off ] default = off + * Anonymous_NoUserId [ on | off ] default = off + * Anonymous_Authoritative [ on | off ] default = off + * + * The magic user id is something like 'anonymous', it is NOT case sensitive. + * + * The MustGiveEmail flag can be used to force users to enter something + * in the password field (like an email address). Default is on. + * + * Furthermore the 'NoUserID' flag can be set to allow completely empty + * usernames in as well; this can be is convenient as a single return + * in broken GUIs like W95 is often given by the user. The Default is off. + * + * Dirk.vanGulik@jrc.it; http://ewse.ceo.org; http://me-www.jrc.it/~dirkx + * + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_request.h" + +typedef struct auth_anon { + char *password; + struct auth_anon *next; +} auth_anon; + +typedef struct { + + auth_anon *auth_anon_passwords; + int auth_anon_nouserid; + int auth_anon_logemail; + int auth_anon_verifyemail; + int auth_anon_mustemail; + int auth_anon_authoritative; + +} anon_auth_config_rec; + +static void *create_anon_auth_dir_config(pool *p, char *d) +{ + anon_auth_config_rec *sec = (anon_auth_config_rec *) + ap_pcalloc(p, sizeof(anon_auth_config_rec)); + + if (!sec) + return NULL; /* no memory... */ + + /* just to illustrate the defaults really. */ + sec->auth_anon_passwords = NULL; + + sec->auth_anon_nouserid = 0; + sec->auth_anon_logemail = 1; + sec->auth_anon_verifyemail = 0; + sec->auth_anon_mustemail = 1; + sec->auth_anon_authoritative = 0; + return sec; +} + +static const char *anon_set_passwd_flag(cmd_parms *cmd, + anon_auth_config_rec * sec, int arg) +{ + sec->auth_anon_mustemail = arg; + return NULL; +} + +static const char *anon_set_userid_flag(cmd_parms *cmd, + anon_auth_config_rec * sec, int arg) +{ + sec->auth_anon_nouserid = arg; + return NULL; +} +static const char *anon_set_logemail_flag(cmd_parms *cmd, + anon_auth_config_rec * sec, int arg) +{ + sec->auth_anon_logemail = arg; + return NULL; +} +static const char *anon_set_verifyemail_flag(cmd_parms *cmd, + anon_auth_config_rec * sec, int arg) +{ + sec->auth_anon_verifyemail = arg; + return NULL; +} +static const char *anon_set_authoritative_flag(cmd_parms *cmd, + anon_auth_config_rec * sec, int arg) +{ + sec->auth_anon_authoritative = arg; + return NULL; +} + +static const char *anon_set_string_slots(cmd_parms *cmd, + anon_auth_config_rec * sec, char *arg) +{ + + auth_anon *first; + + if (!(*arg)) + return "Anonymous string cannot be empty, use Anonymous_NoUserId instead"; + + /* squeeze in a record */ + first = sec->auth_anon_passwords; + + if ( + (!(sec->auth_anon_passwords = (auth_anon *) ap_palloc(cmd->pool, sizeof(auth_anon)))) || + (!(sec->auth_anon_passwords->password = arg)) + ) + return "Failed to claim memory for an anonymous password..."; + + /* and repair the next */ + sec->auth_anon_passwords->next = first; + + return NULL; +} + +static const command_rec anon_auth_cmds[] = +{ + {"Anonymous", anon_set_string_slots, NULL, OR_AUTHCFG, ITERATE, + "a space-separated list of user IDs"}, + {"Anonymous_MustGiveEmail", anon_set_passwd_flag, NULL, OR_AUTHCFG, FLAG, + "Limited to 'on' or 'off'"}, + {"Anonymous_NoUserId", anon_set_userid_flag, NULL, OR_AUTHCFG, FLAG, + "Limited to 'on' or 'off'"}, +{"Anonymous_VerifyEmail", anon_set_verifyemail_flag, NULL, OR_AUTHCFG, FLAG, + "Limited to 'on' or 'off'"}, + {"Anonymous_LogEmail", anon_set_logemail_flag, NULL, OR_AUTHCFG, FLAG, + "Limited to 'on' or 'off'"}, + {"Anonymous_Authoritative", anon_set_authoritative_flag, NULL, OR_AUTHCFG, FLAG, + "Limited to 'on' or 'off'"}, + + {NULL} +}; + +module MODULE_VAR_EXPORT anon_auth_module; + +static int anon_authenticate_basic_user(request_rec *r) +{ + anon_auth_config_rec *sec = + (anon_auth_config_rec *) ap_get_module_config(r->per_dir_config, + &anon_auth_module); + conn_rec *c = r->connection; + const char *sent_pw; + int res = DECLINED; + + if ((res = ap_get_basic_auth_pw(r, &sent_pw))) + return res; + + /* Ignore if we are not configured */ + if (!sec->auth_anon_passwords) + return DECLINED; + + /* Do we allow an empty userID and/or is it the magic one + */ + + if ((!(c->user[0])) && (sec->auth_anon_nouserid)) { + res = OK; + } + else { + auth_anon *p = sec->auth_anon_passwords; + res = DECLINED; + while ((res == DECLINED) && (p != NULL)) { + if (!(strcasecmp(c->user, p->password))) + res = OK; + p = p->next; + } + } + if ( + /* username is OK */ + (res == OK) + /* password been filled out ? */ + && ((!sec->auth_anon_mustemail) || strlen(sent_pw)) + /* does the password look like an email address ? */ + && ((!sec->auth_anon_verifyemail) + || ((strpbrk("@", sent_pw) != NULL) + && (strpbrk(".", sent_pw) != NULL)))) { + if (sec->auth_anon_logemail && ap_is_initial_req(r)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r, + "Anonymous: Passwd <%s> Accepted", + sent_pw ? sent_pw : "\'none\'"); + } + return OK; + } + else { + if (sec->auth_anon_authoritative) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Anonymous: Authoritative, Passwd <%s> not accepted", + sent_pw ? sent_pw : "\'none\'"); + return AUTH_REQUIRED; + } + /* Drop out the bottom to return DECLINED */ + } + + return DECLINED; +} + +static int check_anon_access(request_rec *r) +{ +#ifdef NOTYET + conn_rec *c = r->connection; + anon_auth_config_rec *sec = + (anon_auth_config_rec *) ap_get_module_config(r->per_dir_config, + &anon_auth_module); + + if (!sec->auth_anon) + return DECLINED; + + if (strcasecmp(r->connection->user, sec->auth_anon)) + return DECLINED; + + return OK; +#endif + return DECLINED; +} + + +module MODULE_VAR_EXPORT anon_auth_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_anon_auth_dir_config, /* dir config creater */ + NULL, /* dir merger ensure strictness */ + NULL, /* server config */ + NULL, /* merge server config */ + anon_auth_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + anon_authenticate_basic_user, /* check_user_id */ + check_anon_access, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_auth_db.c b/APACHE_1_3_42/src/modules/standard/mod_auth_db.c new file mode 100644 index 0000000000..246064bbee --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_auth_db.c @@ -0,0 +1,321 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_auth_db: authentication + * + * Original work by Rob McCool & Brian Behlendorf. + * + * Adapted to Apache by rst (mod_auth_dbm) + * + * Adapted for Berkeley DB by Andrew Cohen + * + * mod_auth_db was based on mod_auth_dbm. + * + * Warning, this is not a drop in replacement for mod_auth_dbm, + * for people wanting to switch from dbm to Berkeley DB. + * It requires the use of AuthDBUserFile and AuthDBGroupFile + * instead of AuthDBMUserFile AuthDBMGroupFile + * + * Also, in the configuration file you need to specify + * db_auth_module rather than dbm_auth_module + * + * On some BSD systems (e.g. FreeBSD and NetBSD) dbm is automatically + * mapped to Berkeley DB. You can use either mod_auth_dbm or + * mod_auth_db. The latter makes it more obvious that it's Berkeley. + * On other platforms where you want to use the DB library you + * usually have to install it first. See http://www.sleepycat.com/ + * for the distribution. The interface this module uses is the + * one from DB version 1.85 and 1.86, but DB version 2.x + * can also be used when compatibility mode is enabled. + * + * dirkx - Added Authoritative control to allow passing on to lower + * modules if and only if the user-id is not known to this + * module. A known user with a faulty or absent password still + * causes an AuthRequired. The default is 'Authoritative', i.e. + * no control is passed along. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" +#include <db.h> + +#if defined(DB_VERSION_MAJOR) +#if (DB_VERSION_MAJOR == 2) +#define DB2 +#endif +#if (DB_VERSION_MAJOR == 3) +#define DB3 +#endif +#if (DB_VERSION_MAJOR == 4) +#define DB4 +#endif +#endif + +typedef struct { + + char *auth_dbpwfile; + char *auth_dbgrpfile; + int auth_dbauthoritative; +} db_auth_config_rec; + +static void *create_db_auth_dir_config(pool *p, char *d) +{ + db_auth_config_rec *sec + = (db_auth_config_rec *) ap_pcalloc(p, sizeof(db_auth_config_rec)); + sec->auth_dbpwfile = NULL; + sec->auth_dbgrpfile = NULL; + sec->auth_dbauthoritative = 1; /* fortress is secure by default */ + return sec; +} + +static const char *set_db_slot(cmd_parms *cmd, void *offset, char *f, char *t) +{ + if (!t || strcmp(t, "db")) + return DECLINE_CMD; + + return ap_set_file_slot(cmd, offset, f); +} + +static const command_rec db_auth_cmds[] = +{ + {"AuthDBUserFile", ap_set_file_slot, + (void *) XtOffsetOf(db_auth_config_rec, auth_dbpwfile), + OR_AUTHCFG, TAKE1, NULL}, + {"AuthDBGroupFile", ap_set_file_slot, + (void *) XtOffsetOf(db_auth_config_rec, auth_dbgrpfile), + OR_AUTHCFG, TAKE1, NULL}, + {"AuthUserFile", set_db_slot, + (void *) XtOffsetOf(db_auth_config_rec, auth_dbpwfile), + OR_AUTHCFG, TAKE12, NULL}, + {"AuthGroupFile", set_db_slot, + (void *) XtOffsetOf(db_auth_config_rec, auth_dbgrpfile), + OR_AUTHCFG, TAKE12, NULL}, + {"AuthDBAuthoritative", ap_set_flag_slot, + (void *) XtOffsetOf(db_auth_config_rec, auth_dbauthoritative), + OR_AUTHCFG, FLAG, + "Set to 'no' to allow access control to be passed along to lower modules if the userID is not known to this module"}, + {NULL} +}; + +module db_auth_module; + +static char *get_db_pw(request_rec *r, char *user, const char *auth_dbpwfile) +{ + DB *f; + DBT d, q; + char *pw = NULL; + + memset(&d, 0, sizeof(d)); + memset(&q, 0, sizeof(q)); + + q.data = user; + q.size = strlen(q.data); + +#if defined(DB3) || defined(DB4) + if ( db_create(&f, NULL, 0) != 0 +#if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0 + || f->open(f, NULL, auth_dbpwfile, NULL, DB_HASH, DB_RDONLY, 0664) != 0) { +#else + || f->open(f, auth_dbpwfile, NULL, DB_HASH, DB_RDONLY, 0664) != 0) { +#endif +#elif defined(DB2) + if (db_open(auth_dbpwfile, DB_HASH, DB_RDONLY, 0664, NULL, NULL, &f) != 0) { +#else + if (!(f = dbopen(auth_dbpwfile, O_RDONLY, 0664, DB_HASH, NULL))) { +#endif + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "could not open db auth file: %s", auth_dbpwfile); + return NULL; + } + +#if defined(DB2) || defined(DB3) || defined(DB4) + if (!((f->get) (f, NULL, &q, &d, 0))) { +#else + if (!((f->get) (f, &q, &d, 0))) { +#endif + pw = ap_palloc(r->pool, d.size + 1); + strncpy(pw, d.data, d.size); + pw[d.size] = '\0'; /* Terminate the string */ + } + +#if defined(DB2) || defined(DB3) || defined(DB4) + (f->close) (f, 0); +#else + (f->close) (f); +#endif + return pw; +} + +/* We do something strange with the group file. If the group file + * contains any : we assume the format is + * key=username value=":"groupname [":"anything here is ignored] + * otherwise we now (0.8.14+) assume that the format is + * key=username value=groupname + * The first allows the password and group files to be the same + * physical DB file; key=username value=password":"groupname[":"anything] + * + * mark@telescope.org, 22Sep95 + */ + +static char *get_db_grp(request_rec *r, char *user, const char *auth_dbgrpfile) +{ + char *grp_data = get_db_pw(r, user, auth_dbgrpfile); + char *grp_colon; + char *grp_colon2; + + if (grp_data == NULL) + return NULL; + + if ((grp_colon = strchr(grp_data, ':')) != NULL) { + grp_colon2 = strchr(++grp_colon, ':'); + if (grp_colon2) + *grp_colon2 = '\0'; + return grp_colon; + } + return grp_data; +} + +static int db_authenticate_basic_user(request_rec *r) +{ + db_auth_config_rec *sec = + (db_auth_config_rec *) ap_get_module_config(r->per_dir_config, + &db_auth_module); + conn_rec *c = r->connection; + const char *sent_pw; + char *real_pw, *colon_pw; + char *invalid_pw; + int res; + + if ((res = ap_get_basic_auth_pw(r, &sent_pw))) + return res; + + if (!sec->auth_dbpwfile) + return DECLINED; + + if (!(real_pw = get_db_pw(r, c->user, sec->auth_dbpwfile))) { + if (!(sec->auth_dbauthoritative)) + return DECLINED; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "DB user %s not found: %s", c->user, r->filename); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + /* Password is up to first : if exists */ + colon_pw = strchr(real_pw, ':'); + if (colon_pw) { + *colon_pw = '\0'; + } + invalid_pw = ap_validate_password(sent_pw, real_pw); + if (invalid_pw != NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "DB user %s: authentication failure for \"%s\": %s", + c->user, r->uri, invalid_pw); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + return OK; +} + +/* Checking ID */ + +static int db_check_auth(request_rec *r) +{ + db_auth_config_rec *sec = + (db_auth_config_rec *) ap_get_module_config(r->per_dir_config, + &db_auth_module); + char *user = r->connection->user; + int m = r->method_number; + + const array_header *reqs_arr = ap_requires(r); + require_line *reqs = reqs_arr ? (require_line *) reqs_arr->elts : NULL; + + register int x; + const char *t; + char *w; + + if (!sec->auth_dbgrpfile) + return DECLINED; + if (!reqs_arr) + return DECLINED; + + for (x = 0; x < reqs_arr->nelts; x++) { + + if (!(reqs[x].method_mask & (1 << m))) + continue; + + t = reqs[x].requirement; + w = ap_getword_white(r->pool, &t); + + if (!strcmp(w, "group") && sec->auth_dbgrpfile) { + const char *orig_groups, *groups; + char *v; + + if (!(groups = get_db_grp(r, user, sec->auth_dbgrpfile))) { + if (!(sec->auth_dbauthoritative)) + return DECLINED; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user %s not in DB group file %s: %s", + user, sec->auth_dbgrpfile, r->filename); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + orig_groups = groups; + while (t[0]) { + w = ap_getword_white(r->pool, &t); + groups = orig_groups; + while (groups[0]) { + v = ap_getword(r->pool, &groups, ','); + if (!strcmp(v, w)) + return OK; + } + } + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user %s not in right group: %s", user, r->filename); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + } + + return DECLINED; +} + + +module db_auth_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_db_auth_dir_config, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + db_auth_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + db_authenticate_basic_user, /* check_user_id */ + db_check_auth, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_auth_db.module b/APACHE_1_3_42/src/modules/standard/mod_auth_db.module new file mode 100644 index 0000000000..d982f021b1 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_auth_db.module @@ -0,0 +1,47 @@ +Name: db_auth_module +ConfigStart + DB_VERSION='' + DB_LIB='' + if ./helpers/TestCompile func db_create; then + DB_VERSION='Berkeley-DB/3.x' + elif ./helpers/TestCompile lib db db_create; then + DB_VERSION='Berkeley-DB/3.x' + DB_LIB='-ldb' + elif ./helpers/TestCompile func db_open; then + DB_VERSION='Berkeley-DB/2.x' + elif ./helpers/TestCompile lib db db_open; then + DB_VERSION='Berkeley-DB/2.x' + DB_LIB='-ldb' + elif ./helpers/TestCompile lib db2 db_open; then + DB_VERSION='Berkeley-DB/2.x' + DB_LIB='-ldb2' + elif ./helpers/TestCompile func dbopen; then + DB_VERSION='Berkeley-DB/1.x' + elif ./helpers/TestCompile lib db dbopen; then + DB_VERSION='Berkeley-DB/1.x' + DB_LIB='-ldb' + elif ./helpers/TestCompile lib db1 dbopen; then + DB_VERSION='Berkeley-DB/1.x' + DB_LIB='-ldb1' + elif TCADDINCL='#include <db.h>' INCLUDES1="$INCLUDES1 -I/usr/include/db1" TLIB="-ldb1" \ + helpers/TestCompile func dbm_open; then + # For Red Hat 7 + DB_VERSION='Berkeley-DB/1.x' + DB_LIB='-ldb1' + CFLAGS="$CFLAGS -I/usr/include/db1" + fi + if [ ".$DB_VERSION" != . ]; then + if [ ".$DB_LIB" != . ]; then + LIBS="$LIBS $DB_LIB" + echo " using $DB_VERSION for mod_auth_db ($DB_LIB)" + else + echo " using $DB_VERSION for mod_auth_db (-lc)" + fi + else + echo "Error: None of Berkeley-DB 1.x, 2.x or 3.x libraries found." + echo " Either disable mod_auth_db or provide us with the paths" + echo " to the Berkeley-DB include and library files." + echo " (Hint: INCLUDES, LDFLAGS, LIBS)" + exit 1 + fi +ConfigEnd diff --git a/APACHE_1_3_42/src/modules/standard/mod_auth_dbm.c b/APACHE_1_3_42/src/modules/standard/mod_auth_dbm.c new file mode 100644 index 0000000000..8e443d5fca --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_auth_dbm.c @@ -0,0 +1,296 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * http_auth: authentication + * + * Rob McCool & Brian Behlendorf. + * + * Adapted to Apache by rst. + * + * dirkx - Added Authoritative control to allow passing on to lower + * modules if and only if the user-id is not known to this + * module. A known user with a faulty or absent password still + * causes an AuthRequired. The default is 'Authoritative', i.e. + * no control is passed along. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" +#if (defined(WIN32) || defined(NETWARE)) +#include <sdbm.h> +#define dbm_open sdbm_open +#define dbm_fetch sdbm_fetch +#define dbm_close sdbm_close +#else +#include <ndbm.h> +#endif + +/* + * Module definition information - the part between the -START and -END + * lines below is used by Configure. This could be stored in a separate + * instead. + * + * MODULE-DEFINITION-START + * Name: dbm_auth_module + * ConfigStart + . ./helpers/find-dbm-lib + * ConfigEnd + * MODULE-DEFINITION-END + */ + +typedef struct { + + char *auth_dbmpwfile; + char *auth_dbmgrpfile; + int auth_dbmauthoritative; + +} dbm_auth_config_rec; + +static void *create_dbm_auth_dir_config(pool *p, char *d) +{ + dbm_auth_config_rec *sec + = (dbm_auth_config_rec *) ap_pcalloc(p, sizeof(dbm_auth_config_rec)); + + sec->auth_dbmpwfile = NULL; + sec->auth_dbmgrpfile = NULL; + sec->auth_dbmauthoritative = 1; /* fortress is secure by default */ + + return sec; +} + +static const char *set_dbm_slot(cmd_parms *cmd, void *offset, char *f, char *t) +{ + if (!t || strcmp(t, "dbm")) + return DECLINE_CMD; + + return ap_set_file_slot(cmd, offset, f); +} + +static const command_rec dbm_auth_cmds[] = +{ + {"AuthDBMUserFile", ap_set_file_slot, + (void *) XtOffsetOf(dbm_auth_config_rec, auth_dbmpwfile), + OR_AUTHCFG, TAKE1, NULL}, + {"AuthDBMGroupFile", ap_set_file_slot, + (void *) XtOffsetOf(dbm_auth_config_rec, auth_dbmgrpfile), + OR_AUTHCFG, TAKE1, NULL}, + {"AuthUserFile", set_dbm_slot, + (void *) XtOffsetOf(dbm_auth_config_rec, auth_dbmpwfile), + OR_AUTHCFG, TAKE12, NULL}, + {"AuthGroupFile", set_dbm_slot, + (void *) XtOffsetOf(dbm_auth_config_rec, auth_dbmgrpfile), + OR_AUTHCFG, TAKE12, NULL}, + {"AuthDBMAuthoritative", ap_set_flag_slot, + (void *) XtOffsetOf(dbm_auth_config_rec, auth_dbmauthoritative), + OR_AUTHCFG, FLAG, "Set to 'no' to allow access control to be passed along to lower modules, if the UserID is not known in this module"}, + {NULL} +}; + +module MODULE_VAR_EXPORT dbm_auth_module; + +static char *get_dbm_pw(request_rec *r, char *user, char *auth_dbmpwfile) +{ + DBM *f; + datum d, q; + char *pw = NULL; + + q.dptr = user; +#ifndef NETSCAPE_DBM_COMPAT + q.dsize = strlen(q.dptr); +#else + q.dsize = strlen(q.dptr) + 1; +#endif + + + if (!(f = dbm_open(auth_dbmpwfile, O_RDONLY, 0664))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "could not open dbm auth file: %s", auth_dbmpwfile); + return NULL; + } + + d = dbm_fetch(f, q); + + if (d.dptr) { + pw = ap_palloc(r->pool, d.dsize + 1); + strncpy(pw, d.dptr, d.dsize); + pw[d.dsize] = '\0'; /* Terminate the string */ + } + + dbm_close(f); + return pw; +} + +/* We do something strange with the group file. If the group file + * contains any : we assume the format is + * key=username value=":"groupname [":"anything here is ignored] + * otherwise we now (0.8.14+) assume that the format is + * key=username value=groupname + * The first allows the password and group files to be the same + * physical DBM file; key=username value=password":"groupname[":"anything] + * + * mark@telescope.org, 22Sep95 + */ + +static char *get_dbm_grp(request_rec *r, char *user, char *auth_dbmgrpfile) +{ + char *grp_data = get_dbm_pw(r, user, auth_dbmgrpfile); + char *grp_colon; + char *grp_colon2; + + if (grp_data == NULL) + return NULL; + + if ((grp_colon = strchr(grp_data, ':')) != NULL) { + grp_colon2 = strchr(++grp_colon, ':'); + if (grp_colon2) + *grp_colon2 = '\0'; + return grp_colon; + } + return grp_data; +} + +static int dbm_authenticate_basic_user(request_rec *r) +{ + dbm_auth_config_rec *sec = + (dbm_auth_config_rec *) ap_get_module_config(r->per_dir_config, + &dbm_auth_module); + conn_rec *c = r->connection; + const char *sent_pw; + char *real_pw, *colon_pw; + char *invalid_pw; + int res; + + if ((res = ap_get_basic_auth_pw(r, &sent_pw))) + return res; + + if (!sec->auth_dbmpwfile) + return DECLINED; + + if (!(real_pw = get_dbm_pw(r, c->user, sec->auth_dbmpwfile))) { + if (!(sec->auth_dbmauthoritative)) + return DECLINED; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "DBM user %s not found: %s", c->user, r->filename); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + /* Password is up to first : if exists */ + colon_pw = strchr(real_pw, ':'); + if (colon_pw) { + *colon_pw = '\0'; + } + invalid_pw = ap_validate_password(sent_pw, real_pw); + if (invalid_pw != NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "DBM user %s: authentication failure for \"%s\": %s", + c->user, r->uri, invalid_pw); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + return OK; +} + +/* Checking ID */ + +static int dbm_check_auth(request_rec *r) +{ + dbm_auth_config_rec *sec = + (dbm_auth_config_rec *) ap_get_module_config(r->per_dir_config, + &dbm_auth_module); + char *user = r->connection->user; + int m = r->method_number; + + const array_header *reqs_arr = ap_requires(r); + require_line *reqs = reqs_arr ? (require_line *) reqs_arr->elts : NULL; + + register int x; + const char *t; + char *w; + + if (!sec->auth_dbmgrpfile) + return DECLINED; + if (!reqs_arr) + return DECLINED; + + for (x = 0; x < reqs_arr->nelts; x++) { + + if (!(reqs[x].method_mask & (1 << m))) + continue; + + t = reqs[x].requirement; + w = ap_getword_white(r->pool, &t); + + if (!strcmp(w, "group") && sec->auth_dbmgrpfile) { + const char *orig_groups, *groups; + char *v; + + if (!(groups = get_dbm_grp(r, user, sec->auth_dbmgrpfile))) { + if (!(sec->auth_dbmauthoritative)) + return DECLINED; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user %s not in DBM group file %s: %s", + user, sec->auth_dbmgrpfile, r->filename); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + orig_groups = groups; + while (t[0]) { + w = ap_getword_white(r->pool, &t); + groups = orig_groups; + while (groups[0]) { + v = ap_getword(r->pool, &groups, ','); + if (!strcmp(v, w)) + return OK; + } + } + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user %s not in right group: %s", + user, r->filename); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + } + + return DECLINED; +} + + +module MODULE_VAR_EXPORT dbm_auth_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_dbm_auth_dir_config, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + dbm_auth_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + dbm_authenticate_basic_user, /* check_user_id */ + dbm_check_auth, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_autoindex.c b/APACHE_1_3_42/src/modules/standard/mod_autoindex.c new file mode 100644 index 0000000000..4d9b985c80 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_autoindex.c @@ -0,0 +1,1817 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_autoindex.c: Handles the on-the-fly html index generation + * + * Rob McCool + * 3/23/93 + * + * Adapted to Apache by rst. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_request.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#include "fnmatch.h" + +module MODULE_VAR_EXPORT autoindex_module; + +/**************************************************************** + * + * Handling configuration directives... + */ + +#define HRULE 1 +#define NO_HRULE 0 +#define FRONT_MATTER 1 +#define END_MATTER 0 + +#define FANCY_INDEXING 1 /* Indexing options */ +#define ICONS_ARE_LINKS 2 +#define SCAN_HTML_TITLES 4 +#define SUPPRESS_LAST_MOD 8 +#define SUPPRESS_SIZE 16 +#define SUPPRESS_DESC 32 +#define SUPPRESS_PREAMBLE 64 +#define SUPPRESS_COLSORT 128 +#define NO_OPTIONS 256 +#define FOLDERS_FIRST 512 +#define TRACK_MODIFIED 1024 +#define SORT_NOCASE 2048 + +#define K_PAD 1 +#define K_NOPAD 0 + +#define K_NOADJUST 0 +#define K_ADJUST 1 +#define K_UNSET 2 + +/* + * Define keys for sorting. + */ +#define K_NAME 'N' /* Sort by file name (default) */ +#define K_LAST_MOD 'M' /* Last modification date */ +#define K_SIZE 'S' /* Size (absolute, not as displayed) */ +#define K_DESC 'D' /* Description */ + +#define D_ASCENDING 'A' +#define D_DESCENDING 'D' + +/* + * These are the dimensions of the default icons supplied with Apache. + */ +#define DEFAULT_ICON_WIDTH 20 +#define DEFAULT_ICON_HEIGHT 22 + +/* + * Other default dimensions. + */ +#define DEFAULT_NAME_WIDTH 23 +#define DEFAULT_DESC_WIDTH 23 + +struct item { + char *type; + char *apply_to; + char *apply_path; + char *data; +}; + +typedef struct ai_desc_t { + char *pattern; + char *description; + int full_path; + int wildcards; +} ai_desc_t; + +typedef struct autoindex_config_struct { + char *default_icon; + int opts; + int incremented_opts; + int decremented_opts; + int name_width; + int name_adjust; + int desc_width; + int desc_adjust; + int icon_width; + int icon_height; + char *default_order; + + array_header *icon_list; + array_header *alt_list; + array_header *desc_list; + array_header *ign_list; + array_header *hdr_list; + array_header *rdme_list; + +} autoindex_config_rec; + +static char c_by_encoding, c_by_type, c_by_path; + +#define BY_ENCODING &c_by_encoding +#define BY_TYPE &c_by_type +#define BY_PATH &c_by_path + +/* + * Return true if the specified string refers to the parent directory (i.e., + * matches ".." or "../"). Hopefully this one call is significantly less + * expensive than multiple strcmp() calls. + */ +static ap_inline int is_parent(const char *name) +{ + /* + * Now, IFF the first two bytes are dots, and the third byte is either + * EOS (\0) or a slash followed by EOS, we have a match. + */ + if (((name[0] == '.') && (name[1] == '.')) + && ((name[2] == '\0') + || ((name[2] == '/') && (name[3] == '\0')))) { + return 1; + } + return 0; +} + +/* + * This routine puts the standard HTML header at the top of the index page. + * We include the DOCTYPE because we may be using features therefrom (i.e., + * HEIGHT and WIDTH attributes on the icons if we're FancyIndexing). + */ +static void emit_preamble(request_rec *r, char *title) +{ + ap_rvputs(r, DOCTYPE_HTML_3_2, + "<HTML>\n <HEAD>\n <TITLE>Index of ", title, + "</TITLE>\n </HEAD>\n <BODY>\n", NULL); +} + +static void push_item(array_header *arr, char *type, char *to, char *path, + char *data) +{ + struct item *p = (struct item *) ap_push_array(arr); + + if (!to) { + to = ""; + } + if (!path) { + path = ""; + } + + p->type = type; + p->data = data ? ap_pstrdup(arr->pool, data) : NULL; + p->apply_path = ap_pstrcat(arr->pool, path, "*", NULL); + + if ((type == BY_PATH) && (!ap_is_matchexp(to))) { + p->apply_to = ap_pstrcat(arr->pool, "*", to, NULL); + } + else if (to) { + p->apply_to = ap_pstrdup(arr->pool, to); + } + else { + p->apply_to = NULL; + } +} + +static const char *add_alt(cmd_parms *cmd, void *d, char *alt, char *to) +{ + if (cmd->info == BY_PATH) { + if (!strcmp(to, "**DIRECTORY**")) { + to = "^^DIRECTORY^^"; + } + } + if (cmd->info == BY_ENCODING) { + ap_str_tolower(to); + } + + push_item(((autoindex_config_rec *) d)->alt_list, cmd->info, to, + cmd->path, alt); + return NULL; +} + +static const char *add_icon(cmd_parms *cmd, void *d, char *icon, char *to) +{ + char *iconbak = ap_pstrdup(cmd->pool, icon); + + if (icon[0] == '(') { + char *alt; + char *cl = strchr(iconbak, ')'); + + if (cl == NULL) { + return "missing closing paren"; + } + alt = ap_getword_nc(cmd->pool, &iconbak, ','); + *cl = '\0'; /* Lose closing paren */ + add_alt(cmd, d, &alt[1], to); + } + if (cmd->info == BY_PATH) { + if (!strcmp(to, "**DIRECTORY**")) { + to = "^^DIRECTORY^^"; + } + } + if (cmd->info == BY_ENCODING) { + ap_str_tolower(to); + } + + push_item(((autoindex_config_rec *) d)->icon_list, cmd->info, to, + cmd->path, iconbak); + return NULL; +} + +/* + * Add description text for a filename pattern. If the pattern has + * wildcards already (or we need to add them), add leading and + * trailing wildcards to it to ensure substring processing. If the + * pattern contains a '/' anywhere, force wildcard matching mode, + * add a slash to the prefix so that "bar/bletch" won't be matched + * by "foobar/bletch", and make a note that there's a delimiter; + * the matching routine simplifies to just the actual filename + * whenever it can. This allows definitions in parent directories + * to be made for files in subordinate ones using relative paths. + */ + +/* + * Absent a strcasestr() function, we have to force wildcards on + * systems for which "AAA" and "aaa" mean the same file. + */ +#ifdef CASE_BLIND_FILESYSTEM +#define WILDCARDS_REQUIRED 1 +#else +#define WILDCARDS_REQUIRED 0 +#endif + +static const char *add_desc(cmd_parms *cmd, void *d, char *desc, char *to) +{ + autoindex_config_rec *dcfg = (autoindex_config_rec *) d; + ai_desc_t *desc_entry; + char *prefix = ""; + + desc_entry = (ai_desc_t *) ap_push_array(dcfg->desc_list); + desc_entry->full_path = (strchr(to, '/') == NULL) ? 0 : 1; + desc_entry->wildcards = (WILDCARDS_REQUIRED + || desc_entry->full_path + || ap_is_fnmatch(to)); + if (desc_entry->wildcards) { + prefix = desc_entry->full_path ? "*/" : "*"; + desc_entry->pattern = ap_pstrcat(dcfg->desc_list->pool, + prefix, to, "*", NULL); + } + else { + desc_entry->pattern = ap_pstrdup(dcfg->desc_list->pool, to); + } + desc_entry->description = ap_pstrdup(dcfg->desc_list->pool, desc); + return NULL; +} + +static const char *add_ignore(cmd_parms *cmd, void *d, char *ext) +{ + push_item(((autoindex_config_rec *) d)->ign_list, 0, ext, cmd->path, NULL); + return NULL; +} + +static const char *add_header(cmd_parms *cmd, void *d, char *name) +{ + push_item(((autoindex_config_rec *) d)->hdr_list, 0, NULL, cmd->path, + name); + return NULL; +} + +static const char *add_readme(cmd_parms *cmd, void *d, char *name) +{ + push_item(((autoindex_config_rec *) d)->rdme_list, 0, NULL, cmd->path, + name); + return NULL; +} + +/* A legacy directive, FancyIndexing is superseded by the IndexOptions + * keyword. But for compatibility.. + */ +static const char *fancy_indexing(cmd_parms *cmd, void *d, int arg) +{ + int curopts; + int newopts; + autoindex_config_rec *cfg; + + cfg = (autoindex_config_rec *) d; + curopts = cfg->opts; + if (curopts & NO_OPTIONS) { + return "FancyIndexing directive conflicts with existing " + "IndexOptions None"; + } + newopts = (arg ? (curopts | FANCY_INDEXING) : (curopts & ~FANCY_INDEXING)); + cfg->opts = newopts; + return NULL; +} + +static const char *add_opts(cmd_parms *cmd, void *d, const char *optstr) +{ + char *w; + int opts; + int opts_add; + int opts_remove; + char action; + autoindex_config_rec *d_cfg = (autoindex_config_rec *) d; + + opts = d_cfg->opts; + opts_add = d_cfg->incremented_opts; + opts_remove = d_cfg->decremented_opts; + while (optstr[0]) { + int option = 0; + + w = ap_getword_conf(cmd->pool, &optstr); + if ((*w == '+') || (*w == '-')) { + action = *(w++); + } + else { + action = '\0'; + } + if (!strcasecmp(w, "FancyIndexing")) { + option = FANCY_INDEXING; + } + else if (!strcasecmp(w, "IconsAreLinks")) { + option = ICONS_ARE_LINKS; + } + else if (!strcasecmp(w, "ScanHTMLTitles")) { + option = SCAN_HTML_TITLES; + } + else if (!strcasecmp(w, "SuppressLastModified")) { + option = SUPPRESS_LAST_MOD; + } + else if (!strcasecmp(w, "SuppressSize")) { + option = SUPPRESS_SIZE; + } + else if (!strcasecmp(w, "SuppressDescription")) { + option = SUPPRESS_DESC; + } + else if (!strcasecmp(w, "SuppressHTMLPreamble")) { + option = SUPPRESS_PREAMBLE; + } + else if (!strcasecmp(w, "SuppressColumnSorting")) { + option = SUPPRESS_COLSORT; + } + else if (!strcasecmp(w, "FoldersFirst")) { + option = FOLDERS_FIRST; + } + else if (!strcasecmp(w, "TrackModified")) { + option = TRACK_MODIFIED; + } + else if (!strcasecmp(w, "IgnoreCase")) { + option = SORT_NOCASE; + } + else if (!strcasecmp(w, "None")) { + if (action != '\0') { + return "Cannot combine '+' or '-' with 'None' keyword"; + } + opts = NO_OPTIONS; + opts_add = 0; + opts_remove = 0; + } + else if (!strcasecmp(w, "IconWidth")) { + if (action != '-') { + d_cfg->icon_width = DEFAULT_ICON_WIDTH; + } + else { + d_cfg->icon_width = 0; + } + } + else if (!strncasecmp(w, "IconWidth=", 10)) { + if (action == '-') { + return "Cannot combine '-' with IconWidth=n"; + } + d_cfg->icon_width = atoi(&w[10]); + } + else if (!strcasecmp(w, "IconHeight")) { + if (action != '-') { + d_cfg->icon_height = DEFAULT_ICON_HEIGHT; + } + else { + d_cfg->icon_height = 0; + } + } + else if (!strncasecmp(w, "IconHeight=", 11)) { + if (action == '-') { + return "Cannot combine '-' with IconHeight=n"; + } + d_cfg->icon_height = atoi(&w[11]); + } + else if (!strcasecmp(w, "NameWidth")) { + if (action != '-') { + return "NameWidth with no value may only appear as " + "'-NameWidth'"; + } + d_cfg->name_width = DEFAULT_NAME_WIDTH; + d_cfg->name_adjust = K_NOADJUST; + } + else if (!strncasecmp(w, "NameWidth=", 10)) { + if (action == '-') { + return "Cannot combine '-' with NameWidth=n"; + } + if (w[10] == '*') { + d_cfg->name_adjust = K_ADJUST; + } + else { + int width = atoi(&w[10]); + + if (width < 5) { + return "NameWidth value must be greater than 5"; + } + d_cfg->name_width = width; + d_cfg->name_adjust = K_NOADJUST; + } + } + else if (!strcasecmp(w, "DescriptionWidth")) { + if (action != '-') { + return "DescriptionWidth with no value may only appear as " + "'-DescriptionWidth'"; + } + d_cfg->desc_width = DEFAULT_DESC_WIDTH; + d_cfg->desc_adjust = K_NOADJUST; + } + else if (!strncasecmp(w, "DescriptionWidth=", 17)) { + if (action == '-') { + return "Cannot combine '-' with DescriptionWidth=n"; + } + if (w[17] == '*') { + d_cfg->desc_adjust = K_ADJUST; + } + else { + int width = atoi(&w[17]); + + if (width < 12) { + return "DescriptionWidth value must be greater than 12"; + } + d_cfg->desc_width = width; + d_cfg->desc_adjust = K_NOADJUST; + } + } + else { + return "Invalid directory indexing option"; + } + if (action == '\0') { + opts |= option; + opts_add = 0; + opts_remove = 0; + } + else if (action == '+') { + opts_add |= option; + opts_remove &= ~option; + } + else { + opts_remove |= option; + opts_add &= ~option; + } + } + if ((opts & NO_OPTIONS) && (opts & ~NO_OPTIONS)) { + return "Cannot combine other IndexOptions keywords with 'None'"; + } + d_cfg->incremented_opts = opts_add; + d_cfg->decremented_opts = opts_remove; + d_cfg->opts = opts; + return NULL; +} + +static const char *set_default_order(cmd_parms *cmd, void *m, char *direction, + char *key) +{ + char temp[4]; + autoindex_config_rec *d_cfg = (autoindex_config_rec *) m; + + ap_cpystrn(temp, "k=d", sizeof(temp)); + if (!strcasecmp(direction, "Ascending")) { + temp[2] = D_ASCENDING; + } + else if (!strcasecmp(direction, "Descending")) { + temp[2] = D_DESCENDING; + } + else { + return "First keyword must be 'Ascending' or 'Descending'"; + } + + if (!strcasecmp(key, "Name")) { + temp[0] = K_NAME; + } + else if (!strcasecmp(key, "Date")) { + temp[0] = K_LAST_MOD; + } + else if (!strcasecmp(key, "Size")) { + temp[0] = K_SIZE; + } + else if (!strcasecmp(key, "Description")) { + temp[0] = K_DESC; + } + else { + return "Second keyword must be 'Name', 'Date', 'Size', or " + "'Description'"; + } + + if (d_cfg->default_order == NULL) { + d_cfg->default_order = ap_palloc(cmd->pool, 4); + d_cfg->default_order[3] = '\0'; + } + ap_cpystrn(d_cfg->default_order, temp, sizeof(temp)); + return NULL; +} + +#define DIR_CMD_PERMS OR_INDEXES + +static const command_rec autoindex_cmds[] = +{ + {"AddIcon", add_icon, BY_PATH, DIR_CMD_PERMS, ITERATE2, + "an icon URL followed by one or more filenames"}, + {"AddIconByType", add_icon, BY_TYPE, DIR_CMD_PERMS, ITERATE2, + "an icon URL followed by one or more MIME types"}, + {"AddIconByEncoding", add_icon, BY_ENCODING, DIR_CMD_PERMS, ITERATE2, + "an icon URL followed by one or more content encodings"}, + {"AddAlt", add_alt, BY_PATH, DIR_CMD_PERMS, ITERATE2, + "alternate descriptive text followed by one or more filenames"}, + {"AddAltByType", add_alt, BY_TYPE, DIR_CMD_PERMS, ITERATE2, + "alternate descriptive text followed by one or more MIME types"}, + {"AddAltByEncoding", add_alt, BY_ENCODING, DIR_CMD_PERMS, ITERATE2, + "alternate descriptive text followed by one or more content encodings"}, + {"IndexOptions", add_opts, NULL, DIR_CMD_PERMS, RAW_ARGS, + "one or more index options"}, + {"IndexOrderDefault", set_default_order, NULL, DIR_CMD_PERMS, TAKE2, + "{Ascending,Descending} {Name,Size,Description,Date}"}, + {"IndexIgnore", add_ignore, NULL, DIR_CMD_PERMS, ITERATE, + "one or more file extensions"}, + {"AddDescription", add_desc, BY_PATH, DIR_CMD_PERMS, ITERATE2, + "Descriptive text followed by one or more filenames"}, + {"HeaderName", add_header, NULL, DIR_CMD_PERMS, TAKE1, "a filename"}, + {"ReadmeName", add_readme, NULL, DIR_CMD_PERMS, TAKE1, "a filename"}, + {"FancyIndexing", fancy_indexing, NULL, DIR_CMD_PERMS, FLAG, + "Limited to 'on' or 'off' (superseded by IndexOptions FancyIndexing)"}, + {"DefaultIcon", ap_set_string_slot, + (void *) XtOffsetOf(autoindex_config_rec, default_icon), + DIR_CMD_PERMS, TAKE1, "an icon URL"}, + {NULL} +}; + +static void *create_autoindex_config(pool *p, char *dummy) +{ + autoindex_config_rec *new = + (autoindex_config_rec *) ap_pcalloc(p, sizeof(autoindex_config_rec)); + + new->icon_width = 0; + new->icon_height = 0; + new->name_width = DEFAULT_NAME_WIDTH; + new->name_adjust = K_UNSET; + new->desc_width = DEFAULT_DESC_WIDTH; + new->desc_adjust = K_UNSET; + new->icon_list = ap_make_array(p, 4, sizeof(struct item)); + new->alt_list = ap_make_array(p, 4, sizeof(struct item)); + new->desc_list = ap_make_array(p, 4, sizeof(ai_desc_t)); + new->ign_list = ap_make_array(p, 4, sizeof(struct item)); + new->hdr_list = ap_make_array(p, 4, sizeof(struct item)); + new->rdme_list = ap_make_array(p, 4, sizeof(struct item)); + new->opts = 0; + new->incremented_opts = 0; + new->decremented_opts = 0; + new->default_order = NULL; + + return (void *) new; +} + +static void *merge_autoindex_configs(pool *p, void *basev, void *addv) +{ + autoindex_config_rec *new; + autoindex_config_rec *base = (autoindex_config_rec *) basev; + autoindex_config_rec *add = (autoindex_config_rec *) addv; + + new = (autoindex_config_rec *) ap_pcalloc(p, sizeof(autoindex_config_rec)); + new->default_icon = add->default_icon ? add->default_icon + : base->default_icon; + new->icon_height = add->icon_height ? add->icon_height : base->icon_height; + new->icon_width = add->icon_width ? add->icon_width : base->icon_width; + + new->alt_list = ap_append_arrays(p, add->alt_list, base->alt_list); + new->ign_list = ap_append_arrays(p, add->ign_list, base->ign_list); + new->hdr_list = ap_append_arrays(p, add->hdr_list, base->hdr_list); + new->desc_list = ap_append_arrays(p, add->desc_list, base->desc_list); + new->icon_list = ap_append_arrays(p, add->icon_list, base->icon_list); + new->rdme_list = ap_append_arrays(p, add->rdme_list, base->rdme_list); + if (add->opts & NO_OPTIONS) { + /* + * If the current directory says 'no options' then we also + * clear any incremental mods from being inheritable further down. + */ + new->opts = NO_OPTIONS; + new->incremented_opts = 0; + new->decremented_opts = 0; + } + else { + /* + * If there were any non-incremental options selected for + * this directory, they dominate and we don't inherit *anything.* + * Contrariwise, we *do* inherit if the only settings here are + * incremental ones. + */ + if (add->opts == 0) { + new->incremented_opts = (base->incremented_opts + | add->incremented_opts) + & ~add->decremented_opts; + new->decremented_opts = (base->decremented_opts + | add->decremented_opts); + /* + * We may have incremental settings, so make sure we don't + * inadvertently inherit an IndexOptions None from above. + */ + new->opts = (base->opts & ~NO_OPTIONS); + } + else { + /* + * There are local non-incremental settings, which clear + * all inheritance from above. They *are* the new base settings. + */ + new->opts = add->opts;; + } + /* + * We're guaranteed that there'll be no overlap between + * the add-options and the remove-options. + */ + new->opts |= new->incremented_opts; + new->opts &= ~new->decremented_opts; + } + /* + * Inherit the NameWidth settings if there aren't any specific to + * the new location; otherwise we'll end up using the defaults set in the + * config-rec creation routine. + */ + if (add->name_adjust == K_UNSET) { + new->name_width = base->name_width; + new->name_adjust = base->name_adjust; + } + else { + new->name_width = add->name_width; + new->name_adjust = add->name_adjust; + } + /* + * Likewise for DescriptionWidth. + */ + if (add->desc_adjust == K_UNSET) { + new->desc_width = base->desc_width; + new->desc_adjust = base->desc_adjust; + } + else { + new->desc_width = add->desc_width; + new->desc_adjust = add->desc_adjust; + } + + new->default_order = (add->default_order != NULL) + ? add->default_order : base->default_order; + return new; +} + +/**************************************************************** + * + * Looking things up in config entries... + */ + +/* Structure used to hold entries when we're actually building an index */ + +struct ent { + char *name; + char *icon; + char *alt; + char *desc; + off_t size; + time_t lm; + struct ent *next; + int ascending; + int isdir; + int checkdir; + int ignorecase; + char key; +}; + +static char *find_item(request_rec *r, array_header *list, int path_only) +{ + const char *content_type = ap_field_noparam(r->pool, r->content_type); + const char *content_encoding = r->content_encoding; + char *path = r->filename; + + struct item *items = (struct item *) list->elts; + int i; + + for (i = 0; i < list->nelts; ++i) { + struct item *p = &items[i]; + + /* Special cased for ^^DIRECTORY^^ and ^^BLANKICON^^ */ + if ((path[0] == '^') || (!ap_strcmp_match(path, p->apply_path))) { + if (!*(p->apply_to)) { + return p->data; + } + else if (p->type == BY_PATH || path[0] == '^') { + if (!ap_strcmp_match(path, p->apply_to)) { + return p->data; + } + } + else if (!path_only) { + if (!content_encoding) { + if (p->type == BY_TYPE) { + if (content_type + && !ap_strcasecmp_match(content_type, + p->apply_to)) { + return p->data; + } + } + } + else { + if (p->type == BY_ENCODING) { + if (!ap_strcasecmp_match(content_encoding, + p->apply_to)) { + return p->data; + } + } + } + } + } + } + return NULL; +} + +#define find_icon(d,p,t) find_item(p,d->icon_list,t) +#define find_alt(d,p,t) find_item(p,d->alt_list,t) +#define find_header(d,p) find_item(p,d->hdr_list,0) +#define find_readme(d,p) find_item(p,d->rdme_list,0) + +static char *find_default_icon(autoindex_config_rec *d, char *bogus_name) +{ + request_rec r; + + /* Bleah. I tried to clean up find_item, and it lead to this bit + * of ugliness. Note that the fields initialized are precisely + * those that find_item looks at... + */ + + r.filename = bogus_name; + r.content_type = r.content_encoding = NULL; + + return find_item(&r, d->icon_list, 1); +} + +/* + * Look through the list of pattern/description pairs and return the first one + * if any) that matches the filename in the request. If multiple patterns + * match, only the first one is used; since the order in the array is the + * same as the order in which directives were processed, earlier matching + * directives will dominate. + */ + +#ifdef CASE_BLIND_FILESYSTEM +#define MATCH_FLAGS FNM_CASE_BLIND +#else +#define MATCH_FLAGS 0 +#endif + +static char *find_desc(autoindex_config_rec *dcfg, request_rec *r) +{ + int i; + ai_desc_t *list = (ai_desc_t *) dcfg->desc_list->elts; + const char *filename_full = r->filename; + const char *filename_only; + const char *filename; + + /* + * If the filename includes a path, extract just the name itself + * for the simple matches. + */ + if ((filename_only = strrchr(filename_full, '/')) == NULL) { + filename_only = filename_full; + } + else { + filename_only++; + } + for (i = 0; i < dcfg->desc_list->nelts; ++i) { + ai_desc_t *tuple = &list[i]; + int found; + + /* + * Only use the full-path filename if the pattern contains '/'s. + */ + filename = (tuple->full_path) ? filename_full : filename_only; + /* + * Make the comparison using the cheapest method; only do + * wildcard checking if we must. + */ + if (tuple->wildcards) { + found = (ap_fnmatch(tuple->pattern, filename, MATCH_FLAGS) == 0); + } + else { + found = (strstr(filename, tuple->pattern) != NULL); + } + if (found) { + return tuple->description; + } + } + return NULL; +} + +static int ignore_entry(autoindex_config_rec *d, char *path) +{ + array_header *list = d->ign_list; + struct item *items = (struct item *) list->elts; + char *tt; + int i; + + if ((tt = strrchr(path, '/')) == NULL) { + tt = path; + } + else { + tt++; + } + + for (i = 0; i < list->nelts; ++i) { + struct item *p = &items[i]; + char *ap; + + if ((ap = strrchr(p->apply_to, '/')) == NULL) { + ap = p->apply_to; + } + else { + ap++; + } + +#ifndef CASE_BLIND_FILESYSTEM + if (!ap_strcmp_match(path, p->apply_path) + && !ap_strcmp_match(tt, ap)) { + return 1; + } +#else /* !CASE_BLIND_FILESYSTEM */ + /* + * On some platforms, the match must be case-blind. This is really + * a factor of the filesystem involved, but we can't detect that + * reliably - so we have to granularise at the OS level. + */ + if (!ap_strcasecmp_match(path, p->apply_path) + && !ap_strcasecmp_match(tt, ap)) { + return 1; + } +#endif /* !CASE_BLIND_FILESYSTEM */ + } + return 0; +} + +/***************************************************************** + * + * Actually generating output + */ + +/* + * Elements of the emitted document: + * Preamble + * Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req + * succeeds for the (content_type == text/html) header file. + * Header file + * Emitted if found (and able). + * H1 tag line + * Emitted if a header file is NOT emitted. + * Directory stuff + * Always emitted. + * HR + * Emitted if FANCY_INDEXING is set. + * Readme file + * Emitted if found (and able). + * ServerSig + * Emitted if ServerSignature is not Off AND a readme file + * is NOT emitted. + * Postamble + * Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req + * succeeds for the (content_type == text/html) readme file. + */ + + +/* + * emit a plain text file + */ +static void do_emit_plain(request_rec *r, FILE *f) +{ + char buf[IOBUFSIZE + 1]; + int i, n, c, ch; + + ap_rputs("<PRE>\n", r); + while (!feof(f)) { + do { + n = fread(buf, sizeof(char), IOBUFSIZE, f); + } + while (n == -1 && ferror(f) && errno == EINTR); + if (n == -1 || n == 0) { + break; + } + buf[n] = '\0'; + c = 0; + while (c < n) { + for (i = c; i < n; i++) { + if (buf[i] == '<' || buf[i] == '>' || buf[i] == '&') { + break; + } + } + ch = buf[i]; + buf[i] = '\0'; + ap_rputs(&buf[c], r); + if (ch == '<') { + ap_rputs("<", r); + } + else if (ch == '>') { + ap_rputs(">", r); + } + else if (ch == '&') { + ap_rputs("&", r); + } + c = i + 1; + } + } + ap_rputs("</PRE>\n", r); +} + +/* See mod_include */ +#define SUB_REQ_STRING "Sub request to mod_include" +#define PARENT_STRING "Parent request to mod_include" + +/* + * Handle the preamble through the H1 tag line, inclusive. Locate + * the file with a subrequests. Process text/html documents by actually + * running the subrequest; text/xxx documents get copied verbatim, + * and any other content type is ignored. This means that a non-text + * document (such as HEADER.gif) might get multiviewed as the result + * instead of a text document, meaning nothing will be displayed, but + * oh well. + */ +static void emit_head(request_rec *r, char *header_fname, int suppress_amble, + char *title) +{ + FILE *f; + request_rec *rr = NULL; + int emit_amble = 1; + int emit_H1 = 1; + const char *r_accept; + const char *r_accept_enc; + table *hdrs = r->headers_in; + + /* + * If there's a header file, send a subrequest to look for it. If it's + * found and html do the subrequest, otherwise handle it + */ + r_accept = ap_table_get(hdrs, "Accept"); + r_accept_enc = ap_table_get(hdrs, "Accept-Encoding"); + ap_table_setn(hdrs, "Accept", "text/html, text/plain;q=.5, text/*;q=.1"); + ap_table_unset(hdrs, "Accept-Encoding"); + + /* + * If there's a header file, send a subrequest to look for it. If it's + * found and a text file, handle it -- otherwise fall through and + * pretend there's nothing there. + */ + if ((header_fname != NULL) + && (rr = ap_sub_req_lookup_uri(header_fname, r)) + && (rr->status == HTTP_OK) + && (rr->filename != NULL) + && S_ISREG(rr->finfo.st_mode)) { + /* + * Check for the two specific cases we allow: text/html and + * text/anything-else. The former is allowed to be processed for + * SSIs. + */ + if (rr->content_type != NULL) { + if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type), + "text/html")) { + /* Hope everything will work... */ + emit_amble = 0; + emit_H1 = 0; + + if (! suppress_amble) { + emit_preamble(r, title); + } + + /* See mod_include */ + ap_table_add(r->notes, PARENT_STRING, ""); + ap_table_add(rr->notes, SUB_REQ_STRING, ""); + + /* + * If there's a problem running the subrequest, display the + * preamble if we didn't do it before -- the header file + * didn't get displayed. + */ + if (ap_run_sub_req(rr) != OK) { + /* It didn't work */ + emit_amble = suppress_amble; + emit_H1 = 1; + } + ap_table_unset(r->notes, PARENT_STRING); /* cleanup */ + } + else if (!strncasecmp("text/", rr->content_type, 5)) { + /* + * If we can open the file, prefix it with the preamble + * regardless; since we'll be sending a <PRE> block around + * the file's contents, any HTML header it had won't end up + * where it belongs. + */ + if ((f = ap_pfopen(r->pool, rr->filename, "r")) != 0) { + emit_preamble(r, title); + emit_amble = 0; + do_emit_plain(r, f); + ap_pfclose(r->pool, f); + emit_H1 = 0; + } + } + } + } + + if (r_accept) { + ap_table_setn(hdrs, "Accept", r_accept); + } + else { + ap_table_unset(hdrs, "Accept"); + } + + if (r_accept_enc) { + ap_table_setn(hdrs, "Accept-Encoding", r_accept_enc); + } + + if (emit_amble) { + emit_preamble(r, title); + } + if (emit_H1) { + ap_rvputs(r, "<H1>Index of ", title, "</H1>\n", NULL); + } + if (rr != NULL) { + ap_destroy_sub_req(rr); + } +} + + +/* + * Handle the Readme file through the postamble, inclusive. Locate + * the file with a subrequests. Process text/html documents by actually + * running the subrequest; text/xxx documents get copied verbatim, + * and any other content type is ignored. This means that a non-text + * document (such as FOOTER.gif) might get multiviewed as the result + * instead of a text document, meaning nothing will be displayed, but + * oh well. + */ +static void emit_tail(request_rec *r, char *readme_fname, int suppress_amble) +{ + FILE *f; + request_rec *rr = NULL; + int suppress_post = 0; + int suppress_sig = 0; + const char *r_accept; + const char *r_accept_enc; + table *hdrs = r->headers_in; + + /* + * If there's a readme file, send a subrequest to look for it. If it's + * found and html do the subrequest, otherwise handle it + */ + r_accept = ap_table_get(hdrs, "Accept"); + r_accept_enc = ap_table_get(hdrs, "Accept-Encoding"); + ap_table_setn(hdrs, "Accept", "text/html, text/plain;q=.5, text/*;q=.1"); + ap_table_unset(hdrs, "Accept-Encoding"); + + /* + * If there's a readme file, send a subrequest to look for it. If it's + * found and a text file, handle it -- otherwise fall through and + * pretend there's nothing there. + */ + if ((readme_fname != NULL) + && (rr = ap_sub_req_lookup_uri(readme_fname, r)) + && (rr->status == HTTP_OK) + && (rr->filename != NULL) + && S_ISREG(rr->finfo.st_mode)) { + /* + * Check for the two specific cases we allow: text/html and + * text/anything-else. The former is allowed to be processed for + * SSIs. + */ + if (rr->content_type != NULL) { + if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type), + "text/html")) { + + /* See mod_include */ + ap_table_add(r->notes, PARENT_STRING, ""); + ap_table_add(rr->notes, SUB_REQ_STRING, ""); + + if (ap_run_sub_req(rr) == OK) { + /* worked... */ + suppress_sig = 1; + suppress_post = suppress_amble; + } + ap_table_unset(r->notes, PARENT_STRING); /* cleanup */ + } + else if (!strncasecmp("text/", rr->content_type, 5)) { + /* + * If we can open the file, suppress the signature. + */ + if ((f = ap_pfopen(r->pool, rr->filename, "r")) != 0) { + do_emit_plain(r, f); + ap_pfclose(r->pool, f); + suppress_sig = 1; + } + } + } + } + + if (r_accept) { + ap_table_setn(hdrs, "Accept", r_accept); + } + else { + ap_table_unset(hdrs, "Accept"); + } + + if (r_accept_enc) { + ap_table_setn(hdrs, "Accept-Encoding", r_accept_enc); + } + + if (!suppress_sig) { + ap_rputs(ap_psignature("", r), r); + } + if (!suppress_post) { + ap_rputs("</BODY></HTML>\n", r); + } + if (rr != NULL) { + ap_destroy_sub_req(rr); + } +} + + +static char *find_title(request_rec *r) +{ + char titlebuf[MAX_STRING_LEN], *find = "<TITLE>"; + FILE *thefile = NULL; + int x, y, n, p; + + if (r->status != HTTP_OK) { + return NULL; + } + if ((r->content_type != NULL) + && (!strcasecmp(ap_field_noparam(r->pool, r->content_type), + "text/html") + || !strcmp(r->content_type, INCLUDES_MAGIC_TYPE)) + && !r->content_encoding) { + if (!(thefile = ap_pfopen(r->pool, r->filename, "r"))) { + return NULL; + } + n = fread(titlebuf, sizeof(char), MAX_STRING_LEN - 1, thefile); + if (n <= 0) { + ap_pfclose(r->pool, thefile); + return NULL; + } + titlebuf[n] = '\0'; + for (x = 0, p = 0; titlebuf[x]; x++) { + if (ap_toupper(titlebuf[x]) == find[p]) { + if (!find[++p]) { + if ((p = ap_ind(&titlebuf[++x], '<')) != -1) { + titlebuf[x + p] = '\0'; + } + /* Scan for line breaks for Tanmoy's secretary */ + for (y = x; titlebuf[y]; y++) { + if ((titlebuf[y] == CR) || (titlebuf[y] == LF)) { + if (y == x) { + x++; + } + else { + titlebuf[y] = ' '; + } + } + } + ap_pfclose(r->pool, thefile); + return ap_pstrdup(r->pool, &titlebuf[x]); + } + } + else { + p = 0; + } + } + ap_pfclose(r->pool, thefile); + } + return NULL; +} + +static struct ent *make_autoindex_entry(char *name, int autoindex_opts, + autoindex_config_rec *d, + request_rec *r, char keyid, + char direction) +{ + struct ent *p; + + if ((name[0] == '.') && (!name[1])) { + return (NULL); + } + + if (ignore_entry(d, ap_make_full_path(r->pool, r->filename, name))) { + return (NULL); + } + + p = (struct ent *) ap_pcalloc(r->pool, sizeof(struct ent)); + p->name = ap_pstrdup(r->pool, name); + p->size = -1; + p->icon = NULL; + p->alt = NULL; + p->desc = NULL; + p->lm = -1; + p->isdir = 0; + /* + * It's obnoxious to have to include this in every entry, but the qsort() + * comparison routine only takes two arguments.. The alternative would + * add another function call to each invocation. Let's use memory + * rather than CPU. + */ + p->checkdir = ((d->opts & FOLDERS_FIRST) != 0); + p->ignorecase = ((d->opts & SORT_NOCASE) != 0); + p->key = ap_toupper(keyid); + p->ascending = (ap_toupper(direction) == D_ASCENDING); + + if (autoindex_opts & FANCY_INDEXING) { + request_rec *rr = ap_sub_req_lookup_file(name, r); + + if (rr->finfo.st_mode != 0) { + p->lm = rr->finfo.st_mtime; + if (S_ISDIR(rr->finfo.st_mode)) { + p->isdir = 1; + if (!(p->icon = find_icon(d, rr, 1))) { + p->icon = find_default_icon(d, "^^DIRECTORY^^"); + } + if (!(p->alt = find_alt(d, rr, 1))) { + p->alt = "DIR"; + } + p->size = -1; + p->name = ap_pstrcat(r->pool, name, "/", NULL); + } + else { + p->icon = find_icon(d, rr, 0); + p->alt = find_alt(d, rr, 0); + p->size = rr->finfo.st_size; + } + } + + p->desc = find_desc(d, rr); + + if ((!p->desc) && (autoindex_opts & SCAN_HTML_TITLES)) { + p->desc = ap_pstrdup(r->pool, find_title(rr)); + } + + ap_destroy_sub_req(rr); + } + /* + * We don't need to take any special action for the file size key. If + * we did, it would go here. + */ + if (keyid == K_LAST_MOD) { + if (p->lm < 0) { + p->lm = 0; + } + } + return (p); +} + +static char *terminate_description(autoindex_config_rec *d, char *desc, + int autoindex_opts, int desc_width) +{ + int maxsize = desc_width; + register int x; + + /* + * If there's no DescriptionWidth in effect, default to the old + * behaviour of adjusting the description size depending upon + * what else is being displayed. Otherwise, stick with the + * setting. + */ + if (d->desc_adjust == K_UNSET) { + if (autoindex_opts & SUPPRESS_LAST_MOD) { + maxsize += 19; + } + if (autoindex_opts & SUPPRESS_SIZE) { + maxsize += 7; + } + } + + for (x = 0; desc[x] && ((maxsize > 0) || (desc[x] == '<')); x++) { + if (desc[x] == '<') { + while (desc[x] != '>') { + if (!desc[x]) { + maxsize = 0; + break; + } + ++x; + } + } + else if (desc[x] == '&') { + /* entities like ä count as one character */ + --maxsize; + for ( ; desc[x] != ';'; ++x) { + if (desc[x] == '\0') { + maxsize = 0; + break; + } + } + } + else { + --maxsize; + } + } + if (!maxsize && desc[x] != '\0') { + desc[x - 1] = '>'; /* Grump. */ + desc[x] = '\0'; /* Double Grump! */ + } + return desc; +} + +/* + * Emit the anchor for the specified field. If a field is the key for the + * current request, the link changes its meaning to reverse the order when + * selected again. Non-active fields always start in ascending order. + */ +static void emit_link(request_rec *r, char *anchor, char fname, char curkey, + char curdirection, int nosort) +{ + char qvalue[5]; + int reverse; + + if (!nosort) { + qvalue[0] = '?'; + qvalue[1] = fname; + qvalue[2] = '='; + qvalue[4] = '\0'; + reverse = ((curkey == fname) && (curdirection == D_ASCENDING)); + qvalue[3] = reverse ? D_DESCENDING : D_ASCENDING; + ap_rvputs(r, "<A HREF=\"", qvalue, "\">", anchor, "</A>", NULL); + } + else { + ap_rputs(anchor, r); + } +} + +static void output_directories(struct ent **ar, int n, + autoindex_config_rec *d, request_rec *r, + int autoindex_opts, char keyid, char direction) +{ + int x; + char *name = r->uri; + char *tp; + int static_columns = (autoindex_opts & SUPPRESS_COLSORT); + pool *scratch = ap_make_sub_pool(r->pool); + int name_width; + int desc_width; + char *name_scratch; + char *pad_scratch; + + if (name[0] == '\0') { + name = "/"; + } + + desc_width = d->desc_width; + if (d->desc_adjust == K_ADJUST) { + for (x = 0; x < n; x++) { + if (ar[x]->desc != NULL) { + int t = strlen(ar[x]->desc); + if (t > desc_width) { + desc_width = t; + } + } + } + } + name_width = d->name_width; + if (d->name_adjust == K_ADJUST) { + for (x = 0; x < n; x++) { + int t = strlen(ar[x]->name); + if (t > name_width) { + name_width = t; + } + } + } + name_scratch = ap_palloc(r->pool, name_width + 1); + pad_scratch = ap_palloc(r->pool, name_width + 1); + memset(pad_scratch, ' ', name_width); + pad_scratch[name_width] = '\0'; + + if (autoindex_opts & FANCY_INDEXING) { + ap_rputs("<PRE>", r); + if ((tp = find_default_icon(d, "^^BLANKICON^^"))) { + ap_rvputs(r, "<IMG SRC=\"", ap_escape_html(scratch, tp), + "\" ALT=\" \"", NULL); + if (d->icon_width && d->icon_height) { + ap_rprintf + ( + r, + " HEIGHT=\"%d\" WIDTH=\"%d\"", + d->icon_height, + d->icon_width + ); + } + ap_rputs("> ", r); + } + emit_link(r, "Name", K_NAME, keyid, direction, static_columns); + ap_rputs(pad_scratch + 4, r); + /* + * Emit the guaranteed-at-least-one-space-between-columns byte. + */ + ap_rputs(" ", r); + if (!(autoindex_opts & SUPPRESS_LAST_MOD)) { + emit_link(r, "Last modified", K_LAST_MOD, keyid, direction, + static_columns); + ap_rputs(" ", r); + } + if (!(autoindex_opts & SUPPRESS_SIZE)) { + emit_link(r, "Size", K_SIZE, keyid, direction, static_columns); + ap_rputs(" ", r); + } + if (!(autoindex_opts & SUPPRESS_DESC)) { + emit_link(r, "Description", K_DESC, keyid, direction, + static_columns); + } + ap_rputs("\n<HR>\n", r); + } + else { + ap_rputs("<UL>", r); + } + + for (x = 0; x < n; x++) { + char *anchor, *t, *t2; + int nwidth; + + ap_clear_pool(scratch); + + if (is_parent(ar[x]->name)) { + t = ap_make_full_path(scratch, name, "../"); + ap_getparents(t); + if (t[0] == '\0') { + t = "/"; + } + t2 = "Parent Directory"; + anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0)); + } + else { + t = ar[x]->name; + t2 = t; + anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0)); + } + + if (autoindex_opts & FANCY_INDEXING) { + if (autoindex_opts & ICONS_ARE_LINKS) { + ap_rvputs(r, "<A HREF=\"", anchor, "\">", NULL); + } + if ((ar[x]->icon) || d->default_icon) { + ap_rvputs(r, "<IMG SRC=\"", + ap_escape_html(scratch, + ar[x]->icon ? ar[x]->icon + : d->default_icon), + "\" ALT=\"[", (ar[x]->alt ? ar[x]->alt : " "), + "]\"", NULL); + if (d->icon_width && d->icon_height) { + ap_rprintf(r, " HEIGHT=\"%d\" WIDTH=\"%d\"", + d->icon_height, d->icon_width); + } + ap_rputs(">", r); + } + if (autoindex_opts & ICONS_ARE_LINKS) { + ap_rputs("</A>", r); + } + + nwidth = strlen(t2); + if (nwidth > name_width) { + memcpy(name_scratch, t2, name_width - 3); + name_scratch[name_width - 3] = '.'; + name_scratch[name_width - 2] = '.'; + name_scratch[name_width - 1] = '>'; + name_scratch[name_width] = 0; + t2 = name_scratch; + nwidth = name_width; + } + ap_rvputs(r, " <A HREF=\"", anchor, "\">", + ap_escape_html(scratch, t2), "</A>", + pad_scratch + nwidth, NULL); + /* + * The blank before the storm.. er, before the next field. + */ + ap_rputs(" ", r); + if (!(autoindex_opts & SUPPRESS_LAST_MOD)) { + if (ar[x]->lm != -1) { + char time_str[MAX_STRING_LEN]; + struct tm *ts = localtime(&ar[x]->lm); + strftime(time_str, MAX_STRING_LEN, "%d-%b-%Y %H:%M ", ts); + ap_rputs(time_str, r); + } + else { + /*Length="22-Feb-1998 23:42 " (see 4 lines above) */ + ap_rputs(" ", r); + } + } + if (!(autoindex_opts & SUPPRESS_SIZE)) { + ap_send_size(ar[x]->size, r); + ap_rputs(" ", r); + } + if (!(autoindex_opts & SUPPRESS_DESC)) { + if (ar[x]->desc) { + ap_rputs(terminate_description(d, ar[x]->desc, + autoindex_opts, + desc_width), r); + } + } + } + else { + ap_rvputs(r, "<LI><A HREF=\"", anchor, "\"> ", t2, + "</A>", NULL); + } + ap_rputc('\n', r); + } + if (autoindex_opts & FANCY_INDEXING) { + ap_rputs("</PRE>", r); + } + else { + ap_rputs("</UL>", r); + } +} + +/* + * Compare two file entries according to the sort criteria. The return + * is essentially a signum function value. + */ + +static int dsortf(struct ent **e1, struct ent **e2) +{ + struct ent *c1; + struct ent *c2; + int result = 0; + int ignorecase; + + /* + * First, see if either of the entries is for the parent directory. + * If so, that *always* sorts lower than anything else. + */ + if (is_parent((*e1)->name)) { + return -1; + } + if (is_parent((*e2)->name)) { + return 1; + } + /* + * Now see if one's a directory and one isn't, AND we're listing + * directories first. + */ + if ((*e1)->checkdir) { + if ((*e1)->isdir != (*e2)->isdir) { + return (*e1)->isdir ? -1 : 1; + } + } + /* + * All of our comparisons will be of the c1 entry against the c2 one, + * so assign them appropriately to take care of the ordering. + */ + if ((*e1)->ascending) { + c1 = *e1; + c2 = *e2; + } + else { + c1 = *e2; + c2 = *e1; + } + switch (c1->key) { + case K_LAST_MOD: + if (c1->lm > c2->lm) { + return 1; + } + else if (c1->lm < c2->lm) { + return -1; + } + break; + case K_SIZE: + if (c1->size > c2->size) { + return 1; + } + else if (c1->size < c2->size) { + return -1; + } + break; + case K_DESC: + result = strcmp(c1->desc ? c1->desc : "", c2->desc ? c2->desc : ""); + if (result) { + return result; + } + break; + } + + ignorecase = c1->ignorecase; + if (ignorecase) { + result = strcasecmp(c1->name, c2->name); + if (result == 0) { + /* + * They're identical when treated case-insensitively, so + * pretend they weren't and let strcmp() put them in a + * deterministic order. This means that 'ABC' and 'abc' + * will always appear in the same order, rather than + * unpredictably 'ABC abc' or 'abc ABC'. + */ + ignorecase = 0; + } + } + if (! ignorecase) { + result = strcmp(c1->name, c2->name); + } + return result; +} + + +static int index_directory(request_rec *r, + autoindex_config_rec *autoindex_conf) +{ + char *title_name = ap_escape_html(r->pool, r->uri); + char *title_endp; + char *name = r->filename; + + DIR *d; + struct DIR_TYPE *dstruct; + int num_ent = 0, x; + struct ent *head, *p; + struct ent **ar = NULL; + const char *qstring; + int autoindex_opts = autoindex_conf->opts; + char keyid; + char direction; + + if (!(d = ap_popendir(r->pool, name))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "Can't open directory for index: %s", r->filename); + return HTTP_FORBIDDEN; + } + + r->content_type = "text/html"; + if (autoindex_opts & TRACK_MODIFIED) { + ap_update_mtime(r, r->finfo.st_mtime); + ap_set_last_modified(r); + ap_set_etag(r); + } + ap_send_http_header(r); + +#ifdef CHARSET_EBCDIC + /* Server-generated response, converted */ + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1); +#endif + + if (r->header_only) { + ap_pclosedir(r->pool, d); + return 0; + } + ap_hard_timeout("send directory", r); + + /* Spew HTML preamble */ + + title_endp = title_name + strlen(title_name) - 1; + + while (title_endp > title_name && *title_endp == '/') { + *title_endp-- = '\0'; + } + + emit_head(r, find_header(autoindex_conf, r), + autoindex_opts & SUPPRESS_PREAMBLE, title_name); + + /* + * Figure out what sort of indexing (if any) we're supposed to use. + * + * If no QUERY_STRING was specified or column sorting has been + * explicitly disabled, we use the default specified by the + * IndexOrderDefault directive (if there is one); otherwise, + * we fall back to ascending by name. + */ + qstring = r->args; + if ((autoindex_opts & SUPPRESS_COLSORT) + || ((qstring == NULL) || (*qstring == '\0'))) { + qstring = autoindex_conf->default_order; + } + /* + * If there is no specific ordering defined for this directory, + * default to ascending by filename. + */ + if ((qstring == NULL) || (*qstring == '\0')) { + keyid = K_NAME; + direction = D_ASCENDING; + } + else { + keyid = *qstring; + ap_getword(r->pool, &qstring, '='); + if (*qstring == D_DESCENDING) { + direction = D_DESCENDING; + } + else { + direction = D_ASCENDING; + } + } + + /* + * Since we don't know how many dir. entries there are, put them into a + * linked list and then arrayificate them so qsort can use them. + */ + head = NULL; + while ((dstruct = readdir(d))) { + p = make_autoindex_entry(dstruct->d_name, autoindex_opts, + autoindex_conf, r, keyid, direction); + if (p != NULL) { + p->next = head; + head = p; + num_ent++; + } + } + if (num_ent > 0) { + ar = (struct ent **) ap_palloc(r->pool, + num_ent * sizeof(struct ent *)); + p = head; + x = 0; + while (p) { + ar[x++] = p; + p = p->next; + } + + qsort((void *) ar, num_ent, sizeof(struct ent *), + (int (*)(const void *, const void *)) dsortf); + } + output_directories(ar, num_ent, autoindex_conf, r, autoindex_opts, keyid, + direction); + ap_pclosedir(r->pool, d); + + if (autoindex_opts & FANCY_INDEXING) { + ap_rputs("<HR>\n", r); + } + emit_tail(r, find_readme(autoindex_conf, r), + autoindex_opts & SUPPRESS_PREAMBLE); + + ap_kill_timeout(r); + return 0; +} + +/* The formal handler... */ + +static int handle_autoindex(request_rec *r) +{ + autoindex_config_rec *d; + int allow_opts = ap_allow_options(r); + + d = (autoindex_config_rec *) ap_get_module_config(r->per_dir_config, + &autoindex_module); + + r->allowed |= (1 << M_GET); + if (r->method_number != M_GET) { + return DECLINED; + } + + /* OK, nothing easy. Trot out the heavy artillery... */ + + if (allow_opts & OPT_INDEXES) { + /* KLUDGE --- make the sub_req lookups happen in the right directory. + * Fixing this in the sub_req_lookup functions themselves is difficult, + * and would probably break virtual includes... + */ + + if (r->filename[strlen(r->filename) - 1] != '/') { + r->filename = ap_pstrcat(r->pool, r->filename, "/", NULL); + } + return index_directory(r, d); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Directory index forbidden by rule: %s", r->filename); + return HTTP_FORBIDDEN; + } +} + + +static const handler_rec autoindex_handlers[] = +{ + {DIR_MAGIC_TYPE, handle_autoindex}, + {NULL} +}; + +module MODULE_VAR_EXPORT autoindex_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_autoindex_config, /* dir config creater */ + merge_autoindex_configs, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + autoindex_cmds, /* command table */ + autoindex_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_cern_meta.c b/APACHE_1_3_42/src/modules/standard/mod_cern_meta.c new file mode 100644 index 0000000000..63d4de4142 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_cern_meta.c @@ -0,0 +1,357 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_cern_meta.c + * version 0.1.0 + * status beta + * + * Andrew Wilson <Andrew.Wilson@cm.cf.ac.uk> 25.Jan.96 + * + * *** IMPORTANT *** + * This version of mod_cern_meta.c controls Meta File behaviour on a + * per-directory basis. Previous versions of the module defined behaviour + * on a per-server basis. The upshot is that you'll need to revisit your + * configuration files in order to make use of the new module. + * *** + * + * Emulate the CERN HTTPD Meta file semantics. Meta files are HTTP + * headers that can be output in addition to the normal range of + * headers for each file accessed. They appear rather like the Apache + * .asis files, and are able to provide a crude way of influencing + * the Expires: header, as well as providing other curiosities. + * There are many ways to manage meta information, this one was + * chosen because there is already a large number of CERN users + * who can exploit this module. It should be noted that there are probably + * more sensitive ways of managing the Expires: header specifically. + * + * The module obeys the following directives, which can appear + * in the server's .conf files and in .htaccess files. + * + * MetaFiles <on|off> + * + * turns on|off meta file processing for any directory. + * Default value is off + * + * # turn on MetaFiles in this directory + * MetaFiles on + * + * MetaDir <directory name> + * + * specifies the name of the directory in which Apache can find + * meta information files. The directory is usually a 'hidden' + * subdirectory of the directory that contains the file being + * accessed. eg: + * + * # .meta files are in the *same* directory as the + * # file being accessed + * MetaDir . + * + * the default is to look in a '.web' subdirectory. This is the + * same as for CERN 3.+ webservers and behaviour is the same as + * for the directive: + * + * MetaDir .web + * + * MetaSuffix <meta file suffix> + * + * specifies the file name suffix for the file containing the + * meta information. eg: + * + * # our meta files are suffixed with '.cern_meta' + * MetaSuffix .cern_meta + * + * the default is to look for files with the suffix '.meta'. This + * behaviour is the same as for the directive: + * + * MetaSuffix .meta + * + * When accessing the file + * + * DOCUMENT_ROOT/somedir/index.html + * + * this module will look for the file + * + * DOCUMENT_ROOT/somedir/.web/index.html.meta + * + * and will use its contents to generate additional MIME header + * information. + * + * For more information on the CERN Meta file semantics see: + * + * http://www.w3.org/hypertext/WWW/Daemon/User/Config/General.html#MetaDir + * + * Change-log: + * 29.Jan.96 pfopen/pfclose instead of fopen/fclose + * DECLINE when real file not found, we may be checking each + * of the index.html/index.shtml/index.htm variants and don't + * need to report missing ones as spurious errors. + * 31.Jan.96 log_error reports about a malformed .meta file, rather + * than a script error. + * 20.Jun.96 MetaFiles <on|off> default off, added, so that module + * can be configured per-directory. Prior to this the module + * was running for each request anywhere on the server, naughty.. + * 29.Jun.96 All directives made per-directory. + */ + +#include "httpd.h" +#include "http_config.h" +#ifndef NETWARE +#include <sys/types.h> +#endif +#include <sys/stat.h> +#include "util_script.h" +#include "http_log.h" +#include "http_request.h" + +#define DIR_CMD_PERMS OR_INDEXES + +#define DEFAULT_METADIR ".web" +#define DEFAULT_METASUFFIX ".meta" +#define DEFAULT_METAFILES 0 + +module MODULE_VAR_EXPORT cern_meta_module; + +typedef struct { + char *metadir; + char *metasuffix; + int metafiles; +} cern_meta_dir_config; + +static void *create_cern_meta_dir_config(pool *p, char *dummy) +{ + cern_meta_dir_config *new = + (cern_meta_dir_config *) ap_palloc(p, sizeof(cern_meta_dir_config)); + + new->metadir = NULL; + new->metasuffix = NULL; + new->metafiles = DEFAULT_METAFILES; + + return new; +} + +static void *merge_cern_meta_dir_configs(pool *p, void *basev, void *addv) +{ + cern_meta_dir_config *base = (cern_meta_dir_config *) basev; + cern_meta_dir_config *add = (cern_meta_dir_config *) addv; + cern_meta_dir_config *new = + (cern_meta_dir_config *) ap_palloc(p, sizeof(cern_meta_dir_config)); + + new->metadir = add->metadir ? add->metadir : base->metadir; + new->metasuffix = add->metasuffix ? add->metasuffix : base->metasuffix; + new->metafiles = add->metafiles; + + return new; +} + +static const char *set_metadir(cmd_parms *parms, cern_meta_dir_config * dconf, char *arg) +{ + dconf->metadir = arg; + return NULL; +} + +static const char *set_metasuffix(cmd_parms *parms, cern_meta_dir_config * dconf, char *arg) +{ + dconf->metasuffix = arg; + return NULL; +} + +static const char *set_metafiles(cmd_parms *parms, cern_meta_dir_config * dconf, int arg) +{ + dconf->metafiles = arg; + return NULL; +} + + +static const command_rec cern_meta_cmds[] = +{ + {"MetaFiles", set_metafiles, NULL, DIR_CMD_PERMS, FLAG, + "Limited to 'on' or 'off'"}, + {"MetaDir", set_metadir, NULL, DIR_CMD_PERMS, TAKE1, + "the name of the directory containing meta files"}, + {"MetaSuffix", set_metasuffix, NULL, DIR_CMD_PERMS, TAKE1, + "the filename suffix for meta files"}, + {NULL} +}; + +/* XXX: this is very similar to ap_scan_script_header_err_core... + * are the differences deliberate, or just a result of bit rot? + */ +static int scan_meta_file(request_rec *r, FILE *f) +{ + char w[MAX_STRING_LEN]; + char *l; + int p; + table *tmp_headers; + + tmp_headers = ap_make_table(r->pool, 5); + while (fgets(w, MAX_STRING_LEN - 1, f) != NULL) { + + /* Delete terminal (CR?)LF */ + + p = strlen(w); + if (p > 0 && w[p - 1] == '\n') { + if (p > 1 && w[p - 2] == '\015') + w[p - 2] = '\0'; + else + w[p - 1] = '\0'; + } + + if (w[0] == '\0') { + return OK; + } + + /* if we see a bogus header don't ignore it. Shout and scream */ + + if (!(l = strchr(w, ':'))) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "malformed header in meta file: %s", r->filename); + return SERVER_ERROR; + } + + *l++ = '\0'; + while (*l && ap_isspace(*l)) + ++l; + + if (!strcasecmp(w, "Content-type")) { + char *tmp; + /* Nuke trailing whitespace */ + + char *endp = l + strlen(l) - 1; + while (endp > l && ap_isspace(*endp)) + *endp-- = '\0'; + + tmp = ap_pstrdup(r->pool, l); + ap_content_type_tolower(tmp); + r->content_type = tmp; + } + else if (!strcasecmp(w, "Status")) { + sscanf(l, "%d", &r->status); + r->status_line = ap_pstrdup(r->pool, l); + } + else { + ap_table_set(tmp_headers, w, l); + } + } + ap_overlap_tables(r->headers_out, tmp_headers, AP_OVERLAP_TABLES_SET); + return OK; +} + +static int add_cern_meta_data(request_rec *r) +{ + char *metafilename; + char *last_slash; + char *real_file; + char *scrap_book; + FILE *f; + cern_meta_dir_config *dconf; + int rv; + request_rec *rr; + + dconf = ap_get_module_config(r->per_dir_config, &cern_meta_module); + + if (!dconf->metafiles) { + return DECLINED; + }; + + /* if ./.web/$1.meta exists then output 'asis' */ + + if (r->finfo.st_mode == 0) { + return DECLINED; + }; + + /* is this a directory? */ + if (S_ISDIR(r->finfo.st_mode) || r->uri[strlen(r->uri) - 1] == '/') { + return DECLINED; + }; + + /* what directory is this file in? */ + scrap_book = ap_pstrdup(r->pool, r->filename); + /* skip leading slash, recovered in later processing */ + scrap_book++; + last_slash = strrchr(scrap_book, '/'); + if (last_slash != NULL) { + /* skip over last slash */ + real_file = last_slash; + real_file++; + *last_slash = '\0'; + } + else { + /* no last slash, buh?! */ + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "internal error in mod_cern_meta: %s", r->filename); + /* should really barf, but hey, let's be friends... */ + return DECLINED; + }; + + metafilename = ap_pstrcat(r->pool, "/", scrap_book, "/", + dconf->metadir ? dconf->metadir : DEFAULT_METADIR, + "/", real_file, + dconf->metasuffix ? dconf->metasuffix : DEFAULT_METASUFFIX, + NULL); + + /* XXX: it sucks to require this subrequest to complete, because this + * means people must leave their meta files accessible to the world. + * A better solution might be a "safe open" feature of pfopen to avoid + * pipes, symlinks, and crap like that. + */ + rr = ap_sub_req_lookup_file(metafilename, r); + if (rr->status != HTTP_OK) { + ap_destroy_sub_req(rr); + return DECLINED; + } + ap_destroy_sub_req(rr); + + f = ap_pfopen(r->pool, metafilename, "r"); + if (f == NULL) { + if (errno == ENOENT) { + return DECLINED; + } + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "meta file permissions deny server access: %s", metafilename); + return FORBIDDEN; + }; + + /* read the headers in */ + rv = scan_meta_file(r, f); + ap_pfclose(r->pool, f); + + return rv; +} + +module MODULE_VAR_EXPORT cern_meta_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_cern_meta_dir_config, /* dir config creater */ + merge_cern_meta_dir_configs, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server configs */ + cern_meta_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + add_cern_meta_data, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; + diff --git a/APACHE_1_3_42/src/modules/standard/mod_cgi.c b/APACHE_1_3_42/src/modules/standard/mod_cgi.c new file mode 100644 index 0000000000..b92a95acf8 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_cgi.c @@ -0,0 +1,581 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * http_script: keeps all script-related ramblings together. + * + * Compliant to CGI/1.1 spec + * + * Adapted by rst from original NCSA code by Rob McCool + * + * Apache adds some new env vars; REDIRECT_URL and REDIRECT_QUERY_STRING for + * custom error responses, and DOCUMENT_ROOT because we found it useful. + * It also adds SERVER_ADMIN - useful for scripts to know who to mail when + * they fail. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_main.h" +#include "http_log.h" +#include "util_script.h" +#include "http_conf_globals.h" + +module MODULE_VAR_EXPORT cgi_module; + +/* KLUDGE --- for back-combatibility, we don't have to check ExecCGI + * in ScriptAliased directories, which means we need to know if this + * request came through ScriptAlias or not... so the Alias module + * leaves a note for us. + */ + +static int is_scriptaliased(request_rec *r) +{ + const char *t = ap_table_get(r->notes, "alias-forced-type"); + return t && (!strcasecmp(t, "cgi-script")); +} + +/* Configuration stuff */ + +#define DEFAULT_LOGBYTES 10385760 +#define DEFAULT_BUFBYTES 1024 + +typedef struct { + char *logname; + long logbytes; + int bufbytes; +} cgi_server_conf; + +static void *create_cgi_config(pool *p, server_rec *s) +{ + cgi_server_conf *c = + (cgi_server_conf *) ap_pcalloc(p, sizeof(cgi_server_conf)); + + c->logname = NULL; + c->logbytes = DEFAULT_LOGBYTES; + c->bufbytes = DEFAULT_BUFBYTES; + + return c; +} + +static void *merge_cgi_config(pool *p, void *basev, void *overridesv) +{ + cgi_server_conf *base = (cgi_server_conf *) basev, *overrides = (cgi_server_conf *) overridesv; + + return overrides->logname ? overrides : base; +} + +static const char *set_scriptlog(cmd_parms *cmd, void *dummy, char *arg) +{ + server_rec *s = cmd->server; + cgi_server_conf *conf = + (cgi_server_conf *) ap_get_module_config(s->module_config, &cgi_module); + + conf->logname = arg; + return NULL; +} + +static const char *set_scriptlog_length(cmd_parms *cmd, void *dummy, char *arg) +{ + server_rec *s = cmd->server; + cgi_server_conf *conf = + (cgi_server_conf *) ap_get_module_config(s->module_config, &cgi_module); + + conf->logbytes = atol(arg); + return NULL; +} + +static const char *set_scriptlog_buffer(cmd_parms *cmd, void *dummy, char *arg) +{ + server_rec *s = cmd->server; + cgi_server_conf *conf = + (cgi_server_conf *) ap_get_module_config(s->module_config, &cgi_module); + + conf->bufbytes = atoi(arg); + return NULL; +} + +static const command_rec cgi_cmds[] = +{ + {"ScriptLog", set_scriptlog, NULL, RSRC_CONF, TAKE1, + "the name of a log for script debugging info"}, + {"ScriptLogLength", set_scriptlog_length, NULL, RSRC_CONF, TAKE1, + "the maximum length (in bytes) of the script debug log"}, + {"ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF, TAKE1, + "the maximum size (in bytes) to record of a POST request"}, + {NULL} +}; + +static int log_scripterror(request_rec *r, cgi_server_conf * conf, int ret, + int show_errno, char *error) +{ + FILE *f; + struct stat finfo; + + ap_log_rerror(APLOG_MARK, show_errno|APLOG_ERR, r, + "%s: %s", error, r->filename); + + if (!conf->logname || + ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0) + && (finfo.st_size > conf->logbytes)) || + ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname), + "a")) == NULL)) { + return ret; + } + + /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */ + fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri, + r->args ? "?" : "", r->args ? r->args : "", r->protocol); + /* "%% 500 /usr/local/apache/cgi-bin */ + fprintf(f, "%%%% %d %s\n", ret, r->filename); + + fprintf(f, "%%error\n%s\n", error); + + ap_pfclose(r->pool, f); + return ret; +} + +static int log_script(request_rec *r, cgi_server_conf * conf, int ret, + char *dbuf, const char *sbuf, BUFF *script_in, BUFF *script_err) +{ + array_header *hdrs_arr = ap_table_elts(r->headers_in); + table_entry *hdrs = (table_entry *) hdrs_arr->elts; + char argsbuffer[HUGE_STRING_LEN]; + FILE *f; + int i; + struct stat finfo; + + if (!conf->logname || + ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0) + && (finfo.st_size > conf->logbytes)) || + ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname), + "a")) == NULL)) { + /* Soak up script output */ + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) + continue; +#if defined(WIN32) || defined(NETWARE) + /* Soak up stderr and redirect it to the error log. + * Script output to stderr is already directed to the error log + * on Unix, thanks to the magic of fork(). + */ + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, + "%s", argsbuffer); + } +#else + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) + continue; +#endif + return ret; + } + + /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */ + fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri, + r->args ? "?" : "", r->args ? r->args : "", r->protocol); + /* "%% 500 /usr/local/apache/cgi-bin" */ + fprintf(f, "%%%% %d %s\n", ret, r->filename); + + fputs("%request\n", f); + for (i = 0; i < hdrs_arr->nelts; ++i) { + if (!hdrs[i].key) + continue; + fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val); + } + if ((r->method_number == M_POST || r->method_number == M_PUT) + && dbuf && *dbuf) { + fprintf(f, "\n%s\n", dbuf); + } + + fputs("%response\n", f); + hdrs_arr = ap_table_elts(r->err_headers_out); + hdrs = (table_entry *) hdrs_arr->elts; + + for (i = 0; i < hdrs_arr->nelts; ++i) { + if (!hdrs[i].key) + continue; + fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val); + } + + if (sbuf && *sbuf) + fprintf(f, "%s\n", sbuf); + + if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) { + fputs("%stdout\n", f); + fputs(argsbuffer, f); + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) + fputs(argsbuffer, f); + fputs("\n", f); + } + + if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) { + fputs("%stderr\n", f); + fputs(argsbuffer, f); + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) + fputs(argsbuffer, f); + fputs("\n", f); + } + + ap_bclose(script_in); + ap_bclose(script_err); + + ap_pfclose(r->pool, f); + return ret; +} + +/**************************************************************** + * + * Actual CGI handling... + */ + + +struct cgi_child_stuff { +#ifdef TPF + TPF_FORK_CHILD t; +#endif + request_rec *r; + int nph; + int debug; + char *argv0; +}; + +static int cgi_child(void *child_stuff, child_info *pinfo) +{ + struct cgi_child_stuff *cld = (struct cgi_child_stuff *) child_stuff; + request_rec *r = cld->r; + char *argv0 = cld->argv0; + int child_pid; + +#ifdef DEBUG_CGI +#ifdef OS2 + /* Under OS/2 need to use device con. */ + FILE *dbg = fopen("con", "w"); +#else + FILE *dbg = fopen("/dev/tty", "w"); +#endif + int i; +#endif + + char **env; + + RAISE_SIGSTOP(CGI_CHILD); +#ifdef DEBUG_CGI + fprintf(dbg, "Attempting to exec %s as %sCGI child (argv0 = %s)\n", + r->filename, cld->nph ? "NPH " : "", argv0); +#endif + + ap_add_cgi_vars(r); + env = ap_create_environment(r->pool, r->subprocess_env); + +#ifdef DEBUG_CGI + fprintf(dbg, "Environment: \n"); + for (i = 0; env[i]; ++i) + fprintf(dbg, "'%s'\n", env[i]); +#endif + +#ifndef WIN32 + ap_chdir_file(r->filename); +#endif + if (!cld->debug) + ap_error_log2stderr(r->server); + + /* Transumute outselves into the script. + * NB only ISINDEX scripts get decoded arguments. + */ + +#ifdef TPF + return (0); +#else + ap_cleanup_for_exec(); + + child_pid = ap_call_exec(r, pinfo, argv0, env, 0); +#if defined(WIN32) || defined(OS2) + return (child_pid); +#else + + /* Uh oh. Still here. Where's the kaboom? There was supposed to be an + * EARTH-shattering kaboom! + * + * Oh, well. Muddle through as best we can... + * + * Note that only stderr is available at this point, so don't pass in + * a server to aplog_error. + */ + + ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed", r->filename); + exit(0); + /* NOT REACHED */ + return (0); +#endif +#endif /* TPF */ +} + +static int cgi_handler(request_rec *r) +{ + int retval, nph, dbpos = 0; + char *argv0, *dbuf = NULL; + BUFF *script_out, *script_in, *script_err; + char argsbuffer[HUGE_STRING_LEN]; + int is_included = !strcmp(r->protocol, "INCLUDED"); + void *sconf = r->server->module_config; + cgi_server_conf *conf = + (cgi_server_conf *) ap_get_module_config(sconf, &cgi_module); + + struct cgi_child_stuff cld; + + if ((argv0 = strrchr(r->filename, '/')) != NULL) + argv0++; + else + argv0 = r->filename; + + nph = !(strncmp(argv0, "nph-", 4)); + + if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r)) + return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO, + "Options ExecCGI is off in this directory"); + if (nph && is_included) + return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO, + "attempt to include NPH CGI script"); + +#if defined(OS2) || defined(WIN32) + /* Allow for cgi files without the .EXE extension on them under OS/2 */ + if (r->finfo.st_mode == 0) { + struct stat statbuf; + char *newfile; + + newfile = ap_pstrcat(r->pool, r->filename, ".EXE", NULL); + + if ((stat(newfile, &statbuf) != 0) || (!S_ISREG(statbuf.st_mode))) { + return log_scripterror(r, conf, NOT_FOUND, 0, + "script not found or unable to stat"); + } else { + r->filename = newfile; + } + } +#else + if (r->finfo.st_mode == 0) + return log_scripterror(r, conf, NOT_FOUND, APLOG_NOERRNO, + "script not found or unable to stat"); +#endif + if (S_ISDIR(r->finfo.st_mode)) + return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO, + "attempt to invoke directory as script"); + if (!ap_suexec_enabled) { + if (!ap_can_exec(&r->finfo)) + return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO, + "file permissions deny server execution"); + } + + if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) + return retval; + + ap_add_common_vars(r); + cld.argv0 = argv0; + cld.r = r; + cld.nph = nph; + cld.debug = conf->logname ? 1 : 0; +#ifdef TPF + cld.t.filename = r->filename; + cld.t.subprocess_env = r->subprocess_env; + cld.t.prog_type = FORK_FILE; +#endif /* TPF */ + +#ifdef CHARSET_EBCDIC + /* The included MIME headers must ALWAYS be in text/ebcdic format. + * Only after reading the MIME headers, we check the Content-Type + * and switch to the necessary conversion mode. + * Until then (and in case an nph- script was called), use the + * configured default conversion: + */ + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out); +#endif /*CHARSET_EBCDIC*/ + + /* + * we spawn out of r->main if it's there so that we can avoid + * waiting for free_proc_chain to cleanup in the middle of an + * SSI request -djg + */ + if (!ap_bspawn_child(r->main ? r->main->pool : r->pool, cgi_child, + (void *) &cld, kill_after_timeout, + &script_out, &script_in, &script_err)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "couldn't spawn child process: %s", r->filename); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Transfer any put/post args, CERN style... + * Note that we already ignore SIGPIPE in the core server. + */ + + if (ap_should_client_block(r)) { + int dbsize, len_read; + + if (conf->logname) { + dbuf = ap_pcalloc(r->pool, conf->bufbytes + 1); + dbpos = 0; + } + + ap_hard_timeout("copy script args", r); + + while ((len_read = + ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)) > 0) { + if (conf->logname) { + if ((dbpos + len_read) > conf->bufbytes) { + dbsize = conf->bufbytes - dbpos; + } + else { + dbsize = len_read; + } + memcpy(dbuf + dbpos, argsbuffer, dbsize); + dbpos += dbsize; + } + ap_reset_timeout(r); + if (ap_bwrite(script_out, argsbuffer, len_read) < len_read) { + /* silly script stopped reading, soak up remaining message */ + while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) { + /* dump it */ + } + break; + } + } + + ap_bflush(script_out); + + ap_kill_timeout(r); + } + + ap_bclose(script_out); + + /* Handle script return... */ + if (script_in && !nph) { + const char *location; + char sbuf[MAX_STRING_LEN]; + int ret; + + if ((ret = ap_scan_script_header_err_buff(r, script_in, sbuf))) { + return log_script(r, conf, ret, dbuf, sbuf, script_in, script_err); + } + + location = ap_table_get(r->headers_out, "Location"); + + if (location && location[0] == '/' && r->status == 200) { + + /* Soak up all the script output */ + ap_hard_timeout("read from script", r); + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) { + continue; + } +#if defined(WIN32) || defined(NETWARE) + /* Soak up stderr and redirect it to the error log. + * Script output to stderr is already directed to the error log + * on Unix, thanks to the magic of fork(). + */ + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, + "%s", argsbuffer); + } +#else + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) { + continue; + } +#endif + ap_kill_timeout(r); + + + /* This redirect needs to be a GET no matter what the original + * method was. + */ + r->method = ap_pstrdup(r->pool, "GET"); + r->method_number = M_GET; + + /* We already read the message body (if any), so don't allow + * the redirected request to think it has one. We can ignore + * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR. + */ + ap_table_unset(r->headers_in, "Content-Length"); + + ap_internal_redirect_handler(location, r); + return OK; + } + else if (location && r->status == 200) { + /* XX Note that if a script wants to produce its own Redirect + * body, it now has to explicitly *say* "Status: 302" + */ + return REDIRECT; + } + + ap_send_http_header(r); + if (!r->header_only) { + ap_send_fb(script_in, r); + } + ap_bclose(script_in); + + ap_soft_timeout("soaking script stderr", r); +#if defined(WIN32) || defined(NETWARE) + /* Script output to stderr is already directed to the error log + * on Unix, thanks to the magic of fork(). + */ + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, + "%s", argsbuffer); + } +#else + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) { + continue; + } +#endif + ap_kill_timeout(r); + ap_bclose(script_err); + } + + if (script_in && nph) { + ap_send_fb(script_in, r); + } + + return OK; /* NOT r->status, even if it has changed. */ +} + +static const handler_rec cgi_handlers[] = +{ + {CGI_MAGIC_TYPE, cgi_handler}, + {"cgi-script", cgi_handler}, + {NULL} +}; + +module MODULE_VAR_EXPORT cgi_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + create_cgi_config, /* server config */ + merge_cgi_config, /* merge server config */ + cgi_cmds, /* command table */ + cgi_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_digest.c b/APACHE_1_3_42/src/modules/standard/mod_digest.c new file mode 100644 index 0000000000..fdb6699ba6 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_digest.c @@ -0,0 +1,433 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_digest: MD5 digest authentication + * + * by Alexei Kosut <akosut@nueva.pvt.k12.ca.us> + * based on mod_auth, by Rob McCool and Robert S. Thau + * + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" +#include "util_md5.h" + +typedef struct digest_config_struct { + char *pwfile; +} digest_config_rec; + +typedef struct digest_header_struct { + char *username; + char *realm; + char *nonce; + char *requested_uri; + char *digest; +} digest_header_rec; + +static void *create_digest_dir_config(pool *p, char *d) +{ + return ap_pcalloc(p, sizeof(digest_config_rec)); +} + +static const char *set_digest_slot(cmd_parms *cmd, void *offset, char *f, char *t) +{ + if (t && strcmp(t, "standard")) + return ap_pstrcat(cmd->pool, "Invalid auth file type: ", t, NULL); + + return ap_set_string_slot(cmd, offset, f); +} + +static const command_rec digest_cmds[] = +{ + {"AuthDigestFile", set_digest_slot, + (void *) XtOffsetOf(digest_config_rec, pwfile), OR_AUTHCFG, TAKE12, NULL}, + {NULL} +}; + +module MODULE_VAR_EXPORT digest_module; + +static char *get_hash(request_rec *r, char *user, char *auth_pwfile) +{ + configfile_t *f; + char l[MAX_STRING_LEN]; + const char *rpw; + char *w, *x; + + if (!(f = ap_pcfg_openfile(r->pool, auth_pwfile))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "Could not open password file: %s", auth_pwfile); + return NULL; + } + while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { + if ((l[0] == '#') || (!l[0])) + continue; + rpw = l; + w = ap_getword(r->pool, &rpw, ':'); + x = ap_getword(r->pool, &rpw, ':'); + + if (x && w && !strcmp(user, w) && !strcmp(ap_auth_name(r), x)) { + ap_cfg_closefile(f); + return ap_pstrdup(r->pool, rpw); + } + } + ap_cfg_closefile(f); + return NULL; +} + +/* Parse the Authorization header, if it exists */ + +static int get_digest_rec(request_rec *r, digest_header_rec * response) +{ + const char *auth_line; + int l; + int s, vk = 0, vv = 0; + const char *t; + char *key, *value; + const char *scheme; + + if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest")) + return DECLINED; + + if (!ap_auth_name(r)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "need AuthName: %s", r->uri); + return SERVER_ERROR; + } + + auth_line = ap_table_get(r->headers_in, + r->proxyreq == STD_PROXY ? "Proxy-Authorization" + : "Authorization"); + if (!auth_line) { + ap_note_digest_auth_failure(r); + return AUTH_REQUIRED; + } + + if (strcasecmp(scheme = ap_getword_white(r->pool, &auth_line), "Digest")) { + /* Client tried to authenticate using wrong auth scheme */ + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, + "client used wrong authentication scheme: %s for %s", + scheme, r->uri); + ap_note_digest_auth_failure(r); + return AUTH_REQUIRED; + } + + l = strlen(auth_line); + + /* Note we don't allocate l + 1 bytes for these deliberately, because + * there has to be at least one '=' character for either of these two + * new strings to be terminated. That takes care of the need for +1. + */ + key = ap_palloc(r->pool, l); + value = ap_palloc(r->pool, l); + + /* There's probably a better way to do this, but for the time being... + * + * Right now the parsing is very 'slack'. Actual rules from RFC 2617 are: + * + * Authorization = "Digest" digest-response + * digest-response = 1#( username | realm | nonce | digest-uri | + * response | [ cnonce ] | [ algorithm ] | + * [opaque] | [message-qop] | [nonce-count] | + * [auth-param] ) (see note 4) + * username = "username" "=" username-value + * username-value = quoted-string + * digest-uri = "uri" "=" digest-uri-value + * digest-uri-value = request-uri + * message-qop = "qop" "=" qop-value + * qop-options = "qop" "=" <"> 1#qop-value <"> (see note 3) + * qop-value = "auth" | "auth-int" | token + * cnonce = "cnonce" "=" cnonce-value + * cnonce-value = nonce-value + * nonce-count = "nc" "=" nc-value + * nc-value = 8LHEX + * response = "response" "=" response-digest + * response-digest = <"> *LHEX <"> + * LHEX = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | + * "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f" + * + * Current Discrepancies: + * quoted-string section 2.2 of RFC 2068 + * --> We also acccept unquoted strings or strings + * like foo" bar". And take a space, comma or EOL as + * the terminator in that case. + * + * request-uri section 5.1 of RFC 2068 + * --> We currently also accept any quoted string - and + * ignore those quotes. + * + * response/entity-digest + * --> We ignore the presense of the " if any. + * + * Note: There is an inherent problem with the request URI; as it should + * be used unquoted - yet may contain a ',' - which is used as + * a terminator: + * Authorization: Digest username="dirkx", realm="DAV", nonce="1031662894", + * uri=/mary,+dirkx,+peter+and+mary.ics, response="99a6275793be28c31a5b6e4467fa4c79", + * algorithm=MD5 + * + * Note3: Taken from section 3.2.1 - as this is not actually defined in section 3.2.2 + * which deals with the Authorization Request Header. + * + * Note4: The 'comma separated' list concept is refered to in the RFC + * but whitespace eating and other such things are assumed to be + * as per MIME/RFC2068 spec. + */ + +#define D_KEY 0 +#define D_VALUE 1 +#define D_STRING 2 +#define D_EXIT -1 + + s = D_KEY; + while (s != D_EXIT) { + switch (s) { + case D_STRING: + if (auth_line[0] == '\"') { + s = D_VALUE; + } + else { + value[vv] = auth_line[0]; + vv++; + } + auth_line++; + break; + + case D_VALUE: + /* A request URI may be unquoted and yet + * contain non alpha/num chars. (Though gets terminated by + * a ',' - which in fact may be in the URI - so I guess + * 2069 should be updated to suggest strongly to quote). + */ + if (auth_line[0] == '\"') { + s = D_STRING; + } + else if ((auth_line[0] != ',') && (auth_line[0] != ' ') && (auth_line[0] != '\0')) { + value[vv] = auth_line[0]; + vv++; + } + else { + value[vv] = '\0'; + + if (!strcasecmp(key, "username")) + response->username = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "realm")) + response->realm = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "nonce")) + response->nonce = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "uri")) + response->requested_uri = ap_pstrdup(r->pool, value); + else if (!strcasecmp(key, "response")) + response->digest = ap_pstrdup(r->pool, value); + + vv = 0; + s = D_KEY; + } + auth_line++; + break; + + case D_KEY: + if (ap_isalnum(auth_line[0])) { + key[vk] = auth_line[0]; + vk++; + } + else if (auth_line[0] == '=') { + key[vk] = '\0'; + vk = 0; + s = D_VALUE; + } + auth_line++; + break; + } + + if (auth_line[-1] == '\0') + s = D_EXIT; + } + + if (!response->username || !response->realm || !response->nonce || + !response->requested_uri || !response->digest) { + ap_note_digest_auth_failure(r); + return AUTH_REQUIRED; + } + + r->connection->user = response->username; + r->connection->ap_auth_type = "Digest"; + + return OK; +} + +/* The actual MD5 code... whee */ + +/* Check that a given nonce is actually one which was + * issued by this server in the right context. + */ +static int check_nonce(pool *p, const char *prefix, const char *nonce) { + char *timestamp = (char *)nonce + 2 * MD5_DIGESTSIZE; + char *md5; + + if (strlen(nonce) < 2 * MD5_DIGESTSIZE) + return AUTH_REQUIRED; + + md5 = ap_md5(p, (unsigned char *)ap_pstrcat(p, prefix, timestamp, NULL)); + + return strncmp(md5, nonce, 2 * MD5_DIGESTSIZE); +} + +/* Check the digest itself. + */ +static char *find_digest(request_rec *r, digest_header_rec * h, char *a1) +{ + return ap_md5(r->pool, + (unsigned char *)ap_pstrcat(r->pool, a1, ":", h->nonce, ":", + ap_md5(r->pool, + (unsigned char *)ap_pstrcat(r->pool, r->method, ":", + h->requested_uri, NULL)), + NULL)); +} + +/* These functions return 0 if client is OK, and proper error status + * if not... either AUTH_REQUIRED, if we made a check, and it failed, or + * SERVER_ERROR, if things are so totally confused that we couldn't + * figure out how to tell if the client is authorized or not. + * + * If they return DECLINED, and all other modules also decline, that's + * treated by the server core as a configuration error, logged and + * reported as such. + */ + +/* Determine user ID, and check if it really is that user, for HTTP + * basic authentication... + */ + +static int authenticate_digest_user(request_rec *r) +{ + digest_config_rec *sec = + (digest_config_rec *) ap_get_module_config(r->per_dir_config, + &digest_module); + digest_header_rec *response = ap_pcalloc(r->pool, sizeof(digest_header_rec)); + conn_rec *c = r->connection; + char *a1; + int res; + + if ((res = get_digest_rec(r, response))) + return res; + + if (!sec->pwfile) + return DECLINED; + + /* Check that the nonce was one we actually issued. */ + if (check_nonce(r->pool, ap_auth_nonce(r), response->nonce)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Client is using a nonce which was not issued by " + "this server for this context: %s", r->uri); + ap_note_digest_auth_failure(r); + return AUTH_REQUIRED; + } + + if (!(a1 = get_hash(r, c->user, sec->pwfile))) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user %s not found: %s", c->user, r->uri); + ap_note_digest_auth_failure(r); + return AUTH_REQUIRED; + } + if (strcmp(response->digest, find_digest(r, response, a1))) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user %s: password mismatch: %s", c->user, r->uri); + ap_note_digest_auth_failure(r); + return AUTH_REQUIRED; + } + return OK; +} + +/* Checking ID */ + +static int digest_check_auth(request_rec *r) +{ + char *user = r->connection->user; + int m = r->method_number; + int method_restricted = 0; + register int x; + const char *t; + char *w; + const array_header *reqs_arr; + require_line *reqs; + + if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest")) + return DECLINED; + + reqs_arr = ap_requires(r); + /* If there is no "requires" directive, + * then any user will do. + */ + if (!reqs_arr) + return OK; + reqs = (require_line *) reqs_arr->elts; + + for (x = 0; x < reqs_arr->nelts; x++) { + + if (!(reqs[x].method_mask & (1 << m))) + continue; + + method_restricted = 1; + + t = reqs[x].requirement; + w = ap_getword_white(r->pool, &t); + if (!strcmp(w, "valid-user")) + return OK; + else if (!strcmp(w, "user")) { + while (t[0]) { + w = ap_getword_conf(r->pool, &t); + if (!strcmp(user, w)) + return OK; + } + } + else + return DECLINED; + } + + if (!method_restricted) + return OK; + + ap_note_digest_auth_failure(r); + return AUTH_REQUIRED; +} + +module MODULE_VAR_EXPORT digest_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_digest_dir_config, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + digest_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + authenticate_digest_user, /* check_user_id */ + digest_check_auth, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; + diff --git a/APACHE_1_3_42/src/modules/standard/mod_dir.c b/APACHE_1_3_42/src/modules/standard/mod_dir.c new file mode 100644 index 0000000000..29e003974f --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_dir.c @@ -0,0 +1,206 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_dir.c: handle default index files, and trailing-/ redirects + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_request.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" + +module MODULE_VAR_EXPORT dir_module; + +typedef struct dir_config_struct { + array_header *index_names; +} dir_config_rec; + +#define DIR_CMD_PERMS OR_INDEXES + +static const char *add_index(cmd_parms *cmd, void *dummy, char *arg) +{ + dir_config_rec *d = dummy; + + if (!d->index_names) { + d->index_names = ap_make_array(cmd->pool, 2, sizeof(char *)); + } + *(char **)ap_push_array(d->index_names) = arg; + return NULL; +} + +static const command_rec dir_cmds[] = +{ + {"DirectoryIndex", add_index, NULL, + DIR_CMD_PERMS, ITERATE, + "a list of file names"}, + {NULL} +}; + +static void *create_dir_config(pool *p, char *dummy) +{ + dir_config_rec *new = + (dir_config_rec *) ap_pcalloc(p, sizeof(dir_config_rec)); + + new->index_names = NULL; + return (void *) new; +} + +static void *merge_dir_configs(pool *p, void *basev, void *addv) +{ + dir_config_rec *new = (dir_config_rec *) ap_pcalloc(p, sizeof(dir_config_rec)); + dir_config_rec *base = (dir_config_rec *) basev; + dir_config_rec *add = (dir_config_rec *) addv; + + new->index_names = add->index_names ? add->index_names : base->index_names; + return new; +} + +static int handle_dir(request_rec *r) +{ + dir_config_rec *d = + (dir_config_rec *) ap_get_module_config(r->per_dir_config, + &dir_module); + char *dummy_ptr[1]; + char **names_ptr; + int num_names; + int error_notfound = 0; + + if (r->uri[0] == '\0' || r->uri[strlen(r->uri) - 1] != '/') { + char *ifile; + if (r->args != NULL) + ifile = ap_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri), + "/", "?", r->args, NULL); + else + ifile = ap_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri), + "/", NULL); + + ap_table_setn(r->headers_out, "Location", + ap_construct_url(r->pool, ifile, r)); + return HTTP_MOVED_PERMANENTLY; + } + + /* KLUDGE --- make the sub_req lookups happen in the right directory. + * Fixing this in the sub_req_lookup functions themselves is difficult, + * and would probably break virtual includes... + */ + + if (r->filename[strlen(r->filename) - 1] != '/') { + r->filename = ap_pstrcat(r->pool, r->filename, "/", NULL); + } + + if (d->index_names) { + names_ptr = (char **)d->index_names->elts; + num_names = d->index_names->nelts; + } + else { + dummy_ptr[0] = DEFAULT_INDEX; + names_ptr = dummy_ptr; + num_names = 1; + } + + for (; num_names; ++names_ptr, --num_names) { + char *name_ptr = *names_ptr; + request_rec *rr = ap_sub_req_lookup_uri(name_ptr, r); + + if (rr->status == HTTP_OK && S_ISREG(rr->finfo.st_mode)) { + char *new_uri = ap_escape_uri(r->pool, rr->uri); + + if (rr->args != NULL) + new_uri = ap_pstrcat(r->pool, new_uri, "?", rr->args, NULL); + else if (r->args != NULL) + new_uri = ap_pstrcat(r->pool, new_uri, "?", r->args, NULL); + + ap_destroy_sub_req(rr); + ap_internal_redirect(new_uri, r); + return OK; + } + + /* If the request returned a redirect, propagate it to the client */ + + if (ap_is_HTTP_REDIRECT(rr->status) || + (rr->status == HTTP_NOT_ACCEPTABLE && num_names == 1) || + (rr->status == HTTP_UNAUTHORIZED && num_names == 1)) { + + ap_pool_join(r->pool, rr->pool); + error_notfound = rr->status; + r->notes = ap_overlay_tables(r->pool, r->notes, rr->notes); + r->headers_out = ap_overlay_tables(r->pool, r->headers_out, + rr->headers_out); + r->err_headers_out = ap_overlay_tables(r->pool, r->err_headers_out, + rr->err_headers_out); + return error_notfound; + } + + /* If the request returned something other than 404 (or 200), + * it means the module encountered some sort of problem. To be + * secure, we should return the error, rather than create + * along a (possibly unsafe) directory index. + * + * So we store the error, and if none of the listed files + * exist, we return the last error response we got, instead + * of a directory listing. + */ + if (rr->status && rr->status != HTTP_NOT_FOUND && rr->status != HTTP_OK) + error_notfound = rr->status; + + ap_destroy_sub_req(rr); + } + + if (error_notfound) + return error_notfound; + + if (r->method_number != M_GET) + return DECLINED; + + /* nothing for us to do, pass on through */ + + return DECLINED; +} + + +static const handler_rec dir_handlers[] = +{ + {DIR_MAGIC_TYPE, handle_dir}, + {NULL} +}; + +module MODULE_VAR_EXPORT dir_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_dir_config, /* dir config creater */ + merge_dir_configs, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + dir_cmds, /* command table */ + dir_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_env.c b/APACHE_1_3_42/src/modules/standard/mod_env.c new file mode 100644 index 0000000000..b3889b81fa --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_env.c @@ -0,0 +1,238 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_env.c + * version 0.0.5 + * status beta + * Pass environment variables to CGI/SSI scripts. + * + * Andrew Wilson <Andrew.Wilson@cm.cf.ac.uk> 06.Dec.95 + * + * Change log: + * 08.Dec.95 Now allows PassEnv directive to appear more than once in + * conf files. + * 10.Dec.95 optimisation. getenv() only called at startup and used + * to build a fast-to-access table. table used to build + * per-server environment for each request. + * robustness. better able to handle errors in configuration + * files: + * 1) PassEnv directive present, but no environment variable listed + * 2) PassEnv FOO present, but $FOO not present in environment + * 3) no PassEnv directive present + * 23.Dec.95 Now allows SetEnv directive with same semantics as 'sh' setenv: + * SetEnv Var sets Var to the empty string + * SetEnv Var Val sets Var to the value Val + * Values containing whitespace should be quoted, eg: + * SetEnv Var "this is some text" + * Environment variables take their value from the last instance + * of PassEnv / SetEnv to be reached in the configuration file. + * For example, the sequence: + * PassEnv FOO + * SetEnv FOO override + * Causes FOO to take the value 'override'. + * 23.Feb.96 Added UnsetEnv directive to allow environment variables + * to be removed. + * Virtual hosts now 'inherit' parent server environment which + * they're able to overwrite with their own directives or + * selectively ignore with UnsetEnv. + * *** IMPORTANT - the way that virtual hosts inherit their *** + * *** environment variables from the default server's *** + * *** configuration has changed. You should test your *** + * *** configuration carefully before accepting this *** + * *** version of the module in a live webserver which used *** + * *** older versions of the module. *** + */ + +#include "httpd.h" +#include "http_config.h" + +typedef struct { + table *vars; + char *unsetenv; + int vars_present; +} env_dir_config_rec; + +module MODULE_VAR_EXPORT env_module; + +static void *create_env_dir_config(pool *p, char *dummy) +{ + env_dir_config_rec *new = + (env_dir_config_rec *) ap_palloc(p, sizeof(env_dir_config_rec)); + new->vars = ap_make_table(p, 50); + new->unsetenv = ""; + new->vars_present = 0; + return (void *) new; +} + +static void *merge_env_dir_configs(pool *p, void *basev, void *addv) +{ + env_dir_config_rec *base = (env_dir_config_rec *) basev; + env_dir_config_rec *add = (env_dir_config_rec *) addv; + env_dir_config_rec *new = + (env_dir_config_rec *) ap_palloc(p, sizeof(env_dir_config_rec)); + + table *new_table; + table_entry *elts; + array_header *arr; + + int i; + const char *uenv, *unset; + + /* + * new_table = copy_table( p, base->vars ); + * foreach $element ( @add->vars ) { + * table_set( new_table, $element.key, $element.val ); + * }; + * foreach $unsetenv ( @UNSETENV ) { + * table_unset( new_table, $unsetenv ); + * } + */ + + new_table = ap_copy_table(p, base->vars); + + arr = ap_table_elts(add->vars); + elts = (table_entry *)arr->elts; + + for (i = 0; i < arr->nelts; ++i) { + ap_table_setn(new_table, elts[i].key, elts[i].val); + } + + unset = add->unsetenv; + uenv = ap_getword_conf(p, &unset); + while (uenv[0] != '\0') { + ap_table_unset(new_table, uenv); + uenv = ap_getword_conf(p, &unset); + } + + new->vars = new_table; + + new->vars_present = base->vars_present || add->vars_present; + + return new; +} + +static const char *add_env_module_vars_passed(cmd_parms *cmd, + env_dir_config_rec *sconf, + const char *arg) +{ + table *vars = sconf->vars; + char *env_var; + char *name_ptr; + + while (*arg) { + name_ptr = ap_getword_conf(cmd->pool, &arg); + env_var = getenv(name_ptr); + if (env_var != NULL) { + sconf->vars_present = 1; + ap_table_setn(vars, name_ptr, ap_pstrdup(cmd->pool, env_var)); + } + } + return NULL; +} + +static const char *add_env_module_vars_set(cmd_parms *cmd, + env_dir_config_rec *sconf, + const char *arg) +{ + table *vars = sconf->vars; + char *name, *value; + + name = ap_getword_conf(cmd->pool, &arg); + value = ap_getword_conf(cmd->pool, &arg); + + /* name is mandatory, value is optional. no value means + * set the variable to an empty string + */ + + + if ((*name == '\0') || (*arg != '\0')) { + return "SetEnv takes one or two arguments. An environment variable name and an optional value to pass to CGI."; + } + + sconf->vars_present = 1; + ap_table_setn(vars, name, value); + + return NULL; +} + +static const char *add_env_module_vars_unset(cmd_parms *cmd, + env_dir_config_rec *sconf, + char *arg) +{ + sconf->unsetenv = sconf->unsetenv ? + ap_pstrcat(cmd->pool, sconf->unsetenv, " ", arg, NULL) : + arg; + + if (sconf->vars_present && !cmd->path) { + /* if {Set,Pass}Env FOO, UnsetEnv FOO + * are in the base config, merge never happens, + * unset never happens, so just unset now + */ + ap_table_unset(sconf->vars, arg); + } + + return NULL; +} + +static const command_rec env_module_cmds[] = +{ + {"PassEnv", add_env_module_vars_passed, NULL, + OR_FILEINFO, RAW_ARGS, "a list of environment variables to pass to CGI."}, + {"SetEnv", add_env_module_vars_set, NULL, + OR_FILEINFO, RAW_ARGS, "an environment variable name and a value to pass to CGI."}, + {"UnsetEnv", add_env_module_vars_unset, NULL, + OR_FILEINFO, RAW_ARGS, "a list of variables to remove from the CGI environment."}, + {NULL}, +}; + +static int fixup_env_module(request_rec *r) +{ + table *e = r->subprocess_env; + env_dir_config_rec *sconf = ap_get_module_config(r->per_dir_config, + &env_module); + table *vars = sconf->vars; + + if (!sconf->vars_present) + return DECLINED; + + r->subprocess_env = ap_overlay_tables(r->pool, e, vars); + + return OK; +} + +module MODULE_VAR_EXPORT env_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_env_dir_config, /* dir config creater */ + merge_env_dir_configs, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server configs */ + env_module_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + fixup_env_module, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_expires.c b/APACHE_1_3_42/src/modules/standard/mod_expires.c new file mode 100644 index 0000000000..30bf8f5478 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_expires.c @@ -0,0 +1,473 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_expires.c + * version 0.0.11 + * status beta + * + * Andrew Wilson <Andrew.Wilson@cm.cf.ac.uk> 26.Jan.96 + * + * This module allows you to control the form of the Expires: header + * that Apache issues for each access. Directives can appear in + * configuration files or in .htaccess files so expiry semantics can + * be defined on a per-directory basis. + * + * DIRECTIVE SYNTAX + * + * Valid directives are: + * + * ExpiresActive on | off + * ExpiresDefault <code><seconds> + * ExpiresByType type/encoding <code><seconds> + * + * Valid values for <code> are: + * + * 'M' expires header shows file modification date + <seconds> + * 'A' expires header shows access time + <seconds> + * + * [I'm not sure which of these is best under different + * circumstances, I guess it's for other people to explore. + * The effects may be indistinguishable for a number of cases] + * + * <seconds> should be an integer value [acceptable to atoi()] + * + * There is NO space between the <code> and <seconds>. + * + * For example, a directory which contains information which changes + * frequently might contain: + * + * # reports generated by cron every hour. don't let caches + * # hold onto stale information + * ExpiresDefault M3600 + * + * Another example, our html pages can change all the time, the gifs + * tend not to change often: + * + * # pages are hot (1 week), images are cold (1 month) + * ExpiresByType text/html A604800 + * ExpiresByType image/gif A2592000 + * + * Expires can be turned on for all URLs on the server by placing the + * following directive in a conf file: + * + * ExpiresActive on + * + * ExpiresActive can also appear in .htaccess files, enabling the + * behaviour to be turned on or off for each chosen directory. + * + * # turn off Expires behaviour in this directory + * # and subdirectories + * ExpiresActive off + * + * Directives defined for a directory are valid in subdirectories + * unless explicitly overridden by new directives in the subdirectory + * .htaccess files. + * + * ALTERNATIVE DIRECTIVE SYNTAX + * + * Directives can also be defined in a more readable syntax of the form: + * + * ExpiresDefault "<base> [plus] {<num> <type>}*" + * ExpiresByType type/encoding "<base> [plus] {<num> <type>}*" + * + * where <base> is one of: + * access + * now equivalent to 'access' + * modification + * + * where the 'plus' keyword is optional + * + * where <num> should be an integer value [acceptable to atoi()] + * + * where <type> is one of: + * years + * months + * weeks + * days + * hours + * minutes + * seconds + * + * For example, any of the following directives can be used to make + * documents expire 1 month after being accessed, by default: + * + * ExpiresDefault "access plus 1 month" + * ExpiresDefault "access plus 4 weeks" + * ExpiresDefault "access plus 30 days" + * + * The expiry time can be fine-tuned by adding several '<num> <type>' + * clauses: + * + * ExpiresByType text/html "access plus 1 month 15 days 2 hours" + * ExpiresByType image/gif "modification plus 5 hours 3 minutes" + * + * --- + * + * Change-log: + * 29.Jan.96 Hardened the add_* functions. Server will now bail out + * if bad directives are given in the conf files. + * 02.Feb.96 Returns DECLINED if not 'ExpiresActive on', giving other + * expires-aware modules a chance to play with the same + * directives. [Michael Rutman] + * 03.Feb.96 Call tzset() before localtime(). Trying to get the module + * to work properly in non GMT timezones. + * 12.Feb.96 Modified directive syntax to allow more readable commands: + * ExpiresDefault "now plus 10 days 20 seconds" + * ExpiresDefault "access plus 30 days" + * ExpiresDefault "modification plus 1 year 10 months 30 days" + * 13.Feb.96 Fix call to table_get() with NULL 2nd parameter [Rob Hartill] + * 19.Feb.96 Call gm_timestr_822() to get time formatted correctly, can't + * rely on presence of HTTP_TIME_FORMAT in Apache 1.1+. + * 21.Feb.96 This version (0.0.9) reverses assumptions made in 0.0.8 + * about star/star handlers. Reverting to 0.0.7 behaviour. + * 08.Jun.96 allows ExpiresDefault to be used with responses that use + * the DefaultType by not DECLINING, but instead skipping + * the table_get check and then looking for an ExpiresDefault. + * [Rob Hartill] + * 04.Nov.96 'const' definitions added. + * + * TODO + * add support for Cache-Control: max-age=20 from the HTTP/1.1 + * proposal (in this case, a ttl of 20 seconds) [ask roy] + * add per-file expiry and explicit expiry times - duplicates some + * of the mod_cern_meta.c functionality. eg: + * ExpiresExplicit index.html "modification plus 30 days" + * + * BUGS + * Hi, welcome to the internet. + */ + +#include <ctype.h> +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" + +typedef struct { + int active; + char *expiresdefault; + table *expiresbytype; +} expires_dir_config; + +/* from mod_dir, why is this alias used? + */ +#define DIR_CMD_PERMS OR_INDEXES + +#define ACTIVE_ON 1 +#define ACTIVE_OFF 0 +#define ACTIVE_DONTCARE 2 + +module MODULE_VAR_EXPORT expires_module; + +static void *create_dir_expires_config(pool *p, char *dummy) +{ + expires_dir_config *new = + (expires_dir_config *) ap_pcalloc(p, sizeof(expires_dir_config)); + new->active = ACTIVE_DONTCARE; + new->expiresdefault = ""; + new->expiresbytype = ap_make_table(p, 4); + return (void *) new; +} + +static const char *set_expiresactive(cmd_parms *cmd, expires_dir_config * dir_config, int arg) +{ + /* if we're here at all it's because someone explicitly + * set the active flag + */ + dir_config->active = ACTIVE_ON; + if (arg == 0) { + dir_config->active = ACTIVE_OFF; + }; + return NULL; +} + +/* check_code() parse 'code' and return NULL or an error response + * string. If we return NULL then real_code contains code converted + * to the cnnnn format. + */ +static char *check_code(pool *p, const char *code, char **real_code) +{ + char *word; + char base = 'X'; + int modifier = 0; + int num = 0; + int factor = 0; + + /* 0.0.4 compatibility? + */ + if ((code[0] == 'A') || (code[0] == 'M')) { + *real_code = (char *)code; + return NULL; + }; + + /* <base> [plus] {<num> <type>}* + */ + + /* <base> + */ + word = ap_getword_conf(p, &code); + if (!strncasecmp(word, "now", 1) || + !strncasecmp(word, "access", 1)) { + base = 'A'; + } + else if (!strncasecmp(word, "modification", 1)) { + base = 'M'; + } + else { + return ap_pstrcat(p, "bad expires code, unrecognised <base> '", + word, "'", NULL); + }; + + /* [plus] + */ + word = ap_getword_conf(p, &code); + if (!strncasecmp(word, "plus", 1)) { + word = ap_getword_conf(p, &code); + }; + + /* {<num> <type>}* + */ + while (word[0]) { + /* <num> + */ + if (ap_isdigit(word[0])) { + num = atoi(word); + } + else { + return ap_pstrcat(p, "bad expires code, numeric value expected <num> '", + word, "'", NULL); + }; + + /* <type> + */ + word = ap_getword_conf(p, &code); + if (word[0]) { + /* do nothing */ + } + else { + return ap_pstrcat(p, "bad expires code, missing <type>", NULL); + }; + + factor = 0; + if (!strncasecmp(word, "years", 1)) { + factor = 60 * 60 * 24 * 365; + } + else if (!strncasecmp(word, "months", 2)) { + factor = 60 * 60 * 24 * 30; + } + else if (!strncasecmp(word, "weeks", 1)) { + factor = 60 * 60 * 24 * 7; + } + else if (!strncasecmp(word, "days", 1)) { + factor = 60 * 60 * 24; + } + else if (!strncasecmp(word, "hours", 1)) { + factor = 60 * 60; + } + else if (!strncasecmp(word, "minutes", 2)) { + factor = 60; + } + else if (!strncasecmp(word, "seconds", 1)) { + factor = 1; + } + else { + return ap_pstrcat(p, "bad expires code, unrecognised <type>", + "'", word, "'", NULL); + }; + + modifier = modifier + factor * num; + + /* next <num> + */ + word = ap_getword_conf(p, &code); + }; + + *real_code = ap_psprintf(p, "%c%d", base, modifier); + + return NULL; +} + +static const char *set_expiresbytype(cmd_parms *cmd, expires_dir_config * dir_config, char *mime, char *code) +{ + char *response, *real_code; + + if ((response = check_code(cmd->pool, code, &real_code)) == NULL) { + ap_table_setn(dir_config->expiresbytype, mime, real_code); + return NULL; + }; + return ap_pstrcat(cmd->pool, + "'ExpiresByType ", mime, " ", code, "': ", response, NULL); +} + +static const char *set_expiresdefault(cmd_parms *cmd, expires_dir_config * dir_config, char *code) +{ + char *response, *real_code; + + if ((response = check_code(cmd->pool, code, &real_code)) == NULL) { + dir_config->expiresdefault = real_code; + return NULL; + }; + return ap_pstrcat(cmd->pool, + "'ExpiresDefault ", code, "': ", response, NULL); +} + +static const command_rec expires_cmds[] = +{ + {"ExpiresActive", set_expiresactive, NULL, DIR_CMD_PERMS, FLAG, + "Limited to 'on' or 'off'"}, + {"ExpiresBytype", set_expiresbytype, NULL, DIR_CMD_PERMS, TAKE2, + "a MIME type followed by an expiry date code"}, + {"ExpiresDefault", set_expiresdefault, NULL, DIR_CMD_PERMS, TAKE1, + "an expiry date code"}, + {NULL} +}; + +static void *merge_expires_dir_configs(pool *p, void *basev, void *addv) +{ + expires_dir_config *new = (expires_dir_config *) ap_pcalloc(p, sizeof(expires_dir_config)); + expires_dir_config *base = (expires_dir_config *) basev; + expires_dir_config *add = (expires_dir_config *) addv; + + if (add->active == ACTIVE_DONTCARE) { + new->active = base->active; + } + else { + new->active = add->active; + }; + + if (add->expiresdefault != '\0') { + new->expiresdefault = add->expiresdefault; + }; + + new->expiresbytype = ap_overlay_tables(p, add->expiresbytype, + base->expiresbytype); + return new; +} + +static int add_expires(request_rec *r) +{ + expires_dir_config *conf; + char *code; + time_t base; + time_t additional; + time_t expires; + char age[20]; + + if (ap_is_HTTP_ERROR(r->status)) /* Don't add Expires headers to errors */ + return DECLINED; + + if (r->main != NULL) /* Say no to subrequests */ + return DECLINED; + + conf = (expires_dir_config *) ap_get_module_config(r->per_dir_config, &expires_module); + if (conf == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "internal error: %s", r->filename); + return SERVER_ERROR; + }; + + if (conf->active != ACTIVE_ON) + return DECLINED; + + /* we perhaps could use the default_type(r) in its place but that + * may be 2nd guesing the desired configuration... calling table_get + * with a NULL key will SEGV us + * + * I still don't know *why* r->content_type would ever be NULL, this + * is possibly a result of fixups being called in many different + * places. Fixups is probably the wrong place to be doing all this + * work... Bah. + * + * Changed as of 08.Jun.96 don't DECLINE, look for an ExpiresDefault. + */ + if (r->content_type == NULL) + code = NULL; + else + code = (char *) ap_table_get(conf->expiresbytype, + ap_field_noparam(r->pool, r->content_type)); + + if (code == NULL) { + /* no expires defined for that type, is there a default? */ + code = conf->expiresdefault; + + if (code[0] == '\0') + return OK; + }; + + /* we have our code */ + + switch (code[0]) { + case 'M': + if (r->finfo.st_mode == 0) { + /* file doesn't exist on disk, so we can't do anything based on + * modification time. Note that this does _not_ log an error. + */ + return DECLINED; + } + base = r->finfo.st_mtime; + additional = atoi(&code[1]); + break; + case 'A': + /* there's been some discussion and it's possible that + * 'access time' will be stored in request structure + */ + base = r->request_time; + additional = atoi(&code[1]); + break; + default: + /* expecting the add_* routines to be case-hardened this + * is just a reminder that module is beta + */ + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "internal error: bad expires code: %s", r->filename); + return SERVER_ERROR; + }; + + expires = base + additional; + ap_snprintf(age, sizeof(age), "max-age=%d", + (int) expires - (int) r->request_time); + ap_table_mergen(r->headers_out, "Cache-Control", ap_pstrdup(r->pool, age)); + tzset(); /* redundant? called implicitly by localtime, + * at least under FreeBSD + */ + ap_table_setn(r->headers_out, "Expires", + ap_gm_timestr_822(r->pool, expires)); + return OK; +} + +module MODULE_VAR_EXPORT expires_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_dir_expires_config, /* dir config creater */ + merge_expires_dir_configs, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server configs */ + expires_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + add_expires, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; + diff --git a/APACHE_1_3_42/src/modules/standard/mod_headers.c b/APACHE_1_3_42/src/modules/standard/mod_headers.c new file mode 100644 index 0000000000..946fb34334 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_headers.c @@ -0,0 +1,235 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_headers.c: Add/append/remove HTTP response headers + * Written by Paul Sutton, paul@ukweb.com, 1 Oct 1996 + * + * New directive, Header, can be used to add/replace/remove HTTP headers. + * Valid in both per-server and per-dir configurations. + * + * Syntax is: + * + * Header action header value + * + * Where action is one of: + * set - set this header, replacing any old value + * add - add this header, possible resulting in two or more + * headers with the same name + * append - append this text onto any existing header of this same + * unset - remove this header + * + * Where action is unset, the third argument (value) should not be given. + * The header name can include the colon, or not. + * + * The Header directive can only be used where allowed by the FileInfo + * override. + * + * When the request is processed, the header directives are processed in + * this order: firstly, the main server, then the virtual server handling + * this request (if any), then any <Directory> sections (working downwards + * from the root dir), then an <Location> sections (working down from + * shortest URL component), the any <File> sections. This order is + * important if any 'set' or 'unset' actions are used. For example, + * the following two directives have different effect if applied in + * the reverse order: + * + * Header append Author "John P. Doe" + * Header unset Author + * + * Examples: + * + * To set the "Author" header, use + * Header add Author "John P. Doe" + * + * To remove a header: + * Header unset Author + * + */ + +#include "httpd.h" +#include "http_config.h" + +typedef enum { + hdr_add = 'a', /* add header (could mean multiple hdrs) */ + hdr_set = 's', /* set (replace old value) */ + hdr_append = 'm', /* append (merge into any old value) */ + hdr_unset = 'u' /* unset header */ +} hdr_actions; + +typedef struct { + hdr_actions action; + char *header; + char *value; + int do_err; +} header_entry; + +/* + * headers_conf is our per-module configuration. This is used as both + * a per-dir and per-server config + */ +typedef struct { + array_header *headers; +} headers_conf; + +module MODULE_VAR_EXPORT headers_module; + +static void *create_headers_config(pool *p, server_rec *s) +{ + headers_conf *a = + (headers_conf *) ap_pcalloc(p, sizeof(headers_conf)); + + a->headers = ap_make_array(p, 2, sizeof(header_entry)); + return a; +} + +static void *create_headers_dir_config(pool *p, char *d) +{ + return (headers_conf *) create_headers_config(p, NULL); +} + +static void *merge_headers_config(pool *p, void *basev, void *overridesv) +{ + headers_conf *a = + (headers_conf *) ap_pcalloc(p, sizeof(headers_conf)); + headers_conf *base = (headers_conf *) basev, *overrides = (headers_conf *) overridesv; + + a->headers = ap_append_arrays(p, base->headers, overrides->headers); + + return a; +} + +static const char *header_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); + } + + if (cmd->info) { + new->do_err = 1; + } else { + new->do_err = 0; + } + + 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, (void *)0, OR_FILEINFO, TAKE23, + "an action, header and value"}, + {"ErrorHeader", header_cmd, (void *)1, OR_FILEINFO, TAKE23, + "an action, header and value"}, + {NULL} +}; + +static void do_headers_fixup(request_rec *r, array_header *headers) +{ + int i; + + for (i = 0; i < headers->nelts; ++i) { + header_entry *hdr = &((header_entry *) (headers->elts))[i]; + table *tbl = (hdr->do_err ? r->err_headers_out : r->headers_out); + switch (hdr->action) { + case hdr_add: + ap_table_addn(tbl, hdr->header, hdr->value); + break; + case hdr_append: + ap_table_mergen(tbl, hdr->header, hdr->value); + break; + case hdr_set: + ap_table_setn(tbl, hdr->header, hdr->value); + break; + case hdr_unset: + ap_table_unset(tbl, hdr->header); + break; + } + } + +} + +static int fixup_headers(request_rec *r) +{ + void *sconf = r->server->module_config; + headers_conf *serverconf = + (headers_conf *) ap_get_module_config(sconf, &headers_module); + void *dconf = r->per_dir_config; + headers_conf *dirconf = + (headers_conf *) ap_get_module_config(dconf, &headers_module); + + do_headers_fixup(r, serverconf->headers); + do_headers_fixup(r, dirconf->headers); + + return DECLINED; +} + +module MODULE_VAR_EXPORT headers_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_headers_dir_config, /* dir config creater */ + merge_headers_config, /* dir merger --- default is to override */ + create_headers_config, /* server config */ + merge_headers_config, /* merge server configs */ + headers_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + fixup_headers, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; + + diff --git a/APACHE_1_3_42/src/modules/standard/mod_imap.c b/APACHE_1_3_42/src/modules/standard/mod_imap.c new file mode 100644 index 0000000000..ff2d2848e1 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_imap.c @@ -0,0 +1,884 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This imagemap module started as a port of the original imagemap.c + * written by Rob McCool (11/13/93 robm@ncsa.uiuc.edu). + * This version includes the mapping algorithms found in version 1.3 + * of imagemap.c. + * + * Contributors to this code include: + * + * Kevin Hughes, kevinh@pulua.hcc.hawaii.edu + * + * Eric Haines, erich@eye.com + * "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com + * + * Randy Terbush, randy@zyzzyva.com + * port to Apache module format, "base_uri" and support for relative URLs + * + * James H. Cloos, Jr., cloos@jhcloos.com + * Added point datatype, using code in NCSA's version 1.8 imagemap.c + * program, as distributed with version 1.4.1 of their server. + * The point code is originally added by Craig Milo Rogers, Rogers@ISI.Edu + * + * Nathan Kurz, nate@tripod.com + * Rewrite/reorganization. New handling of default, base and relative URLs. + * New Configuration directives: + * ImapMenu {none, formatted, semiformatted, unformatted} + * ImapDefault {error, nocontent, referer, menu, URL} + * ImapBase {map, referer, URL} + * Support for creating non-graphical menu added. (backwards compatible): + * Old: directive URL [x,y ...] + * New: directive URL "Menu text" [x,y ...] + * or: directive URL x,y ... "Menu text" + * Map format and menu concept courtesy Joshua Bell, jsbell@acs.ucalgary.ca. + * + * Mark Cox, mark@ukweb.com, Allow relative URLs even when no base specified + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_main.h" +#include "http_log.h" +#include "util_script.h" + +#define IMAP_MAGIC_TYPE "application/x-httpd-imap" +#define MAXVERTS 100 +#define X 0 +#define Y 1 + +#define IMAP_MENU_DEFAULT "formatted" +#define IMAP_DEFAULT_DEFAULT "nocontent" +#define IMAP_BASE_DEFAULT "map" + +#ifdef SUNOS4 +double strtod(); /* SunOS needed this */ +#endif + +module MODULE_VAR_EXPORT imap_module; + +typedef struct { + char *imap_menu; + char *imap_default; + char *imap_base; +} imap_conf_rec; + +static void *create_imap_dir_config(pool *p, char *dummy) +{ + imap_conf_rec *icr = + (imap_conf_rec *) ap_palloc(p, sizeof(imap_conf_rec)); + + icr->imap_menu = NULL; + icr->imap_default = NULL; + icr->imap_base = NULL; + + return icr; +} + +static void *merge_imap_dir_configs(pool *p, void *basev, void *addv) +{ + imap_conf_rec *new = (imap_conf_rec *) ap_pcalloc(p, sizeof(imap_conf_rec)); + imap_conf_rec *base = (imap_conf_rec *) basev; + imap_conf_rec *add = (imap_conf_rec *) addv; + + new->imap_menu = add->imap_menu ? add->imap_menu : base->imap_menu; + new->imap_default = add->imap_default ? add->imap_default + : base->imap_default; + new->imap_base = add->imap_base ? add->imap_base : base->imap_base; + + return new; +} + + +static const command_rec imap_cmds[] = +{ + {"ImapMenu", ap_set_string_slot, + (void *) XtOffsetOf(imap_conf_rec, imap_menu), OR_INDEXES, TAKE1, + "the type of menu generated: none, formatted, semiformatted, unformatted"}, + {"ImapDefault", ap_set_string_slot, + (void *) XtOffsetOf(imap_conf_rec, imap_default), OR_INDEXES, TAKE1, + "the action taken if no match: error, nocontent, referer, menu, URL"}, + {"ImapBase", ap_set_string_slot, + (void *) XtOffsetOf(imap_conf_rec, imap_base), OR_INDEXES, TAKE1, + "the base for all URL's: map, referer, URL (or start of)"}, + {NULL} +}; + +static int pointinrect(const double point[2], double coords[MAXVERTS][2]) +{ + double max[2], min[2]; + if (coords[0][X] > coords[1][X]) { + max[0] = coords[0][X]; + min[0] = coords[1][X]; + } + else { + max[0] = coords[1][X]; + min[0] = coords[0][X]; + } + + if (coords[0][Y] > coords[1][Y]) { + max[1] = coords[0][Y]; + min[1] = coords[1][Y]; + } + else { + max[1] = coords[1][Y]; + min[1] = coords[0][Y]; + } + + return ((point[X] >= min[0] && point[X] <= max[0]) && + (point[Y] >= min[1] && point[Y] <= max[1])); +} + +static int pointincircle(const double point[2], double coords[MAXVERTS][2]) +{ + double radius1, radius2; + + radius1 = ((coords[0][Y] - coords[1][Y]) * (coords[0][Y] - coords[1][Y])) + + ((coords[0][X] - coords[1][X]) * (coords[0][X] - coords[1][X])); + + radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y])) + + ((coords[0][X] - point[X]) * (coords[0][X] - point[X])); + + return (radius2 <= radius1); +} + +#define fmin(a,b) (((a)>(b))?(b):(a)) +#define fmax(a,b) (((a)>(b))?(a):(b)) + +static int pointinpoly(const double point[2], double pgon[MAXVERTS][2]) +{ + int i, numverts, crossings = 0; + double x = point[X], y = point[Y]; + + for (numverts = 0; pgon[numverts][X] != -1 && numverts < MAXVERTS; + numverts++) { + /* just counting the vertexes */ + } + + for (i = 0; i < numverts; i++) { + double x1=pgon[i][X]; + double y1=pgon[i][Y]; + double x2=pgon[(i + 1) % numverts][X]; + double y2=pgon[(i + 1) % numverts][Y]; + double d=(y - y1) * (x2 - x1) - (x - x1) * (y2 - y1); + + if ((y1 >= y) != (y2 >= y)) { + crossings +=y2 - y1 >= 0 ? d >= 0 : d <= 0; + } + if (!d && fmin(x1,x2) <= x && x <= fmax(x1,x2) + && fmin(y1,y2) <= y && y <= fmax(y1,y2)) { + return 1; + } + } + return crossings & 0x01; +} + + +static int is_closer(const double point[2], double coords[MAXVERTS][2], + double *closest) +{ + double dist_squared = ((point[X] - coords[0][X]) + * (point[X] - coords[0][X])) + + ((point[Y] - coords[0][Y]) + * (point[Y] - coords[0][Y])); + + if (point[X] < 0 || point[Y] < 0) { + return (0); /* don't mess around with negative coordinates */ + } + + if (*closest < 0 || dist_squared < *closest) { + *closest = dist_squared; + return (1); /* if this is the first point or is the closest yet + set 'closest' equal to this distance^2 */ + } + + return (0); /* if it's not the first or closest */ + +} + +static double get_x_coord(const char *args) +{ + char *endptr; /* we want it non-null */ + double x_coord = -1; /* -1 is returned if no coordinate is given */ + + if (args == NULL) { + return (-1); /* in case we aren't passed anything */ + } + + while (*args && !ap_isdigit(*args) && *args != ',') { + args++; /* jump to the first digit, but not past + a comma or end */ + } + + x_coord = strtod(args, &endptr); + + if (endptr > args) { /* if a conversion was made */ + return (x_coord); + } + + return (-1); /* else if no conversion was made, + or if no args was given */ +} + +static double get_y_coord(const char *args) +{ + char *endptr; /* we want it non-null */ + char *start_of_y = NULL; + double y_coord = -1; /* -1 is returned on error */ + + if (args == NULL) { + return (-1); /* in case we aren't passed anything */ + } + + start_of_y = strchr(args, ','); /* the comma */ + + if (start_of_y) { + + start_of_y++; /* start looking at the character after + the comma */ + + while (*start_of_y && !ap_isdigit(*start_of_y)) { + start_of_y++; /* jump to the first digit, but not + past the end */ + } + + y_coord = strtod(start_of_y, &endptr); + + if (endptr > start_of_y) { + return (y_coord); + } + } + + return (-1); /* if no conversion was made, or + no comma was found in args */ +} + + +/* See if string has a "quoted part", and if so set *quoted_part to + * the first character of the quoted part, then hammer a \0 onto the + * trailing quote, and set *string to point at the first character + * past the second quote. + * + * Otherwise set *quoted_part to NULL, and leave *string alone. + */ +static void read_quoted(char **string, char **quoted_part) +{ + char *strp = *string; + + /* assume there's no quoted part */ + *quoted_part = NULL; + + while (ap_isspace(*strp)) { + strp++; /* go along string until non-whitespace */ + } + + if (*strp == '"') { /* if that character is a double quote */ + strp++; /* step over it */ + *quoted_part = strp; /* note where the quoted part begins */ + + while (*strp && *strp != '"') { + ++strp; /* skip the quoted portion */ + } + + *strp = '\0'; /* end the string with a NUL */ + + strp++; /* step over the last double quote */ + *string = strp; + } +} + +/* + * returns the mapped URL or NULL. + */ +static char *imap_url(request_rec *r, const char *base, const char *value) +{ +/* translates a value into a URL. */ + int slen, clen; + char *string_pos = NULL; + const char *string_pos_const = NULL; + char *directory = NULL; + const char *referer = NULL; + char *my_base; + + if (!strcasecmp(value, "map") || !strcasecmp(value, "menu")) { + return ap_construct_url(r->pool, r->uri, r); + } + + if (!strcasecmp(value, "nocontent") || !strcasecmp(value, "error")) { + return ap_pstrdup(r->pool, value); /* these are handled elsewhere, + so just copy them */ + } + + if (!strcasecmp(value, "referer")) { + referer = ap_table_get(r->headers_in, "Referer"); + if (referer && *referer) { + return ap_escape_html(r->pool, referer); + } + else { + /* XXX: This used to do *value = '\0'; ... which is totally bogus + * because it hammers the passed in value, which can be a string + * constant, or part of a config, or whatever. Total garbage. + * This works around that without changing the rest of this + * code much + */ + value = ""; /* if 'referer' but no referring page, + null the value */ + } + } + + string_pos_const = value; + while (ap_isalpha(*string_pos_const)) { + string_pos_const++; /* go along the URL from the map + until a non-letter */ + } + if (*string_pos_const == ':') { + /* if letters and then a colon (like http:) */ + /* it's an absolute URL, so use it! */ + return ap_pstrdup(r->pool, value); + } + + if (!base || !*base) { + if (value && *value) { + return ap_pstrdup(r->pool, value); /* no base: use what is given */ + } + /* no base, no value: pick a simple default */ + return ap_construct_url(r->pool, "/", r); + } + + /* must be a relative URL to be combined with base */ + if (strchr(base, '/') == NULL && (!strncmp(value, "../", 3) + || !strcmp(value, ".."))) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "invalid base directive in map file: %s", r->uri); + return NULL; + } + my_base = ap_pstrdup(r->pool, base); + string_pos = my_base; + while (*string_pos) { + if (*string_pos == '/' && *(string_pos + 1) == '/') { + string_pos += 2; /* if there are two slashes, jump over them */ + continue; + } + if (*string_pos == '/') { /* the first single slash */ + if (value[0] == '/') { + *string_pos = '\0'; + } /* if the URL from the map starts from root, + end the base URL string at the first single + slash */ + else { + directory = string_pos; /* save the start of + the directory portion */ + + string_pos = strrchr(string_pos, '/'); /* now reuse + string_pos */ + string_pos++; /* step over that last slash */ + *string_pos = '\0'; + } /* but if the map url is relative, leave the + slash on the base (if there is one) */ + break; + } + string_pos++; /* until we get to the end of my_base without + finding a slash by itself */ + } + + while (!strncmp(value, "../", 3) || !strcmp(value, "..")) { + + if (directory && (slen = strlen(directory))) { + + /* for each '..', knock a directory off the end + by ending the string right at the last slash. + But only consider the directory portion: don't eat + into the server name. And only try if a directory + portion was found */ + + clen = slen - 1; + + while ((slen - clen) == 1) { + + if ((string_pos = strrchr(directory, '/'))) { + *string_pos = '\0'; + } + clen = strlen(directory); + if (clen == 0) { + break; + } + } + + value += 2; /* jump over the '..' that we found in the + value */ + } + else if (directory) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "invalid directory name in map file: %s", r->uri); + return NULL; + } + + if (!strncmp(value, "/../", 4) || !strcmp(value, "/..")) { + value++; /* step over the '/' if there are more '..' + to do. This way, we leave the starting + '/' on value after the last '..', but get + rid of it otherwise */ + } + + } /* by this point, value does not start + with '..' */ + + if (value && *value) { + return ap_pstrcat(r->pool, my_base, value, NULL); + } + return my_base; +} + +static int imap_reply(request_rec *r, char *redirect) +{ + if (!strcasecmp(redirect, "error")) { + return SERVER_ERROR; /* they actually requested an error! */ + } + if (!strcasecmp(redirect, "nocontent")) { + return HTTP_NO_CONTENT; /* tell the client to keep the page it has */ + } + if (redirect && *redirect) { + ap_table_setn(r->headers_out, "Location", redirect); + return REDIRECT; /* must be a URL, so redirect to it */ + } + return SERVER_ERROR; +} + +static void menu_header(request_rec *r, char *menu) +{ + r->content_type = "text/html; charset=ISO-8859-1"; + ap_send_http_header(r); +#ifdef CHARSET_EBCDIC + /* Server-generated response, converted */ + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1); +#endif + ap_hard_timeout("send menu", r); /* killed in menu_footer */ + + ap_rvputs(r, DOCTYPE_HTML_3_2, "<html><head>\n<title>Menu for ", + ap_escape_html(r->pool, r->uri), + "</title>\n</head><body>\n", NULL); + + if (!strcasecmp(menu, "formatted")) { + ap_rvputs(r, "<h1>Menu for ", ap_escape_html(r->pool, r->uri), + "</h1>\n<hr>\n\n", NULL); + } + + return; +} + +static void menu_blank(request_rec *r, char *menu) +{ + if (!strcasecmp(menu, "formatted")) { + ap_rputs("\n", r); + } + if (!strcasecmp(menu, "semiformatted")) { + ap_rputs("<br>\n", r); + } + if (!strcasecmp(menu, "unformatted")) { + ap_rputs("\n", r); + } + return; +} + +static void menu_comment(request_rec *r, char *menu, char *comment) +{ + if (!strcasecmp(menu, "formatted")) { + ap_rputs("\n", r); /* print just a newline if 'formatted' */ + } + if (!strcasecmp(menu, "semiformatted") && *comment) { + ap_rvputs(r, comment, "\n", NULL); + } + if (!strcasecmp(menu, "unformatted") && *comment) { + ap_rvputs(r, comment, "\n", NULL); + } + return; /* comments are ignored in the + 'formatted' form */ +} + +static void menu_default(request_rec *r, char *menu, char *href, char *text) +{ + if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) { + return; /* don't print such lines, these aren't + really href's */ + } + if (!strcasecmp(menu, "formatted")) { + ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text, + "</a></pre>\n", NULL); + } + if (!strcasecmp(menu, "semiformatted")) { + ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text, + "</a></pre>\n", NULL); + } + if (!strcasecmp(menu, "unformatted")) { + ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL); + } + return; +} + +static void menu_directive(request_rec *r, char *menu, char *href, char *text) +{ + if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) { + return; /* don't print such lines, as this isn't + really an href */ + } + if (!strcasecmp(menu, "formatted")) { + ap_rvputs(r, "<pre> <a href=\"", href, "\">", text, + "</a></pre>\n", NULL); + } + if (!strcasecmp(menu, "semiformatted")) { + ap_rvputs(r, "<pre> <a href=\"", href, "\">", text, + "</a></pre>\n", NULL); + } + if (!strcasecmp(menu, "unformatted")) { + ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL); + } + return; +} + +static void menu_footer(request_rec *r) +{ + ap_rputs("\n\n</body>\n</html>\n", r); /* finish the menu */ + ap_kill_timeout(r); +} + +static int imap_handler(request_rec *r) +{ + char input[MAX_STRING_LEN]; + char *directive; + char *value; + char *href_text; + char *base; + char *redirect; + char *mapdflt; + char *closest = NULL; + double closest_yet = -1; + + double testpoint[2]; + double pointarray[MAXVERTS + 1][2]; + int vertex; + + char *string_pos; + int showmenu = 0; + + imap_conf_rec *icr = ap_get_module_config(r->per_dir_config, &imap_module); + + char *imap_menu = icr->imap_menu ? icr->imap_menu : IMAP_MENU_DEFAULT; + char *imap_default = icr->imap_default + ? icr->imap_default : IMAP_DEFAULT_DEFAULT; + char *imap_base = icr->imap_base ? icr->imap_base : IMAP_BASE_DEFAULT; + + configfile_t *imap; + + if (r->method_number != M_GET) { + return DECLINED; + } + + imap = ap_pcfg_openfile(r->pool, r->filename); + + if (!imap) { + return NOT_FOUND; + } + + base = imap_url(r, NULL, imap_base); /* set base according + to default */ + if (!base) { + return HTTP_INTERNAL_SERVER_ERROR; + } + mapdflt = imap_url(r, NULL, imap_default); /* and default to + global default */ + if (!mapdflt) { + return HTTP_INTERNAL_SERVER_ERROR; + } + + testpoint[X] = get_x_coord(r->args); + testpoint[Y] = get_y_coord(r->args); + + if ((testpoint[X] == -1 || testpoint[Y] == -1) || + (testpoint[X] == 0 && testpoint[Y] == 0)) { + /* if either is -1 or if both are zero (new Lynx) */ + /* we don't have valid coordinates */ + testpoint[X] = -1; + testpoint[Y] = -1; + if (strncasecmp(imap_menu, "none", 2)) { + showmenu = 1; /* show the menu _unless_ ImapMenu is + 'none' or 'no' */ + } + } + + if (showmenu) { /* send start of imagemap menu if + we're going to */ + menu_header(r, imap_menu); + } + + while (!ap_cfg_getline(input, sizeof(input), imap)) { + if (!input[0]) { + if (showmenu) { + menu_blank(r, imap_menu); + } + continue; + } + + if (input[0] == '#') { + if (showmenu) { + menu_comment(r, imap_menu, input + 1); + } + continue; + } /* blank lines and comments are ignored + if we aren't printing a menu */ + + /* find the first two space delimited fields, recall that + * ap_cfg_getline has removed leading/trailing whitespace. + * + * note that we're tokenizing as we go... if we were to use the + * ap_getword() class of functions we would end up allocating extra + * memory for every line of the map file + */ + string_pos = input; + if (!*string_pos) { /* need at least two fields */ + goto need_2_fields; + } + + directive = string_pos; + while (*string_pos && !ap_isspace(*string_pos)) { /* past directive */ + ++string_pos; + } + if (!*string_pos) { /* need at least two fields */ + goto need_2_fields; + } + *string_pos++ = '\0'; + + if (!*string_pos) { /* need at least two fields */ + goto need_2_fields; + } + while(*string_pos && ap_isspace(*string_pos)) { /* past whitespace */ + ++string_pos; + } + + value = string_pos; + while (*string_pos && !ap_isspace(*string_pos)) { /* past value */ + ++string_pos; + } + if (ap_isspace(*string_pos)) { + *string_pos++ = '\0'; + } + else { + /* end of input, don't advance past it */ + *string_pos = '\0'; + } + + if (!strncasecmp(directive, "base", 4)) { /* base, base_uri */ + base = imap_url(r, NULL, value); + if (!base) { + goto menu_bail; + } + continue; /* base is never printed to a menu */ + } + + read_quoted(&string_pos, &href_text); + + if (!strcasecmp(directive, "default")) { /* default */ + mapdflt = imap_url(r, NULL, value); + if (!mapdflt) { + goto menu_bail; + } + if (showmenu) { /* print the default if there's a menu */ + redirect = imap_url(r, base, mapdflt); + if (!redirect) { + goto menu_bail; + } + menu_default(r, imap_menu, redirect, + href_text ? href_text : mapdflt); + } + continue; + } + + vertex = 0; + while (vertex < MAXVERTS && + sscanf(string_pos, "%lf%*[, ]%lf", + &pointarray[vertex][X], &pointarray[vertex][Y]) == 2) { + /* Now skip what we just read... we can't use ANSIism %n */ + while (ap_isspace(*string_pos)) { /* past whitespace */ + string_pos++; + } + while (ap_isdigit(*string_pos)) { /* and the 1st number */ + string_pos++; + } + string_pos++; /* skip the ',' */ + while (ap_isspace(*string_pos)) { /* past any more whitespace */ + string_pos++; + } + while (ap_isdigit(*string_pos)) { /* 2nd number */ + string_pos++; + } + vertex++; + } /* so long as there are more vertices to + read, and we have room, read them in. + We start where we left off of the last + sscanf, not at the beginning. */ + + pointarray[vertex][X] = -1; /* signals the end of vertices */ + + if (showmenu) { + if (!href_text) { + read_quoted(&string_pos, &href_text); /* href text could + be here instead */ + } + redirect = imap_url(r, base, value); + if (!redirect) { + goto menu_bail; + } + menu_directive(r, imap_menu, redirect, + href_text ? href_text : value); + continue; + } + /* note that we don't make it past here if we are making a menu */ + + if (testpoint[X] == -1 || pointarray[0][X] == -1) { + continue; /* don't try the following tests if testpoints + are invalid, or if there are no + coordinates */ + } + + if (!strcasecmp(directive, "poly")) { /* poly */ + + if (pointinpoly(testpoint, pointarray)) { + ap_cfg_closefile(imap); + redirect = imap_url(r, base, value); + if (!redirect) { + return HTTP_INTERNAL_SERVER_ERROR; + } + return (imap_reply(r, redirect)); + } + continue; + } + + if (!strcasecmp(directive, "circle")) { /* circle */ + + if (pointincircle(testpoint, pointarray)) { + ap_cfg_closefile(imap); + redirect = imap_url(r, base, value); + if (!redirect) { + return HTTP_INTERNAL_SERVER_ERROR; + } + return (imap_reply(r, redirect)); + } + continue; + } + + if (!strcasecmp(directive, "rect")) { /* rect */ + + if (pointinrect(testpoint, pointarray)) { + ap_cfg_closefile(imap); + redirect = imap_url(r, base, value); + if (!redirect) { + return HTTP_INTERNAL_SERVER_ERROR; + } + return (imap_reply(r, redirect)); + } + continue; + } + + if (!strcasecmp(directive, "point")) { /* point */ + + if (is_closer(testpoint, pointarray, &closest_yet)) { + closest = ap_pstrdup(r->pool, value); + } + + continue; + } /* move on to next line whether it's + closest or not */ + + } /* nothing matched, so we get another line! */ + + ap_cfg_closefile(imap); /* we are done with the map file; close it */ + + if (showmenu) { + menu_footer(r); /* finish the menu and we are done */ + return OK; + } + + if (closest) { /* if a 'point' directive has been seen */ + redirect = imap_url(r, base, closest); + if (!redirect) { + return HTTP_INTERNAL_SERVER_ERROR; + } + return (imap_reply(r, redirect)); + } + + if (mapdflt) { /* a default should be defined, even if + only 'nocontent' */ + redirect = imap_url(r, base, mapdflt); + if (!redirect) { + return HTTP_INTERNAL_SERVER_ERROR; + } + return (imap_reply(r, redirect)); + } + + return HTTP_INTERNAL_SERVER_ERROR; /* If we make it this far, + we failed. They lose! */ + +need_2_fields: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "map file %s, line %d syntax error: requires at " + "least two fields", r->uri, imap->line_number); + /* fall through */ +menu_bail: + ap_cfg_closefile(imap); + if (showmenu) { + /* There's not much else we can do ... we've already sent the headers + * to the client. + */ + ap_rputs("\n\n[an internal server error occured]\n", r); + menu_footer(r); + return OK; + } + return HTTP_INTERNAL_SERVER_ERROR; +} + + +static const handler_rec imap_handlers[] = +{ + {IMAP_MAGIC_TYPE, imap_handler}, + {"imap-file", imap_handler}, + {NULL} +}; + +module MODULE_VAR_EXPORT imap_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_imap_dir_config, /* dir config creater */ + merge_imap_dir_configs, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + imap_cmds, /* command table */ + imap_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_include.c b/APACHE_1_3_42/src/modules/standard/mod_include.c new file mode 100644 index 0000000000..440e57b5d2 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_include.c @@ -0,0 +1,2557 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * http_include.c: Handles the server-parsed HTML documents + * + * Original by Rob McCool; substantial fixups by David Robinson; + * incorporated into the Apache module framework by rst. + * + */ +/* + * sub key may be anything a Perl*Handler can be: + * subroutine name, package name (defaults to package::handler), + * Class->method call or anoymous sub {} + * + * Child <!--#perl sub="sub {print $$}" --> accessed + * <!--#perl sub="sub {print ++$Access::Cnt }" --> times. <br> + * + * <!--#perl arg="one" sub="mymod::includer" --> + * + * -Doug MacEachern + */ + +#ifdef USE_PERL_SSI +#include "config.h" +#undef VOIDUSED +#ifdef USE_SFIO +#undef USE_SFIO +#define USE_STDIO +#endif +#include "modules/perl/mod_perl.h" +#else +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#endif + +#define STARTING_SEQUENCE "<!--#" +#define ENDING_SEQUENCE "-->" +#define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]" +#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z" +#define SIZEFMT_BYTES 0 +#define SIZEFMT_KMG 1 +#ifdef CHARSET_EBCDIC +#define RAW_ASCII_CHAR(ch) os_toebcdic[(unsigned char)ch] +#else /*CHARSET_EBCDIC*/ +#define RAW_ASCII_CHAR(ch) (ch) +#endif /*CHARSET_EBCDIC*/ + +module MODULE_VAR_EXPORT includes_module; + +/* ------------------------ Environment function -------------------------- */ + +/* XXX: could use ap_table_overlap here */ +static void add_include_vars(request_rec *r, char *timefmt) +{ +#if !defined(WIN32) && !defined(NETWARE) + struct passwd *pw; +#endif /* ndef WIN32 */ + table *e = r->subprocess_env; + char *t; + time_t date = r->request_time; + + ap_table_setn(e, "DATE_LOCAL", ap_ht_time(r->pool, date, timefmt, 0)); + ap_table_setn(e, "DATE_GMT", ap_ht_time(r->pool, date, timefmt, 1)); + ap_table_setn(e, "LAST_MODIFIED", + ap_ht_time(r->pool, r->finfo.st_mtime, timefmt, 0)); + ap_table_setn(e, "DOCUMENT_URI", r->uri); + ap_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info); +#if !defined(WIN32) && !defined(NETWARE) + pw = getpwuid(r->finfo.st_uid); + if (pw) { + ap_table_setn(e, "USER_NAME", ap_pstrdup(r->pool, pw->pw_name)); + } + else { + ap_table_setn(e, "USER_NAME", ap_psprintf(r->pool, "user#%lu", + (unsigned long) r->finfo.st_uid)); + } +#endif /* ndef WIN32 */ + + if ((t = strrchr(r->filename, '/'))) { + ap_table_setn(e, "DOCUMENT_NAME", ++t); + } + else { + ap_table_setn(e, "DOCUMENT_NAME", r->uri); + } + if (r->args) { + char *arg_copy = ap_pstrdup(r->pool, r->args); + + ap_unescape_url(arg_copy); + ap_table_setn(e, "QUERY_STRING_UNESCAPED", + ap_escape_shell_cmd(r->pool, arg_copy)); + } +} + + + +/* --------------------------- Parser functions --------------------------- */ + +#define OUTBUFSIZE 4096 +/* PUT_CHAR and FLUSH_BUF currently only work within the scope of + * find_string(); they are hacks to avoid calling rputc for each and + * every character output. A common set of buffering calls for this + * type of output SHOULD be implemented. + */ +#define PUT_CHAR(c,r) \ + { \ + outbuf[outind++] = c; \ + if (outind == OUTBUFSIZE) { \ + FLUSH_BUF(r) \ + }; \ + } + +/* there SHOULD be some error checking on the return value of + * rwrite, however it is unclear what the API for rwrite returning + * errors is and little can really be done to help the error in + * any case. + */ +#define FLUSH_BUF(r) \ + { \ + ap_rwrite(outbuf, outind, r); \ + outind = 0; \ + } + +/* + * f: file handle being read from + * c: character to read into + * ret: return value to use if input fails + * r: current request_rec + * + * This macro is redefined after find_string() for historical reasons + * to avoid too many code changes. This is one of the many things + * that should be fixed. + */ +#define GET_CHAR(f,c,ret,r) \ + { \ + int i = getc(f); \ + if (i == EOF) { /* either EOF or error -- needs error handling if latter */ \ + if (ferror(f)) { \ + fprintf(stderr, "encountered error in GET_CHAR macro, " \ + "mod_include.\n"); \ + } \ + FLUSH_BUF(r); \ + ap_pfclose(r->pool, f); \ + return ret; \ + } \ + c = (char)i; \ + } + +static int find_string(FILE *in, const char *str, request_rec *r, int printing) +{ + int x, l = strlen(str), p; + char outbuf[OUTBUFSIZE]; + int outind = 0; + char c; + + p = 0; + while (1) { + GET_CHAR(in, c, 1, r); + if (c == str[p]) { + if ((++p) == l) { + FLUSH_BUF(r); + return 0; + } + } + else { + if (printing) { + for (x = 0; x < p; x++) { + PUT_CHAR(str[x], r); + } + PUT_CHAR(c, r); + } + p = 0; + } + } +} + +#undef FLUSH_BUF +#undef PUT_CHAR +#undef GET_CHAR +#define GET_CHAR(f,c,r,p) \ + { \ + int i = getc(f); \ + if (i == EOF) { /* either EOF or error -- needs error handling if latter */ \ + if (ferror(f)) { \ + fprintf(stderr, "encountered error in GET_CHAR macro, " \ + "mod_include.\n"); \ + } \ + ap_pfclose(p, f); \ + return r; \ + } \ + c = (char)i; \ + } + +/* + * decodes a string containing html entities or numeric character references. + * 's' is overwritten with the decoded string. + * If 's' is syntatically incorrect, then the followed fixups will be made: + * unknown entities will be left undecoded; + * references to unused numeric characters will be deleted. + * In particular, � will not be decoded, but will be deleted. + * + * drtr + */ + +/* maximum length of any ISO-LATIN-1 HTML entity name. */ +#define MAXENTLEN (6) + +/* The following is a shrinking transformation, therefore safe. */ + +static void decodehtml(char *s) +{ + int val, i, j; + char *p = s; + const char *ents; + static const char * const entlist[MAXENTLEN + 1] = + { + NULL, /* 0 */ + NULL, /* 1 */ + "lt\074gt\076", /* 2 */ + "amp\046ETH\320eth\360", /* 3 */ + "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\ +iuml\357ouml\366uuml\374yuml\377", /* 4 */ + "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\ +THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\ +ucirc\373thorn\376", /* 5 */ + "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\ +Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\ +Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\ +egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\ +otilde\365oslash\370ugrave\371uacute\372yacute\375" /* 6 */ + }; + + for (; *s != '\0'; s++, p++) { + if (*s != '&') { + *p = *s; + continue; + } + /* find end of entity */ + for (i = 1; s[i] != ';' && s[i] != '\0'; i++) { + continue; + } + + if (s[i] == '\0') { /* treat as normal data */ + *p = *s; + continue; + } + + /* is it numeric ? */ + if (s[1] == '#') { + for (j = 2, val = 0; j < i && ap_isdigit(s[j]); j++) { + val = val * 10 + s[j] - '0'; + } + s += i; + if (j < i || val <= 8 || (val >= 11 && val <= 31) || + (val >= 127 && val <= 160) || val >= 256) { + p--; /* no data to output */ + } + else { + *p = RAW_ASCII_CHAR(val); + } + } + else { + j = i - 1; + if (j > MAXENTLEN || entlist[j] == NULL) { + /* wrong length */ + *p = '&'; + continue; /* skip it */ + } + for (ents = entlist[j]; *ents != '\0'; ents += i) { + if (strncmp(s + 1, ents, j) == 0) { + break; + } + } + + if (*ents == '\0') { + *p = '&'; /* unknown */ + } + else { + *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]); + s += i; + } + } + } + + *p = '\0'; +} + +/* + * extract the next tag name and value. + * if there are no more tags, set the tag name to 'done' + * the tag value is html decoded if dodecode is non-zero + */ + +static char *get_tag(request_rec *r, FILE *in, char *tag, int tagbuf_len, int dodecode) +{ + char *t = tag, *tag_val, c, term; + pool *p = r->pool; + + /* makes code below a little less cluttered */ + --tagbuf_len; + + do { /* skip whitespace */ + GET_CHAR(in, c, NULL, p); + } while (ap_isspace(c)); + + /* tags can't start with - */ + if (c == '-') { + GET_CHAR(in, c, NULL, p); + if (c == '-') { + do { + GET_CHAR(in, c, NULL, p); + } while (ap_isspace(c)); + if (c == '>') { + ap_cpystrn(tag, "done", tagbuf_len); + return tag; + } + } + return NULL; /* failed */ + } + + /* find end of tag name */ + while (1) { + if (t == tag + tagbuf_len) { + *t = '\0'; + return NULL; + } + if (c == '=' || ap_isspace(c)) { + break; + } + *(t++) = ap_tolower(c); + GET_CHAR(in, c, NULL, p); + } + + *t++ = '\0'; + tag_val = t; + + while (ap_isspace(c)) { + GET_CHAR(in, c, NULL, p); /* space before = */ + } + if (c != '=') { + ungetc(c, in); + return NULL; + } + + do { + GET_CHAR(in, c, NULL, p); /* space after = */ + } while (ap_isspace(c)); + + /* we should allow a 'name' as a value */ + + if (c != '"' && c != '\'') { + return NULL; + } + term = c; + while (1) { + GET_CHAR(in, c, NULL, p); + if (t == tag + tagbuf_len) { + *t = '\0'; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "mod_include: value length exceeds limit" + " (%d) in %s", tagbuf_len, r->filename); + return NULL; + } + /* Want to accept \" as a valid character within a string. */ + if (c == '\\') { + GET_CHAR(in, c, NULL, p); + /* Insert backslash only if not escaping a terminator char */ + if (c != term) { + *(t++) = '\\'; + /* + * check to make sure that adding in the backslash won't cause + * an overflow, since we're now 1 character ahead. + */ + if (t == tag + tagbuf_len) { + *t = '\0'; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "mod_include: value length exceeds limit" + " (%d) in %s", tagbuf_len, r->filename); + return NULL; + } + } + } + else if (c == term) { + break; + } + *(t++) = c; + } + *t = '\0'; + if (dodecode) { + decodehtml(tag_val); + } + return ap_pstrdup(p, tag_val); +} + +static int get_directive(FILE *in, char *dest, size_t len, request_rec *r) +{ + char *d = dest; + pool *p = r->pool; + char c; + + /* make room for nul terminator */ + --len; + + /* skip initial whitespace */ + while (1) { + GET_CHAR(in, c, 1, p); + if (!ap_isspace(c)) { + break; + } + } + /* now get directive */ + while (1) { + if (d == len + dest) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "mod_include: directive length exceeds limit" + " (%lu) in %s", (unsigned long)len+1, r->filename); + return 1; + } + *d++ = ap_tolower(c); + GET_CHAR(in, c, 1, p); + if (ap_isspace(c)) { + break; + } + } + *d = '\0'; + return 0; +} + +/* + * Do variable substitution on strings + */ +static void parse_string(request_rec *r, const char *in, char *out, + size_t length, int leave_name) +{ + char ch; + char *next = out; + char *end_out; + + /* leave room for nul terminator */ + end_out = out + length - 1; + + while ((ch = *in++) != '\0') { + switch (ch) { + case '\\': + if (next == end_out) { + /* truncated */ + *next = '\0'; + return; + } + if (*in == '$') { + *next++ = *in++; + } + else { + *next++ = ch; + } + break; + case '$': + { + char var[MAX_STRING_LEN]; + const char *start_of_var_name; + const char *end_of_var_name; /* end of var name + 1 */ + const char *expansion; + const char *val; + size_t l; + + /* guess that the expansion won't happen */ + expansion = in - 1; + if (*in == '{') { + ++in; + start_of_var_name = in; + in = strchr(in, '}'); + if (in == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, + r, "Missing '}' on variable \"%s\"", + expansion); + *next = '\0'; + return; + } + end_of_var_name = in; + ++in; + } + else { + start_of_var_name = in; + while (ap_isalnum(*in) || *in == '_') { + ++in; + } + end_of_var_name = in; + } + /* what a pain, too bad there's no table_getn where you can + * pass a non-nul terminated string */ + l = end_of_var_name - start_of_var_name; + if (l != 0) { + l = (l > sizeof(var) - 1) ? (sizeof(var) - 1) : l; + memcpy(var, start_of_var_name, l); + var[l] = '\0'; + + val = ap_table_get(r->subprocess_env, var); + if (val) { + expansion = val; + l = strlen(expansion); + } + else if (leave_name) { + l = in - expansion; + } + else { + break; /* no expansion to be done */ + } + } + else { + /* zero-length variable name causes just the $ to be copied */ + l = 1; + } + l = (l + next > end_out) ? (end_out - next) : l; + memcpy(next, expansion, l); + next += l; + break; + } + default: + if (next == end_out) { + /* truncated */ + *next = '\0'; + return; + } + *next++ = ch; + break; + } + } + *next = '\0'; + return; +} + +/* --------------------------- Action handlers ---------------------------- */ + +static int include_cgi(char *s, request_rec *r) +{ + request_rec *rr = ap_sub_req_lookup_uri(s, r); + int rr_status; + + if (rr->status != HTTP_OK) { + return -1; + } + + /* No hardwired path info or query allowed */ + + if ((rr->path_info && rr->path_info[0]) || rr->args) { + return -1; + } + if (rr->finfo.st_mode == 0) { + return -1; + } + + /* Script gets parameters of the *document*, for back compatibility */ + + rr->path_info = r->path_info; /* hard to get right; see mod_cgi.c */ + rr->args = r->args; + + /* Force sub_req to be treated as a CGI request, even if ordinary + * typing rules would have called it something else. + */ + + rr->content_type = CGI_MAGIC_TYPE; + + /* Run it. */ + + rr_status = ap_run_sub_req(rr); + if (ap_is_HTTP_REDIRECT(rr_status)) { + const char *location = ap_table_get(rr->headers_out, "Location"); + location = ap_escape_html(rr->pool, location); + ap_rvputs(r, "<A HREF=\"", location, "\">", location, "</A>", NULL); + } + + ap_destroy_sub_req(rr); +#if !defined(WIN32) && !defined(NETWARE) + ap_chdir_file(r->filename); +#endif + + return 0; +} + +/* ensure that path is relative, and does not contain ".." elements + * ensentially ensure that it does not match the regex: + * (^/|(^|/)\.\.(/|$)) + * XXX: this needs os abstraction... consider c:..\foo in win32 + * ???: No, c:../foo is not relative to ., it's potentially on another volume + */ +static int is_only_below(const char *path) +{ +#ifdef HAVE_DRIVE_LETTERS + if (path[1] == ':') + return 0; +#endif +#ifdef NETWARE + if (strchr(path, ':')) + return 0; +#endif + if (path[0] == '/') { + return 0; + } + if (path[0] == '.' && path[1] == '.' + && (path[2] == '\0' || path[2] == '/')) { + return 0; + } + while (*path) { + if (*path == '/' && path[1] == '.' && path[2] == '.' + && (path[3] == '\0' || path[3] == '/')) { + return 0; + } + ++path; + } + return 1; +} + +static int handle_include(FILE *in, request_rec *r, const char *error, int noexec) +{ + char tag[MAX_STRING_LEN]; + char parsed_string[MAX_STRING_LEN]; + char *tag_val; + + while (1) { + if (!(tag_val = get_tag(r, in, tag, sizeof(tag), 1))) { + return 1; + } + if (!strcmp(tag, "file") || !strcmp(tag, "virtual")) { + request_rec *rr = NULL; + char *error_fmt = NULL; + + parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0); + if (tag[0] == 'f') { + /* be safe; only files in this directory or below allowed */ + if (!is_only_below(parsed_string)) { + error_fmt = "unable to include file \"%s\" " + "in parsed file %s"; + } + else { + rr = ap_sub_req_lookup_file(parsed_string, r); + } + } + else { + rr = ap_sub_req_lookup_uri(parsed_string, r); + } + + if (!error_fmt && rr->status != HTTP_OK) { + error_fmt = "unable to include \"%s\" in parsed file %s"; + } + + if (!error_fmt && noexec && rr->content_type + && (strncmp(rr->content_type, "text/", 5))) { + error_fmt = "unable to include potential exec \"%s\" " + "in parsed file %s"; + } + if (error_fmt == NULL) { + /* try to avoid recursive includes. We do this by walking + * up the r->main list of subrequests, and at each level + * walking back through any internal redirects. At each + * step, we compare the filenames and the URIs. + * + * The filename comparison catches a recursive include + * with an ever-changing URL, eg. + * <!--#include virtual= + * "$REQUEST_URI/$QUERY_STRING?$QUERY_STRING/x"--> + * which, although they would eventually be caught because + * we have a limit on the length of files, etc., can + * recurse for a while. + * + * The URI comparison catches the case where the filename + * is changed while processing the request, so the + * current name is never the same as any previous one. + * This can happen with "DocumentRoot /foo" when you + * request "/" on the server and it includes "/". + * This only applies to modules such as mod_dir that + * (somewhat improperly) mess with r->filename outside + * of a filename translation phase. + */ + int founddupe = 0; + request_rec *p; + for (p = r; p != NULL && !founddupe; p = p->main) { + request_rec *q; + for (q = p; q != NULL; q = q->prev) { + if ( (q->filename && strcmp(q->filename, rr->filename) == 0) || + (strcmp(q->uri, rr->uri) == 0) ){ + founddupe = 1; + break; + } + } + } + + if (p != NULL) { + error_fmt = "Recursive include of \"%s\" " + "in parsed file %s"; + } + } + + /* see the Kludge in send_parsed_file for why */ + if (rr) + ap_set_module_config(rr->request_config, &includes_module, r); + + if (!error_fmt && ap_run_sub_req(rr)) { + error_fmt = "unable to include \"%s\" in parsed file %s"; + } +#if !defined(WIN32) && !defined(NETWARE) + ap_chdir_file(r->filename); +#endif + if (error_fmt) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, + r, error_fmt, tag_val, r->filename); + ap_rputs(error, r); + } + + if (rr != NULL) { + ap_destroy_sub_req(rr); + } + } + else if (!strcmp(tag, "done")) { + return 0; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unknown parameter \"%s\" to tag include in %s", + tag, r->filename); + ap_rputs(error, r); + } + } +} + +typedef struct { +#ifdef TPF + TPF_FORK_CHILD t; +#endif + request_rec *r; + char *s; +} include_cmd_arg; + +static int include_cmd_child(void *arg, child_info *pinfo) +{ + request_rec *r = ((include_cmd_arg *) arg)->r; + char *s = ((include_cmd_arg *) arg)->s; + table *env = r->subprocess_env; + int child_pid = 0; +#ifdef DEBUG_INCLUDE_CMD +#ifdef OS2 + /* under OS/2 /dev/tty is referenced as con */ + FILE *dbg = fopen("con", "w"); +#else + FILE *dbg = fopen("/dev/tty", "w"); +#endif +#endif +#if !defined(WIN32) && !defined(OS2) + char err_string[MAX_STRING_LEN]; +#endif + +#ifdef DEBUG_INCLUDE_CMD + fprintf(dbg, "Attempting to include command '%s'\n", s); +#endif + + if (r->path_info && r->path_info[0] != '\0') { + request_rec *pa_req; + + ap_table_setn(env, "PATH_INFO", ap_escape_shell_cmd(r->pool, r->path_info)); + + pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r); + if (pa_req->filename) { + ap_table_setn(env, "PATH_TRANSLATED", + ap_pstrcat(r->pool, pa_req->filename, pa_req->path_info, + NULL)); + } + } + + if (r->args) { + char *arg_copy = ap_pstrdup(r->pool, r->args); + + ap_table_setn(env, "QUERY_STRING", r->args); + ap_unescape_url(arg_copy); + ap_table_setn(env, "QUERY_STRING_UNESCAPED", + ap_escape_shell_cmd(r->pool, arg_copy)); + } + + ap_error_log2stderr(r->server); + +#ifdef DEBUG_INCLUDE_CMD + fprintf(dbg, "Attempting to exec '%s'\n", s); +#endif +#ifdef TPF + return (0); +#else + ap_cleanup_for_exec(); + /* set shellcmd flag to pass arg to SHELL_PATH */ + child_pid = ap_call_exec(r, pinfo, s, ap_create_environment(r->pool, env), + 1); +#if defined(WIN32) || defined(OS2) + return (child_pid); +#else + /* Oh, drat. We're still here. The log file descriptors are closed, + * so we have to whimper a complaint onto stderr... + */ + +#ifdef DEBUG_INCLUDE_CMD + fprintf(dbg, "Exec failed\n"); +#endif + ap_snprintf(err_string, sizeof(err_string), + "exec of %s failed, reason: %s (errno = %d)\n", + SHELL_PATH, strerror(errno), errno); + write(STDERR_FILENO, err_string, strlen(err_string)); + exit(0); + /* NOT REACHED */ + return (child_pid); +#endif /* WIN32 */ +#endif /* TPF */ +} + +static int include_cmd(char *s, request_rec *r) +{ + include_cmd_arg arg; + BUFF *script_in; + + arg.r = r; + arg.s = s; +#ifdef TPF + arg.t.filename = r->filename; + arg.t.subprocess_env = r->subprocess_env; + arg.t.prog_type = FORK_FILE; +#endif + + if (!ap_bspawn_child(r->pool, include_cmd_child, &arg, + kill_after_timeout, NULL, &script_in, NULL)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "couldn't spawn include command"); + return -1; + } + + ap_send_fb(script_in, r); + ap_bclose(script_in); + return 0; +} + +static int handle_exec(FILE *in, request_rec *r, const char *error) +{ + char tag[MAX_STRING_LEN]; + char *tag_val; + char *file = r->filename; + char parsed_string[MAX_STRING_LEN]; + + while (1) { + if (!(tag_val = get_tag(r, in, tag, sizeof(tag), 1))) { + return 1; + } + if (!strcmp(tag, "cmd")) { + parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 1); + if (include_cmd(parsed_string, r) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "execution failure for parameter \"%s\" " + "to tag exec in file %s", + tag, r->filename); + ap_rputs(error, r); + } + /* just in case some stooge changed directories */ +#if !defined(WIN32) && !defined(NETWARE) + ap_chdir_file(r->filename); +#endif + } + else if (!strcmp(tag, "cgi")) { + parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0); + if (include_cgi(parsed_string, r) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "invalid CGI ref \"%s\" in %s", tag_val, file); + ap_rputs(error, r); + } + /* grumble groan */ +#if !defined(WIN32) && !defined(NETWARE) + ap_chdir_file(r->filename); +#endif + } + else if (!strcmp(tag, "done")) { + return 0; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unknown parameter \"%s\" to tag exec in %s", + tag, file); + ap_rputs(error, r); + } + } + +} + +static int handle_echo(FILE *in, request_rec *r, const char *error) +{ + char tag[MAX_STRING_LEN]; + char *tag_val; + enum {E_NONE, E_URL, E_ENTITY} encode; + + encode = E_ENTITY; + + while (1) { + if (!(tag_val = get_tag(r, in, tag, sizeof(tag), 1))) { + return 1; + } + if (!strcmp(tag, "var")) { + const char *val = ap_table_get(r->subprocess_env, tag_val); + + if (val) { + if (encode == E_NONE) { + ap_rputs(val, r); + } + else if (encode == E_URL) { + ap_rputs(ap_escape_uri(r->pool, val), r); + } + else if (encode == E_ENTITY) { + ap_rputs(ap_escape_html(r->pool, val), r); + } + } + else { + ap_rputs("(none)", r); + } + } + else if (!strcmp(tag, "done")) { + return 0; + } + else if (!strcmp(tag, "encoding")) { + if (!strcasecmp(tag_val, "none")) encode = E_NONE; + else if (!strcasecmp(tag_val, "url")) encode = E_URL; + else if (!strcasecmp(tag_val, "entity")) encode = E_ENTITY; + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unknown value \"%s\" to parameter \"encoding\" of " + "tag echo in %s", + tag_val, r->filename); + ap_rputs(error, r); + } + } + + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unknown parameter \"%s\" to tag echo in %s", + tag, r->filename); + ap_rputs(error, r); + } + } +} + +#ifdef USE_PERL_SSI +static int handle_perl(FILE *in, request_rec *r, const char *error) +{ + char tag[MAX_STRING_LEN]; + char parsed_string[MAX_STRING_LEN]; + char *tag_val; + SV *sub = Nullsv; + AV *av = newAV(); + + if (ap_allow_options(r) & OPT_INCNOEXEC) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "#perl SSI disallowed by IncludesNoExec in %s", + r->filename); + return DECLINED; + } + while (1) { + if (!(tag_val = get_tag(r, in, tag, sizeof(tag), 1))) { + break; + } + if (strnEQ(tag, "sub", 3)) { + sub = newSVpv(tag_val, 0); + } + else if (strnEQ(tag, "arg", 3)) { + parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0); + av_push(av, newSVpv(parsed_string, 0)); + } + else if (strnEQ(tag, "done", 4)) { + break; + } + } + perl_stdout2client(r); + perl_setup_env(r); + perl_call_handler(sub, r, av); + return OK; +} +#endif + +/* error and tf must point to a string with room for at + * least MAX_STRING_LEN characters + */ +static int handle_config(FILE *in, request_rec *r, char *error, char *tf, + int *sizefmt) +{ + char tag[MAX_STRING_LEN]; + char *tag_val; + char parsed_string[MAX_STRING_LEN]; + table *env = r->subprocess_env; + + while (1) { + if (!(tag_val = get_tag(r, in, tag, sizeof(tag), 0))) { + return 1; + } + if (!strcmp(tag, "errmsg")) { + parse_string(r, tag_val, error, MAX_STRING_LEN, 0); + } + else if (!strcmp(tag, "timefmt")) { + time_t date = r->request_time; + + parse_string(r, tag_val, tf, MAX_STRING_LEN, 0); + ap_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, tf, 0)); + ap_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, tf, 1)); + ap_table_setn(env, "LAST_MODIFIED", + ap_ht_time(r->pool, r->finfo.st_mtime, tf, 0)); + } + else if (!strcmp(tag, "sizefmt")) { + parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0); + decodehtml(parsed_string); + if (!strcmp(parsed_string, "bytes")) { + *sizefmt = SIZEFMT_BYTES; + } + else if (!strcmp(parsed_string, "abbrev")) { + *sizefmt = SIZEFMT_KMG; + } + } + else if (!strcmp(tag, "done")) { + return 0; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unknown parameter \"%s\" to tag config in %s", + tag, r->filename); + ap_rputs(error, r); + } + } +} + + +static int find_file(request_rec *r, const char *directive, const char *tag, + char *tag_val, struct stat *finfo, const char *error) +{ + char *to_send = tag_val; + request_rec *rr = NULL; + int ret=0; + char *error_fmt = NULL; + + if (!strcmp(tag, "file")) { + /* be safe; only files in this directory or below allowed */ + if (!is_only_below(tag_val)) { + error_fmt = "unable to access file \"%s\" " + "in parsed file %s"; + } + else { + ap_getparents(tag_val); /* get rid of any nasties */ + rr = ap_sub_req_lookup_file(tag_val, r); + + if (rr->status == HTTP_OK && rr->finfo.st_mode != 0) { + to_send = rr->filename; + if (stat(to_send, finfo)) { + error_fmt = "unable to get information about \"%s\" " + "in parsed file %s"; + } + } + else { + error_fmt = "unable to lookup information about \"%s\" " + "in parsed file %s"; + } + } + + if (error_fmt) { + ret = -1; + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, error_fmt, to_send, r->filename); + ap_rputs(error, r); + } + + if (rr) ap_destroy_sub_req(rr); + + return ret; + } + else if (!strcmp(tag, "virtual")) { + rr = ap_sub_req_lookup_uri(tag_val, r); + + if (rr->status == HTTP_OK && rr->finfo.st_mode != 0) { + memcpy((char *) finfo, (const char *) &rr->finfo, + sizeof(struct stat)); + ap_destroy_sub_req(rr); + return 0; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unable to get information about \"%s\" " + "in parsed file %s", + tag_val, r->filename); + ap_rputs(error, r); + ap_destroy_sub_req(rr); + return -1; + } + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unknown parameter \"%s\" to tag %s in %s", + tag, directive, r->filename); + ap_rputs(error, r); + return -1; + } +} + + +static int handle_fsize(FILE *in, request_rec *r, const char *error, int sizefmt) +{ + char tag[MAX_STRING_LEN]; + char *tag_val; + struct stat finfo; + char parsed_string[MAX_STRING_LEN]; + + while (1) { + if (!(tag_val = get_tag(r, in, tag, sizeof(tag), 1))) { + return 1; + } + else if (!strcmp(tag, "done")) { + return 0; + } + else { + parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0); + if (!find_file(r, "fsize", tag, parsed_string, &finfo, error)) { + if (sizefmt == SIZEFMT_KMG) { + ap_send_size(finfo.st_size, r); + } + else { + int l, x; +#if defined(AP_OFF_T_IS_QUAD) + ap_snprintf(tag, sizeof(tag), "%qd", finfo.st_size); +#else + ap_snprintf(tag, sizeof(tag), "%ld", finfo.st_size); +#endif + l = strlen(tag); /* grrr */ + for (x = 0; x < l; x++) { + if (x && (!((l - x) % 3))) { + ap_rputc(',', r); + } + ap_rputc(tag[x], r); + } + } + } + } + } +} + +static int handle_flastmod(FILE *in, request_rec *r, const char *error, const char *tf) +{ + char tag[MAX_STRING_LEN]; + char *tag_val; + struct stat finfo; + char parsed_string[MAX_STRING_LEN]; + + while (1) { + if (!(tag_val = get_tag(r, in, tag, sizeof(tag), 1))) { + return 1; + } + else if (!strcmp(tag, "done")) { + return 0; + } + else { + parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0); + if (!find_file(r, "flastmod", tag, parsed_string, &finfo, error)) { + ap_rputs(ap_ht_time(r->pool, finfo.st_mtime, tf, 0), r); + } + } + } +} + +static int re_check(request_rec *r, char *string, char *rexp) +{ + regex_t *compiled; + int regex_error; + + compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB); + if (compiled == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unable to compile pattern \"%s\"", rexp); + return -1; + } + regex_error = ap_regexec(compiled, string, 0, (regmatch_t *) NULL, 0); + ap_pregfree(r->pool, compiled); + return (!regex_error); +} + +enum token_type { + token_string, + token_and, token_or, token_not, token_eq, token_ne, + token_rbrace, token_lbrace, token_group, + token_ge, token_le, token_gt, token_lt +}; +struct token { + enum token_type type; + char value[MAX_STRING_LEN]; +}; + +/* there is an implicit assumption here that string is at most MAX_STRING_LEN-1 + * characters long... + */ +static const char *get_ptoken(request_rec *r, const char *string, struct token *token) +{ + char ch; + int next = 0; + int qs = 0; + + /* Skip leading white space */ + if (string == (char *) NULL) { + return (char *) NULL; + } + while ((ch = *string++)) { + if (!ap_isspace(ch)) { + break; + } + } + if (ch == '\0') { + return (char *) NULL; + } + + token->type = token_string; /* the default type */ + switch (ch) { + case '(': + token->type = token_lbrace; + return (string); + case ')': + token->type = token_rbrace; + return (string); + case '=': + token->type = token_eq; + return (string); + case '!': + if (*string == '=') { + token->type = token_ne; + return (string + 1); + } + else { + token->type = token_not; + return (string); + } + case '\'': + token->type = token_string; + qs = 1; + break; + case '|': + if (*string == '|') { + token->type = token_or; + return (string + 1); + } + break; + case '&': + if (*string == '&') { + token->type = token_and; + return (string + 1); + } + break; + case '>': + if (*string == '=') { + token->type = token_ge; + return (string + 1); + } + else { + token->type = token_gt; + return (string); + } + case '<': + if (*string == '=') { + token->type = token_le; + return (string + 1); + } + else { + token->type = token_lt; + return (string); + } + default: + token->type = token_string; + break; + } + /* We should only be here if we are in a string */ + if (!qs) { + --string; + } + + /* + * Yes I know that goto's are BAD. But, c doesn't allow me to + * exit a loop from a switch statement. Yes, I could use a flag, + * but that is (IMHO) even less readable/maintainable than the goto. + */ + /* + * I used the ++string throughout this section so that string + * ends up pointing to the next token and I can just return it + */ + for (ch = *string; ch != '\0'; ch = *++string) { + if (ch == '\\') { + if ((ch = *++string) == '\0') { + goto TOKEN_DONE; + } + token->value[next++] = ch; + continue; + } + if (!qs) { + if (ap_isspace(ch)) { + goto TOKEN_DONE; + } + switch (ch) { + case '(': + goto TOKEN_DONE; + case ')': + goto TOKEN_DONE; + case '=': + goto TOKEN_DONE; + case '!': + goto TOKEN_DONE; + case '|': + if (*(string + 1) == '|') { + goto TOKEN_DONE; + } + break; + case '&': + if (*(string + 1) == '&') { + goto TOKEN_DONE; + } + break; + case '<': + goto TOKEN_DONE; + case '>': + goto TOKEN_DONE; + } + token->value[next++] = ch; + } + else { + if (ch == '\'') { + qs = 0; + ++string; + goto TOKEN_DONE; + } + token->value[next++] = ch; + } + } + TOKEN_DONE: + /* If qs is still set, I have an unmatched ' */ + if (qs) { + ap_rputs("\nUnmatched '\n", r); + next = 0; + } + token->value[next] = '\0'; + return (string); +} + + +/* + * Hey I still know that goto's are BAD. I don't think that I've ever + * used two in the same project, let alone the same file before. But, + * I absolutely want to make sure that I clean up the memory in all + * cases. And, without rewriting this completely, the easiest way + * is to just branch to the return code which cleans it up. + */ +/* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1 + * characters long... + */ +static int parse_expr(request_rec *r, const char *expr, const char *error) +{ + struct parse_node { + struct parse_node *left, *right, *parent; + struct token token; + int value, done; + } *root, *current, *new; + const char *parse; + char buffer[MAX_STRING_LEN]; + pool *expr_pool; + int retval = 0; + + if ((parse = expr) == (char *) NULL) { + return (0); + } + root = current = (struct parse_node *) NULL; + expr_pool = ap_make_sub_pool(r->pool); + + /* Create Parse Tree */ + while (1) { + new = (struct parse_node *) ap_palloc(expr_pool, + sizeof(struct parse_node)); + new->parent = new->left = new->right = (struct parse_node *) NULL; + new->done = 0; + if ((parse = get_ptoken(r, parse, &new->token)) == (char *) NULL) { + break; + } + switch (new->token.type) { + + case token_string: +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Token: string (", new->token.value, ")\n", NULL); +#endif + if (current == (struct parse_node *) NULL) { + root = current = new; + break; + } + switch (current->token.type) { + case token_string: + if (current->token.value[0] != '\0') { + strncat(current->token.value, " ", + sizeof(current->token.value) + - strlen(current->token.value) - 1); + } + strncat(current->token.value, new->token.value, + sizeof(current->token.value) + - strlen(current->token.value) - 1); + current->token.value[sizeof(current->token.value) - 1] = '\0'; + break; + case token_eq: + case token_ne: + case token_and: + case token_or: + case token_lbrace: + case token_not: + case token_ge: + case token_gt: + case token_le: + case token_lt: + new->parent = current; + current = current->right = new; + break; + default: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + break; + + case token_and: + case token_or: +#ifdef DEBUG_INCLUDE + ap_rputs(" Token: and/or\n", r); +#endif + if (current == (struct parse_node *) NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + /* Percolate upwards */ + while (current != (struct parse_node *) NULL) { + switch (current->token.type) { + case token_string: + case token_group: + case token_not: + case token_eq: + case token_ne: + case token_and: + case token_or: + case token_ge: + case token_gt: + case token_le: + case token_lt: + current = current->parent; + continue; + case token_lbrace: + break; + default: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + break; + } + if (current == (struct parse_node *) NULL) { + new->left = root; + new->left->parent = new; + new->parent = (struct parse_node *) NULL; + root = new; + } + else { + new->left = current->right; + new->left->parent = new; + current->right = new; + new->parent = current; + } + current = new; + break; + + case token_not: +#ifdef DEBUG_INCLUDE + ap_rputs(" Token: not\n", r); +#endif + if (current == (struct parse_node *) NULL) { + root = current = new; + break; + } + /* Percolate upwards */ + while (current != (struct parse_node *) NULL) { + switch (current->token.type) { + case token_not: + case token_eq: + case token_ne: + case token_and: + case token_or: + case token_lbrace: + case token_ge: + case token_gt: + case token_le: + case token_lt: + break; + default: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + break; + /* NOTREACHED */ + } + if (current == (struct parse_node *) NULL) { + new->left = root; + new->left->parent = new; + new->parent = (struct parse_node *) NULL; + root = new; + } + else { + new->left = current->right; + current->right = new; + new->parent = current; + } + current = new; + break; + + case token_eq: + case token_ne: + case token_ge: + case token_gt: + case token_le: + case token_lt: +#ifdef DEBUG_INCLUDE + ap_rputs(" Token: eq/ne/ge/gt/le/lt\n", r); +#endif + if (current == (struct parse_node *) NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + /* Percolate upwards */ + while (current != (struct parse_node *) NULL) { + switch (current->token.type) { + case token_string: + case token_group: + current = current->parent; + continue; + case token_lbrace: + case token_and: + case token_or: + break; + case token_not: + case token_eq: + case token_ne: + case token_ge: + case token_gt: + case token_le: + case token_lt: + default: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + break; + } + if (current == (struct parse_node *) NULL) { + new->left = root; + new->left->parent = new; + new->parent = (struct parse_node *) NULL; + root = new; + } + else { + new->left = current->right; + new->left->parent = new; + current->right = new; + new->parent = current; + } + current = new; + break; + + case token_rbrace: +#ifdef DEBUG_INCLUDE + ap_rputs(" Token: rbrace\n", r); +#endif + while (current != (struct parse_node *) NULL) { + if (current->token.type == token_lbrace) { + current->token.type = token_group; + break; + } + current = current->parent; + } + if (current == (struct parse_node *) NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Unmatched ')' in \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + break; + + case token_lbrace: +#ifdef DEBUG_INCLUDE + ap_rputs(" Token: lbrace\n", r); +#endif + if (current == (struct parse_node *) NULL) { + root = current = new; + break; + } + /* Percolate upwards */ + while (current != (struct parse_node *) NULL) { + switch (current->token.type) { + case token_not: + case token_eq: + case token_ne: + case token_and: + case token_or: + case token_lbrace: + case token_ge: + case token_gt: + case token_le: + case token_lt: + break; + case token_string: + case token_group: + default: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + break; + /* NOTREACHED */ + } + if (current == (struct parse_node *) NULL) { + new->left = root; + new->left->parent = new; + new->parent = (struct parse_node *) NULL; + root = new; + } + else { + new->left = current->right; + current->right = new; + new->parent = current; + } + current = new; + break; + default: + break; + } + } + + /* Evaluate Parse Tree */ + current = root; + while (current != (struct parse_node *) NULL) { + switch (current->token.type) { + case token_string: +#ifdef DEBUG_INCLUDE + ap_rputs(" Evaluate string\n", r); +#endif + parse_string(r, current->token.value, buffer, sizeof(buffer), 0); + ap_cpystrn(current->token.value, buffer, sizeof(current->token.value)); + current->value = (current->token.value[0] != '\0'); + current->done = 1; + current = current->parent; + break; + + case token_and: + case token_or: +#ifdef DEBUG_INCLUDE + ap_rputs(" Evaluate and/or\n", r); +#endif + if (current->left == (struct parse_node *) NULL || + current->right == (struct parse_node *) NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + if (!current->left->done) { + switch (current->left->token.type) { + case token_string: + parse_string(r, current->left->token.value, + buffer, sizeof(buffer), 0); + ap_cpystrn(current->left->token.value, buffer, + sizeof(current->left->token.value)); + current->left->value = (current->left->token.value[0] != '\0'); + current->left->done = 1; + break; + default: + current = current->left; + continue; + } + } + if (!current->right->done) { + switch (current->right->token.type) { + case token_string: + parse_string(r, current->right->token.value, + buffer, sizeof(buffer), 0); + ap_cpystrn(current->right->token.value, buffer, + sizeof(current->right->token.value)); + current->right->value = (current->right->token.value[0] != '\0'); + current->right->done = 1; + break; + default: + current = current->right; + continue; + } + } +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Left: ", current->left->value ? "1" : "0", + "\n", NULL); + ap_rvputs(r, " Right: ", current->right->value ? "1" : "0", + "\n", NULL); +#endif + if (current->token.type == token_and) { + current->value = current->left->value && current->right->value; + } + else { + current->value = current->left->value || current->right->value; + } +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Returning ", current->value ? "1" : "0", + "\n", NULL); +#endif + current->done = 1; + current = current->parent; + break; + + case token_eq: + case token_ne: +#ifdef DEBUG_INCLUDE + ap_rputs(" Evaluate eq/ne\n", r); +#endif + if ((current->left == (struct parse_node *) NULL) || + (current->right == (struct parse_node *) NULL) || + (current->left->token.type != token_string) || + (current->right->token.type != token_string)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + parse_string(r, current->left->token.value, + buffer, sizeof(buffer), 0); + ap_cpystrn(current->left->token.value, buffer, + sizeof(current->left->token.value)); + parse_string(r, current->right->token.value, + buffer, sizeof(buffer), 0); + ap_cpystrn(current->right->token.value, buffer, + sizeof(current->right->token.value)); + if (current->right->token.value[0] == '/') { + int len; + len = strlen(current->right->token.value); + if (current->right->token.value[len - 1] == '/') { + current->right->token.value[len - 1] = '\0'; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid rexp \"%s\" in file %s", + current->right->token.value, r->filename); + ap_rputs(error, r); + goto RETURN; + } +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Re Compare (", current->left->token.value, + ") with /", ¤t->right->token.value[1], "/\n", NULL); +#endif + current->value = + re_check(r, current->left->token.value, + ¤t->right->token.value[1]); + } + else { +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Compare (", current->left->token.value, + ") with (", current->right->token.value, ")\n", NULL); +#endif + current->value = + (strcmp(current->left->token.value, + current->right->token.value) == 0); + } + if (current->token.type == token_ne) { + current->value = !current->value; + } +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Returning ", current->value ? "1" : "0", + "\n", NULL); +#endif + current->done = 1; + current = current->parent; + break; + case token_ge: + case token_gt: + case token_le: + case token_lt: +#ifdef DEBUG_INCLUDE + ap_rputs(" Evaluate ge/gt/le/lt\n", r); +#endif + if ((current->left == (struct parse_node *) NULL) || + (current->right == (struct parse_node *) NULL) || + (current->left->token.type != token_string) || + (current->right->token.type != token_string)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + } + parse_string(r, current->left->token.value, + buffer, sizeof(buffer), 0); + ap_cpystrn(current->left->token.value, buffer, + sizeof(current->left->token.value)); + parse_string(r, current->right->token.value, + buffer, sizeof(buffer), 0); + ap_cpystrn(current->right->token.value, buffer, + sizeof(current->right->token.value)); +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Compare (", current->left->token.value, + ") with (", current->right->token.value, ")\n", NULL); +#endif + current->value = + strcmp(current->left->token.value, + current->right->token.value); + if (current->token.type == token_ge) { + current->value = current->value >= 0; + } + else if (current->token.type == token_gt) { + current->value = current->value > 0; + } + else if (current->token.type == token_le) { + current->value = current->value <= 0; + } + else if (current->token.type == token_lt) { + current->value = current->value < 0; + } + else { + current->value = 0; /* Don't return -1 if unknown token */ + } +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Returning ", current->value ? "1" : "0", + "\n", NULL); +#endif + current->done = 1; + current = current->parent; + break; + + case token_not: + if (current->right != (struct parse_node *) NULL) { + if (!current->right->done) { + current = current->right; + continue; + } + current->value = !current->right->value; + } + else { + current->value = 0; + } +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Evaluate !: ", current->value ? "1" : "0", + "\n", NULL); +#endif + current->done = 1; + current = current->parent; + break; + + case token_group: + if (current->right != (struct parse_node *) NULL) { + if (!current->right->done) { + current = current->right; + continue; + } + current->value = current->right->value; + } + else { + current->value = 1; + } +#ifdef DEBUG_INCLUDE + ap_rvputs(r, " Evaluate (): ", current->value ? "1" : "0", + "\n", NULL); +#endif + current->done = 1; + current = current->parent; + break; + + case token_lbrace: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Unmatched '(' in \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + + case token_rbrace: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Unmatched ')' in \"%s\" in file %s", + expr, r->filename); + ap_rputs(error, r); + goto RETURN; + + default: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "bad token type"); + ap_rputs(error, r); + goto RETURN; + } + } + + retval = (root == (struct parse_node *) NULL) ? 0 : root->value; + RETURN: + ap_destroy_pool(expr_pool); + return (retval); +} + +static int handle_if(FILE *in, request_rec *r, const char *error, + int *conditional_status, int *printing) +{ + char tag[MAX_STRING_LEN]; + char *tag_val; + char *expr; + + expr = NULL; + while (1) { + tag_val = get_tag(r, in, tag, sizeof(tag), 0); + if (!tag_val || *tag == '\0') { + return 1; + } + else if (!strcmp(tag, "done")) { + if (expr == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "missing expr in if statement: %s", + r->filename); + ap_rputs(error, r); + return 1; + } + *printing = *conditional_status = parse_expr(r, expr, error); +#ifdef DEBUG_INCLUDE + ap_rvputs(r, "**** if conditional_status=\"", + *conditional_status ? "1" : "0", "\"\n", NULL); +#endif + return 0; + } + else if (!strcmp(tag, "expr")) { + expr = tag_val; +#ifdef DEBUG_INCLUDE + ap_rvputs(r, "**** if expr=\"", expr, "\"\n", NULL); +#endif + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unknown parameter \"%s\" to tag if in %s", + tag, r->filename); + ap_rputs(error, r); + } + } +} + +static int handle_elif(FILE *in, request_rec *r, const char *error, + int *conditional_status, int *printing) +{ + char tag[MAX_STRING_LEN]; + char *tag_val; + char *expr; + + expr = NULL; + while (1) { + tag_val = get_tag(r, in, tag, sizeof(tag), 0); + if (!tag_val || *tag == '\0') { + return 1; + } + else if (!strcmp(tag, "done")) { +#ifdef DEBUG_INCLUDE + ap_rvputs(r, "**** elif conditional_status=\"", + *conditional_status ? "1" : "0", "\"\n", NULL); +#endif + if (*conditional_status) { + *printing = 0; + return (0); + } + if (expr == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "missing expr in elif statement: %s", + r->filename); + ap_rputs(error, r); + return 1; + } + *printing = *conditional_status = parse_expr(r, expr, error); +#ifdef DEBUG_INCLUDE + ap_rvputs(r, "**** elif conditional_status=\"", + *conditional_status ? "1" : "0", "\"\n", NULL); +#endif + return 0; + } + else if (!strcmp(tag, "expr")) { + expr = tag_val; +#ifdef DEBUG_INCLUDE + ap_rvputs(r, "**** if expr=\"", expr, "\"\n", NULL); +#endif + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unknown parameter \"%s\" to tag if in %s", + tag, r->filename); + ap_rputs(error, r); + } + } +} + +static int handle_else(FILE *in, request_rec *r, const char *error, + int *conditional_status, int *printing) +{ + char tag[MAX_STRING_LEN]; + + if (!get_tag(r, in, tag, sizeof(tag), 1)) { + return 1; + } + else if (!strcmp(tag, "done")) { +#ifdef DEBUG_INCLUDE + ap_rvputs(r, "**** else conditional_status=\"", + *conditional_status ? "1" : "0", "\"\n", NULL); +#endif + *printing = !(*conditional_status); + *conditional_status = 1; + return 0; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "else directive does not take tags in %s", + r->filename); + if (*printing) { + ap_rputs(error, r); + } + return -1; + } +} + +static int handle_endif(FILE *in, request_rec *r, const char *error, + int *conditional_status, int *printing) +{ + char tag[MAX_STRING_LEN]; + + if (!get_tag(r, in, tag, sizeof(tag), 1)) { + return 1; + } + else if (!strcmp(tag, "done")) { +#ifdef DEBUG_INCLUDE + ap_rvputs(r, "**** endif conditional_status=\"", + *conditional_status ? "1" : "0", "\"\n", NULL); +#endif + *printing = 1; + *conditional_status = 1; + return 0; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "endif directive does not take tags in %s", + r->filename); + ap_rputs(error, r); + return -1; + } +} + +static int handle_set(FILE *in, request_rec *r, const char *error) +{ + char tag[MAX_STRING_LEN]; + char parsed_string[MAX_STRING_LEN]; + char *tag_val; + char *var; + + var = (char *) NULL; + while (1) { + if (!(tag_val = get_tag(r, in, tag, sizeof(tag), 1))) { + return 1; + } + else if (!strcmp(tag, "done")) { + return 0; + } + else if (!strcmp(tag, "var")) { + var = tag_val; + } + else if (!strcmp(tag, "value")) { + if (var == (char *) NULL) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "variable must precede value in set directive in %s", + r->filename); + ap_rputs(error, r); + return -1; + } + parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0); + ap_table_setn(r->subprocess_env, var, ap_pstrdup(r->pool, parsed_string)); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Invalid tag for set directive in %s", r->filename); + ap_rputs(error, r); + return -1; + } + } +} + +static int handle_printenv(FILE *in, request_rec *r, const char *error) +{ + char tag[MAX_STRING_LEN]; + char *tag_val; + array_header *arr = ap_table_elts(r->subprocess_env); + table_entry *elts = (table_entry *) arr->elts; + int i; + + if (!(tag_val = get_tag(r, in, tag, sizeof(tag), 1))) { + return 1; + } + else if (!strcmp(tag, "done")) { + for (i = 0; i < arr->nelts; ++i) { + ap_rvputs(r, ap_escape_html(r->pool, elts[i].key), "=", + ap_escape_html(r->pool, elts[i].val), "\n", NULL); + } + return 0; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "printenv directive does not take tags in %s", + r->filename); + ap_rputs(error, r); + return -1; + } +} + + + +/* -------------------------- The main function --------------------------- */ + +/* This is a stub which parses a file descriptor. */ + +static void send_parsed_content(FILE *f, request_rec *r) +{ +#ifdef NETWARE + /* NetWare has a fixed lengh stack. Since MAX_STRING_LEN is set + to 8k, one call to this function allocates 24k of stack space. + During a server-side include evaluation this function is + called recusively, allocating 24k each time. Obviously it + doesn't take long to blow a 64k stack which is the default + for Apache for NetWare. Since MAX_STRING_LEN is used all + throughout the Apache code, we should rethink using a default + of 8k especially in recursive functions. + */ + char directive[512], error[512]; + char timefmt[512]; +#else + char directive[MAX_STRING_LEN], error[MAX_STRING_LEN]; + char timefmt[MAX_STRING_LEN]; +#endif + int noexec = ap_allow_options(r) & OPT_INCNOEXEC; + int ret, sizefmt; + int if_nesting; + int printing; + int conditional_status; + + ap_cpystrn(error, DEFAULT_ERROR_MSG, sizeof(error)); + ap_cpystrn(timefmt, DEFAULT_TIME_FORMAT, sizeof(timefmt)); + sizefmt = SIZEFMT_KMG; + +/* Turn printing on */ + printing = conditional_status = 1; + if_nesting = 0; + +#if !defined(WIN32) && !defined(NETWARE) + ap_chdir_file(r->filename); +#endif + if (r->args) { /* add QUERY stuff to env cause it ain't yet */ + char *arg_copy = ap_pstrdup(r->pool, r->args); + + ap_table_setn(r->subprocess_env, "QUERY_STRING", r->args); + ap_unescape_url(arg_copy); + ap_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED", + ap_escape_shell_cmd(r->pool, arg_copy)); + } + + while (1) { + if (!find_string(f, STARTING_SEQUENCE, r, printing)) { + if (get_directive(f, directive, sizeof(directive), r)) { + ap_rputs(error, r); + return; + } + if (!strcmp(directive, "if")) { + ret = 0; + if (!printing) { + if_nesting++; + } + else { + ret = handle_if(f, r, error, &conditional_status, + &printing); + if_nesting = 0; + } + } + else if (!strcmp(directive, "else")) { + ret = 0; + if (!if_nesting) { + ret = handle_else(f, r, error, &conditional_status, + &printing); + } + } + else if (!strcmp(directive, "elif")) { + ret = 0; + if (!if_nesting) { + ret = handle_elif(f, r, error, &conditional_status, + &printing); + } + } + else if (!strcmp(directive, "endif")) { + ret = 0; + if (!if_nesting) { + ret = handle_endif(f, r, error, &conditional_status, + &printing); + } + else { + if_nesting--; + } + } + else if (!printing) { + continue; + } + else if (!strcmp(directive, "exec")) { + if (noexec) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "exec used but not allowed in %s", + r->filename); + if (printing) { + ap_rputs(error, r); + } + ret = find_string(f, ENDING_SEQUENCE, r, 0); + } + else { + ret = handle_exec(f, r, error); + } + } + else if (!strcmp(directive, "config")) { + ret = handle_config(f, r, error, timefmt, &sizefmt); + } + else if (!strcmp(directive, "set")) { + ret = handle_set(f, r, error); + } + else if (!strcmp(directive, "include")) { + ret = handle_include(f, r, error, noexec); + } + else if (!strcmp(directive, "echo")) { + ret = handle_echo(f, r, error); + } + else if (!strcmp(directive, "fsize")) { + ret = handle_fsize(f, r, error, sizefmt); + } + else if (!strcmp(directive, "flastmod")) { + ret = handle_flastmod(f, r, error, timefmt); + } + else if (!strcmp(directive, "printenv")) { + ret = handle_printenv(f, r, error); + } +#ifdef USE_PERL_SSI + else if (!strcmp(directive, "perl")) { + ret = handle_perl(f, r, error); + } +#endif + else { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "unknown directive \"%s\" " + "in parsed doc %s", + directive, r->filename); + if (printing) { + ap_rputs(error, r); + } + ret = find_string(f, ENDING_SEQUENCE, r, 0); + } + if (ret) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "premature EOF in parsed file %s", + r->filename); + return; + } + } + else { + return; + } + } +} + +/***************************************************************** + * + * XBITHACK. Sigh... NB it's configurable per-directory; the compile-time + * option only changes the default. + */ + +module includes_module; +enum xbithack { + xbithack_off, xbithack_on, xbithack_full +}; + +#ifdef XBITHACK +#define DEFAULT_XBITHACK xbithack_full +#else +#define DEFAULT_XBITHACK xbithack_off +#endif + +static void *create_includes_dir_config(pool *p, char *dummy) +{ + enum xbithack *result = (enum xbithack *) ap_palloc(p, sizeof(enum xbithack)); + *result = DEFAULT_XBITHACK; + return result; +} + +static const char *set_xbithack(cmd_parms *cmd, void *xbp, char *arg) +{ + enum xbithack *state = (enum xbithack *) xbp; + + if (!strcasecmp(arg, "off")) { + *state = xbithack_off; + } + else if (!strcasecmp(arg, "on")) { + *state = xbithack_on; + } + else if (!strcasecmp(arg, "full")) { + *state = xbithack_full; + } + else { + return "XBitHack must be set to Off, On, or Full"; + } + + return NULL; +} + +static int send_parsed_file(request_rec *r) +{ + FILE *f; + enum xbithack *state = + (enum xbithack *) ap_get_module_config(r->per_dir_config, &includes_module); + int errstatus; + request_rec *parent; + + if (!(ap_allow_options(r) & OPT_INCLUDES)) { + return DECLINED; + } + r->allowed |= (1 << M_GET); + if (r->method_number != M_GET) { + return DECLINED; + } + if (r->finfo.st_mode == 0) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "File does not exist: %s", + (r->path_info + ? ap_pstrcat(r->pool, r->filename, r->path_info, NULL) + : r->filename)); + return HTTP_NOT_FOUND; + } + + if (!(f = ap_pfopen(r->pool, r->filename, "r"))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "file permissions deny server access: %s", r->filename); + return HTTP_FORBIDDEN; + } + + if ((*state == xbithack_full) +#if !defined(OS2) && !defined(WIN32) && !defined(NETWARE) + /* OS/2 dosen't support Groups. */ + && (r->finfo.st_mode & S_IXGRP) +#endif + ) { + ap_update_mtime(r, r->finfo.st_mtime); + ap_set_last_modified(r); + } + if ((errstatus = ap_meets_conditions(r)) != OK) { + return errstatus; + } + + ap_send_http_header(r); + + if (r->header_only) { + ap_pfclose(r->pool, f); + return OK; + } + +#define SUB_REQ_STRING "Sub request to mod_include" +#define PARENT_STRING "Parent request to mod_include" + + if (ap_table_get(r->notes, SUB_REQ_STRING)) { + /* + * The note is a flag to mod_include that this request + * should be treated as if it was a subrequest originating + * in the handle_include() procedure of mod_include. + */ + + /* + * There is no good way to pass the parent request_rec to mod_include. + * Tables only take string values and there is nowhere appropriate in + * in the request_rec that can safely be used. So, search for the + * parent note by walking up the r->main list of subrequests, and at + * each level walking back through any internal redirects. This is + * the same request walking that mod_include uses in the procedure + * handle_include(). + */ + request_rec *p = r->main; + request_rec *q = p; + + while (q) { + if (ap_table_get(q->notes, PARENT_STRING)) { + /* Kludge --- See below */ + ap_set_module_config(r->request_config, &includes_module, q); + + /* Create the initial environment in the parent */ + ap_add_common_vars(q); + ap_add_cgi_vars(q); + add_include_vars(q, DEFAULT_TIME_FORMAT); + + /* Cleanup - This should allow this technique to nest */ + ap_table_unset(r->notes, SUB_REQ_STRING); + ap_table_unset(q->notes, PARENT_STRING); + break; + } + if (q->prev != NULL) { + q = q->prev; + } + else { + p = p->main; + q = p; + } + } + } + + if ((parent = ap_get_module_config(r->request_config, &includes_module))) { + /* Kludge --- for nested includes, we want to keep the subprocess + * environment of the base document (for compatibility); that means + * torquing our own last_modified date as well so that the + * LAST_MODIFIED variable gets reset to the proper value if the + * nested document resets <!--#config timefmt-->. + * We also insist that the memory for this subrequest not be + * destroyed, that's dealt with in handle_include(). + */ + r->subprocess_env = parent->subprocess_env; + ap_pool_join(parent->pool, r->pool); + r->finfo.st_mtime = parent->finfo.st_mtime; + } + else { + /* we're not a nested include, so we create an initial + * environment */ + ap_add_common_vars(r); + ap_add_cgi_vars(r); + add_include_vars(r, DEFAULT_TIME_FORMAT); + } + /* XXX: this is bogus, at some point we're going to do a subrequest, + * and when we do it we're going to be subjecting code that doesn't + * expect to be signal-ready to SIGALRM. There is no clean way to + * fix this, except to put alarm support into BUFF. -djg + */ + ap_hard_timeout("send SSI", r); + +#ifdef CHARSET_EBCDIC + /* XXX:@@@ Is the generated/included output ALWAYS in text/ebcdic format? */ + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, 1); +#endif + + send_parsed_content(f, r); + + if (parent) { + /* + * All the work is finished for this subrequest. The following + * makes it safe for the creator of the subrequest to destroy it + * via ap_destroy_sub_req() once the call to ap_run_sub_req() + * returns. This is required since the original pool of the + * subrequest has been merged into the pool of the parent request + * of the subrequest (see Kludge above). The alternative is to + * NOT destroy the subrequest. + */ + r->pool = ap_make_sub_pool(r->pool); + } + + ap_kill_timeout(r); + return OK; +} + +static int send_shtml_file(request_rec *r) +{ + r->content_type = "text/html"; + return send_parsed_file(r); +} + +static int xbithack_handler(request_rec *r) +{ +#if defined(OS2) || defined(WIN32) || defined(NETWARE) + /* OS/2 dosen't currently support the xbithack. This is being worked on. */ + return DECLINED; +#else + enum xbithack *state; + + if (!(r->finfo.st_mode & S_IXUSR)) { + return DECLINED; + } + + state = (enum xbithack *) ap_get_module_config(r->per_dir_config, + &includes_module); + + if (*state == xbithack_off) { + return DECLINED; + } + return send_parsed_file(r); +#endif +} + +static const command_rec includes_cmds[] = +{ + {"XBitHack", set_xbithack, NULL, OR_OPTIONS, TAKE1, "Off, On, or Full"}, + {NULL} +}; + +static const handler_rec includes_handlers[] = +{ + {INCLUDES_MAGIC_TYPE, send_shtml_file}, + {INCLUDES_MAGIC_TYPE3, send_shtml_file}, + {"server-parsed", send_parsed_file}, + {"text/html", xbithack_handler}, + {NULL} +}; + +module MODULE_VAR_EXPORT includes_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_includes_dir_config, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + includes_cmds, /* command table */ + includes_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_info.c b/APACHE_1_3_42/src/modules/standard/mod_info.c new file mode 100644 index 0000000000..e672807a0b --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_info.c @@ -0,0 +1,730 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Info Module. Display configuration information for the server and + * all included modules. + * + * <Location /server-info> + * SetHandler server-info + * </Location> + * + * GET /server-info - Returns full configuration page for server and all modules + * GET /server-info?server - Returns server configuration only + * GET /server-info?module_name - Returns configuration for a single module + * GET /server-info?list - Returns quick list of included modules + * + * Rasmus Lerdorf <rasmus@php.net>, May 1996 + * + * 05.01.96 Initial Version + * + * Lou Langholtz <ldl@usi.utah.edu>, July 1997 + * + * 07.11.97 Addition of the AddModuleInfo directive + * + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_main.h" +#include "http_protocol.h" +#include "util_script.h" +#include "http_conf_globals.h" + +typedef struct { + char *name; /* matching module name */ + char *info; /* additional info */ +} info_entry; + +typedef struct { + array_header *more_info; +} info_svr_conf; + +typedef struct info_cfg_lines { + char *cmd; + char *line; + struct info_cfg_lines *next; +} info_cfg_lines; + +typedef struct { /* shamelessly lifted from http_config.c */ + char *fname; +} info_fnames; + +typedef struct { + info_cfg_lines *clines; + char *fname; +} info_clines; + +module MODULE_VAR_EXPORT info_module; +extern module API_VAR_EXPORT *top_module; + +/* shamelessly lifted from http_config.c */ +static int fname_alphasort(const void *fn1, const void *fn2) +{ + const info_fnames *f1 = fn1; + const info_fnames *f2 = fn2; + + return strcmp(f1->fname,f2->fname); +} + +static void *create_info_config(pool *p, server_rec *s) +{ + info_svr_conf *conf = (info_svr_conf *) ap_pcalloc(p, sizeof(info_svr_conf)); + + conf->more_info = ap_make_array(p, 20, sizeof(info_entry)); + return conf; +} + +static void *merge_info_config(pool *p, void *basev, void *overridesv) +{ + info_svr_conf *new = (info_svr_conf *) ap_pcalloc(p, sizeof(info_svr_conf)); + info_svr_conf *base = (info_svr_conf *) basev; + info_svr_conf *overrides = (info_svr_conf *) overridesv; + + new->more_info = ap_append_arrays(p, overrides->more_info, base->more_info); + return new; +} + +static char *mod_info_html_cmd_string(const char *string, char *buf, size_t buf_len) +{ + const char *s; + char *t; + char *end_buf; + + s = string; + t = buf; + /* keep space for \0 byte */ + end_buf = buf + buf_len - 1; + while ((*s) && (t < end_buf)) { + if (*s == '<') { + strncpy(t, "<", end_buf - t); + t += 4; + } + else if (*s == '>') { + strncpy(t, ">", end_buf - t); + t += 4; + } + else if (*s == '&') { + strncpy(t, "&", end_buf - t); + t += 5; + } + else { + *t++ = *s; + } + s++; + } + /* oops, overflowed... don't overwrite */ + if (t > end_buf) { + *end_buf = '\0'; + } + else { + *t = '\0'; + } + return (buf); +} + +static info_cfg_lines *mod_info_load_config(pool *p, const char *filename, + request_rec *r) +{ + char s[MAX_STRING_LEN]; + configfile_t *fp; + info_cfg_lines *new, *ret, *prev; + const char *t; + + fp = ap_pcfg_openfile(p, filename); + if (!fp) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, r, + "mod_info: couldn't open config file %s", + filename); + return NULL; + } + ret = NULL; + prev = NULL; + while (!ap_cfg_getline(s, MAX_STRING_LEN, fp)) { + if (*s == '#') { + continue; /* skip comments */ + } + new = ap_palloc(p, sizeof(struct info_cfg_lines)); + new->next = NULL; + if (!ret) { + ret = new; + } + if (prev) { + prev->next = new; + } + t = s; + new->cmd = ap_getword_conf(p, &t); + if (*t) { + new->line = ap_pstrdup(p, t); + } + else { + new->line = NULL; + } + prev = new; + } + ap_cfg_closefile(fp); + return (ret); +} + +static void mod_info_module_cmds(request_rec *r, info_cfg_lines *cfg, + const command_rec *cmds, char *label) +{ + const command_rec *cmd = cmds; + info_cfg_lines *li = cfg, *li_st = NULL, *li_se = NULL; + info_cfg_lines *block_start = NULL; + int lab = 0, nest = 0; + char buf[MAX_STRING_LEN]; + + while (li) { + if (!strncasecmp(li->cmd, "<directory", 10) || + !strncasecmp(li->cmd, "<location", 9) || + !strncasecmp(li->cmd, "<limit", 6) || + !strncasecmp(li->cmd, "<files", 6)) { + if (nest) { + li_se = li; + } + else { + li_st = li; + } + li = li->next; + nest++; + continue; + } + else if (nest && (!strncasecmp(li->cmd, "</limit", 7) || + !strncasecmp(li->cmd, "</location", 10) || + !strncasecmp(li->cmd, "</directory", 11) || + !strncasecmp(li->cmd, "</files", 7))) { + if (block_start) { + if ((nest == 1 && block_start == li_st) || + (nest == 2 && block_start == li_se)) { + ap_rputs("<dd><tt>", r); + if (nest == 2) { + ap_rputs(" ", r); + } + ap_rputs(mod_info_html_cmd_string(li->cmd, buf, sizeof(buf)), r); + ap_rputs(" ", r); + if (li->line) { + ap_rputs(mod_info_html_cmd_string(li->line, buf, sizeof(buf)), r); + } + ap_rputs("</tt>\n", r); + nest--; + if (!nest) { + block_start = NULL; + li_st = NULL; + } + else { + block_start = li_st; + } + li_se = NULL; + } + else { + nest--; + if (!nest) { + li_st = NULL; + } + li_se = NULL; + } + } + else { + nest--; + if (!nest) { + li_st = NULL; + } + li_se = NULL; + } + li = li->next; + continue; + } + cmd = cmds; + while (cmd) { + if (cmd->name) { + if (!strcasecmp(cmd->name, li->cmd)) { + if (!lab) { + ap_rputs("<dt><strong>", r); + ap_rputs(label, r); + ap_rputs("</strong>\n", r); + lab = 1; + } + if (((nest && block_start == NULL) || + (nest == 2 && block_start == li_st)) && + (strncasecmp(li->cmd, "<directory", 10) && + strncasecmp(li->cmd, "<location", 9) && + strncasecmp(li->cmd, "<limit", 6) && + strncasecmp(li->cmd, "</limit", 7) && + strncasecmp(li->cmd, "</location", 10) && + strncasecmp(li->cmd, "</directory", 11) && + strncasecmp(li->cmd, "</files", 7))) { + ap_rputs("<dd><tt>", r); + ap_rputs(mod_info_html_cmd_string(li_st->cmd, buf, sizeof(buf)), r); + ap_rputs(" ", r); + if (li_st->line) { + ap_rputs(mod_info_html_cmd_string(li_st->line, buf, sizeof(buf)), r); + } + ap_rputs("</tt>\n", r); + block_start = li_st; + if (li_se) { + ap_rputs("<dd><tt> ", r); + ap_rputs(mod_info_html_cmd_string(li_se->cmd, buf, sizeof(buf)), r); + ap_rputs(" ", r); + if (li_se->line) { + ap_rputs(mod_info_html_cmd_string(li_se->line, buf, sizeof(buf)), r); + } + ap_rputs("</tt>\n", r); + block_start = li_se; + } + } + ap_rputs("<dd><tt>", r); + if (nest) { + ap_rputs(" ", r); + } + if (nest == 2) { + ap_rputs(" ", r); + } + ap_rputs(mod_info_html_cmd_string(li->cmd, buf, sizeof(buf)), r); + if (li->line) { + ap_rputs(" <i>", r); + ap_rputs(mod_info_html_cmd_string(li->line, buf, sizeof(buf)), r); + ap_rputs("</i>", r); + } + ap_rputs("</tt>", r); + } + } + else + break; + cmd++; + } + li = li->next; + } +} + +static char *find_more_info(server_rec *s, const char *module_name) +{ + int i; + info_svr_conf *conf = (info_svr_conf *) ap_get_module_config(s->module_config, + &info_module); + info_entry *entry = (info_entry *) conf->more_info->elts; + + if (!module_name) { + return 0; + } + for (i = 0; i < conf->more_info->nelts; i++) { + if (!strcmp(module_name, entry->name)) { + return entry->info; + } + entry++; + } + return 0; +} + +static void mod_info_dirwalk(pool *p, const char *fname, + request_rec *r, array_header *carray) +{ + info_clines *cnew = NULL; + info_cfg_lines *mod_info_cfg_tmp = NULL; + + if (!ap_is_rdirectory(fname)) { + mod_info_cfg_tmp = mod_info_load_config(p, fname, r); + cnew = (info_clines *) ap_push_array(carray); + cnew->fname = ap_pstrdup(p, fname); + cnew->clines = mod_info_cfg_tmp; + } else { + DIR *dirp; + struct DIR_TYPE *dir_entry; + int current; + array_header *candidates = NULL; + info_fnames *fnew; + + dirp = ap_popendir(p, fname); + if (dirp == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, r, + "mod_info: couldn't open config directory %s", + fname); + return; + } + candidates = ap_make_array(p, 1, sizeof(info_fnames)); + while ((dir_entry = readdir(dirp)) != NULL) { + /* strip out '.' and '..' */ + if (strcmp(dir_entry->d_name, ".") && + strcmp(dir_entry->d_name, "..")) { + fnew = (info_fnames *) ap_push_array(candidates); + fnew->fname = ap_make_full_path(p, fname, dir_entry->d_name); + } + } + ap_pclosedir(p, dirp); + if (candidates->nelts != 0) { + qsort((void *) candidates->elts, candidates->nelts, + sizeof(info_fnames), fname_alphasort); + for (current = 0; current < candidates->nelts; ++current) { + fnew = &((info_fnames *) candidates->elts)[current]; + mod_info_dirwalk(p, fnew->fname, r, carray); + } + } + } + return; +} + +static int display_info(request_rec *r) +{ + module *modp = NULL; + char buf[MAX_STRING_LEN], *cfname; + char *more_info; + const command_rec *cmd = NULL; + const handler_rec *hand = NULL; + server_rec *serv = r->server; + int comma = 0; + array_header *allconfigs = NULL; + info_clines *cnew = NULL; + int current; + char *relpath; + + r->allowed |= (1 << M_GET); + if (r->method_number != M_GET) + return DECLINED; + + r->content_type = "text/html"; + ap_send_http_header(r); + if (r->header_only) { + return 0; + } +#ifdef CHARSET_EBCDIC + /* Server-generated response, converted */ + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1); +#endif + ap_hard_timeout("send server info", r); + + ap_rputs(DOCTYPE_HTML_3_2 + "<html><head><title>Server Information</title></head>\n", r); + ap_rputs("<body><h1 align=center>Apache Server Information</h1>\n", r); + if (!r->args || strcasecmp(r->args, "list")) { + allconfigs = ap_make_array(r->pool, 1, sizeof(info_clines)); + cfname = ap_server_root_relative(r->pool, ap_server_confname); + mod_info_dirwalk(r->pool, cfname, r, allconfigs); + cfname = ap_server_root_relative(r->pool, serv->srm_confname); + mod_info_dirwalk(r->pool, cfname, r, allconfigs); + cfname = ap_server_root_relative(r->pool, serv->access_confname); + mod_info_dirwalk(r->pool, cfname, r, allconfigs); + if (!r->args) { + ap_rputs("<tt><a href=\"#server\">Server Settings</a>, ", r); + for (modp = top_module; modp; modp = modp->next) { + ap_rprintf(r, "<a href=\"#%s\">%s</a>", modp->name, modp->name); + if (modp->next) { + ap_rputs(", ", r); + } + } + ap_rputs("</tt><hr>", r); + + } + if (!r->args || !strcasecmp(r->args, "server")) { + ap_rprintf(r, "<a name=\"server\"><strong>Server Version:</strong> " + "<font size=+1><tt>%s</tt></a></font><br>\n", + ap_get_server_version()); + ap_rprintf(r, "<strong>Server Built:</strong> " + "<font size=+1><tt>%s</tt></a></font><br>\n", + ap_get_server_built()); + ap_rprintf(r, "<strong>API Version:</strong> " + "<tt>%d:%d</tt><br>\n", + MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR); + ap_rprintf(r, "<strong>Run Mode:</strong> <tt>%s</tt><br>\n", + (ap_standalone ? "standalone" : "inetd")); + ap_rprintf(r, "<strong>User/Group:</strong> " + "<tt>%s(%d)/%d</tt><br>\n", + ap_user_name, (int) ap_user_id, (int) ap_group_id); + ap_rprintf(r, "<strong>Hostname/port:</strong> " + "<tt>%s:%u</tt><br>\n", + serv->server_hostname, serv->port); + ap_rprintf(r, "<strong>Daemons:</strong> " + "<tt>start: %d " + "min idle: %d " + "max idle: %d " + "max: %d</tt><br>\n", + ap_daemons_to_start, ap_daemons_min_free, + ap_daemons_max_free, ap_daemons_limit); + ap_rprintf(r, "<strong>Max Requests:</strong> " + "<tt>per child: %d " + "keep alive: %s " + "max per connection: %d</tt><br>\n", + ap_max_requests_per_child, + (serv->keep_alive ? "on" : "off"), + serv->keep_alive_max); + ap_rprintf(r, "<strong>Threads:</strong> " + "<tt>per child: %d </tt><br>\n", + ap_threads_per_child); + ap_rprintf(r, "<strong>Excess requests:</strong> " + "<tt>per child: %d </tt><br>\n", + ap_excess_requests_per_child); + ap_rprintf(r, "<strong>Timeouts:</strong> " + "<tt>connection: %d " + "keep-alive: %d</tt><br>", + serv->timeout, serv->keep_alive_timeout); + ap_rprintf(r, "<strong>Server Root:</strong> " + "<tt>%s</tt><br>\n", ap_server_root); + ap_rprintf(r, "<strong>Config File:</strong> " + "<tt>%s</tt><br>\n", ap_server_confname); + ap_rprintf(r, "<strong>PID File:</strong> " + "<tt>%s</tt><br>\n", ap_pid_fname); + ap_rprintf(r, "<strong>Scoreboard File:</strong> " + "<tt>%s</tt><br>\n", ap_scoreboard_fname); + } + ap_rputs("<hr><dl>", r); + for (modp = top_module; modp; modp = modp->next) { + if (!r->args || !strcasecmp(modp->name, r->args)) { + ap_rprintf(r, "<dt><a name=\"%s\"><strong>Module Name:</strong> " + "<font size=+1><tt>%s</tt></a></font>\n", + modp->name, modp->name); + ap_rputs("<dt><strong>Content handlers:</strong>", r); + hand = modp->handlers; + if (hand) { + while (hand) { + if (hand->content_type) { + ap_rprintf(r, " <tt>%s</tt>\n", hand->content_type); + } + else { + break; + } + hand++; + if (hand && hand->content_type) { + ap_rputs(",", r); + } + } + } + else { + ap_rputs("<tt> <EM>none</EM></tt>", r); + } + ap_rputs("<dt><strong>Configuration Phase Participation:</strong> \n", + r); + if (modp->child_init) { + ap_rputs("<tt>Child Init</tt>", r); + comma = 1; + } + if (modp->create_dir_config) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Create Directory Config</tt>", r); + comma = 1; + } + if (modp->merge_dir_config) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Merge Directory Configs</tt>", r); + comma = 1; + } + if (modp->create_server_config) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Create Server Config</tt>", r); + comma = 1; + } + if (modp->merge_server_config) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Merge Server Configs</tt>", r); + comma = 1; + } + if (modp->child_exit) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Child Exit</tt>", r); + comma = 1; + } + if (!comma) + ap_rputs("<tt> <EM>none</EM></tt>", r); + comma = 0; + ap_rputs("<dt><strong>Request Phase Participation:</strong> \n", + r); + if (modp->post_read_request) { + ap_rputs("<tt>Post-Read Request</tt>", r); + comma = 1; + } + if (modp->header_parser) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Header Parse</tt>", r); + comma = 1; + } + if (modp->translate_handler) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Translate Path</tt>", r); + comma = 1; + } + if (modp->access_checker) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Check Access</tt>", r); + comma = 1; + } + if (modp->ap_check_user_id) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Verify User ID</tt>", r); + comma = 1; + } + if (modp->auth_checker) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Verify User Access</tt>", r); + comma = 1; + } + if (modp->type_checker) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Check Type</tt>", r); + comma = 1; + } + if (modp->fixer_upper) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Fixups</tt>", r); + comma = 1; + } + if (modp->logger) { + if (comma) { + ap_rputs(", ", r); + } + ap_rputs("<tt>Logging</tt>", r); + comma = 1; + } + if (!comma) + ap_rputs("<tt> <EM>none</EM></tt>", r); + comma = 0; + ap_rputs("<dt><strong>Module Directives:</strong> ", r); + cmd = modp->cmds; + if (cmd) { + while (cmd) { + if (cmd->name) { + ap_rprintf(r, "<dd><tt>%s - <i>", + mod_info_html_cmd_string(cmd->name, + buf, sizeof(buf))); + if (cmd->errmsg) { + ap_rputs(cmd->errmsg, r); + } + ap_rputs("</i></tt>\n", r); + } + else { + break; + } + cmd++; + } + ap_rputs("<dt><strong>Current Configuration:</strong>\n", r); + for (current = 0; current < allconfigs->nelts; ++current) { + cnew = &((info_clines *) allconfigs->elts)[current]; + /* get relative pathname with some safeguards */ + relpath = ap_stripprefix(cnew->fname,ap_server_root); + if (*relpath != '\0' && relpath != cnew->fname && + *relpath == '/') + relpath++; + mod_info_module_cmds(r, cnew->clines, modp->cmds, + relpath); + } + } + else { + ap_rputs("<tt> none</tt>\n", r); + } + more_info = find_more_info(serv, modp->name); + if (more_info) { + ap_rputs("<dt><strong>Additional Information:</strong>\n<dd>", + r); + ap_rputs(more_info, r); + } + ap_rputs("<dt><hr>\n", r); + if (r->args) { + break; + } + } + } + if (!modp && r->args && strcasecmp(r->args, "server")) { + ap_rputs("<b>No such module</b>\n", r); + } + } + else { + for (modp = top_module; modp; modp = modp->next) { + ap_rputs(modp->name, r); + if (modp->next) { + ap_rputs("<br>", r); + } + } + } + ap_rputs("</dl>\n", r); + ap_rputs(ap_psignature("",r), r); + ap_rputs("</body></html>\n", r); + /* Done, turn off timeout, close file and return */ + ap_kill_timeout(r); + return 0; +} + +static const char *add_module_info(cmd_parms *cmd, void *dummy, char *name, + char *info) +{ + server_rec *s = cmd->server; + info_svr_conf *conf = (info_svr_conf *) ap_get_module_config(s->module_config, + &info_module); + info_entry *new = ap_push_array(conf->more_info); + + new->name = name; + new->info = info; + return NULL; +} + +static const command_rec info_cmds[] = +{ + {"AddModuleInfo", add_module_info, NULL, RSRC_CONF, TAKE2, + "a module name and additional information on that module"}, + {NULL} +}; + +static const handler_rec info_handlers[] = +{ + {"server-info", display_info}, + {NULL} +}; + +module MODULE_VAR_EXPORT info_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + create_info_config, /* server config */ + merge_info_config, /* merge server config */ + info_cmds, /* command table */ + info_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; + diff --git a/APACHE_1_3_42/src/modules/standard/mod_log_agent.c b/APACHE_1_3_42/src/modules/standard/mod_log_agent.c new file mode 100644 index 0000000000..54da090e41 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_log_agent.c @@ -0,0 +1,147 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" + +module agent_log_module; + +static int xfer_flags = (O_WRONLY | O_APPEND | O_CREAT); +#ifdef OS2 +/* OS/2 dosen't support users and groups */ +static mode_t xfer_mode = (S_IREAD | S_IWRITE); +#else +static mode_t xfer_mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +#endif + +typedef struct { + char *fname; + int agent_fd; +} agent_log_state; + +static void *make_agent_log_state(pool *p, server_rec *s) +{ + agent_log_state *cls = + (agent_log_state *) ap_palloc(p, sizeof(agent_log_state)); + + cls->fname = ""; + cls->agent_fd = -1; + + return (void *) cls; +} + +static const char *set_agent_log(cmd_parms *parms, void *dummy, char *arg) +{ + agent_log_state *cls = ap_get_module_config(parms->server->module_config, + &agent_log_module); + + cls->fname = arg; + return NULL; +} + +static const command_rec agent_log_cmds[] = +{ + {"AgentLog", set_agent_log, NULL, RSRC_CONF, TAKE1, + "the filename of the agent log"}, + {NULL} +}; + +static void open_agent_log(server_rec *s, pool *p) +{ + agent_log_state *cls = ap_get_module_config(s->module_config, + &agent_log_module); + + char *fname = ap_server_root_relative(p, cls->fname); + + if (cls->agent_fd > 0) + return; /* virtual log shared w/main server */ + + if (*cls->fname == '|') { + piped_log *pl; + + pl = ap_open_piped_log(p, cls->fname + 1); + if (pl == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "couldn't spawn agent log pipe"); + exit(1); + } + cls->agent_fd = ap_piped_log_write_fd(pl); + } + else if (*cls->fname != '\0') { + if ((cls->agent_fd = ap_popenf_ex(p, fname, xfer_flags, xfer_mode, 1)) + < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "could not open agent log file %s.", fname); + exit(1); + } + } +} + +static void init_agent_log(server_rec *s, pool *p) +{ + for (; s; s = s->next) + open_agent_log(s, p); +} + +static int agent_log_transaction(request_rec *orig) +{ + agent_log_state *cls = ap_get_module_config(orig->server->module_config, + &agent_log_module); + + char str[HUGE_STRING_LEN]; + const char *agent; + request_rec *r; + + if (cls->agent_fd < 0) + return OK; + + for (r = orig; r->next; r = r->next) + continue; + if (*cls->fname == '\0') /* Don't log agent */ + return DECLINED; + + agent = ap_table_get(orig->headers_in, "User-Agent"); + if (agent != NULL) { + ap_snprintf(str, sizeof(str), "%s\n", agent); + write(cls->agent_fd, str, strlen(str)); + } + + return OK; +} + +module agent_log_module = +{ + STANDARD_MODULE_STUFF, + init_agent_log, /* initializer */ + NULL, /* create per-dir config */ + NULL, /* merge per-dir config */ + make_agent_log_state, /* server config */ + NULL, /* merge server config */ + agent_log_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + agent_log_transaction, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_log_config.c b/APACHE_1_3_42/src/modules/standard/mod_log_config.c new file mode 100644 index 0000000000..a7a0c22a3d --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_log_config.c @@ -0,0 +1,1169 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Modified by djm@va.pubnix.com: + * If no TransferLog is given explicitly, decline to log. + * + * This is module implements the TransferLog directive (same as the + * common log module), and additional directives, LogFormat and CustomLog. + * + * + * Syntax: + * + * TransferLog fn Logs transfers to fn in standard log format, unless + * a custom format is set with LogFormat + * LogFormat format Set a log format from TransferLog files + * CustomLog fn format + * Log to file fn with format given by the format + * argument + * + * CookieLog fn For backwards compatability with old Cookie + * logging module - now deprecated. + * + * There can be any number of TransferLog and CustomLog + * commands. Each request will be logged to _ALL_ the + * named files, in the appropriate format. + * + * If no TransferLog or CustomLog directive appears in a VirtualHost, + * the request will be logged to the log file(s) defined outside + * the virtual host section. If a TransferLog or CustomLog directive + * appears in the VirtualHost section, the log files defined outside + * the VirtualHost will _not_ be used. This makes this module compatable + * with the CLF and config log modules, where the use of TransferLog + * inside the VirtualHost section overrides its use outside. + * + * Examples: + * + * TransferLog logs/access_log + * <VirtualHost> + * LogFormat "... custom format ..." + * TransferLog log/virtual_only + * CustomLog log/virtual_useragents "%t %{user-agent}i" + * </VirtualHost> + * + * This will log using CLF to access_log any requests handled by the + * main server, while any requests to the virtual host will be logged + * with the "... custom format..." to virtual_only _AND_ using + * the custom user-agent log to virtual_useragents. + * + * Note that the NCSA referer and user-agent logs are easily added with + * CustomLog: + * CustomLog logs/referer "%{referer}i -> %U" + * CustomLog logs/agent "%{user-agent}i" + * + * RefererIgnore functionality can be obtained with conditional + * logging (SetEnvIf and CustomLog ... env=!VAR). + * + * But using this method allows much easier modification of the + * log format, e.g. to log hosts along with UA: + * CustomLog logs/referer "%{referer}i %U %h" + * + * The argument to LogFormat and CustomLog is a string, which can include + * literal characters copied into the log files, and '%' directives as + * follows: + * + * %...a: remote IP-address + * %...A: local IP-address + * %...b: bytes sent, excluding HTTP headers in CLF format, i.e. a '-' + * when no bytes where sent (rather than a '0'. + * %...B: bytes sent, excluding HTTP headers. + * %...c: Status of the connection. + * 'X' = connection aborted before the response completed. + * '+' = connection may be kept alive after the response is sent. + * '-' = connection will be closed after the response is sent. + * %...{FOOBAR}e: The contents of the environment variable FOOBAR + * %...f: filename + * %...h: remote host + * %...H: the request protocol + * %...{Foobar}i: The contents of Foobar: header line(s) in the request + * sent to the client. + * %...l: remote logname (from identd, if supplied) + * %...m: the request method + * %...{Foobar}n: The contents of note "Foobar" from another module. + * %...{Foobar}o: The contents of Foobar: header line(s) in the reply. + * %...p: the port the request was served to + * %...P: the process ID of the child that serviced the request. + * %...q: the query string prepended by "?", or empty if no query string + * %...r: first line of request + * %...s: status. For requests that got internally redirected, this + * is status of the *original* request --- %...>s for the last. + * %...t: time, in common log format time format + * %...{format}t: The time, in the form given by format, which should + * be in strftime(3) format. + * %...T: the time taken to serve the request, in seconds. + * %...u: remote user (from auth; may be bogus if return status (%s) is 401) + * %...U: the URL path requested. + * %...v: the configured name of the server (i.e. which virtual host?) + * %...V: the server name according to the UseCanonicalName setting + * %...X: An alias for %..c (Status of the connection). + * + * The '...' can be nothing at all (e.g. "%h %u %r %s %b"), or it can + * indicate conditions for inclusion of the item (which will cause it + * to be replaced with '-' if the condition is not met). Note that + * there is no escaping performed on the strings from %r, %...i and + * %...o; some with long memories may remember that I thought this was + * a bad idea, once upon a time, and I'm still not comfortable with + * it, but it is difficult to see how to "do the right thing" with all + * of '%..i', unless we URL-escape everything and break with CLF. + * + * The forms of condition are a list of HTTP status codes, which may + * or may not be preceded by '!'. Thus, '%400,501{User-agent}i' logs + * User-agent: on 400 errors and 501 errors (Bad Request, Not + * Implemented) only; '%!200,304,302{Referer}i' logs Referer: on all + * requests which did *not* return some sort of normal status. + * + * The default LogFormat reproduces CLF; see below. + * + * The way this is supposed to work with virtual hosts is as follows: + * a virtual host can have its own LogFormat, or its own TransferLog. + * If it doesn't have its own LogFormat, it inherits from the main + * server. If it doesn't have its own TransferLog, it writes to the + * same descriptor (meaning the same process for "| ..."). + * + * --- rst */ + +#define DEFAULT_LOG_FORMAT "%h %l %u %t \"%r\" %>s %b" + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" /* For REMOTE_NAME */ +#include "http_log.h" +#include <limits.h> + +module MODULE_VAR_EXPORT config_log_module; + +static int xfer_flags = (O_WRONLY | O_APPEND | O_CREAT); +#if defined(OS2) || defined(WIN32) || defined(NETWARE) +/* OS/2 dosen't support users and groups */ +static mode_t xfer_mode = (S_IREAD | S_IWRITE); +#else +static mode_t xfer_mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +#endif + +/* POSIX.1 defines PIPE_BUF as the maximum number of bytes that is + * guaranteed to be atomic when writing a pipe. And PIPE_BUF >= 512 + * is guaranteed. So we'll just guess 512 in the event the system + * doesn't have this. Now, for file writes there is actually no limit, + * the entire write is atomic. Whether all systems implement this + * correctly is another question entirely ... so we'll just use PIPE_BUF + * because it's probably a good guess as to what is implemented correctly + * everywhere. + */ +#ifdef PIPE_BUF +#define LOG_BUFSIZE PIPE_BUF +#else +#define LOG_BUFSIZE (512) +#endif + +/* + * multi_log_state is our per-(virtual)-server configuration. We store + * an array of the logs we are going to use, each of type config_log_state. + * If a default log format is given by LogFormat, store in default_format + * (backward compat. with mod_log_config). We also store for each virtual + * server a pointer to the logs specified for the main server, so that if this + * vhost has no logs defined, we can use the main server's logs instead. + * + * So, for the main server, config_logs contains a list of the log files + * and server_config_logs in empty. For a vhost, server_config_logs + * points to the same array as config_logs in the main server, and + * config_logs points to the array of logs defined inside this vhost, + * which might be empty. + */ + +typedef struct { + char *default_format_string; + array_header *default_format; + array_header *config_logs; + array_header *server_config_logs; + table *formats; +} multi_log_state; + +/* + * config_log_state holds the status of a single log file. fname might + * be NULL, which means this module does no logging for this + * request. format might be NULL, in which case the default_format + * from the multi_log_state should be used, or if that is NULL as + * well, use the CLF. log_fd is -1 before the log file is opened and + * set to a valid fd after it is opened. + */ + +typedef struct { + char *fname; + char *format_string; + array_header *format; + int log_fd; + char *condition_var; +#ifdef BUFFERED_LOGS + int outcnt; + char outbuf[LOG_BUFSIZE]; +#endif +} config_log_state; + +/* + * Format items... + * Note that many of these could have ap_sprintfs replaced with static buffers. + */ + +typedef const char *(*item_key_func) (request_rec *, char *); + +typedef struct { + item_key_func func; + char *arg; + int condition_sense; + int want_orig; + array_header *conditions; +} log_format_item; + +static char *format_integer(pool *p, int i) +{ + return ap_psprintf(p, "%d", i); +} + +static char *pfmt(pool *p, int i) +{ + if (i <= 0) { + return "-"; + } + else { + return format_integer(p, i); + } +} + +static const char *constant_item(request_rec *dummy, char *stuff) +{ + return stuff; +} + +static const char *log_remote_host(request_rec *r, char *a) +{ + return ap_escape_logitem(r->pool, ap_get_remote_host(r->connection, r->per_dir_config, + REMOTE_NAME)); +} + +static const char *log_remote_address(request_rec *r, char *a) +{ + return r->connection->remote_ip; +} + +static const char *log_local_address(request_rec *r, char *a) +{ + return r->connection->local_ip; +} + +static const char *log_remote_logname(request_rec *r, char *a) +{ + return ap_escape_logitem(r->pool, ap_get_remote_logname(r)); +} + +static const char *log_remote_user(request_rec *r, char *a) +{ + char *rvalue = r->connection->user; + + if (rvalue == NULL) { + rvalue = "-"; + } + else if (strlen(rvalue) == 0) { + rvalue = "\"\""; + } + else + rvalue = ap_escape_logitem(r->pool, rvalue); + return rvalue; +} + +static const char *log_request_line(request_rec *r, char *a) +{ + /* NOTE: If the original request contained a password, we + * re-write the request line here to contain XXXXXX instead: + * (note the truncation before the protocol string for HTTP/0.9 requests) + * (note also that r->the_request contains the unmodified request) + */ + return ap_escape_logitem(r->pool, + (r->parsed_uri.password) ? ap_pstrcat(r->pool, r->method, " ", + ap_unparse_uri_components(r->pool, &r->parsed_uri, 0), + r->assbackwards ? NULL : " ", r->protocol, NULL) + : r->the_request + ); +} + +static const char *log_request_file(request_rec *r, char *a) +{ + return r->filename; +} +static const char *log_request_uri(request_rec *r, char *a) +{ + return ap_escape_logitem(r->pool, r->uri); +} +static const char *log_request_method(request_rec *r, char *a) +{ + return ap_escape_logitem(r->pool, r->method); +} +static const char *log_request_protocol(request_rec *r, char *a) +{ + return ap_escape_logitem(r->pool, r->protocol); +} +static const char *log_request_query(request_rec *r, char *a) +{ + return (r->args != NULL) ? ap_pstrcat(r->pool, "?", + ap_escape_logitem(r->pool, r->args), NULL) + : ""; +} +static const char *log_status(request_rec *r, char *a) +{ + return pfmt(r->pool, r->status); +} + +static const char *clf_log_bytes_sent(request_rec *r, char *a) +{ + if (!r->sent_bodyct) { + return "-"; + } + else { + long int bs; + ap_bgetopt(r->connection->client, BO_BYTECT, &bs); + return ap_psprintf(r->pool, "%ld", bs); + } +} + +static const char *log_bytes_sent(request_rec *r, char *a) +{ + if (!r->sent_bodyct) { + return "0"; + } + else { + long int bs; + ap_bgetopt(r->connection->client, BO_BYTECT, &bs); + return ap_psprintf(r->pool, "%ld", bs); + } +} + + +static const char *log_header_in(request_rec *r, char *a) +{ + return ap_escape_logitem(r->pool, ap_table_get(r->headers_in, a)); +} + +static const char *log_header_out(request_rec *r, char *a) +{ + const char *cp = ap_table_get(r->headers_out, a); + if (!strcasecmp(a, "Content-type") && r->content_type) { + cp = ap_field_noparam(r->pool, r->content_type); + } + if (cp) { + return cp; + } + return ap_table_get(r->err_headers_out, a); +} + +static const char *log_note(request_rec *r, char *a) +{ + return ap_table_get(r->notes, a); +} +static const char *log_env_var(request_rec *r, char *a) +{ + return ap_table_get(r->subprocess_env, a); +} + +static const char *log_request_time(request_rec *r, char *a) +{ + int timz; + struct tm *t; + char tstr[MAX_STRING_LEN]; + + t = ap_get_gmtoff(&timz); + + if (a && *a) { /* Custom format */ + strftime(tstr, MAX_STRING_LEN, a, t); + } + else { /* CLF format */ + char sign = (timz < 0 ? '-' : '+'); + + if (timz < 0) { + timz = -timz; + } + ap_snprintf(tstr, sizeof(tstr), "[%02d/%s/%d:%02d:%02d:%02d %c%.2d%.2d]", + t->tm_mday, ap_month_snames[t->tm_mon], t->tm_year+1900, + t->tm_hour, t->tm_min, t->tm_sec, + sign, timz / 60, timz % 60); + } + + return ap_pstrdup(r->pool, tstr); +} + +static const char *log_request_duration(request_rec *r, char *a) +{ + return ap_psprintf(r->pool, "%ld", time(NULL) - r->request_time); +} + +/* These next two routines use the canonical name:port so that log + * parsers don't need to duplicate all the vhost parsing crud. + */ +static const char *log_virtual_host(request_rec *r, char *a) +{ + return r->server->server_hostname; +} + +static const char *log_server_port(request_rec *r, char *a) +{ + return ap_psprintf(r->pool, "%u", + r->server->port ? r->server->port : ap_default_port(r)); +} + +/* This respects the setting of UseCanonicalName so that + * the dynamic mass virtual hosting trick works better. + */ +static const char *log_server_name(request_rec *r, char *a) +{ + return ap_get_server_name(r); +} + +static const char *log_child_pid(request_rec *r, char *a) +{ + return ap_psprintf(r->pool, "%ld", (long) getpid()); +} + +static const char *log_connection_status(request_rec *r, char *a) +{ + if (r->connection->aborted) + return "X"; + + if ((r->connection->keepalive) && + ((r->server->keep_alive_max - r->connection->keepalives) > 0)) { + return "+"; + } + + return "-"; +} + +/***************************************************************** + * + * Parsing the log format string + */ + +static struct log_item_list { + char ch; + item_key_func func; + int want_orig_default; +} log_item_keys[] = { + + { + 'a', log_remote_address, 0 + }, + { + 'A', log_local_address, 0 + }, + { + 'b', clf_log_bytes_sent, 0 + }, + { + 'B', log_bytes_sent, 0 + }, + { + 'c', log_connection_status, 0 + }, + { + 'e', log_env_var, 0 + }, + { + 'f', log_request_file, 0 + }, + { + 'h', log_remote_host, 0 + }, + { + 'H', log_request_protocol, 0 + }, + { + 'i', log_header_in, 0 + }, + { + 'l', log_remote_logname, 0 + }, + { + 'm', log_request_method, 0 + }, + { + 'n', log_note, 0 + }, + { + 'o', log_header_out, 0 + }, + { + 'p', log_server_port, 0 + }, + { + 'P', log_child_pid, 0 + }, + { + 'q', log_request_query, 0 + }, + { + 'r', log_request_line, 1 + }, + { + 's', log_status, 1 + }, + { + 't', log_request_time, 0 + }, + { + 'T', log_request_duration, 1 + }, + { + 'u', log_remote_user, 0 + }, + { + 'U', log_request_uri, 1 + }, + { + 'v', log_virtual_host, 0 + }, + { + 'V', log_server_name, 0 + }, + { + 'X', log_connection_status, 0 + }, + { + '\0' + } +}; + +static struct log_item_list *find_log_func(char k) +{ + int i; + + for (i = 0; log_item_keys[i].ch; ++i) + if (k == log_item_keys[i].ch) { + return &log_item_keys[i]; + } + + return NULL; +} + +static char *parse_log_misc_string(pool *p, log_format_item *it, + const char **sa) +{ + const char *s; + char *d; + + it->func = constant_item; + it->conditions = NULL; + + s = *sa; + while (*s && *s != '%') { + s++; + } + /* + * This might allocate a few chars extra if there's a backslash + * escape in the format string. + */ + it->arg = ap_palloc(p, s - *sa + 1); + + d = it->arg; + s = *sa; + while (*s && *s != '%') { + if (*s != '\\') { + *d++ = *s++; + } + else { + s++; + switch (*s) { + case '\\': + *d++ = '\\'; + s++; + break; + case 'n': + *d++ = '\n'; + s++; + break; + case 't': + *d++ = '\t'; + s++; + break; + default: + /* copy verbatim */ + *d++ = '\\'; + /* + * Allow the loop to deal with this *s in the normal + * fashion so that it handles end of string etc. + * properly. + */ + break; + } + } + } + *d = '\0'; + + *sa = s; + return NULL; +} + +static char *parse_log_item(pool *p, log_format_item *it, const char **sa) +{ + const char *s = *sa; + + if (*s != '%') { + return parse_log_misc_string(p, it, sa); + } + + ++s; + it->condition_sense = 0; + it->conditions = NULL; + it->want_orig = -1; + it->arg = ""; /* For safety's sake... */ + + while (*s) { + int i; + struct log_item_list *l; + + switch (*s) { + case '!': + ++s; + it->condition_sense = !it->condition_sense; + break; + + case '<': + ++s; + it->want_orig = 1; + break; + + case '>': + ++s; + it->want_orig = 0; + break; + + case ',': + ++s; + break; + + case '{': + ++s; + it->arg = ap_getword(p, &s, '}'); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + i = *s - '0'; + while (ap_isdigit(*++s)) { + i = i * 10 + (*s) - '0'; + } + if (!it->conditions) { + it->conditions = ap_make_array(p, 4, sizeof(int)); + } + *(int *) ap_push_array(it->conditions) = i; + break; + + default: + l = find_log_func(*s++); + if (!l) { + char dummy[2]; + + dummy[0] = s[-1]; + dummy[1] = '\0'; + return ap_pstrcat(p, "Unrecognized LogFormat directive %", + dummy, NULL); + } + it->func = l->func; + if (it->want_orig == -1) { + it->want_orig = l->want_orig_default; + } + *sa = s; + return NULL; + } + } + + return "Ran off end of LogFormat parsing args to some directive"; +} + +static array_header *parse_log_string(pool *p, const char *s, const char **err) +{ + array_header *a = ap_make_array(p, 30, sizeof(log_format_item)); + char *res; + + while (*s) { + if ((res = parse_log_item(p, (log_format_item *) ap_push_array(a), &s))) { + *err = res; + return NULL; + } + } + + s = "\n"; + parse_log_item(p, (log_format_item *) ap_push_array(a), &s); + return a; +} + +/***************************************************************** + * + * Actually logging. + */ + +static const char *process_item(request_rec *r, request_rec *orig, + log_format_item *item) +{ + const char *cp; + + /* First, see if we need to process this thing at all... */ + + if (item->conditions && item->conditions->nelts != 0) { + int i; + int *conds = (int *) item->conditions->elts; + int in_list = 0; + + for (i = 0; i < item->conditions->nelts; ++i) { + if (r->status == conds[i]) { + in_list = 1; + break; + } + } + + if ((item->condition_sense && in_list) + || (!item->condition_sense && !in_list)) { + return "-"; + } + } + + /* We do. Do it... */ + + cp = (*item->func) (item->want_orig ? orig : r, item->arg); + return cp ? cp : "-"; +} + +#ifdef BUFFERED_LOGS +static void flush_log(config_log_state *cls) +{ + if (cls->outcnt && cls->log_fd != -1) { + write(cls->log_fd, cls->outbuf, cls->outcnt); + cls->outcnt = 0; + } +} +#endif + +static int config_log_transaction(request_rec *r, config_log_state *cls, + array_header *default_format) +{ + log_format_item *items; + char *str, *s; + const char **strs; + int *strl; + request_rec *orig; + int i; + int len = 0; + array_header *format; + char *envar; + + if (cls->fname == NULL) { + return DECLINED; + } + + /* + * See if we've got any conditional envariable-controlled logging decisions + * to make. + */ + if (cls->condition_var != NULL) { + envar = cls->condition_var; + if (*envar != '!') { + if (ap_table_get(r->subprocess_env, envar) == NULL) { + return DECLINED; + } + } + else { + if (ap_table_get(r->subprocess_env, &envar[1]) != NULL) { + return DECLINED; + } + } + } + + format = cls->format ? cls->format : default_format; + + strs = ap_palloc(r->pool, sizeof(char *) * (format->nelts)); + strl = ap_palloc(r->pool, sizeof(int) * (format->nelts)); + items = (log_format_item *) format->elts; + + orig = r; + while (orig->prev) { + orig = orig->prev; + } + while (r->next) { + r = r->next; + } + + for (i = 0; i < format->nelts; ++i) { + strs[i] = process_item(r, orig, &items[i]); + } + + for (i = 0; i < format->nelts; ++i) { + len += strl[i] = strlen(strs[i]); + } + +#ifdef BUFFERED_LOGS + if (len + cls->outcnt > LOG_BUFSIZE) { + flush_log(cls); + } + if (len >= LOG_BUFSIZE) { + str = ap_palloc(r->pool, len + 1); + for (i = 0, s = str; i < format->nelts; ++i) { + memcpy(s, strs[i], strl[i]); + s += strl[i]; + } + write(cls->log_fd, str, len); + } + else { + for (i = 0, s = &cls->outbuf[cls->outcnt]; i < format->nelts; ++i) { + memcpy(s, strs[i], strl[i]); + s += strl[i]; + } + cls->outcnt += len; + } +#else + str = ap_palloc(r->pool, len + 1); + + for (i = 0, s = str; i < format->nelts; ++i) { + memcpy(s, strs[i], strl[i]); + s += strl[i]; + } + + write(cls->log_fd, str, len); +#endif + + return OK; +} + +static int multi_log_transaction(request_rec *r) +{ + multi_log_state *mls = ap_get_module_config(r->server->module_config, + &config_log_module); + config_log_state *clsarray; + int i; + + /* + * Log this transaction.. + */ + if (mls->config_logs->nelts) { + clsarray = (config_log_state *) mls->config_logs->elts; + for (i = 0; i < mls->config_logs->nelts; ++i) { + config_log_state *cls = &clsarray[i]; + + config_log_transaction(r, cls, mls->default_format); + } + } + else if (mls->server_config_logs) { + clsarray = (config_log_state *) mls->server_config_logs->elts; + for (i = 0; i < mls->server_config_logs->nelts; ++i) { + config_log_state *cls = &clsarray[i]; + + config_log_transaction(r, cls, mls->default_format); + } + } + + return OK; +} + +/***************************************************************** + * + * Module glue... + */ + +static void *make_config_log_state(pool *p, server_rec *s) +{ + multi_log_state *mls; + + mls = (multi_log_state *) ap_palloc(p, sizeof(multi_log_state)); + mls->config_logs = ap_make_array(p, 1, sizeof(config_log_state)); + mls->default_format_string = NULL; + mls->default_format = NULL; + mls->server_config_logs = NULL; + mls->formats = ap_make_table(p, 4); + ap_table_setn(mls->formats, "CLF", DEFAULT_LOG_FORMAT); + + return mls; +} + +/* + * Use the merger to simply add a pointer from the vhost log state + * to the log of logs specified for the non-vhost configuration. Make sure + * vhosts inherit any globally-defined format names. + */ + +static void *merge_config_log_state(pool *p, void *basev, void *addv) +{ + multi_log_state *base = (multi_log_state *) basev; + multi_log_state *add = (multi_log_state *) addv; + + add->server_config_logs = base->config_logs; + if (!add->default_format) { + add->default_format_string = base->default_format_string; + add->default_format = base->default_format; + } + add->formats = ap_overlay_tables(p, base->formats, add->formats); + + return add; +} + +/* + * Set the default logfile format, or define a nickname for a format string. + */ +static const char *log_format(cmd_parms *cmd, void *dummy, char *fmt, + char *name) +{ + const char *err_string = NULL; + multi_log_state *mls = ap_get_module_config(cmd->server->module_config, + &config_log_module); + + /* + * If we were given two arguments, the second is a name to be given to the + * format. This syntax just defines the nickname - it doesn't actually + * make the format the default. + */ + if (name != NULL) { + parse_log_string(cmd->pool, fmt, &err_string); + if (err_string == NULL) { + ap_table_setn(mls->formats, name, fmt); + } + } + else { + mls->default_format_string = fmt; + mls->default_format = parse_log_string(cmd->pool, fmt, &err_string); + } + return err_string; +} + + +static const char *add_custom_log(cmd_parms *cmd, void *dummy, char *fn, + char *fmt, char *envclause) +{ + const char *err_string = NULL; + multi_log_state *mls = ap_get_module_config(cmd->server->module_config, + &config_log_module); + config_log_state *cls; + + cls = (config_log_state *) ap_push_array(mls->config_logs); + cls->condition_var = NULL; + if (envclause != NULL) { + if (strncasecmp(envclause, "env=", 4) != 0) { + return "error in condition clause"; + } + if ((envclause[4] == '\0') + || ((envclause[4] == '!') && (envclause[5] == '\0'))) { + return "missing environment variable name"; + } + cls->condition_var = ap_pstrdup(cmd->pool, &envclause[4]); + } + + cls->fname = fn; + cls->format_string = fmt; + if (fmt == NULL) { + cls->format = NULL; + } + else { + cls->format = parse_log_string(cmd->pool, fmt, &err_string); + } + cls->log_fd = -1; + + return err_string; +} + +static const char *set_transfer_log(cmd_parms *cmd, void *dummy, char *fn) +{ + return add_custom_log(cmd, dummy, fn, NULL, NULL); +} + +static const char *set_cookie_log(cmd_parms *cmd, void *dummy, char *fn) +{ + return add_custom_log(cmd, dummy, fn, "%{Cookie}n \"%r\" %t", NULL); +} + +static const command_rec config_log_cmds[] = +{ + {"CustomLog", add_custom_log, NULL, RSRC_CONF, TAKE23, + "a file name, a custom log format string or format name, " + "and an optional \"env=\" clause (see docs)"}, + {"TransferLog", set_transfer_log, NULL, RSRC_CONF, TAKE1, + "the filename of the access log"}, + {"LogFormat", log_format, NULL, RSRC_CONF, TAKE12, + "a log format string (see docs) and an optional format name"}, + {"CookieLog", set_cookie_log, NULL, RSRC_CONF, TAKE1, + "the filename of the cookie log"}, + {NULL} +}; + +static config_log_state *open_config_log(server_rec *s, pool *p, + config_log_state *cls, + array_header *default_format) +{ + if (cls->log_fd > 0) { + return cls; /* virtual config shared w/main server */ + } + + if (cls->fname == NULL) { + return cls; /* Leave it NULL to decline. */ + } + + if (*cls->fname == '|') { + piped_log *pl; + + pl = ap_open_piped_log(p, cls->fname + 1); + if (pl == NULL) { + exit(1); + } + cls->log_fd = ap_piped_log_write_fd(pl); + } + else { + char *fname = ap_server_root_relative(p, cls->fname); + if ((cls->log_fd = ap_popenf_ex(p, fname, xfer_flags, xfer_mode, 1)) + < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "could not open transfer log file %s.", fname); + exit(1); + } + } +#ifdef BUFFERED_LOGS + cls->outcnt = 0; +#endif + + return cls; +} + +static config_log_state *open_multi_logs(server_rec *s, pool *p) +{ + int i; + multi_log_state *mls = ap_get_module_config(s->module_config, + &config_log_module); + config_log_state *clsarray; + const char *dummy; + const char *format; + + if (mls->default_format_string) { + format = ap_table_get(mls->formats, mls->default_format_string); + if (format) { + mls->default_format = parse_log_string(p, format, &dummy); + } + } + + if (!mls->default_format) { + mls->default_format = parse_log_string(p, DEFAULT_LOG_FORMAT, &dummy); + } + + if (mls->config_logs->nelts) { + clsarray = (config_log_state *) mls->config_logs->elts; + for (i = 0; i < mls->config_logs->nelts; ++i) { + config_log_state *cls = &clsarray[i]; + + if (cls->format_string) { + format = ap_table_get(mls->formats, cls->format_string); + if (format) { + cls->format = parse_log_string(p, format, &dummy); + } + } + + cls = open_config_log(s, p, cls, mls->default_format); + } + } + else if (mls->server_config_logs) { + clsarray = (config_log_state *) mls->server_config_logs->elts; + for (i = 0; i < mls->server_config_logs->nelts; ++i) { + config_log_state *cls = &clsarray[i]; + + if (cls->format_string) { + format = ap_table_get(mls->formats, cls->format_string); + if (format) { + cls->format = parse_log_string(p, format, &dummy); + } + } + + cls = open_config_log(s, p, cls, mls->default_format); + } + } + + return NULL; +} + +static void init_config_log(server_rec *s, pool *p) +{ + /* First, do "physical" server, which gets default log fd and format + * for the virtual servers, if they don't override... + */ + + open_multi_logs(s, p); + + /* Then, virtual servers */ + + for (s = s->next; s; s = s->next) { + open_multi_logs(s, p); + } +} + +#ifdef BUFFERED_LOGS +static void flush_all_logs(server_rec *s, pool *p) +{ + multi_log_state *mls; + array_header *log_list; + config_log_state *clsarray; + int i; + + for (; s; s = s->next) { + mls = ap_get_module_config(s->module_config, &config_log_module); + log_list = NULL; + if (mls->config_logs->nelts) { + log_list = mls->config_logs; + } + else if (mls->server_config_logs) { + log_list = mls->server_config_logs; + } + if (log_list) { + clsarray = (config_log_state *) log_list->elts; + for (i = 0; i < log_list->nelts; ++i) { + flush_log(&clsarray[i]); + } + } + } +} +#endif + +module MODULE_VAR_EXPORT config_log_module = +{ + STANDARD_MODULE_STUFF, + init_config_log, /* initializer */ + NULL, /* create per-dir config */ + NULL, /* merge per-dir config */ + make_config_log_state, /* server config */ + merge_config_log_state, /* merge server config */ + config_log_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + multi_log_transaction, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ +#ifdef BUFFERED_LOGS + flush_all_logs, /* child_exit */ +#else + NULL, +#endif + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_log_forensic.c b/APACHE_1_3_42/src/modules/standard/mod_log_forensic.c new file mode 100644 index 0000000000..4e2008722f --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_log_forensic.c @@ -0,0 +1,322 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * See also support/check_forensic. + * Relate the forensic log to the transfer log by including + * %{forensic-id}n in the custom log format, for example: + * CustomLog logs/custom "%h %l %u %t \"%r\" %>s %b %{forensic-id}n" + * + * Credit is due to Tina Bird <tbird@precision-guesswork.com>, whose + * idea this module was. + * + * Ben Laurie 29/12/2003 + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "multithread.h" + +#ifdef NETWARE +#include "test_char.h" +#else +/* XXX This should be fixed in the INCLUDE path of the makefile + so that a specific location is not hard coded here. */ +#include "../../main/test_char.h" +#endif + +module MODULE_VAR_EXPORT log_forensic_module; + +#ifdef WIN32 + +static DWORD tls_index; + +BOOL WINAPI DllMain (HINSTANCE dllhandle, DWORD reason, LPVOID reserved) +{ + switch (reason) { + case DLL_PROCESS_ATTACH: + tls_index = TlsAlloc(); + case DLL_THREAD_ATTACH: /* intentional no break */ + TlsSetValue(tls_index, 0); + break; + } + return TRUE; +} + +const char * get_forensic_id(pool *p) +{ + /* The 'error' default for Get undefined is 0 - a nice number + * for this purpose. The cast might look evil, but the evil + * empire had switched this API out from underneath developers, + * and the DWORD flavor will truncate nicely for our purposes. + */ + DWORD next_id = (DWORD)TlsGetValue(tls_index); + TlsSetValue(tls_index, (void*)(next_id + 1)); + + return ap_psprintf(p, "%x:%x:%lx:%x", GetCurrentProcessId(), + GetCurrentThreadId(), + time(NULL), next_id); +} + +#else /* !WIN32 */ + +/* Even when not MULTITHREAD, this will return a single structure, since + * APACHE_TLS should be defined as empty on single-threaded platforms. + */ +const char * get_forensic_id(pool *p) +{ + static APACHE_TLS next_id = 0; + + /* we make the assumption that we can't go through all the PIDs in + under 1 second */ +#ifdef MULTITHREAD + return ap_psprintf(p, "%x:%x:%lx:%x", getpid(), gettid(), time(NULL), next_id++); +#else + return ap_psprintf(p, "%x:%lx:%x", getpid(), time(NULL), next_id++); +#endif +} + +#endif /* !WIN32 */ + +typedef struct fcfg { + char *logname; + int fd; +} fcfg; + +static void *make_forensic_log_scfg(pool *p, server_rec *s) +{ + fcfg *cfg = ap_pcalloc(p, sizeof *cfg); + + cfg->logname = NULL; + cfg->fd = -1; + + return cfg; +} + +static void *merge_forensic_log_scfg(pool *p, void *parent, void *new) +{ + fcfg *cfg = ap_pcalloc(p, sizeof *cfg); + fcfg *pc = parent; + fcfg *nc = new; + + cfg->logname = ap_pstrdup(p, nc->logname ? nc->logname : pc->logname); + cfg->fd = -1; + + return cfg; +} + +static void open_log(server_rec *s, pool *p) +{ + fcfg *cfg = ap_get_module_config(s->module_config, &log_forensic_module); + + if (!cfg->logname || cfg->fd >= 0) + return; + + if (*cfg->logname == '|') { + piped_log *pl; + + pl = ap_open_piped_log(p, cfg->logname+1); + if (pl == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "couldn't spawn forensic log pipe %s", cfg->logname); + exit(1); + } + cfg->fd = ap_piped_log_write_fd(pl); + } + else { + char *fname = ap_server_root_relative(p, cfg->logname); + + if ((cfg->fd = ap_popenf_ex(p, fname, O_WRONLY | O_APPEND | O_CREAT, + 0644, 1)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "could not open forensic log file %s.", fname); + exit(1); + } + } +} + +static void log_init(server_rec *s, pool *p) +{ + for ( ; s ; s = s->next) + open_log(s, p); +} + + +/* e is the first _invalid_ location in q + N.B. returns the terminating NUL. + */ +static char *log_escape(char *q, const char *e, const char *p) +{ + for ( ; *p ; ++p) { + ap_assert(q < e); + if (test_char_table[*(unsigned char *)p]&T_ESCAPE_FORENSIC) { + ap_assert(q+2 < e); + *q++ = '%'; + sprintf(q, "%02x", *(unsigned char *)p); + q += 2; + } + else + *q++ = *p; + } + ap_assert(q < e); + *q = '\0'; + + return q; +} + +typedef struct hlog { + char *log; + char *pos; + char *end; + pool *p; + int count; +} hlog; + +static int count_string(const char *p) +{ + int n; + + for (n = 0 ; *p ; ++p, ++n) + if (test_char_table[*(unsigned char *)p]&T_ESCAPE_FORENSIC) + n += 2; + return n; +} + +static int count_headers(void *h_, const char *key, const char *value) +{ + hlog *h = h_; + + h->count += count_string(key)+count_string(value)+2; + + return 1; +} + +static int log_headers(void *h_, const char *key, const char *value) +{ + hlog *h = h_; + + /* note that we don't have to check h->pos here, coz its been done + for us by log_escape */ + *h->pos++ = '|'; + h->pos = log_escape(h->pos, h->end, key); + *h->pos++ = ':'; + h->pos = log_escape(h->pos, h->end, value); + + return 1; +} + +static int log_before(request_rec *r) +{ + fcfg *cfg = ap_get_module_config(r->server->module_config, + &log_forensic_module); + const char *id; + hlog h; + + if (cfg->fd < 0 || r->prev) + return DECLINED; + + if (!(id = ap_table_get(r->subprocess_env, "UNIQUE_ID"))) { + id = get_forensic_id(r->pool); + } + + h.p = r->pool; + h.count = 0; + + ap_table_do(count_headers, &h, r->headers_in, NULL); + + h.count += 1+strlen(id)+1+count_string(r->the_request)+1+1; + h.log = ap_palloc(r->pool, h.count); + h.pos = h.log; + h.end = h.log+h.count; + + *h.pos++ = '+'; + strcpy(h.pos, id); + h.pos += strlen(h.pos); + *h.pos++ = '|'; + h.pos = log_escape(h.pos, h.end, r->the_request); + + ap_table_do(log_headers, &h, r->headers_in, NULL); + + ap_assert(h.pos < h.end); + *h.pos++ = '\n'; + + write(cfg->fd, h.log, h.count-1); + + ap_table_setn(r->notes, "forensic-id", id); + + return OK; +} + +static int log_after(request_rec *r) +{ + fcfg *cfg = ap_get_module_config(r->server->module_config, + &log_forensic_module); + const char *id; + char *s; + + if(cfg->fd < 0) + return DECLINED; + + id = ap_table_get(r->notes, "forensic-id"); + + if (!id) + return DECLINED; + + s = ap_pstrcat(r->pool, "-", id, "\n", NULL); + write(cfg->fd, s, strlen(s)); + + return OK; +} + +static const char *set_forensic_log(cmd_parms *cmd, void *dummy, char *fn) +{ + fcfg *cfg = ap_get_module_config(cmd->server->module_config, + &log_forensic_module); + + cfg->logname = fn; + return NULL; +} + +static const command_rec forensic_log_cmds[] = +{ + { "ForensicLog", set_forensic_log, NULL, RSRC_CONF, TAKE1, + "the filename of the forensic log" }, + { NULL } +}; + +module MODULE_VAR_EXPORT log_forensic_module = +{ + STANDARD_MODULE_STUFF, + log_init, /* initializer */ + NULL, /* create per-dir config */ + NULL, /* merge per-dir config */ + make_forensic_log_scfg, /* server config */ + merge_forensic_log_scfg, /* merge server config */ + forensic_log_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + log_after, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + log_before /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_log_referer.c b/APACHE_1_3_42/src/modules/standard/mod_log_referer.c new file mode 100644 index 0000000000..9e3957a435 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_log_referer.c @@ -0,0 +1,188 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" + +module referer_log_module; + +static int xfer_flags = (O_WRONLY | O_APPEND | O_CREAT); + +#ifdef OS2 +/* OS/2 lacks support for users and groups */ +static mode_t xfer_mode = (S_IREAD | S_IWRITE); +#else +static mode_t xfer_mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +#endif + +typedef struct { + char *fname; + int referer_fd; + array_header *referer_ignore_list; +} referer_log_state; + +static void *make_referer_log_state(pool *p, server_rec *s) +{ + referer_log_state *cls = + (referer_log_state *) ap_palloc(p, sizeof(referer_log_state)); + + cls->fname = ""; + cls->referer_fd = -1; + cls->referer_ignore_list = ap_make_array(p, 1, sizeof(char *)); + return (void *) cls; +} + +static const char *set_referer_log(cmd_parms *parms, void *dummy, char *arg) +{ + referer_log_state *cls = ap_get_module_config(parms->server->module_config, + &referer_log_module); + + cls->fname = arg; + return NULL; +} + +static const char *add_referer_ignore(cmd_parms *parms, void *dummy, char *arg) +{ + char **addme; + referer_log_state *cls = ap_get_module_config(parms->server->module_config, + &referer_log_module); + + addme = ap_push_array(cls->referer_ignore_list); + ap_str_tolower(arg); + *addme = arg; + return NULL; +} + +static const command_rec referer_log_cmds[] = +{ + {"RefererLog", set_referer_log, NULL, RSRC_CONF, TAKE1, + "the filename of the referer log"}, + {"RefererIgnore", add_referer_ignore, NULL, RSRC_CONF, ITERATE, + "referer hostnames to ignore"}, + {NULL} +}; + +static void open_referer_log(server_rec *s, pool *p) +{ + referer_log_state *cls = ap_get_module_config(s->module_config, + &referer_log_module); + + char *fname = ap_server_root_relative(p, cls->fname); + + if (cls->referer_fd > 0) + return; /* virtual log shared w/main server */ + + if (*cls->fname == '|') { + piped_log *pl; + + pl = ap_open_piped_log(p, cls->fname + 1); + if (pl == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "couldn't spawn referer log pipe"); + exit(1); + } + + cls->referer_fd = ap_piped_log_write_fd(pl); + } + else if (*cls->fname != '\0') { + if ((cls->referer_fd = ap_popenf_ex(p, fname, xfer_flags, xfer_mode, 1)) + < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "could not open referer log file %s.", fname); + exit(1); + } + } +} + +static void init_referer_log(server_rec *s, pool *p) +{ + for (; s; s = s->next) + open_referer_log(s, p); +} + +static int referer_log_transaction(request_rec *orig) +{ + char **ptrptr, **ptrptr2; + referer_log_state *cls = ap_get_module_config(orig->server->module_config, + &referer_log_module); + + char *str; + const char *referer; + char *referertest; + request_rec *r; + + if (cls->referer_fd < 0) + return OK; + + for (r = orig; r->next; r = r->next) + continue; + if (*cls->fname == '\0') /* Don't log referer */ + return DECLINED; + + referer = ap_table_get(orig->headers_in, "Referer"); + if (referer != NULL) { + + referertest = ap_pstrdup(orig->pool, referer); + ap_str_tolower(referertest); + /* The following is an upsetting mess of pointers, I'm sorry + Anyone with the motiviation and/or the time should feel free + to make this cleaner... */ + + ptrptr2 = (char **) (cls->referer_ignore_list->elts + + (cls->referer_ignore_list->nelts * + cls->referer_ignore_list->elt_size)); + + /* Go through each element of the ignore list and compare it to the + referer_host. If we get a match, return without logging */ + + for (ptrptr = (char **) cls->referer_ignore_list->elts; + ptrptr < ptrptr2; + ptrptr = (char **) ((char *) ptrptr + cls->referer_ignore_list->elt_size)) { + if (strstr(referertest, *ptrptr)) + return OK; + } + + + str = ap_pstrcat(orig->pool, referer, " -> ", r->uri, "\n", NULL); + write(cls->referer_fd, str, strlen(str)); + } + + return OK; +} + +module referer_log_module = +{ + STANDARD_MODULE_STUFF, + init_referer_log, /* initializer */ + NULL, /* create per-dir config */ + NULL, /* merge per-dir config */ + make_referer_log_state, /* server config */ + NULL, /* merge server config */ + referer_log_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + referer_log_transaction, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_mime.c b/APACHE_1_3_42/src/modules/standard/mod_mime.c new file mode 100644 index 0000000000..b096466417 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_mime.c @@ -0,0 +1,756 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * http_mime.c: Sends/gets MIME headers for requests + * + * Rob McCool + * + */ + +#define MIME_PRIVATE + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" + +/* + * isascii(c) isn't universal, and even those places where it is + * defined it's not always right for our needs. Roll our own that + * we can rely on. + */ +#define ap_isascii(c) ((OS_ASC(c) & 0x80) == 0) + +typedef struct attrib_info { + char *name; +} attrib_info; + +typedef struct { + table *forced_types; /* Additional AddTyped stuff */ + table *encoding_types; /* Added with AddEncoding... */ + table *charset_types; /* Added with AddCharset... */ + table *language_types; /* Added with AddLanguage... */ + table *handlers; /* Added with AddHandler... */ + array_header *handlers_remove; /* List of handlers to remove */ + array_header *types_remove; /* List of MIME types to remove */ + array_header *encodings_remove; /* List of encodings to remove */ + + char *type; /* Type forced with ForceType */ + char *handler; /* Handler forced with SetHandler */ + char *default_language; /* Language if no AddLanguage ext found */ +} mime_dir_config; + +typedef struct param_s { + char *attr; + char *val; + struct param_s *next; +} param; + +typedef struct { + char *type; + char *subtype; + param *param; +} content_type; + +static char tspecial[] = { + '(', ')', '<', '>', '@', ',', ';', ':', + '\\', '"', '/', '[', ']', '?', '=', + '\0' +}; + +module MODULE_VAR_EXPORT mime_module; + +static void *create_mime_dir_config(pool *p, char *dummy) +{ + mime_dir_config *new = + (mime_dir_config *) ap_palloc(p, sizeof(mime_dir_config)); + + new->forced_types = ap_make_table(p, 4); + new->encoding_types = ap_make_table(p, 4); + new->charset_types = ap_make_table(p, 4); + new->language_types = ap_make_table(p, 4); + new->handlers = ap_make_table(p, 4); + new->handlers_remove = ap_make_array(p, 4, sizeof(attrib_info)); + new->types_remove = ap_make_array(p, 4, sizeof(attrib_info)); + new->encodings_remove = ap_make_array(p, 4, sizeof(attrib_info)); + + new->type = NULL; + new->handler = NULL; + new->default_language = NULL; + + return new; +} + +static void *merge_mime_dir_configs(pool *p, void *basev, void *addv) +{ + mime_dir_config *base = (mime_dir_config *) basev; + mime_dir_config *add = (mime_dir_config *) addv; + mime_dir_config *new = + (mime_dir_config *) ap_palloc(p, sizeof(mime_dir_config)); + int i; + attrib_info *suffix; + + new->forced_types = ap_overlay_tables(p, add->forced_types, + base->forced_types); + new->encoding_types = ap_overlay_tables(p, add->encoding_types, + base->encoding_types); + new->charset_types = ap_overlay_tables(p, add->charset_types, + base->charset_types); + new->language_types = ap_overlay_tables(p, add->language_types, + base->language_types); + new->handlers = ap_overlay_tables(p, add->handlers, + base->handlers); + + suffix = (attrib_info *) add->handlers_remove->elts; + for (i = 0; i < add->handlers_remove->nelts; i++) { + ap_table_unset(new->handlers, suffix[i].name); + } + suffix = (attrib_info *) add->types_remove->elts; + for (i = 0; i < add->types_remove->nelts; i++) { + ap_table_unset(new->forced_types, suffix[i].name); + } + suffix = (attrib_info *) add->encodings_remove->elts; + for (i = 0; i < add->encodings_remove->nelts; i++) { + ap_table_unset(new->encoding_types, suffix[i].name); + } + + new->type = add->type ? add->type : base->type; + new->handler = add->handler ? add->handler : base->handler; + new->default_language = add->default_language ? + add->default_language : base->default_language; + + return new; +} + +static const char *add_type(cmd_parms *cmd, mime_dir_config *m, char *ct, + char *ext) +{ + if (*ext == '.') + ++ext; + + ap_str_tolower(ct); + ap_table_setn(m->forced_types, ext, ct); + return NULL; +} + +static const char *add_encoding(cmd_parms *cmd, mime_dir_config *m, char *enc, + char *ext) +{ + if (*ext == '.') + ++ext; + ap_str_tolower(enc); + ap_table_setn(m->encoding_types, ext, enc); + return NULL; +} + +static const char *add_charset(cmd_parms *cmd, mime_dir_config *m, + char *charset, char *ext) +{ + if (*ext == '.') { + ++ext; + } + ap_str_tolower(charset); + ap_table_setn(m->charset_types, ext, charset); + return NULL; +} + +static const char *add_language(cmd_parms *cmd, mime_dir_config *m, char *lang, + char *ext) +{ + if (*ext == '.') { + ++ext; + } + ap_str_tolower(lang); + ap_table_setn(m->language_types, ext, lang); + return NULL; +} + +static const char *add_handler(cmd_parms *cmd, mime_dir_config *m, char *hdlr, + char *ext) +{ + if (*ext == '.') + ++ext; + ap_str_tolower(hdlr); + ap_table_setn(m->handlers, ext, hdlr); + return NULL; +} + +/* + * Note handler names that should be un-added for this location. This + * will keep the association from being inherited, as well, but not + * from being re-added at a subordinate level. + */ +static const char *remove_handler(cmd_parms *cmd, void *m, char *ext) +{ + mime_dir_config *mcfg = (mime_dir_config *) m; + attrib_info *suffix; + + if (*ext == '.') { + ++ext; + } + suffix = (attrib_info *) ap_push_array(mcfg->handlers_remove); + suffix->name = ap_pstrdup(cmd->pool, ext); + return NULL; +} + +/* + * Just like the previous function, except that it records encoding + * associations to be undone. + */ +static const char *remove_encoding(cmd_parms *cmd, void *m, char *ext) +{ + mime_dir_config *mcfg = (mime_dir_config *) m; + attrib_info *suffix; + + if (*ext == '.') { + ++ext; + } + suffix = (attrib_info *) ap_push_array(mcfg->encodings_remove); + suffix->name = ap_pstrdup(cmd->pool, ext); + return NULL; +} + +/* + * Similar to the previous functions, except that it deals with filename + * suffix/MIME-type associations. + */ +static const char *remove_type(cmd_parms *cmd, void *m, char *ext) +{ + mime_dir_config *mcfg = (mime_dir_config *) m; + attrib_info *suffix; + + if (*ext == '.') { + ++ext; + } + suffix = (attrib_info *) ap_push_array(mcfg->types_remove); + suffix->name = ap_pstrdup(cmd->pool, ext); + return NULL; +} + +/* The sole bit of server configuration that the MIME module has is + * the name of its config file, so... + */ + +static const char *set_types_config(cmd_parms *cmd, void *dummy, char *arg) +{ + ap_set_module_config(cmd->server->module_config, &mime_module, arg); + return NULL; +} + +static const command_rec mime_cmds[] = +{ + {"AddType", add_type, NULL, OR_FILEINFO, ITERATE2, + "a mime type followed by one or more file extensions"}, + {"AddEncoding", add_encoding, NULL, OR_FILEINFO, ITERATE2, + "an encoding (e.g., gzip), followed by one or more file extensions"}, + {"AddCharset", add_charset, NULL, OR_FILEINFO, ITERATE2, + "a charset (e.g., iso-2022-jp), followed by one or more file extensions"}, + {"AddLanguage", add_language, NULL, OR_FILEINFO, ITERATE2, + "a language (e.g., fr), followed by one or more file extensions"}, + {"AddHandler", add_handler, NULL, OR_FILEINFO, ITERATE2, + "a handler name followed by one or more file extensions"}, + {"ForceType", ap_set_string_slot_lower, + (void *)XtOffsetOf(mime_dir_config, type), OR_FILEINFO, TAKE1, + "a media type"}, + {"RemoveHandler", remove_handler, NULL, OR_FILEINFO, ITERATE, + "one or more file extensions"}, + {"RemoveEncoding", remove_encoding, NULL, OR_FILEINFO, ITERATE, + "one or more file extensions"}, + {"RemoveType", remove_type, NULL, OR_FILEINFO, ITERATE, + "one or more file extensions"}, + {"SetHandler", ap_set_string_slot_lower, + (void *)XtOffsetOf(mime_dir_config, handler), OR_FILEINFO, TAKE1, + "a handler name"}, + {"TypesConfig", set_types_config, NULL, RSRC_CONF, TAKE1, + "the MIME types config file"}, + {"DefaultLanguage", ap_set_string_slot, + (void*)XtOffsetOf(mime_dir_config, default_language), OR_FILEINFO, TAKE1, + "language to use for documents with no other language file extension" }, + {NULL} +}; + +/* Hash table --- only one of these per daemon; virtual hosts can + * get private versions through AddType... + */ + +#define MIME_HASHSIZE (32) +#define hash(i) (ap_tolower(i) % MIME_HASHSIZE) + +static table *hash_buckets[MIME_HASHSIZE]; + +static void init_mime(server_rec *s, pool *p) +{ + configfile_t *f; + char l[MAX_STRING_LEN]; + int x; + char *types_confname = ap_get_module_config(s->module_config, &mime_module); + + if (!types_confname) + types_confname = TYPES_CONFIG_FILE; + + types_confname = ap_server_root_relative(p, types_confname); + + if (!(f = ap_pcfg_openfile(p, types_confname))) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "could not open mime types config file %s.", types_confname); + exit(1); + } + + for (x = 0; x < MIME_HASHSIZE; x++) + hash_buckets[x] = ap_make_table(p, 10); + + while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { + const char *ll = l, *ct; + + if (l[0] == '#') + continue; + ct = ap_getword_conf(p, &ll); + + while (ll[0]) { + char *ext = ap_getword_conf(p, &ll); + ap_str_tolower(ext); /* ??? */ + ap_table_setn(hash_buckets[hash(ext[0])], ext, ct); + } + } + ap_cfg_closefile(f); +} + +static char *zap_sp(char *s) +{ + char *tp; + + if (s == NULL) { + return (NULL); + } + if (*s == '\0') { + return (s); + } + + /* delete prefixed white space */ + for (; *s == ' ' || *s == '\t' || *s == '\n'; s++); + + /* delete postfixed white space */ + for (tp = s; *tp != '\0'; tp++); + for (tp--; tp != s && (*tp == ' ' || *tp == '\t' || *tp == '\n'); tp--) { + *tp = '\0'; + } + return (s); +} + +static int is_token(int c) +{ + int res; + + res = (ap_isascii(c) && ap_isgraph(c) + && (strchr(tspecial, c) == NULL)) ? 1 : -1; + return res; +} + +static int is_qtext(int c) +{ + int res; + + res = (ap_isascii(c) && (c != '"') && (c != '\\') && (c != '\n')) + ? 1 : -1; + return res; +} + +static int is_quoted_pair(char *s) +{ + int res = -1; + int c; + + if (((s + 1) != NULL) && (*s == '\\')) { + c = (int) *(s + 1); + if (ap_isascii(c)) { + res = 1; + } + } + return (res); +} + +static content_type *analyze_ct(pool *p, char *s) +{ + char *tp, *mp, *cp; + char *attribute, *value; + int quoted = 0; + + content_type *ctp; + param *pp, *npp; + + /* initialize ctp */ + ctp = (content_type *) ap_palloc(p, sizeof(content_type)); + ctp->type = NULL; + ctp->subtype = NULL; + ctp->param = NULL; + + tp = ap_pstrdup(p, s); + + mp = tp; + cp = mp; + + /* getting a type */ + if (!(cp = strchr(mp, '/'))) { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "mod_mime: analyze_ct: cannot get media type from '%s'", + mp); + return (NULL); + } + ctp->type = ap_pstrndup(p, mp, cp - mp); + ctp->type = zap_sp(ctp->type); + if (ctp->type == NULL || *(ctp->type) == '\0' || + strchr(ctp->type, ';') || strchr(ctp->type, ' ') || + strchr(ctp->type, '\t')) { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media subtype."); + return (NULL); + } + + /* getting a subtype */ + cp++; + mp = cp; + + for (; *cp != ';' && *cp != '\0'; cp++); + ctp->subtype = ap_pstrndup(p, mp, cp - mp); + ctp->subtype = zap_sp(ctp->subtype); + if ((ctp->subtype == NULL) || (*(ctp->subtype) == '\0') || + strchr(ctp->subtype, ' ') || strchr(ctp->subtype, '\t')) { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media subtype."); + return (NULL); + } + cp = zap_sp(cp); + if (cp == NULL || *cp == '\0') { + return (ctp); + } + + /* getting parameters */ + cp++; + cp = zap_sp(cp); + if (cp == NULL || *cp == '\0') { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media parameter."); + return (NULL); + } + mp = cp; + attribute = NULL; + value = NULL; + + while (cp != NULL && *cp != '\0') { + if (attribute == NULL) { + if (is_token((int) *cp) > 0) { + cp++; + continue; + } + else if (*cp == ' ' || *cp == '\t' || *cp == '\n') { + cp++; + continue; + } + else if (*cp == '=') { + attribute = ap_pstrndup(p, mp, cp - mp); + attribute = zap_sp(attribute); + if (attribute == NULL || *attribute == '\0') { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media parameter."); + return (NULL); + } + cp++; + cp = zap_sp(cp); + if (cp == NULL || *cp == '\0') { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media parameter."); + return (NULL); + } + mp = cp; + continue; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media parameter."); + return (NULL); + } + } + else { + if (mp == cp) { + if (*cp == '"') { + quoted = 1; + cp++; + } + else { + quoted = 0; + } + } + if (quoted > 0) { + while (quoted && *cp != '\0') { + if (is_qtext((int) *cp) > 0) { + cp++; + } + else if (is_quoted_pair(cp) > 0) { + cp += 2; + } + else if (*cp == '"') { + cp++; + while (*cp == ' ' || *cp == '\t' || *cp == '\n') { + cp++; + } + if (*cp != ';' && *cp != '\0') { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media parameter."); + return(NULL); + } + quoted = 0; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media parameter."); + return (NULL); + } + } + } + else { + while (1) { + if (is_token((int) *cp) > 0) { + cp++; + } + else if (*cp == '\0' || *cp == ';') { + break; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media parameter."); + return (NULL); + } + } + } + value = ap_pstrndup(p, mp, cp - mp); + value = zap_sp(value); + if (value == NULL || *value == '\0') { + ap_log_error(APLOG_MARK, APLOG_WARNING, NULL, + "Cannot get media parameter."); + return (NULL); + } + + pp = ap_palloc(p, sizeof(param)); + pp->attr = attribute; + pp->val = value; + pp->next = NULL; + + if (ctp->param == NULL) { + ctp->param = pp; + } + else { + npp = ctp->param; + while (npp->next) { + npp = npp->next; + } + npp->next = pp; + } + quoted = 0; + attribute = NULL; + value = NULL; + if (*cp == '\0') { + break; + } + cp++; + mp = cp; + } + } + return (ctp); +} + +static int find_ct(request_rec *r) +{ + mime_dir_config *conf; + array_header *exception_list; + const char *fn; + char *ext; + const char *type; + const char *charset = NULL; + int found_metadata = 0; + + if (S_ISDIR(r->finfo.st_mode)) { + r->content_type = DIR_MAGIC_TYPE; + return OK; + } + + conf = (mime_dir_config *) ap_get_module_config(r->per_dir_config, + &mime_module); + + exception_list = ap_make_array(r->pool, 2, sizeof(char *)); + + /* Always drop the leading element */ + fn = strrchr(r->filename, '/'); + if (fn == NULL) + fn = r->filename; + else + ++fn; + + /* The exception list keeps track of those filename components that + * are not associated with extensions indicating metadata. + * The base name is always the first exception (i.e., "txt.html" has + * a basename of "txt" even though it might look like an extension). + */ + ext = ap_getword(r->pool, &fn, '.'); + *((const char **) ap_push_array(exception_list)) = ext; + + /* Parse filename extensions, which can be in any order */ + while ((ext = ap_getword(r->pool, &fn, '.')) && *ext) { + int found = 0; + + /* Check for Content-Type */ + if ((type = ap_table_get(conf->forced_types, ext)) + || (type = ap_table_get(hash_buckets[hash(*ext)], ext))) { + r->content_type = type; + found = 1; + } + + /* Add charset to Content-Type */ + if ((type = ap_table_get(conf->charset_types, ext))) { + charset = type; + found = 1; + } + + /* Check for Content-Language */ + if ((type = ap_table_get(conf->language_types, ext))) { + const char **new; + + r->content_language = type; /* back compat. only */ + if (!r->content_languages) + r->content_languages = ap_make_array(r->pool, 2, sizeof(char *)); + new = (const char **) ap_push_array(r->content_languages); + *new = type; + found = 1; + } + + /* Check for Content-Encoding */ + if ((type = ap_table_get(conf->encoding_types, ext))) { + if (!r->content_encoding) + r->content_encoding = type; + else + r->content_encoding = ap_pstrcat(r->pool, r->content_encoding, + ", ", type, NULL); + found = 1; + } + + /* Check for a special handler, but not for proxy request */ + if ((type = ap_table_get(conf->handlers, ext)) + && r->proxyreq == NOT_PROXY) { + r->handler = type; + found = 1; + } + + if (found) + found_metadata = 1; + else + *((const char **) ap_push_array(exception_list)) = ext; + } + + /* Need to see a notes entry on r for unrecognized elements. + * Somebody better claim them! If we did absolutly nothing, + * skip the notes to alert mod_negotiation we are clueless. + */ + if (found_metadata) { + ap_table_setn(r->notes, "ap-mime-exceptions-list", + (void *) exception_list); + } + + if (r->content_type) { + content_type *ctp; + char *ct; + int override = 0; + + ct = (char *) ap_palloc(r->pool, + sizeof(char) * (strlen(r->content_type) + 1)); + strcpy(ct, r->content_type); + + if ((ctp = analyze_ct(r->pool, ct))) { + param *pp = ctp->param; + r->content_type = ap_pstrcat(r->pool, ctp->type, "/", + ctp->subtype, NULL); + while (pp != NULL) { + if (charset && !strcmp(pp->attr, "charset")) { + if (!override) { + r->content_type = ap_pstrcat(r->pool, r->content_type, + "; charset=", charset, + NULL); + override = 1; + } + } + else { + r->content_type = ap_pstrcat(r->pool, r->content_type, + "; ", pp->attr, + "=", pp->val, + NULL); + } + pp = pp->next; + } + if (charset && !override) { + r->content_type = ap_pstrcat(r->pool, r->content_type, + "; charset=", charset, + NULL); + } + } + } + + /* Set default language, if none was specified by the extensions + * and we have a DefaultLanguage setting in force + */ + + if (!r->content_languages && conf->default_language) { + const char **new; + + r->content_language = conf->default_language; /* back compat. only */ + if (!r->content_languages) + r->content_languages = ap_make_array(r->pool, 2, sizeof(char *)); + new = (const char **) ap_push_array(r->content_languages); + *new = conf->default_language; + } + + /* Check for overrides with ForceType/SetHandler */ + + if (conf->type && strcmp(conf->type, "none")) + r->content_type = conf->type; + if (conf->handler && strcmp(conf->handler, "none")) + r->handler = conf->handler; + + if (!r->content_type) + return DECLINED; + + return OK; +} + +module MODULE_VAR_EXPORT mime_module = +{ + STANDARD_MODULE_STUFF, + init_mime, /* initializer */ + create_mime_dir_config, /* dir config creator */ + merge_mime_dir_configs, /* dir config merger */ + NULL, /* server config */ + NULL, /* merge server config */ + mime_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + find_ct, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_mime_magic.c b/APACHE_1_3_42/src/modules/standard/mod_mime_magic.c new file mode 100644 index 0000000000..90c532776c --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_mime_magic.c @@ -0,0 +1,2472 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_mime_magic: MIME type lookup via file magic numbers + * Copyright (c) 1996-1997 Cisco Systems, Inc. + * + * This software was submitted by Cisco Systems to the Apache Group in July + * 1997. Future revisions and derivatives of this source code must + * acknowledge Cisco Systems as the original contributor of this module. + * All other licensing and usage conditions are those of the Apache Group. + * + * Some of this code is derived from the free version of the file command + * originally posted to comp.sources.unix. Copyright info for that program + * is included below as required. + * --------------------------------------------------------------------------- + * - Copyright (c) Ian F. Darwin, 1987. Written by Ian F. Darwin. + * + * This software is not subject to any license of the American Telephone and + * Telegraph Company or of the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on any + * computer system, and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The author is not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, credits + * must appear in the documentation. + * + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users ever read + * sources, credits must appear in the documentation. + * + * 4. This notice may not be removed or altered. + * ------------------------------------------------------------------------- + * + * For compliance with Mr Darwin's terms: this has been very significantly + * modified from the free "file" command. + * - all-in-one file for compilation convenience when moving from one + * version of Apache to the next. + * - Memory allocation is done through the Apache API's pool structure. + * - All functions have had necessary Apache API request or server + * structures passed to them where necessary to call other Apache API + * routines. (i.e. usually for logging, files, or memory allocation in + * itself or a called function.) + * - struct magic has been converted from an array to a single-ended linked + * list because it only grows one record at a time, it's only accessed + * sequentially, and the Apache API has no equivalent of realloc(). + * - Functions have been changed to get their parameters from the server + * configuration instead of globals. (It should be reentrant now but has + * not been tested in a threaded environment.) + * - Places where it used to print results to stdout now saves them in a + * list where they're used to set the MIME type in the Apache request + * record. + * - Command-line flags have been removed since they will never be used here. + * + * Ian Kluft <ikluft@cisco.com> + * Engineering Information Framework + * Central Engineering + * Cisco Systems, Inc. + * San Jose, CA, USA + * + * Initial installation July/August 1996 + * Misc bug fixes May 1997 + * Submission to Apache Group July 1997 + * + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" + +#ifndef WIN32 +#include <utime.h> +#endif + +/* + * data structures and related constants + */ + +#define MODNAME "mod_mime_magic" +#define MIME_MAGIC_DEBUG 0 + +#define MIME_BINARY_UNKNOWN "application/octet-stream" +#define MIME_TEXT_UNKNOWN "text/plain" + +#define MAXMIMESTRING 256 + +/* HOWMANY must be at least 4096 to make gzip -dcq work */ +#define HOWMANY 4096 +/* SMALL_HOWMANY limits how much work we do to figure out text files */ +#define SMALL_HOWMANY 1024 +#define MAXDESC 50 /* max leng of text description */ +#define MAXstring 64 /* max leng of "string" types */ + +struct magic { + struct magic *next; /* link to next entry */ + int lineno; /* line number from magic file */ + + short flag; +#define INDIR 1 /* if '>(...)' appears, */ +#define UNSIGNED 2 /* comparison is unsigned */ + short cont_level; /* level of ">" */ + struct { + char type; /* byte short long */ + long offset; /* offset from indirection */ + } in; + long offset; /* offset to magic number */ + unsigned char reln; /* relation (0=eq, '>'=gt, etc) */ + char type; /* int, short, long or string. */ + char vallen; /* length of string value, if any */ +#define BYTE 1 +#define SHORT 2 +#define LONG 4 +#define STRING 5 +#define DATE 6 +#define BESHORT 7 +#define BELONG 8 +#define BEDATE 9 +#define LESHORT 10 +#define LELONG 11 +#define LEDATE 12 + union VALUETYPE { + unsigned char b; + unsigned short h; + unsigned long l; + char s[MAXstring]; + unsigned char hs[2]; /* 2 bytes of a fixed-endian "short" */ + unsigned char hl[4]; /* 2 bytes of a fixed-endian "long" */ + } value; /* either number or string */ + unsigned long mask; /* mask before comparison with value */ + char nospflag; /* supress space character */ + + /* NOTE: this string is suspected of overrunning - find it! */ + char desc[MAXDESC]; /* description */ +}; + +/* + * data structures for tar file recognition + * -------------------------------------------------------------------------- + * Header file for public domain tar (tape archive) program. + * + * @(#)tar.h 1.20 86/10/29 Public Domain. Created 25 August 1985 by John + * Gilmore, ihnp4!hoptoad!gnu. + * + * Header block on tape. + * + * I'm going to use traditional DP naming conventions here. A "block" is a big + * chunk of stuff that we do I/O on. A "record" is a piece of info that we + * care about. Typically many "record"s fit into a "block". + */ +#define RECORDSIZE 512 +#define NAMSIZ 100 +#define TUNMLEN 32 +#define TGNMLEN 32 + +union record { + char charptr[RECORDSIZE]; + struct header { + char name[NAMSIZ]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char chksum[8]; + char linkflag; + char linkname[NAMSIZ]; + char magic[8]; + char uname[TUNMLEN]; + char gname[TGNMLEN]; + char devmajor[8]; + char devminor[8]; + } header; +}; + +/* The magic field is filled with this if uname and gname are valid. */ +#define TMAGIC "ustar " /* 7 chars and a null */ + +/* + * file-function prototypes + */ +static int ascmagic(request_rec *, unsigned char *, int); +static int is_tar(unsigned char *, int); +static int softmagic(request_rec *, unsigned char *, int); +static void tryit(request_rec *, unsigned char *, int, int); +static int zmagic(request_rec *, unsigned char *, int); + +static int getvalue(server_rec *, struct magic *, char **); +static int hextoint(int); +static char *getstr(server_rec *, char *, char *, int, int *); +static int parse(server_rec *, pool *p, char *, int); + +static int match(request_rec *, unsigned char *, int); +static int mget(request_rec *, union VALUETYPE *, unsigned char *, + struct magic *, int); +static int mcheck(request_rec *, union VALUETYPE *, struct magic *); +static void mprint(request_rec *, union VALUETYPE *, struct magic *); + +static int uncompress(request_rec *, int, + unsigned char **, int); +static long from_oct(int, char *); +static int fsmagic(request_rec *r, const char *fn); + +/* + * includes for ASCII substring recognition formerly "names.h" in file + * command + * + * Original notes: names and types used by ascmagic in file(1). These tokens are + * here because they can appear anywhere in the first HOWMANY bytes, while + * tokens in /etc/magic must appear at fixed offsets into the file. Don't + * make HOWMANY too high unless you have a very fast CPU. + */ + +/* these types are used to index the table 'types': keep em in sync! */ +/* HTML inserted in first because this is a web server module now */ +#define L_HTML 0 /* HTML */ +#define L_C 1 /* first and foremost on UNIX */ +#define L_FORT 2 /* the oldest one */ +#define L_MAKE 3 /* Makefiles */ +#define L_PLI 4 /* PL/1 */ +#define L_MACH 5 /* some kinda assembler */ +#define L_ENG 6 /* English */ +#define L_PAS 7 /* Pascal */ +#define L_MAIL 8 /* Electronic mail */ +#define L_NEWS 9 /* Usenet Netnews */ + +static char *types[] = +{ + "text/html", /* HTML */ + "text/plain", /* "c program text", */ + "text/plain", /* "fortran program text", */ + "text/plain", /* "make commands text", */ + "text/plain", /* "pl/1 program text", */ + "text/plain", /* "assembler program text", */ + "text/plain", /* "English text", */ + "text/plain", /* "pascal program text", */ + "message/rfc822", /* "mail text", */ + "message/news", /* "news text", */ + "application/binary", /* "can't happen error on names.h/types", */ + 0 +}; + +static struct names { + char *name; + short type; +} names[] = { + + /* These must be sorted by eye for optimal hit rate */ + /* Add to this list only after substantial meditation */ + { + "<html>", L_HTML + }, + { + "<HTML>", L_HTML + }, + { + "<head>", L_HTML + }, + { + "<HEAD>", L_HTML + }, + { + "<title>", L_HTML + }, + { + "<TITLE>", L_HTML + }, + { + "<h1>", L_HTML + }, + { + "<H1>", L_HTML + }, + { + "<!--", L_HTML + }, + { + "<!DOCTYPE HTML", L_HTML + }, + { + "/*", L_C + }, /* must precede "The", "the", etc. */ + { + "#include", L_C + }, + { + "char", L_C + }, + { + "The", L_ENG + }, + { + "the", L_ENG + }, + { + "double", L_C + }, + { + "extern", L_C + }, + { + "float", L_C + }, + { + "real", L_C + }, + { + "struct", L_C + }, + { + "union", L_C + }, + { + "CFLAGS", L_MAKE + }, + { + "LDFLAGS", L_MAKE + }, + { + "all:", L_MAKE + }, + { + ".PRECIOUS", L_MAKE + }, + /* + * Too many files of text have these words in them. Find another way to + * recognize Fortrash. + */ +#ifdef NOTDEF + { + "subroutine", L_FORT + }, + { + "function", L_FORT + }, + { + "block", L_FORT + }, + { + "common", L_FORT + }, + { + "dimension", L_FORT + }, + { + "integer", L_FORT + }, + { + "data", L_FORT + }, +#endif /* NOTDEF */ + { + ".ascii", L_MACH + }, + { + ".asciiz", L_MACH + }, + { + ".byte", L_MACH + }, + { + ".even", L_MACH + }, + { + ".globl", L_MACH + }, + { + "clr", L_MACH + }, + { + "(input,", L_PAS + }, + { + "dcl", L_PLI + }, + { + "Received:", L_MAIL + }, + { + ">From", L_MAIL + }, + { + "Return-Path:", L_MAIL + }, + { + "Cc:", L_MAIL + }, + { + "Newsgroups:", L_NEWS + }, + { + "Path:", L_NEWS + }, + { + "Organization:", L_NEWS + }, + { + NULL, 0 + } +}; + +#define NNAMES ((sizeof(names)/sizeof(struct names)) - 1) + +/* + * Result String List (RSL) + * + * The file(1) command prints its output. Instead, we store the various + * "printed" strings in a list (allocating memory as we go) and concatenate + * them at the end when we finally know how much space they'll need. + */ + +typedef struct magic_rsl_s { + char *str; /* string, possibly a fragment */ + struct magic_rsl_s *next; /* pointer to next fragment */ +} magic_rsl; + +/* + * Apache module configuration structures + */ + +/* per-server info */ +typedef struct { + char *magicfile; /* where magic be found */ + struct magic *magic; /* head of magic config list */ + struct magic *last; +} magic_server_config_rec; + +/* per-request info */ +typedef struct { + magic_rsl *head; /* result string list */ + magic_rsl *tail; + unsigned suf_recursion; /* recursion depth in suffix check */ +} magic_req_rec; + +/* + * configuration functions - called by Apache API routines + */ + +module MODULE_VAR_EXPORT mime_magic_module; + +static void *create_magic_server_config(pool *p, server_rec *d) +{ + /* allocate the config - use pcalloc because it needs to be zeroed */ + return ap_pcalloc(p, sizeof(magic_server_config_rec)); +} + +static void *merge_magic_server_config(pool *p, void *basev, void *addv) +{ + magic_server_config_rec *base = (magic_server_config_rec *) basev; + magic_server_config_rec *add = (magic_server_config_rec *) addv; + magic_server_config_rec *new = (magic_server_config_rec *) + ap_palloc(p, sizeof(magic_server_config_rec)); + + new->magicfile = add->magicfile ? add->magicfile : base->magicfile; + new->magic = NULL; + new->last = NULL; + return new; +} + +static const char *set_magicfile(cmd_parms *cmd, char *d, char *arg) +{ + magic_server_config_rec *conf = (magic_server_config_rec *) + ap_get_module_config(cmd->server->module_config, + &mime_magic_module); + + if (!conf) { + return MODNAME ": server structure not allocated"; + } + conf->magicfile = arg; + return NULL; +} + +/* + * configuration file commands - exported to Apache API + */ + +static const command_rec mime_magic_cmds[] = +{ + {"MimeMagicFile", set_magicfile, NULL, RSRC_CONF, TAKE1, + "Path to MIME Magic file (in file(1) format)"}, + {NULL} +}; + +/* + * RSL (result string list) processing routines + * + * These collect strings that would have been printed in fragments by file(1) + * into a list of magic_rsl structures with the strings. When complete, + * they're concatenated together to become the MIME content and encoding + * types. + * + * return value conventions for these functions: functions which return int: + * failure = -1, other = result functions which return pointers: failure = 0, + * other = result + */ + +/* allocate a per-request structure and put it in the request record */ +static magic_req_rec *magic_set_config(request_rec *r) +{ + magic_req_rec *req_dat = (magic_req_rec *) ap_palloc(r->pool, + sizeof(magic_req_rec)); + + req_dat->head = req_dat->tail = (magic_rsl *) NULL; + ap_set_module_config(r->request_config, &mime_magic_module, req_dat); + return req_dat; +} + +/* add a string to the result string list for this request */ +/* it is the responsibility of the caller to allocate "str" */ +static int magic_rsl_add(request_rec *r, char *str) +{ + magic_req_rec *req_dat = (magic_req_rec *) + ap_get_module_config(r->request_config, &mime_magic_module); + magic_rsl *rsl; + + /* make sure we have a list to put it in */ + if (!req_dat) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": request config should not be NULL"); + if (!(req_dat = magic_set_config(r))) { + /* failure */ + return -1; + } + } + + /* allocate the list entry */ + rsl = (magic_rsl *) ap_palloc(r->pool, sizeof(magic_rsl)); + + /* fill it */ + rsl->str = str; + rsl->next = (magic_rsl *) NULL; + + /* append to the list */ + if (req_dat->head && req_dat->tail) { + req_dat->tail->next = rsl; + req_dat->tail = rsl; + } + else { + req_dat->head = req_dat->tail = rsl; + } + + /* success */ + return 0; +} + +/* RSL hook for puts-type functions */ +static int magic_rsl_puts(request_rec *r, char *str) +{ + return magic_rsl_add(r, str); +} + +/* RSL hook for printf-type functions */ +static int magic_rsl_printf(request_rec *r, char *str,...) +{ + va_list ap; + + char buf[MAXMIMESTRING]; + + /* assemble the string into the buffer */ + va_start(ap, str); + ap_vsnprintf(buf, sizeof(buf), str, ap); + va_end(ap); + + /* add the buffer to the list */ + return magic_rsl_add(r, ap_pstrdup(r->pool, buf)); +} + +/* RSL hook for putchar-type functions */ +static int magic_rsl_putchar(request_rec *r, char c) +{ + char str[2]; + + /* high overhead for 1 char - just hope they don't do this much */ + str[0] = c; + str[1] = '\0'; + return magic_rsl_add(r, str); +} + +/* allocate and copy a contiguous string from a result string list */ +static char *rsl_strdup(request_rec *r, int start_frag, int start_pos, int len) +{ + char *result; /* return value */ + int cur_frag, /* current fragment number/counter */ + cur_pos, /* current position within fragment */ + res_pos; /* position in result string */ + magic_rsl *frag; /* list-traversal pointer */ + magic_req_rec *req_dat = (magic_req_rec *) + ap_get_module_config(r->request_config, &mime_magic_module); + + /* allocate the result string */ + result = (char *) ap_palloc(r->pool, len + 1); + + /* loop through and collect the string */ + res_pos = 0; + for (frag = req_dat->head, cur_frag = 0; + frag->next; + frag = frag->next, cur_frag++) { + /* loop to the first fragment */ + if (cur_frag < start_frag) + continue; + + /* loop through and collect chars */ + for (cur_pos = (cur_frag == start_frag) ? start_pos : 0; + frag->str[cur_pos]; + cur_pos++) { + if (cur_frag >= start_frag + && cur_pos >= start_pos + && res_pos <= len) { + result[res_pos++] = frag->str[cur_pos]; + if (res_pos > len) { + break; + } + } + } + } + + /* clean up and return */ + result[res_pos] = 0; +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": rsl_strdup() %d chars: %s", res_pos - 1, result); +#endif + return result; +} + +/* states for the state-machine algorithm in magic_rsl_to_request() */ +typedef enum { + rsl_leading_space, rsl_type, rsl_subtype, rsl_separator, rsl_encoding +} rsl_states; + +/* process the RSL and set the MIME info in the request record */ +static int magic_rsl_to_request(request_rec *r) +{ + int cur_frag, /* current fragment number/counter */ + cur_pos, /* current position within fragment */ + type_frag, /* content type starting point: fragment */ + type_pos, /* content type starting point: position */ + type_len, /* content type length */ + encoding_frag, /* content encoding starting point: fragment */ + encoding_pos, /* content encoding starting point: position */ + encoding_len; /* content encoding length */ + + magic_rsl *frag; /* list-traversal pointer */ + rsl_states state; + + magic_req_rec *req_dat = (magic_req_rec *) + ap_get_module_config(r->request_config, &mime_magic_module); + + /* check if we have a result */ + if (!req_dat || !req_dat->head) { + /* empty - no match, we defer to other Apache modules */ + return DECLINED; + } + + /* start searching for the type and encoding */ + state = rsl_leading_space; + type_frag = type_pos = type_len = 0; + encoding_frag = encoding_pos = encoding_len = 0; + for (frag = req_dat->head, cur_frag = 0; + frag && frag->next; + frag = frag->next, cur_frag++) { + /* loop through the characters in the fragment */ + for (cur_pos = 0; frag->str[cur_pos]; cur_pos++) { + if (ap_isspace(frag->str[cur_pos])) { + /* process whitespace actions for each state */ + if (state == rsl_leading_space) { + /* eat whitespace in this state */ + continue; + } + else if (state == rsl_type) { + /* whitespace: type has no slash! */ + return DECLINED; + } + else if (state == rsl_subtype) { + /* whitespace: end of MIME type */ + state++; + continue; + } + else if (state == rsl_separator) { + /* eat whitespace in this state */ + continue; + } + else if (state == rsl_encoding) { + /* whitespace: end of MIME encoding */ + /* we're done */ + frag = req_dat->tail; + break; + } + else { + /* should not be possible */ + /* abandon malfunctioning module */ + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": bad state %d (ws)", state); + return DECLINED; + } + /* NOTREACHED */ + } + else if (state == rsl_type && + frag->str[cur_pos] == '/') { + /* copy the char and go to rsl_subtype state */ + type_len++; + state++; + } + else { + /* process non-space actions for each state */ + if (state == rsl_leading_space) { + /* non-space: begin MIME type */ + state++; + type_frag = cur_frag; + type_pos = cur_pos; + type_len = 1; + continue; + } + else if (state == rsl_type || + state == rsl_subtype) { + /* non-space: adds to type */ + type_len++; + continue; + } + else if (state == rsl_separator) { + /* non-space: begin MIME encoding */ + state++; + encoding_frag = cur_frag; + encoding_pos = cur_pos; + encoding_len = 1; + continue; + } + else if (state == rsl_encoding) { + /* non-space: adds to encoding */ + encoding_len++; + continue; + } + else { + /* should not be possible */ + /* abandon malfunctioning module */ + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": bad state %d (ns)", state); + return DECLINED; + } + /* NOTREACHED */ + } + /* NOTREACHED */ + } + } + + /* if we ended prior to state rsl_subtype, we had incomplete info */ + if (state != rsl_subtype && state != rsl_separator && + state != rsl_encoding) { + /* defer to other modules */ + return DECLINED; + } + + /* save the info in the request record */ + if (state == rsl_subtype || state == rsl_encoding || + state == rsl_encoding) { + char *tmp; + tmp = rsl_strdup(r, type_frag, type_pos, type_len); + /* XXX: this could be done at config time I'm sure... but I'm + * confused by all this magic_rsl stuff. -djg */ + ap_content_type_tolower(tmp); + r->content_type = tmp; + } + if (state == rsl_encoding) { + char *tmp; + tmp = rsl_strdup(r, encoding_frag, + encoding_pos, encoding_len); + /* XXX: this could be done at config time I'm sure... but I'm + * confused by all this magic_rsl stuff. -djg */ + ap_str_tolower(tmp); + r->content_encoding = tmp; + } + + /* detect memory allocation or other errors */ + if (!r->content_type || + (state == rsl_encoding && !r->content_encoding)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": unexpected state %d; could be caused by bad " + "data in magic file", + state); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* success! */ + return OK; +} + +/* + * magic_process - process input file r Apache API request record + * (formerly called "process" in file command, prefix added for clarity) Opens + * the file and reads a fixed-size buffer to begin processing the contents. + */ +static int magic_process(request_rec *r) +{ + int fd = 0; + unsigned char buf[HOWMANY + 1]; /* one extra for terminating '\0' */ + int nbytes = 0; /* number of bytes read from a datafile */ + int result; + + /* + * first try judging the file based on its filesystem status + */ + switch ((result = fsmagic(r, r->filename))) { + case DONE: + magic_rsl_putchar(r, '\n'); + return OK; + case OK: + break; + default: + /* fatal error, bail out */ + return result; + } + + if ((fd = ap_popenf(r->pool, r->filename, O_RDONLY, 0)) < 0) { + /* We can't open it, but we were able to stat it. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + MODNAME ": can't read `%s'", r->filename); + /* let some other handler decide what the problem is */ + return DECLINED; + } + + /* + * try looking at the first HOWMANY bytes + */ + if ((nbytes = read(fd, (char *) buf, sizeof(buf) - 1)) == -1) { + (void) ap_pclosef(r->pool, fd); + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + MODNAME ": read failed: %s", r->filename); + return HTTP_INTERNAL_SERVER_ERROR; + } + + if (nbytes == 0) + magic_rsl_puts(r, MIME_TEXT_UNKNOWN); + else { + buf[nbytes++] = '\0'; /* null-terminate it */ + tryit(r, buf, nbytes, 1); + } + + (void) ap_pclosef(r->pool, fd); + (void) magic_rsl_putchar(r, '\n'); + + return OK; +} + + +static void tryit(request_rec *r, unsigned char *buf, int nb, int checkzmagic) +{ + /* + * Try compression stuff + */ + if (checkzmagic == 1) { + if (zmagic(r, buf, nb) == 1) + return; + } + + /* + * try tests in /etc/magic (or surrogate magic file) + */ + if (softmagic(r, buf, nb) == 1) + return; + + /* + * try known keywords, check for ascii-ness too. + */ + if (ascmagic(r, buf, nb) == 1) + return; + + /* + * abandon hope, all ye who remain here + */ + magic_rsl_puts(r, MIME_BINARY_UNKNOWN); +} + +#define EATAB {while (ap_isspace((unsigned char) *l)) ++l;} + +/* + * apprentice - load configuration from the magic file r + * API request record + */ +static int apprentice(server_rec *s, pool *p) +{ + FILE *f; + char line[BUFSIZ + 1]; + int errs = 0; + int lineno; +#if MIME_MAGIC_DEBUG + int rule = 0; + struct magic *m, *prevm; +#endif + char *fname; + + magic_server_config_rec *conf = (magic_server_config_rec *) + ap_get_module_config(s->module_config, &mime_magic_module); + + fname = ap_server_root_relative(p, conf->magicfile); + f = ap_pfopen(p, fname, "r"); + if (f == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + MODNAME ": can't read magic file %s", fname); + return -1; + } + + /* set up the magic list (empty) */ + conf->magic = conf->last = NULL; + + /* parse it */ + for (lineno = 1; fgets(line, BUFSIZ, f) != NULL; lineno++) { + int ws_offset; + + /* delete newline */ + if (line[0]) { + line[strlen(line) - 1] = '\0'; + } + + /* skip leading whitespace */ + ws_offset = 0; + while (line[ws_offset] && ap_isspace(line[ws_offset])) { + ws_offset++; + } + + /* skip blank lines */ + if (line[ws_offset] == 0) { + continue; + } + + /* comment, do not parse */ + if (line[ws_offset] == '#') + continue; + +#if MIME_MAGIC_DEBUG + /* if we get here, we're going to use it so count it */ + rule++; +#endif + + /* parse it */ + if (parse(s, p, line + ws_offset, lineno) != 0) + ++errs; + } + + (void) ap_pfclose(p, f); + +#if MIME_MAGIC_DEBUG + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, s, + MODNAME ": apprentice conf=%x file=%s m=%s m->next=%s last=%s", + conf, + conf->magicfile ? conf->magicfile : "NULL", + conf->magic ? "set" : "NULL", + (conf->magic && conf->magic->next) ? "set" : "NULL", + conf->last ? "set" : "NULL"); + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, s, + MODNAME ": apprentice read %d lines, %d rules, %d errors", + lineno, rule, errs); +#endif + +#if MIME_MAGIC_DEBUG + prevm = 0; + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, s, + MODNAME ": apprentice test"); + for (m = conf->magic; m; m = m->next) { + if (ap_isprint((((unsigned long) m) >> 24) & 255) && + ap_isprint((((unsigned long) m) >> 16) & 255) && + ap_isprint((((unsigned long) m) >> 8) & 255) && + ap_isprint(((unsigned long) m) & 255)) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, s, + MODNAME ": apprentice: POINTER CLOBBERED! " + "m=\"%c%c%c%c\" line=%d", + (((unsigned long) m) >> 24) & 255, + (((unsigned long) m) >> 16) & 255, + (((unsigned long) m) >> 8) & 255, + ((unsigned long) m) & 255, + prevm ? prevm->lineno : -1); + break; + } + prevm = m; + } +#endif + + return (errs ? -1 : 0); +} + +/* + * extend the sign bit if the comparison is to be signed + */ +static unsigned long signextend(server_rec *s, struct magic *m, unsigned long v) +{ + if (!(m->flag & UNSIGNED)) + switch (m->type) { + /* + * Do not remove the casts below. They are vital. When later + * compared with the data, the sign extension must have happened. + */ + case BYTE: + v = (char) v; + break; + case SHORT: + case BESHORT: + case LESHORT: + v = (short) v; + break; + case DATE: + case BEDATE: + case LEDATE: + case LONG: + case BELONG: + case LELONG: + v = (long) v; + break; + case STRING: + break; + default: + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, s, + MODNAME ": can't happen: m->type=%d", m->type); + return -1; + } + return v; +} + +/* + * parse one line from magic file, put into magic[index++] if valid + */ +static int parse(server_rec *serv, pool *p, char *l, int lineno) +{ + struct magic *m; + char *t, *s; + magic_server_config_rec *conf = (magic_server_config_rec *) + ap_get_module_config(serv->module_config, &mime_magic_module); + + /* allocate magic structure entry */ + m = (struct magic *) ap_pcalloc(p, sizeof(struct magic)); + + /* append to linked list */ + m->next = NULL; + if (!conf->magic || !conf->last) { + conf->magic = conf->last = m; + } + else { + conf->last->next = m; + conf->last = m; + } + + /* set values in magic structure */ + m->flag = 0; + m->cont_level = 0; + m->lineno = lineno; + + while (*l == '>') { + ++l; /* step over */ + m->cont_level++; + } + + if (m->cont_level != 0 && *l == '(') { + ++l; /* step over */ + m->flag |= INDIR; + } + + /* get offset, then skip over it */ + m->offset = (int) ap_strtol(l, &t, 0); + if (l == t) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, serv, + MODNAME ": offset %s invalid", l); + } + l = t; + + if (m->flag & INDIR) { + m->in.type = LONG; + m->in.offset = 0; + /* + * read [.lbs][+-]nnnnn) + */ + if (*l == '.') { + switch (*++l) { + case 'l': + m->in.type = LONG; + break; + case 's': + m->in.type = SHORT; + break; + case 'b': + m->in.type = BYTE; + break; + default: + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, serv, + MODNAME ": indirect offset type %c invalid", *l); + break; + } + l++; + } + s = l; + if (*l == '+' || *l == '-') + l++; + if (ap_isdigit((unsigned char) *l)) { + m->in.offset = ap_strtol(l, &t, 0); + if (*s == '-') + m->in.offset = -m->in.offset; + } + else + t = l; + if (*t++ != ')') { + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, serv, + MODNAME ": missing ')' in indirect offset"); + } + l = t; + } + + + while (ap_isdigit((unsigned char) *l)) + ++l; + EATAB; + +#define NBYTE 4 +#define NSHORT 5 +#define NLONG 4 +#define NSTRING 6 +#define NDATE 4 +#define NBESHORT 7 +#define NBELONG 6 +#define NBEDATE 6 +#define NLESHORT 7 +#define NLELONG 6 +#define NLEDATE 6 + + if (*l == 'u') { + ++l; + m->flag |= UNSIGNED; + } + + /* get type, skip it */ + if (strncmp(l, "byte", NBYTE) == 0) { + m->type = BYTE; + l += NBYTE; + } + else if (strncmp(l, "short", NSHORT) == 0) { + m->type = SHORT; + l += NSHORT; + } + else if (strncmp(l, "long", NLONG) == 0) { + m->type = LONG; + l += NLONG; + } + else if (strncmp(l, "string", NSTRING) == 0) { + m->type = STRING; + l += NSTRING; + } + else if (strncmp(l, "date", NDATE) == 0) { + m->type = DATE; + l += NDATE; + } + else if (strncmp(l, "beshort", NBESHORT) == 0) { + m->type = BESHORT; + l += NBESHORT; + } + else if (strncmp(l, "belong", NBELONG) == 0) { + m->type = BELONG; + l += NBELONG; + } + else if (strncmp(l, "bedate", NBEDATE) == 0) { + m->type = BEDATE; + l += NBEDATE; + } + else if (strncmp(l, "leshort", NLESHORT) == 0) { + m->type = LESHORT; + l += NLESHORT; + } + else if (strncmp(l, "lelong", NLELONG) == 0) { + m->type = LELONG; + l += NLELONG; + } + else if (strncmp(l, "ledate", NLEDATE) == 0) { + m->type = LEDATE; + l += NLEDATE; + } + else { + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, serv, + MODNAME ": type %s invalid", l); + return -1; + } + /* New-style anding: "0 byte&0x80 =0x80 dynamically linked" */ + if (*l == '&') { + ++l; + m->mask = signextend(serv, m, ap_strtol(l, &l, 0)); + } + else + m->mask = ~0L; + EATAB; + + switch (*l) { + case '>': + case '<': + /* Old-style anding: "0 byte &0x80 dynamically linked" */ + case '&': + case '^': + case '=': + m->reln = *l; + ++l; + break; + case '!': + if (m->type != STRING) { + m->reln = *l; + ++l; + break; + } + /* FALL THROUGH */ + default: + if (*l == 'x' && ap_isspace((unsigned char) l[1])) { + m->reln = *l; + ++l; + goto GetDesc; /* Bill The Cat */ + } + m->reln = '='; + break; + } + EATAB; + + if (getvalue(serv, m, &l)) + return -1; + /* + * now get last part - the description + */ + GetDesc: + EATAB; + if (l[0] == '\b') { + ++l; + m->nospflag = 1; + } + else if ((l[0] == '\\') && (l[1] == 'b')) { + ++l; + ++l; + m->nospflag = 1; + } + else + m->nospflag = 0; + strncpy(m->desc, l, sizeof(m->desc) - 1); + m->desc[sizeof(m->desc) - 1] = '\0'; + +#if MIME_MAGIC_DEBUG + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, serv, + MODNAME ": parse line=%d m=%x next=%x cont=%d desc=%s", + lineno, m, m->next, m->cont_level, m->desc); +#endif /* MIME_MAGIC_DEBUG */ + + return 0; +} + +/* + * Read a numeric value from a pointer, into the value union of a magic + * pointer, according to the magic type. Update the string pointer to point + * just after the number read. Return 0 for success, non-zero for failure. + */ +static int getvalue(server_rec *s, struct magic *m, char **p) +{ + int slen; + + if (m->type == STRING) { + *p = getstr(s, *p, m->value.s, sizeof(m->value.s), &slen); + m->vallen = slen; + } + else if (m->reln != 'x') + m->value.l = signextend(s, m, ap_strtol(*p, p, 0)); + return 0; +} + +/* + * Convert a string containing C character escapes. Stop at an unescaped + * space or tab. Copy the converted version to "p", returning its length in + * *slen. Return updated scan pointer as function result. + */ +static char *getstr(server_rec *serv, register char *s, register char *p, + int plen, int *slen) +{ + char *origs = s, *origp = p; + char *pmax = p + plen - 1; + register int c; + register int val; + + while ((c = *s++) != '\0') { + if (ap_isspace((unsigned char) c)) + break; + if (p >= pmax) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, serv, + MODNAME ": string too long: %s", origs); + break; + } + if (c == '\\') { + switch (c = *s++) { + + case '\0': + goto out; + + default: + *p++ = (char) c; + break; + + case 'n': + *p++ = '\n'; + break; + + case 'r': + *p++ = '\r'; + break; + + case 'b': + *p++ = '\b'; + break; + + case 't': + *p++ = '\t'; + break; + + case 'f': + *p++ = '\f'; + break; + + case 'v': + *p++ = '\v'; + break; + + /* \ and up to 3 octal digits */ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + val = c - '0'; + c = *s++; /* try for 2 */ + if (c >= '0' && c <= '7') { + val = (val << 3) | (c - '0'); + c = *s++; /* try for 3 */ + if (c >= '0' && c <= '7') + val = (val << 3) | (c - '0'); + else + --s; + } + else + --s; + *p++ = (char) val; + break; + + /* \x and up to 3 hex digits */ + case 'x': + val = 'x'; /* Default if no digits */ + c = hextoint(*s++); /* Get next char */ + if (c >= 0) { + val = c; + c = hextoint(*s++); + if (c >= 0) { + val = (val << 4) + c; + c = hextoint(*s++); + if (c >= 0) { + val = (val << 4) + c; + } + else + --s; + } + else + --s; + } + else + --s; + *p++ = (char) val; + break; + } + } + else + *p++ = (char) c; + } + out: + *p = '\0'; + *slen = p - origp; + return s; +} + + +/* Single hex char to int; -1 if not a hex char. */ +static int hextoint(int c) +{ + if (ap_isdigit((unsigned char) c)) + return c - '0'; + if ((c >= 'a') && (c <= 'f')) + return c + 10 - 'a'; + if ((c >= 'A') && (c <= 'F')) + return c + 10 - 'A'; + return -1; +} + + +/* + * return DONE to indicate it's been handled + * return OK to indicate it's a regular file still needing handling + * other returns indicate a failure of some sort + */ +static int fsmagic(request_rec *r, const char *fn) +{ + switch (r->finfo.st_mode & S_IFMT) { + case S_IFDIR: + magic_rsl_puts(r, DIR_MAGIC_TYPE); + return DONE; + case S_IFCHR: + /* + * (void) magic_rsl_printf(r,"character special (%d/%d)", + * major(sb->st_rdev), minor(sb->st_rdev)); + */ + (void) magic_rsl_puts(r, MIME_BINARY_UNKNOWN); + return DONE; +#ifdef S_IFBLK + case S_IFBLK: + /* + * (void) magic_rsl_printf(r,"block special (%d/%d)", + * major(sb->st_rdev), minor(sb->st_rdev)); + */ + (void) magic_rsl_puts(r, MIME_BINARY_UNKNOWN); + return DONE; + /* TODO add code to handle V7 MUX and Blit MUX files */ +#endif +#ifdef S_IFIFO + case S_IFIFO: + /* + * magic_rsl_puts(r,"fifo (named pipe)"); + */ + (void) magic_rsl_puts(r, MIME_BINARY_UNKNOWN); + return DONE; +#endif +#ifdef S_IFLNK + case S_IFLNK: + /* We used stat(), the only possible reason for this is that the + * symlink is broken. + */ + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": broken symlink (%s)", fn); + return HTTP_INTERNAL_SERVER_ERROR; +#endif +#ifdef S_IFSOCK +#ifndef __COHERENT__ + case S_IFSOCK: + magic_rsl_puts(r, MIME_BINARY_UNKNOWN); + return DONE; +#endif +#endif + case S_IFREG: + break; + default: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": invalid mode 0%o.", (unsigned int)r->finfo.st_mode); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* + * regular file, check next possibility + */ + if (r->finfo.st_size == 0) { + magic_rsl_puts(r, MIME_TEXT_UNKNOWN); + return DONE; + } + return OK; +} + +/* + * softmagic - lookup one file in database (already read from /etc/magic by + * apprentice.c). Passed the name and FILE * of one file to be typed. + */ + /* ARGSUSED1 *//* nbytes passed for regularity, maybe need later */ +static int softmagic(request_rec *r, unsigned char *buf, int nbytes) +{ + if (match(r, buf, nbytes)) + return 1; + + return 0; +} + +/* + * Go through the whole list, stopping if you find a match. Process all the + * continuations of that match before returning. + * + * We support multi-level continuations: + * + * At any time when processing a successful top-level match, there is a current + * continuation level; it represents the level of the last successfully + * matched continuation. + * + * Continuations above that level are skipped as, if we see one, it means that + * the continuation that controls them - i.e, the lower-level continuation + * preceding them - failed to match. + * + * Continuations below that level are processed as, if we see one, it means + * we've finished processing or skipping higher-level continuations under the + * control of a successful or unsuccessful lower-level continuation, and are + * now seeing the next lower-level continuation and should process it. The + * current continuation level reverts to the level of the one we're seeing. + * + * Continuations at the current level are processed as, if we see one, there's + * no lower-level continuation that may have failed. + * + * If a continuation matches, we bump the current continuation level so that + * higher-level continuations are processed. + */ +static int match(request_rec *r, unsigned char *s, int nbytes) +{ +#if MIME_MAGIC_DEBUG + int rule_counter = 0; +#endif + int cont_level = 0; + int need_separator = 0; + union VALUETYPE p; + magic_server_config_rec *conf = (magic_server_config_rec *) + ap_get_module_config(r->server->module_config, &mime_magic_module); + struct magic *m; + +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": match conf=%x file=%s m=%s m->next=%s last=%s", + conf, + conf->magicfile ? conf->magicfile : "NULL", + conf->magic ? "set" : "NULL", + (conf->magic && conf->magic->next) ? "set" : "NULL", + conf->last ? "set" : "NULL"); +#endif + +#if MIME_MAGIC_DEBUG + for (m = conf->magic; m; m = m->next) { + if (ap_isprint((((unsigned long) m) >> 24) & 255) && + ap_isprint((((unsigned long) m) >> 16) & 255) && + ap_isprint((((unsigned long) m) >> 8) & 255) && + ap_isprint(((unsigned long) m) & 255)) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": match: POINTER CLOBBERED! " + "m=\"%c%c%c%c\"", + (((unsigned long) m) >> 24) & 255, + (((unsigned long) m) >> 16) & 255, + (((unsigned long) m) >> 8) & 255, + ((unsigned long) m) & 255); + break; + } + } +#endif + + for (m = conf->magic; m; m = m->next) { +#if MIME_MAGIC_DEBUG + rule_counter++; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": line=%d desc=%s", m->lineno, m->desc); +#endif + + /* check if main entry matches */ + if (!mget(r, &p, s, m, nbytes) || + !mcheck(r, &p, m)) { + struct magic *m_cont; + + /* + * main entry didn't match, flush its continuations + */ + if (!m->next || (m->next->cont_level == 0)) { + continue; + } + + m_cont = m->next; + while (m_cont && (m_cont->cont_level != 0)) { +#if MIME_MAGIC_DEBUG + rule_counter++; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": line=%d mc=%x mc->next=%x cont=%d desc=%s", + m_cont->lineno, m_cont, + m_cont->next, m_cont->cont_level, + m_cont->desc); +#endif + /* + * this trick allows us to keep *m in sync when the continue + * advances the pointer + */ + m = m_cont; + m_cont = m_cont->next; + } + continue; + } + + /* if we get here, the main entry rule was a match */ + /* this will be the last run through the loop */ +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": rule matched, line=%d type=%d %s", + m->lineno, m->type, + (m->type == STRING) ? m->value.s : ""); +#endif + + /* print the match */ + mprint(r, &p, m); + + /* + * If we printed something, we'll need to print a blank before we + * print something else. + */ + if (m->desc[0]) + need_separator = 1; + /* and any continuations that match */ + cont_level++; + /* + * while (m && m->next && m->next->cont_level != 0 && ( m = m->next + * )) + */ + m = m->next; + while (m && (m->cont_level != 0)) { +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": match line=%d cont=%d type=%d %s", + m->lineno, m->cont_level, m->type, + (m->type == STRING) ? m->value.s : ""); +#endif + if (cont_level >= m->cont_level) { + if (cont_level > m->cont_level) { + /* + * We're at the end of the level "cont_level" + * continuations. + */ + cont_level = m->cont_level; + } + if (mget(r, &p, s, m, nbytes) && + mcheck(r, &p, m)) { + /* + * This continuation matched. Print its message, with a + * blank before it if the previous item printed and this + * item isn't empty. + */ + /* space if previous printed */ + if (need_separator + && (m->nospflag == 0) + && (m->desc[0] != '\0') + ) { + (void) magic_rsl_putchar(r, ' '); + need_separator = 0; + } + mprint(r, &p, m); + if (m->desc[0]) + need_separator = 1; + + /* + * If we see any continuations at a higher level, process + * them. + */ + cont_level++; + } + } + + /* move to next continuation record */ + m = m->next; + } +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": matched after %d rules", rule_counter); +#endif + return 1; /* all through */ + } +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": failed after %d rules", rule_counter); +#endif + return 0; /* no match at all */ +} + +static void mprint(request_rec *r, union VALUETYPE *p, struct magic *m) +{ + char *pp, *rt; + unsigned long v; + + switch (m->type) { + case BYTE: + v = p->b; + break; + + case SHORT: + case BESHORT: + case LESHORT: + v = p->h; + break; + + case LONG: + case BELONG: + case LELONG: + v = p->l; + break; + + case STRING: + if (m->reln == '=') { + (void) magic_rsl_printf(r, m->desc, m->value.s); + } + else { + (void) magic_rsl_printf(r, m->desc, p->s); + } + return; + + case DATE: + case BEDATE: + case LEDATE: + /* XXX: not multithread safe */ + pp = ctime((time_t *) & p->l); + if ((rt = strchr(pp, '\n')) != NULL) + *rt = '\0'; + (void) magic_rsl_printf(r, m->desc, pp); + return; + default: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": invalid m->type (%d) in mprint().", + m->type); + return; + } + + v = signextend(r->server, m, v) & m->mask; + (void) magic_rsl_printf(r, m->desc, (unsigned long) v); +} + +/* + * Convert the byte order of the data we are looking at + */ +static int mconvert(request_rec *r, union VALUETYPE *p, struct magic *m) +{ + char *rt; + + switch (m->type) { + case BYTE: + case SHORT: + case LONG: + case DATE: + return 1; + case STRING: + /* Null terminate and eat the return */ + p->s[sizeof(p->s) - 1] = '\0'; + if ((rt = strchr(p->s, '\n')) != NULL) + *rt = '\0'; + return 1; + case BESHORT: + p->h = (short) ((p->hs[0] << 8) | (p->hs[1])); + return 1; + case BELONG: + case BEDATE: + p->l = (long) + ((p->hl[0] << 24) | (p->hl[1] << 16) | (p->hl[2] << 8) | (p->hl[3])); + return 1; + case LESHORT: + p->h = (short) ((p->hs[1] << 8) | (p->hs[0])); + return 1; + case LELONG: + case LEDATE: + p->l = (long) + ((p->hl[3] << 24) | (p->hl[2] << 16) | (p->hl[1] << 8) | (p->hl[0])); + return 1; + default: + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": invalid type %d in mconvert().", m->type); + return 0; + } +} + + +static int mget(request_rec *r, union VALUETYPE *p, unsigned char *s, + struct magic *m, int nbytes) +{ + long offset = m->offset; + + if (offset + (long)sizeof(union VALUETYPE) > nbytes) + return 0; + + memcpy(p, s + offset, sizeof(union VALUETYPE)); + + if (!mconvert(r, p, m)) + return 0; + + if (m->flag & INDIR) { + + switch (m->in.type) { + case BYTE: + offset = p->b + m->in.offset; + break; + case SHORT: + offset = p->h + m->in.offset; + break; + case LONG: + offset = p->l + m->in.offset; + break; + } + + if (offset + (long)sizeof(union VALUETYPE) > nbytes) + return 0; + + memcpy(p, s + offset, sizeof(union VALUETYPE)); + + if (!mconvert(r, p, m)) + return 0; + } + return 1; +} + +static int mcheck(request_rec *r, union VALUETYPE *p, struct magic *m) +{ + register unsigned long l = m->value.l; + register unsigned long v; + int matched; + + if ((m->value.s[0] == 'x') && (m->value.s[1] == '\0')) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": BOINK"); + return 1; + } + + switch (m->type) { + case BYTE: + v = p->b; + break; + + case SHORT: + case BESHORT: + case LESHORT: + v = p->h; + break; + + case LONG: + case BELONG: + case LELONG: + case DATE: + case BEDATE: + case LEDATE: + v = p->l; + break; + + case STRING: + l = 0; + /* + * What we want here is: v = strncmp(m->value.s, p->s, m->vallen); + * but ignoring any nulls. bcmp doesn't give -/+/0 and isn't + * universally available anyway. + */ + v = 0; + { + register unsigned char *a = (unsigned char *) m->value.s; + register unsigned char *b = (unsigned char *) p->s; + register int len = m->vallen; + + while (--len >= 0) + if ((v = *b++ - *a++) != 0) + break; + } + break; + default: + /* bogosity, pretend that it just wasn't a match */ + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": invalid type %d in mcheck().", m->type); + return 0; + } + + v = signextend(r->server, m, v) & m->mask; + + switch (m->reln) { + case 'x': +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + "%lu == *any* = 1", v); +#endif + matched = 1; + break; + + case '!': + matched = v != l; +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + "%lu != %lu = %d", v, l, matched); +#endif + break; + + case '=': + matched = v == l; +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + "%lu == %lu = %d", v, l, matched); +#endif + break; + + case '>': + if (m->flag & UNSIGNED) { + matched = v > l; +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + "%lu > %lu = %d", v, l, matched); +#endif + } + else { + matched = (long) v > (long) l; +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + "%ld > %ld = %d", v, l, matched); +#endif + } + break; + + case '<': + if (m->flag & UNSIGNED) { + matched = v < l; +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + "%lu < %lu = %d", v, l, matched); +#endif + } + else { + matched = (long) v < (long) l; +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + "%ld < %ld = %d", v, l, matched); +#endif + } + break; + + case '&': + matched = (v & l) == l; +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + "((%lx & %lx) == %lx) = %d", v, l, l, matched); +#endif + break; + + case '^': + matched = (v & l) != l; +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + "((%lx & %lx) != %lx) = %d", v, l, l, matched); +#endif + break; + + default: + /* bogosity, pretend it didn't match */ + matched = 0; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, + MODNAME ": mcheck: can't happen: invalid relation %d.", + m->reln); + break; + } + + return matched; +} + +/* an optimization over plain strcmp() */ +#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0) + +static int ascmagic(request_rec *r, unsigned char *buf, int nbytes) +{ + int has_escapes = 0; + unsigned char *s; + char nbuf[HOWMANY + 1]; /* one extra for terminating '\0' */ + char *token; + register struct names *p; + int small_nbytes; + + /* these are easy, do them first */ + + /* + * for troff, look for . + letter + letter or .\"; this must be done to + * disambiguate tar archives' ./file and other trash from real troff + * input. + */ + if (*buf == '.') { + unsigned char *tp = buf + 1; + + while (ap_isspace(*tp)) + ++tp; /* skip leading whitespace */ + if ((ap_isalnum(*tp) || *tp == '\\') && + (ap_isalnum(*(tp + 1)) || *tp == '"')) { + magic_rsl_puts(r, "application/x-troff"); + return 1; + } + } + if ((*buf == 'c' || *buf == 'C') && ap_isspace(*(buf + 1))) { + /* Fortran */ + magic_rsl_puts(r, "text/plain"); + return 1; + } + + /* look for tokens from names.h - this is expensive!, so we'll limit + * ourselves to only SMALL_HOWMANY bytes */ + small_nbytes = (nbytes > SMALL_HOWMANY) ? SMALL_HOWMANY : nbytes; + /* make a copy of the buffer here because strtok() will destroy it */ + s = (unsigned char *) memcpy(nbuf, buf, small_nbytes); + s[small_nbytes] = '\0'; + has_escapes = (memchr(s, '\033', small_nbytes) != NULL); + /* XXX: not multithread safe */ + while ((token = strtok((char *) s, " \t\n\r\f")) != NULL) { + s = NULL; /* make strtok() keep on tokin' */ + for (p = names; p < names + NNAMES; p++) { + if (STREQ(p->name, token)) { + magic_rsl_puts(r, types[p->type]); + if (has_escapes) + magic_rsl_puts(r, " (with escape sequences)"); + return 1; + } + } + } + + switch (is_tar(buf, nbytes)) { + case 1: + /* V7 tar archive */ + magic_rsl_puts(r, "application/x-tar"); + return 1; + case 2: + /* POSIX tar archive */ + magic_rsl_puts(r, "application/x-tar"); + return 1; + } + + /* all else fails, but it is ascii... */ + if (has_escapes) { + /* text with escape sequences */ + /* we leave this open for further differentiation later */ + magic_rsl_puts(r, "text/plain"); + } + else { + /* plain text */ + magic_rsl_puts(r, "text/plain"); + } + return 1; +} + + +/* + * compress routines: zmagic() - returns 0 if not recognized, uncompresses + * and prints information if recognized uncompress(s, method, old, n, newch) + * - uncompress old into new, using method, return sizeof new + */ + +static struct { + char *magic; + int maglen; + char *argv[3]; + int silent; + char *encoding; /* MUST be lowercase */ +} compr[] = { + + /* we use gzip here rather than uncompress because we have to pass + * it a full filename -- and uncompress only considers filenames + * ending with .Z + */ + { + "\037\235", 2, { + "gzip", "-dcq", NULL + }, 0, "x-compress" + }, + { + "\037\213", 2, { + "gzip", "-dcq", NULL + }, 1, "x-gzip" + }, + /* + * XXX pcat does not work, cause I don't know how to make it read stdin, + * so we use gzip + */ + { + "\037\036", 2, { + "gzip", "-dcq", NULL + }, 0, "x-gzip" + }, +}; + +static int ncompr = sizeof(compr) / sizeof(compr[0]); + +static int zmagic(request_rec *r, unsigned char *buf, int nbytes) +{ + unsigned char *newbuf; + int newsize; + int i; + + for (i = 0; i < ncompr; i++) { + if (nbytes < compr[i].maglen) + continue; + if (memcmp(buf, compr[i].magic, compr[i].maglen) == 0) + break; + } + + if (i == ncompr) + return 0; + + if ((newsize = uncompress(r, i, &newbuf, nbytes)) > 0) { + tryit(r, newbuf, newsize, 0); + + /* set encoding type in the request record */ + r->content_encoding = compr[i].encoding; + } + return 1; +} + + +struct uncompress_parms { + request_rec *r; + int method; +}; + +static int uncompress_child(void *data, child_info *pinfo) +{ + struct uncompress_parms *parm = data; +#ifndef WIN32 + char *new_argv[4]; + + new_argv[0] = compr[parm->method].argv[0]; + new_argv[1] = compr[parm->method].argv[1]; + new_argv[2] = parm->r->filename; + new_argv[3] = NULL; + + if (compr[parm->method].silent) { + close(STDERR_FILENO); + } + + execvp(compr[parm->method].argv[0], new_argv); + ap_log_rerror(APLOG_MARK, APLOG_ERR, parm->r, + MODNAME ": could not execute `%s'.", + compr[parm->method].argv[0]); + return -1; +#else + char *pCommand; + STARTUPINFO si; + PROCESS_INFORMATION pi; + pid_t pid; + + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + + pid = -1; + + /* + * Look at the arguments... + */ + pCommand = ap_pstrcat(parm->r->pool, compr[parm->method].argv[0], " ", + compr[parm->method].argv[1], " \"", + parm->r->filename, "\"", NULL); + + /* + * Make child process use hPipeOutputWrite as standard out, + * and make sure it does not show on screen. + */ + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdInput = pinfo->hPipeInputRead; + si.hStdOutput = pinfo->hPipeOutputWrite; + si.hStdError = pinfo->hPipeErrorWrite; + + if (CreateProcess(NULL, pCommand, NULL, NULL, TRUE, 0, NULL, + ap_make_dirstr_parent(parm->r->pool, parm->r->filename), + &si, &pi)) { + pid = pi.dwProcessId; + /* + * We must close the handles to the new process and its main thread + * to prevent handle and memory leaks. + */ + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return (pid); +#endif +} + + +static int uncompress(request_rec *r, int method, + unsigned char **newch, int n) +{ + struct uncompress_parms parm; + BUFF *bout; + pool *sub_pool; + + parm.r = r; + parm.method = method; + + /* We make a sub_pool so that we can collect our child early, otherwise + * there are cases (i.e. generating directory indicies with mod_autoindex) + * where we would end up with LOTS of zombies. + */ + sub_pool = ap_make_sub_pool(r->pool); + + if (!ap_bspawn_child(sub_pool, uncompress_child, &parm, kill_always, + NULL, &bout, NULL)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + MODNAME ": couldn't spawn uncompress process: %s", r->uri); + return -1; + } + + *newch = (unsigned char *) ap_palloc(r->pool, n); + if ((n = ap_bread(bout, *newch, n)) <= 0) { + ap_destroy_pool(sub_pool); + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + MODNAME ": read failed %s", r->filename); + return -1; + } + ap_destroy_pool(sub_pool); + return n; +} + +/* + * is_tar() -- figure out whether file is a tar archive. + * + * Stolen (by author of file utility) from the public domain tar program: Public + * Domain version written 26 Aug 1985 John Gilmore (ihnp4!hoptoad!gnu). + * + * @(#)list.c 1.18 9/23/86 Public Domain - gnu $Id: mod_mime_magic.c,v 1.7 + * 1997/06/24 00:41:02 ikluft Exp ikluft $ + * + * Comments changed and some code/comments reformatted for file command by Ian + * Darwin. + */ + +#define isodigit(c) ( ((c) >= '0') && ((c) <= '7') ) + +/* + * Return 0 if the checksum is bad (i.e., probably not a tar archive), 1 for + * old UNIX tar file, 2 for Unix Std (POSIX) tar file. + */ + +static int is_tar(unsigned char *buf, int nbytes) +{ + register union record *header = (union record *) buf; + register int i; + register long sum, recsum; + register char *p; + + if (nbytes < sizeof(union record)) + return 0; + + recsum = from_oct(8, header->header.chksum); + + sum = 0; + p = header->charptr; + for (i = sizeof(union record); --i >= 0;) { + /* + * We can't use unsigned char here because of old compilers, e.g. V7. + */ + sum += 0xFF & *p++; + } + + /* Adjust checksum to count the "chksum" field as blanks. */ + for (i = sizeof(header->header.chksum); --i >= 0;) + sum -= 0xFF & header->header.chksum[i]; + sum += ' ' * sizeof header->header.chksum; + + if (sum != recsum) + return 0; /* Not a tar archive */ + + if (0 == strcmp(header->header.magic, TMAGIC)) + return 2; /* Unix Standard tar archive */ + + return 1; /* Old fashioned tar archive */ +} + + +/* + * Quick and dirty octal conversion. + * + * Result is -1 if the field is invalid (all blank, or nonoctal). + */ +static long from_oct(int digs, char *where) +{ + register long value; + + while (ap_isspace(*where)) { /* Skip spaces */ + where++; + if (--digs <= 0) + return -1; /* All blank field */ + } + value = 0; + while (digs > 0 && isodigit(*where)) { /* Scan til nonoctal */ + value = (value << 3) | (*where++ - '0'); + --digs; + } + + if (digs > 0 && *where && !ap_isspace(*where)) + return -1; /* Ended on non-space/nul */ + + return value; +} + +/* + * Check for file-revision suffix + * + * This is for an obscure document control system used on an intranet. + * The web representation of each file's revision has an @1, @2, etc + * appended with the revision number. This needs to be stripped off to + * find the file suffix, which can be recognized by sending the name back + * through a sub-request. The base file name (without the @num suffix) + * must exist because its type will be used as the result. + */ +static int revision_suffix(request_rec *r) +{ + int suffix_pos, result; + char *sub_filename; + request_rec *sub; + +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": revision_suffix checking %s", r->filename); +#endif /* MIME_MAGIC_DEBUG */ + + /* check for recognized revision suffix */ + suffix_pos = strlen(r->filename) - 1; + if (!ap_isdigit(r->filename[suffix_pos])) { + return 0; + } + while (suffix_pos >= 0 && ap_isdigit(r->filename[suffix_pos])) + suffix_pos--; + if (suffix_pos < 0 || r->filename[suffix_pos] != '@') { + return 0; + } + + /* perform sub-request for the file name without the suffix */ + result = 0; + sub_filename = ap_pstrndup(r->pool, r->filename, suffix_pos); +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": subrequest lookup for %s", sub_filename); +#endif /* MIME_MAGIC_DEBUG */ + sub = ap_sub_req_lookup_file(sub_filename, r); + + /* extract content type/encoding/language from sub-request */ + if (sub->content_type) { + r->content_type = ap_pstrdup(r->pool, sub->content_type); +#if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r, + MODNAME ": subrequest %s got %s", + sub_filename, r->content_type); +#endif /* MIME_MAGIC_DEBUG */ + if (sub->content_encoding) + r->content_encoding = + ap_pstrdup(r->pool, sub->content_encoding); + if (sub->content_language) + r->content_language = + ap_pstrdup(r->pool, sub->content_language); + result = 1; + } + + /* clean up */ + ap_destroy_sub_req(sub); + + return result; +} + +/* + * initialize the module + */ + +static void magic_init(server_rec *main_server, pool *p) +{ + int result; + magic_server_config_rec *conf; + magic_server_config_rec *main_conf; + server_rec *s; +#if MIME_MAGIC_DEBUG + struct magic *m, *prevm; +#endif /* MIME_MAGIC_DEBUG */ + + main_conf = ap_get_module_config(main_server->module_config, &mime_magic_module); + for (s = main_server; s; s = s->next) { + conf = ap_get_module_config(s->module_config, &mime_magic_module); + if (conf->magicfile == NULL && s != main_server) { + /* inherits from the parent */ + *conf = *main_conf; + } + else if (conf->magicfile) { + result = apprentice(s, p); + if (result == -1) + return; +#if MIME_MAGIC_DEBUG + prevm = 0; + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, s, + MODNAME ": magic_init 1 test"); + for (m = conf->magic; m; m = m->next) { + if (ap_isprint((((unsigned long) m) >> 24) & 255) && + ap_isprint((((unsigned long) m) >> 16) & 255) && + ap_isprint((((unsigned long) m) >> 8) & 255) && + ap_isprint(((unsigned long) m) & 255)) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, s, + MODNAME ": magic_init 1: POINTER CLOBBERED! " + "m=\"%c%c%c%c\" line=%d", + (((unsigned long) m) >> 24) & 255, + (((unsigned long) m) >> 16) & 255, + (((unsigned long) m) >> 8) & 255, + ((unsigned long) m) & 255, + prevm ? prevm->lineno : -1); + break; + } + prevm = m; + } +#endif + } + } +} + +/* + * Find the Content-Type from any resource this module has available + */ + +static int magic_find_ct(request_rec *r) +{ + int result; + magic_server_config_rec *conf; + + /* the file has to exist */ + if (r->finfo.st_mode == 0 || !r->filename) { + return DECLINED; + } + + /* was someone else already here? */ + if (r->content_type) { + return DECLINED; + } + + conf = ap_get_module_config(r->server->module_config, &mime_magic_module); + if (!conf || !conf->magic) { + return DECLINED; + } + + /* initialize per-request info */ + if (!magic_set_config(r)) { + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* try excluding file-revision suffixes */ + if (revision_suffix(r) != 1) { + /* process it based on the file contents */ + if ((result = magic_process(r)) != OK) { + return result; + } + } + + /* if we have any results, put them in the request structure */ + return magic_rsl_to_request(r); +} + +/* + * Apache API module interface + */ + +module MODULE_VAR_EXPORT mime_magic_module = +{ + STANDARD_MODULE_STUFF, + magic_init, /* initializer */ + NULL, /* dir config creator */ + NULL, /* dir merger --- default is to override */ + create_magic_server_config, /* server config */ + merge_magic_server_config, /* merge server config */ + mime_magic_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + magic_find_ct, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_negotiation.c b/APACHE_1_3_42/src/modules/standard/mod_negotiation.c new file mode 100644 index 0000000000..2dc401c87c --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_negotiation.c @@ -0,0 +1,2801 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_negotiation.c: keeps track of MIME types the client is willing to + * accept, and contains code to handle type arbitration. + * + * rst + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_protocol.h" +#include "http_core.h" +#include "http_log.h" +#include "util_script.h" + +/* Commands --- configuring document caching on a per (virtual?) + * server basis... + */ + +typedef struct { + array_header *language_priority; +} neg_dir_config; + +module MODULE_VAR_EXPORT negotiation_module; + +static void *create_neg_dir_config(pool *p, char *dummy) +{ + neg_dir_config *new = (neg_dir_config *) ap_palloc(p, sizeof(neg_dir_config)); + + new->language_priority = ap_make_array(p, 4, sizeof(char *)); + return new; +} + +static void *merge_neg_dir_configs(pool *p, void *basev, void *addv) +{ + neg_dir_config *base = (neg_dir_config *) basev; + neg_dir_config *add = (neg_dir_config *) addv; + neg_dir_config *new = (neg_dir_config *) ap_palloc(p, sizeof(neg_dir_config)); + + /* give priority to the config in the subdirectory */ + new->language_priority = ap_append_arrays(p, add->language_priority, + base->language_priority); + return new; +} + +static const char *set_language_priority(cmd_parms *cmd, void *n, char *lang) +{ + array_header *arr = ((neg_dir_config *) n)->language_priority; + char **langp = (char **) ap_push_array(arr); + + *langp = lang; + return NULL; +} + +static const char *cache_negotiated_docs(cmd_parms *cmd, void *dummy, + char *dummy2) +{ + void *server_conf = cmd->server->module_config; + + ap_set_module_config(server_conf, &negotiation_module, "Cache"); + return NULL; +} + +static int do_cache_negotiated_docs(server_rec *s) +{ + return (ap_get_module_config(s->module_config, &negotiation_module) != NULL); +} + +static const command_rec negotiation_cmds[] = +{ + {"CacheNegotiatedDocs", cache_negotiated_docs, NULL, RSRC_CONF, NO_ARGS, + "no arguments (either present or absent)"}, + {"LanguagePriority", set_language_priority, NULL, OR_FILEINFO, ITERATE, + "space-delimited list of MIME language abbreviations"}, + {NULL} +}; + +/* + * Record of available info on a media type specified by the client + * (we also use 'em for encodings and languages) + */ + +typedef struct accept_rec { + char *name; /* MUST be lowercase */ + float quality; + float level; + char *charset; /* for content-type only */ +} accept_rec; + +/* + * Record of available info on a particular variant + * + * Note that a few of these fields are updated by the actual negotiation + * code. These are: + * + * level_matched --- initialized to zero. Set to the value of level + * if the client actually accepts this media type at that + * level (and *not* if it got in on a wildcard). See level_cmp + * below. + * mime_stars -- initialized to zero. Set to the number of stars + * present in the best matching Accept header element. + * 1 for star/star, 2 for type/star and 3 for + * type/subtype. + * + * definite -- initialized to 1. Set to 0 if there is a match which + * makes the variant non-definite according to the rules + * in rfc2296. + */ + +typedef struct var_rec { + request_rec *sub_req; /* May be NULL (is, for map files) */ + char *mime_type; /* MUST be lowercase */ + char *file_name; + const char *content_encoding; + array_header *content_languages; /* list of languages for this variant */ + char *content_charset; + char *description; + + /* The next five items give the quality values for the dimensions + * of negotiation for this variant. They are obtained from the + * appropriate header lines, except for source_quality, which + * is obtained from the variant itself (the 'qs' parameter value + * from the variant's mime-type). Apart from source_quality, + * these values are set when we find the quality for each variant + * (see best_match()). source_quality is set from the 'qs' parameter + * of the variant description or mime type: see set_mime_fields(). + */ + float lang_quality; /* quality of this variant's language */ + float encoding_quality; /* ditto encoding */ + float charset_quality; /* ditto charset */ + float mime_type_quality; /* ditto media type */ + float source_quality; /* source quality for this variant */ + + /* Now some special values */ + float level; /* Auxiliary to content-type... */ + float bytes; /* content length, if known */ + int lang_index; /* pre HTTP/1.1 language priority stuff */ + int is_pseudo_html; /* text/html, *or* the INCLUDES_MAGIC_TYPEs */ + + /* Above are all written-once properties of the variant. The + * three fields below are changed during negotiation: + */ + + float level_matched; + int mime_stars; + int definite; +} var_rec; + +/* Something to carry around the state of negotiation (and to keep + * all of this thread-safe)... + */ + +typedef struct { + pool *pool; + request_rec *r; + char *dir_name; + int accept_q; /* 1 if an Accept item has a q= param */ + float default_lang_quality; /* fiddle lang q for variants with no lang */ + + /* the array pointers below are NULL if the corresponding accept + * headers are not present + */ + array_header *accepts; /* accept_recs */ + array_header *accept_encodings; /* accept_recs */ + array_header *accept_charsets; /* accept_recs */ + array_header *accept_langs; /* accept_recs */ + + array_header *avail_vars; /* available variants */ + + int count_multiviews_variants; /* number of variants found on disk */ + + int is_transparent; /* 1 if this resource is trans. negotiable */ + + int dont_fiddle_headers; /* 1 if we may not fiddle with accept hdrs */ + int ua_supports_trans; /* 1 if ua supports trans negotiation */ + int send_alternates; /* 1 if we want to send an Alternates header */ + int may_choose; /* 1 if we may choose a variant for the client */ + int use_rvsa; /* 1 if we must use RVSA/1.0 negotiation algo */ +} negotiation_state; + +/* A few functions to manipulate var_recs. + * Cleaning out the fields... + */ + +static void clean_var_rec(var_rec *mime_info) +{ + mime_info->sub_req = NULL; + mime_info->mime_type = ""; + mime_info->file_name = ""; + mime_info->content_encoding = NULL; + mime_info->content_languages = NULL; + mime_info->content_charset = ""; + mime_info->description = ""; + + mime_info->is_pseudo_html = 0; + mime_info->level = 0.0f; + mime_info->level_matched = 0.0f; + mime_info->bytes = 0.0f; + mime_info->lang_index = -1; + mime_info->mime_stars = 0; + mime_info->definite = 1; + + mime_info->charset_quality = 1.0f; + mime_info->encoding_quality = 1.0f; + mime_info->lang_quality = 1.0f; + mime_info->mime_type_quality = 1.0f; + mime_info->source_quality = 0.0f; +} + +/* Initializing the relevant fields of a variant record from the + * accept_info read out of its content-type, one way or another. + */ + +static void set_mime_fields(var_rec *var, accept_rec *mime_info) +{ + var->mime_type = mime_info->name; + var->source_quality = mime_info->quality; + var->level = mime_info->level; + var->content_charset = mime_info->charset; + + var->is_pseudo_html = (!strcmp(var->mime_type, "text/html") + || !strcmp(var->mime_type, INCLUDES_MAGIC_TYPE) + || !strcmp(var->mime_type, INCLUDES_MAGIC_TYPE3)); +} + +/* Create a variant list validator in r using info from vlistr. */ + +static void set_vlist_validator(request_rec *r, request_rec *vlistr) +{ + /* Calculating the variant list validator is similar to + * calculating an etag for the source of the variant list + * information, so we use ap_make_etag(). Note that this + * validator can be 'weak' in extreme case. + */ + + ap_update_mtime (vlistr, vlistr->finfo.st_mtime); + r->vlist_validator = ap_make_etag(vlistr, 0); + + /* ap_set_etag will later take r->vlist_validator into account + * when creating the etag header + */ +} + + +/***************************************************************** + * + * Parsing (lists of) media types and their parameters, as seen in + * HTTPD header lines and elsewhere. + */ + +/* + * Get a single mime type entry --- one media type and parameters; + * enter the values we recognize into the argument accept_rec + */ + +static const char *get_entry(pool *p, accept_rec *result, + const char *accept_line) +{ + result->quality = 1.0f; + result->level = 0.0f; + result->charset = ""; + + /* + * Note that this handles what I gather is the "old format", + * + * Accept: text/html text/plain moo/zot + * + * without any compatibility kludges --- if the token after the + * MIME type begins with a semicolon, we know we're looking at parms, + * otherwise, we know we aren't. (So why all the pissing and moaning + * in the CERN server code? I must be missing something). + */ + + result->name = ap_get_token(p, &accept_line, 0); + ap_str_tolower(result->name); /* You want case-insensitive, + * you'll *get* case-insensitive. + */ + + /* KLUDGE!!! Default HTML to level 2.0 unless the browser + * *explicitly* says something else. + */ + + if (!strcmp(result->name, "text/html") && (result->level == 0.0)) { + result->level = 2.0f; + } + else if (!strcmp(result->name, INCLUDES_MAGIC_TYPE)) { + result->level = 2.0f; + } + else if (!strcmp(result->name, INCLUDES_MAGIC_TYPE3)) { + result->level = 3.0f; + } + + while (*accept_line == ';') { + /* Parameters ... */ + + char *parm; + char *cp; + char *end; + + ++accept_line; + parm = ap_get_token(p, &accept_line, 1); + + /* Look for 'var = value' --- and make sure the var is in lcase. */ + + for (cp = parm; (*cp && !ap_isspace(*cp) && *cp != '='); ++cp) { + *cp = ap_tolower(*cp); + } + + if (!*cp) { + continue; /* No '='; just ignore it. */ + } + + *cp++ = '\0'; /* Delimit var */ + while (*cp && (ap_isspace(*cp) || *cp == '=')) { + ++cp; + } + + if (*cp == '"') { + ++cp; + for (end = cp; + (*end && *end != '\n' && *end != '\r' && *end != '\"'); + end++); + } + else { + for (end = cp; (*end && !ap_isspace(*end)); end++); + } + if (*end) { + *end = '\0'; /* strip ending quote or return */ + } + ap_str_tolower(cp); + + if (parm[0] == 'q' + && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0'))) { + result->quality = (float)atof(cp); + } + else if (parm[0] == 'l' && !strcmp(&parm[1], "evel")) { + result->level = (float)atof(cp); + } + else if (!strcmp(parm, "charset")) { + result->charset = cp; + } + } + + if (*accept_line == ',') { + ++accept_line; + } + + return accept_line; +} + +/***************************************************************** + * + * Dealing with header lines ... + * + * Accept, Accept-Charset, Accept-Language and Accept-Encoding + * are handled by do_header_line() - they all have the same + * basic structure of a list of items of the format + * name; q=N; charset=TEXT + * + * where charset is only valid in Accept. + */ + +static array_header *do_header_line(pool *p, const char *accept_line) +{ + array_header *accept_recs; + + if (!accept_line) { + return NULL; + } + + accept_recs = ap_make_array(p, 40, sizeof(accept_rec)); + + while (*accept_line) { + accept_rec *new = (accept_rec *) ap_push_array(accept_recs); + accept_line = get_entry(p, new, accept_line); + } + + return accept_recs; +} + +/* Given the text of the Content-Languages: line from the var map file, + * return an array containing the languages of this variant + */ + +static array_header *do_languages_line(pool *p, const char **lang_line) +{ + array_header *lang_recs = ap_make_array(p, 2, sizeof(char *)); + + if (!lang_line) { + return lang_recs; + } + + while (**lang_line) { + char **new = (char **) ap_push_array(lang_recs); + *new = ap_get_token(p, lang_line, 0); + ap_str_tolower(*new); + if (**lang_line == ',' || **lang_line == ';') { + ++(*lang_line); + } + } + + return lang_recs; +} + +/***************************************************************** + * + * Handling header lines from clients... + */ + +static negotiation_state *parse_accept_headers(request_rec *r) +{ + negotiation_state *new = + (negotiation_state *) ap_pcalloc(r->pool, sizeof(negotiation_state)); + accept_rec *elts; + table *hdrs = r->headers_in; + int i; + + new->pool = r->pool; + new->r = r; + new->dir_name = ap_make_dirstr_parent(r->pool, r->filename); + + new->accepts = do_header_line(r->pool, ap_table_get(hdrs, "Accept")); + + /* calculate new->accept_q value */ + if (new->accepts) { + elts = (accept_rec *) new->accepts->elts; + + for (i = 0; i < new->accepts->nelts; ++i) { + if (elts[i].quality < 1.0) { + new->accept_q = 1; + } + } + } + + new->accept_encodings = + do_header_line(r->pool, ap_table_get(hdrs, "Accept-Encoding")); + new->accept_langs = + do_header_line(r->pool, ap_table_get(hdrs, "Accept-Language")); + new->accept_charsets = + do_header_line(r->pool, ap_table_get(hdrs, "Accept-Charset")); + + new->avail_vars = ap_make_array(r->pool, 40, sizeof(var_rec)); + + return new; +} + + +static void parse_negotiate_header(request_rec *r, negotiation_state *neg) +{ + const char *negotiate = ap_table_get(r->headers_in, "Negotiate"); + char *tok; + + /* First, default to no TCN, no Alternates, and the original Apache + * negotiation algorithm with fiddles for broken browser configs. + * + * To save network bandwidth, we do not configure to send an + * Alternates header to the user agent by default. User + * agents that want an Alternates header for agent-driven + * negotiation will have to request it by sending an + * appropriate Negotiate header. + */ + neg->ua_supports_trans = 0; + neg->send_alternates = 0; + neg->may_choose = 1; + neg->use_rvsa = 0; + neg->dont_fiddle_headers = 0; + + if (!negotiate) + return; + + if (strcmp(negotiate, "trans") == 0) { + /* Lynx 2.7 and 2.8 send 'negotiate: trans' even though they + * do not support transparent content negotiation, so for Lynx we + * ignore the negotiate header when its contents are exactly "trans". + * If future versions of Lynx ever need to say 'negotiate: trans', + * they can send the equivalent 'negotiate: trans, trans' instead + * to avoid triggering the workaround below. + */ + const char *ua = ap_table_get(r->headers_in, "User-Agent"); + + if (ua && (strncmp(ua, "Lynx", 4) == 0)) + return; + } + + neg->may_choose = 0; /* An empty Negotiate would require 300 response */ + + while ((tok = ap_get_list_item(neg->pool, &negotiate)) != NULL) { + + if (strcmp(tok, "trans") == 0 || + strcmp(tok, "vlist") == 0 || + strcmp(tok, "guess-small") == 0 || + ap_isdigit(tok[0]) || + strcmp(tok, "*") == 0) { + + /* The user agent supports transparent negotiation */ + neg->ua_supports_trans = 1; + + /* Send-alternates could be configurable, but note + * that it must be 1 if we have 'vlist' in the + * negotiate header. + */ + neg->send_alternates = 1; + + if (strcmp(tok, "1.0") == 0) { + /* we may use the RVSA/1.0 algorithm, configure for it */ + neg->may_choose = 1; + neg->use_rvsa = 1; + neg->dont_fiddle_headers = 1; + } + else if (tok[0] == '*') { + /* we may use any variant selection algorithm, configure + * to use the Apache algorithm + */ + neg->may_choose = 1; + + /* We disable header fiddles on the assumption that a + * client sending Negotiate knows how to send correct + * headers which don't need fiddling. + */ + neg->dont_fiddle_headers = 1; + } + } + } + +#ifdef NEG_DEBUG + fprintf(stderr, "dont_fiddle_headers=%d use_rvsa=%d ua_supports_trans=%d " + "send_alternates=%d, may_choose=%d\n", + neg->dont_fiddle_headers, neg->use_rvsa, + neg->ua_supports_trans, neg->send_alternates, neg->may_choose); +#endif + +} + +/* Sometimes clients will give us no Accept info at all; this routine sets + * up the standard default for that case, and also arranges for us to be + * willing to run a CGI script if we find one. (In fact, we set up to + * dramatically prefer CGI scripts in cases where that's appropriate, + * e.g., POST or when URI includes query args or extra path info). + */ +static void maybe_add_default_accepts(negotiation_state *neg, + int prefer_scripts) +{ + accept_rec *new_accept; + + if (!neg->accepts) { + neg->accepts = ap_make_array(neg->pool, 4, sizeof(accept_rec)); + + new_accept = (accept_rec *) ap_push_array(neg->accepts); + + new_accept->name = "*/*"; + new_accept->quality = 1.0f; + new_accept->level = 0.0f; + } + + new_accept = (accept_rec *) ap_push_array(neg->accepts); + + new_accept->name = CGI_MAGIC_TYPE; + if (neg->use_rvsa) { + new_accept->quality = 0; + } + else { + new_accept->quality = prefer_scripts ? 2.0f : 0.001f; + } + new_accept->level = 0.0f; +} + +/***************************************************************** + * + * Parsing type-map files, in Roy's meta/http format augmented with + * #-comments. + */ + +/* Reading RFC822-style header lines, ignoring #-comments and + * handling continuations. + */ + +enum header_state { + header_eof, header_seen, header_sep +}; + +static enum header_state get_header_line(char *buffer, int len, FILE *map) +{ + char *buf_end = buffer + len; + char *cp; + int c; + + /* Get a noncommented line */ + + do { + if (fgets(buffer, MAX_STRING_LEN, map) == NULL) { + return header_eof; + } + } while (buffer[0] == '#'); + + /* If blank, just return it --- this ends information on this variant */ + + for (cp = buffer; (*cp && ap_isspace(*cp)); ++cp) { + continue; + } + + if (*cp == '\0') { + return header_sep; + } + + /* If non-blank, go looking for header lines, but note that we still + * have to treat comments specially... + */ + + cp += strlen(cp); + + while ((c = getc(map)) != EOF) { + if (c == '#') { + /* Comment line */ + while ((c = getc(map)) != EOF && c != '\n') { + continue; + } + } + else if (ap_isspace(c)) { + /* Leading whitespace. POSSIBLE continuation line + * Also, possibly blank --- if so, we ungetc() the final newline + * so that we will pick up the blank line the next time 'round. + */ + + while (c != EOF && c != '\n' && ap_isspace(c)) { + c = getc(map); + } + + ungetc(c, map); + + if (c == '\n') { + return header_seen; /* Blank line */ + } + + /* Continuation */ + + while (cp < buf_end - 2 && (c = getc(map)) != EOF && c != '\n') { + *cp++ = c; + } + + *cp++ = '\n'; + *cp = '\0'; + } + else { + + /* Line beginning with something other than whitespace */ + + ungetc(c, map); + return header_seen; + } + } + + return header_seen; +} + +/* Stripping out RFC822 comments */ + +static void strip_paren_comments(char *hdr) +{ + /* Hmmm... is this correct? In Roy's latest draft, (comments) can nest! */ + /* Nope, it isn't correct. Fails to handle backslash escape as well. */ + + while (*hdr) { + if (*hdr == '"') { + hdr = strchr(hdr, '"'); + if (hdr == NULL) { + return; + } + ++hdr; + } + else if (*hdr == '(') { + while (*hdr && *hdr != ')') { + *hdr++ = ' '; + } + + if (*hdr) { + *hdr++ = ' '; + } + } + else { + ++hdr; + } + } +} + +/* Getting to a header body from the header */ + +static char *lcase_header_name_return_body(char *header, request_rec *r) +{ + char *cp = header; + + for ( ; *cp && *cp != ':' ; ++cp) { + *cp = ap_tolower(*cp); + } + + if (!*cp) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Syntax error in type map --- no ':': %s", r->filename); + return NULL; + } + + do { + ++cp; + } while (*cp && ap_isspace(*cp)); + + if (!*cp) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Syntax error in type map --- no header body: %s", + r->filename); + return NULL; + } + + return cp; +} + +static int read_type_map(negotiation_state *neg, request_rec *rr) +{ + request_rec *r = neg->r; + FILE *map; + char buffer[MAX_STRING_LEN]; + enum header_state hstate; + struct var_rec mime_info; + int has_content; + + /* We are not using multiviews */ + neg->count_multiviews_variants = 0; + + map = ap_pfopen(neg->pool, rr->filename, "r"); + if (map == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "cannot access type map file: %s", rr->filename); + return HTTP_FORBIDDEN; + } + + clean_var_rec(&mime_info); + has_content = 0; + + do { + hstate = get_header_line(buffer, MAX_STRING_LEN, map); + + if (hstate == header_seen) { + char *body1 = lcase_header_name_return_body(buffer, neg->r); + const char *body; + + if (body1 == NULL) { + return SERVER_ERROR; + } + + strip_paren_comments(body1); + body = body1; + + if (!strncmp(buffer, "uri:", 4)) { + mime_info.file_name = ap_get_token(neg->pool, &body, 0); + } + else if (!strncmp(buffer, "content-type:", 13)) { + struct accept_rec accept_info; + + get_entry(neg->pool, &accept_info, body); + set_mime_fields(&mime_info, &accept_info); + has_content = 1; + } + else if (!strncmp(buffer, "content-length:", 15)) { + mime_info.bytes = (float)atof(body); + has_content = 1; + } + else if (!strncmp(buffer, "content-language:", 17)) { + mime_info.content_languages = do_languages_line(neg->pool, + &body); + has_content = 1; + } + else if (!strncmp(buffer, "content-encoding:", 17)) { + mime_info.content_encoding = ap_get_token(neg->pool, &body, 0); + has_content = 1; + } + else if (!strncmp(buffer, "description:", 12)) { + char *desc = ap_pstrdup(neg->pool, body); + char *cp; + + for (cp = desc; *cp; ++cp) { + if (*cp=='\n') *cp=' '; + } + if (cp>desc) *(cp-1)=0; + mime_info.description = desc; + } + } + else { + if (*mime_info.file_name && has_content) { + void *new_var = ap_push_array(neg->avail_vars); + + memcpy(new_var, (void *) &mime_info, sizeof(var_rec)); + } + + clean_var_rec(&mime_info); + has_content = 0; + } + } while (hstate != header_eof); + + ap_pfclose(neg->pool, map); + + set_vlist_validator(r, rr); + + return OK; +} + + +/* Sort function used by read_types_multi. */ +static int variantsortf(var_rec *a, var_rec *b) { + + /* First key is the source quality, sort in descending order. */ + + /* XXX: note that we currently implement no method of setting the + * source quality for multiviews variants, so we are always comparing + * 1.0 to 1.0 for now + */ + if (a->source_quality < b->source_quality) + return 1; + if (a->source_quality > b->source_quality) + return -1; + + /* Second key is the variant name */ + return strcmp(a->file_name, b->file_name); +} + +/***************************************************************** + * + * Same as read_type_map, except we use a filtered directory listing + * as the map... + */ + +static int read_types_multi(negotiation_state *neg) +{ + request_rec *r = neg->r; + + char *filp; + int prefix_len; + DIR *dirp; + struct DIR_TYPE *dir_entry; + struct var_rec mime_info; + struct accept_rec accept_info; + void *new_var; + struct { int any, all; } forbidden; + + clean_var_rec(&mime_info); + + if (!(filp = strrchr(r->filename, '/'))) { + return DECLINED; /* Weird... */ + } + + if (strncmp(r->filename, "proxy:", 6) == 0) { + return DECLINED; + } + + ++filp; + prefix_len = strlen(filp); + + dirp = ap_popendir(neg->pool, neg->dir_name); + + if (dirp == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "cannot read directory for multi: %s", neg->dir_name); + return HTTP_FORBIDDEN; + } + + forbidden.any = 0; + forbidden.all = 1; + + while ((dir_entry = readdir(dirp))) { + array_header *exception_list; + request_rec *sub_req; + + /* Do we have a match? */ +#ifdef CASE_BLIND_FILESYSTEM + if (strncasecmp(dir_entry->d_name, filp, prefix_len)) { +#else + if (strncmp(dir_entry->d_name, filp, prefix_len)) { +#endif + continue; + } + if (dir_entry->d_name[prefix_len] != '.') { + continue; + } + + /* Yep. See if it's something which we have access to, and + * which has a known type and encoding (as opposed to something + * which we'll be slapping default_type on later). + */ + + sub_req = ap_sub_req_lookup_file(dir_entry->d_name, r); + + /* If it has a handler, we'll pretend it's a CGI script, + * since that's a good indication of the sort of thing it + * might be doing. + */ + if (sub_req->handler && !sub_req->content_type) { + sub_req->content_type = CGI_MAGIC_TYPE; + } + + /* HTTP_FORBIDDEN is returned, e.g., if the path length limit was exceeded */ + /* HTTP_OK does NOT necessarily mean that the file is really readable! */ + if (sub_req->status == HTTP_OK) + forbidden.all = 0; + else if (sub_req->status == HTTP_FORBIDDEN) + forbidden.any = 1; + + /* + * mod_mime will _always_ provide us the base name in the + * ap-mime-exception-list, if it processed anything. If + * this list is empty, give up immediately, there was + * nothing interesting. For example, looking at the files + * readme.txt and readme.foo, we will throw away .foo if + * it's an insignificant file (e.g. did not identify a + * language, charset, encoding, content type or handler,) + */ + exception_list = + (array_header *) ap_table_get(sub_req->notes, + "ap-mime-exceptions-list"); + if (!exception_list) { + ap_destroy_sub_req(sub_req); + continue; + } + + /* Each unregonized bit better match our base name, in sequence. + * A test of index.html.foo will match index.foo or index.html.foo, + * but it will never transpose the segments and allow index.foo.html + * because that would introduce too much CPU consumption. Better that + * we don't attempt a many-to-many match here. + */ + { + int nexcept = exception_list->nelts; + char **cur_except = (char**)exception_list->elts; + char *segstart = filp, *segend, saveend; + + while (*segstart && nexcept) { + if (!(segend = strchr(segstart, '.'))) + segend = strchr(segstart, '\0'); + saveend = *segend; + *segend = '\0'; + +#ifdef CASE_BLIND_FILESYSTEM + if (strcasecmp(segstart, *cur_except) == 0) { +#else + if (strcmp(segstart, *cur_except) == 0) { +#endif + --nexcept; + ++cur_except; + } + + if (!saveend) + break; + + *segend = saveend; + segstart = segend + 1; + } + + if (nexcept) { + /* Something you don't know is, something you don't know... + */ + ap_destroy_sub_req(sub_req); + continue; + } + } + + /* + * ###: be warned, the _default_ content type is already + * picked up here! If we failed the subrequest, or don't + * know what we are serving, then continue. + */ + if (sub_req->status != HTTP_OK || (!sub_req->content_type)) { + ap_destroy_sub_req(sub_req); + continue; + } + + /* If it's a map file, we use that instead of the map + * we're building... + */ + if (((sub_req->content_type) && + !strcmp(sub_req->content_type, MAP_FILE_MAGIC_TYPE)) || + ((sub_req->handler) && + !strcmp(sub_req->handler, "type-map"))) { + + ap_pclosedir(neg->pool, dirp); + neg->avail_vars->nelts = 0; + if (sub_req->status != HTTP_OK) { + return sub_req->status; + } + return read_type_map(neg, sub_req); + } + + /* Have reasonable variant --- gather notes. */ + + mime_info.sub_req = sub_req; + mime_info.file_name = ap_pstrdup(neg->pool, dir_entry->d_name); + if (sub_req->content_encoding) { + mime_info.content_encoding = sub_req->content_encoding; + } + if (sub_req->content_languages) { + mime_info.content_languages = sub_req->content_languages; + } + + get_entry(neg->pool, &accept_info, sub_req->content_type); + set_mime_fields(&mime_info, &accept_info); + + new_var = ap_push_array(neg->avail_vars); + memcpy(new_var, (void *) &mime_info, sizeof(var_rec)); + + neg->count_multiviews_variants++; + + clean_var_rec(&mime_info); + } + + ap_pclosedir(neg->pool, dirp); + + /* If all variants we considered turn out to be forbidden, then return FORBIDDEN */ + if (forbidden.any && forbidden.all) + return HTTP_FORBIDDEN; + + set_vlist_validator(r, r); + + /* Sort the variants into a canonical order. The negotiation + * result sometimes depends on the order of the variants. By + * sorting the variants into a canonical order, rather than using + * the order in which readdir() happens to return them, we ensure + * that the negotiation result will be consistent over filesystem + * backup/restores and over all mirror sites. + */ + + qsort((void *) neg->avail_vars->elts, neg->avail_vars->nelts, + sizeof(var_rec), (int (*)(const void *, const void *)) variantsortf); + + return OK; +} + + +/***************************************************************** + * And now for the code you've been waiting for... actually + * finding a match to the client's requirements. + */ + +/* Matching MIME types ... the star/star and foo/star commenting conventions + * are implemented here. (You know what I mean by star/star, but just + * try mentioning those three characters in a C comment). Using strcmp() + * is legit, because everything has already been smashed to lowercase. + * + * Note also that if we get an exact match on the media type, we update + * level_matched for use in level_cmp below... + * + * We also give a value for mime_stars, which is used later. It should + * be 1 for star/star, 2 for type/star and 3 for type/subtype. + */ + +static int mime_match(accept_rec *accept_r, var_rec *avail) +{ + char *accept_type = accept_r->name; + char *avail_type = avail->mime_type; + int len = strlen(accept_type); + + if (accept_type[0] == '*') { /* Anything matches star/star */ + if (avail->mime_stars < 1) { + avail->mime_stars = 1; + } + return 1; + } + else if ((accept_type[len - 1] == '*') && + !strncmp(accept_type, avail_type, len - 2)) { + if (avail->mime_stars < 2) { + avail->mime_stars = 2; + } + return 1; + } + else if (!strcmp(accept_type, avail_type) + || (!strcmp(accept_type, "text/html") + && (!strcmp(avail_type, INCLUDES_MAGIC_TYPE) + || !strcmp(avail_type, INCLUDES_MAGIC_TYPE3)))) { + if (accept_r->level >= avail->level) { + avail->level_matched = avail->level; + avail->mime_stars = 3; + return 1; + } + } + + return OK; +} + +/* This code implements a piece of the tie-breaking algorithm between + * variants of equal quality. This piece is the treatment of variants + * of the same base media type, but different levels. What we want to + * return is the variant at the highest level that the client explicitly + * claimed to accept. + * + * If all the variants available are at a higher level than that, or if + * the client didn't say anything specific about this media type at all + * and these variants just got in on a wildcard, we prefer the lowest + * level, on grounds that that's the one that the client is least likely + * to choke on. + * + * (This is all motivated by treatment of levels in HTML --- we only + * want to give level 3 to browsers that explicitly ask for it; browsers + * that don't, including HTTP/0.9 browsers that only get the implicit + * "Accept: * / *" [space added to avoid confusing cpp --- no, that + * syntax doesn't really work] should get HTML2 if available). + * + * (Note that this code only comes into play when we are choosing among + * variants of equal quality, where the draft standard gives us a fair + * bit of leeway about what to do. It ain't specified by the standard; + * rather, it is a choice made by this server about what to do in cases + * where the standard does not specify a unique course of action). + */ + +static int level_cmp(var_rec *var1, var_rec *var2) +{ + /* Levels are only comparable between matching media types */ + + if (var1->is_pseudo_html && !var2->is_pseudo_html) { + return 0; + } + + if (!var1->is_pseudo_html && strcmp(var1->mime_type, var2->mime_type)) { + return 0; + } + /* The result of the above if statements is that, if we get to + * here, both variants have the same mime_type or both are + * pseudo-html. + */ + + /* Take highest level that matched, if either did match. */ + + if (var1->level_matched > var2->level_matched) { + return 1; + } + if (var1->level_matched < var2->level_matched) { + return -1; + } + + /* Neither matched. Take lowest level, if there's a difference. */ + + if (var1->level < var2->level) { + return 1; + } + if (var1->level > var2->level) { + return -1; + } + + /* Tied */ + + return 0; +} + +/* Finding languages. The main entry point is set_language_quality() + * which is called for each variant. It sets two elements in the + * variant record: + * language_quality - the 'q' value of the 'best' matching language + * from Accept-Language: header (HTTP/1.1) + * lang_index - Pre HTTP/1.1 language priority, using + * position of language on the Accept-Language: + * header, if present, else LanguagePriority + * directive order. + * + * When we do the variant checking for best variant, we use language + * quality first, and if a tie, language_index next (this only applies + * when _not_ using the RVSA/1.0 algorithm). If using the RVSA/1.0 + * algorithm, lang_index is never used. + * + * set_language_quality() calls find_lang_index() and find_default_index() + * to set lang_index. + */ + +static int find_lang_index(array_header *accept_langs, char *lang) +{ + accept_rec *accs; + int i; + + if (!lang || !accept_langs) { + return -1; + } + + accs = (accept_rec *) accept_langs->elts; + + for (i = 0; i < accept_langs->nelts; ++i) { + if (!strncmp(lang, accs[i].name, strlen(accs[i].name))) { + return i; + } + } + + return -1; +} + +/* This function returns the priority of a given language + * according to LanguagePriority. It is used in case of a tie + * between several languages. + */ + +static int find_default_index(neg_dir_config *conf, char *lang) +{ + array_header *arr; + int nelts; + char **elts; + int i; + + if (!lang) { + return -1; + } + + arr = conf->language_priority; + nelts = arr->nelts; + elts = (char **) arr->elts; + + for (i = 0; i < nelts; ++i) { + if (!strcasecmp(elts[i], lang)) { + return i; + } + } + + return -1; +} + +/* set_default_lang_quality() sets the quality we apply to variants + * which have no language assigned to them. If none of the variants + * have a language, we are not negotiating on language, so all are + * acceptable, and we set the default q value to 1.0. However if + * some of the variants have languages, we set this default to 0.001. + * The value of this default will be applied to all variants with + * no explicit language -- which will have the effect of making them + * acceptable, but only if no variants with an explicit language + * are acceptable. The default q value set here is assigned to variants + * with no language type in set_language_quality(). + * + * Note that if using the RVSA/1.0 algorithm, we don't use this + * fiddle. + */ + +static void set_default_lang_quality(negotiation_state *neg) +{ + var_rec *avail_recs = (var_rec *) neg->avail_vars->elts; + int j; + + if (!neg->dont_fiddle_headers) { + for (j = 0; j < neg->avail_vars->nelts; ++j) { + var_rec *variant = &avail_recs[j]; + if (variant->content_languages && + variant->content_languages->nelts) { + neg->default_lang_quality = 0.001f; + return; + } + } + } + + neg->default_lang_quality = 1.0f; +} + +/* Set the language_quality value in the variant record. Also + * assigns lang_index for back-compat. + * + * To find the language_quality value, we look for the 'q' value + * of the 'best' matching language on the Accept-Language + * header. The 'best' match is the language on Accept-Language + * header which matches the language of this variant either fully, + * or as far as the prefix marker (-). If two or more languages + * match, use the longest string from the Accept-Language header + * (see HTTP/1.1 [14.4]) + * + * When a variant has multiple languages, we find the 'best' + * match for each variant language tag as above, then select the + * one with the highest q value. Because both the accept-header + * and variant can have multiple languages, we now have a hairy + * loop-within-a-loop here. + * + * If the variant has no language and we have no Accept-Language + * items, leave the quality at 1.0 and return. + * + * If the variant has no language, we use the default as set by + * set_default_lang_quality() (1.0 if we are not negotiating on + * language, 0.001 if we are). + * + * Following the setting of the language quality, we drop through to + * set the old 'lang_index'. This is set based on either the order + * of the languages on the Accept-Language header, or the + * order on the LanguagePriority directive. This is only used + * in the negotiation if the language qualities tie. + */ + +static void set_language_quality(negotiation_state *neg, var_rec *variant) +{ + char *firstlang; + int idx; + + if (!variant->content_languages || !variant->content_languages->nelts) { + /* This variant has no content-language, so use the default + * quality factor for variants with no content-language + * (previously set by set_default_lang_quality()). + * Leave the factor alone (it remains at 1.0) when we may not fiddle + * with the headers. + */ + if (!neg->dont_fiddle_headers) { + variant->lang_quality = neg->default_lang_quality; + } + if (!neg->accept_langs) { + return; /* no accept-language header */ + } + + } + else { + /* Variant has one (or more) languages. Look for the best + * match. We do this by going through each language on the + * variant description looking for a match on the + * Accept-Language header. The best match is the longest + * matching language on the header. The final result is the + * best q value from all the languages on the variant + * description. + */ + + if (!neg->accept_langs) { + /* no accept-language header makes the variant indefinite */ + variant->definite = 0; + } + else { /* There is an accept-language with 0 or more items */ + accept_rec *accs = (accept_rec *) neg->accept_langs->elts; + accept_rec *best = NULL, *star = NULL; + accept_rec *bestthistag; + char *lang, *p; + float fiddle_q = 0.0f; + int any_match_on_star = 0; + int i, j, alen, longest_lang_range_len; + + for (j = 0; j < variant->content_languages->nelts; ++j) { + p = NULL; + bestthistag = NULL; + longest_lang_range_len = 0; + alen = 0; + + /* lang is the variant's language-tag, which is the one + * we are allowed to use the prefix of in HTTP/1.1 + */ + lang = ((char **) (variant->content_languages->elts))[j]; + + /* now find the best (i.e. longest) matching + * Accept-Language header language. We put the best match + * for this tag in bestthistag. We cannot update the + * overall best (based on q value) because the best match + * for this tag is the longest language item on the accept + * header, not necessarily the highest q. + */ + for (i = 0; i < neg->accept_langs->nelts; ++i) { + if (!strcmp(accs[i].name, "*")) { + if (!star) { + star = &accs[i]; + } + continue; + } + /* Find language. We match if either the variant + * language tag exactly matches the language range + * from the accept header, or a prefix of the variant + * language tag up to a '-' character matches the + * whole of the language range in the Accept-Language + * header. Note that HTTP/1.x allows any number of + * '-' characters in a tag or range, currently only + * tags with zero or one '-' characters are defined + * for general use (see rfc1766). + * + * We only use language range in the Accept-Language + * header the best match for the variant language tag + * if it is longer than the previous best match. + */ + + alen = strlen(accs[i].name); + + if (((int)strlen(lang) >= alen) && + !strncmp(lang, accs[i].name, alen) && + ((lang[alen] == 0) || (lang[alen] == '-')) ) { + + if (alen > longest_lang_range_len) { + longest_lang_range_len = alen; + bestthistag = &accs[i]; + } + } + + if (!bestthistag && !neg->dont_fiddle_headers) { + /* The next bit is a fiddle. Some browsers might + * be configured to send more specific language + * ranges than desirable. For example, an + * Accept-Language of en-US should never match + * variants with languages en or en-GB. But US + * English speakers might pick en-US as their + * language choice. So this fiddle checks if the + * language range has a prefix, and if so, it + * matches variants which match that prefix with a + * priority of 0.001. So a request for en-US would + * match variants of types en and en-GB, but at + * much lower priority than matches of en-US + * directly, or of any other language listed on + * the Accept-Language header. Note that this + * fiddle does not handle multi-level prefixes. + */ + if ((p = strchr(accs[i].name, '-'))) { + int plen = p - accs[i].name; + + if (!strncmp(lang, accs[i].name, plen)) { + fiddle_q = 0.001f; + } + } + } + } + /* Finished looking at Accept-Language headers, the best + * (longest) match is in bestthistag, or NULL if no match + */ + if (!best || + (bestthistag && bestthistag->quality > best->quality)) { + best = bestthistag; + } + + /* See if the tag matches on a * in the Accept-Language + * header. If so, record this fact for later use + */ + if (!bestthistag && star) { + any_match_on_star = 1; + } + } + + /* If one of the language tags of the variant matched on *, we + * need to see if its q is better than that of any non-* match + * on any other tag of the variant. If so the * match takes + * precedence and the overall match is not definite. + */ + if ( any_match_on_star && + ((best && star->quality > best->quality) || + (!best)) ) { + best = star; + variant->definite = 0; + } + + variant->lang_quality = best ? best->quality : fiddle_q; + } + } + + /* Now set the old lang_index field. Since this is old + * stuff anyway, don't bother with handling multiple languages + * per variant, just use the first one assigned to it + */ + idx = 0; + if (variant->content_languages && variant->content_languages->nelts) { + firstlang = ((char **) variant->content_languages->elts)[0]; + } + else { + firstlang = ""; + } + if (!neg->accept_langs) { /* Client doesn't care */ + idx = find_default_index((neg_dir_config *) ap_get_module_config( + neg->r->per_dir_config, &negotiation_module), + firstlang); + } + else { /* Client has Accept-Language */ + idx = find_lang_index(neg->accept_langs, firstlang); + } + variant->lang_index = idx; + + return; +} + +/* Determining the content length --- if the map didn't tell us, + * we have to do a stat() and remember for next time. + * + * Grump. For Apache, even the first stat here may well be + * redundant (for multiviews) with a stat() done by the sub_req + * machinery. At some point, that ought to be fixed. + */ + +static float find_content_length(negotiation_state *neg, var_rec *variant) +{ + struct stat statb; + + if (variant->bytes == 0) { + char *fullname = ap_make_full_path(neg->pool, neg->dir_name, + variant->file_name); + + if (stat(fullname, &statb) >= 0) { + /* Note, precision may be lost */ + variant->bytes = (float) statb.st_size; + } + } + + return variant->bytes; +} + +/* For a given variant, find the best matching Accept: header + * and assign the Accept: header's quality value to the + * mime_type_quality field of the variant, for later use in + * determining the best matching variant. + */ + +static void set_accept_quality(negotiation_state *neg, var_rec *variant) +{ + int i; + accept_rec *accept_recs; + float q = 0.0f; + int q_definite = 1; + + /* if no Accept: header, leave quality alone (will + * remain at the default value of 1) + * + * XXX: This if is currently never true because of the effect of + * maybe_add_default_accepts(). + */ + if (!neg->accepts) { + if (variant->mime_type && *variant->mime_type) + variant->definite = 0; + return; + } + + accept_recs = (accept_rec *) neg->accepts->elts; + + /* + * Go through each of the ranges on the Accept: header, + * looking for the 'best' match with this variant's + * content-type. We use the best match's quality + * value (from the Accept: header) for this variant's + * mime_type_quality field. + * + * The best match is determined like this: + * type/type is better than type/ * is better than * / * + * if match is type/type, use the level mime param if available + */ + for (i = 0; i < neg->accepts->nelts; ++i) { + + accept_rec *type = &accept_recs[i]; + int prev_mime_stars; + + prev_mime_stars = variant->mime_stars; + + if (!mime_match(type, variant)) { + continue; /* didn't match the content type at all */ + } + else { + /* did match - see if there were less or more stars than + * in previous match + */ + if (prev_mime_stars == variant->mime_stars) { + continue; /* more stars => not as good a match */ + } + } + + /* If we are allowed to mess with the q-values + * and have no explicit q= parameters in the accept header, + * make wildcards very low, so we have a low chance + * of ending up with them if there's something better. + */ + + if (!neg->dont_fiddle_headers && !neg->accept_q && + variant->mime_stars == 1) { + q = 0.01f; + } + else if (!neg->dont_fiddle_headers && !neg->accept_q && + variant->mime_stars == 2) { + q = 0.02f; + } + else { + q = type->quality; + } + + q_definite = (variant->mime_stars == 3); + } + variant->mime_type_quality = q; + variant->definite = variant->definite && q_definite; + +} + +/* For a given variant, find the 'q' value of the charset given + * on the Accept-Charset line. If no charsets are listed, + * assume value of '1'. + */ +static void set_charset_quality(negotiation_state *neg, var_rec *variant) +{ + int i; + accept_rec *accept_recs; + char *charset = variant->content_charset; + accept_rec *star = NULL; + + /* if no Accept-Charset: header, leave quality alone (will + * remain at the default value of 1) + */ + if (!neg->accept_charsets) { + if (charset && *charset) + variant->definite = 0; + return; + } + + accept_recs = (accept_rec *) neg->accept_charsets->elts; + + if (charset == NULL || !*charset) { + /* Charset of variant not known */ + + /* if not a text / * type, leave quality alone */ + if (!(!strncmp(variant->mime_type, "text/", 5) + || !strcmp(variant->mime_type, INCLUDES_MAGIC_TYPE) + || !strcmp(variant->mime_type, INCLUDES_MAGIC_TYPE3) + )) + return; + + /* Don't go guessing if we are in strict header mode, + * e.g. when running the rvsa, as any guess won't be reflected + * in the variant list or content-location headers. + */ + if (neg->dont_fiddle_headers) + return; + + charset = "iso-8859-1"; /* The default charset for HTTP text types */ + } + + /* + * Go through each of the items on the Accept-Charset header, + * looking for a match with this variant's charset. If none + * match, charset is unacceptable, so set quality to 0. + */ + for (i = 0; i < neg->accept_charsets->nelts; ++i) { + + accept_rec *type = &accept_recs[i]; + + if (!strcmp(type->name, charset)) { + variant->charset_quality = type->quality; + return; + } + else if (strcmp(type->name, "*") == 0) { + star = type; + } + } + /* No explicit match */ + if (star) { + variant->charset_quality = star->quality; + variant->definite = 0; + return; + } + /* If this variant is in charset iso-8859-1, the default is 1.0 */ + if (strcmp(charset, "iso-8859-1") == 0) { + variant->charset_quality = 1.0f; + } + else { + variant->charset_quality = 0.0f; + } +} + + +/* is_identity_encoding is included for back-compat, but does anyone + * use 7bit, 8bin or binary in their var files?? + */ + +static int is_identity_encoding(const char *enc) +{ + return (!enc || !enc[0] || !strcmp(enc, "7bit") || !strcmp(enc, "8bit") + || !strcmp(enc, "binary")); +} + +/* + * set_encoding_quality determines whether the encoding for a particular + * variant is acceptable for the user-agent. + * + * The rules for encoding are that if the user-agent does not supply + * any Accept-Encoding header, then all encodings are allowed but a + * variant with no encoding should be preferred. + * If there is an empty Accept-Encoding header, then no encodings are + * acceptable. If there is a non-empty Accept-Encoding header, then + * any of the listed encodings are acceptable, as well as no encoding + * unless the "identity" encoding is specifically excluded. + */ +static void set_encoding_quality(negotiation_state *neg, var_rec *variant) +{ + accept_rec *accept_recs; + const char *enc = variant->content_encoding; + accept_rec *star = NULL; + float value_if_not_found = 0.0f; + int i; + + if (!neg->accept_encodings) { + /* We had no Accept-Encoding header, assume that all + * encodings are acceptable with a low quality, + * but we prefer no encoding if available. + */ + if (!enc || is_identity_encoding(enc)) + variant->encoding_quality = 1.0f; + else + variant->encoding_quality = 0.5f; + + return; + } + + if (!enc || is_identity_encoding(enc)) { + enc = "identity"; + value_if_not_found = 0.0001f; + } + + accept_recs = (accept_rec *) neg->accept_encodings->elts; + + /* Go through each of the encodings on the Accept-Encoding: header, + * looking for a match with our encoding. x- prefixes are ignored. + */ + if (enc[0] == 'x' && enc[1] == '-') { + enc += 2; + } + for (i = 0; i < neg->accept_encodings->nelts; ++i) { + + char *name = accept_recs[i].name; + + if (name[0] == 'x' && name[1] == '-') { + name += 2; + } + + if (!strcmp(name, enc)) { + variant->encoding_quality = accept_recs[i].quality; + return; + } + + if (strcmp(name, "*") == 0) { + star = &accept_recs[i]; + } + + } + /* No explicit match */ + if (star) { + variant->encoding_quality = star->quality; + return; + } + + /* Encoding not found on Accept-Encoding: header, so it is + * _not_ acceptable unless it is the identity (no encoding) + */ + variant->encoding_quality = value_if_not_found; +} + +/************************************************************* + * Possible results of the variant selection algorithm + */ +enum algorithm_results { + alg_choice = 1, /* choose variant */ + alg_list /* list variants */ +}; + +/* Below is the 'best_match' function. It returns an int, which has + * one of the two values alg_choice or alg_list, which give the result + * of the variant selection algorithm. alg_list means that no best + * variant was found by the algorithm, alg_choice means that a best + * variant was found and should be returned. The list/choice + * terminology comes from TCN (rfc2295), but is used in a more generic + * way here. The best variant is returned in *pbest. best_match has + * two possible algorithms for determining the best variant: the + * RVSA/1.0 algorithm (from RFC2296), and the standard Apache + * algorithm. These are split out into separate functions + * (is_variant_better_rvsa() and is_variant_better()). Selection of + * one is through the neg->use_rvsa flag. + * + * The call to best_match also creates full information, including + * language, charset, etc quality for _every_ variant. This is needed + * for generating a correct Vary header, and can be used for the + * Alternates header, the human-readable list responses and 406 errors. + */ + +/* Firstly, the RVSA/1.0 (HTTP Remote Variant Selection Algorithm + * v1.0) from rfc2296. This is the algorithm that goes together with + * transparent content negotiation (TCN). + */ +static int is_variant_better_rvsa(negotiation_state *neg, var_rec *variant, + var_rec *best, float *p_bestq) +{ + float bestq = *p_bestq, q; + + /* TCN does not cover negotiation on content-encoding. For now, + * we ignore the encoding unless it was explicitly excluded. + */ + if (variant->encoding_quality == 0.0f) + return 0; + + q = variant->mime_type_quality * + variant->source_quality * + variant->charset_quality * + variant->lang_quality; + + /* RFC 2296 calls for the result to be rounded to 5 decimal places, + * but we don't do that because it serves no useful purpose other + * than to ensure that a remote algorithm operates on the same + * precision as ours. That is silly, since what we obviously want + * is for the algorithm to operate on the best available precision + * regardless of who runs it. Since the above calculation may + * result in significant variance at 1e-12, rounding would be bogus. + */ + +#ifdef NEG_DEBUG + fprintf(stderr, "Variant: file=%s type=%s lang=%s sourceq=%1.3f " + "mimeq=%1.3f langq=%1.3f charq=%1.3f encq=%1.3f " + "q=%1.5f definite=%d\n", + (variant->file_name ? variant->file_name : ""), + (variant->mime_type ? variant->mime_type : ""), + (variant->content_languages + ? ap_array_pstrcat(neg->pool, variant->content_languages, ',') + : ""), + variant->source_quality, + variant->mime_type_quality, + variant->lang_quality, + variant->charset_quality, + variant->encoding_quality, + q, + variant->definite); +#endif + + if (q <= 0.0f) { + return 0; + } + if (q > bestq) { + *p_bestq = q; + return 1; + } + if (q == bestq) { + /* If the best variant's encoding is of lesser quality than + * this variant, then we prefer this variant + */ + if (variant->encoding_quality > best->encoding_quality) { + *p_bestq = q; + return 1; + } + } + return 0; +} + +/* Negotiation algorithm as used by previous versions of Apache + * (just about). + */ + +static int is_variant_better(negotiation_state *neg, var_rec *variant, + var_rec *best, float *p_bestq) +{ + float bestq = *p_bestq, q; + int levcmp; + + /* For non-transparent negotiation, server can choose how + * to handle the negotiation. We'll use the following in + * order: content-type, language, content-type level, charset, + * content encoding, content length. + * + * For each check, we have three possible outcomes: + * This variant is worse than current best: return 0 + * This variant is better than the current best: + * assign this variant's q to *p_bestq, and return 1 + * This variant is just as desirable as the current best: + * drop through to the next test. + * + * This code is written in this long-winded way to allow future + * customisation, either by the addition of additional + * checks, or to allow the order of the checks to be determined + * by configuration options (e.g. we might prefer to check + * language quality _before_ content type). + */ + + /* First though, eliminate this variant if it is not + * acceptable by type, charset, encoding or language. + */ + +#ifdef NEG_DEBUG + fprintf(stderr, "Variant: file=%s type=%s lang=%s sourceq=%1.3f " + "mimeq=%1.3f langq=%1.3f langidx=%d charq=%1.3f encq=%1.3f \n", + (variant->file_name ? variant->file_name : ""), + (variant->mime_type ? variant->mime_type : ""), + (variant->content_languages + ? ap_array_pstrcat(neg->pool, variant->content_languages, ',') + : ""), + variant->source_quality, + variant->mime_type_quality, + variant->lang_quality, + variant->lang_index, + variant->charset_quality, + variant->encoding_quality); +#endif + + if (variant->encoding_quality == 0.0f || + variant->lang_quality == 0.0f || + variant->source_quality == 0.0f || + variant->charset_quality == 0.0f || + variant->mime_type_quality == 0.0f) { + return 0; /* don't consider unacceptables */ + } + + q = variant->mime_type_quality * variant->source_quality; + if (q == 0.0 || q < bestq) { + return 0; + } + if (q > bestq || !best) { + *p_bestq = q; + return 1; + } + + /* language */ + if (variant->lang_quality < best->lang_quality) { + return 0; + } + if (variant->lang_quality > best->lang_quality) { + *p_bestq = q; + return 1; + } + + /* if language qualities were equal, try the LanguagePriority stuff */ + if (best->lang_index != -1 && + (variant->lang_index == -1 || variant->lang_index > best->lang_index)) { + return 0; + } + if (variant->lang_index != -1 && + (best->lang_index == -1 || variant->lang_index < best->lang_index)) { + *p_bestq = q; + return 1; + } + + /* content-type level (sometimes used with text/html, though we + * support it on other types too) + */ + levcmp = level_cmp(variant, best); + if (levcmp == -1) { + return 0; + } + if (levcmp == 1) { + *p_bestq = q; + return 1; + } + + /* charset */ + if (variant->charset_quality < best->charset_quality) { + return 0; + } + /* If the best variant's charset is ISO-8859-1 and this variant has + * the same charset quality, then we prefer this variant + */ + + if (variant->charset_quality > best->charset_quality || + ((variant->content_charset != NULL && + *variant->content_charset != '\0' && + strcmp(variant->content_charset, "iso-8859-1") != 0) && + (best->content_charset == NULL || + *best->content_charset == '\0' || + strcmp(best->content_charset, "iso-8859-1") == 0))) { + *p_bestq = q; + return 1; + } + + /* Prefer the highest value for encoding_quality. + */ + if (variant->encoding_quality < best->encoding_quality) { + return 0; + } + if (variant->encoding_quality > best->encoding_quality) { + *p_bestq = q; + return 1; + } + + /* content length if all else equal */ + if (find_content_length(neg, variant) >= find_content_length(neg, best)) { + return 0; + } + + /* ok, to get here means every thing turned out equal, except + * we have a shorter content length, so use this variant + */ + *p_bestq = q; + return 1; +} + +static int best_match(negotiation_state *neg, var_rec **pbest) +{ + int j; + var_rec *best = NULL; + float bestq = 0.0f; + enum algorithm_results algorithm_result; + + var_rec *avail_recs = (var_rec *) neg->avail_vars->elts; + + set_default_lang_quality(neg); + + /* + * Find the 'best' variant + */ + + for (j = 0; j < neg->avail_vars->nelts; ++j) { + var_rec *variant = &avail_recs[j]; + + /* Find all the relevant 'quality' values from the + * Accept... headers, and store in the variant. This also + * prepares for sending an Alternates header etc so we need to + * do it even if we do not actually plan to find a best + * variant. + */ + set_accept_quality(neg, variant); + set_language_quality(neg, variant); + set_encoding_quality(neg, variant); + set_charset_quality(neg, variant); + + /* Only do variant selection if we may actually choose a + * variant for the client + */ + if (neg->may_choose) { + + /* Now find out if this variant is better than the current + * best, either using the RVSA/1.0 algorithm, or Apache's + * internal server-driven algorithm. Presumably other + * server-driven algorithms are possible, and could be + * implemented here. + */ + + if (neg->use_rvsa) { + if (is_variant_better_rvsa(neg, variant, best, &bestq)) { + best = variant; + } + } + else { + if (is_variant_better(neg, variant, best, &bestq)) { + best = variant; + } + } + } + } + + /* We now either have a best variant, or no best variant */ + + if (neg->use_rvsa) { + /* calculate result for RVSA/1.0 algorithm: + * only a choice response if the best variant has q>0 + * and is definite + */ + algorithm_result = (best && best->definite) && (bestq > 0) ? + alg_choice : alg_list; + } + else { + /* calculate result for Apache negotiation algorithm */ + algorithm_result = bestq > 0 ? alg_choice : alg_list; + } + + /* Returning a choice response with a non-neighboring variant is a + * protocol security error in TCN (see rfc2295). We do *not* + * verify here that the variant and URI are neighbors, even though + * we may return alg_choice. We depend on the environment (the + * caller) to only declare the resource transparently negotiable if + * all variants are neighbors. + */ + *pbest = best; + return algorithm_result; +} + +/* Sets response headers for a negotiated response. + * neg->is_transparent determines whether a transparently negotiated + * response or a plain `server driven negotiation' response is + * created. Applicable headers are Alternates, Vary, and TCN. + * + * The Vary header we create is sometimes longer than is required for + * the correct caching of negotiated results by HTTP/1.1 caches. For + * example if we have 3 variants x.html, x.ps.en and x.ps.nl, and if + * the Accept: header assigns a 0 quality to .ps, then the results of + * the two server-side negotiation algorithms we currently implement + * will never depend on Accept-Language so we could return `Vary: + * negotiate, accept' instead of the longer 'Vary: negotiate, accept, + * accept-language' which the code below will return. A routine for + * computing the exact minimal Vary header would be a huge pain to code + * and maintain though, especially because we need to take all possible + * twiddles in the server-side negotiation algorithms into account. + */ +static void set_neg_headers(request_rec *r, negotiation_state *neg, + int alg_result) +{ + table *hdrs; + var_rec *avail_recs = (var_rec *) neg->avail_vars->elts; + const char *sample_type = NULL; + const char *sample_language = NULL; + const char *sample_encoding = NULL; + const char *sample_charset = NULL; + char *lang; + char *qstr; + char *lenstr; + long len; + array_header *arr; + int max_vlist_array = (neg->avail_vars->nelts * 21); + int first_variant = 1; + int vary_by_type = 0; + int vary_by_language = 0; + int vary_by_charset = 0; + int vary_by_encoding = 0; + int j; + + /* In order to avoid O(n^2) memory copies in building Alternates, + * we preallocate a table with the maximum substrings possible, + * fill it with the variant list, and then concatenate the entire array. + * Note that if you change the number of substrings pushed, you also + * need to change the calculation of max_vlist_array above. + */ + if (neg->send_alternates && neg->avail_vars->nelts) + arr = ap_make_array(r->pool, max_vlist_array, sizeof(char *)); + else + arr = NULL; + + /* Put headers into err_headers_out, since send_http_header() + * outputs both headers_out and err_headers_out. + */ + hdrs = r->err_headers_out; + + for (j = 0; j < neg->avail_vars->nelts; ++j) { + var_rec *variant = &avail_recs[j]; + + if (variant->content_languages && variant->content_languages->nelts) { + lang = ap_array_pstrcat(r->pool, variant->content_languages, ','); + } + else { + lang = NULL; + } + + /* Calculate Vary by looking for any difference between variants */ + + if (first_variant) { + sample_type = variant->mime_type; + sample_charset = variant->content_charset; + sample_language = lang; + sample_encoding = variant->content_encoding; + } + else { + if (!vary_by_type && + strcmp(sample_type ? sample_type : "", + variant->mime_type ? variant->mime_type : "")) { + vary_by_type = 1; + } + if (!vary_by_charset && + strcmp(sample_charset ? sample_charset : "", + variant->content_charset ? + variant->content_charset : "")) { + vary_by_charset = 1; + } + if (!vary_by_language && + strcmp(sample_language ? sample_language : "", + lang ? lang : "")) { + vary_by_language = 1; + } + if (!vary_by_encoding && + strcmp(sample_encoding ? sample_encoding : "", + variant->content_encoding ? + variant->content_encoding : "")) { + vary_by_encoding = 1; + } + } + first_variant = 0; + + if (!neg->send_alternates) + continue; + + /* Generate the string components for this Alternates entry */ + + *((const char **) ap_push_array(arr)) = "{\""; + *((const char **) ap_push_array(arr)) = variant->file_name; + *((const char **) ap_push_array(arr)) = "\" "; + + qstr = (char *) ap_palloc(r->pool, 6); + ap_snprintf(qstr, 6, "%1.3f", variant->source_quality); + + /* Strip trailing zeros (saves those valuable network bytes) */ + if (qstr[4] == '0') { + qstr[4] = '\0'; + if (qstr[3] == '0') { + qstr[3] = '\0'; + if (qstr[2] == '0') { + qstr[1] = '\0'; + } + } + } + *((const char **) ap_push_array(arr)) = qstr; + + if (variant->mime_type && *variant->mime_type) { + *((const char **) ap_push_array(arr)) = " {type "; + *((const char **) ap_push_array(arr)) = variant->mime_type; + *((const char **) ap_push_array(arr)) = "}"; + } + if (variant->content_charset && *variant->content_charset) { + *((const char **) ap_push_array(arr)) = " {charset "; + *((const char **) ap_push_array(arr)) = variant->content_charset; + *((const char **) ap_push_array(arr)) = "}"; + } + if (lang) { + *((const char **) ap_push_array(arr)) = " {language "; + *((const char **) ap_push_array(arr)) = lang; + *((const char **) ap_push_array(arr)) = "}"; + } + if (variant->content_encoding && *variant->content_encoding) { + /* Strictly speaking, this is non-standard, but so is TCN */ + + *((const char **) ap_push_array(arr)) = " {encoding "; + *((const char **) ap_push_array(arr)) = variant->content_encoding; + *((const char **) ap_push_array(arr)) = "}"; + } + + /* Note that the Alternates specification (in rfc2295) does + * not require that we include {length x}, so we could omit it + * if determining the length is too expensive. We currently + * always include it though. 22 bytes is enough for 2^64. + * + * If the variant is a CGI script, find_content_length would + * return the length of the script, not the output it + * produces, so we check for the presence of a handler and if + * there is one we don't add a length. + * + * XXX: TODO: This check does not detect a CGI script if we + * get the variant from a type map. This needs to be fixed + * (without breaking things if the type map specifies a + * content-length, which currently leads to the correct result). + */ + if (!(variant->sub_req && variant->sub_req->handler) + && (len = (long)find_content_length(neg, variant)) != 0) { + + lenstr = (char *) ap_palloc(r->pool, 22); + ap_snprintf(lenstr, 22, "%ld", len); + *((const char **) ap_push_array(arr)) = " {length "; + *((const char **) ap_push_array(arr)) = lenstr; + *((const char **) ap_push_array(arr)) = "}"; + } + + *((const char **) ap_push_array(arr)) = "}"; + *((const char **) ap_push_array(arr)) = ", "; /* trimmed below */ + } + + if (neg->send_alternates && neg->avail_vars->nelts) { + arr->nelts--; /* remove last comma */ + ap_table_mergen(hdrs, "Alternates", + ap_array_pstrcat(r->pool, arr, '\0')); + } + + if (neg->is_transparent || vary_by_type || vary_by_language || + vary_by_language || vary_by_charset || vary_by_encoding) { + + ap_table_mergen(hdrs, "Vary", 2 + ap_pstrcat(r->pool, + neg->is_transparent ? ", negotiate" : "", + vary_by_type ? ", accept" : "", + vary_by_language ? ", accept-language" : "", + vary_by_charset ? ", accept-charset" : "", + vary_by_encoding ? ", accept-encoding" : "", NULL)); + } + + if (neg->is_transparent) { /* Create TCN response header */ + ap_table_setn(hdrs, "TCN", + alg_result == alg_list ? "list" : "choice"); + } +} + +/********************************************************************** + * + * Return an HTML list of variants. This is output as part of the + * choice response or 406 status body. + */ + +static char *make_variant_list(request_rec *r, negotiation_state *neg) +{ + array_header *arr; + int i; + int max_vlist_array = (neg->avail_vars->nelts * 15) + 2; + + /* In order to avoid O(n^2) memory copies in building the list, + * we preallocate a table with the maximum substrings possible, + * fill it with the variant list, and then concatenate the entire array. + */ + arr = ap_make_array(r->pool, max_vlist_array, sizeof(char *)); + + *((const char **) ap_push_array(arr)) = "Available variants:\n<ul>\n"; + + for (i = 0; i < neg->avail_vars->nelts; ++i) { + var_rec *variant = &((var_rec *) neg->avail_vars->elts)[i]; + char *filename = variant->file_name ? variant->file_name : ""; + array_header *languages = variant->content_languages; + char *description = variant->description ? variant->description : ""; + + /* The format isn't very neat, and it would be nice to make + * the tags human readable (eg replace 'language en' with 'English'). + * Note that if you change the number of substrings pushed, you also + * need to change the calculation of max_vlist_array above. + */ + *((const char **) ap_push_array(arr)) = "<li><a href=\""; + *((const char **) ap_push_array(arr)) = filename; + *((const char **) ap_push_array(arr)) = "\">"; + *((const char **) ap_push_array(arr)) = filename; + *((const char **) ap_push_array(arr)) = "</a> "; + *((const char **) ap_push_array(arr)) = description; + + if (variant->mime_type && *variant->mime_type) { + *((const char **) ap_push_array(arr)) = ", type "; + *((const char **) ap_push_array(arr)) = variant->mime_type; + } + if (languages && languages->nelts) { + *((const char **) ap_push_array(arr)) = ", language "; + *((const char **) ap_push_array(arr)) = ap_array_pstrcat(r->pool, + languages, ','); + } + if (variant->content_charset && *variant->content_charset) { + *((const char **) ap_push_array(arr)) = ", charset "; + *((const char **) ap_push_array(arr)) = variant->content_charset; + } + if (variant->content_encoding) { + *((const char **) ap_push_array(arr)) = ", encoding "; + *((const char **) ap_push_array(arr)) = variant->content_encoding; + } + *((const char **) ap_push_array(arr)) = "\n"; + } + *((const char **) ap_push_array(arr)) = "</ul>\n"; + + return ap_array_pstrcat(r->pool, arr, '\0'); +} + +static void store_variant_list(request_rec *r, negotiation_state *neg) +{ + if (r->main == NULL) { + ap_table_setn(r->notes, "variant-list", make_variant_list(r, neg)); + } + else { + ap_table_setn(r->main->notes, "variant-list", + make_variant_list(r->main, neg)); + } +} + +/* Called if we got a "Choice" response from the variant selection algorithm. + * It checks the result of the chosen variant to see if it + * is itself negotiated (if so, return error VARIANT_ALSO_VARIES). + * Otherwise, add the appropriate headers to the current response. + */ + +static int setup_choice_response(request_rec *r, negotiation_state *neg, + var_rec *variant) +{ + request_rec *sub_req; + const char *sub_vary; + + if (!variant->sub_req) { + int status; + + sub_req = ap_sub_req_lookup_file(variant->file_name, r); + status = sub_req->status; + + if (status != HTTP_OK && + !ap_table_get(sub_req->err_headers_out, "TCN")) { + ap_destroy_sub_req(sub_req); + return status; + } + variant->sub_req = sub_req; + } + else { + sub_req = variant->sub_req; + } + + /* The variant selection algorithm told us to return a "Choice" + * response. This is the normal variant response, with + * some extra headers. First, ensure that the chosen + * variant did or will not itself engage in transparent negotiation. + * If not, set the appropriate headers, and fall through to + * the normal variant handling + */ + + /* This catches the error that a transparent type map selects a + * transparent multiviews resource as the best variant. + * + * XXX: We do not signal an error if a transparent type map + * selects a _non_transparent multiviews resource as the best + * variant, because we can generate a legal negotiation response + * in this case. In this case, the vlist_validator of the + * nontransparent subrequest will be lost however. This could + * lead to cases in which a change in the set of variants or the + * negotiation algorithm of the nontransparent resource is never + * propagated up to a HTTP/1.1 cache which interprets Vary. To be + * completely on the safe side we should return VARIANT_ALSO_VARIES + * for this type of recursive negotiation too. + */ + if (neg->is_transparent && + ap_table_get(sub_req->err_headers_out, "TCN")) { + return VARIANT_ALSO_VARIES; + } + + /* This catches the error that a transparent type map recursively + * selects, as the best variant, another type map which itself + * causes transparent negotiation to be done. + * + * XXX: Actually, we catch this error by catching all cases of + * type map recursion. There are some borderline recursive type + * map arrangements which would not produce transparent + * negotiation protocol errors or lack of cache propagation + * problems, but such arrangements are very hard to detect at this + * point in the control flow, so we do not bother to single them + * out. + * + * Recursive type maps imply a recursive arrangement of negotiated + * resources which is visible to outside clients, and this is not + * supported by the transparent negotiation caching protocols, so + * if we are to have generic support for recursive type maps, we + * have to create some configuration setting which makes all type + * maps non-transparent when recursion is enabled. Also, if we + * want recursive type map support which ensures propagation of + * type map changes into HTTP/1.1 caches that handle Vary, we + * would have to extend the current mechanism for generating + * variant list validators. + */ + if (sub_req->handler && strcmp(sub_req->handler, "type-map") == 0) { + return VARIANT_ALSO_VARIES; + } + + /* This adds an appropriate Variant-Vary header if the subrequest + * is a multiviews resource. + * + * XXX: TODO: Note that this does _not_ handle any Vary header + * returned by a CGI if sub_req is a CGI script, because we don't + * see that Vary header yet at this point in the control flow. + * This won't cause any cache consistency problems _unless_ the + * CGI script also returns a Cache-Control header marking the + * response as cachable. This needs to be fixed, also there are + * problems if a CGI returns an Etag header which also need to be + * fixed. + */ + if ((sub_vary = ap_table_get(sub_req->err_headers_out, "Vary")) != NULL) { + ap_table_setn(r->err_headers_out, "Variant-Vary", sub_vary); + + /* Move the subreq Vary header into the main request to + * prevent having two Vary headers in the response, which + * would be legal but strange. + */ + ap_table_setn(r->err_headers_out, "Vary", sub_vary); + ap_table_unset(sub_req->err_headers_out, "Vary"); + } + + ap_table_setn(r->err_headers_out, "Content-Location", + ap_pstrdup(r->pool, variant->file_name)); + + set_neg_headers(r, neg, alg_choice); /* add Alternates and Vary */ + + /* Still to do by caller: add Expires */ + + return 0; +} + +/**************************************************************** + * + * Executive... + */ + +static int do_negotiation(request_rec *r, negotiation_state *neg, + var_rec **bestp, int prefer_scripts) +{ + var_rec *avail_recs = (var_rec *) neg->avail_vars->elts; + int alg_result; /* result of variant selection algorithm */ + int res; + int j; + + /* Decide if resource is transparently negotiable */ + + /* GET or HEAD? (HEAD has same method number as GET) */ + if (r->method_number == M_GET) { + + /* maybe this should be configurable, see also the comment + * about recursive type maps in setup_choice_response() + */ + neg->is_transparent = 1; + + /* We can't be transparent if we are a map file in the middle + * of the request URI. + */ + if (r->path_info && *r->path_info) + neg->is_transparent = 0; + + for (j = 0; j < neg->avail_vars->nelts; ++j) { + var_rec *variant = &avail_recs[j]; + + /* We can't be transparent, because of internal + * assumptions in best_match(), if there is a + * non-neighboring variant. We can have a non-neighboring + * variant when processing a type map. + */ + if (strchr(variant->file_name, '/')) + neg->is_transparent = 0; + } + } + + if (neg->is_transparent) { + parse_negotiate_header(r, neg); + } + else { /* configure negotiation on non-transparent resource */ + neg->may_choose = 1; + } + + maybe_add_default_accepts(neg, prefer_scripts); + + alg_result = best_match(neg, bestp); + + /* alg_result is one of + * alg_choice: a best variant is chosen + * alg_list: no best variant is chosen + */ + + if (alg_result == alg_list) { + /* send a list response or NOT_ACCEPTABLE error response */ + + neg->send_alternates = 1; /* always include Alternates header */ + set_neg_headers(r, neg, alg_result); + store_variant_list(r, neg); + + if (neg->is_transparent && neg->ua_supports_trans) { + /* XXX todo: expires? cachability? */ + + /* Some HTTP/1.0 clients are known to choke when they get + * a 300 (multiple choices) response without a Location + * header. However the 300 code response we are are about + * to generate will only reach 1.0 clients which support + * transparent negotiation, and they should be OK. The + * response should never reach older 1.0 clients, even if + * we have CacheNegotiatedDocs enabled, because no 1.0 + * proxy cache (we know of) will cache and return 300 + * responses (they certainly won't if they conform to the + * HTTP/1.0 specification). + */ + return MULTIPLE_CHOICES; + } + + if (!*bestp) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "no acceptable variant: %s", r->filename); + return NOT_ACCEPTABLE; + } + } + + /* Variant selection chose a variant */ + + /* XXX todo: merge the two cases in the if statement below */ + if (neg->is_transparent) { + + if ((res = setup_choice_response(r, neg, *bestp)) != 0) { + return res; /* return if error */ + } + } + else { + set_neg_headers(r, neg, alg_result); + } + + /* Make sure caching works - Vary should handle HTTP/1.1, but for + * HTTP/1.0, we can't allow caching at all. + */ + + /* XXX: Note that we only set r->no_cache to 1, which causes + * Expires: <now> to be added, when responding to a HTTP/1.0 + * client. If we return the response to a 1.1 client, we do not + * add Expires <now>, because doing so would degrade 1.1 cache + * performance by preventing re-use of the response without prior + * revalidation. On the other hand, if the 1.1 client is a proxy + * which was itself contacted by a 1.0 client, or a proxy cache + * which can be contacted later by 1.0 clients, then we currently + * rely on this 1.1 proxy to add the Expires: <now> when it + * forwards the response. + * + * XXX: TODO: Find out if the 1.1 spec requires proxies and + * tunnels to add Expires: <now> when forwarding the response to + * 1.0 clients. I (kh) recall it is rather vague on this point. + * Testing actual 1.1 proxy implementations would also be nice. If + * Expires: <now> is not added by proxies then we need to always + * include Expires: <now> ourselves to ensure correct caching, but + * this would degrade HTTP/1.1 cache efficiency unless we also add + * Cache-Control: max-age=N, which we currently don't. + * + * Roy: No, we are not going to screw over HTTP future just to + * ensure that people who can't be bothered to upgrade their + * clients will always receive perfect server-side negotiation. + * Hell, those clients are sending bogus accept headers anyway. + * + * Manual setting of cache-control/expires always overrides this + * automated kluge, on purpose. + */ + + if ((!do_cache_negotiated_docs(r->server) + && (r->proto_num < HTTP_VERSION(1,1))) + && neg->count_multiviews_variants != 1) { + r->no_cache = 1; + } + + return OK; +} + +static int handle_map_file(request_rec *r) +{ + negotiation_state *neg = parse_accept_headers(r); + var_rec *best; + int res; + + char *udir; + + if ((res = read_type_map(neg, r))) { + return res; + } + + res = do_negotiation(r, neg, &best, 0); + if (res != 0) return res; + + if (r->path_info && *r->path_info) { + r->uri[ap_find_path_info(r->uri, r->path_info)] = '\0'; + } + udir = ap_make_dirstr_parent(r->pool, r->uri); + udir = ap_escape_uri(r->pool, udir); + ap_internal_redirect(ap_pstrcat(r->pool, udir, best->file_name, + r->path_info, NULL), r); + return OK; +} + +static int handle_multi(request_rec *r) +{ + negotiation_state *neg; + var_rec *best, *avail_recs; + request_rec *sub_req; + int res; + int j; + + if (r->finfo.st_mode != 0 || !(ap_allow_options(r) & OPT_MULTI)) { + return DECLINED; + } + + neg = parse_accept_headers(r); + + if ((res = read_types_multi(neg))) { + return_from_multi: + /* free all allocated memory from subrequests */ + avail_recs = (var_rec *) neg->avail_vars->elts; + for (j = 0; j < neg->avail_vars->nelts; ++j) { + var_rec *variant = &avail_recs[j]; + if (variant->sub_req) { + ap_destroy_sub_req(variant->sub_req); + } + } + return res; + } + if (neg->avail_vars->nelts == 0) { + return DECLINED; + } + + res = do_negotiation(r, neg, &best, + (r->method_number != M_GET) || r->args || + (r->path_info && *r->path_info)); + if (res != 0) + goto return_from_multi; + + if (!(sub_req = best->sub_req)) { + /* We got this out of a map file, so we don't actually have + * a sub_req structure yet. Get one now. + */ + + sub_req = ap_sub_req_lookup_file(best->file_name, r); + if (sub_req->status != HTTP_OK) { + res = sub_req->status; + ap_destroy_sub_req(sub_req); + goto return_from_multi; + } + } + + /* BLECH --- don't multi-resolve non-ordinary files */ + + if (!S_ISREG(sub_req->finfo.st_mode)) { + res = NOT_FOUND; + goto return_from_multi; + } + + /* Otherwise, use it. */ + + /* now do a "fast redirect" ... promote the sub_req into the main req */ + /* We need to tell POOL_DEBUG that we're guaranteeing that sub_req->pool + * will exist as long as r->pool. Otherwise we run into troubles because + * some values in this request will be allocated in r->pool, and others in + * sub_req->pool. + */ + ap_pool_join(r->pool, sub_req->pool); + r->mtime = 0; /* reset etag info for subrequest */ + /* XXX: uri/args/path_info are all retained from the original request. + * It is entirely possible, but not common, for a handler to choke + * on some expectation based on the uri (or more commonly, args) that + * the file subrequest was prepared to handle, but a lookup_uri would + * have considered an error. This leaves an improbable possibility + * that the user might fail a mod_dir request later, and the server + * may respond with a mod_autoindex response. However, this has been + * the behavior throughout much of the Apache 1.3 era with minimal + * side effects, mostly caused by obscure configuration bugs. + * r->uri = sub_req->uri; + * r->args = sub_req->args; + * r->path_info = sub_req->path_info; + */ + r->filename = sub_req->filename; + r->handler = sub_req->handler; + r->content_type = sub_req->content_type; + r->content_encoding = sub_req->content_encoding; + r->content_languages = sub_req->content_languages; + r->content_language = sub_req->content_language; + r->finfo = sub_req->finfo; + r->per_dir_config = sub_req->per_dir_config; + /* copy output headers from subrequest, but leave negotiation headers */ + r->notes = ap_overlay_tables(r->pool, sub_req->notes, r->notes); + r->headers_out = ap_overlay_tables(r->pool, sub_req->headers_out, + r->headers_out); + r->err_headers_out = ap_overlay_tables(r->pool, sub_req->err_headers_out, + r->err_headers_out); + r->subprocess_env = ap_overlay_tables(r->pool, sub_req->subprocess_env, + r->subprocess_env); + avail_recs = (var_rec *) neg->avail_vars->elts; + for (j = 0; j < neg->avail_vars->nelts; ++j) { + var_rec *variant = &avail_recs[j]; + if (variant != best && variant->sub_req) { + ap_destroy_sub_req(variant->sub_req); + } + } + return OK; +} + +/********************************************************************** + * There is a problem with content-encoding, as some clients send and + * expect an x- token (e.g. x-gzip) while others expect the plain token + * (i.e. gzip). To try and deal with this as best as possible we do + * the following: if the client sent an Accept-Encoding header and it + * contains a plain token corresponding to the content encoding of the + * response, then set content encoding using the plain token. Else if + * the A-E header contains the x- token use the x- token in the C-E + * header. Else don't do anything. + * + * Note that if no A-E header was sent, or it does not contain a token + * compatible with the final content encoding, then the token in the + * C-E header will be whatever was specified in the AddEncoding + * directive. + */ +static int fix_encoding(request_rec *r) +{ + const char *enc = r->content_encoding; + char *x_enc = NULL; + array_header *accept_encodings; + accept_rec *accept_recs; + int i; + + if (!enc || !*enc) { + return DECLINED; + } + + if (enc[0] == 'x' && enc[1] == '-') { + enc += 2; + } + + if ((accept_encodings = do_header_line(r->pool, + ap_table_get(r->headers_in, "Accept-Encoding"))) == NULL) { + return DECLINED; + } + + accept_recs = (accept_rec *) accept_encodings->elts; + + for (i = 0; i < accept_encodings->nelts; ++i) { + char *name = accept_recs[i].name; + + if (!strcmp(name, enc)) { + r->content_encoding = name; + return OK; + } + + if (name[0] == 'x' && name[1] == '-' && !strcmp(name+2, enc)) { + x_enc = name; + } + } + + if (x_enc) { + r->content_encoding = x_enc; + return OK; + } + + return DECLINED; +} + +static const handler_rec negotiation_handlers[] = +{ + {MAP_FILE_MAGIC_TYPE, handle_map_file}, + {"type-map", handle_map_file}, + {NULL} +}; + +module MODULE_VAR_EXPORT negotiation_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_neg_dir_config, /* dir config creator */ + merge_neg_dir_configs, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + negotiation_cmds, /* command table */ + negotiation_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + handle_multi, /* type_checker */ + fix_encoding, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_rewrite.c b/APACHE_1_3_42/src/modules/standard/mod_rewrite.c new file mode 100644 index 0000000000..35db954fb6 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_rewrite.c @@ -0,0 +1,4494 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/* _ _ _ +** _ __ ___ ___ __| | _ __ _____ ___ __(_) |_ ___ +** | '_ ` _ \ / _ \ / _` | | '__/ _ \ \ /\ / / '__| | __/ _ \ +** | | | | | | (_) | (_| | | | | __/\ V V /| | | | || __/ +** |_| |_| |_|\___/ \__,_|___|_| \___| \_/\_/ |_| |_|\__\___| +** |_____| +** +** URL Rewriting Module +** +** This module uses a rule-based rewriting engine (based on a +** regular-expression parser) to rewrite requested URLs on the fly. +** +** It supports an unlimited number of additional rule conditions (which can +** operate on a lot of variables, even on HTTP headers) for granular +** matching and even external database lookups (either via plain text +** tables, DBM hash files or even external processes) for advanced URL +** substitution. +** +** It operates on the full URLs (including the PATH_INFO part) both in +** per-server context (httpd.conf) and per-dir context (.htaccess) and even +** can generate QUERY_STRING parts on result. The rewriting result finally +** can lead to internal subprocessing, external request redirection or even +** to internal proxy throughput. +** +** This module was originally written in April 1996 and +** gifted exclusively to the The Apache Group in July 1997 by +** +** Ralf S. Engelschall +** rse@engelschall.com +** www.engelschall.com +*/ + + +#include "mod_rewrite.h" + +#ifndef NO_WRITEV +#ifndef NETWARE +#include <sys/types.h> +#endif +#include <sys/uio.h> +#endif + +#ifdef NETWARE +#include <nwsemaph.h> +static LONG locking_sem = 0; +#endif + +/* +** +-------------------------------------------------------+ +** | | +** | static module configuration +** | | +** +-------------------------------------------------------+ +*/ + + +/* +** Our interface to the Apache server kernel: +** +** o Runtime logic of a request is as following: +** while(request or subrequest) +** foreach(stage #0...#9) +** foreach(module) (**) +** try to run hook +** +** o the order of modules at (**) is the inverted order as +** given in the "Configuration" file, i.e. the last module +** specified is the first one called for each hook! +** The core module is always the last! +** +** o there are two different types of result checking and +** continue processing: +** for hook #0,#1,#4,#5,#6,#8: +** hook run loop stops on first modules which gives +** back a result != DECLINED, i.e. it usually returns OK +** which says "OK, module has handled this _stage_" and for #1 +** this have not to mean "Ok, the filename is now valid". +** for hook #2,#3,#7,#9: +** all hooks are run, independend of result +** +** o at the last stage, the core module always +** - says "BAD_REQUEST" if r->filename does not begin with "/" +** - prefix URL with document_root or replaced server_root +** with document_root and sets r->filename +** - always return a "OK" independed if the file really exists +** or not! +*/ + + /* The section for the Configure script: + * MODULE-DEFINITION-START + * Name: rewrite_module + * ConfigStart + . ./helpers/find-dbm-lib + if [ "x$found_dbm" = "x1" ]; then + echo " enabling DBM support for mod_rewrite" + else + echo " disabling DBM support for mod_rewrite" + echo " (perhaps you need to add -ldbm, -lndbm or -lgdbm to EXTRA_LIBS)" + CFLAGS="$CFLAGS -DNO_DBM_REWRITEMAP" + fi + * ConfigEnd + * MODULE-DEFINITION-END + */ + + /* the table of commands we provide */ +static const command_rec command_table[] = { + { "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO, FLAG, + "On or Off to enable or disable (default) the whole rewriting engine" }, + { "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO, ITERATE, + "List of option strings to set" }, + { "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO, TAKE1, + "the base URL of the per-directory context" }, + { "RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO, RAW_ARGS, + "an input string and a to be applied regexp-pattern" }, + { "RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO, RAW_ARGS, + "an URL-applied regexp-pattern and a substitution URL" }, + { "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF, TAKE2, + "a mapname and a filename" }, + { "RewriteLock", cmd_rewritelock, NULL, RSRC_CONF, TAKE1, + "the filename of a lockfile used for inter-process synchronization"}, + { "RewriteLog", cmd_rewritelog, NULL, RSRC_CONF, TAKE1, + "the filename of the rewriting logfile" }, + { "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF, TAKE1, + "the level of the rewriting logfile verbosity " + "(0=none, 1=std, .., 9=max)" }, + { NULL } +}; + + /* the table of content handlers we provide */ +static const handler_rec handler_table[] = { + { "redirect-handler", handler_redirect }, + { NULL } +}; + + /* the main config structure */ +module MODULE_VAR_EXPORT rewrite_module = { + STANDARD_MODULE_STUFF, + init_module, /* module initializer */ + config_perdir_create, /* create per-dir config structures */ + config_perdir_merge, /* merge per-dir config structures */ + config_server_create, /* create per-server config structures */ + config_server_merge, /* merge per-server config structures */ + command_table, /* table of config file commands */ + handler_table, /* [#8] MIME-typed-dispatched handlers */ + hook_uri2file, /* [#1] URI to filename translation */ + NULL, /* [#4] validate user id from request */ + NULL, /* [#5] check if the user is ok _here_ */ + NULL, /* [#3] check access by host address */ + hook_mimetype, /* [#6] determine MIME type */ + hook_fixup, /* [#7] pre-run fixups */ + NULL, /* [#9] log a transaction */ + NULL, /* [#2] header parser */ + init_child, /* child_init */ + NULL, /* child_exit */ + NULL /* [#0] post read-request */ +}; + + /* the cache */ +static cache *cachep; + + /* whether proxy module is available or not */ +static int proxy_available; + +static char *lockname; +static int lockfd = -1; + +/* +** +-------------------------------------------------------+ +** | | +** | configuration directive handling +** | | +** +-------------------------------------------------------+ +*/ + +/* +** +** per-server configuration structure handling +** +*/ + +static void *config_server_create(pool *p, server_rec *s) +{ + rewrite_server_conf *a; + + a = (rewrite_server_conf *)ap_pcalloc(p, sizeof(rewrite_server_conf)); + + a->state = ENGINE_DISABLED; + a->options = OPTION_NONE; + a->rewritelogfile = NULL; + a->rewritelogfp = -1; + a->rewriteloglevel = 0; + a->rewritemaps = ap_make_array(p, 2, sizeof(rewritemap_entry)); + a->rewriteconds = ap_make_array(p, 2, sizeof(rewritecond_entry)); + a->rewriterules = ap_make_array(p, 2, sizeof(rewriterule_entry)); + a->server = s; + a->redirect_limit = 0; /* unset (use default) */ + + return (void *)a; +} + +static void *config_server_merge(pool *p, void *basev, void *overridesv) +{ + rewrite_server_conf *a, *base, *overrides; + + a = (rewrite_server_conf *)ap_pcalloc(p, sizeof(rewrite_server_conf)); + base = (rewrite_server_conf *)basev; + overrides = (rewrite_server_conf *)overridesv; + + a->state = overrides->state; + a->options = overrides->options; + a->server = overrides->server; + a->redirect_limit = overrides->redirect_limit + ? overrides->redirect_limit + : base->redirect_limit; + + if (a->options & OPTION_INHERIT) { + /* + * local directives override + * and anything else is inherited + */ + a->rewriteloglevel = overrides->rewriteloglevel != 0 + ? overrides->rewriteloglevel + : base->rewriteloglevel; + a->rewritelogfile = overrides->rewritelogfile != NULL + ? overrides->rewritelogfile + : base->rewritelogfile; + a->rewritelogfp = overrides->rewritelogfp != -1 + ? overrides->rewritelogfp + : base->rewritelogfp; + a->rewritemaps = ap_append_arrays(p, overrides->rewritemaps, + base->rewritemaps); + a->rewriteconds = ap_append_arrays(p, overrides->rewriteconds, + base->rewriteconds); + a->rewriterules = ap_append_arrays(p, overrides->rewriterules, + base->rewriterules); + } + else { + /* + * local directives override + * and anything else gets defaults + */ + a->rewriteloglevel = overrides->rewriteloglevel; + a->rewritelogfile = overrides->rewritelogfile; + a->rewritelogfp = overrides->rewritelogfp; + a->rewritemaps = overrides->rewritemaps; + a->rewriteconds = overrides->rewriteconds; + a->rewriterules = overrides->rewriterules; + } + + return (void *)a; +} + + +/* +** +** per-directory configuration structure handling +** +*/ + +static void *config_perdir_create(pool *p, char *path) +{ + rewrite_perdir_conf *a; + + a = (rewrite_perdir_conf *)ap_pcalloc(p, sizeof(rewrite_perdir_conf)); + + a->state = ENGINE_DISABLED; + a->options = OPTION_NONE; + a->baseurl = NULL; + a->rewriteconds = ap_make_array(p, 2, sizeof(rewritecond_entry)); + a->rewriterules = ap_make_array(p, 2, sizeof(rewriterule_entry)); + a->redirect_limit = 0; /* unset (use server config) */ + + if (path == NULL) { + a->directory = NULL; + } + else { + /* make sure it has a trailing slash */ + if (path[strlen(path)-1] == '/') { + a->directory = ap_pstrdup(p, path); + } + else { + a->directory = ap_pstrcat(p, path, "/", NULL); + } + } + + return (void *)a; +} + +static void *config_perdir_merge(pool *p, void *basev, void *overridesv) +{ + rewrite_perdir_conf *a, *base, *overrides; + + a = (rewrite_perdir_conf *)ap_pcalloc(p, + sizeof(rewrite_perdir_conf)); + base = (rewrite_perdir_conf *)basev; + overrides = (rewrite_perdir_conf *)overridesv; + + a->state = overrides->state; + a->options = overrides->options; + a->directory = overrides->directory; + a->baseurl = overrides->baseurl; + a->redirect_limit = overrides->redirect_limit + ? overrides->redirect_limit + : base->redirect_limit; + + if (a->options & OPTION_INHERIT) { + a->rewriteconds = ap_append_arrays(p, overrides->rewriteconds, + base->rewriteconds); + a->rewriterules = ap_append_arrays(p, overrides->rewriterules, + base->rewriterules); + } + else { + a->rewriteconds = overrides->rewriteconds; + a->rewriterules = overrides->rewriterules; + } + + return (void *)a; +} + + +/* +** +** the configuration commands +** +*/ + +static const char *cmd_rewriteengine(cmd_parms *cmd, + rewrite_perdir_conf *dconf, int flag) +{ + rewrite_server_conf *sconf; + + sconf = + (rewrite_server_conf *)ap_get_module_config(cmd->server->module_config, + &rewrite_module); + + if (cmd->path == NULL) { /* is server command */ + sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED); + } + else /* is per-directory command */ { + dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED); + } + + return NULL; +} + +static const char *cmd_rewriteoptions(cmd_parms *cmd, + void *in_dconf, const char *option) +{ + int options = 0, limit = 0; + char *w; + + while (*option) { + w = ap_getword_conf(cmd->pool, &option); + + if (!strcasecmp(w, "inherit")) { + options |= OPTION_INHERIT; + } + else if (!strncasecmp(w, "MaxRedirects=", 13)) { + limit = atoi(&w[13]); + if (limit <= 0) { + return "RewriteOptions: MaxRedirects takes a number greater " + "than zero."; + } + } + else if (!strcasecmp(w, "MaxRedirects")) { /* be nice */ + return "RewriteOptions: MaxRedirects has the format MaxRedirects" + "=n."; + } + else { + return ap_pstrcat(cmd->pool, "RewriteOptions: unknown option '", + w, "'", NULL); + } + } + + /* put it into the appropriate config */ + if (cmd->path == NULL) { /* is server command */ + rewrite_server_conf *conf = + ap_get_module_config(cmd->server->module_config, + &rewrite_module); + + conf->options |= options; + conf->redirect_limit = limit; + } + else { /* is per-directory command */ + rewrite_perdir_conf *conf = in_dconf; + + conf->options |= options; + conf->redirect_limit = limit; + } + + return NULL; +} + +static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, char *a1) +{ + rewrite_server_conf *sconf; + + sconf = (rewrite_server_conf *) + ap_get_module_config(cmd->server->module_config, &rewrite_module); + + sconf->rewritelogfile = a1; + + return NULL; +} + +static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, char *a1) +{ + rewrite_server_conf *sconf; + + sconf = (rewrite_server_conf *) + ap_get_module_config(cmd->server->module_config, &rewrite_module); + + sconf->rewriteloglevel = atoi(a1); + + return NULL; +} + +static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, char *a1, + char *a2) +{ + rewrite_server_conf *sconf; + rewritemap_entry *new; + struct stat st; + + sconf = (rewrite_server_conf *) + ap_get_module_config(cmd->server->module_config, &rewrite_module); + + new = ap_push_array(sconf->rewritemaps); + + new->name = a1; + new->func = NULL; + if (strncmp(a2, "txt:", 4) == 0) { + new->type = MAPTYPE_TXT; + new->datafile = a2+4; + new->checkfile = a2+4; + new->cachename = ap_psprintf(cmd->pool, "%pp:%s", + (void *)cmd->server, a1); + } + else if (strncmp(a2, "rnd:", 4) == 0) { + new->type = MAPTYPE_RND; + new->datafile = a2+4; + new->checkfile = a2+4; + new->cachename = ap_psprintf(cmd->pool, "%pp:%s", + (void *)cmd->server, a1); + } + else if (strncmp(a2, "dbm:", 4) == 0) { +#ifndef NO_DBM_REWRITEMAP + new->type = MAPTYPE_DBM; + new->datafile = a2+4; + new->checkfile = ap_pstrcat(cmd->pool, a2+4, NDBM_FILE_SUFFIX, NULL); + new->cachename = ap_psprintf(cmd->pool, "%pp:%s", + (void *)cmd->server, a1); +#else + return ap_pstrdup(cmd->pool, "RewriteMap: cannot use NDBM mapfile, " + "because no NDBM support is compiled in"); +#endif + } + else if (strncmp(a2, "prg:", 4) == 0) { + new->type = MAPTYPE_PRG; + new->datafile = a2+4; + new->checkfile = a2+4; + new->cachename = NULL; + } + else if (strncmp(a2, "int:", 4) == 0) { + new->type = MAPTYPE_INT; + new->datafile = NULL; + new->checkfile = NULL; + new->cachename = NULL; + if (strcmp(a2+4, "tolower") == 0) { + new->func = rewrite_mapfunc_tolower; + } + else if (strcmp(a2+4, "toupper") == 0) { + new->func = rewrite_mapfunc_toupper; + } + else if (strcmp(a2+4, "escape") == 0) { + new->func = rewrite_mapfunc_escape; + } + else if (strcmp(a2+4, "unescape") == 0) { + new->func = rewrite_mapfunc_unescape; + } + else if (sconf->state == ENGINE_ENABLED) { + return ap_pstrcat(cmd->pool, "RewriteMap: internal map not found:", + a2+4, NULL); + } + } + else { + new->type = MAPTYPE_TXT; + new->datafile = a2; + new->checkfile = a2; + new->cachename = ap_psprintf(cmd->pool, "%pp:%s", + (void *)cmd->server, a1); + } + new->fpin = -1; + new->fpout = -1; + + if (new->checkfile && (sconf->state == ENGINE_ENABLED) + && (stat(new->checkfile, &st) == -1)) { + return ap_pstrcat(cmd->pool, + "RewriteMap: map file or program not found:", + new->checkfile, NULL); + } + + return NULL; +} + +static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, char *a1) +{ + const char *error; + + if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL) + return error; + + lockname = a1; + + return NULL; +} + +static const char *cmd_rewritebase(cmd_parms *cmd, rewrite_perdir_conf *dconf, + char *a1) +{ + if (cmd->path == NULL || dconf == NULL) { + return "RewriteBase: only valid in per-directory config files"; + } + if (a1[0] == '\0') { + return "RewriteBase: empty URL not allowed"; + } + if (a1[0] != '/') { + return "RewriteBase: argument is not a valid URL"; + } + + dconf->baseurl = a1; + + return NULL; +} + +static const char *cmd_rewritecond(cmd_parms *cmd, rewrite_perdir_conf *dconf, + char *str) +{ + rewrite_server_conf *sconf; + rewritecond_entry *new; + regex_t *regexp; + char *a1; + char *a2; + char *a3; + char *cp; + const char *err; + int rc; + + sconf = (rewrite_server_conf *) + ap_get_module_config(cmd->server->module_config, &rewrite_module); + + /* make a new entry in the internal temporary rewrite rule list */ + if (cmd->path == NULL) { /* is server command */ + new = ap_push_array(sconf->rewriteconds); + } + else { /* is per-directory command */ + new = ap_push_array(dconf->rewriteconds); + } + + /* parse the argument line ourself */ + if (parseargline(str, &a1, &a2, &a3)) { + return ap_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str, + "'\n", NULL); + } + + /* arg1: the input string */ + new->input = ap_pstrdup(cmd->pool, a1); + + /* arg3: optional flags field + (this have to be first parsed, because we need to + know if the regex should be compiled with ICASE!) */ + new->flags = CONDFLAG_NONE; + if (a3 != NULL) { + if ((err = cmd_rewritecond_parseflagfield(cmd->pool, new, + a3)) != NULL) { + return err; + } + } + + /* arg2: the pattern + try to compile the regexp to test if is ok */ + cp = a2; + if (cp[0] == '!') { + new->flags |= CONDFLAG_NOTMATCH; + cp++; + } + + /* now be careful: Under the POSIX regex library + we can compile the pattern for case-insensitive matching, + under the old V8 library we have to do it self via a hack */ + if (new->flags & CONDFLAG_NOCASE) { + rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED|REG_ICASE)) + == NULL); + } + else { + rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED)) == NULL); + } + if (rc) { + return ap_pstrcat(cmd->pool, + "RewriteCond: cannot compile regular expression '", + a2, "'\n", NULL); + } + + new->pattern = ap_pstrdup(cmd->pool, cp); + new->regexp = regexp; + + return NULL; +} + +static const char *cmd_rewritecond_parseflagfield(pool *p, + rewritecond_entry *cfg, + char *str) +{ + char *cp; + char *cp1; + char *cp2; + char *cp3; + char *key; + char *val; + const char *err; + + if (str[0] != '[' || str[strlen(str)-1] != ']') { + return "RewriteCond: bad flag delimiters"; + } + + cp = str+1; + str[strlen(str)-1] = ','; /* for simpler parsing */ + for ( ; *cp != '\0'; ) { + /* skip whitespaces */ + for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++) + ; + if (*cp == '\0') { + break; + } + cp1 = cp; + if ((cp2 = strchr(cp, ',')) != NULL) { + cp = cp2+1; + for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--) + ; + *cp2 = '\0'; + if ((cp3 = strchr(cp1, '=')) != NULL) { + *cp3 = '\0'; + key = cp1; + val = cp3+1; + } + else { + key = cp1; + val = ""; + } + if ((err = cmd_rewritecond_setflag(p, cfg, key, val)) != NULL) { + return err; + } + } + else { + break; + } + } + + return NULL; +} + +static const char *cmd_rewritecond_setflag(pool *p, rewritecond_entry *cfg, + char *key, char *val) +{ + if ( strcasecmp(key, "nocase") == 0 + || strcasecmp(key, "NC") == 0 ) { + cfg->flags |= CONDFLAG_NOCASE; + } + else if ( strcasecmp(key, "ornext") == 0 + || strcasecmp(key, "OR") == 0 ) { + cfg->flags |= CONDFLAG_ORNEXT; + } + else { + return ap_pstrcat(p, "RewriteCond: unknown flag '", key, "'\n", NULL); + } + return NULL; +} + +static const char *cmd_rewriterule(cmd_parms *cmd, rewrite_perdir_conf *dconf, + char *str) +{ + rewrite_server_conf *sconf; + rewriterule_entry *new; + regex_t *regexp; + char *a1; + char *a2; + char *a3; + char *cp; + const char *err; + int mode; + + sconf = (rewrite_server_conf *) + ap_get_module_config(cmd->server->module_config, &rewrite_module); + + /* make a new entry in the internal rewrite rule list */ + if (cmd->path == NULL) { /* is server command */ + new = ap_push_array(sconf->rewriterules); + } + else { /* is per-directory command */ + new = ap_push_array(dconf->rewriterules); + } + + /* parse the argument line ourself */ + if (parseargline(str, &a1, &a2, &a3)) { + return ap_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str, + "'\n", NULL); + } + + /* arg3: optional flags field */ + new->forced_mimetype = NULL; + new->forced_responsecode = HTTP_MOVED_TEMPORARILY; + new->flags = RULEFLAG_NONE; + new->env[0] = NULL; + new->skip = 0; + if (a3 != NULL) { + if ((err = cmd_rewriterule_parseflagfield(cmd->pool, new, + a3)) != NULL) { + return err; + } + } + + /* arg1: the pattern + * try to compile the regexp to test if is ok + */ + cp = a1; + if (cp[0] == '!') { + new->flags |= RULEFLAG_NOTMATCH; + cp++; + } + mode = REG_EXTENDED; + if (new->flags & RULEFLAG_NOCASE) { + mode |= REG_ICASE; + } + if ((regexp = ap_pregcomp(cmd->pool, cp, mode)) == NULL) { + return ap_pstrcat(cmd->pool, + "RewriteRule: cannot compile regular expression '", + a1, "'\n", NULL); + } + new->pattern = ap_pstrdup(cmd->pool, cp); + new->regexp = regexp; + + /* arg2: the output string + * replace the $<N> by \<n> which is needed by the currently + * used Regular Expression library + */ + new->output = ap_pstrdup(cmd->pool, a2); + + /* now, if the server or per-dir config holds an + * array of RewriteCond entries, we take it for us + * and clear the array + */ + if (cmd->path == NULL) { /* is server command */ + new->rewriteconds = sconf->rewriteconds; + sconf->rewriteconds = ap_make_array(cmd->pool, 2, + sizeof(rewritecond_entry)); + } + else { /* is per-directory command */ + new->rewriteconds = dconf->rewriteconds; + dconf->rewriteconds = ap_make_array(cmd->pool, 2, + sizeof(rewritecond_entry)); + } + + return NULL; +} + +static const char *cmd_rewriterule_parseflagfield(pool *p, + rewriterule_entry *cfg, + char *str) +{ + char *cp; + char *cp1; + char *cp2; + char *cp3; + char *key; + char *val; + const char *err; + + if (str[0] != '[' || str[strlen(str)-1] != ']') { + return "RewriteRule: bad flag delimiters"; + } + + cp = str+1; + str[strlen(str)-1] = ','; /* for simpler parsing */ + for ( ; *cp != '\0'; ) { + /* skip whitespaces */ + for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++) + ; + if (*cp == '\0') { + break; + } + cp1 = cp; + if ((cp2 = strchr(cp, ',')) != NULL) { + cp = cp2+1; + for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--) + ; + *cp2 = '\0'; + if ((cp3 = strchr(cp1, '=')) != NULL) { + *cp3 = '\0'; + key = cp1; + val = cp3+1; + } + else { + key = cp1; + val = ""; + } + if ((err = cmd_rewriterule_setflag(p, cfg, key, val)) != NULL) { + return err; + } + } + else { + break; + } + } + + return NULL; +} + +static const char *cmd_rewriterule_setflag(pool *p, rewriterule_entry *cfg, + char *key, char *val) +{ + int status = 0; + int i; + + if ( strcasecmp(key, "redirect") == 0 + || strcasecmp(key, "R") == 0 ) { + cfg->flags |= RULEFLAG_FORCEREDIRECT; + if (strlen(val) > 0) { + if (strcasecmp(val, "permanent") == 0) { + status = HTTP_MOVED_PERMANENTLY; + } + else if (strcasecmp(val, "temp") == 0) { + status = HTTP_MOVED_TEMPORARILY; + } + else if (strcasecmp(val, "seeother") == 0) { + status = HTTP_SEE_OTHER; + } + else if (ap_isdigit(*val)) { + status = atoi(val); + } + if (!ap_is_HTTP_REDIRECT(status)) { + return "RewriteRule: invalid HTTP response code " + "for flag 'R'"; + } + cfg->forced_responsecode = status; + } + } + else if ( strcasecmp(key, "noescape") == 0 + || strcasecmp(key, "NE") == 0 ) { + cfg->flags |= RULEFLAG_NOESCAPE; + } + else if ( strcasecmp(key, "last") == 0 + || strcasecmp(key, "L") == 0 ) { + cfg->flags |= RULEFLAG_LASTRULE; + } + else if ( strcasecmp(key, "next") == 0 + || strcasecmp(key, "N") == 0 ) { + cfg->flags |= RULEFLAG_NEWROUND; + } + else if ( strcasecmp(key, "chain") == 0 + || strcasecmp(key, "C") == 0 ) { + cfg->flags |= RULEFLAG_CHAIN; + } + else if ( strcasecmp(key, "type") == 0 + || strcasecmp(key, "T") == 0 ) { + cfg->forced_mimetype = ap_pstrdup(p, val); + ap_str_tolower(cfg->forced_mimetype); + } + else if ( strcasecmp(key, "env") == 0 + || strcasecmp(key, "E") == 0 ) { + for (i = 0; (cfg->env[i] != NULL) && (i < MAX_ENV_FLAGS); i++) + ; + if (i < MAX_ENV_FLAGS) { + cfg->env[i] = ap_pstrdup(p, val); + cfg->env[i+1] = NULL; + } + else { + return "RewriteRule: too many environment flags 'E'"; + } + } + else if ( strcasecmp(key, "nosubreq") == 0 + || strcasecmp(key, "NS") == 0 ) { + cfg->flags |= RULEFLAG_IGNOREONSUBREQ; + } + else if ( strcasecmp(key, "proxy") == 0 + || strcasecmp(key, "P") == 0 ) { + cfg->flags |= RULEFLAG_PROXY; + } + else if ( strcasecmp(key, "passthrough") == 0 + || strcasecmp(key, "PT") == 0 ) { + cfg->flags |= RULEFLAG_PASSTHROUGH; + } + else if ( strcasecmp(key, "skip") == 0 + || strcasecmp(key, "S") == 0 ) { + cfg->skip = atoi(val); + } + else if ( strcasecmp(key, "forbidden") == 0 + || strcasecmp(key, "F") == 0 ) { + cfg->flags |= RULEFLAG_FORBIDDEN; + } + else if ( strcasecmp(key, "gone") == 0 + || strcasecmp(key, "G") == 0 ) { + cfg->flags |= RULEFLAG_GONE; + } + else if ( strcasecmp(key, "qsappend") == 0 + || strcasecmp(key, "QSA") == 0 ) { + cfg->flags |= RULEFLAG_QSAPPEND; + } + else if ( strcasecmp(key, "nocase") == 0 + || strcasecmp(key, "NC") == 0 ) { + cfg->flags |= RULEFLAG_NOCASE; + } + else { + return ap_pstrcat(p, "RewriteRule: unknown flag '", key, "'\n", NULL); + } + return NULL; +} + + +/* +** +** Global Module Initialization +** [called from read_config() after all +** config commands were already called] +** +*/ + +static void init_module(server_rec *s, pool *p) +{ + /* check if proxy module is available */ + proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL); + + /* create the rewriting lockfile in the parent */ + rewritelock_create(s, p); + ap_register_cleanup(p, (void *)s, rewritelock_remove, ap_null_cleanup); + + /* step through the servers and + * - open each rewriting logfile + * - open the RewriteMap prg:xxx programs + */ + for (; s; s = s->next) { + open_rewritelog(s, p); + run_rewritemap_programs(s, p); + } +} + + +/* +** +** Per-Child Module Initialization +** [called after a child process is spawned] +** +*/ + +static void init_child(server_rec *s, pool *p) +{ + /* open the rewriting lockfile */ + rewritelock_open(s, p); + + /* create the lookup cache */ + cachep = init_cache(p); +} + + +/* +** +-------------------------------------------------------+ +** | | +** | runtime hooks +** | | +** +-------------------------------------------------------+ +*/ + +/* +** +** URI-to-filename hook +** +** [used for the rewriting engine triggered by +** the per-server 'RewriteRule' directives] +** +*/ + +static int hook_uri2file(request_rec *r) +{ + void *sconf; + rewrite_server_conf *conf; + const char *var; + const char *thisserver; + char *thisport; + const char *thisurl; + char buf[512]; + char docroot[512]; + const char *ccp; + unsigned int port; + int rulestatus; + int n; + int l; + + /* + * retrieve the config structures + */ + sconf = r->server->module_config; + conf = (rewrite_server_conf *)ap_get_module_config(sconf, + &rewrite_module); + + /* + * only do something under runtime if the engine is really enabled, + * else return immediately! + */ + if (conf->state == ENGINE_DISABLED) { + return DECLINED; + } + + /* + * check for the ugly API case of a virtual host section where no + * mod_rewrite directives exists. In this situation we became no chance + * by the API to setup our default per-server config so we have to + * on-the-fly assume we have the default config. But because the default + * config has a disabled rewriting engine we are lucky because can + * just stop operating now. + */ + if (conf->server != r->server) { + return DECLINED; + } + + /* + * add the SCRIPT_URL variable to the env. this is a bit complicated + * due to the fact that apache uses subrequests and internal redirects + */ + + if (r->main == NULL) { + var = ap_pstrcat(r->pool, "REDIRECT_", ENVVAR_SCRIPT_URL, NULL); + var = ap_table_get(r->subprocess_env, var); + if (var == NULL) { + ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri); + } + else { + ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var); + } + } + else { + var = ap_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL); + ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var); + } + + /* + * create the SCRIPT_URI variable for the env + */ + + /* add the canonical URI of this URL */ + thisserver = ap_get_server_name(r); + port = ap_get_server_port(r); + if (ap_is_default_port(port, r)) { + thisport = ""; + } + else { + ap_snprintf(buf, sizeof(buf), ":%u", port); + thisport = buf; + } + thisurl = ap_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL); + + /* set the variable */ + var = ap_pstrcat(r->pool, ap_http_method(r), "://", thisserver, thisport, + thisurl, NULL); + ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var); + + /* if filename was not initially set, + * we start with the requested URI + */ + if (r->filename == NULL) { + r->filename = ap_pstrdup(r->pool, r->uri); + rewritelog(r, 2, "init rewrite engine with requested uri %s", + r->filename); + } + + /* + * now apply the rules ... + */ + rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL); + if (rulestatus) { + unsigned skip; + + if (strlen(r->filename) > 6 && + strncmp(r->filename, "proxy:", 6) == 0) { + /* it should be go on as an internal proxy request */ + + /* check if the proxy module is enabled, so + * we can actually use it! + */ + if (!proxy_available) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "attempt to make remote request from mod_rewrite " + "without proxy enabled: %s", r->filename); + return FORBIDDEN; + } + + /* make sure the QUERY_STRING and + * PATH_INFO parts get incorporated + */ + if (r->path_info != NULL) { + r->filename = ap_pstrcat(r->pool, r->filename, + r->path_info, NULL); + } + if (r->args != NULL && + r->uri == r->unparsed_uri) { + /* see proxy_http:proxy_http_canon() */ + r->filename = ap_pstrcat(r->pool, r->filename, + "?", r->args, NULL); + } + + /* now make sure the request gets handled by the proxy handler */ + r->proxyreq = PROXY_PASS; + r->handler = "proxy-server"; + + rewritelog(r, 1, "go-ahead with proxy request %s [OK]", + r->filename); + return OK; + } + else if ((skip = is_absolute_uri(r->filename)) > 0) { + /* it was finally rewritten to a remote URL */ + + if (rulestatus != ACTION_NOESCAPE) { + rewritelog(r, 1, "escaping %s for redirect", r->filename); + r->filename = escape_absolute_uri(r->pool, r->filename, skip); + } + + /* append the QUERY_STRING part */ + if (r->args) { + r->filename = ap_pstrcat(r->pool, r->filename, "?", + (rulestatus == ACTION_NOESCAPE) + ? r->args + : ap_escape_uri(r->pool, r->args), + NULL); + } + + /* determine HTTP redirect response code */ + if (ap_is_HTTP_REDIRECT(r->status)) { + n = r->status; + r->status = HTTP_OK; /* make Apache kernel happy */ + } + else { + n = REDIRECT; + } + + /* now do the redirection */ + ap_table_setn(r->headers_out, "Location", r->filename); + rewritelog(r, 1, "redirect to %s [REDIRECT/%d]", r->filename, n); + return n; + } + else if (strlen(r->filename) > 10 && + strncmp(r->filename, "forbidden:", 10) == 0) { + /* This URLs is forced to be forbidden for the requester */ + return FORBIDDEN; + } + else if (strlen(r->filename) > 5 && + strncmp(r->filename, "gone:", 5) == 0) { + /* This URLs is forced to be gone */ + return HTTP_GONE; + } + else if (strlen(r->filename) > 12 && + strncmp(r->filename, "passthrough:", 12) == 0) { + /* + * Hack because of underpowered API: passing the current + * rewritten filename through to other URL-to-filename handlers + * just as it were the requested URL. This is to enable + * post-processing by mod_alias, etc. which always act on + * r->uri! The difference here is: We do not try to + * add the document root + */ + r->uri = ap_pstrdup(r->pool, r->filename+12); + return DECLINED; + } + else { + /* it was finally rewritten to a local path */ + + /* expand "/~user" prefix */ +#if !defined(WIN32) && !defined(NETWARE) + r->filename = expand_tildepaths(r, r->filename); +#endif + rewritelog(r, 2, "local path result: %s", r->filename); + + /* the filename must be either an absolute local path or an + * absolute local URL. + */ + if ( *r->filename != '/' + && !ap_os_is_path_absolute(r->filename)) { + return BAD_REQUEST; + } + + /* if there is no valid prefix, we have + * to emulate the translator from the core and + * prefix the filename with document_root + * + * NOTICE: + * We cannot leave out the prefix_stat because + * - when we always prefix with document_root + * then no absolute path can be created, e.g. via + * emulating a ScriptAlias directive, etc. + * - when we always NOT prefix with document_root + * then the files under document_root have to + * be references directly and document_root + * gets never used and will be a dummy parameter - + * this is also bad + * + * BUT: + * Under real Unix systems this is no problem, + * because we only do stat() on the first directory + * and this gets cached by the kernel for along time! + */ + n = prefix_stat(r->filename, r->pool); + if (n == 0) { + if ((ccp = ap_document_root(r)) != NULL) { + l = ap_cpystrn(docroot, ccp, sizeof(docroot)) - docroot; + + /* always NOT have a trailing slash */ + if (docroot[l-1] == '/') { + docroot[l-1] = '\0'; + } + if (r->server->path + && !strncmp(r->filename, r->server->path, + r->server->pathlen)) { + r->filename = ap_pstrcat(r->pool, docroot, + (r->filename + + r->server->pathlen), NULL); + } + else { + r->filename = ap_pstrcat(r->pool, docroot, + r->filename, NULL); + } + rewritelog(r, 2, "prefixed with document_root to %s", + r->filename); + } + } + + rewritelog(r, 1, "go-ahead with %s [OK]", r->filename); + return OK; + } + } + else { + rewritelog(r, 1, "pass through %s", r->filename); + return DECLINED; + } +} + + +/* +** +** MIME-type hook +** +** [used to support the forced-MIME-type feature] +** +*/ + +static int hook_mimetype(request_rec *r) +{ + const char *t; + + /* now check if we have to force a MIME-type */ + t = ap_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR); + if (t == NULL) { + return DECLINED; + } + else { + rewritelog(r, 1, "force filename %s to have MIME-type '%s'", + r->filename, t); + r->content_type = t; + return OK; + } +} + + +/* +** +** Fixup hook +** +** [used for the rewriting engine triggered by +** the per-directory 'RewriteRule' directives] +** +*/ + +static int hook_fixup(request_rec *r) +{ + rewrite_perdir_conf *dconf; + char *cp; + char *cp2; + const char *ccp; + char *prefix; + int l; + int rulestatus; + int n; + char *ofilename; + + dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, + &rewrite_module); + + /* if there is no per-dir config we return immediately */ + if (dconf == NULL) { + return DECLINED; + } + + /* we shouldn't do anything in subrequests */ + if (r->main != NULL) { + return DECLINED; + } + + /* if there are no real (i.e. no RewriteRule directives!) + per-dir config of us, we return also immediately */ + if (dconf->directory == NULL) { + return DECLINED; + } + + /* + * only do something under runtime if the engine is really enabled, + * for this directory, else return immediately! + */ + if (dconf->state == ENGINE_DISABLED) { + return DECLINED; + } + + /* + * Do the Options check after engine check, so + * the user is able to explicitely turn RewriteEngine Off. + */ + if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) { + /* FollowSymLinks is mandatory! */ + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Options FollowSymLinks or SymLinksIfOwnerMatch is off " + "which implies that RewriteRule directive is forbidden: " + "%s", r->filename); + return FORBIDDEN; + } + + /* + * remember the current filename before rewriting for later check + * to prevent deadlooping because of internal redirects + * on final URL/filename which can be equal to the inital one. + */ + ofilename = r->filename; + + /* + * now apply the rules ... + */ + rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory); + if (rulestatus) { + unsigned skip; + + if (strlen(r->filename) > 6 && + strncmp(r->filename, "proxy:", 6) == 0) { + /* it should go on as an internal proxy request */ + + /* make sure the QUERY_STRING and + * PATH_INFO parts get incorporated + * (r->path_info was already appended by the + * rewriting engine because of the per-dir context!) + */ + if (r->args != NULL) { + r->filename = ap_pstrcat(r->pool, r->filename, + "?", r->args, NULL); + } + + /* now make sure the request gets handled by the proxy handler */ + r->proxyreq = PROXY_PASS; + r->handler = "proxy-server"; + + rewritelog(r, 1, "[per-dir %s] go-ahead with proxy request " + "%s [OK]", dconf->directory, r->filename); + return OK; + } + else if ((skip = is_absolute_uri(r->filename)) > 0) { + /* it was finally rewritten to a remote URL */ + + /* because we are in a per-dir context + * first try to replace the directory with its base-URL + * if there is a base-URL available + */ + if (dconf->baseurl != NULL) { + /* skip 'scheme://' */ + cp = r->filename + skip; + + if ((cp = strchr(cp, '/')) != NULL && *(++cp)) { + rewritelog(r, 2, + "[per-dir %s] trying to replace " + "prefix %s with %s", + dconf->directory, dconf->directory, + dconf->baseurl); + + /* I think, that hack needs an explanation: + * well, here is it: + * mod_rewrite was written for unix systems, were + * absolute file-system paths start with a slash. + * URL-paths _also_ start with slashes, so they + * can be easily compared with system paths. + * + * the following assumes, that the actual url-path + * may be prefixed by the current directory path and + * tries to replace the system path with the RewriteBase + * URL. + * That assumption is true if we use a RewriteRule like + * + * RewriteRule ^foo bar [R] + * + * (see apply_rewrite_rule function) + * However on systems that don't have a / as system + * root this will never match, so we skip the / after the + * hostname and compare/substitute only the stuff after it. + * + * (note that cp was already increased to the right value) + */ + cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/') + ? dconf->directory + 1 + : dconf->directory, + dconf->baseurl + 1); + if (strcmp(cp2, cp) != 0) { + *cp = '\0'; + r->filename = ap_pstrcat(r->pool, r->filename, + cp2, NULL); + } + } + } + + /* now prepare the redirect... */ + if (rulestatus != ACTION_NOESCAPE) { + rewritelog(r, 1, "[per-dir %s] escaping %s for redirect", + dconf->directory, r->filename); + r->filename = escape_absolute_uri(r->pool, r->filename, skip); + } + + /* append the QUERY_STRING part */ + if (r->args) { + r->filename = ap_pstrcat(r->pool, r->filename, "?", + (rulestatus == ACTION_NOESCAPE) + ? r->args + : ap_escape_uri(r->pool, r->args), + NULL); + } + + /* determine HTTP redirect response code */ + if (ap_is_HTTP_REDIRECT(r->status)) { + n = r->status; + r->status = HTTP_OK; /* make Apache kernel happy */ + } + else { + n = REDIRECT; + } + + /* now do the redirection */ + ap_table_setn(r->headers_out, "Location", r->filename); + rewritelog(r, 1, "[per-dir %s] redirect to %s [REDIRECT/%d]", + dconf->directory, r->filename, n); + return n; + } + else if (strlen(r->filename) > 10 && + strncmp(r->filename, "forbidden:", 10) == 0) { + /* This URL is forced to be forbidden for the requester */ + return FORBIDDEN; + } + else if (strlen(r->filename) > 5 && + strncmp(r->filename, "gone:", 5) == 0) { + /* This URL is forced to be gone */ + return HTTP_GONE; + } + else { + /* it was finally rewritten to a local path */ + + /* if someone used the PASSTHROUGH flag in per-dir + * context we just ignore it. It is only useful + * in per-server context + */ + if (strlen(r->filename) > 12 && + strncmp(r->filename, "passthrough:", 12) == 0) { + r->filename = ap_pstrdup(r->pool, r->filename+12); + } + + /* the filename must be either an absolute local path or an + * absolute local URL. + */ + if ( *r->filename != '/' + && !ap_os_is_path_absolute(r->filename)) { + return BAD_REQUEST; + } + + /* Check for deadlooping: + * At this point we KNOW that at least one rewriting + * rule was applied, but when the resulting URL is + * the same as the initial URL, we are not allowed to + * use the following internal redirection stuff because + * this would lead to a deadloop. + */ + if (strcmp(r->filename, ofilename) == 0) { + rewritelog(r, 1, "[per-dir %s] initial URL equal rewritten " + "URL: %s [IGNORING REWRITE]", + dconf->directory, r->filename); + return OK; + } + + /* if there is a valid base-URL then substitute + * the per-dir prefix with this base-URL if the + * current filename still is inside this per-dir + * context. If not then treat the result as a + * plain URL + */ + if (dconf->baseurl != NULL) { + rewritelog(r, 2, + "[per-dir %s] trying to replace prefix %s with %s", + dconf->directory, dconf->directory, dconf->baseurl); + r->filename = subst_prefix_path(r, r->filename, + dconf->directory, + dconf->baseurl); + } + else { + /* if no explicit base-URL exists we assume + * that the directory prefix is also a valid URL + * for this webserver and only try to remove the + * document_root if it is prefix + */ + if ((ccp = ap_document_root(r)) != NULL) { + prefix = ap_pstrdup(r->pool, ccp); + /* always NOT have a trailing slash */ + l = strlen(prefix); + if (prefix[l-1] == '/') { + prefix[l-1] = '\0'; + l--; + } + if (strncmp(r->filename, prefix, l) == 0) { + rewritelog(r, 2, + "[per-dir %s] strip document_root " + "prefix: %s -> %s", + dconf->directory, r->filename, + r->filename+l); + r->filename = ap_pstrdup(r->pool, r->filename+l); + } + } + } + + /* now initiate the internal redirect */ + rewritelog(r, 1, "[per-dir %s] internal redirect with %s " + "[INTERNAL REDIRECT]", dconf->directory, r->filename); + r->filename = ap_pstrcat(r->pool, "redirect:", r->filename, NULL); + r->handler = "redirect-handler"; + return OK; + } + } + else { + rewritelog(r, 1, "[per-dir %s] pass through %s", + dconf->directory, r->filename); + return DECLINED; + } +} + + +/* +** +** Content-Handlers +** +** [used for redirect support] +** +*/ + +static int handler_redirect(request_rec *r) +{ + /* just make sure that we are really meant! */ + if (strncmp(r->filename, "redirect:", 9) != 0) { + return DECLINED; + } + + if (is_redirect_limit_exceeded(r)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, + "mod_rewrite: maximum number of internal redirects " + "reached. Assuming configuration error. Use " + "'RewriteOptions MaxRedirects' to increase the limit " + "if neccessary."); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* now do the internal redirect */ + ap_internal_redirect(ap_pstrcat(r->pool, r->filename+9, + r->args ? "?" : NULL, r->args, NULL), r); + + /* and return gracefully */ + return OK; +} + +/* + * check whether redirect limit is reached + */ +static int is_redirect_limit_exceeded(request_rec *r) +{ + request_rec *top = r; + rewrite_request_conf *reqc; + rewrite_perdir_conf *dconf; + + /* we store it in the top request */ + while (top->main) { + top = top->main; + } + while (top->prev) { + top = top->prev; + } + + /* fetch our config */ + reqc = (rewrite_request_conf *) ap_get_module_config(top->request_config, + &rewrite_module); + + /* no config there? create one. */ + if (!reqc) { + rewrite_server_conf *sconf; + + reqc = ap_palloc(top->pool, sizeof(rewrite_request_conf)); + sconf = ap_get_module_config(r->server->module_config, &rewrite_module); + + reqc->redirects = 0; + reqc->redirect_limit = sconf->redirect_limit + ? sconf->redirect_limit + : REWRITE_REDIRECT_LIMIT; + + /* associate it with this request */ + ap_set_module_config(top->request_config, &rewrite_module, reqc); + } + + /* allow to change the limit during redirects. */ + dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, + &rewrite_module); + + /* 0 == unset; take server conf ... */ + if (dconf->redirect_limit) { + reqc->redirect_limit = dconf->redirect_limit; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, + "mod_rewrite's internal redirect status: %d/%d.", + reqc->redirects, reqc->redirect_limit); + + /* and now give the caller a hint */ + return (reqc->redirects++ >= reqc->redirect_limit); +} + + +/* +** +-------------------------------------------------------+ +** | | +** | the rewriting engine +** | | +** +-------------------------------------------------------+ +*/ + +/* + * Apply a complete rule set, + * i.e. a list of rewrite rules + */ +static int apply_rewrite_list(request_rec *r, array_header *rewriterules, + char *perdir) +{ + rewriterule_entry *entries; + rewriterule_entry *p; + int i; + int changed; + int rc; + int s; + + /* + * Iterate over all existing rules + */ + entries = (rewriterule_entry *)rewriterules->elts; + changed = 0; + loop: + for (i = 0; i < rewriterules->nelts; i++) { + p = &entries[i]; + + /* + * Ignore this rule on subrequests if we are explicitly + * asked to do so or this is a proxy-throughput or a + * forced redirect rule. + */ + if (r->main != NULL && + (p->flags & RULEFLAG_IGNOREONSUBREQ || + p->flags & RULEFLAG_PROXY || + p->flags & RULEFLAG_FORCEREDIRECT )) { + continue; + } + + /* + * Apply the current rule. + */ + rc = apply_rewrite_rule(r, p, perdir); + if (rc) { + /* + * Indicate a change if this was not a match-only rule. + */ + if (rc != 2) { + changed = ((p->flags & RULEFLAG_NOESCAPE) + ? ACTION_NOESCAPE : ACTION_NORMAL); + } + + /* + * Pass-Through Feature (`RewriteRule .. .. [PT]'): + * Because the Apache 1.x API is very limited we + * need this hack to pass the rewritten URL to other + * modules like mod_alias, mod_userdir, etc. + */ + if (p->flags & RULEFLAG_PASSTHROUGH) { + rewritelog(r, 2, "forcing '%s' to get passed through " + "to next API URI-to-filename handler", r->filename); + r->filename = ap_pstrcat(r->pool, "passthrough:", + r->filename, NULL); + changed = ACTION_NORMAL; + break; + } + + /* + * Rule has the "forbidden" flag set which means that + * we stop processing and indicate this to the caller. + */ + if (p->flags & RULEFLAG_FORBIDDEN) { + rewritelog(r, 2, "forcing '%s' to be forbidden", r->filename); + r->filename = ap_pstrcat(r->pool, "forbidden:", + r->filename, NULL); + changed = ACTION_NORMAL; + break; + } + + /* + * Rule has the "gone" flag set which means that + * we stop processing and indicate this to the caller. + */ + if (p->flags & RULEFLAG_GONE) { + rewritelog(r, 2, "forcing '%s' to be gone", r->filename); + r->filename = ap_pstrcat(r->pool, "gone:", r->filename, NULL); + changed = ACTION_NORMAL; + break; + } + + /* + * Stop processing also on proxy pass-through and + * last-rule and new-round flags. + */ + if (p->flags & RULEFLAG_PROXY) { + break; + } + if (p->flags & RULEFLAG_LASTRULE) { + break; + } + + /* + * On "new-round" flag we just start from the top of + * the rewriting ruleset again. + */ + if (p->flags & RULEFLAG_NEWROUND) { + goto loop; + } + + /* + * If we are forced to skip N next rules, do it now. + */ + if (p->skip > 0) { + s = p->skip; + while ( i < rewriterules->nelts + && s > 0) { + i++; + p = &entries[i]; + s--; + } + } + } + else { + /* + * If current rule is chained with next rule(s), + * skip all this next rule(s) + */ + while ( i < rewriterules->nelts + && p->flags & RULEFLAG_CHAIN) { + i++; + p = &entries[i]; + } + } + } + return changed; +} + +/* + * Apply a single(!) rewrite rule + */ +static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p, + char *perdir) +{ + char *uri; + char *output; + const char *vary; + char newuri[MAX_STRING_LEN]; + regex_t *regexp; + regmatch_t regmatch[AP_MAX_REG_MATCH]; + backrefinfo *briRR = NULL; + backrefinfo *briRC = NULL; + int prefixstrip; + int failed; + array_header *rewriteconds; + rewritecond_entry *conds; + rewritecond_entry *c; + int i; + int rc; + + /* + * Initialisation + */ + uri = r->filename; + regexp = p->regexp; + output = p->output; + + /* + * Add (perhaps splitted away) PATH_INFO postfix to URL to + * make sure we really match against the complete URL. + */ + if (perdir != NULL && r->path_info != NULL && r->path_info[0] != '\0') { + rewritelog(r, 3, "[per-dir %s] add path-info postfix: %s -> %s%s", + perdir, uri, uri, r->path_info); + uri = ap_pstrcat(r->pool, uri, r->path_info, NULL); + } + + /* + * On per-directory context (.htaccess) strip the location + * prefix from the URL to make sure patterns apply only to + * the local part. Additionally indicate this special + * threatment in the logfile. + */ + prefixstrip = 0; + if (perdir != NULL) { + if ( strlen(uri) >= strlen(perdir) + && strncmp(uri, perdir, strlen(perdir)) == 0) { + rewritelog(r, 3, "[per-dir %s] strip per-dir prefix: %s -> %s", + perdir, uri, uri+strlen(perdir)); + uri = uri+strlen(perdir); + prefixstrip = 1; + } + } + + /* + * Try to match the URI against the RewriteRule pattern + * and exit immeddiately if it didn't apply. + */ + if (perdir == NULL) { + rewritelog(r, 3, "applying pattern '%s' to uri '%s'", + p->pattern, uri); + } + else { + rewritelog(r, 3, "[per-dir %s] applying pattern '%s' to uri '%s'", + perdir, p->pattern, uri); + } + rc = (ap_regexec(regexp, uri, AP_MAX_REG_MATCH, regmatch, 0) == 0); + if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) || + (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) { + return 0; + } + + /* + * Else create the RewriteRule `regsubinfo' structure which + * holds the substitution information. + */ + briRR = (backrefinfo *)ap_palloc(r->pool, sizeof(backrefinfo)); + if (!rc && (p->flags & RULEFLAG_NOTMATCH)) { + /* empty info on negative patterns */ + briRR->source = ""; + briRR->nsub = 0; + } + else { + briRR->source = ap_pstrdup(r->pool, uri); + briRR->nsub = regexp->re_nsub; + memcpy((void *)(briRR->regmatch), (void *)(regmatch), + sizeof(regmatch)); + } + + /* + * Initiallally create the RewriteCond backrefinfo with + * empty backrefinfo, i.e. not subst parts + * (this one is adjusted inside apply_rewrite_cond() later!!) + */ + briRC = (backrefinfo *)ap_pcalloc(r->pool, sizeof(backrefinfo)); + briRC->source = ""; + briRC->nsub = 0; + + /* + * Ok, we already know the pattern has matched, but we now + * additionally have to check for all existing preconditions + * (RewriteCond) which have to be also true. We do this at + * this very late stage to avoid unnessesary checks which + * would slow down the rewriting engine!! + */ + rewriteconds = p->rewriteconds; + conds = (rewritecond_entry *)rewriteconds->elts; + failed = 0; + for (i = 0; i < rewriteconds->nelts; i++) { + c = &conds[i]; + rc = apply_rewrite_cond(r, c, perdir, briRR, briRC); + if (c->flags & CONDFLAG_ORNEXT) { + /* + * The "OR" case + */ + if (rc == 0) { + /* One condition is false, but another can be + * still true, so we have to continue... + */ + ap_table_unset(r->notes, VARY_KEY_THIS); + continue; + } + else { + /* One true condition is enough in "or" case, so + * skip the other conditions which are "ornext" + * chained + */ + while ( i < rewriteconds->nelts + && c->flags & CONDFLAG_ORNEXT) { + i++; + c = &conds[i]; + } + continue; + } + } + else { + /* + * The "AND" case, i.e. no "or" flag, + * so a single failure means total failure. + */ + if (rc == 0) { + failed = 1; + break; + } + } + vary = ap_table_get(r->notes, VARY_KEY_THIS); + if (vary != NULL) { + ap_table_merge(r->notes, VARY_KEY, vary); + ap_table_unset(r->notes, VARY_KEY_THIS); + } + } + /* if any condition fails the complete rule fails */ + if (failed) { + ap_table_unset(r->notes, VARY_KEY); + ap_table_unset(r->notes, VARY_KEY_THIS); + return 0; + } + + /* + * Regardless of what we do next, we've found a match. Check to see + * if any of the request header fields were involved, and add them + * to the Vary field of the response. + */ + if ((vary = ap_table_get(r->notes, VARY_KEY)) != NULL) { + ap_table_merge(r->headers_out, "Vary", vary); + ap_table_unset(r->notes, VARY_KEY); + } + + /* + * If this is a pure matching rule (`RewriteRule <pat> -') + * we stop processing and return immediately. The only thing + * we have not to forget are the environment variables + * (`RewriteRule <pat> - [E=...]') + */ + if (strcmp(output, "-") == 0) { + do_expand_env(r, p->env, briRR, briRC); + if (p->forced_mimetype != NULL) { + if (perdir == NULL) { + /* In the per-server context we can force the MIME-type + * the correct way by notifying our MIME-type hook handler + * to do the job when the MIME-type API stage is reached. + */ + rewritelog(r, 2, "remember %s to have MIME-type '%s'", + r->filename, p->forced_mimetype); + ap_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR, + p->forced_mimetype); + } + else { + /* In per-directory context we operate in the Fixup API hook + * which is after the MIME-type hook, so our MIME-type handler + * has no chance to set r->content_type. And because we are + * in the situation where no substitution takes place no + * sub-request will happen (which could solve the + * restriction). As a workaround we do it ourself now + * immediately although this is not strictly API-conforming. + * But it's the only chance we have... + */ + rewritelog(r, 1, "[per-dir %s] force %s to have MIME-type " + "'%s'", perdir, r->filename, p->forced_mimetype); + r->content_type = p->forced_mimetype; + } + } + return 2; + } + + /* + * Ok, now we finally know all patterns have matched and + * that there is something to replace, so we create the + * substitution URL string in `newuri'. + */ + do_expand(r, output, newuri, sizeof(newuri), briRR, briRC); + if (perdir == NULL) { + rewritelog(r, 2, "rewrite %s -> %s", uri, newuri); + } + else { + rewritelog(r, 2, "[per-dir %s] rewrite %s -> %s", perdir, uri, newuri); + } + + /* + * Additionally do expansion for the environment variable + * strings (`RewriteRule .. .. [E=<string>]'). + */ + do_expand_env(r, p->env, briRR, briRC); + + /* + * Now replace API's knowledge of the current URI: + * Replace r->filename with the new URI string and split out + * an on-the-fly generated QUERY_STRING part into r->args + */ + r->filename = ap_pstrdup(r->pool, newuri); + splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND); + + /* + * Add the previously stripped per-directory location + * prefix if the new URI is not a new one for this + * location, i.e. if it's not an absolute URL (!) path nor + * a fully qualified URL scheme. + */ + if (prefixstrip && *r->filename != '/' + && !is_absolute_uri(r->filename)) { + rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s", + perdir, r->filename, perdir, r->filename); + r->filename = ap_pstrcat(r->pool, perdir, r->filename, NULL); + } + + /* + * If this rule is forced for proxy throughput + * (`RewriteRule ... ... [P]') then emulate mod_proxy's + * URL-to-filename handler to be sure mod_proxy is triggered + * for this URL later in the Apache API. But make sure it is + * a fully-qualified URL. (If not it is qualified with + * ourself). + */ + if (p->flags & RULEFLAG_PROXY) { + fully_qualify_uri(r); + if (perdir == NULL) { + rewritelog(r, 2, "forcing proxy-throughput with %s", r->filename); + } + else { + rewritelog(r, 2, "[per-dir %s] forcing proxy-throughput with %s", + perdir, r->filename); + } + r->filename = ap_pstrcat(r->pool, "proxy:", r->filename, NULL); + return 1; + } + + /* + * If this rule is explicitly forced for HTTP redirection + * (`RewriteRule .. .. [R]') then force an external HTTP + * redirect. But make sure it is a fully-qualified URL. (If + * not it is qualified with ourself). + */ + if (p->flags & RULEFLAG_FORCEREDIRECT) { + fully_qualify_uri(r); + if (perdir == NULL) { + rewritelog(r, 2, + "explicitly forcing redirect with %s", r->filename); + } + else { + rewritelog(r, 2, + "[per-dir %s] explicitly forcing redirect with %s", + perdir, r->filename); + } + r->status = p->forced_responsecode; + return 1; + } + + /* + * Special Rewriting Feature: Self-Reduction + * We reduce the URL by stripping a possible + * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which + * corresponds to ourself. This is to simplify rewrite maps + * and to avoid recursion, etc. When this prefix is not a + * coincidence then the user has to use [R] explicitly (see + * above). + */ + reduce_uri(r); + + /* + * If this rule is still implicitly forced for HTTP + * redirection (`RewriteRule .. <scheme>://...') then + * directly force an external HTTP redirect. + */ + if (is_absolute_uri(r->filename)) { + if (perdir == NULL) { + rewritelog(r, 2, + "implicitly forcing redirect (rc=%d) with %s", + p->forced_responsecode, r->filename); + } + else { + rewritelog(r, 2, "[per-dir %s] implicitly forcing redirect " + "(rc=%d) with %s", perdir, p->forced_responsecode, + r->filename); + } + r->status = p->forced_responsecode; + return 1; + } + + /* + * Finally we had to remember if a MIME-type should be + * forced for this URL (`RewriteRule .. .. [T=<type>]') + * Later in the API processing phase this is forced by our + * MIME API-hook function. This time its no problem even for + * the per-directory context (where the MIME-type hook was + * already processed) because a sub-request happens ;-) + */ + if (p->forced_mimetype != NULL) { + ap_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR, + p->forced_mimetype); + if (perdir == NULL) { + rewritelog(r, 2, "remember %s to have MIME-type '%s'", + r->filename, p->forced_mimetype); + } + else { + rewritelog(r, 2, + "[per-dir %s] remember %s to have MIME-type '%s'", + perdir, r->filename, p->forced_mimetype); + } + } + + /* + * Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_) + * But now we're done for this particular rule. + */ + return 1; +} + +static int apply_rewrite_cond(request_rec *r, rewritecond_entry *p, + char *perdir, backrefinfo *briRR, + backrefinfo *briRC) +{ + char input[MAX_STRING_LEN]; + struct stat sb; + request_rec *rsub; + regmatch_t regmatch[AP_MAX_REG_MATCH]; + int rc; + + /* + * Construct the string we match against + */ + + do_expand(r, p->input, input, sizeof(input), briRR, briRC); + + /* + * Apply the patterns + */ + + rc = 0; + if (strcmp(p->pattern, "-f") == 0) { + if (stat(input, &sb) == 0) { + if (S_ISREG(sb.st_mode)) { + rc = 1; + } + } + } + else if (strcmp(p->pattern, "-s") == 0) { + if (stat(input, &sb) == 0) { + if (S_ISREG(sb.st_mode) && sb.st_size > 0) { + rc = 1; + } + } + } + else if (strcmp(p->pattern, "-l") == 0) { +#if !defined(OS2) && !defined(WIN32) && !defined(NETWARE) + if (lstat(input, &sb) == 0) { + if (S_ISLNK(sb.st_mode)) { + rc = 1; + } + } +#endif + } + else if (strcmp(p->pattern, "-d") == 0) { + if (stat(input, &sb) == 0) { + if (S_ISDIR(sb.st_mode)) { + rc = 1; + } + } + } + else if (strcmp(p->pattern, "-U") == 0) { + /* avoid infinite subrequest recursion */ + if (strlen(input) > 0 && subreq_ok(r)) { + + /* run a URI-based subrequest */ + rsub = ap_sub_req_lookup_uri(input, r); + + /* URI exists for any result up to 3xx, redirects allowed */ + if (rsub->status < 400) + rc = 1; + + /* log it */ + rewritelog(r, 5, "RewriteCond URI (-U) check: " + "path=%s -> status=%d", input, rsub->status); + + /* cleanup by destroying the subrequest */ + ap_destroy_sub_req(rsub); + } + } + else if (strcmp(p->pattern, "-F") == 0) { + /* avoid infinite subrequest recursion */ + if (strlen(input) > 0 && subreq_ok(r)) { + + /* process a file-based subrequest: + * this differs from -U in that no path translation is done. + */ + rsub = ap_sub_req_lookup_file(input, r); + + /* file exists for any result up to 2xx, no redirects */ + if (rsub->status < 300 && + /* double-check that file exists since default result is 200 */ + stat(rsub->filename, &sb) == 0) { + rc = 1; + } + + /* log it */ + rewritelog(r, 5, "RewriteCond file (-F) check: path=%s " + "-> file=%s status=%d", input, rsub->filename, + rsub->status); + + /* cleanup by destroying the subrequest */ + ap_destroy_sub_req(rsub); + } + } + else if (strlen(p->pattern) > 1 && *(p->pattern) == '>') { + rc = (compare_lexicography(input, p->pattern+1) == 1 ? 1 : 0); + } + else if (strlen(p->pattern) > 1 && *(p->pattern) == '<') { + rc = (compare_lexicography(input, p->pattern+1) == -1 ? 1 : 0); + } + else if (strlen(p->pattern) > 1 && *(p->pattern) == '=') { + if (strcmp(p->pattern+1, "\"\"") == 0) { + rc = (*input == '\0'); + } + else { + rc = (strcmp(input, p->pattern+1) == 0 ? 1 : 0); + } + } + else { + /* it is really a regexp pattern, so apply it */ + rc = (ap_regexec(p->regexp, input, AP_MAX_REG_MATCH, regmatch,0) == 0); + + /* if it isn't a negated pattern and really matched + we update the passed-through regex subst info structure */ + if (rc && !(p->flags & CONDFLAG_NOTMATCH)) { + briRC->source = ap_pstrdup(r->pool, input); + briRC->nsub = p->regexp->re_nsub; + memcpy((void *)(briRC->regmatch), (void *)(regmatch), + sizeof(regmatch)); + } + } + + /* if this is a non-matching regexp, just negate the result */ + if (p->flags & CONDFLAG_NOTMATCH) { + rc = !rc; + } + + rewritelog(r, 4, "RewriteCond: input='%s' pattern='%s%s' => %s", + input, (p->flags & CONDFLAG_NOTMATCH ? "!" : ""), + p->pattern, rc ? "matched" : "not-matched"); + + /* end just return the result */ + return rc; +} + + +/* +** +-------------------------------------------------------+ +** | | +** | URL transformation functions +** | | +** +-------------------------------------------------------+ +*/ + + +/* +** +** perform all the expansions on the input string +** leaving the result in the supplied buffer +** +*/ + +static void do_expand(request_rec *r, char *input, char *buffer, int nbuf, + backrefinfo *briRR, backrefinfo *briRC) +{ + char *inp, *outp; + size_t span, space; + + /* + * for security reasons this expansion must be perfomed in a + * single pass, otherwise an attacker can arrange for the result + * of an earlier expansion to include expansion specifiers that + * are interpreted by a later expansion, producing results that + * were not intended by the administrator. + */ + + inp = input; + outp = buffer; + space = nbuf - 1; /* room for '\0' */ + + for (;;) { + span = strcspn(inp, "\\$%"); + if (span > space) { + span = space; + } + memcpy(outp, inp, span); + inp += span; + outp += span; + space -= span; + if (space == 0 || *inp == '\0') { + break; + } + /* now we have a '\', '$', or '%' */ + if (inp[0] == '\\') { + if (inp[1] != '\0') { + inp++; + goto skip; + } + } + else if (inp[1] == '{') { + char *endp; + endp = find_closing_bracket(inp+2, '{', '}'); + if (endp == NULL) { + goto skip; + } + /* + * These lookups may be recursive in a very convoluted + * fashion -- see the LA-U and LA-F variable expansion + * prefixes -- so we copy lookup keys to a separate buffer + * rather than adding zero bytes in order to use them in + * place. + */ + if (inp[0] == '$') { + /* ${...} map lookup expansion */ + /* + * To make rewrite maps useful the lookup key and + * default values must be expanded, so we make + * recursive calls to do the work. For security + * reasons we must never expand a string that includes + * verbatim data from the network. The recursion here + * isn't a problem because the result of expansion is + * only passed to lookup_map() so it cannot be + * re-expanded, only re-looked-up. Another way of + * looking at it is that the recursion is entirely + * driven by the syntax of the nested curly brackets. + */ + char *map, *key, *dflt, *result; + char xkey[MAX_STRING_LEN]; + char xdflt[MAX_STRING_LEN]; + key = find_char_in_brackets(inp+2, ':', '{', '}'); + if (key == NULL) { + goto skip; + } + map = ap_pstrndup(r->pool, inp+2, key-inp-2); + dflt = find_char_in_brackets(key+1, '|', '{', '}'); + if (dflt == NULL) { + key = ap_pstrndup(r->pool, key+1, endp-key-1); + dflt = ""; + } + else { + key = ap_pstrndup(r->pool, key+1, dflt-key-1); + dflt = ap_pstrndup(r->pool, dflt+1, endp-dflt-1); + } + do_expand(r, key, xkey, sizeof(xkey), briRR, briRC); + result = lookup_map(r, map, xkey); + if (result) { + span = ap_cpystrn(outp, result, space) - outp; + } else { + do_expand(r, dflt, xdflt, sizeof(xdflt), briRR, briRC); + span = ap_cpystrn(outp, xdflt, space) - outp; + } + } + else if (inp[0] == '%') { + /* %{...} variable lookup expansion */ + char *var; + var = ap_pstrndup(r->pool, inp+2, endp-inp-2); + span = ap_cpystrn(outp, lookup_variable(r, var), space) - outp; + } + else { + span = 0; + } + inp = endp+1; + outp += span; + space -= span; + continue; + } + else if (ap_isdigit(inp[1])) { + int n = inp[1] - '0'; + backrefinfo *bri = NULL; + if (inp[0] == '$') { + /* $N RewriteRule regexp backref expansion */ + bri = briRR; + } + else if (inp[0] == '%') { + /* %N RewriteCond regexp backref expansion */ + bri = briRC; + } + /* see ap_pregsub() in src/main/util.c */ + if (bri && n < AP_MAX_REG_MATCH && + bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) { + span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so; + if (span > space) { + span = space; + } + memcpy(outp, bri->source + bri->regmatch[n].rm_so, span); + outp += span; + space -= span; + } + inp += 2; + continue; + } + skip: + *outp++ = *inp++; + space--; + } + *outp++ = '\0'; +} + + +/* +** +** perform all the expansions on the environment variables +** +*/ + +static void do_expand_env(request_rec *r, char *env[], + backrefinfo *briRR, backrefinfo *briRC) +{ + int i; + char buf[MAX_STRING_LEN]; + + for (i = 0; env[i] != NULL; i++) { + do_expand(r, env[i], buf, sizeof(buf), briRR, briRC); + add_env_variable(r, buf); + } +} + + +/* +** +** split out a QUERY_STRING part from +** the current URI string +** +*/ + +static void splitout_queryargs(request_rec *r, int qsappend) +{ + char *q; + char *olduri; + + /* don't touch, unless it's an http or mailto URL. + * See RFC 1738 and RFC 2368. + */ + if ( is_absolute_uri(r->filename) + && strncasecmp(r->filename, "http", 4) + && strncasecmp(r->filename, "mailto", 6)) { + r->args = NULL; /* forget the query that's still flying around */ + return; + } + + q = strchr(r->filename, '?'); + if (q != NULL) { + olduri = ap_pstrdup(r->pool, r->filename); + *q++ = '\0'; + if (qsappend) { + r->args = ap_pstrcat(r->pool, q, "&", r->args, NULL); + } + else { + r->args = ap_pstrdup(r->pool, q); + } + if (strlen(r->args) == 0) { + r->args = NULL; + rewritelog(r, 3, "split uri=%s -> uri=%s, args=<none>", olduri, + r->filename); + } + else { + if (r->args[strlen(r->args)-1] == '&') { + r->args[strlen(r->args)-1] = '\0'; + } + rewritelog(r, 3, "split uri=%s -> uri=%s, args=%s", olduri, + r->filename, r->args); + } + } + + return; +} + + +/* +** +** strip 'http[s]://ourhost/' from URI +** +*/ + +static void reduce_uri(request_rec *r) +{ + char *cp; + unsigned short port; + char *portp; + char *hostp; + char *url; + char c; + char host[LONG_STRING_LEN]; + char buf[MAX_STRING_LEN]; + char *olduri; + int l; + + cp = ap_http_method(r); + l = strlen(cp); + if ( (int)strlen(r->filename) > l+3 + && strncasecmp(r->filename, cp, l) == 0 + && r->filename[l] == ':' + && r->filename[l+1] == '/' + && r->filename[l+2] == '/' ) { + /* there was really a rewrite to a remote path */ + + olduri = ap_pstrdup(r->pool, r->filename); /* save for logging */ + + /* cut the hostname and port out of the URI */ + ap_cpystrn(buf, r->filename+(l+3), sizeof(buf)); + hostp = buf; + for (cp = hostp; *cp != '\0' && *cp != '/' && *cp != ':'; cp++) + ; + if (*cp == ':') { + /* set host */ + *cp++ = '\0'; + ap_cpystrn(host, hostp, sizeof(host)); + /* set port */ + portp = cp; + for (; *cp != '\0' && *cp != '/'; cp++) + ; + c = *cp; + *cp = '\0'; + port = atoi(portp); + *cp = c; + /* set remaining url */ + url = cp; + } + else if (*cp == '/') { + /* set host */ + *cp = '\0'; + ap_cpystrn(host, hostp, sizeof(host)); + *cp = '/'; + /* set port */ + port = ap_default_port(r); + /* set remaining url */ + url = cp; + } + else { + /* set host */ + ap_cpystrn(host, hostp, sizeof(host)); + /* set port */ + port = ap_default_port(r); + /* set remaining url */ + url = "/"; + } + + /* now check whether we could reduce it to a local path... */ + if (ap_matches_request_vhost(r, host, port)) { + /* this is our host, so only the URL remains */ + r->filename = ap_pstrdup(r->pool, url); + rewritelog(r, 3, "reduce %s -> %s", olduri, r->filename); + } + } + return; +} + + +/* +** +** add 'http[s]://ourhost[:ourport]/' to URI +** if URI is still not fully qualified +** +*/ + +static void fully_qualify_uri(request_rec *r) +{ + char buf[32]; + const char *thisserver; + char *thisport; + int port; + + if (!is_absolute_uri(r->filename)) { + + thisserver = ap_get_server_name(r); + port = ap_get_server_port(r); + if (ap_is_default_port(port,r)) { + thisport = ""; + } + else { + ap_snprintf(buf, sizeof(buf), ":%u", port); + thisport = buf; + } + + if (r->filename[0] == '/') { + r->filename = ap_psprintf(r->pool, "%s://%s%s%s", + ap_http_method(r), thisserver, + thisport, r->filename); + } + else { + r->filename = ap_psprintf(r->pool, "%s://%s%s/%s", + ap_http_method(r), thisserver, + thisport, r->filename); + } + } + return; +} + + +/* return number of chars of the scheme (incl. '://') + * if the URI is absolute (includes a scheme etc.) + * otherwise 0. + * + * NOTE: If you add new schemes here, please have a + * look at escape_absolute_uri and splitout_queryargs. + * Not every scheme takes query strings and some schemes + * may be handled in a special way. + * + * XXX: we should consider a scheme registry, perhaps with + * appropriate escape callbacks to allow other modules + * to extend mod_rewrite at runtime. + */ +static unsigned is_absolute_uri(char *uri) +{ + /* fast exit */ + if (*uri == '/' || strlen(uri) <= 5) { + return 0; + } + + switch (*uri++) { + case 'f': + case 'F': + if (!strncasecmp(uri, "tp://", 5)) { /* ftp:// */ + return 6; + } + break; + + case 'g': + case 'G': + if (!strncasecmp(uri, "opher://", 8)) { /* gopher:// */ + return 9; + } + break; + + case 'h': + case 'H': + if (!strncasecmp(uri, "ttp://", 6)) { /* http:// */ + return 7; + } + else if (!strncasecmp(uri, "ttps://", 7)) { /* https:// */ + return 8; + } + break; + + case 'l': + case 'L': + if (!strncasecmp(uri, "dap://", 6)) { /* ldap:// */ + return 7; + } + break; + + case 'm': + case 'M': + if (!strncasecmp(uri, "ailto:", 6)) { /* mailto: */ + return 7; + } + break; + + case 'n': + case 'N': + if (!strncasecmp(uri, "ews:", 4)) { /* news: */ + return 5; + } + else if (!strncasecmp(uri, "ntp://", 6)) { /* nntp:// */ + return 7; + } + break; + } + + return 0; +} + + +/* escape absolute uri, which may or may not be path oriented. + * So let's handle them differently. + */ +static char *escape_absolute_uri(ap_pool *p, char *uri, unsigned scheme) +{ + char *cp; + + /* be safe. + * NULL should indicate elsewhere, that something's wrong + */ + if (!scheme || strlen(uri) < scheme) { + return NULL; + } + + cp = uri + scheme; + + /* scheme with authority part? */ + if (cp[-1] == '/') { + /* skip host part */ + while (*cp && *cp != '/') { + ++cp; + } + + /* nothing after the hostpart. ready! */ + if (!*cp || !*++cp) { + return ap_pstrdup(p, uri); + } + + /* remember the hostname stuff */ + scheme = cp - uri; + + /* special thing for ldap. + * The parts are separated by question marks. From RFC 2255: + * ldapurl = scheme "://" [hostport] ["/" + * [dn ["?" [attributes] ["?" [scope] + * ["?" [filter] ["?" extensions]]]]]] + */ + if (!strncasecmp(uri, "ldap", 4)) { + char *token[5]; + int c = 0; + + token[0] = cp = ap_pstrdup(p, cp); + while (*cp && c < 4) { + if (*cp == '?') { + token[++c] = cp + 1; + *cp = '\0'; + } + ++cp; + } + + return ap_pstrcat(p, ap_pstrndup(p, uri, scheme), + ap_escape_uri(p, token[0]), + (c >= 1) ? "?" : NULL, + (c >= 1) ? ap_escape_uri(p, token[1]) : NULL, + (c >= 2) ? "?" : NULL, + (c >= 2) ? ap_escape_uri(p, token[2]) : NULL, + (c >= 3) ? "?" : NULL, + (c >= 3) ? ap_escape_uri(p, token[3]) : NULL, + (c >= 4) ? "?" : NULL, + (c >= 4) ? ap_escape_uri(p, token[4]) : NULL, + NULL); + } + } + + /* Nothing special here. Apply normal escaping. */ + return ap_pstrcat(p, ap_pstrndup(p, uri, scheme), + ap_escape_uri(p, cp), NULL); +} + +/* +** +** Expand tilde-paths (/~user) through +** Unix /etc/passwd database information +** +*/ +#if !defined(WIN32) && !defined(NETWARE) +static char *expand_tildepaths(request_rec *r, char *uri) +{ + char user[LONG_STRING_LEN]; + struct passwd *pw; + char *newuri; + int i, j; + + newuri = uri; + if (uri != NULL && strlen(uri) > 2 && uri[0] == '/' && uri[1] == '~') { + /* cut out the username */ + for (j = 0, i = 2; j < sizeof(user)-1 + && uri[i] != '\0' + && uri[i] != '/' ; ) { + user[j++] = uri[i++]; + } + user[j] = '\0'; + + /* lookup username in systems passwd file */ + if ((pw = getpwnam(user)) != NULL) { + /* ok, user was found, so expand the ~user string */ + if (uri[i] != '\0') { + /* ~user/anything... has to be expanded */ + if (pw->pw_dir[strlen(pw->pw_dir)-1] == '/') { + pw->pw_dir[strlen(pw->pw_dir)-1] = '\0'; + } + newuri = ap_pstrcat(r->pool, pw->pw_dir, uri+i, NULL); + } + else { + /* only ~user has to be expanded */ + newuri = ap_pstrdup(r->pool, pw->pw_dir); + } + } + } + return newuri; +} +#endif + + + +/* +** +-------------------------------------------------------+ +** | | +** | DBM hashfile support +** | | +** +-------------------------------------------------------+ +*/ + + +static char *lookup_map(request_rec *r, char *name, char *key) +{ + void *sconf; + rewrite_server_conf *conf; + array_header *rewritemaps; + rewritemap_entry *entries; + rewritemap_entry *s; + char *value; + struct stat st; + int i; + + /* get map configuration */ + sconf = r->server->module_config; + conf = (rewrite_server_conf *)ap_get_module_config(sconf, + &rewrite_module); + rewritemaps = conf->rewritemaps; + + entries = (rewritemap_entry *)rewritemaps->elts; + for (i = 0; i < rewritemaps->nelts; i++) { + s = &entries[i]; + if (strcmp(s->name, name) == 0) { + if (s->type == MAPTYPE_TXT) { + if (stat(s->checkfile, &st) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_rewrite: can't access text RewriteMap " + "file %s", s->checkfile); + rewritelog(r, 1, "can't open RewriteMap file, " + "see error log"); + return NULL; + } + value = get_cache_string(cachep, s->cachename, CACHEMODE_TS, + st.st_mtime, key); + if (value == NULL) { + rewritelog(r, 6, "cache lookup FAILED, forcing new " + "map lookup"); + if ((value = + lookup_map_txtfile(r, s->datafile, key)) != NULL) { + rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] " + "-> val=%s", s->name, key, value); + set_cache_string(cachep, s->cachename, CACHEMODE_TS, + st.st_mtime, key, value); + return value; + } + else { + rewritelog(r, 5, "map lookup FAILED: map=%s[txt] " + "key=%s", s->name, key); + set_cache_string(cachep, s->cachename, CACHEMODE_TS, + st.st_mtime, key, ""); + return NULL; + } + } + else { + rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s " + "-> val=%s", s->name, key, value); + return value[0] != '\0' ? value : NULL; + } + } + else if (s->type == MAPTYPE_DBM) { +#ifndef NO_DBM_REWRITEMAP + if (stat(s->checkfile, &st) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_rewrite: can't access DBM RewriteMap " + "file %s", s->checkfile); + rewritelog(r, 1, "can't open DBM RewriteMap file, " + "see error log"); + return NULL; + } + value = get_cache_string(cachep, s->cachename, CACHEMODE_TS, + st.st_mtime, key); + if (value == NULL) { + rewritelog(r, 6, + "cache lookup FAILED, forcing new map lookup"); + if ((value = + lookup_map_dbmfile(r, s->datafile, key)) != NULL) { + rewritelog(r, 5, "map lookup OK: map=%s[dbm] key=%s " + "-> val=%s", s->name, key, value); + set_cache_string(cachep, s->cachename, CACHEMODE_TS, + st.st_mtime, key, value); + return value; + } + else { + rewritelog(r, 5, "map lookup FAILED: map=%s[dbm] " + "key=%s", s->name, key); + set_cache_string(cachep, s->cachename, CACHEMODE_TS, + st.st_mtime, key, ""); + return NULL; + } + } + else { + rewritelog(r, 5, "cache lookup OK: map=%s[dbm] key=%s " + "-> val=%s", s->name, key, value); + return value[0] != '\0' ? value : NULL; + } +#else + return NULL; +#endif + } + else if (s->type == MAPTYPE_PRG) { + if ((value = + lookup_map_program(r, s->fpin, s->fpout, key)) != NULL) { + rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s", + s->name, key, value); + return value; + } + else { + rewritelog(r, 5, "map lookup FAILED: map=%s key=%s", + s->name, key); + } + } + else if (s->type == MAPTYPE_INT) { + if ((value = lookup_map_internal(r, s->func, key)) != NULL) { + rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s", + s->name, key, value); + return value; + } + else { + rewritelog(r, 5, "map lookup FAILED: map=%s key=%s", + s->name, key); + } + } + else if (s->type == MAPTYPE_RND) { + if (stat(s->checkfile, &st) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_rewrite: can't access text RewriteMap " + "file %s", s->checkfile); + rewritelog(r, 1, "can't open RewriteMap file, " + "see error log"); + return NULL; + } + value = get_cache_string(cachep, s->cachename, CACHEMODE_TS, + st.st_mtime, key); + if (value == NULL) { + rewritelog(r, 6, "cache lookup FAILED, forcing new " + "map lookup"); + if ((value = + lookup_map_txtfile(r, s->datafile, key)) != NULL) { + rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] " + "-> val=%s", s->name, key, value); + set_cache_string(cachep, s->cachename, CACHEMODE_TS, + st.st_mtime, key, value); + } + else { + rewritelog(r, 5, "map lookup FAILED: map=%s[txt] " + "key=%s", s->name, key); + set_cache_string(cachep, s->cachename, CACHEMODE_TS, + st.st_mtime, key, ""); + return NULL; + } + } + else { + rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s " + "-> val=%s", s->name, key, value); + } + if (value[0] != '\0') { + value = select_random_value_part(r, value); + rewritelog(r, 5, "randomly choosen the subvalue `%s'", value); + } + else { + value = NULL; + } + return value; + } + } + } + return NULL; +} + +static char *lookup_map_txtfile(request_rec *r, char *file, char *key) +{ + FILE *fp = NULL; + char line[1024]; + char *value = NULL; + char *cpT; + size_t skip; + char *curkey; + char *curval; + + if ((fp = ap_pfopen(r->pool, file, "r")) == NULL) { + return NULL; + } + + while (fgets(line, sizeof(line), fp) != NULL) { + if (line[0] == '#') + continue; /* ignore comments */ + cpT = line; + curkey = cpT; + skip = strcspn(cpT," \t\r\n"); + if (skip == 0) + continue; /* ignore lines that start with a space, tab, CR, or LF */ + cpT += skip; + *cpT = '\0'; + if (strcmp(curkey, key) != 0) + continue; /* key does not match... */ + + /* found a matching key; now extract and return the value */ + ++cpT; + skip = strspn(cpT, " \t\r\n"); + cpT += skip; + curval = cpT; + skip = strcspn(cpT, " \t\r\n"); + if (skip == 0) + continue; /* no value... */ + cpT += skip; + *cpT = '\0'; + value = ap_pstrdup(r->pool, curval); + break; + } + ap_pfclose(r->pool, fp); + return value; +} + +#ifndef NO_DBM_REWRITEMAP +static char *lookup_map_dbmfile(request_rec *r, char *file, char *key) +{ + DBM *dbmfp = NULL; + datum dbmkey; + datum dbmval; + char *value; + + if (!(dbmfp = dbm_open(file, O_RDONLY, 0666))) { + return NULL; + } + + dbmkey.dptr = key; + dbmkey.dsize = strlen(key); + + dbmval = dbm_fetch(dbmfp, dbmkey); + if (dbmval.dptr) { + value = ap_palloc(r->pool, dbmval.dsize + 1); + memcpy(value, dbmval.dptr, dbmval.dsize); + value[dbmval.dsize] = '\0'; + } + else { + value = NULL; + } + + dbm_close(dbmfp); + + return value; +} +#endif + +static char *lookup_map_program(request_rec *r, int fpin, int fpout, char *key) +{ + char buf[LONG_STRING_LEN]; + char c; + int i; +#ifndef NO_WRITEV + struct iovec iov[2]; +#endif + + /* when `RewriteEngine off' was used in the per-server + * context then the rewritemap-programs were not spawned. + * In this case using such a map (usually in per-dir context) + * is useless because it is not available. + * + * newlines in the key leave bytes in the pipe and cause + * bad things to happen (next map lookup will use the chars + * after the \n instead of the new key etc etc - in other words, + * the Rewritemap falls out of sync with the requests). + */ + if (fpin == -1 || fpout == -1 || strchr(key, '\n')) { + return NULL; + } + + /* take the lock */ + rewritelock_alloc(r); + + /* write out the request key */ +#ifdef NO_WRITEV + write(fpin, key, strlen(key)); + write(fpin, "\n", 1); +#else + iov[0].iov_base = key; + iov[0].iov_len = strlen(key); + iov[1].iov_base = "\n"; + iov[1].iov_len = 1; + writev(fpin, iov, 2); +#endif + + /* read in the response value */ + i = 0; + while (read(fpout, &c, 1) == 1 && (i < LONG_STRING_LEN-1)) { + if (c == '\n') { + break; + } + buf[i++] = c; + } + buf[i] = '\0'; + + /* give the lock back */ + rewritelock_free(r); + + if (strcasecmp(buf, "NULL") == 0) { + return NULL; + } + else { + return ap_pstrdup(r->pool, buf); + } +} + +static char *lookup_map_internal(request_rec *r, + char *(*func)(request_rec *, char *), + char *key) +{ + /* currently we just let the function convert + the key to a corresponding value */ + return func(r, key); +} + +static char *rewrite_mapfunc_toupper(request_rec *r, char *key) +{ + char *value, *cp; + + for (cp = value = ap_pstrdup(r->pool, key); cp != NULL && *cp != '\0'; + cp++) { + *cp = ap_toupper(*cp); + } + return value; +} + +static char *rewrite_mapfunc_tolower(request_rec *r, char *key) +{ + char *value, *cp; + + for (cp = value = ap_pstrdup(r->pool, key); cp != NULL && *cp != '\0'; + cp++) { + *cp = ap_tolower(*cp); + } + return value; +} + +static char *rewrite_mapfunc_escape(request_rec *r, char *key) +{ + char *value; + + value = ap_escape_uri(r->pool, key); + return value; +} + +static char *rewrite_mapfunc_unescape(request_rec *r, char *key) +{ + char *value; + + value = ap_pstrdup(r->pool, key); + ap_unescape_url(value); + return value; +} + +static int rewrite_rand_init_done = 0; + +static void rewrite_rand_init(void) +{ + if (!rewrite_rand_init_done) { + srand((unsigned)(getpid())); + rewrite_rand_init_done = 1; + } + return; +} + +static int rewrite_rand(int l, int h) +{ + rewrite_rand_init(); + + /* Get [0,1) and then scale to the appropriate range. Note that using + * a floating point value ensures that we use all bits of the rand() + * result. Doing an integer modulus would only use the lower-order bits + * which may not be as uniformly random. + */ + return (int)(((double)(rand() % RAND_MAX) / RAND_MAX) * (h - l + 1) + l); +} + +static char *select_random_value_part(request_rec *r, char *value) +{ + char *buf; + int n, i, k; + + /* count number of distinct values */ + for (n = 1, i = 0; value[i] != '\0'; i++) { + if (value[i] == '|') { + n++; + } + } + + /* when only one value we have no option to choose */ + if (n == 1) { + return value; + } + + /* else randomly select one */ + k = rewrite_rand(1, n); + + /* and grep it out */ + for (n = 1, i = 0; value[i] != '\0'; i++) { + if (n == k) { + break; + } + if (value[i] == '|') { + n++; + } + } + buf = ap_pstrdup(r->pool, &value[i]); + for (i = 0; buf[i] != '\0' && buf[i] != '|'; i++) + ; + buf[i] = '\0'; + return buf; +} + + +/* +** +-------------------------------------------------------+ +** | | +** | rewriting logfile support +** | | +** +-------------------------------------------------------+ +*/ + + +static void open_rewritelog(server_rec *s, pool *p) +{ + rewrite_server_conf *conf; + char *fname; + piped_log *pl; + int rewritelog_flags = ( O_WRONLY|O_APPEND|O_CREAT ); +#if defined(NETWARE) + mode_t rewritelog_mode = ( S_IREAD|S_IWRITE ); +#elif defined(WIN32) + mode_t rewritelog_mode = ( _S_IREAD|_S_IWRITE ); +#else + mode_t rewritelog_mode = ( S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH ); +#endif + + conf = ap_get_module_config(s->module_config, &rewrite_module); + + if (conf->rewritelogfile == NULL) { + return; + } + if (*(conf->rewritelogfile) == '\0') { + return; + } + if (conf->rewritelogfp > 0) { + return; /* virtual log shared w/ main server */ + } + + fname = ap_server_root_relative(p, conf->rewritelogfile); + + if (*conf->rewritelogfile == '|') { + if ((pl = ap_open_piped_log(p, conf->rewritelogfile+1)) == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "mod_rewrite: could not open reliable pipe " + "to RewriteLog filter %s", conf->rewritelogfile+1); + exit(1); + } + conf->rewritelogfp = ap_piped_log_write_fd(pl); + } + else if (*conf->rewritelogfile != '\0') { + if ((conf->rewritelogfp = ap_popenf_ex(p, fname, rewritelog_flags, + rewritelog_mode, 1)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + + "mod_rewrite: could not open RewriteLog " + "file %s", fname); + exit(1); + } + } + return; +} + +static void rewritelog(request_rec *r, int level, const char *text, ...) +{ + rewrite_server_conf *conf; + conn_rec *conn; + char *str1; + char str2[512]; + char str3[1024]; + char type[20]; + char redir[20]; + va_list ap; + int i; + request_rec *req; + char *ruser; + const char *rhost; + + va_start(ap, text); + conf = ap_get_module_config(r->server->module_config, &rewrite_module); + conn = r->connection; + + if (conf->rewritelogfp < 0) { + return; + } + if (conf->rewritelogfile == NULL) { + return; + } + if (*(conf->rewritelogfile) == '\0') { + return; + } + + if (level > conf->rewriteloglevel) { + return; + } + + if (conn->user == NULL) { + ruser = "-"; + } + else if (strlen(conn->user) != 0) { + ruser = conn->user; + } + else { + ruser = "\"\""; + } + + rhost = ap_get_remote_host(conn, r->server->module_config, + REMOTE_NOLOOKUP); + if (rhost == NULL) { + rhost = "UNKNOWN-HOST"; + } + + str1 = ap_pstrcat(r->pool, rhost, " ", + (conn->remote_logname != NULL ? + conn->remote_logname : "-"), " ", + ruser, NULL); + ap_vsnprintf(str2, sizeof(str2), text, ap); + + if (r->main == NULL) { + strcpy(type, "initial"); + } + else { + strcpy(type, "subreq"); + } + + for (i = 0, req = r; req->prev != NULL; req = req->prev) { + i++; + } + if (i == 0) { + redir[0] = '\0'; + } + else { + ap_snprintf(redir, sizeof(redir), "/redir#%d", i); + } + + ap_snprintf(str3, sizeof(str3), + "%s %s [%s/sid#%lx][rid#%lx/%s%s] (%d) %s\n", str1, + current_logtime(r), ap_get_server_name(r), + (unsigned long)(r->server), (unsigned long)r, + type, redir, level, str2); + + fd_lock(r, conf->rewritelogfp); + write(conf->rewritelogfp, str3, strlen(str3)); + fd_unlock(r, conf->rewritelogfp); + + va_end(ap); + return; +} + +static char *current_logtime(request_rec *r) +{ + int timz; + struct tm *t; + char tstr[80]; + char sign; + + t = ap_get_gmtoff(&timz); + sign = (timz < 0 ? '-' : '+'); + if (timz < 0) { + timz = -timz; + } + + strftime(tstr, 80, "[%d/%b/%Y:%H:%M:%S ", t); + ap_snprintf(tstr + strlen(tstr), 80-strlen(tstr), "%c%.2d%.2d]", + sign, timz/60, timz%60); + return ap_pstrdup(r->pool, tstr); +} + + + + +/* +** +-------------------------------------------------------+ +** | | +** | rewriting lockfile support +** | | +** +-------------------------------------------------------+ +*/ + +#if defined(NETWARE) +#define REWRITELOCK_MODE ( S_IREAD|S_IWRITE ) +#elif defined(WIN32) +#define REWRITELOCK_MODE ( _S_IREAD|_S_IWRITE ) +#else +#define REWRITELOCK_MODE ( S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH ) +#endif + +static void rewritelock_create(server_rec *s, pool *p) +{ + /* only operate if a lockfile is used */ + if (lockname == NULL || *(lockname) == '\0') { + return; + } + + /* fixup the path, especially for rewritelock_remove() */ + lockname = ap_server_root_relative(p, lockname); + + /* create the lockfile */ + unlink(lockname); + if ((lockfd = ap_popenf_ex(p, lockname, O_WRONLY|O_CREAT, + REWRITELOCK_MODE, 1)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "mod_rewrite: Parent could not create RewriteLock " + "file %s", lockname); + exit(1); + } +#if !defined(OS2) && !defined(WIN32) && !defined(NETWARE) + /* make sure the childs have access to this file */ + if (geteuid() == 0 /* is superuser */) + chown(lockname, ap_user_id, -1 /* no gid change */); +#endif + +#ifdef NETWARE + locking_sem = OpenLocalSemaphore (1); +#endif + + return; +} + +static void rewritelock_open(server_rec *s, pool *p) +{ + /* only operate if a lockfile is used */ + if (lockname == NULL || *(lockname) == '\0') { + return; + } + + /* open the lockfile (once per child) to get a unique fd */ + if ((lockfd = ap_popenf_ex(p, lockname, O_WRONLY, + REWRITELOCK_MODE, 1)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "mod_rewrite: Child could not open RewriteLock " + "file %s", lockname); + exit(1); + } + return; +} + +static void rewritelock_remove(void *data) +{ + /* only operate if a lockfile is used */ + if (lockname == NULL || *(lockname) == '\0') { + return; + } + + /* remove the lockfile */ + unlink(lockname); + lockname = NULL; + lockfd = -1; +#ifdef NETWARE + CloseLocalSemaphore (locking_sem); +#endif + +} + +static void rewritelock_alloc(request_rec *r) +{ + if (lockfd != -1) { + fd_lock(r, lockfd); + } + return; +} + +static void rewritelock_free(request_rec *r) +{ + if (lockfd != -1) { + fd_unlock(r, lockfd); + } + return; +} + + +/* +** +-------------------------------------------------------+ +** | | +** | program map support +** | | +** +-------------------------------------------------------+ +*/ + +static void run_rewritemap_programs(server_rec *s, pool *p) +{ + rewrite_server_conf *conf; + FILE *fpin; + FILE *fpout; + FILE *fperr; + array_header *rewritemaps; + rewritemap_entry *entries; + rewritemap_entry *map; + int i; + int rc; + + conf = ap_get_module_config(s->module_config, &rewrite_module); + + /* If the engine isn't turned on, + * don't even try to do anything. + */ + if (conf->state == ENGINE_DISABLED) { + return; + } + + rewritemaps = conf->rewritemaps; + entries = (rewritemap_entry *)rewritemaps->elts; + for (i = 0; i < rewritemaps->nelts; i++) { + map = &entries[i]; + if (map->type != MAPTYPE_PRG) { + continue; + } + if (map->datafile == NULL + || *(map->datafile) == '\0' + || map->fpin != -1 + || map->fpout != -1 ) { + continue; + } + fpin = NULL; + fpout = NULL; + rc = ap_spawn_child(p, rewritemap_program_child, + (void *)map->datafile, kill_after_timeout, + &fpin, &fpout, &fperr); + if (rc == 0 || fpin == NULL || fpout == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "mod_rewrite: could not fork child for " + "RewriteMap process"); + exit(1); + } + map->fpin = fileno(fpin); + map->fpout = fileno(fpout); + map->fperr = fileno(fperr); + } + return; +} + +/* child process code */ +static int rewritemap_program_child(void *cmd, child_info *pinfo) +{ + int child_pid = 1; + + /* + * Prepare for exec + */ + ap_cleanup_for_exec(); +#ifdef SIGHUP + signal(SIGHUP, SIG_IGN); +#endif + + /* + * Exec() the child program + */ +#if defined(WIN32) + /* MS Windows */ + { + char pCommand[MAX_STRING_LEN]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ap_snprintf(pCommand, sizeof(pCommand), "%s /C %s", SHELL_PATH, cmd); + + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + si.wShowWindow = SW_HIDE; + si.hStdInput = pinfo->hPipeInputRead; + si.hStdOutput = pinfo->hPipeOutputWrite; + si.hStdError = pinfo->hPipeErrorWrite; + + if (CreateProcess(NULL, pCommand, NULL, NULL, TRUE, 0, + NULL, NULL, &si, &pi)) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + child_pid = pi.dwProcessId; + } + } +#elif defined(NETWARE) + /* Need something here!!! Spawn???? */ +#elif defined(OS2) + /* IBM OS/2 */ + execl(SHELL_PATH, SHELL_PATH, "/c", (char *)cmd, NULL); +#else + /* Standard Unix */ + execl(SHELL_PATH, SHELL_PATH, "-c", (char *)cmd, NULL); +#endif + return(child_pid); +} + + + + +/* +** +-------------------------------------------------------+ +** | | +** | environment variable support +** | | +** +-------------------------------------------------------+ +*/ + + +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_PORT") == 0) { + return ap_psprintf(r->pool, "%d", + ntohs(r->connection->remote_addr.sin_port)); + } + 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 = ap_document_root(r); + } + else if (strcasecmp(var, "SERVER_ADMIN") == 0) { + result = r->server->server_admin; + } + else if (strcasecmp(var, "SERVER_NAME") == 0) { + result = 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; + rewritelog(r, 1, "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 */ \ + rewritelog(r, 5, "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; +} + + + + +/* +** +-------------------------------------------------------+ +** | | +** | caching support +** | | +** +-------------------------------------------------------+ +*/ + + +static cache *init_cache(pool *p) +{ + cache *c; + + c = (cache *)ap_palloc(p, sizeof(cache)); + c->pool = ap_make_sub_pool(p); + c->lists = ap_make_array(c->pool, 2, sizeof(cachelist)); + return c; +} + +static void set_cache_string(cache *c, char *res, int mode, time_t t, + char *key, char *value) +{ + cacheentry ce; + + ce.time = t; + ce.key = key; + ce.value = value; + store_cache_string(c, res, &ce); + return; +} + +static char *get_cache_string(cache *c, char *res, int mode, + time_t t, char *key) +{ + cacheentry *ce; + + ce = retrieve_cache_string(c, res, key); + if (ce == NULL) { + return NULL; + } + if (mode & CACHEMODE_TS) { + if (t != ce->time) { + return NULL; + } + } + else if (mode & CACHEMODE_TTL) { + if (t > ce->time) { + return NULL; + } + } + return ce->value; +} + +static int cache_tlb_hash(char *key) +{ + unsigned long n; + char *p; + + n = 0; + for (p = key; *p != '\0'; p++) { + n = ((n << 5) + n) ^ (unsigned long)(*p++); + } + + return (int)(n % CACHE_TLB_ROWS); +} + +static cacheentry *cache_tlb_lookup(cachetlbentry *tlb, cacheentry *elt, + char *key) +{ + int ix = cache_tlb_hash(key); + int i; + int j; + + for (i=0; i < CACHE_TLB_COLS; ++i) { + j = tlb[ix].t[i]; + if (j < 0) + return NULL; + if (strcmp(elt[j].key, key) == 0) + return &elt[j]; + } + return NULL; +} + +static void cache_tlb_replace(cachetlbentry *tlb, cacheentry *elt, + cacheentry *e) +{ + int ix = cache_tlb_hash(e->key); + int i; + + tlb = &tlb[ix]; + + for (i=1; i < CACHE_TLB_COLS; ++i) + tlb->t[i] = tlb->t[i-1]; + + tlb->t[0] = e - elt; +} + +static void store_cache_string(cache *c, char *res, cacheentry *ce) +{ + int i; + int j; + cachelist *l; + cacheentry *e; + cachetlbentry *t; + int found_list; + + found_list = 0; + /* first try to edit an existing entry */ + for (i = 0; i < c->lists->nelts; i++) { + l = &(((cachelist *)c->lists->elts)[i]); + if (strcmp(l->resource, res) == 0) { + found_list = 1; + + e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts, + (cacheentry *)l->entries->elts, ce->key); + if (e != NULL) { + e->time = ce->time; + e->value = ap_pstrdup(c->pool, ce->value); + return; + } + + for (j = 0; j < l->entries->nelts; j++) { + e = &(((cacheentry *)l->entries->elts)[j]); + if (strcmp(e->key, ce->key) == 0) { + e->time = ce->time; + e->value = ap_pstrdup(c->pool, ce->value); + cache_tlb_replace((cachetlbentry *)l->tlb->elts, + (cacheentry *)l->entries->elts, e); + return; + } + } + } + } + + /* create a needed new list */ + if (!found_list) { + l = ap_push_array(c->lists); + l->resource = ap_pstrdup(c->pool, res); + l->entries = ap_make_array(c->pool, 2, sizeof(cacheentry)); + l->tlb = ap_make_array(c->pool, CACHE_TLB_ROWS, + sizeof(cachetlbentry)); + for (i=0; i<CACHE_TLB_ROWS; ++i) { + t = &((cachetlbentry *)l->tlb->elts)[i]; + for (j=0; j<CACHE_TLB_COLS; ++j) + t->t[j] = -1; + } + } + + /* create the new entry */ + for (i = 0; i < c->lists->nelts; i++) { + l = &(((cachelist *)c->lists->elts)[i]); + if (strcmp(l->resource, res) == 0) { + e = ap_push_array(l->entries); + e->time = ce->time; + e->key = ap_pstrdup(c->pool, ce->key); + e->value = ap_pstrdup(c->pool, ce->value); + cache_tlb_replace((cachetlbentry *)l->tlb->elts, + (cacheentry *)l->entries->elts, e); + return; + } + } + + /* not reached, but when it is no problem... */ + return; +} + +static cacheentry *retrieve_cache_string(cache *c, char *res, char *key) +{ + int i; + int j; + cachelist *l; + cacheentry *e; + + for (i = 0; i < c->lists->nelts; i++) { + l = &(((cachelist *)c->lists->elts)[i]); + if (strcmp(l->resource, res) == 0) { + + e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts, + (cacheentry *)l->entries->elts, key); + if (e != NULL) + return e; + + for (j = 0; j < l->entries->nelts; j++) { + e = &(((cacheentry *)l->entries->elts)[j]); + if (strcmp(e->key, key) == 0) { + return e; + } + } + } + } + return NULL; +} + + + + +/* +** +-------------------------------------------------------+ +** | | +** | misc functions +** | | +** +-------------------------------------------------------+ +*/ + +/* + * substitute the prefix path 'match' in 'input' with 'subst' + * (think of RewriteBase which substitutes the physical path with + * the virtual path) + */ + +static char *subst_prefix_path(request_rec *r, char *input, char *match, + const char *subst) +{ + size_t len = strlen(match); + + if (len && match[len - 1] == '/') { + --len; + } + + if (!strncmp(input, match, len) && input[len++] == '/') { + size_t slen, outlen; + char *output; + + rewritelog(r, 5, "strip matching prefix: %s -> %s", input, input+len); + + slen = strlen(subst); + if (slen && subst[slen - 1] != '/') { + ++slen; + } + + outlen = strlen(input) + slen - len; + output = ap_palloc(r->pool, outlen + 1); /* don't forget the \0 */ + + memcpy(output, subst, slen); + if (slen && !output[slen-1]) { + output[slen-1] = '/'; + } + memcpy(output+slen, input+len, outlen - slen); + output[outlen] = '\0'; + + rewritelog(r, 4, "add subst prefix: %s -> %s", input+len, output); + + return output; + } + + /* prefix didn't match */ + return input; +} + + +/* +** +** own command line parser which don't have the '\\' problem +** +*/ + +static int parseargline(char *str, char **a1, char **a2, char **a3) +{ + char *cp; + int isquoted; + +#define SKIP_WHITESPACE(cp) \ + for ( ; *cp == ' ' || *cp == '\t'; ) { \ + cp++; \ + }; + +#define CHECK_QUOTATION(cp,isquoted) \ + isquoted = 0; \ + if (*cp == '"') { \ + isquoted = 1; \ + cp++; \ + } + +#define DETERMINE_NEXTSTRING(cp,isquoted) \ + for ( ; *cp != '\0'; cp++) { \ + if ( (isquoted && (*cp == ' ' || *cp == '\t')) \ + || (*cp == '\\' && (*(cp+1) == ' ' || *(cp+1) == '\t'))) { \ + cp++; \ + continue; \ + } \ + if ( (!isquoted && (*cp == ' ' || *cp == '\t')) \ + || (isquoted && *cp == '"') ) { \ + break; \ + } \ + } + + cp = str; + SKIP_WHITESPACE(cp); + + /* determine first argument */ + CHECK_QUOTATION(cp, isquoted); + *a1 = cp; + DETERMINE_NEXTSTRING(cp, isquoted); + if (*cp == '\0') { + return 1; + } + *cp++ = '\0'; + + SKIP_WHITESPACE(cp); + + /* determine second argument */ + CHECK_QUOTATION(cp, isquoted); + *a2 = cp; + DETERMINE_NEXTSTRING(cp, isquoted); + if (*cp == '\0') { + *cp++ = '\0'; + *a3 = NULL; + return 0; + } + *cp++ = '\0'; + + SKIP_WHITESPACE(cp); + + /* again check if there are only two arguments */ + if (*cp == '\0') { + *cp++ = '\0'; + *a3 = NULL; + return 0; + } + + /* determine second argument */ + CHECK_QUOTATION(cp, isquoted); + *a3 = cp; + DETERMINE_NEXTSTRING(cp, isquoted); + *cp++ = '\0'; + + return 0; +} + + +static void add_env_variable(request_rec *r, char *s) +{ + char var[MAX_STRING_LEN]; + char val[MAX_STRING_LEN]; + char *cp; + int n; + + if ((cp = strchr(s, ':')) != NULL) { + n = ((cp-s) > MAX_STRING_LEN-1 ? MAX_STRING_LEN-1 : (cp-s)); + memcpy(var, s, n); + var[n] = '\0'; + ap_cpystrn(val, cp+1, sizeof(val)); + ap_table_set(r->subprocess_env, var, val); + rewritelog(r, 5, "setting env variable '%s' to '%s'", var, val); + } +} + + +/* +** +** check that a subrequest won't cause infinite recursion +** +*/ + +static int subreq_ok(request_rec *r) +{ + /* + * either not in a subrequest, or in a subrequest + * and URIs aren't NULL and sub/main URIs differ + */ + return (r->main == NULL || + (r->main->uri != NULL && r->uri != NULL && + strcmp(r->main->uri, r->uri) != 0)); +} + + +/* +** +** stat() for only the prefix of a path +** +*/ + +static int prefix_stat(const char *path, ap_pool *pool) +{ + const char *curpath = path; + char *root; + char *slash; + char *statpath; + struct stat sb; + + if (!ap_os_is_path_absolute(curpath)) { + return 0; + } + + /* need to be a bit tricky here. + * Actually we're looking for the first path segment ... + */ + if (*curpath != '/') { + /* be safe: +1 = '\0'; +1 = possible additional '\0' + * from ap_make_dirstr_prefix + */ + root = ap_palloc(pool, strlen(curpath) + 2); + slash = ap_make_dirstr_prefix(root, curpath, 1); + curpath += strlen(root); + } + else { +#if defined(HAVE_UNC_PATHS) + /* Check for UNC names. */ + if (curpath[1] == '/') { + slash = strchr(curpath + 2, '/'); + + /* XXX not sure here. Be safe for now */ + if (!slash) { + return 0; + } + root = ap_pstrndup(pool, curpath, slash - curpath + 1); + curpath += strlen(root); + } + else { +#endif /* UNC */ + root = "/"; + ++curpath; +#if defined(HAVE_UNC_PATHS) + } +#endif + } + + /* let's recognize slashes only, the mod_rewrite semantics are opaque + * enough. + */ + if ((slash = strchr(curpath, '/')) != NULL) { + statpath = ap_pstrcat(pool, root, + ap_pstrndup(pool, curpath, slash - curpath), + NULL); + } + else { + statpath = ap_pstrcat(pool, root, curpath, NULL); + } + + if (stat(statpath, &sb) == 0) { + return 1; + } + + return 0; +} + + +/* +** +** File locking +** +*/ + +#ifdef USE_FCNTL +static struct flock lock_it; +static struct flock unlock_it; +#endif + +static void fd_lock(request_rec *r, int fd) +{ + int rc; + +#ifdef USE_FCNTL + lock_it.l_whence = SEEK_SET; /* from current point */ + lock_it.l_start = 0; /* -"- */ + lock_it.l_len = 0; /* until end of file */ + lock_it.l_type = F_WRLCK; /* set exclusive/write lock */ + lock_it.l_pid = 0; /* pid not actually interesting */ + + while ( ((rc = fcntl(fd, F_SETLKW, &lock_it)) < 0) + && (errno == EINTR) ) { + continue; + } +#endif +#ifdef USE_FLOCK + while ( ((rc = flock(fd, LOCK_EX)) < 0) + && (errno == EINTR) ) { + continue; + } +#endif +#ifdef USE_LOCKING + /* Lock the first byte, always, assume we want to append + and seek to the end afterwards */ + lseek(fd, 0, SEEK_SET); + rc = _locking(fd, _LK_LOCK, 1); + lseek(fd, 0, SEEK_END); +#endif +#ifdef NETWARE + if ((locking_sem != 0) && (TimedWaitOnLocalSemaphore (locking_sem, 10000) != 0)) + rc = -1; + else + rc = 1; +#endif + + if (rc < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_rewrite: failed to lock file descriptor"); + exit(1); + } + return; +} + +static void fd_unlock(request_rec *r, int fd) +{ + int rc; + +#ifdef USE_FCNTL + unlock_it.l_whence = SEEK_SET; /* from current point */ + unlock_it.l_start = 0; /* -"- */ + unlock_it.l_len = 0; /* until end of file */ + unlock_it.l_type = F_UNLCK; /* unlock */ + unlock_it.l_pid = 0; /* pid not actually interesting */ + + rc = fcntl(fd, F_SETLKW, &unlock_it); +#endif +#ifdef USE_FLOCK + rc = flock(fd, LOCK_UN); +#endif +#ifdef USE_LOCKING + lseek(fd, 0, SEEK_SET); + rc = _locking(fd, _LK_UNLCK, 1); + lseek(fd, 0, SEEK_END); +#endif +#ifdef NETWARE + if (locking_sem) + SignalLocalSemaphore (locking_sem); + rc = 1; +#endif + + if (rc < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "mod_rewrite: failed to unlock file descriptor"); + exit(1); + } +} + +/* +** +** Lexicographic Compare +** +*/ + +static int compare_lexicography(char *cpNum1, char *cpNum2) +{ + int i; + int n1, n2; + + n1 = strlen(cpNum1); + n2 = strlen(cpNum2); + if (n1 > n2) { + return 1; + } + if (n1 < n2) { + return -1; + } + for (i = 0; i < n1; i++) { + if (cpNum1[i] > cpNum2[i]) { + return 1; + } + if (cpNum1[i] < cpNum2[i]) { + return -1; + } + } + return 0; +} + +/* +** +** Bracketed expression handling +** s points after the opening bracket +** +*/ + +static char *find_closing_bracket(char *s, int left, int right) +{ + int depth; + + for (depth = 1; *s; ++s) { + if (*s == right && --depth == 0) { + return s; + } + else if (*s == left) { + ++depth; + } + } + return NULL; +} + +static char *find_char_in_brackets(char *s, int c, int left, int right) +{ + int depth; + + for (depth = 1; *s; ++s) { + if (*s == c && depth == 1) { + return s; + } + else if (*s == right && --depth == 0) { + return NULL; + } + else if (*s == left) { + ++depth; + } + } + return NULL; +} + +/*EOF*/ diff --git a/APACHE_1_3_42/src/modules/standard/mod_rewrite.h b/APACHE_1_3_42/src/modules/standard/mod_rewrite.h new file mode 100644 index 0000000000..5a853abec1 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_rewrite.h @@ -0,0 +1,475 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef _MOD_REWRITE_H +#define _MOD_REWRITE_H 1 + +/* +** _ _ _ +** _ __ ___ ___ __| | _ __ _____ ___ __(_) |_ ___ +** | '_ ` _ \ / _ \ / _` | | '__/ _ \ \ /\ / / '__| | __/ _ \ +** | | | | | | (_) | (_| | | | | __/\ V V /| | | | || __/ +** |_| |_| |_|\___/ \__,_|___|_| \___| \_/\_/ |_| |_|\__\___| +** |_____| +** +** URL Rewriting Module +** +** This module uses a rule-based rewriting engine (based on a +** regular-expression parser) to rewrite requested URLs on the fly. +** +** It supports an unlimited number of additional rule conditions (which can +** operate on a lot of variables, even on HTTP headers) for granular +** matching and even external database lookups (either via plain text +** tables, DBM hash files or even external processes) for advanced URL +** substitution. +** +** It operates on the full URLs (including the PATH_INFO part) both in +** per-server context (httpd.conf) and per-dir context (.htaccess) and even +** can generate QUERY_STRING parts on result. The rewriting result finally +** can lead to internal subprocessing, external request redirection or even +** to internal proxy throughput. +** +** This module was originally written in April 1996 and +** gifted exclusively to the The Apache Group in July 1997 by +** +** Ralf S. Engelschall +** rse@engelschall.com +** www.engelschall.com +*/ + + + /* Include from the underlaying Unix system ... */ +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <time.h> +#include <signal.h> +#include <errno.h> +#include <ctype.h> +#ifndef NETWARE +#include <sys/types.h> +#endif +#include <sys/stat.h> + + /* Include from the Apache server ... */ +#define CORE_PRIVATE +#include "httpd.h" +#include "http_config.h" +#include "http_conf_globals.h" +#include "http_request.h" +#include "http_core.h" +#include "http_log.h" +#include "http_vhost.h" + + /* + * 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 "rewrite-Vary" +#define VARY_KEY_THIS "rewrite-Vary-this" + + /* The NDBM support: + * We support only NDBM files. + * But we have to stat the file for the mtime, + * so we also need to know the file extension + */ +#ifndef NO_DBM_REWRITEMAP +#include <ndbm.h> +#if defined(DBM_SUFFIX) +#define NDBM_FILE_SUFFIX DBM_SUFFIX +#elif defined(__FreeBSD__) || (defined(DB_LOCK) && defined(DB_SHMEM)) +#define NDBM_FILE_SUFFIX ".db" +#else +#define NDBM_FILE_SUFFIX ".pag" +#endif +#endif + + + /* The locking support: + * Try to determine whether we should use fcntl() or flock(). + * Would be better ap_config.h could provide this... :-( + * Small monkey business to ensure that fcntl is preferred, + * unless we specified USE_FLOCK_SERIALIZED_ACCEPT during compile. + */ +#if defined(HAVE_FCNTL_SERIALIZED_ACCEPT) && !defined(USE_FLOCK_SERIALIZED_ACCEPT) +#define USE_FCNTL 1 +#include <fcntl.h> +#elif defined(HAVE_FLOCK_SERIALIZED_ACCEPT) +#define USE_FLOCK 1 +#include <sys/file.h> +#endif +#if !defined(USE_FCNTL) && !defined(USE_FLOCK) +#define USE_FLOCK 1 +#if !defined(MPE) && !defined(WIN32) && !defined(__TANDEM) && !defined(NETWARE) +#include <sys/file.h> +#endif +#ifndef LOCK_UN +#undef USE_FLOCK +#define USE_FCNTL 1 +#include <fcntl.h> +#endif +#endif +#if defined(AIX) || defined(AIXIA64) +#undef USE_FLOCK +#define USE_FCNTL 1 +#include <fcntl.h> +#endif +#ifdef WIN32 +#undef USE_FCNTL +#define USE_LOCKING +#include <sys/locking.h> +#endif + + +/* +** +** Some defines +** +*/ + +#define ENVVAR_SCRIPT_URL "SCRIPT_URL" +#define ENVVAR_SCRIPT_URI "SCRIPT_URI" + +#ifndef SUPPORT_DBM_REWRITEMAP +#define SUPPORT_DBM_REWRITEMAP 0 +#endif + +#define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype" + +#define CONDFLAG_NONE 1<<0 +#define CONDFLAG_NOCASE 1<<1 +#define CONDFLAG_NOTMATCH 1<<2 +#define CONDFLAG_ORNEXT 1<<3 + +#define RULEFLAG_NONE 1<<0 +#define RULEFLAG_FORCEREDIRECT 1<<1 +#define RULEFLAG_LASTRULE 1<<2 +#define RULEFLAG_NEWROUND 1<<3 +#define RULEFLAG_CHAIN 1<<4 +#define RULEFLAG_IGNOREONSUBREQ 1<<5 +#define RULEFLAG_NOTMATCH 1<<6 +#define RULEFLAG_PROXY 1<<7 +#define RULEFLAG_PASSTHROUGH 1<<8 +#define RULEFLAG_FORBIDDEN 1<<9 +#define RULEFLAG_GONE 1<<10 +#define RULEFLAG_QSAPPEND 1<<11 +#define RULEFLAG_NOCASE 1<<12 +#define RULEFLAG_NOESCAPE 1<<13 + +#define ACTION_NORMAL 1<<0 +#define ACTION_NOESCAPE 1<<1 + +#define MAPTYPE_TXT 1<<0 +#define MAPTYPE_DBM 1<<1 +#define MAPTYPE_PRG 1<<2 +#define MAPTYPE_INT 1<<3 +#define MAPTYPE_RND 1<<4 + +#define ENGINE_DISABLED 1<<0 +#define ENGINE_ENABLED 1<<1 + +#define OPTION_NONE 1<<0 +#define OPTION_INHERIT 1<<1 + +#define CACHEMODE_TS 1<<0 +#define CACHEMODE_TTL 1<<1 + +#define CACHE_TLB_ROWS 1024 +#define CACHE_TLB_COLS 4 + +#ifndef FALSE +#define FALSE 0 +#define TRUE !FALSE +#endif + +#ifndef NO +#define NO FALSE +#define YES TRUE +#endif + +#ifndef RAND_MAX +#define RAND_MAX 32767 +#endif + +#ifndef LONG_STRING_LEN +#define LONG_STRING_LEN 2048 +#endif + +#define MAX_ENV_FLAGS 15 + +/* default maximum number of internal redirects */ +#define REWRITE_REDIRECT_LIMIT 10 + +/* +** +** our private data structures we handle with +** +*/ + + /* the list structures for holding the mapfile information + * and the rewrite rules + */ +typedef struct { + char *name; /* the name of the map */ + char *datafile; /* filename for map data files */ + char *checkfile; /* filename to check for map existence */ + int type; /* the type of the map */ + int fpin; /* in file pointer for program maps */ + int fpout; /* out file pointer for program maps */ + int fperr; /* err file pointer for program maps */ + char *(*func)(request_rec *, /* function pointer for internal maps */ + char *); + char *cachename; /* name for the cache */ +} rewritemap_entry; + +typedef struct { + char *input; /* Input string of RewriteCond */ + char *pattern; /* the RegExp pattern string */ + regex_t *regexp; + int flags; /* Flags which control the match */ +} rewritecond_entry; + +typedef struct { + array_header *rewriteconds; /* the corresponding RewriteCond entries */ + char *pattern; /* the RegExp pattern string */ + regex_t *regexp; /* the RegExp pattern compilation */ + char *output; /* the Substitution string */ + int flags; /* Flags which control the substitution */ + char *forced_mimetype; /* forced MIME type of substitution */ + int forced_responsecode; /* forced HTTP redirect response status */ + char *env[MAX_ENV_FLAGS+1]; /* added environment variables */ + int skip; /* number of next rules to skip */ +} rewriterule_entry; + + + /* the per-server or per-virtual-server configuration + * statically generated once on startup for every server + */ +typedef struct { + int state; /* the RewriteEngine state */ + int options; /* the RewriteOption state */ + char *rewritelogfile; /* the RewriteLog filename */ + int rewritelogfp; /* the RewriteLog open filepointer */ + int rewriteloglevel; /* the RewriteLog level of verbosity */ + array_header *rewritemaps; /* the RewriteMap entries */ + array_header *rewriteconds; /* the RewriteCond entries (temporary) */ + array_header *rewriterules; /* the RewriteRule entries */ + server_rec *server; /* the corresponding server indicator */ + int redirect_limit; /* maximum number of internal redirects */ +} rewrite_server_conf; + + + /* the per-directory configuration + * generated on-the-fly by Apache server for current request + */ +typedef struct { + int state; /* the RewriteEngine state */ + int options; /* the RewriteOption state */ + array_header *rewriteconds; /* the RewriteCond entries (temporary) */ + array_header *rewriterules; /* the RewriteRule entries */ + char *directory; /* the directory where it applies */ + char *baseurl; /* the base-URL where it applies */ + int redirect_limit; /* maximum number of internal redirects */ +} rewrite_perdir_conf; + + /* the per-request configuration + */ +typedef struct { + int redirects; /* current number of redirects */ + int redirect_limit; /* maximum number of redirects */ +} rewrite_request_conf; + + + /* the cache structures, + * a 4-way hash table with LRU functionality + */ +typedef struct cacheentry { + time_t time; + char *key; + char *value; +} cacheentry; + +typedef struct tlbentry { + int t[CACHE_TLB_COLS]; +} cachetlbentry; + +typedef struct cachelist { + char *resource; + array_header *entries; + array_header *tlb; +} cachelist; + +typedef struct cache { + pool *pool; + array_header *lists; +} cache; + + + /* the regex structure for the + * substitution of backreferences + */ +typedef struct backrefinfo { + char *source; + int nsub; + regmatch_t regmatch[AP_MAX_REG_MATCH]; +} backrefinfo; + + +/* +** +** forward declarations +** +*/ + + /* config structure handling */ +static void *config_server_create(pool *p, server_rec *s); +static void *config_server_merge (pool *p, void *basev, void *overridesv); +static void *config_perdir_create(pool *p, char *path); +static void *config_perdir_merge (pool *p, void *basev, void *overridesv); + + /* config directive handling */ +static const char *cmd_rewriteengine(cmd_parms *cmd, + rewrite_perdir_conf *dconf, int flag); +static const char *cmd_rewriteoptions(cmd_parms *cmd, + void *in_dconf, + const char *option); +static const char *cmd_rewritelog (cmd_parms *cmd, void *dconf, char *a1); +static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, char *a1); +static const char *cmd_rewritemap (cmd_parms *cmd, void *dconf, char *a1, + char *a2); +static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, char *a1); +static const char *cmd_rewritebase(cmd_parms *cmd, rewrite_perdir_conf *dconf, + char *a1); +static const char *cmd_rewritecond(cmd_parms *cmd, rewrite_perdir_conf *dconf, + char *str); +static const char *cmd_rewritecond_parseflagfield(pool *p, + rewritecond_entry *new, + char *str); +static const char *cmd_rewritecond_setflag(pool *p, rewritecond_entry *cfg, + char *key, char *val); +static const char *cmd_rewriterule(cmd_parms *cmd, rewrite_perdir_conf *dconf, + char *str); +static const char *cmd_rewriterule_parseflagfield(pool *p, + rewriterule_entry *new, + char *str); +static const char *cmd_rewriterule_setflag(pool *p, rewriterule_entry *cfg, + char *key, char *val); + + /* initialisation */ +static void init_module(server_rec *s, pool *p); +static void init_child(server_rec *s, pool *p); + + /* runtime hooks */ +static int hook_uri2file (request_rec *r); +static int hook_mimetype (request_rec *r); +static int hook_fixup (request_rec *r); +static int handler_redirect(request_rec *r); + + /* rewriting engine */ +static int apply_rewrite_list(request_rec *r, array_header *rewriterules, + char *perdir); +static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p, + char *perdir); +static int apply_rewrite_cond(request_rec *r, rewritecond_entry *p, + char *perdir, backrefinfo *briRR, + backrefinfo *briRC); + +static void do_expand(request_rec *r, char *input, char *buffer, int nbuf, + backrefinfo *briRR, backrefinfo *briRC); +static void do_expand_env(request_rec *r, char *env[], + backrefinfo *briRR, backrefinfo *briRC); + + /* URI transformation function */ +static void splitout_queryargs(request_rec *r, int qsappend); +static void fully_qualify_uri(request_rec *r); +static void reduce_uri(request_rec *r); +static unsigned is_absolute_uri(char *uri); +static char *escape_absolute_uri(ap_pool *p, char *uri, unsigned scheme); +static char *expand_tildepaths(request_rec *r, char *uri); + + /* rewrite map support functions */ +static char *lookup_map(request_rec *r, char *name, char *key); +static char *lookup_map_txtfile(request_rec *r, char *file, char *key); +#ifndef NO_DBM_REWRITEMAP +static char *lookup_map_dbmfile(request_rec *r, char *file, char *key); +#endif +static char *lookup_map_program(request_rec *r, int fpin, + int fpout, char *key); +static char *lookup_map_internal(request_rec *r, + char *(*func)(request_rec *r, char *key), + char *key); +static char *rewrite_mapfunc_toupper(request_rec *r, char *key); +static char *rewrite_mapfunc_tolower(request_rec *r, char *key); +static char *rewrite_mapfunc_escape(request_rec *r, char *key); +static char *rewrite_mapfunc_unescape(request_rec *r, char *key); +static char *select_random_value_part(request_rec *r, char *value); +static void rewrite_rand_init(void); +static int rewrite_rand(int l, int h); + + /* rewriting logfile support */ +static void open_rewritelog(server_rec *s, pool *p); +static void rewritelog(request_rec *r, int level, const char *text, ...) + __attribute__((format(printf,3,4))); +static char *current_logtime(request_rec *r); + + /* rewriting lockfile support */ +static void rewritelock_create(server_rec *s, pool *p); +static void rewritelock_open(server_rec *s, pool *p); +static void rewritelock_remove(void *data); +static void rewritelock_alloc(request_rec *r); +static void rewritelock_free(request_rec *r); + + /* program map support */ +static void run_rewritemap_programs(server_rec *s, pool *p); +static int rewritemap_program_child(void *cmd, child_info *pinfo); + + /* env variable support */ +static char *lookup_variable(request_rec *r, char *var); +static char *lookup_header(request_rec *r, const char *name); + + /* caching functions */ +static cache *init_cache(pool *p); +static char *get_cache_string(cache *c, char *res, int mode, time_t mtime, + char *key); +static void set_cache_string(cache *c, char *res, int mode, time_t mtime, + char *key, char *value); +static cacheentry *retrieve_cache_string(cache *c, char *res, char *key); +static void store_cache_string(cache *c, char *res, cacheentry *ce); + + /* misc functions */ +static char *subst_prefix_path(request_rec *r, char *input, char *match, + const char *subst); +static int parseargline(char *str, char **a1, char **a2, char **a3); +static int prefix_stat(const char *path, ap_pool *pool); +static void add_env_variable(request_rec *r, char *s); +static int subreq_ok(request_rec *r); +static int is_redirect_limit_exceeded(request_rec *r); + + /* File locking */ +static void fd_lock(request_rec *r, int fd); +static void fd_unlock(request_rec *r, int fd); + + /* Lexicographic Comparison */ +static int compare_lexicography(char *cpNum1, char *cpNum2); + + /* Bracketed expression handling */ +static char *find_closing_bracket(char *s, int left, int right); +static char *find_char_in_brackets(char *s, int c, int left, int right); + +#endif /* _MOD_REWRITE_H */ + +/*EOF*/ diff --git a/APACHE_1_3_42/src/modules/standard/mod_setenvif.c b/APACHE_1_3_42/src/modules/standard/mod_setenvif.c new file mode 100644 index 0000000000..64136dee8f --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_setenvif.c @@ -0,0 +1,433 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_setenvif.c + * Set environment variables based on matching request headers or + * attributes against regex strings + * + * Paul Sutton <paul@ukweb.com> 27 Oct 1996 + * Based on mod_browser by Alexei Kosut <akosut@organic.com> + */ + +/* + * Used to set environment variables based on the incoming request headers, + * or some selected other attributes of the request (e.g., the remote host + * name). + * + * Usage: + * + * SetEnvIf name regex var ... + * + * where name is either a HTTP request header name, or one of the + * special values (see below). The 'value' of the header (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 + * environment variables listed as var ... are set. Each var can + * be in one of three formats: var, which sets the named variable + * (the value value "1"); var=value, which sets the variable to + * the given value; or !var, which unsets the variable is it has + * been previously set. + * + * Normally the strings are compared with regard to case. To ignore + * case, use the directive SetEnvIfNoCase instead. + * + * Special values for 'name' are: + * + * server_addr IP address of interface on which request arrived + * (analogous to SERVER_ADDR set in ap_add_common_vars()) + * remote_host Remote host name (if available) + * remote_addr Remote IP address + * request_method Request method (GET, POST, etc) + * request_uri Requested URI + * + * Examples: + * + * To set the enviroment variable LOCALHOST if the client is the local + * machine: + * + * SetEnvIf remote_addr 127.0.0.1 LOCALHOST + * + * To set LOCAL if the client is the local host, or within our company's + * domain (192.168.10): + * + * SetEnvIf remote_addr 192.168.10. LOCAL + * SetEnvIf remote_addr 127.0.0.1 LOCALHOST + * + * This could be written as: + * + * SetEnvIf remote_addr (127.0.0.1|192.168.10.) LOCAL + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" + +enum special { + SPECIAL_NOT, + SPECIAL_REMOTE_ADDR, + SPECIAL_REMOTE_HOST, + SPECIAL_REQUEST_URI, + SPECIAL_REQUEST_METHOD, + SPECIAL_REQUEST_PROTOCOL, + SPECIAL_SERVER_ADDR +}; +typedef struct { + char *name; /* header name */ + char *regex; /* regex to match against */ + regex_t *preg; /* compiled regex */ + table *features; /* env vars to set (or unset) */ + ENUM_BITFIELD( /* is it a "special" header ? */ + enum special, + special_type,4); + unsigned icase : 1; /* ignoring case? */ +} sei_entry; + +typedef struct { + array_header *conditionals; +} sei_cfg_rec; + +module MODULE_VAR_EXPORT setenvif_module; + +/* + * These routines, the create- and merge-config functions, are called + * for both the server-wide and the per-directory contexts. This is + * because the different definitions are used at different times; the + * server-wide ones are used in the post-read-request phase, and the + * per-directory ones are used during the header-parse phase (after + * the URI has been mapped to a file and we have anything from the + * .htaccess file and <Directory> and <Files> containers). + */ +static void *create_setenvif_config(pool *p) +{ + sei_cfg_rec *new = (sei_cfg_rec *) ap_palloc(p, sizeof(sei_cfg_rec)); + + new->conditionals = ap_make_array(p, 20, sizeof(sei_entry)); + return (void *) new; +} + +static void *create_setenvif_config_svr(pool *p, server_rec *dummy) +{ + return create_setenvif_config(p); +} + +static void *create_setenvif_config_dir(pool *p, char *dummy) +{ + return create_setenvif_config(p); +} + +static void *merge_setenvif_config(pool *p, void *basev, void *overridesv) +{ + sei_cfg_rec *a = ap_pcalloc(p, sizeof(sei_cfg_rec)); + sei_cfg_rec *base = basev, *overrides = overridesv; + + a->conditionals = ap_append_arrays(p, base->conditionals, + overrides->conditionals); + return a; +} + +/* + * any non-NULL magic constant will do... used to indicate if REG_ICASE should + * be used + */ +#define ICASE_MAGIC ((void *)(&setenvif_module)) +#define SEI_MAGIC_HEIRLOOM "setenvif-phase-flag" + +static const char *add_setenvif_core(cmd_parms *cmd, void *mconfig, + char *fname, const char *args) +{ + char *regex; + const char *feature; + sei_cfg_rec *sconf; + sei_entry *new; + sei_entry *entries; + char *var; + int i; + int beenhere = 0; + unsigned icase; + int perdir; + + /* + * Determine from our context into which record to put the entry. + * cmd->path == NULL means we're in server-wide context; otherwise, + * we're dealing with a per-directory setting. + */ + perdir = (cmd->path != NULL); + sconf = perdir + ? (sei_cfg_rec *) mconfig + : (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) { + return ap_pstrcat(cmd->pool, "Missing regular expression for ", + cmd->cmd->name, NULL); + } + + /* + * If we've already got a sei_entry with the same name we want to + * just copy the name pointer... so that later on we can compare + * two header names just by comparing the pointers. + */ + + for (i = 0; i < sconf->conditionals->nelts; ++i) { + new = &entries[i]; + if (!strcasecmp(new->name, fname)) { + fname = new->name; + break; + } + } + + /* if the last entry has an identical headername and regex then + * merge with it + */ + i = sconf->conditionals->nelts - 1; + icase = cmd->info == ICASE_MAGIC; + if (i < 0 + || entries[i].name != fname + || entries[i].icase != icase + || strcmp(entries[i].regex, regex)) { + + /* no match, create a new entry */ + + new = ap_push_array(sconf->conditionals); + new->name = fname; + new->regex = regex; + new->icase = icase; + new->preg = ap_pregcomp(cmd->pool, regex, + (REG_EXTENDED | REG_NOSUB + | (icase ? REG_ICASE : 0))); + if (new->preg == NULL) { + return ap_pstrcat(cmd->pool, cmd->cmd->name, + " regex could not be compiled.", NULL); + } + new->features = ap_make_table(cmd->pool, 2); + + if (!strcasecmp(fname, "remote_addr")) { + new->special_type = SPECIAL_REMOTE_ADDR; + } + else if (!strcasecmp(fname, "remote_host")) { + new->special_type = SPECIAL_REMOTE_HOST; + } + else if (!strcasecmp(fname, "request_uri")) { + new->special_type = SPECIAL_REQUEST_URI; + } + else if (!strcasecmp(fname, "request_method")) { + new->special_type = SPECIAL_REQUEST_METHOD; + } + else if (!strcasecmp(fname, "request_protocol")) { + new->special_type = SPECIAL_REQUEST_PROTOCOL; + } + else if (!strcasecmp(fname, "server_addr")) { + new->special_type = SPECIAL_SERVER_ADDR; + } + else { + new->special_type = SPECIAL_NOT; + } + } + else { + new = &entries[i]; + } + + for ( ; ; ) { + feature = ap_getword_conf(cmd->pool, &args); + if (!*feature) { + break; + } + beenhere++; + + var = ap_getword(cmd->pool, &feature, '='); + if (*feature) { + ap_table_setn(new->features, var, feature); + } + else if (*var == '!') { + ap_table_setn(new->features, var + 1, "!"); + } + else { + ap_table_setn(new->features, var, "1"); + } + } + + if (!beenhere) { + return ap_pstrcat(cmd->pool, "Missing envariable expression for ", + cmd->cmd->name, NULL); + } + + return NULL; +} + +static const char *add_setenvif(cmd_parms *cmd, void *mconfig, + const char *args) +{ + char *fname; + + /* get header name */ + fname = ap_getword_conf(cmd->pool, &args); + if (!*fname) { + return ap_pstrcat(cmd->pool, "Missing header-field name for ", + cmd->cmd->name, NULL); + } + return add_setenvif_core(cmd, mconfig, fname, args); +} + +/* + * This routine handles the BrowserMatch* directives. It simply turns around + * and feeds them, with the appropriate embellishments, to the general-purpose + * command handler. + */ +static const char *add_browser(cmd_parms *cmd, void *mconfig, const char *args) +{ + return add_setenvif_core(cmd, mconfig, "User-Agent", args); +} + +static const command_rec setenvif_module_cmds[] = +{ + { "SetEnvIf", add_setenvif, NULL, + OR_FILEINFO, RAW_ARGS, "A header-name, regex and a list of variables." }, + { "SetEnvIfNoCase", add_setenvif, ICASE_MAGIC, + OR_FILEINFO, RAW_ARGS, "a header-name, regex and a list of variables." }, + { "BrowserMatch", add_browser, NULL, + OR_FILEINFO, RAW_ARGS, "A browser regex and a list of variables." }, + { "BrowserMatchNoCase", add_browser, ICASE_MAGIC, + OR_FILEINFO, RAW_ARGS, "A browser regex and a list of variables." }, + { NULL }, +}; + +/* + * This routine gets called at two different points in request processing: + * once before the URI has been translated (during the post-read-request + * phase) and once after (during the header-parse phase). We use different + * config records for the two different calls to reduce overhead (by not + * re-doing the server-wide settings during directory processing), and + * signal which call it is by having the earlier one pass a flag to the + * later one. + */ +static int match_headers(request_rec *r) +{ + sei_cfg_rec *sconf; + sei_entry *entries; + table_entry *elts; + const char *val; + int i, j; + int perdir; + char *last_name; + + perdir = (ap_table_get(r->notes, SEI_MAGIC_HEIRLOOM) != NULL); + if (! perdir) { + ap_table_set(r->notes, SEI_MAGIC_HEIRLOOM, "post-read done"); + sconf = (sei_cfg_rec *) ap_get_module_config(r->server->module_config, + &setenvif_module); + } + else { + sconf = (sei_cfg_rec *) ap_get_module_config(r->per_dir_config, + &setenvif_module); + } + entries = (sei_entry *) sconf->conditionals->elts; + last_name = NULL; + val = NULL; + for (i = 0; i < sconf->conditionals->nelts; ++i) { + sei_entry *b = &entries[i]; + + /* Optimize the case where a bunch of directives in a row use the + * same header. Remember we don't need to strcmp the two header + * names because we made sure the pointers were equal during + * configuration. + */ + if (b->name != last_name) { + last_name = b->name; + switch (b->special_type) { + case SPECIAL_REMOTE_ADDR: + val = r->connection->remote_ip; + break; + case SPECIAL_SERVER_ADDR: + val = r->connection->local_ip; + break; + case SPECIAL_REMOTE_HOST: + val = ap_get_remote_host(r->connection, r->per_dir_config, + REMOTE_NAME); + break; + case SPECIAL_REQUEST_URI: + val = r->uri; + break; + case SPECIAL_REQUEST_METHOD: + val = r->method; + break; + case SPECIAL_REQUEST_PROTOCOL: + val = r->protocol; + break; + case SPECIAL_NOT: + val = ap_table_get(r->headers_in, b->name); + if (val == NULL) { + val = ap_table_get(r->subprocess_env, b->name); + } + break; + } + } + + /* + * A NULL value indicates that the header field or special entity + * wasn't present or is undefined. Represent that as an empty string + * so that REs like "^$" will work and allow envariable setting + * based on missing or empty field. + */ + if (val == NULL) { + val = ""; + } + + if (!ap_regexec(b->preg, val, 0, NULL, 0)) { + array_header *arr = ap_table_elts(b->features); + elts = (table_entry *) arr->elts; + + for (j = 0; j < arr->nelts; ++j) { + if (!strcmp(elts[j].val, "!")) { + ap_table_unset(r->subprocess_env, elts[j].key); + } + else { + ap_table_setn(r->subprocess_env, elts[j].key, elts[j].val); + } + } + } + } + + return DECLINED; +} + +module MODULE_VAR_EXPORT setenvif_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_setenvif_config_dir, /* dir config creater */ + merge_setenvif_config, /* dir merger --- default is to override */ + create_setenvif_config_svr, /* server config */ + merge_setenvif_config, /* merge server configs */ + setenvif_module_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + match_headers, /* input header parse */ + NULL, /* child (process) initialization */ + NULL, /* child (process) rundown */ + match_headers /* post_read_request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_so.c b/APACHE_1_3_42/src/modules/standard/mod_so.c new file mode 100644 index 0000000000..7530c6ed1e --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_so.c @@ -0,0 +1,375 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This module is used to load Apache modules at runtime. This means that the + * server functionality can be extended without recompiling and even without + * taking the server down at all. Only a HUP or USR1 signal needs to be send + * to the server to reload the dynamically loaded modules. + * + * To use, you'll first need to build your module as a shared library, then + * update your configuration (httpd.conf) to get the Apache core to load the + * module at start-up. + * + * The easiest way to build a module as a shared library is to use the + * `SharedModule' command in the Configuration file, instead of `AddModule'. + * You should also change the file extension from `.o' to `.so'. So, for + * example, to build the status module as a shared library edit Configuration + * and change + * AddModule modules/standard/mod_status.o + * to + * SharedModule modules/standard/mod_status.so + * + * Run Configure and make. Now Apache's httpd binary will _not_ include + * mod_status. Instead a shared object called mod_status.so will be build, in + * the modules/standard directory. You can build most of the modules as shared + * libraries like this. + * + * To use the shared module, move the .so file(s) into an appropriate + * directory. You might like to create a directory called "modules" under you + * server root for this (e.g. /usr/local/httpd/modules). + * + * Then edit your conf/httpd.conf file, and add LoadModule lines. For + * example + * LoadModule status_module modules/mod_status.so + * + * The first argument is the module's structure name (look at the end of the + * module source to find this). The second option is the path to the module + * file, relative to the server root. Put these directives right at the top + * of your httpd.conf file. + * + * Now you can start Apache. A message will be logged at "debug" level to your + * error_log to confirm that the module(s) are loaded (use "LogLevel debug" + * directive to get these log messages). + * + * If you edit the LoadModule directives while the server is live you can get + * Apache to re-load the modules by sending it a HUP or USR1 signal as normal. + * You can use this to dynamically change the capability of your server + * without bringing it down. + * + * Because currently there is only limited built-in support in the Configure + * script for creating the shared library files (`.so'), please consult your + * vendors cc(1), ld(1) and dlopen(3) manpages to find out the appropriate + * compiler and linker flags and insert them manually into the Configuration + * file under CFLAGS_SHLIB, LDFLAGS_SHLIB and LDFLAGS_SHLIB_EXPORT. + * + * If you still have problems figuring out the flags both try the paper + * http://developer.netscape.com/library/documentation/enterprise + * /unix/svrplug.htm#1013807 + * or install a Perl 5 interpreter on your platform and then run the command + * + * $ perl -V:usedl -V:ccdlflags -V:cccdlflags -V:lddlflags + * + * This gives you what type of dynamic loading Perl 5 uses on your platform + * and which compiler and linker flags Perl 5 uses to create the shared object + * files. + * + * Another location where you can find useful hints is the `ltconfig' script + * of the GNU libtool 1.2 package. Search for your platform name inside the + * various "case" constructs. + * + */ + + +#define CORE_PRIVATE +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" + +module MODULE_VAR_EXPORT so_module; + + +/* + * Server configuration to keep track of actually + * loaded modules and the corresponding module name. + */ + +typedef struct moduleinfo { + char *name; + module *modp; +} moduleinfo; + +typedef struct so_server_conf { + array_header *loaded_modules; +} so_server_conf; + +static void *so_sconf_create(pool *p, server_rec *s) +{ + so_server_conf *soc; + + soc = (so_server_conf *)ap_pcalloc(p, sizeof(so_server_conf)); + soc->loaded_modules = ap_make_array(p, DYNAMIC_MODULE_LIMIT, + sizeof(moduleinfo)); +#ifndef NO_DLOPEN + ap_os_dso_init(); +#endif + + return (void *)soc; +} + +#ifndef NO_DLOPEN + +/* + * This is the cleanup for a loaded shared object. It unloads the module. + * This is called as a cleanup function from the core. + */ + +static void unload_module(moduleinfo *modi) +{ + /* only unload if module information is still existing */ + if (modi->modp == NULL) + return; + + /* remove the module pointer from the core structure */ + ap_remove_loaded_module(modi->modp); + + /* unload the module space itself */ +#ifdef NETWARE + ap_os_dso_unsym((ap_os_dso_handle_t)modi->modp->dynamic_load_handle, modi->name); +#endif + ap_os_dso_unload((ap_os_dso_handle_t)modi->modp->dynamic_load_handle); + + /* destroy the module information */ + modi->modp = NULL; + modi->name = NULL; +} + +/* + * This is the cleanup routine for files loaded by + * load_file(). Unfortunately we don't keep a record of the filename + * that was loaded, so we can't report the unload for debug purposes + * or include the filename in error message. + */ + +static void unload_file(void *handle) +{ + ap_os_dso_unload((ap_os_dso_handle_t)handle); +} + +/* + * This is called for the directive LoadModule and actually loads + * a shared object file into the address space of the server process. + */ + +static const char *load_module(cmd_parms *cmd, void *dummy, + char *modname, char *filename) +{ + ap_os_dso_handle_t modhandle; + module *modp; + const char *szModuleFile=ap_server_root_relative(cmd->pool, filename); + so_server_conf *sconf; + moduleinfo *modi; + moduleinfo *modie; + int i; + + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + /* + * check for already existing module + * If it already exists, we have nothing to do + * Check both dynamically-loaded modules and statically-linked modules. + */ + sconf = (so_server_conf *)ap_get_module_config(cmd->server->module_config, + &so_module); + modie = (moduleinfo *)sconf->loaded_modules->elts; + for (i = 0; i < sconf->loaded_modules->nelts; i++) { + modi = &modie[i]; + if (modi->name != NULL && strcmp(modi->name, modname) == 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, cmd->server, + "module %s is already loaded, skipping", modname); + return NULL; + } + } + + for (i = 0; ap_preloaded_modules[i]; i++) { + const char *preload_name; + size_t preload_len; + size_t thismod_len; + + modp = ap_preloaded_modules[i]; + + /* make sure we're comparing apples with apples + * make sure name of preloaded module is mod_FOO.c + * make sure name of structure being loaded is FOO_module + */ + + if (memcmp(modp->name, "mod_", 4)) { + continue; + } + + preload_name = modp->name + strlen("mod_"); + preload_len = strlen(preload_name) - 2; + + if (strlen(modname) <= strlen("_module")) { + continue; + } + thismod_len = strlen(modname) - strlen("_module"); + if (strcmp(modname + thismod_len, "_module")) { + continue; + } + + if (thismod_len != preload_len) { + continue; + } + + if (!memcmp(modname, preload_name, preload_len)) { + return ap_pstrcat(cmd->pool, "module ", modname, + " is built-in and can't be loaded", + NULL); + } + } + + modi = ap_push_array(sconf->loaded_modules); + modi->name = modname; + + /* + * Load the file into the Apache address space + */ + if (!(modhandle = ap_os_dso_load(szModuleFile))) { + const char *my_error = ap_os_dso_error(); + return ap_pstrcat (cmd->pool, "Cannot load ", szModuleFile, + " into server: ", + my_error ? my_error : "(reason unknown)", + NULL); + } + ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, NULL, + "loaded module %s", modname); + + /* + * Retrieve the pointer to the module structure through the module name: + * First with the hidden variant (prefix `AP_') and then with the plain + * symbol name. + */ + if (!(modp = (module *)(ap_os_dso_sym(modhandle, modname)))) { + return ap_pstrcat(cmd->pool, "Can't locate API module structure `", modname, + "' in file ", szModuleFile, ": ", ap_os_dso_error(), NULL); + } + modi->modp = modp; + modp->dynamic_load_handle = (void *)modhandle; + + /* + * Make sure the found module structure is really a module structure + * + */ + if (modp->magic != MODULE_MAGIC_COOKIE) { + return ap_pstrcat(cmd->pool, "API module structure `", modname, + "' in file ", szModuleFile, " is garbled -" + " perhaps this is not an Apache module DSO?", NULL); + } + + /* + * Add this module to the Apache core structures + */ + ap_add_loaded_module(modp); + + /* + * Register a cleanup in the config pool (normally pconf). When + * we do a restart (or shutdown) this cleanup will cause the + * shared object to be unloaded. + */ + ap_register_cleanup(cmd->pool, modi, + (void (*)(void*))unload_module, ap_null_cleanup); + + /* + * Finally we need to run the configuration process for the module + */ + ap_single_module_configure(cmd->pool, cmd->server, modp); + + return NULL; +} + +/* + * This implements the LoadFile directive and loads an arbitrary + * shared object file into the adress space of the server process. + */ + +static const char *load_file(cmd_parms *cmd, void *dummy, char *filename) +{ + ap_os_dso_handle_t handle; + char *file; + + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + file = ap_server_root_relative(cmd->pool, filename); + + if (!(handle = ap_os_dso_load(file))) { + const char *my_error = ap_os_dso_error(); + return ap_pstrcat (cmd->pool, "Cannot load ", filename, + " into server:", + my_error ? my_error : "(reason unknown)", + NULL); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, NULL, + "loaded file %s", filename); + + ap_register_cleanup(cmd->pool, (void *)handle, unload_file, ap_null_cleanup); + + return NULL; +} + +#else /* not NO_DLOPEN */ + +static const char *load_file(cmd_parms *cmd, void *dummy, char *filename) +{ + fprintf(stderr, "WARNING: LoadFile not supported on this platform\n"); + return NULL; +} + +static const char *load_module(cmd_parms *cmd, void *dummy, + char *modname, char *filename) +{ + fprintf(stderr, "WARNING: LoadModule not supported on this platform\n"); + return NULL; +} + +#endif /* NO_DLOPEN */ + +static const command_rec so_cmds[] = { + { "LoadModule", load_module, NULL, RSRC_CONF, TAKE2, + "a module name and the name of a shared object file to load it from"}, + { "LoadFile", load_file, NULL, RSRC_CONF, ITERATE, + "shared object file or library to load into the server at runtime"}, + { NULL } +}; + +module MODULE_VAR_EXPORT so_module = { + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + NULL, /* create per-dir config */ + NULL, /* merge per-dir config */ + so_sconf_create, /* server config */ + NULL, /* merge server config */ + so_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixer_upper */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_speling.c b/APACHE_1_3_42/src/modules/standard/mod_speling.c new file mode 100644 index 0000000000..57e939a153 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_speling.c @@ -0,0 +1,520 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define WANT_BASENAME_MATCH + +#include "httpd.h" +#include "http_core.h" +#include "http_config.h" +#include "http_log.h" + +/* mod_speling.c - by Alexei Kosut <akosut@organic.com> June, 1996 + * + * This module is transparent, and simple. It attempts to correct + * misspellings of URLs that users might have entered, namely by checking + * capitalizations. If it finds a match, it sends a redirect. + * + * 08-Aug-1997 <Martin.Kraemer@Mch.SNI.De> + * o Upgraded module interface to apache_1.3a2-dev API (more NULL's in + * speling_module). + * o Integrated tcsh's "spelling correction" routine which allows one + * misspelling (character insertion/omission/typo/transposition). + * Rewrote it to ignore case as well. This ought to catch the majority + * of misspelled requests. + * o Commented out the second pass where files' suffixes are stripped. + * Given the better hit rate of the first pass, this rather ugly + * (request index.html, receive index.db ?!?!) solution can be + * omitted. + * o wrote a "kind of" html page for mod_speling + * + * Activate it with "CheckSpelling On" + */ + +MODULE_VAR_EXPORT module speling_module; + +typedef struct { + int enabled; +} spconfig; + +/* + * Create a configuration specific to this module for a server or directory + * location, and fill it with the default settings. + * + * The API says that in the absence of a merge function, the record for the + * closest ancestor is used exclusively. That's what we want, so we don't + * bother to have such a function. + */ + +static void *mkconfig(pool *p) +{ + spconfig *cfg = ap_pcalloc(p, sizeof(spconfig)); + + cfg->enabled = 0; + return cfg; +} + +/* + * Respond to a callback to create configuration record for a server or + * vhost environment. + */ +static void *create_mconfig_for_server(pool *p, server_rec *s) +{ + return mkconfig(p); +} + +/* + * Respond to a callback to create a config record for a specific directory. + */ +static void *create_mconfig_for_directory(pool *p, char *dir) +{ + return mkconfig(p); +} + +/* + * Handler for the CheckSpelling directive, which is FLAG. + */ +static const char *set_speling(cmd_parms *cmd, void *mconfig, int arg) +{ + spconfig *cfg = (spconfig *) mconfig; + + cfg->enabled = arg; + return NULL; +} + +/* + * Define the directives specific to this module. This structure is referenced + * later by the 'module' structure. + */ +static const command_rec speling_cmds[] = +{ + { "CheckSpelling", set_speling, NULL, OR_OPTIONS, FLAG, + "whether or not to fix miscapitalized/misspelled requests" }, + { NULL } +}; + +typedef enum { + SP_IDENTICAL = 0, + SP_MISCAPITALIZED = 1, + SP_TRANSPOSITION = 2, + SP_MISSINGCHAR = 3, + SP_EXTRACHAR = 4, + SP_SIMPLETYPO = 5, + SP_VERYDIFFERENT = 6 +} sp_reason; + +static const char *sp_reason_str[] = +{ + "identical", + "miscapitalized", + "transposed characters", + "character missing", + "extra character", + "mistyped character", + "common basename", +}; + +typedef struct { + const char *name; + sp_reason quality; +} misspelled_file; + +/* + * spdist() is taken from Kernighan & Pike, + * _The_UNIX_Programming_Environment_ + * and adapted somewhat to correspond better to psychological reality. + * (Note the changes to the return values) + * + * According to Pollock and Zamora, CACM April 1984 (V. 27, No. 4), + * page 363, the correct order for this is: + * OMISSION = TRANSPOSITION > INSERTION > SUBSTITUTION + * thus, it was exactly backwards in the old version. -- PWP + * + * This routine was taken out of tcsh's spelling correction code + * (tcsh-6.07.04) and re-converted to apache data types ("char" type + * instead of tcsh's NLS'ed "Char"). Plus it now ignores the case + * during comparisons, so is a "approximate strcasecmp()". + * NOTE that is still allows only _one_ real "typo", + * it does NOT try to correct multiple errors. + */ + +static sp_reason spdist(const char *s, const char *t) +{ + for (; ap_tolower(*s) == ap_tolower(*t); t++, s++) { + if (*t == '\0') { + return SP_MISCAPITALIZED; /* exact match (sans case) */ + } + } + if (*s) { + if (*t) { + if (s[1] && t[1] && ap_tolower(*s) == ap_tolower(t[1]) + && ap_tolower(*t) == ap_tolower(s[1]) + && strcasecmp(s + 2, t + 2) == 0) { + return SP_TRANSPOSITION; /* transposition */ + } + if (strcasecmp(s + 1, t + 1) == 0) { + return SP_SIMPLETYPO; /* 1 char mismatch */ + } + } + if (strcasecmp(s + 1, t) == 0) { + return SP_EXTRACHAR; /* extra character */ + } + } + if (*t && strcasecmp(s, t + 1) == 0) { + return SP_MISSINGCHAR; /* missing character */ + } + return SP_VERYDIFFERENT; /* distance too large to fix. */ +} + +static int sort_by_quality(const void *left, const void *rite) +{ + return (int) (((misspelled_file *) left)->quality) + - (int) (((misspelled_file *) rite)->quality); +} + +static int check_speling(request_rec *r) +{ + spconfig *cfg; + char *good, *bad, *postgood, *url; + int filoc, dotloc, urlen, pglen; + DIR *dirp; + struct DIR_TYPE *dir_entry; + array_header *candidates = NULL; + + cfg = ap_get_module_config(r->per_dir_config, &speling_module); + if (!cfg->enabled) { + return DECLINED; + } + + /* We only want to worry about GETs */ + if (r->method_number != M_GET) { + return DECLINED; + } + + /* We've already got a file of some kind or another */ + if (r->proxyreq != NOT_PROXY || (r->finfo.st_mode != 0)) { + return DECLINED; + } + + /* This is a sub request - don't mess with it */ + if (r->main) { + return DECLINED; + } + + /* + * The request should end up looking like this: + * r->uri: /correct-url/mispelling/more + * r->filename: /correct-file/mispelling r->path_info: /more + * + * So we do this in steps. First break r->filename into two pieces + */ + + filoc = ap_rind(r->filename, '/'); + /* + * Don't do anything if the request doesn't contain a slash, or + * requests "/" + */ + if (filoc == -1 || strcmp(r->uri, "/") == 0) { + return DECLINED; + } + + /* good = /correct-file */ + good = ap_pstrndup(r->pool, r->filename, filoc); + /* bad = mispelling */ + bad = ap_pstrdup(r->pool, r->filename + filoc + 1); + /* postgood = mispelling/more */ + postgood = ap_pstrcat(r->pool, bad, r->path_info, NULL); + + urlen = strlen(r->uri); + pglen = strlen(postgood); + + /* Check to see if the URL pieces add up */ + if (strcmp(postgood, r->uri + (urlen - pglen))) { + return DECLINED; + } + + /* url = /correct-url */ + url = ap_pstrndup(r->pool, r->uri, (urlen - pglen)); + + /* Now open the directory and do ourselves a check... */ + dirp = ap_popendir(r->pool, good); + if (dirp == NULL) { /* Oops, not a directory... */ + return DECLINED; + } + + candidates = ap_make_array(r->pool, 2, sizeof(misspelled_file)); + + dotloc = ap_ind(bad, '.'); + if (dotloc == -1) { + dotloc = strlen(bad); + } + + while ((dir_entry = readdir(dirp)) != NULL) { + sp_reason q; + + /* + * If we end up with a "fixed" URL which is identical to the + * requested one, we must have found a broken symlink or some such. + * Do _not_ try to redirect this, it causes a loop! + */ + if (strcmp(bad, dir_entry->d_name) == 0) { + ap_pclosedir(r->pool, dirp); + return OK; + } + /* + * miscapitalization errors are checked first (like, e.g., lower case + * file, upper case request) + */ + else if (strcasecmp(bad, dir_entry->d_name) == 0) { + misspelled_file *sp_new; + + sp_new = (misspelled_file *) ap_push_array(candidates); + sp_new->name = ap_pstrdup(r->pool, dir_entry->d_name); + sp_new->quality = SP_MISCAPITALIZED; + } + /* + * simple typing errors are checked next (like, e.g., + * missing/extra/transposed char) + */ + else if ((q = spdist(bad, dir_entry->d_name)) != SP_VERYDIFFERENT) { + misspelled_file *sp_new; + + sp_new = (misspelled_file *) ap_push_array(candidates); + sp_new->name = ap_pstrdup(r->pool, dir_entry->d_name); + sp_new->quality = q; + } + /* + * The spdist() should have found the majority of the misspelled + * requests. It is of questionable use to continue looking for + * files with the same base name, but potentially of totally wrong + * type (index.html <-> index.db). + * I would propose to not set the WANT_BASENAME_MATCH define. + * 08-Aug-1997 <Martin.Kraemer@Mch.SNI.De> + * + * However, Alexei replied giving some reasons to add it anyway: + * > Oh, by the way, I remembered why having the + * > extension-stripping-and-matching stuff is a good idea: + * > + * > If you're using MultiViews, and have a file named foobar.html, + * > which you refer to as "foobar", and someone tried to access + * > "Foobar", mod_speling won't find it, because it won't find + * > anything matching that spelling. With the extension-munging, + * > it would locate "foobar.html". Not perfect, but I ran into + * > that problem when I first wrote the module. + */ + else { +#ifdef WANT_BASENAME_MATCH + /* + * Okay... we didn't find anything. Now we take out the hard-core + * power tools. There are several cases here. Someone might have + * entered a wrong extension (.htm instead of .html or vice + * versa) or the document could be negotiated. At any rate, now + * we just compare stuff before the first dot. If it matches, we + * figure we got us a match. This can result in wrong things if + * there are files of different content types but the same prefix + * (e.g. foo.gif and foo.html) This code will pick the first one + * it finds. Better than a Not Found, though. + */ + int entloc = ap_ind(dir_entry->d_name, '.'); + if (entloc == -1) { + entloc = strlen(dir_entry->d_name); + } + + if ((dotloc == entloc) + && !strncasecmp(bad, dir_entry->d_name, dotloc)) { + misspelled_file *sp_new; + + sp_new = (misspelled_file *) ap_push_array(candidates); + sp_new->name = ap_pstrdup(r->pool, dir_entry->d_name); + sp_new->quality = SP_VERYDIFFERENT; + } +#endif + } + } + ap_pclosedir(r->pool, dirp); + + if (candidates->nelts != 0) { + /* Wow... we found us a mispelling. Construct a fixed url */ + char *nuri; + const char *ref; + misspelled_file *variant = (misspelled_file *) candidates->elts; + int i; + + ref = ap_table_get(r->headers_in, "Referer"); + + qsort((void *) candidates->elts, candidates->nelts, + sizeof(misspelled_file), sort_by_quality); + + /* + * Conditions for immediate redirection: + * a) the first candidate was not found by stripping the suffix + * AND b) there exists only one candidate OR the best match is not + * ambiguous + * then return a redirection right away. + */ + if (variant[0].quality != SP_VERYDIFFERENT + && (candidates->nelts == 1 + || variant[0].quality != variant[1].quality)) { + + nuri = ap_escape_uri(r->pool, ap_pstrcat(r->pool, url, + variant[0].name, + r->path_info, NULL)); + if (r->parsed_uri.query) + nuri = ap_pstrcat(r->pool, nuri, "?", r->parsed_uri.query, NULL); + + ap_table_setn(r->headers_out, "Location", + ap_construct_url(r->pool, nuri, r)); + + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, r, + ref ? "Fixed spelling: %s to %s from %s" + : "Fixed spelling: %s to %s", + r->uri, nuri, ref); + + return HTTP_MOVED_PERMANENTLY; + } + /* + * Otherwise, a "[300] Multiple Choices" list with the variants is + * returned. + */ + else { + pool *p; + table *notes; + pool *sub_pool; + array_header *t; + array_header *v; + + + if (r->main == NULL) { + p = r->pool; + notes = r->notes; + } + else { + p = r->main->pool; + notes = r->main->notes; + } + + sub_pool = ap_make_sub_pool(p); + t = ap_make_array(sub_pool, candidates->nelts * 8 + 8, + sizeof(char *)); + v = ap_make_array(sub_pool, candidates->nelts * 5, + sizeof(char *)); + + /* Generate the response text. */ + + *(const char **)ap_push_array(t) = + "The document name you requested (<code>"; + *(const char **)ap_push_array(t) = ap_escape_html(sub_pool, r->uri); + *(const char **)ap_push_array(t) = + "</code>) could not be found on this server.\n" + "However, we found documents with names similar " + "to the one you requested.<p>" + "Available documents:\n<ul>\n"; + + for (i = 0; i < candidates->nelts; ++i) { + char *vuri; + const char *reason; + + reason = sp_reason_str[(int) (variant[i].quality)]; + /* The format isn't very neat... */ + vuri = ap_pstrcat(sub_pool, url, variant[i].name, r->path_info, + (r->parsed_uri.query != NULL) ? "?" : "", + (r->parsed_uri.query != NULL) + ? r->parsed_uri.query : "", + NULL); + *(const char **)ap_push_array(v) = "\""; + *(const char **)ap_push_array(v) = ap_escape_uri(sub_pool, vuri); + *(const char **)ap_push_array(v) = "\";\""; + *(const char **)ap_push_array(v) = reason; + *(const char **)ap_push_array(v) = "\""; + + *(const char **)ap_push_array(t) = "<li><a href=\""; + *(const char **)ap_push_array(t) = ap_escape_uri(sub_pool, vuri); + *(const char **)ap_push_array(t) = "\">"; + *(const char **)ap_push_array(t) = ap_escape_html(sub_pool, vuri); + *(const char **)ap_push_array(t) = "</a> ("; + *(const char **)ap_push_array(t) = reason; + *(const char **)ap_push_array(t) = ")\n"; + + /* + * when we have printed the "close matches" and there are + * more "distant matches" (matched by stripping the suffix), + * then we insert an additional separator text to suggest + * that the user LOOK CLOSELY whether these are really the + * files she wanted. + */ + if (i > 0 && i < candidates->nelts - 1 + && variant[i].quality != SP_VERYDIFFERENT + && variant[i + 1].quality == SP_VERYDIFFERENT) { + *(const char **)ap_push_array(t) = + "</ul>\nFurthermore, the following related " + "documents were found:\n<ul>\n"; + } + } + *(const char **)ap_push_array(t) = "</ul>\n"; + + /* If we know there was a referring page, add a note: */ + if (ref != NULL) { + *(const char **)ap_push_array(t) = + "Please consider informing the owner of the " + "<a href=\""; + *(const char **)ap_push_array(t) = ap_escape_uri(sub_pool, ref); + *(const char **)ap_push_array(t) = "\">referring page</a> " + "about the broken link.\n"; + } + + + /* Pass our table to http_protocol.c (see mod_negotiation): */ + ap_table_setn(notes, "variant-list", ap_array_pstrcat(p, t, 0)); + + ap_table_mergen(r->subprocess_env, "VARIANTS", + ap_array_pstrcat(p, v, ',')); + + ap_destroy_pool(sub_pool); + + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, r, + ref ? "Spelling fix: %s: %d candidates from %s" + : "Spelling fix: %s: %d candidates", + r->uri, candidates->nelts, ref); + + return HTTP_MULTIPLE_CHOICES; + } + } + + return OK; +} + +module MODULE_VAR_EXPORT speling_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + create_mconfig_for_directory, /* create per-dir config */ + NULL, /* merge per-dir config */ + create_mconfig_for_server, /* server config */ + NULL, /* merge server config */ + speling_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + check_speling, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; + diff --git a/APACHE_1_3_42/src/modules/standard/mod_status.c b/APACHE_1_3_42/src/modules/standard/mod_status.c new file mode 100644 index 0000000000..68460eaf3e --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_status.c @@ -0,0 +1,760 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Status Module. Display lots of internal data about how Apache is + * performing and the state of all children processes. + * + * To enable this, add the following lines into any config file: + * + * <Location /server-status> + * SetHandler server-status + * </Location> + * + * You may want to protect this location by password or domain so no one + * else can look at it. Then you can access the statistics with a URL like: + * + * http://your_server_name/server-status + * + * /server-status - Returns page using tables + * /server-status?notable - Returns page for browsers without table support + * /server-status?refresh - Returns page with 1 second refresh + * /server-status?refresh=6 - Returns page with refresh every 6 seconds + * /server-status?auto - Returns page with data for automatic parsing + * + * Mark Cox, mark@ukweb.com, November 1995 + * + * 12.11.95 Initial version for www.telescope.org + * 13.3.96 Updated to remove rprintf's [Mark] + * 18.3.96 Added CPU usage, process information, and tidied [Ben Laurie] + * 18.3.96 Make extra Scoreboard variables #definable + * 25.3.96 Make short report have full precision [Ben Laurie suggested] + * 25.3.96 Show uptime better [Mark/Ben Laurie] + * 29.3.96 Better HTML and explanation [Mark/Rob Hartill suggested] + * 09.4.96 Added message for non-STATUS compiled version + * 18.4.96 Added per child and per slot counters [Jim Jagielski] + * 01.5.96 Table format, cleanup, even more spiffy data [Chuck Murcko/Jim J.] + * 18.5.96 Adapted to use new rprintf() routine, incidentally fixing a missing + * piece in short reports [Ben Laurie] + * 21.5.96 Additional Status codes (DNS and LOGGING only enabled if + * extended STATUS is enabled) [George Burgyan/Jim J.] + * 10.8.98 Allow for extended status info at runtime (no more STATUS) + * [Jim J.] + */ + +#define CORE_PRIVATE +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_conf_globals.h" /* for ap_extended_status */ +#include "http_main.h" +#include "util_script.h" +#include <time.h> +#include "scoreboard.h" +#include "http_log.h" + +#ifdef NEXT +#if (NX_CURRENT_COMPILER_RELEASE == 410) +#ifdef m68k +#define HZ 64 +#else +#define HZ 100 +#endif +#else +#include <machine/param.h> +#endif +#endif /* NEXT */ + +#define STATUS_MAXLINE 64 + +#define KBYTE 1024 +#define MBYTE 1048576L +#define GBYTE 1073741824L + +#ifndef DEFAULT_TIME_FORMAT +#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z" +#endif + +module MODULE_VAR_EXPORT status_module; + +/* + *command-related code. This is here to prevent use of ExtendedStatus + * without status_module included. + */ +static const char *set_extended_status(cmd_parms *cmd, void *dummy, int arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + ap_extended_status = arg; + return NULL; +} + +static const command_rec status_module_cmds[] = +{ + { "ExtendedStatus", set_extended_status, NULL, RSRC_CONF, FLAG, + "\"On\" to enable extended status information, \"Off\" to disable" }, + {NULL} +}; + +/* Format the number of bytes nicely */ +static void format_byte_out(request_rec *r, unsigned long bytes) +{ + if (bytes < (5 * KBYTE)) + ap_rprintf(r, "%d B", (int) bytes); + else if (bytes < (MBYTE / 2)) + ap_rprintf(r, "%.1f kB", (float) bytes / KBYTE); + else if (bytes < (GBYTE / 2)) + ap_rprintf(r, "%.1f MB", (float) bytes / MBYTE); + else + ap_rprintf(r, "%.1f GB", (float) bytes / GBYTE); +} + +static void format_kbyte_out(request_rec *r, unsigned long kbytes) +{ + if (kbytes < KBYTE) + ap_rprintf(r, "%d kB", (int) kbytes); + else if (kbytes < MBYTE) + ap_rprintf(r, "%.1f MB", (float) kbytes / KBYTE); + else + ap_rprintf(r, "%.1f GB", (float) kbytes / MBYTE); +} + +static void show_time(request_rec *r, time_t tsecs) +{ + long days, hrs, mins, secs; + + secs = tsecs % 60; + tsecs /= 60; + mins = tsecs % 60; + tsecs /= 60; + hrs = tsecs % 24; + days = tsecs / 24; + if (days) + ap_rprintf(r, " %ld day%s", days, days == 1 ? "" : "s"); + if (hrs) + ap_rprintf(r, " %ld hour%s", hrs, hrs == 1 ? "" : "s"); + if (mins) + ap_rprintf(r, " %ld minute%s", mins, mins == 1 ? "" : "s"); + if (secs) + ap_rprintf(r, " %ld second%s", secs, secs == 1 ? "" : "s"); +} + +/* Main handler for x-httpd-status requests */ + +/* ID values for command table */ + +#define STAT_OPT_END -1 +#define STAT_OPT_REFRESH 0 +#define STAT_OPT_NOTABLE 1 +#define STAT_OPT_AUTO 2 + +struct stat_opt { + int id; + const char *form_data_str; + const char *hdr_out_str; +}; + +static const struct stat_opt status_options[] = /* see #defines above */ +{ + {STAT_OPT_REFRESH, "refresh", "Refresh"}, + {STAT_OPT_NOTABLE, "notable", NULL}, + {STAT_OPT_AUTO, "auto", NULL}, + {STAT_OPT_END, NULL, NULL} +}; + +static char status_flags[SERVER_NUM_STATUS]; + +static int status_handler(request_rec *r) +{ + char *loc; + time_t nowtime = time(NULL); + time_t up_time; + int i, res; + int ready = 0; + int busy = 0; + unsigned long count = 0; + unsigned long lres, bytes; + unsigned long my_lres, my_bytes, conn_bytes; + unsigned short conn_lres; + unsigned long bcount = 0; + unsigned long kbcount = 0; + long req_time; +#ifndef NO_TIMES +#ifdef _SC_CLK_TCK + float tick = sysconf(_SC_CLK_TCK); +#else + float tick = HZ; +#endif +#endif + int short_report = 0; + int no_table_report = 0; + short_score score_record; + parent_score ps_record; + char stat_buffer[HARD_SERVER_LIMIT]; + int pid_buffer[HARD_SERVER_LIMIT]; + clock_t tu, ts, tcu, tcs; + server_rec *vhost; + + tu = ts = tcu = tcs = 0; + + if (!ap_exists_scoreboard_image()) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "Server status unavailable in inetd mode"); + return HTTP_INTERNAL_SERVER_ERROR; + } + r->allowed = (1 << M_GET); + if (r->method_number != M_GET) + return DECLINED; + + r->content_type = "text/html; charset=ISO-8859-1"; + + /* + * Simple table-driven form data set parser that lets you alter the header + */ + + if (r->args) { + i = 0; + while (status_options[i].id != STAT_OPT_END) { + if ((loc = strstr(r->args, status_options[i].form_data_str)) != NULL) { + switch (status_options[i].id) { + case STAT_OPT_REFRESH: { + long refreshtime = 0; + if (*(loc + strlen(status_options[i].form_data_str)) == '=') + refreshtime = atol(loc + strlen(status_options[i].form_data_str)+1); + ap_table_set(r->headers_out, + status_options[i].hdr_out_str, + ap_psprintf(r->pool,"%ld",(refreshtime<1)?10:refreshtime)); + break; + } + case STAT_OPT_NOTABLE: + no_table_report = 1; + break; + case STAT_OPT_AUTO: + r->content_type = "text/plain; charset=ISO-8859-1"; + short_report = 1; + break; + } + } + i++; + } + } + + ap_send_http_header(r); +#ifdef CHARSET_EBCDIC + /* Server-generated response, converted */ + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1); +#endif + + if (r->header_only) + return 0; + + ap_sync_scoreboard_image(); + for (i = 0; i < HARD_SERVER_LIMIT; ++i) { + score_record = ap_scoreboard_image->servers[i]; + ps_record = ap_scoreboard_image->parent[i]; + res = score_record.status; + stat_buffer[i] = status_flags[res]; + pid_buffer[i] = (int) ps_record.pid; + if (res == SERVER_READY) + ready++; + else if (res != SERVER_DEAD) + busy++; + if (ap_extended_status) { + lres = score_record.access_count; + bytes = score_record.bytes_served; + if (lres != 0 || (res != SERVER_READY && res != SERVER_DEAD)) { +#ifndef NO_TIMES + tu += score_record.times.tms_utime; + ts += score_record.times.tms_stime; + tcu += score_record.times.tms_cutime; + tcs += score_record.times.tms_cstime; +#endif /* NO_TIMES */ + count += lres; + bcount += bytes; + if (bcount >= KBYTE) { + kbcount += (bcount >> 10); + bcount = bcount & 0x3ff; + } + } + } + } + + up_time = nowtime - ap_restart_time; + + ap_hard_timeout("send status info", r); + + if (!short_report) { + ap_rputs(DOCTYPE_HTML_3_2 + "<HTML><HEAD>\n<TITLE>Apache Status</TITLE>\n</HEAD><BODY>\n", + r); + ap_rputs("<H1>Apache Server Status for ", r); + ap_rvputs(r, ap_get_server_name(r), "</H1>\n\n", NULL); + ap_rvputs(r, "Server Version: ", + ap_get_server_version(), "<br>\n", NULL); + ap_rvputs(r, "Server Built: ", + ap_get_server_built(), "<br>\n<hr>\n", NULL); + ap_rvputs(r, "Current Time: ", + ap_ht_time(r->pool, nowtime, DEFAULT_TIME_FORMAT, 0), "<br>\n", NULL); + ap_rvputs(r, "Restart Time: ", + ap_ht_time(r->pool, ap_restart_time, DEFAULT_TIME_FORMAT, 0), + "<br>\n", NULL); + ap_rprintf(r, "Parent Server Generation: %d <br>\n", (int) ap_my_generation); + ap_rputs("Server uptime: ", r); + show_time(r, up_time); + ap_rputs("<br>\n", r); + } + + if (ap_extended_status) { + if (short_report) { + ap_rprintf(r, "Total Accesses: %lu\nTotal kBytes: %lu\n", + count, kbcount); + +#ifndef NO_TIMES + /* Allow for OS/2 not having CPU stats */ + if (ts || tu || tcu || tcs) + ap_rprintf(r, "CPULoad: %g\n", + (tu + ts + tcu + tcs) / tick / up_time * 100.); +#endif + + ap_rprintf(r, "Uptime: %ld\n", (long) (up_time)); + if (up_time > 0) + ap_rprintf(r, "ReqPerSec: %g\n", + (float) count / (float) up_time); + + if (up_time > 0) + ap_rprintf(r, "BytesPerSec: %g\n", + KBYTE * (float) kbcount / (float) up_time); + + if (count > 0) + ap_rprintf(r, "BytesPerReq: %g\n", + KBYTE * (float) kbcount / (float) count); + } + else { /* !short_report */ + ap_rprintf(r, "Total accesses: %lu - Total Traffic: ", count); + format_kbyte_out(r, kbcount); + +#ifndef NO_TIMES + /* Allow for OS/2 not having CPU stats */ + ap_rputs("<br>\n", r); + ap_rprintf(r, "CPU Usage: u%g s%g cu%g cs%g", + tu / tick, ts / tick, tcu / tick, tcs / tick); + + if (ts || tu || tcu || tcs) + ap_rprintf(r, " - %.3g%% CPU load", + (tu + ts + tcu + tcs) / tick / up_time * 100.); +#endif + + ap_rputs("<br>\n", r); + + if (up_time > 0) + ap_rprintf(r, "%.3g requests/sec - ", + (float) count / (float) up_time); + + if (up_time > 0) { + format_byte_out(r, (unsigned long) (KBYTE * (float) kbcount + / (float) up_time)); + ap_rputs("/second - ", r); + } + + if (count > 0) { + format_byte_out(r, (unsigned long) (KBYTE * (float) kbcount + / (float) count)); + ap_rputs("/request", r); + } + + ap_rputs("<br>\n", r); + } /* short_report */ + } /* ap_extended_status */ + + if (!short_report) + ap_rprintf(r, "\n%d requests currently being processed, %d idle servers\n" + ,busy, ready); + else + ap_rprintf(r, "BusyServers: %d\nIdleServers: %d\n", busy, ready); + + /* send the scoreboard 'table' out */ + + if (!short_report) + ap_rputs("<PRE>", r); + else + ap_rputs("Scoreboard: ", r); + + for (i = 0; i < HARD_SERVER_LIMIT; ++i) { + ap_rputc(stat_buffer[i], r); + if ((i % STATUS_MAXLINE == (STATUS_MAXLINE - 1)) && !short_report) + ap_rputs("\n", r); + } + + if (short_report) + ap_rputs("\n", r); + else { + ap_rputs("</PRE>\n", r); + ap_rputs("Scoreboard Key: <br>\n", r); + ap_rputs("\"<B><code>_</code></B>\" Waiting for Connection, \n", r); + ap_rputs("\"<B><code>S</code></B>\" Starting up, \n", r); + ap_rputs("\"<B><code>R</code></B>\" Reading Request,<BR>\n", r); + ap_rputs("\"<B><code>W</code></B>\" Sending Reply, \n", r); + ap_rputs("\"<B><code>K</code></B>\" Keepalive (read), \n", r); + ap_rputs("\"<B><code>D</code></B>\" DNS Lookup,<BR>\n", r); + ap_rputs("\"<B><code>L</code></B>\" Logging, \n", r); + ap_rputs("\"<B><code>G</code></B>\" Gracefully finishing, \n", r); + ap_rputs("\"<B><code>.</code></B>\" Open slot with no current process<P>\n", r); + ap_rputs("<P>\n", r); + if (!ap_extended_status) { + int j = 0; + ap_rputs("PID Key: <br>\n", r); + ap_rputs("<PRE>\n", r); + for (i = 0; i < HARD_SERVER_LIMIT; ++i) { + if (stat_buffer[i] != '.') { + ap_rprintf(r, " %d in state: %c ", pid_buffer[i], + stat_buffer[i]); + if (++j >= 3) { + ap_rputs("\n", r); + j = 0; + } else + ap_rputs(",", r); + } + } + ap_rputs("\n", r); + ap_rputs("</PRE>\n", r); + } + } + + if (ap_extended_status) { + if (!short_report) { + if (no_table_report) + ap_rputs("<p><hr><h2>Server Details</h2>\n\n", r); + else +#ifdef NO_TIMES + /* Allow for OS/2 not having CPU stats */ + ap_rputs("<p>\n\n<table border=0><tr><th>Srv<th>PID<th>Acc<th>M\n<th>SS<th>Req<th>Conn<th>Child<th>Slot<th>Client<th>VHost<th>Request</tr>\n\n", r); +#else + ap_rputs("<p>\n\n<table border=0><tr><th>Srv<th>PID<th>Acc<th>M<th>CPU\n<th>SS<th>Req<th>Conn<th>Child<th>Slot<th>Client<th>VHost<th>Request</tr>\n\n", r); +#endif + } + + for (i = 0; i < HARD_SERVER_LIMIT; ++i) { + score_record = ap_scoreboard_image->servers[i]; + ps_record = ap_scoreboard_image->parent[i]; + vhost = score_record.vhostrec; + if (ps_record.generation != ap_my_generation) { + vhost = NULL; + } + +#if defined(NO_GETTIMEOFDAY) +#ifndef NO_TIMES + if (score_record.start_time == (clock_t) 0) +#endif /* NO_TIMES */ + req_time = 0L; +#ifndef NO_TIMES + else { + req_time = score_record.stop_time - score_record.start_time; + req_time = (req_time * 1000) / (int) tick; + } +#endif /* NO_TIMES */ +#else + if (score_record.start_time.tv_sec == 0L && + score_record.start_time.tv_usec == 0L) + req_time = 0L; + else + req_time = + ((score_record.stop_time.tv_sec - score_record.start_time.tv_sec) * 1000) + + ((score_record.stop_time.tv_usec - score_record.start_time.tv_usec) / 1000); +#endif + if (req_time < 0L) + req_time = 0L; + + lres = score_record.access_count; + my_lres = score_record.my_access_count; + conn_lres = score_record.conn_count; + bytes = score_record.bytes_served; + my_bytes = score_record.my_bytes_served; + conn_bytes = score_record.conn_bytes; + if (lres != 0 || (score_record.status != SERVER_READY + && score_record.status != SERVER_DEAD)) { + if (!short_report) { + if (no_table_report) { + if (score_record.status == SERVER_DEAD) +#ifdef TPF + if (kill(ps_record.pid, 0) == 0) { + /* on TPF show PIDs of the living dead */ + ap_rprintf(r, + "<b>Server %d-%d</b> (%d): %d|%lu|%lu [", + i, (int) ps_record.generation, + (int)ps_record.pid, (int) conn_lres, + my_lres, lres); + } else +#endif /* TPF */ + ap_rprintf(r, + "<b>Server %d-%d</b> (-): %d|%lu|%lu [", + i, (int) ps_record.generation, (int) conn_lres, + my_lres, lres); + else + ap_rprintf(r, + "<b>Server %d-%d</b> (%d): %d|%lu|%lu [", + i, (int) ps_record.generation, + (int) ps_record.pid, + (int) conn_lres, my_lres, lres); + + switch (score_record.status) { + case SERVER_READY: + ap_rputs("Ready", r); + break; + case SERVER_STARTING: + ap_rputs("Starting", r); + break; + case SERVER_BUSY_READ: + ap_rputs("<b>Read</b>", r); + break; + case SERVER_BUSY_WRITE: + ap_rputs("<b>Write</b>", r); + break; + case SERVER_BUSY_KEEPALIVE: + ap_rputs("<b>Keepalive</b>", r); + break; + case SERVER_BUSY_LOG: + ap_rputs("<b>Logging</b>", r); + break; + case SERVER_BUSY_DNS: + ap_rputs("<b>DNS lookup</b>", r); + break; + case SERVER_DEAD: + ap_rputs("Dead", r); + break; + case SERVER_GRACEFUL: + ap_rputs("Graceful", r); + break; + default: + ap_rputs("?STATE?", r); + break; + } +#ifdef NO_TIMES + /* Allow for OS/2 not having CPU stats */ + ap_rprintf(r, "]\n %.0f %ld (", +#else + + ap_rprintf(r, "] u%g s%g cu%g cs%g\n %.0f %ld (", + score_record.times.tms_utime / tick, + score_record.times.tms_stime / tick, + score_record.times.tms_cutime / tick, + score_record.times.tms_cstime / tick, +#endif +#ifdef OPTIMIZE_TIMEOUTS + difftime(nowtime, ps_record.last_rtime), +#else + difftime(nowtime, score_record.last_used), +#endif + (long) req_time); + format_byte_out(r, conn_bytes); + ap_rputs("|", r); + format_byte_out(r, my_bytes); + ap_rputs("|", r); + format_byte_out(r, bytes); + ap_rputs(")\n", r); + ap_rprintf(r, " <i>%s {%s}</i> <b>[%s]</b><br>\n\n", + ap_escape_html(r->pool, score_record.client), + ap_escape_html(r->pool, + ap_escape_logitem(r->pool, score_record.request)), + vhost ? ap_escape_html(r->pool, + vhost->server_hostname) : "(unavailable)"); + } + else { /* !no_table_report */ + if (score_record.status == SERVER_DEAD) +#ifdef TPF + if (kill(ps_record.pid, 0) == 0) { + /* on TPF show PIDs of the living dead */ + ap_rprintf(r, + "<tr><td><b>%d-%d</b><td>%d<td>%d/%lu/%lu", + i, (int) ps_record.generation, + (int) ps_record.pid, + (int) conn_lres, my_lres, lres); + } else +#endif /* TPF */ + ap_rprintf(r, + "<tr><td><b>%d-%d</b><td>-<td>%d/%lu/%lu", + i, (int) ps_record.generation, + (int) conn_lres, my_lres, lres); + else + ap_rprintf(r, + "<tr><td><b>%d-%d</b><td>%d<td>%d/%lu/%lu", + i, (int) ps_record.generation, + (int) ps_record.pid, (int) conn_lres, + my_lres, lres); + + switch (score_record.status) { + case SERVER_READY: + ap_rputs("<td>_", r); + break; + case SERVER_STARTING: + ap_rputs("<td><b>S</b>", r); + break; + case SERVER_BUSY_READ: + ap_rputs("<td><b>R</b>", r); + break; + case SERVER_BUSY_WRITE: + ap_rputs("<td><b>W</b>", r); + break; + case SERVER_BUSY_KEEPALIVE: + ap_rputs("<td><b>K</b>", r); + break; + case SERVER_BUSY_LOG: + ap_rputs("<td><b>L</b>", r); + break; + case SERVER_BUSY_DNS: + ap_rputs("<td><b>D</b>", r); + break; + case SERVER_DEAD: + ap_rputs("<td>.", r); + break; + case SERVER_GRACEFUL: + ap_rputs("<td>G", r); + break; + default: + ap_rputs("<td>?", r); + break; + } +#ifdef NO_TIMES + /* Allow for OS/2 not having CPU stats */ + ap_rprintf(r, "\n<td>%.0f<td>%ld", +#else + ap_rprintf(r, "\n<td>%.2f<td>%.0f<td>%ld", + (score_record.times.tms_utime + + score_record.times.tms_stime + + score_record.times.tms_cutime + + score_record.times.tms_cstime) / tick, +#endif +#ifdef OPTIMIZE_TIMEOUTS + difftime(nowtime, ps_record.last_rtime), +#else + difftime(nowtime, score_record.last_used), +#endif + (long) req_time); + ap_rprintf(r, "<td>%-1.1f<td>%-2.2f<td>%-2.2f\n", + (float) conn_bytes / KBYTE, (float) my_bytes / MBYTE, + (float) bytes / MBYTE); + if (score_record.status == SERVER_BUSY_READ) + ap_rprintf(r, + "<td>?<td nowrap>?<td nowrap>..reading.. </tr>\n\n"); + else + ap_rprintf(r, + "<td>%s<td nowrap>%s<td nowrap>%s</tr>\n\n", + ap_escape_html(r->pool, score_record.client), + vhost ? ap_escape_html(r->pool, + vhost->server_hostname) : "(unavailable)", + ap_escape_html(r->pool, + ap_escape_logitem(r->pool, score_record.request))); + } /* no_table_report */ + } /* !short_report */ + } /* if (<active child>) */ + } /* for () */ + + if (!(short_report || no_table_report)) { +#ifdef NO_TIMES + ap_rputs("</table>\n \ +<hr> \ +<table>\n \ +<tr><th>Srv<td>Child Server number - generation\n \ +<tr><th>PID<td>OS process ID\n \ +<tr><th>Acc<td>Number of accesses this connection / this child / this slot\n \ +<tr><th>M<td>Mode of operation\n \ +<tr><th>SS<td>Seconds since beginning of most recent request\n \ +<tr><th>Req<td>Milliseconds required to process most recent request\n \ +<tr><th>Conn<td>Kilobytes transferred this connection\n \ +<tr><th>Child<td>Megabytes transferred this child\n \ +<tr><th>Slot<td>Total megabytes transferred this slot\n \ +</table>\n", r); +#else + ap_rputs("</table>\n \ +<hr> \ +<table>\n \ +<tr><th>Srv<td>Child Server number - generation\n \ +<tr><th>PID<td>OS process ID\n \ +<tr><th>Acc<td>Number of accesses this connection / this child / this slot\n \ +<tr><th>M<td>Mode of operation\n \ +<tr><th>CPU<td>CPU usage, number of seconds\n \ +<tr><th>SS<td>Seconds since beginning of most recent request\n \ +<tr><th>Req<td>Milliseconds required to process most recent request\n \ +<tr><th>Conn<td>Kilobytes transferred this connection\n \ +<tr><th>Child<td>Megabytes transferred this child\n \ +<tr><th>Slot<td>Total megabytes transferred this slot\n \ +</table>\n", r); +#endif + } + + } else { + + if (!short_report) { + ap_rputs("<hr>To obtain a full report with current status information ", r); + ap_rputs("you need to use the <code>ExtendedStatus On</code> directive. \n", r); + } + + } + + if (!short_report) { + ap_rputs(ap_psignature("<HR>\n",r), r); + ap_rputs("</BODY></HTML>\n", r); + } + + ap_kill_timeout(r); + return 0; +} + + +static void status_init(server_rec *s, pool *p) +{ + status_flags[SERVER_DEAD] = '.'; /* We don't want to assume these are in */ + status_flags[SERVER_READY] = '_'; /* any particular order in scoreboard.h */ + status_flags[SERVER_STARTING] = 'S'; + status_flags[SERVER_BUSY_READ] = 'R'; + status_flags[SERVER_BUSY_WRITE] = 'W'; + status_flags[SERVER_BUSY_KEEPALIVE] = 'K'; + status_flags[SERVER_BUSY_LOG] = 'L'; + status_flags[SERVER_BUSY_DNS] = 'D'; + status_flags[SERVER_GRACEFUL] = 'G'; +} + +static const handler_rec status_handlers[] = +{ + {STATUS_MAGIC_TYPE, status_handler}, + {"server-status", status_handler}, + {NULL} +}; + +module MODULE_VAR_EXPORT status_module = +{ + STANDARD_MODULE_STUFF, + status_init, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + status_module_cmds, /* command table */ + status_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; + diff --git a/APACHE_1_3_42/src/modules/standard/mod_unique_id.c b/APACHE_1_3_42/src/modules/standard/mod_unique_id.c new file mode 100644 index 0000000000..3f83487248 --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_unique_id.c @@ -0,0 +1,446 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_unique_id.c: generate a unique identifier for each request + * + * Original author: Dean Gaudet <dgaudet@arctic.org> + * UUencoding modified by: Alvaro Martinez Echevarria <alvaro@lander.es> + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "multithread.h" + +typedef struct { + unsigned int stamp; + unsigned int in_addr; + unsigned int pid; +#ifdef MULTITHREAD + unsigned int tid; +#endif + unsigned short counter; +} unique_id_rec; + +/* Comments: + * + * We want an identifier which is unique across all hits, everywhere. + * "everywhere" includes multiple httpd instances on the same machine, or on + * multiple machines. Essentially "everywhere" should include all possible + * httpds across all servers at a particular "site". We make some assumptions + * that if the site has a cluster of machines then their time is relatively + * synchronized. We also assume that the first address returned by a + * gethostbyname (gethostname()) is unique across all the machines at the + * "site". + * + * We also further assume that pids fit in 32-bits. If something uses more + * than 32-bits, the fix is trivial, but it requires the unrolled uuencoding + * loop to be extended. + * + * Together, the in_addr and pid are assumed to absolutely uniquely identify + * this one child from all other currently running children on all servers + * (including this physical server if it is running multiple httpds) from each + * other. + * + * The stamp and counter are used to distinguish all hits for a particular + * (in_addr,pid) pair. The stamp is updated using r->request_time, + * saving cpu cycles. The counter is never reset, and is used to permit up to + * 64k requests in a single second by a single child. + * + * The 112-bits of unique_id_rec are encoded using the alphabet + * [A-Za-z0-9@-], resulting in 19 bytes of printable characters. That is then + * stuffed into the environment variable UNIQUE_ID so that it is available to + * other modules. The alphabet choice differs from normal base64 encoding + * [A-Za-z0-9+/] because + and / are special characters in URLs and we want to + * make it easy to use UNIQUE_ID in URLs. + * + * Note that UNIQUE_ID should be considered an opaque token by other + * applications. No attempt should be made to dissect its internal components. + * It is an abstraction that may change in the future as the needs of this + * module change. + * + * It is highly desirable that identifiers exist for "eternity". But future + * needs (such as much faster webservers, moving to 64-bit pids, or moving to a + * multithreaded server) may dictate a need to change the contents of + * unique_id_rec. Such a future implementation should ensure that the first + * field is still a time_t stamp. By doing that, it is possible for a site to + * have a "flag second" in which they stop all of their old-format servers, + * wait one entire second, and then start all of their new-servers. This + * procedure will ensure that the new space of identifiers is completely unique + * from the old space. (Since the first four unencoded bytes always differ.) + */ +/* + * Sun Jun 7 05:43:49 CEST 1998 -- Alvaro + * More comments: + * 1) The UUencoding prodecure is now done in a general way, avoiding + * the problems with sizes and paddings that can arise depending on + * the architecture. Now the offsets and sizes of the elements of the + * unique_id_rec structure are calculated in unique_id_global_init; + * and then used to duplicate the structure without the paddings that + * might exist. The multithreaded server fix should be now very easy: + * just add a new "tid" field to the unique_id_rec structure, and + * increase by one UNIQUE_ID_REC_MAX. + * 2) unique_id_rec.stamp has been changed from "time_t" to + * "unsigned int", because its size is 64bits on some platforms + * (linux/alpha), and this caused problems with htonl/ntohl. Well, + * this shouldn't be a problem till year 2106. + */ + +static unsigned global_in_addr; + +#ifdef WIN32 + +static DWORD tls_index; + +BOOL WINAPI DllMain (HINSTANCE dllhandle, DWORD reason, LPVOID reserved) +{ + LPVOID memptr; + + switch (reason) { + case DLL_PROCESS_ATTACH: + tls_index = TlsAlloc(); + case DLL_THREAD_ATTACH: /* intentional no break */ + TlsSetValue(tls_index, calloc(sizeof(unique_id_rec), 1)); + break; + case DLL_THREAD_DETACH: + memptr = TlsGetValue(tls_index); + if (memptr) { + free (memptr); + TlsSetValue (tls_index, 0); + } + break; + } + + return TRUE; +} + +static unique_id_rec* get_cur_unique_id(int parent) +{ + /* Apache initializes the child process, not the individual child threads. + * Copy the original parent record if this->pid is not yet initialized. + */ + static unique_id_rec *parent_id; + unique_id_rec *cur_unique_id = (unique_id_rec *) TlsGetValue(tls_index); + + if (parent) { + parent_id = cur_unique_id; + } + else if (!cur_unique_id->pid) { + memcpy(cur_unique_id, parent_id, sizeof(*parent_id)); + } + return cur_unique_id; +} + +#else /* !WIN32 */ + +/* Even when not MULTITHREAD, this will return a single structure, since + * APACHE_TLS should be defined as empty on single-threaded platforms. + */ +static unique_id_rec* get_cur_unique_id(int parent) +{ + static APACHE_TLS unique_id_rec spcid; + return &spcid; +} + +#endif /* !WIN32 */ + + +/* + * Number of elements in the structure unique_id_rec. + */ +#ifdef MULTITHREAD +#define UNIQUE_ID_REC_MAX 5 +#else +#define UNIQUE_ID_REC_MAX 4 +#endif + +static unsigned short unique_id_rec_offset[UNIQUE_ID_REC_MAX], + unique_id_rec_size[UNIQUE_ID_REC_MAX], + unique_id_rec_total_size, + unique_id_rec_size_uu; + +static void unique_id_global_init(server_rec *s, pool *p) +{ +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + char str[MAXHOSTNAMELEN + 1]; + struct hostent *hent; +#ifndef NO_GETTIMEOFDAY + struct timeval tv; +#endif + unique_id_rec *cur_unique_id = get_cur_unique_id(1); + + /* + * Calculate the sizes and offsets in cur_unique_id. + */ + unique_id_rec_offset[0] = XtOffsetOf(unique_id_rec, stamp); + unique_id_rec_size[0] = sizeof(cur_unique_id->stamp); + unique_id_rec_offset[1] = XtOffsetOf(unique_id_rec, in_addr); + unique_id_rec_size[1] = sizeof(cur_unique_id->in_addr); + unique_id_rec_offset[2] = XtOffsetOf(unique_id_rec, pid); + unique_id_rec_size[2] = sizeof(cur_unique_id->pid); +#ifdef MULTITHREAD + unique_id_rec_offset[3] = XtOffsetOf(unique_id_rec, tid); + unique_id_rec_size[3] = sizeof(cur_unique_id->tid); + unique_id_rec_offset[4] = XtOffsetOf(unique_id_rec, counter); + unique_id_rec_size[4] = sizeof(cur_unique_id->counter); + unique_id_rec_total_size = unique_id_rec_size[0] + unique_id_rec_size[1] + + unique_id_rec_size[2] + unique_id_rec_size[3] + + unique_id_rec_size[4]; +#else + unique_id_rec_offset[3] = XtOffsetOf(unique_id_rec, counter); + unique_id_rec_size[3] = sizeof(cur_unique_id->counter); + unique_id_rec_total_size = unique_id_rec_size[0] + unique_id_rec_size[1] + + unique_id_rec_size[2] + unique_id_rec_size[3]; +#endif + + /* + * Calculate the size of the structure when encoded. + */ + unique_id_rec_size_uu = (unique_id_rec_total_size*8+5)/6; + + /* + * Now get the global in_addr. Note that it is not sufficient to use one + * of the addresses from the main_server, since those aren't as likely to + * be unique as the physical address of the machine + */ + if (gethostname(str, sizeof(str) - 1) != 0) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ALERT, s, + "gethostname: mod_unique_id requires the " + "hostname of the server"); + exit(1); + } + str[sizeof(str) - 1] = '\0'; + + if ((hent = gethostbyname(str)) == NULL) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ALERT, s, + "mod_unique_id: unable to gethostbyname(\"%s\")", str); + exit(1); + } + + global_in_addr = ((struct in_addr *) hent->h_addr_list[0])->s_addr; + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, s, + "mod_unique_id: using ip addr %s", + inet_ntoa(*(struct in_addr *) hent->h_addr_list[0])); + + /* + * If the server is pummelled with restart requests we could possibly end + * up in a situation where we're starting again during the same second + * that has been used in previous identifiers. Avoid that situation. + * + * In truth, for this to actually happen not only would it have to restart + * in the same second, but it would have to somehow get the same pids as + * one of the other servers that was running in that second. Which would + * mean a 64k wraparound on pids ... not very likely at all. + * + * But protecting against it is relatively cheap. We just sleep into the + * next second. + */ +#ifdef NO_GETTIMEOFDAY + sleep(1); +#else + if (gettimeofday(&tv, NULL) == -1) { + sleep(1); + } + else if (tv.tv_usec) { + tv.tv_sec = 0; + tv.tv_usec = 1000000 - tv.tv_usec; + select(0, NULL, NULL, NULL, &tv); + } +#endif +} + +static void unique_id_child_init(server_rec *s, pool *p) +{ + pid_t pid; +#ifndef NO_GETTIMEOFDAY + struct timeval tv; +#endif + unique_id_rec *cur_unique_id = get_cur_unique_id(1); + + /* + * Note that we use the pid because it's possible that on the same + * physical machine there are multiple servers (i.e. using Listen). But + * it's guaranteed that none of them will share the same pids between + * children. + */ + pid = getpid(); + cur_unique_id->pid = pid; + + /* + * Test our assumption that the pid is 32-bits. It's possible that + * 64-bit machines will declare pid_t to be 64 bits but only use 32 + * of them. It would have been really nice to test this during + * global_init ... but oh well. + */ + if ((pid_t)cur_unique_id->pid != pid) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_CRIT, s, + "oh no! pids are greater than 32-bits! I'm broken!"); + } + + cur_unique_id->in_addr = global_in_addr; + + /* + * If we use 0 as the initial counter we have a little less protection + * against restart problems, and a little less protection against a clock + * going backwards in time. + */ +#ifndef NO_GETTIMEOFDAY + if (gettimeofday(&tv, NULL) == -1) { + cur_unique_id->counter = 0; + } + else { + /* Some systems have very low variance on the low end of their + * system counter, defend against that. + */ + cur_unique_id->counter = tv.tv_usec / 10; + } +#else + cur_unique_id->counter = 0; +#endif + + /* + * We must always use network ordering for these bytes, so that + * identifiers are comparable between machines of different byte + * orderings. Note in_addr is already in network order. + */ + cur_unique_id->pid = htonl(cur_unique_id->pid); + cur_unique_id->counter = htons(cur_unique_id->counter); +} + +/* NOTE: This is *NOT* the same encoding used by base64encode ... the last two + * characters should be + and /. But those two characters have very special + * meanings in URLs, and we want to make it easy to use identifiers in + * URLs. So we replace them with @ and -. + */ +static const char uuencoder[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '@', '-', +}; + +static int gen_unique_id(request_rec *r) +{ + char *str; + /* + * Buffer padded with two final bytes, used to copy the unique_id_red + * structure without the internal paddings that it could have. + */ + struct { + unique_id_rec foo; + unsigned char pad[2]; + } paddedbuf; + unsigned char *x,*y; + unsigned short counter; + const char *e; + int i,j,k; + unique_id_rec *cur_unique_id = get_cur_unique_id(0); + + /* copy the unique_id if this is an internal redirect (we're never + * actually called for sub requests, so we don't need to test for + * them) */ + if (r->prev + && (e = ap_table_get(r->subprocess_env, "REDIRECT_UNIQUE_ID"))) { + ap_table_setn(r->subprocess_env, "UNIQUE_ID", e); + return DECLINED; + } + + cur_unique_id->stamp = htonl((unsigned int)r->request_time); + +#ifdef MULTITHREAD + /* + * Note that we use the pid because it's possible that on the same + * physical machine there are multiple servers (i.e. using Listen). But + * it's guaranteed that none of them will share the same pid+tids between + * children. + */ + cur_unique_id->tid = gettid(); + cur_unique_id->tid = htonl(cur_unique_id->tid); +#endif + + /* we'll use a temporal buffer to avoid uuencoding the possible internal + * paddings of the original structure + */ + x = (unsigned char *) &paddedbuf; + y = (unsigned char *) cur_unique_id; + k = 0; + for (i = 0; i < UNIQUE_ID_REC_MAX; i++) { + y = ((unsigned char *) cur_unique_id) + unique_id_rec_offset[i]; + for (j = 0; j < unique_id_rec_size[i]; j++, k++) { + x[k] = y[j]; + } + } + /* + * We reset two more bytes just in case padding is needed for + * the uuencoding. + */ + x[k++] = '\0'; + x[k++] = '\0'; + + /* alloc str and do the uuencoding */ + str = (char *)ap_palloc(r->pool, unique_id_rec_size_uu + 1); + k = 0; + for (i = 0; i < unique_id_rec_total_size; i += 3) { + y = x + i; + str[k++] = uuencoder[y[0] >> 2]; + str[k++] = uuencoder[((y[0] & 0x03) << 4) | ((y[1] & 0xf0) >> 4)]; + if (k == unique_id_rec_size_uu) { + break; + } + str[k++] = uuencoder[((y[1] & 0x0f) << 2) | ((y[2] & 0xc0) >> 6)]; + if (k == unique_id_rec_size_uu) { + break; + } + str[k++] = uuencoder[y[2] & 0x3f]; + } + str[k++] = '\0'; + + /* set the environment variable */ + ap_table_setn(r->subprocess_env, "UNIQUE_ID", str); + + /* and increment the identifier for the next call */ + counter = ntohs(cur_unique_id->counter) + 1; + cur_unique_id->counter = htons(counter); + + return DECLINED; +} + +module MODULE_VAR_EXPORT unique_id_module = { + STANDARD_MODULE_STUFF, + unique_id_global_init, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server configs */ + NULL, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + unique_id_child_init, /* child_init */ + NULL, /* child_exit */ + gen_unique_id /* post_read_request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_userdir.c b/APACHE_1_3_42/src/modules/standard/mod_userdir.c new file mode 100644 index 0000000000..1ab6f1188d --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_userdir.c @@ -0,0 +1,363 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_userdir... implement the UserDir command. Broken away from the + * Alias stuff for a couple of good and not-so-good reasons: + * + * 1) It shows a real minimal working example of how to do something like + * this. + * 2) I know people who are actually interested in changing this *particular* + * aspect of server functionality without changing the rest of it. That's + * what this whole modular arrangement is supposed to be good at... + * + * Modified by Alexei Kosut to support the following constructs + * (server running at www.foo.com, request for /~bar/one/two.html) + * + * UserDir public_html -> ~bar/public_html/one/two.html + * UserDir /usr/web -> /usr/web/bar/one/two.html + * UserDir /home/ * /www -> /home/bar/www/one/two.html + * NOTE: theses ^ ^ space only added allow it to work in a comment, ignore + * UserDir http://x/users -> (302) http://x/users/bar/one/two.html + * UserDir http://x/ * /y -> (302) http://x/bar/y/one/two.html + * NOTE: here also ^ ^ + * + * In addition, you can use multiple entries, to specify alternate + * user directories (a la Directory Index). For example: + * + * UserDir public_html /usr/web http://www.xyz.com/users + * + * Modified by Ken Coar to provide for the following: + * + * UserDir disable[d] username ... + * UserDir enable[d] username ... + * + * If "disabled" has no other arguments, *all* ~<username> references are + * disabled, except those explicitly turned on with the "enabled" keyword. + */ + +#include "httpd.h" +#include "http_config.h" + +module userdir_module; + +typedef struct userdir_config { + int globally_disabled; + char *userdir; + table *enabled_users; + table *disabled_users; +} userdir_config; + +/* + * Server config for this module: global disablement flag, a list of usernames + * ineligible for UserDir access, a list of those immune to global (but not + * explicit) disablement, and the replacement string for all others. + */ + +static void *create_userdir_config(pool *p, server_rec *s) +{ + userdir_config *newcfg; + + newcfg = (userdir_config *) ap_pcalloc(p, sizeof(userdir_config)); + newcfg->globally_disabled = 0; + newcfg->userdir = DEFAULT_USER_DIR; + newcfg->enabled_users = ap_make_table(p, 4); + newcfg->disabled_users = ap_make_table(p, 4); + return (void *) newcfg; +} + +#define O_DEFAULT 0 +#define O_ENABLE 1 +#define O_DISABLE 2 + +static const char *set_user_dir(cmd_parms *cmd, void *dummy, char *arg) +{ + userdir_config *s_cfg; + char *username; + const char *usernames = arg; + char *kw = ap_getword_conf(cmd->pool, &usernames); + table *usertable; + + s_cfg = (userdir_config *) ap_get_module_config(cmd->server->module_config, + &userdir_module); + /* + * Let's do the comparisons once. + */ + if ((!strcasecmp(kw, "disable")) || (!strcasecmp(kw, "disabled"))) { + /* + * If there are no usernames specified, this is a global disable - we + * need do no more at this point than record the fact. + */ + if (strlen(usernames) == 0) { + s_cfg->globally_disabled = 1; + return NULL; + } + usertable = s_cfg->disabled_users; + } + else if ((!strcasecmp(kw, "enable")) || (!strcasecmp(kw, "enabled"))) { + /* + * The "disable" keyword can stand alone or take a list of names, but + * the "enable" keyword requires the list. Whinge if it doesn't have + * it. + */ + if (strlen(usernames) == 0) { + return "UserDir \"enable\" keyword requires a list of usernames"; + } + usertable = s_cfg->enabled_users; + } + else { + /* + * If the first (only?) value isn't one of our keywords, look at each + * config 'word' for validity and copy the entire arg to the userdir + * if all paths are valid. + */ + const char *userdirs = arg; + while (*userdirs) { + char *thisdir = ap_getword_conf(cmd->pool, &userdirs); + if (!ap_os_is_path_absolute(thisdir) && !strchr(thisdir, ':')) { +#if defined(WIN32) || defined(NETWARE) + return "UserDir must specify an absolute redirect " + "or absolute file path"; +#else + if (strchr(thisdir, '*')) { + return "UserDir cannot specify '*' substitution within " + "a relative path"; + } +#endif + } + } + s_cfg->userdir = ap_pstrdup(cmd->pool, arg); +#if defined(WIN32) || defined(OS2) || defined(NETWARE) + /* These are incomplete paths, so we cannot canonicalize them yet. + * but any backslashes will confuse the parser, later, so simply + * change them to slash form. + */ + arg = s_cfg->userdir; + while (arg = strchr(arg, '\\')) { + *(arg++) = '/'; + } +#endif + return NULL; + } + /* + * Now we just take each word in turn from the command line and add it to + * the appropriate table. + */ + while (*usernames) { + username = ap_getword_conf(cmd->pool, &usernames); + ap_table_setn(usertable, username, kw); + } + return NULL; +} + +static const command_rec userdir_cmds[] = +{ + {"UserDir", set_user_dir, NULL, RSRC_CONF, RAW_ARGS, + "the public subdirectory in users' home directories, or " + "'disabled', or 'disabled username username...', or " + "'enabled username username...'"}, + {NULL} +}; + +static int translate_userdir(request_rec *r) +{ + void *server_conf = r->server->module_config; + const userdir_config *s_cfg = + (userdir_config *) ap_get_module_config(server_conf, &userdir_module); + char *name = r->uri; + const char *userdirs = s_cfg->userdir; + const char *w, *dname; + char *redirect; + struct stat statbuf; + + /* + * If the URI doesn't match our basic pattern, we've nothing to do with + * it. + */ + if ((s_cfg->userdir == NULL) + || (name[0] != '/') + || (name[1] != '~')) { + return DECLINED; + } + + dname = name + 2; + w = ap_getword(r->pool, &dname, '/'); + + /* + * The 'dname' funny business involves backing it up to capture the '/' + * delimiting the "/~user" part from the rest of the URL, in case there + * was one (the case where there wasn't being just "GET /~user HTTP/1.0", + * for which we don't want to tack on a '/' onto the filename). + */ + + if (dname[-1] == '/') { + --dname; + } + + /* + * If there's no username, it's not for us. Ignore . and .. as well. + */ + if ((w[0] == '\0') + || ((w[1] == '.') + && ((w[2] == '\0') + || ((w[2] == '.') && (w[3] == '\0'))))) { + return DECLINED; + } + /* + * Nor if there's an username but it's in the disabled list. + */ + if (ap_table_get(s_cfg->disabled_users, w) != NULL) { + return DECLINED; + } + /* + * If there's a global interdiction on UserDirs, check to see if this + * name is one of the Blessed. + */ + if (s_cfg->globally_disabled + && (ap_table_get(s_cfg->enabled_users, w) == NULL)) { + return DECLINED; + } + + /* + * Special cases all checked, onward to normal substitution processing. + */ + + while (*userdirs) { + const char *userdir = ap_getword_conf(r->pool, &userdirs); + char *filename = NULL; + int is_absolute = ap_os_is_path_absolute(userdir); + + if (strchr(userdir, '*')) { + /* token '*' embedded: + */ + char *x = ap_getword(r->pool, &userdir, '*'); + if (is_absolute) { + /* token '*' within absolute path + * serves [UserDir arg-pre*][user][UserDir arg-post*] + * /somepath/ * /somedir + /~smith -> /somepath/smith/somedir + */ + filename = ap_pstrcat(r->pool, x, w, userdir, NULL); + } + else if (strchr(x, ':')) { + /* token '*' within a redirect path + * serves [UserDir arg-pre*][user][UserDir arg-post*] + * http://server/user/ * + /~smith/foo -> + * http://server/user/smith/foo + */ + redirect = ap_pstrcat(r->pool, x, w, userdir, dname, NULL); + ap_table_setn(r->headers_out, "Location", redirect); + return REDIRECT; + } + else { + /* Not a redirect, not an absolute path, '*' token: + * serves [homedir]/[UserDir arg] + * something/ * /public_html + * Shouldn't happen, we trap for this in set_user_dir + */ + return DECLINED; + } + } + else if (is_absolute) { + /* An absolute path, no * token: + * serves [UserDir arg]/[user] + * /home + /~smith -> /home/smith + */ + if (userdir[strlen(userdir) - 1] == '/') + filename = ap_pstrcat(r->pool, userdir, w, NULL); + else + filename = ap_pstrcat(r->pool, userdir, "/", w, NULL); + } + else if (strchr(userdir, ':')) { + /* A redirect, not an absolute path, no * token: + * serves [UserDir arg]/[user][dname] + * http://server/ + /~smith/foo -> http://server/smith/foo + */ + if (userdir[strlen(userdir) - 1] == '/') { + redirect = ap_pstrcat(r->pool, userdir, w, dname, NULL); + } + else { + redirect = ap_pstrcat(r->pool, userdir, "/", w, dname, NULL); + } + ap_table_setn(r->headers_out, "Location", redirect); + return REDIRECT; + } + else { + /* Not a redirect, not an absolute path, no * token: + * serves [homedir]/[UserDir arg] + * e.g. /~smith -> /home/smith/public_html + */ +#if defined(WIN32) || defined(NETWARE) + /* Need to figure out home dirs on NT and NetWare + * Shouldn't happen here, though, we trap for this in set_user_dir + */ + return DECLINED; +#else /* WIN32 & NetWare */ + struct passwd *pw; + if ((pw = getpwnam(w))) { +#ifdef OS2 + /* Need to manually add user name for OS/2 */ + filename = ap_pstrcat(r->pool, pw->pw_dir, w, "/", + userdir, NULL); +#else + filename = ap_pstrcat(r->pool, pw->pw_dir, "/", + userdir, NULL); +#endif + } +#endif /* WIN32 & NetWare */ + } + + /* + * Now see if it exists, or we're at the last entry. If we are at the + * last entry, then use the filename generated (if there is one) + * anyway, in the hope that some handler might handle it. This can be + * used, for example, to run a CGI script for the user. + */ + if (filename && (!*userdirs || stat(filename, &statbuf) != -1)) { + r->filename = ap_pstrcat(r->pool, filename, dname, NULL); + /* when statbuf contains info on r->filename we can save a syscall + * by copying it to r->finfo + */ + if (*userdirs && dname[0] == 0) { + r->finfo = statbuf; + } + return OK; + } + } + + return DECLINED; +} + +module userdir_module = { + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + create_userdir_config, /* server config */ + NULL, /* merge server config */ + userdir_cmds, /* command table */ + NULL, /* handlers */ + translate_userdir, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/standard/mod_usertrack.c b/APACHE_1_3_42/src/modules/standard/mod_usertrack.c new file mode 100644 index 0000000000..92e05f096b --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_usertrack.c @@ -0,0 +1,590 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* User Tracking Module (Was mod_cookies.c) + * + * *** IMPORTANT NOTE: This module is not designed to generate + * *** cryptographically secure cookies. This means you should not + * *** use cookies generated by this module for authentication purposes + * + * This Apache module is designed to track users paths through a site. + * It uses the client-side state ("Cookie") protocol developed by Netscape. + * It is known to work on most browsers. + * + * Each time a page is requested we look to see if the browser is sending + * us a Cookie: header that we previously generated. + * + * If we don't find one then the user hasn't been to this site since + * starting their browser or their browser doesn't support cookies. So + * we generate a unique Cookie for the transaction and send it back to + * the browser (via a "Set-Cookie" header) + * Future requests from the same browser should keep the same Cookie line. + * + * By matching up all the requests with the same cookie you can + * work out exactly what path a user took through your site. To log + * the cookie use the " %{Cookie}n " directive in a custom access log; + * + * Example 1 : If you currently use the standard Log file format (CLF) + * and use the command "TransferLog somefilename", add the line + * LogFormat "%h %l %u %t \"%r\" %s %b %{Cookie}n" + * to your config file. + * + * Example 2 : If you used to use the old "CookieLog" directive, you + * can emulate it by adding the following command to your config file + * CustomLog filename "%{Cookie}n \"%r\" %t" + * + * Mark Cox, mjc@apache.org, 6 July 95 + * + * This file replaces mod_cookies.c + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#if !defined(WIN32) && !defined(MPE) && !defined(TPF41) +#include <sys/time.h> +#endif + +module MODULE_VAR_EXPORT usertrack_module; + +typedef struct { + int always; + time_t expires; +} cookie_log_state; + +typedef enum { + CT_UNSET, + CT_NETSCAPE, + CT_COOKIE, + CT_COOKIE2 +} cookie_type_e; + +typedef enum { + CF_NORMAL, + CF_COMPACT +} cookie_format_e; + +typedef struct { + int enabled; + cookie_type_e style; + cookie_format_e format; + char *cookie_name; + char *cookie_domain; + char *prefix_string; + char *regexp_string; /* used to compile regexp; save for debugging */ + regex_t *regexp; /* used to find usertrack cookie in cookie header */ +} cookie_dir_rec; + +/* Define this to allow post-2000 cookies. Cookies use two-digit dates, + * so it might be dicey. (Netscape does it correctly, but others may not) + */ +#define MILLENIAL_COOKIES + +/* Default name of the cookie + */ +#define COOKIE_NAME "Apache" + + +/* Make cookie id: Try to make something unique based on + * pid, time, and hostid, plus the user-configurable prefix. + * + */ +static char * make_cookie_id(char * buffer, int bufsize, request_rec *r, + cookie_format_e cformat) +{ +#if defined(NO_GETTIMEOFDAY) && !defined(NO_TIMES) + clock_t mpe_times; + struct tms mpe_tms; +#elif !defined(WIN32) + struct timeval tv; +#ifdef NETWARE + time_t tz = 0; +#else + struct timezone tz = {0, 0}; +#endif /* defined(NETWARE) */ +#endif + + cookie_dir_rec *dcfg; + + long reqtime = (long) r->request_time; + long clocktime; + + unsigned long ipaddr = ntohl(r->connection->remote_addr.sin_addr.s_addr); + const char *rname = ap_get_remote_host(r->connection, r->per_dir_config, + REMOTE_NAME); + dcfg = ap_get_module_config(r->per_dir_config, &usertrack_module); + +#if defined(NO_GETTIMEOFDAY) && !defined(NO_TIMES) +/* We lack gettimeofday(), so we must use time() to obtain the epoch + seconds, and then times() to obtain CPU clock ticks (milliseconds). + Combine this together to obtain a hopefully unique cookie ID. */ + + mpe_times = times(&mpe_tms); + clocktime = (long) mpe_tms.tms_utime; + +#elif defined(NETWARE) + clocktime = (long) clock(); + +#elif defined(WIN32) + /* + * We lack gettimeofday() and we lack times(). So we'll use + * GetTickCount(), which returns milliseconds since Windows + * was started. It should be relatively unique. + */ + + clocktime = (long) GetTickCount(); + +#else + gettimeofday(&tv, &tz); + + reqtime = (long) tv.tv_sec; + if (cformat == CF_COMPACT) + clocktime = (long) (tv.tv_usec % 65535); + else + clocktime = (long) (tv.tv_usec / 1000); +#endif + + if (cformat == CF_COMPACT) + ap_snprintf(buffer, bufsize, "%s%lx%x%lx%lx", + dcfg->prefix_string, ipaddr, (int) getpid(), + reqtime, clocktime); + else + ap_snprintf(buffer, bufsize, "%s%s.%d%ld%ld", + dcfg->prefix_string, rname, (int) getpid(), + reqtime, clocktime); + + return buffer; +} + + + +static void make_cookie(request_rec *r) +{ + cookie_log_state *cls = ap_get_module_config(r->server->module_config, + &usertrack_module); + + /* 1024 == hardcoded constant */ + char cookiebuf[1024]; + char *new_cookie; + cookie_dir_rec *dcfg; + + dcfg = ap_get_module_config(r->per_dir_config, &usertrack_module); + + make_cookie_id(cookiebuf, sizeof(cookiebuf), r, dcfg->format); + + if (cls->expires) { + struct tm *tms; + time_t when; + + when = cls->expires; + if ((dcfg->style == CT_UNSET) || (dcfg->style == CT_NETSCAPE)) { + when += r->request_time; + +#ifndef MILLENIAL_COOKIES + /* + * Only two-digit date string, so we can't trust "00" or more. + * Therefore, we knock it all back to just before midnight on + * 1/1/2000 (which is 946684799) + */ + + if (when > 946684799) + when = 946684799; +#endif + } + tms = gmtime(&when); + + /* Cookie with date; as strftime '%a, %d-%h-%y %H:%M:%S GMT' */ + new_cookie = ap_psprintf(r->pool, "%s=%s; path=/", + dcfg->cookie_name, cookiebuf); + if ((dcfg->style == CT_UNSET) || (dcfg->style == CT_NETSCAPE)) { + new_cookie = ap_psprintf(r->pool, "%s; " + "expires=%s, %.2d-%s-%.2d " + "%.2d:%.2d:%.2d GMT", + new_cookie, + ap_day_snames[tms->tm_wday], + tms->tm_mday, + ap_month_snames[tms->tm_mon], + tms->tm_year % 100, + tms->tm_hour, tms->tm_min, tms->tm_sec); + } + else { + new_cookie = ap_psprintf(r->pool, "%s; max-age=%d", + new_cookie, (int) when); + } + } + else { + new_cookie = ap_psprintf(r->pool, "%s=%s; path=/", + dcfg->cookie_name, cookiebuf); + } + if (dcfg->cookie_domain != NULL) { + new_cookie = ap_psprintf(r->pool, "%s; domain=%s", + new_cookie, dcfg->cookie_domain); + } + if (dcfg->style == CT_COOKIE2) { + new_cookie = ap_pstrcat(r->pool, new_cookie, "; version=1", NULL); + } + + ap_table_addn(r->headers_out, + (dcfg->style == CT_COOKIE2 ? "Set-Cookie2" : "Set-Cookie"), + new_cookie); + ap_table_setn(r->notes, "cookie", ap_pstrdup(r->pool, cookiebuf)); /* log first time */ + return; +} + +/* + * dcfg->regexp is "^cookie_name=([^;]+)|;[ \t]+cookie_name=([^;]+)", + * which has three subexpressions, $0..$2 + */ +#define NUM_SUBS 3 + +static void set_and_comp_regexp(cookie_dir_rec *dcfg, + pool *p, + const char *cookie_name) +{ + int danger_chars = 0; + const char *sp = cookie_name; + + /* + * The goal is to end up with this regexp, + * ^cookie_name=([^;]+)|;[\t]+cookie_name=([^;]+) + * with cookie_name obviously substituted either + * with the real cookie name set by the user in httpd.conf, + * or with the default COOKIE_NAME. + */ + + /* Anyway, we need to escape the cookie_name before pasting it + * into the regex + */ + while (*sp) { + if (!ap_isalnum(*sp)) { + ++danger_chars; + } + ++sp; + } + + if (danger_chars) { + char *cp; + cp = ap_palloc(p, sp - cookie_name + danger_chars + 1); /* 1 == \0 */ + sp = cookie_name; + cookie_name = cp; + while (*sp) { + if (!ap_isalnum(*sp)) { + *cp++ = '\\'; + } + *cp++ = *sp++; + } + *cp = '\0'; + } + + dcfg->regexp_string = ap_pstrcat(p, "^", cookie_name, + "=([^;]+)|;[ \t]+", cookie_name, + "=([^;]+)", NULL); + dcfg->regexp = ap_pregcomp(p, dcfg->regexp_string, REG_EXTENDED); + ap_assert(dcfg->regexp != NULL); +} + +static int spot_cookie(request_rec *r) +{ + cookie_dir_rec *dcfg = ap_get_module_config(r->per_dir_config, + &usertrack_module); + const char *cookie_header; + regmatch_t regm[NUM_SUBS]; + + if (!dcfg->enabled) { + return DECLINED; + } + + if ((cookie_header = ap_table_get(r->headers_in, "Cookie"))) { + if (!ap_regexec(dcfg->regexp, cookie_header, NUM_SUBS, regm, 0)) { + char *cookieval = NULL; + /* Our regexp, + * ^cookie_name=([^;]+)|;[ \t]+cookie_name=([^;]+) + * only allows for $1 or $2 to be available. ($0 is always + * filled with the entire matched expression, not just + * the part in parentheses.) So just check for either one + * and assign to cookieval if present. */ + if (regm[1].rm_so != -1) { + cookieval = ap_pregsub(r->pool, "$1", cookie_header, + NUM_SUBS, regm); + } + if (regm[2].rm_so != -1) { + cookieval = ap_pregsub(r->pool, "$2", cookie_header, + NUM_SUBS, regm); + } + /* Set the cookie in a note, for logging */ + ap_table_setn(r->notes, "cookie", cookieval); + + return DECLINED; /* There's already a cookie, no new one */ + } + } + make_cookie(r); + return OK; /* We set our cookie */ +} + +static void *make_cookie_log_state(pool *p, server_rec *s) +{ + cookie_log_state *cls = + (cookie_log_state *) ap_palloc(p, sizeof(cookie_log_state)); + + cls->expires = 0; + + return (void *) cls; +} + +static void *make_cookie_dir(pool *p, char *d) +{ + cookie_dir_rec *dcfg; + + dcfg = (cookie_dir_rec *) ap_pcalloc(p, sizeof(cookie_dir_rec)); + dcfg->cookie_name = COOKIE_NAME; + dcfg->cookie_domain = NULL; + dcfg->prefix_string = ""; + dcfg->style = CT_UNSET; + dcfg->format = CF_NORMAL; + dcfg->enabled = 0; + /* + * In case the user does not use the CookieName directive, + * we need to compile the regexp for the default cookie name. + */ + set_and_comp_regexp(dcfg, p, COOKIE_NAME); + return dcfg; +} + +static const char *set_cookie_enable(cmd_parms *cmd, void *mconfig, int arg) +{ + cookie_dir_rec *dcfg = mconfig; + + dcfg->enabled = arg; + return NULL; +} + +static const char *set_cookie_exp(cmd_parms *parms, void *dummy, + const char *arg) +{ + cookie_log_state *cls; + time_t factor, modifier = 0; + time_t num = 0; + char *word; + + cls = ap_get_module_config(parms->server->module_config, + &usertrack_module); + /* The simple case first - all numbers (we assume) */ + if (ap_isdigit(arg[0]) && ap_isdigit(arg[strlen(arg) - 1])) { + cls->expires = atol(arg); + return NULL; + } + + /* + * The harder case - stolen from mod_expires + * + * CookieExpires "[plus] {<num> <type>}*" + */ + + word = ap_getword_conf(parms->pool, &arg); + if (!strncasecmp(word, "plus", 1)) { + word = ap_getword_conf(parms->pool, &arg); + }; + + /* {<num> <type>}* */ + while (word[0]) { + /* <num> */ + if (ap_isdigit(word[0])) + num = atoi(word); + else + return "bad expires code, numeric value expected."; + + /* <type> */ + word = ap_getword_conf(parms->pool, &arg); + if (!word[0]) + return "bad expires code, missing <type>"; + + factor = 0; + if (!strncasecmp(word, "years", 1)) + factor = 60 * 60 * 24 * 365; + else if (!strncasecmp(word, "months", 2)) + factor = 60 * 60 * 24 * 30; + else if (!strncasecmp(word, "weeks", 1)) + factor = 60 * 60 * 24 * 7; + else if (!strncasecmp(word, "days", 1)) + factor = 60 * 60 * 24; + else if (!strncasecmp(word, "hours", 1)) + factor = 60 * 60; + else if (!strncasecmp(word, "minutes", 2)) + factor = 60; + else if (!strncasecmp(word, "seconds", 1)) + factor = 1; + else + return "bad expires code, unrecognized type"; + + modifier = modifier + factor * num; + + /* next <num> */ + word = ap_getword_conf(parms->pool, &arg); + } + + cls->expires = modifier; + + return NULL; +} + +static const char *set_cookie_name(cmd_parms *cmd, void *mconfig, char *name) +{ + cookie_dir_rec *dcfg = (cookie_dir_rec *) mconfig; + + dcfg->cookie_name = ap_pstrdup(cmd->pool, name); + + set_and_comp_regexp(dcfg, cmd->pool, name); + + if (dcfg->regexp == NULL) { + return "Regular expression could not be compiled."; + } + if (dcfg->regexp->re_nsub + 1 != NUM_SUBS) { + return ap_pstrcat(cmd->pool, "Invalid cookie name \"", + name, "\"", NULL); + } + + return NULL; +} + +/* + * Set the value for the 'Domain=' attribute. + */ +static const char *set_cookie_domain(cmd_parms *cmd, void *mconfig, char *name) +{ + cookie_dir_rec *dcfg; + + dcfg = (cookie_dir_rec *) mconfig; + + /* + * Apply the restrictions on cookie domain attributes. + */ + if (strlen(name) == 0) { + return "CookieDomain values may not be null"; + } + if (name[0] != '.') { + return "CookieDomain values must begin with a dot"; + } + if (strchr(&name[1], '.') == NULL) { + return "CookieDomain values must contain at least one embedded dot"; + } + + dcfg->cookie_domain = ap_pstrdup(cmd->pool, name); + return NULL; +} + +/* + * Make a note of the cookie style we should use. + */ +static const char *set_cookie_style(cmd_parms *cmd, void *mconfig, char *name) +{ + cookie_dir_rec *dcfg; + + dcfg = (cookie_dir_rec *) mconfig; + + if (strcasecmp(name, "Netscape") == 0) { + dcfg->style = CT_NETSCAPE; + } + else if ((strcasecmp(name, "Cookie") == 0) + || (strcasecmp(name, "RFC2109") == 0)) { + dcfg->style = CT_COOKIE; + } + else if ((strcasecmp(name, "Cookie2") == 0) + || (strcasecmp(name, "RFC2965") == 0)) { + dcfg->style = CT_COOKIE2; + } + else { + return ap_psprintf(cmd->pool, "Invalid %s keyword: '%s'", + cmd->cmd->name, name); + } + + return NULL; +} + +/* + * Make a note of the cookie format we should use. + */ +static const char *set_cookie_format(cmd_parms *cmd, void *mconfig, char *name) +{ + cookie_dir_rec *dcfg; + + dcfg = (cookie_dir_rec *) mconfig; + + if (strcasecmp(name, "Normal") == 0) { + dcfg->format = CF_NORMAL; + } + else if (strcasecmp(name, "Compact") == 0) { + dcfg->format = CF_COMPACT; + } + else { + return ap_psprintf(cmd->pool, "Invalid %s keyword: '%s'", + cmd->cmd->name, name); + } + + return NULL; +} + +static const char *set_cookie_prefix(cmd_parms *cmd, void *mconfig, char *name) +{ + cookie_dir_rec *dcfg = (cookie_dir_rec *) mconfig; + + dcfg->prefix_string = ap_pstrdup(cmd->pool, name); + + return NULL; +} + + +static const command_rec cookie_log_cmds[] = { + {"CookieExpires", set_cookie_exp, NULL, OR_FILEINFO, TAKE1, + "an expiry date code"}, + {"CookieTracking", set_cookie_enable, NULL, OR_FILEINFO, FLAG, + "whether or not to enable cookies"}, + {"CookieName", set_cookie_name, NULL, OR_FILEINFO, TAKE1, + "name of the tracking cookie"}, + {"CookieDomain", set_cookie_domain, NULL, OR_FILEINFO, TAKE1, + "domain to which this cookie applies"}, + {"CookieStyle", set_cookie_style, NULL, OR_FILEINFO, TAKE1, + "'Netscape', 'Cookie' (RFC2109), or 'Cookie2' (RFC2965)"}, + {"CookieFormat", set_cookie_format, NULL, OR_FILEINFO, TAKE1, + "'Normal' or 'Compact'"}, + {"CookiePrefix", set_cookie_prefix, NULL, OR_FILEINFO, TAKE1, + "String prepended to cookie"}, + {NULL} +}; + +module MODULE_VAR_EXPORT usertrack_module = { + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + make_cookie_dir, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + make_cookie_log_state, /* server config */ + NULL, /* merge server configs */ + cookie_log_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + spot_cookie, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; + + + diff --git a/APACHE_1_3_42/src/modules/standard/mod_vhost_alias.c b/APACHE_1_3_42/src/modules/standard/mod_vhost_alias.c new file mode 100644 index 0000000000..75bca5eefe --- /dev/null +++ b/APACHE_1_3_42/src/modules/standard/mod_vhost_alias.c @@ -0,0 +1,442 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_vhost_alias.c: support for dynamically configured mass virtual hosting + * + * Copyright (c) 1998-1999 Demon Internet Ltd. + * + * This software was submitted by Demon Internet to the Apache Group + * in May 1999. Future revisions and derivatives of this source code + * must acknowledge Demon Internet as the original contributor of + * this module. All other licensing and usage conditions are those + * of the Apache Group. + * + * Originally written by Tony Finch <fanf@demon.net> <dot@dotat.at>. + * + * Implementation ideas were taken from mod_alias.c. The overall + * concept is derived from the OVERRIDE_DOC_ROOT/OVERRIDE_CGIDIR + * patch to Apache 1.3b3 and a similar feature in Demon's thttpd, + * both written by James Grinter <jrg@blodwen.demon.co.uk>. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" + + +module MODULE_VAR_EXPORT vhost_alias_module; + + +/* + * basic configuration things + * we abbreviate "mod_vhost_alias" to "mva" for shorter names + */ + +typedef enum { + VHOST_ALIAS_UNSET, VHOST_ALIAS_NONE, VHOST_ALIAS_NAME, VHOST_ALIAS_IP +} mva_mode_e; + +/* + * Per-server module config record. + */ +typedef struct mva_sconf_t { + char *doc_root; + char *cgi_root; + mva_mode_e doc_root_mode; + mva_mode_e cgi_root_mode; +} mva_sconf_t; + +static void *mva_create_server_config(pool *p, server_rec *s) +{ + mva_sconf_t *conf; + + conf = (mva_sconf_t *) ap_pcalloc(p, sizeof(mva_sconf_t)); + conf->doc_root = NULL; + conf->cgi_root = NULL; + conf->doc_root_mode = VHOST_ALIAS_UNSET; + conf->cgi_root_mode = VHOST_ALIAS_UNSET; + return conf; +} + +static void *mva_merge_server_config(pool *p, void *parentv, void *childv) +{ + mva_sconf_t *parent = (mva_sconf_t *) parentv; + mva_sconf_t *child = (mva_sconf_t *) childv; + mva_sconf_t *conf; + + conf = (mva_sconf_t *) ap_pcalloc(p, sizeof(*conf)); + if (child->doc_root_mode == VHOST_ALIAS_UNSET) { + conf->doc_root_mode = parent->doc_root_mode; + conf->doc_root = parent->doc_root; + } + else { + conf->doc_root_mode = child->doc_root_mode; + conf->doc_root = child->doc_root; + } + if (child->cgi_root_mode == VHOST_ALIAS_UNSET) { + conf->cgi_root_mode = parent->cgi_root_mode; + conf->cgi_root = parent->cgi_root; + } + else { + conf->cgi_root_mode = child->cgi_root_mode; + conf->cgi_root = child->cgi_root; + } + return conf; +} + + +/* + * These are just here to tell us what vhost_alias_set should do. + * We don't put anything into them; we just use the cell addresses. + */ +static int vhost_alias_set_doc_root_ip, + vhost_alias_set_cgi_root_ip, + vhost_alias_set_doc_root_name, + vhost_alias_set_cgi_root_name; + +static const char *vhost_alias_set(cmd_parms *cmd, void *dummy, char *map) +{ + mva_sconf_t *conf; + mva_mode_e mode, *pmode; + char **pmap; + char *p; + + conf = (mva_sconf_t *) ap_get_module_config(cmd->server->module_config, + &vhost_alias_module); + /* there ought to be a better way of doing this */ + if (&vhost_alias_set_doc_root_ip == cmd->info) { + mode = VHOST_ALIAS_IP; + pmap = &conf->doc_root; + pmode = &conf->doc_root_mode; + } + else if (&vhost_alias_set_cgi_root_ip == cmd->info) { + mode = VHOST_ALIAS_IP; + pmap = &conf->cgi_root; + pmode = &conf->cgi_root_mode; + } + else if (&vhost_alias_set_doc_root_name == cmd->info) { + mode = VHOST_ALIAS_NAME; + pmap = &conf->doc_root; + pmode = &conf->doc_root_mode; + } + else if (&vhost_alias_set_cgi_root_name == cmd->info) { + mode = VHOST_ALIAS_NAME; + pmap = &conf->cgi_root; + pmode = &conf->cgi_root_mode; + } + else { + return "INTERNAL ERROR: unknown command info"; + } + + if (!(ap_os_is_path_absolute(map))) { + if (strcasecmp(map, "none")) { + return "format string must be an absolute file path or 'none'"; + } + *pmap = NULL; + *pmode = VHOST_ALIAS_NONE; + return NULL; + } + + /* sanity check */ + p = map; + while (*p != '\0') { + if (*p++ != '%') { + continue; + } + /* we just found a '%' */ + if (*p == 'p' || *p == '%') { + ++p; + continue; + } + /* optional dash */ + if (*p == '-') { + ++p; + } + /* digit N */ + if (ap_isdigit(*p)) { + ++p; + } + else { + return "syntax error in format string"; + } + /* optional plus */ + if (*p == '+') { + ++p; + } + /* do we end here? */ + if (*p != '.') { + continue; + } + ++p; + /* optional dash */ + if (*p == '-') { + ++p; + } + /* digit M */ + if (ap_isdigit(*p)) { + ++p; + } + else { + return "syntax error in format string"; + } + /* optional plus */ + if (*p == '+') { + ++p; + } + } + *pmap = map; + *pmode = mode; + return NULL; +} + +static const command_rec mva_commands[] = +{ + {"VirtualScriptAlias", vhost_alias_set, &vhost_alias_set_cgi_root_name, + RSRC_CONF, TAKE1, "how to create a ScriptAlias based on the host"}, + {"VirtualDocumentRoot", vhost_alias_set, &vhost_alias_set_doc_root_name, + RSRC_CONF, TAKE1, "how to create the DocumentRoot based on the host"}, + {"VirtualScriptAliasIP", vhost_alias_set, &vhost_alias_set_cgi_root_ip, + RSRC_CONF, TAKE1, "how to create a ScriptAlias based on the host"}, + {"VirtualDocumentRootIP", vhost_alias_set, &vhost_alias_set_doc_root_ip, + RSRC_CONF, TAKE1, "how to create the DocumentRoot based on the host"}, + { NULL } +}; + + +/* + * This really wants to be a nested function + * but C is too feeble to support them. + */ +static ap_inline void vhost_alias_checkspace(request_rec *r, char *buf, + char **pdest, int size) +{ + /* XXX: what if size > HUGE_STRING_LEN? */ + if (*pdest + size > buf + HUGE_STRING_LEN) { + **pdest = '\0'; + if (r->filename) { + r->filename = ap_pstrcat(r->pool, r->filename, buf, NULL); + } + else { + r->filename = ap_pstrdup(r->pool, buf); + } + *pdest = buf; + } +} + +static void vhost_alias_interpolate(request_rec *r, const char *name, + const char *map, const char *uri) +{ + /* 0..9 9..0 */ + enum { MAXDOTS = 19 }; + const char *dots[MAXDOTS+1]; + int ndots; + + char buf[HUGE_STRING_LEN]; + char *dest, last; + + int N, M, Np, Mp, Nd, Md; + const char *start, *end; + + const char *p; + + ndots = 0; + dots[ndots++] = name-1; /* slightly naughty */ + for (p = name; *p; ++p){ + if (*p == '.' && ndots < MAXDOTS) { + dots[ndots++] = p; + } + } + dots[ndots] = p; + + r->filename = NULL; + + dest = buf; + last = '\0'; + while (*map) { + if (*map != '%') { + /* normal characters */ + vhost_alias_checkspace(r, buf, &dest, 1); + last = *dest++ = *map++; + continue; + } + /* we are in a format specifier */ + ++map; + /* can't be a slash */ + last = '\0'; + /* %% -> % */ + if (*map == '%') { + ++map; + vhost_alias_checkspace(r, buf, &dest, 1); + *dest++ = '%'; + continue; + } + /* port number */ + if (*map == 'p') { + ++map; + /* no. of decimal digits in a short plus one */ + vhost_alias_checkspace(r, buf, &dest, 7); + dest += ap_snprintf(dest, 7, "%d", ap_get_server_port(r)); + continue; + } + /* deal with %-N+.-M+ -- syntax is already checked */ + N = M = 0; /* value */ + Np = Mp = 0; /* is there a plus? */ + Nd = Md = 0; /* is there a dash? */ + if (*map == '-') ++map, Nd = 1; + N = *map++ - '0'; + if (*map == '+') ++map, Np = 1; + if (*map == '.') { + ++map; + if (*map == '-') { + ++map, Md = 1; + } + M = *map++ - '0'; + if (*map == '+') { + ++map, Mp = 1; + } + } + /* note that N and M are one-based indices, not zero-based */ + start = dots[0]+1; /* ptr to the first character */ + end = dots[ndots]; /* ptr to the character after the last one */ + if (N != 0) { + if (N > ndots) { + start = "_"; + end = start+1; + } + else if (!Nd) { + start = dots[N-1]+1; + if (!Np) { + end = dots[N]; + } + } + else { + if (!Np) { + start = dots[ndots-N]+1; + } + end = dots[ndots-N+1]; + } + } + if (M != 0) { + if (M > end - start) { + start = "_"; + end = start+1; + } + else if (!Md) { + start = start+M-1; + if (!Mp) { + end = start+1; + } + } + else { + if (!Mp) { + start = end-M; + } + end = end-M+1; + } + } + vhost_alias_checkspace(r, buf, &dest, end - start); + for (p = start; p < end; ++p) { + *dest++ = ap_tolower(*p); + } + } + *dest = '\0'; + /* no double slashes */ + if (last == '/') { + ++uri; + } + if (r->filename) { + r->filename = ap_pstrcat(r->pool, r->filename, buf, uri, NULL); + } + else { + r->filename = ap_pstrcat(r->pool, buf, uri, NULL); + } +} + +static int mva_translate(request_rec *r) +{ + mva_sconf_t *conf; + const char *name, *map, *uri; + mva_mode_e mode; + const char *cgi; + + conf = (mva_sconf_t *) ap_get_module_config(r->server->module_config, + &vhost_alias_module); + cgi = NULL; + if (conf->cgi_root) { + cgi = strstr(r->uri, "cgi-bin/"); + if (cgi && (cgi != r->uri + strspn(r->uri, "/"))) { + cgi = NULL; + } + } + if (cgi) { + mode = conf->cgi_root_mode; + map = conf->cgi_root; + uri = cgi + strlen("cgi-bin"); + } + else if (r->uri[0] == '/') { + mode = conf->doc_root_mode; + map = conf->doc_root; + uri = r->uri; + } + else { + return DECLINED; + } + + if (mode == VHOST_ALIAS_NAME) { + name = ap_get_server_name(r); + } + else if (mode == VHOST_ALIAS_IP) { + name = r->connection->local_ip; + } + else { + return DECLINED; + } + + vhost_alias_interpolate(r, name, map, uri); + + if (cgi) { + /* see is_scriptaliased() in mod_cgi */ + r->handler = "cgi-script"; + ap_table_setn(r->notes, "alias-forced-type", r->handler); + } + + return OK; +} + + +module MODULE_VAR_EXPORT vhost_alias_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + mva_create_server_config, /* server config */ + mva_merge_server_config, /* merge server configs */ + mva_commands, /* command table */ + NULL, /* handlers */ + mva_translate, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* post read-request */ +}; diff --git a/APACHE_1_3_42/src/modules/test/.indent.pro b/APACHE_1_3_42/src/modules/test/.indent.pro new file mode 100644 index 0000000000..a9fbe9f9a1 --- /dev/null +++ b/APACHE_1_3_42/src/modules/test/.indent.pro @@ -0,0 +1,54 @@ +-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1 +-TBUFF +-TFILE +-TTRANS +-TUINT4 +-T_trans +-Tallow_options_t +-Tapache_sfio +-Tarray_header +-Tbool_int +-Tbuf_area +-Tbuff_struct +-Tbuffy +-Tcmd_how +-Tcmd_parms +-Tcommand_rec +-Tcommand_struct +-Tconn_rec +-Tcore_dir_config +-Tcore_server_config +-Tdir_maker_func +-Tevent +-Tglobals_s +-Thandler_func +-Thandler_rec +-Tjoblist_s +-Tlisten_rec +-Tmerger_func +-Tmode_t +-Tmodule +-Tmodule_struct +-Tmutex +-Tn_long +-Tother_child_rec +-Toverrides_t +-Tparent_score +-Tpid_t +-Tpiped_log +-Tpool +-Trequest_rec +-Trequire_line +-Trlim_t +-Tscoreboard +-Tsemaphore +-Tserver_addr_rec +-Tserver_rec +-Tserver_rec_chain +-Tshort_score +-Ttable +-Ttable_entry +-Tthread +-Tu_wide_int +-Tvtime_t +-Twide_int diff --git a/APACHE_1_3_42/src/modules/test/Makefile.tmpl b/APACHE_1_3_42/src/modules/test/Makefile.tmpl new file mode 100644 index 0000000000..d79e5fa47a --- /dev/null +++ b/APACHE_1_3_42/src/modules/test/Makefile.tmpl @@ -0,0 +1,18 @@ + +#Dependencies + +$(OBJS) $(OBJS_PIC): Makefile + +# DO NOT REMOVE +mod_rndchunk.o: mod_rndchunk.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_protocol.h \ + $(INCDIR)/http_config.h $(INCDIR)/http_main.h +mod_test_util_uri.o: mod_test_util_uri.c $(INCDIR)/httpd.h \ + $(INCDIR)/ap_config.h $(INCDIR)/ap_config_auto.h \ + $(OSDIR)/os.h $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \ + $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \ + $(INCDIR)/util_uri.h $(INCDIR)/http_protocol.h \ + $(INCDIR)/http_config.h $(INCDIR)/http_main.h diff --git a/APACHE_1_3_42/src/modules/test/README b/APACHE_1_3_42/src/modules/test/README new file mode 100644 index 0000000000..c61763c1dd --- /dev/null +++ b/APACHE_1_3_42/src/modules/test/README @@ -0,0 +1,3 @@ +This directory is intended to house modules which are used for testing +server functionality. They're unsupported, and not guaranteed to remain +consistant between releases. You're on your own completely with these. diff --git a/APACHE_1_3_42/src/modules/test/mod_rndchunk.c b/APACHE_1_3_42/src/modules/test/mod_rndchunk.c new file mode 100644 index 0000000000..5dae103579 --- /dev/null +++ b/APACHE_1_3_42/src/modules/test/mod_rndchunk.c @@ -0,0 +1,142 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This module is intended to be used for testing chunked encoding. It + * generates a whole whack of output using ap_bputc() and ap_bputs(). It + * also exercises start_chunk() and end_chunk() in buff.c. To use it + * you should use a tool like netcat and the src/test/check_chunked + * tool. Add something like this to your access.conf file: + * + * <Location /rndchunk> + * SetHandler rndchunk + * </Location> + * + * Then fake requests such as: + * + * GET /rndchunk?0,1000000 HTTP/1.1 + * Host: localhost + * + * The first arg is the random seed, the second is the number of + * "things" to do. You should try a few seeds. + * + * You should also edit main/buff.c and change DEFAULT_BUFSIZE (and + * CHUNK_HEADER_SIZE). Small values are particularly useful for + * finding bugs. Try a few different values. + * + * -djg + */ + +#include "httpd.h" +#include "http_protocol.h" +#include "http_config.h" +#include "http_main.h" + +#define MAX_SEGMENT 32 +#define ONE_WEIGHT (256-32) + +static int send_rndchunk(request_rec *r) +{ + const char *args; + char *endptr; + unsigned int seed; + unsigned int count; + int i; + char buf[MAX_SEGMENT + 1]; + unsigned int len; + + r->allowed |= (1 << M_GET); + if (r->method_number != M_GET) + return DECLINED; + + r->content_type = "text/html"; + ap_send_http_header(r); +#ifdef CHARSET_EBCDIC + /* Server-generated response, converted */ + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1); +#endif + if(r->header_only) { + return 0; + } + ap_hard_timeout("send_rndchunk", r); + + if (!r->chunked) { + ap_rputs("Not chunked!", r); + ap_kill_timeout(r); + return 0; + } + + args = r->args; + if (!args) { +error: + ap_rputs("Must include args! ... of the form <code>?seed,count</code>", r); + ap_kill_timeout(r); + return 0; + } + seed = ap_strtol(args, &endptr, 0); + if (!endptr || *endptr != ',') { + goto error; + } + ++endptr; + count = ap_strtol(endptr, &endptr, 0); + + srandom(seed); + for (i = 0; i < count; ++i) { + len = random() % (MAX_SEGMENT + ONE_WEIGHT); + if (len >= MAX_SEGMENT) { + ap_rputc((i & 1) ? '0' : '1', r); + } + else if (len == 0) { + /* not a really nice thing to do, but we need to test + * beginning/ending chunks as well + */ + ap_bsetflag(r->connection->client, B_CHUNK, 0); + ap_bsetflag(r->connection->client, B_CHUNK, 1); + } + else { + memset(buf, '2' + len, len); + buf[len] = 0; + ap_rputs(buf, r); + } + } + ap_kill_timeout(r); + return 0; +} + +static const handler_rec rndchunk_handlers[] = +{ + {"rndchunk", send_rndchunk}, + {NULL} +}; + +module rndchunk_module = { + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + NULL, /* command table */ + rndchunk_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL /* header parser */ +}; diff --git a/APACHE_1_3_42/src/modules/test/mod_test_util_uri.c b/APACHE_1_3_42/src/modules/test/mod_test_util_uri.c new file mode 100644 index 0000000000..5f0f04d6df --- /dev/null +++ b/APACHE_1_3_42/src/modules/test/mod_test_util_uri.c @@ -0,0 +1,317 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This module is intended to test the util_uri routines by parsing a + * bunch of urls and comparing the results with what we expect to + * see. + * + * Usage: + * + * <Location /test-util-uri> + * SetHandler test-util-uri + * </Location> + * + * Then make a request to /test-util-uri. An html table of errors will + * be output... and a total count of errors. + */ + +#include "httpd.h" +#include "http_protocol.h" +#include "http_config.h" +#include "http_main.h" + +typedef struct { + const char *scheme; + const char *user; + const char *password; + const char *hostname; + const char *port_str; + const char *path; + const char *query; + const char *fragment; +} test_uri_t; + +#define T_scheme 0x01 +#define T_user 0x02 +#define T_password 0x04 +#define T_hostname 0x08 +#define T_port_str 0x10 +#define T_path 0x20 +#define T_query 0x40 +#define T_fragment 0x80 +#define T_MAX 0x100 + +/* The idea is that we list here a bunch of url pieces that we want + * stitched together in every way that's valid. + */ +static const test_uri_t uri_tests[] = { + { "http", "userid", "passwd", "hostname.goes.here", "80", "/path/goes/here", "query-here", "frag-here" }, + { "http", "", "passwd", "hostname.goes.here", "80", "/path/goes/here", "query-here", "frag-here" }, + { "http", "userid", "", "hostname.goes.here", "80", "/path/goes/here", "query-here", "frag-here" }, + { "http", "userid", "passwd", "", "80", "/path/goes/here", "query-here", "frag-here" }, + { "http", "userid", "passwd", "hostname.goes.here", "", "/path/goes/here", "query-here", "frag-here" }, +#if 0 + /* An empty path means two different things depending on whether this is a + * relative or an absolute uri... consider <a href="#frag"> versus "GET + * http://hostname HTTP/1.1". So this is why parse_uri_components returns + * a NULL for path when it doesn't find one, instead of returning an empty + * string. + * + * We don't really need to test it explicitly since path has no explicit + * character that indicates its precense, and so we test empty paths all + * the time by varying T_path in the loop. It would just cost us extra + * code to special case the empty path string... + */ + { "http", "userid", "passwd", "hostname.goes.here", "80", "", "query-here", "frag-here" }, +#endif + { "http", "userid", "passwd", "hostname.goes.here", "80", "/path/goes/here", "", "frag-here" }, + { "http", "userid", "passwd", "hostname.goes.here", "80", "/path/goes/here", "query-here", "" }, + { "https", "user@d", "pa:swd", "hostname.goes.here.", "", "/~path/goes/here", "query&query?crud", "frag-here?baby" } + +}; + +static char *my_stpcpy(char *d, const char *s) +{ + while((*d = *s)) { + ++d; + ++s; + } + return d; +} + +/* return the number of failures */ +static unsigned iterate_pieces(request_rec *r, const test_uri_t *pieces, int row) +{ + unsigned u; + pool *sub; + char *input_uri; + char *strp; + uri_components result; + unsigned expect; + int status; + unsigned failures; + + failures = 0; + + input_uri = ap_palloc(r->pool, + strlen(pieces->scheme) + 3 + + strlen(pieces->user) + 1 + + strlen(pieces->password) + 1 + + strlen(pieces->hostname) + 1 + + strlen(pieces->port_str) + 1 + + strlen(pieces->path) + + + strlen(pieces->query) + 1 + + strlen(pieces->fragment) + 1 + + 1); + + for (u = 0; u < T_MAX; ++u) { + strp = input_uri; + expect = 0; + + /* a scheme requires a hostinfo and vice versa */ + /* a hostinfo requires a hostname */ + if (u & (T_scheme|T_user|T_password|T_hostname|T_port_str)) { + expect |= T_scheme; + strp = my_stpcpy(strp, pieces->scheme); + *strp++ = ':'; + *strp++ = '/'; + *strp++ = '/'; + /* can't have password without user */ + if (u & (T_user|T_password)) { + expect |= T_user; + strp = my_stpcpy(strp, pieces->user); + if (u & T_password) { + expect |= T_password; + *strp++ = ':'; + strp = my_stpcpy(strp, pieces->password); + } + *strp++ = '@'; + } + expect |= T_hostname; + strp = my_stpcpy(strp, pieces->hostname); + if (u & T_port_str) { + expect |= T_port_str; + *strp++ = ':'; + strp = my_stpcpy(strp, pieces->port_str); + } + } + if (u & T_path) { + expect |= T_path; + strp = my_stpcpy(strp, pieces->path); + } + if (u & T_query) { + expect |= T_query; + *strp++ = '?'; + strp = my_stpcpy(strp, pieces->query); + } + if (u & T_fragment) { + expect |= T_fragment; + *strp++ = '#'; + strp = my_stpcpy(strp, pieces->fragment); + } + *strp = 0; + + sub = ap_make_sub_pool(r->pool); + status = ap_parse_uri_components(sub, input_uri, &result); + if (status == HTTP_OK) { +#define CHECK(f) \ + if ((expect & T_##f) \ + && (result.f == NULL || strcmp(result.f, pieces->f))) { \ + status = HTTP_INTERNAL_SERVER_ERROR; \ + } \ + else if (!(expect & T_##f) && result.f != NULL) { \ + status = HTTP_INTERNAL_SERVER_ERROR; \ + } + CHECK(scheme) + CHECK(user) + CHECK(password) + CHECK(hostname) + CHECK(port_str) + CHECK(path) + CHECK(query) + CHECK(fragment) +#undef CHECK + } + if (status != HTTP_OK) { + ap_rprintf(r, "<tr><td>%d</td><td>0x%02x</td><td>0x%02x</td><td>%d</td><td>\"%s\"</td>", row, u, expect, status, input_uri); +#define DUMP(f) \ + if (result.f) { \ + ap_rvputs(r, "<td>\"", result.f, "\"<br>", NULL); \ + } \ + else { \ + ap_rputs("<td>NULL<br>", r); \ + } \ + if (expect & T_##f) { \ + ap_rvputs(r, "\"", pieces->f, "\"</td>", NULL); \ + } \ + else { \ + ap_rputs("NULL</td>", r); \ + } + DUMP(scheme); + DUMP(user); + DUMP(password); + DUMP(hostname); + DUMP(port_str); + DUMP(path); + DUMP(query); + DUMP(fragment); +#undef DUMP + ap_rputs("</tr>\n", r); + ++failures; + } + ap_destroy_pool(sub); + } + return failures; +} + +static int test_util_uri(request_rec *r) +{ + unsigned total_failures; + int i; + + r->allowed |= (1 << M_GET); + if (r->method_number != M_GET) + return DECLINED; + + r->content_type = "text/html"; + ap_send_http_header(r); +#ifdef CHARSET_EBCDIC + /* Server-generated response, converted */ + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1); +#endif + if(r->header_only) { + return 0; + } + ap_hard_timeout("test_util_uri", r); + + ap_rputs( +DOCTYPE_HTML_2_0 " +<html><body> +<p>Key: +<dl> +<dt>row +<dd>entry number in the uri_tests array +<dt>u +<dd>fields under test +<dt>expected +<dd>fields expected in the result +<dt>status +<dd>response from parse_uri_components, or 500 if unexpected results +<dt>input uri +<dd>the uri given to parse_uri_components +</dl> +<p>The remaining fields are the pieces returned from parse_uri_components, and +the values we expected for each piece (resp.). +<p>Only failures are displayed. +<p> +<table><tr><th>row</th><th>u</th><th>expect</th><th>status</th><th>input uri</th>", r); +#define HEADER(f) ap_rprintf(r, "<th>" #f "<br>0x%02x</th>", T_##f) + HEADER(scheme); + HEADER(user); + HEADER(password); + HEADER(hostname); + HEADER(port_str); + HEADER(path); + HEADER(query); + HEADER(fragment); +#undef HEADER + + if (r->args) { + i = atoi(r->args); + total_failures = iterate_pieces(r, &uri_tests[i], i); + } + else { + total_failures = 0; + for (i = 0; i < sizeof(uri_tests) / sizeof(uri_tests[0]); ++i) { + total_failures += iterate_pieces(r, &uri_tests[i], i); + if (total_failures > 256) { + ap_rprintf(r, "</table>\n<b>Stopped early to save your browser " + "from certain death!</b>\nTOTAL FAILURES = %u\n", + total_failures); + return OK; + } + } + } + ap_rprintf(r, "</table>\nTOTAL FAILURES = %u\n", total_failures); + + return OK; +} + +static const handler_rec test_util_uri_handlers[] = +{ + {"test-util-uri", test_util_uri}, + {NULL} +}; + +module test_util_uri_module = { + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + NULL, /* command table */ + test_util_uri_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL, /* logger */ + NULL /* header parser */ +}; |