diff options
Diffstat (limited to '')
86 files changed, 54470 insertions, 0 deletions
diff --git a/server/.indent.pro b/server/.indent.pro new file mode 100644 index 0000000..a9fbe9f --- /dev/null +++ b/server/.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/server/Makefile.in b/server/Makefile.in new file mode 100644 index 0000000..1fa3344 --- /dev/null +++ b/server/Makefile.in @@ -0,0 +1,105 @@ + +CLEAN_TARGETS = gen_test_char test_char.h \ + ApacheCoreOS2.def httpd.exp export_files \ + exports.c export_vars.h + +SUBDIRS = mpm + +LTLIBRARY_NAME = libmain.la +LTLIBRARY_SOURCES = \ + config.c log.c main.c vhost.c util.c util_fcgi.c \ + util_script.c util_md5.c util_cfgtree.c util_ebcdic.c util_time.c \ + connection.c listen.c util_mutex.c \ + mpm_common.c mpm_unix.c mpm_fdqueue.c \ + util_charset.c util_cookies.c util_debug.c util_xml.c \ + util_filter.c util_pcre.c util_regex.c exports.c \ + scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \ + eoc_bucket.c eor_bucket.c core_filters.c \ + util_expr_parse.c util_expr_scan.c util_expr_eval.c + +LTLIBRARY_DEPENDENCIES = test_char.h + +TARGETS = delete-exports $(LTLIBRARY_NAME) $(CORE_IMPLIB_FILE) export_vars.h httpd.exp + +include $(top_builddir)/build/rules.mk +include $(top_srcdir)/build/library.mk + +gen_test_char_OBJECTS = gen_test_char.lo +gen_test_char: $(gen_test_char_OBJECTS) + $(LINK) $(EXTRA_LDFLAGS) $(gen_test_char_OBJECTS) $(EXTRA_LIBS) + +test_char.h: gen_test_char + ./gen_test_char > test_char.h + +util.lo: test_char.h + +EXPORT_DIRS = $(top_srcdir)/include $(top_srcdir)/os/$(OS_DIR) +EXPORT_DIRS_APR = $(APR_INCLUDEDIR) $(APU_INCLUDEDIR) + +# If export_files is a dependency here, but we remove it during this stage, +# when exports.c is generated, make will not detect that export_files is no +# longer here and deadlock. So, export_files can't be a dependency of +# delete-exports. +delete-exports: + @if test -f exports.c; then \ + if test -f export_files; then \ + files=`cat export_files`; \ + headers="`find $$files -newer exports.c`"; \ + if test -n "$$headers"; then \ + echo Found newer headers. Will rebuild exports.c.; \ + echo rm -f exports.c export_files; \ + rm -f exports.c export_files; \ + fi; \ + else \ + rm -f exports.c; \ + fi; \ + fi + +export_files: + ( for dir in $(EXPORT_DIRS); do \ + ls $$dir/*.h ; \ + done; \ + echo "$(top_srcdir)/server/mpm_fdqueue.h"; \ + for dir in $(EXPORT_DIRS_APR); do \ + ls $$dir/ap[ru].h $$dir/ap[ru]_*.h 2>/dev/null; \ + done; \ + ) | sed -e s,//,/,g | sort -u > $@ + +exports.c: export_files + $(AWK) -f $(top_srcdir)/build/make_exports.awk `cat $?` > $@ + +export_vars.h: export_files + $(AWK) -f $(top_srcdir)/build/make_var_export.awk `cat $?` > $@ + +# Rule to make def file for OS/2 core dll +ApacheCoreOS2.def: exports.c export_vars.h $(top_srcdir)/os/$(OS_DIR)/core_header.def + cat $(top_srcdir)/os/$(OS_DIR)/core_header.def > $@ + $(CPP) $< $(ALL_CPPFLAGS) $(ALL_INCLUDES) | grep "ap_hack_" | sed -e 's/^.*[)]\(.*\);$$/ "\1"/' >> $@ + $(CPP) $(ALL_CPPFLAGS) $(ALL_INCLUDES) export_vars.h | grep "^[a-z]" | sed -e 's/^\(.*\)$$/ "\1"/' >> $@ + +# Rule to make exp file for AIX DSOs +httpd.exp: exports.c export_vars.h + @echo "#! ." > $@ + @echo "* This file was AUTOGENERATED at build time." >> $@ + @echo "* Please do not edit by hand." >> $@ + $(CPP) $(ALL_CPPFLAGS) $(ALL_INCLUDES) exports.c | grep "ap_hack_" | grep -v apr_ | sed -e 's/^.*[)]\(.*\);$$/\1/' >> $@ + $(CPP) $(ALL_CPPFLAGS) $(ALL_INCLUDES) export_vars.h | grep -v apr_ | sed -e 's/^\#[^!]*//' | sed -e '/^$$/d' >> $@ + + +# developer stuff +# (we really don't expect end users to use these targets!) +# +util_expr_scan.c util_expr_parse.c util_expr_parse.h: util_expr_scan.l util_expr_parse.y + bison -pap_expr_yy --defines=$(builddir)/util_expr_parse.h \ + -o $(builddir)/util_expr_parse.c $(srcdir)/util_expr_parse.y + flex -Pap_expr_yy -o $(builddir)/util_expr_scan.c $(srcdir)/util_expr_scan.l + set -e ; \ + for f in util_expr_scan.c util_expr_parse.c util_expr_parse.h ; do \ + sed -e "s|\"$(builddir)/|\"|g" < $(builddir)/$$f > \ + $(builddir)/$$f.$$$$ && \ + mv $(builddir)/$$f.$$$$ $(builddir)/$$f ; \ + done + # work around flex bug + # http://sourceforge.net/tracker/?func=detail&aid=3029024&group_id=97492&atid=618177 + perl -0777 -p -i -e 's,\n(void|int) ap_expr_yy[gs]et_column[^\n]+\)\n.*?\n\},,gs' \ + $(builddir)/util_expr_scan.c diff --git a/server/NWGNUmakefile b/server/NWGNUmakefile new file mode 100644 index 0000000..7f96e81 --- /dev/null +++ b/server/NWGNUmakefile @@ -0,0 +1,261 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + ../build \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)/build/NWGNUhead.inc + +# +# build this level's files + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(NWOS) \ + $(APR)/include \ + $(AP_WORK)/include \ + $(APRUTIL)/include \ + $(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 = genchars + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Generate Test Characters + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = genchars + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\NWGNUNetWare.rul +# +NLM_VERSION = 1,0,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 = + +# +# 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 '-check' directive +# +NLM_CHECK_SYM = + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = PSEUDOPREEMPTION + +# +# If this is specified it will be linked in with the XDCData option in the def +# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled +# by setting APACHE_UNIPROC in the environment +# +XDCDATA = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ +$(OBJDIR)/genchars.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)/gen_test_char.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 = \ + $(PRELUDE) \ + $(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 = \ + Libc \ + $(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 = \ + @libc.imp \ + $(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 = \ + $(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. +# +install :: nlms FORCE + +# +# Any specialized rules here +# + +# Make sure that the build doesn't attempt to regenerate the shipping files. +# This requires a 'touch' utility. Can be downloaded from 'coreutils' at +# http://sourceforge.net/projects/gnuwin32/ +util_expr_parse.h : util_expr_parse.y + touch util_expr_parse.h +util_expr_parse.c : util_expr_parse.y + touch util_expr_parse.c +util_expr_scan.c : util_expr_scan.l + touch util_expr_scan.c + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(APBUILD)/NWGNUtail.inc + diff --git a/server/buildmark.c b/server/buildmark.c new file mode 100644 index 0000000..a9cd684 --- /dev/null +++ b/server/buildmark.c @@ -0,0 +1,29 @@ +/* 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 "ap_config.h" +#include "httpd.h" + +#if defined(__DATE__) && defined(__TIME__) +static const char server_built[] = __DATE__ " " __TIME__; +#else +static const char server_built[] = "unknown"; +#endif + +AP_DECLARE(const char *) ap_get_server_built() +{ + return server_built; +} diff --git a/server/config.c b/server/config.c new file mode 100644 index 0000000..f815b22 --- /dev/null +++ b/server/config.c @@ -0,0 +1,2738 @@ +/* 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_config.c: once was auxiliary functions for reading httpd's config + * file and converting filenames into a namespace + * + * Rob McCool + * + * Wall-to-wall rewrite for Apache... commands which are part of the + * server core can now be found next door in "http_core.c". Now contains + * general command loop, and functions which do bookkeeping for the new + * Apache config stuff (modules and configuration vectors). + * + * rst + * + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_file_io.h" +#include "apr_fnmatch.h" + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_protocol.h" +#include "http_core.h" +#include "http_log.h" /* for errors in parse_htaccess */ +#include "http_request.h" /* for default_handler (see invoke_handler) */ +#include "http_main.h" +#include "http_vhost.h" +#include "util_cfgtree.h" +#include "util_varbuf.h" +#include "mpm_common.h" + +#define APLOG_UNSET (APLOG_NO_MODULE - 1) +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +AP_DECLARE_DATA const char *ap_server_argv0 = NULL; +AP_DECLARE_DATA const char *ap_server_root = NULL; +AP_DECLARE_DATA const char *ap_runtime_dir = NULL; +AP_DECLARE_DATA server_rec *ap_server_conf = NULL; +AP_DECLARE_DATA apr_pool_t *ap_pglobal = NULL; + +AP_DECLARE_DATA apr_array_header_t *ap_server_pre_read_config = NULL; +AP_DECLARE_DATA apr_array_header_t *ap_server_post_read_config = NULL; +AP_DECLARE_DATA apr_array_header_t *ap_server_config_defines = NULL; + +AP_DECLARE_DATA ap_directive_t *ap_conftree = NULL; + +APR_HOOK_STRUCT( + APR_HOOK_LINK(header_parser) + APR_HOOK_LINK(pre_config) + APR_HOOK_LINK(check_config) + APR_HOOK_LINK(post_config) + APR_HOOK_LINK(open_logs) + APR_HOOK_LINK(child_init) + APR_HOOK_LINK(handler) + APR_HOOK_LINK(quick_handler) + APR_HOOK_LINK(optional_fn_retrieve) + APR_HOOK_LINK(test_config) + APR_HOOK_LINK(open_htaccess) +) + +AP_IMPLEMENT_HOOK_RUN_ALL(int, header_parser, + (request_rec *r), (r), OK, DECLINED) + +AP_IMPLEMENT_HOOK_RUN_ALL(int, pre_config, + (apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp), + (pconf, plog, ptemp), OK, DECLINED) + +AP_IMPLEMENT_HOOK_RUN_ALL(int, check_config, + (apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s), + (pconf, plog, ptemp, s), OK, DECLINED) + +AP_IMPLEMENT_HOOK_VOID(test_config, + (apr_pool_t *pconf, server_rec *s), + (pconf, s)) + +AP_IMPLEMENT_HOOK_RUN_ALL(int, post_config, + (apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s), + (pconf, plog, ptemp, s), OK, DECLINED) + +/* During the course of debugging I expanded this macro out, so + * rather than remove all the useful information there is in the + * following lines, I'm going to leave it here in case anyone + * else finds it useful. + * + * Ben has looked at it and thinks it correct :) + * +AP_DECLARE(int) ap_hook_post_config(ap_HOOK_post_config_t *pf, + const char * const *aszPre, + const char * const *aszSucc, + int nOrder) +{ + ap_LINK_post_config_t *pHook; + + if (!_hooks.link_post_config) { + _hooks.link_post_config = apr_array_make(apr_hook_global_pool, 1, + sizeof(ap_LINK_post_config_t)); + apr_hook_sort_register("post_config", &_hooks.link_post_config); + } + + pHook = apr_array_push(_hooks.link_post_config); + pHook->pFunc = pf; + pHook->aszPredecessors = aszPre; + pHook->aszSuccessors = aszSucc; + pHook->nOrder = nOrder; + pHook->szName = apr_hook_debug_current; + + if (apr_hook_debug_enabled) + apr_hook_debug_show("post_config", aszPre, aszSucc); +} + +AP_DECLARE(apr_array_header_t *) ap_hook_get_post_config(void) +{ + return _hooks.link_post_config; +} + +AP_DECLARE(int) ap_run_post_config(apr_pool_t *pconf, + apr_pool_t *plog, + apr_pool_t *ptemp, + server_rec *s) +{ + ap_LINK_post_config_t *pHook; + int n; + + if (!_hooks.link_post_config) + return; + + pHook = (ap_LINK_post_config_t *)_hooks.link_post_config->elts; + for (n = 0; n < _hooks.link_post_config->nelts; ++n) + pHook[n].pFunc (pconf, plog, ptemp, s); +} + */ + +AP_IMPLEMENT_HOOK_RUN_ALL(int, open_logs, + (apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s), + (pconf, plog, ptemp, s), OK, DECLINED) + +AP_IMPLEMENT_HOOK_VOID(child_init, + (apr_pool_t *pchild, server_rec *s), + (pchild, s)) + +AP_IMPLEMENT_HOOK_RUN_FIRST(int, handler, (request_rec *r), + (r), DECLINED) + +AP_IMPLEMENT_HOOK_RUN_FIRST(int, quick_handler, (request_rec *r, int lookup), + (r, lookup), DECLINED) + +AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, open_htaccess, + (request_rec *r, const char *dir_name, const char *access_name, + ap_configfile_t **conffile, const char **full_name), + (r, dir_name, access_name, conffile, full_name), + AP_DECLINED) + +/* hooks with no args are implemented last, after disabling APR hook probes */ +#if defined(APR_HOOK_PROBES_ENABLED) +#undef APR_HOOK_PROBES_ENABLED +#undef APR_HOOK_PROBE_ENTRY +#define APR_HOOK_PROBE_ENTRY(ud,ns,name,args) +#undef APR_HOOK_PROBE_RETURN +#define APR_HOOK_PROBE_RETURN(ud,ns,name,rv,args) +#undef APR_HOOK_PROBE_INVOKE +#define APR_HOOK_PROBE_INVOKE(ud,ns,name,src,args) +#undef APR_HOOK_PROBE_COMPLETE +#define APR_HOOK_PROBE_COMPLETE(ud,ns,name,src,rv,args) +#undef APR_HOOK_INT_DCL_UD +#define APR_HOOK_INT_DCL_UD +#endif +AP_IMPLEMENT_HOOK_VOID(optional_fn_retrieve, (void), ()) + +/**************************************************************** + * + * We begin with the functions which deal with the linked list + * of modules which control just about all of the server operation. + */ + +/* total_modules is the number of modules that have been linked + * into the server. + */ +static int total_modules = 0; + +/* dynamic_modules is the number of modules that have been added + * after the pre-loaded ones have been set up. It shouldn't be larger + * than DYNAMIC_MODULE_LIMIT. + */ +static int dynamic_modules = 0; + +/* The maximum possible value for total_modules, i.e. number of static + * modules plus DYNAMIC_MODULE_LIMIT. + */ +static int max_modules = 0; + +/* The number of elements we need to alloc for config vectors. Before loading + * of dynamic modules, we must be liberal and set this to max_modules. After + * loading of dynamic modules, we can trim it down to total_modules. On + * restart, reset to max_modules. + */ +static int conf_vector_length = 0; + +static int reserved_module_slots = 0; + +AP_DECLARE_DATA module *ap_top_module = NULL; +AP_DECLARE_DATA module **ap_loaded_modules=NULL; + +static apr_hash_t *ap_config_hash = NULL; + +/* a list of the module symbol names with the trailing "_module"removed */ +static char **ap_module_short_names = NULL; + +typedef int (*handler_func)(request_rec *); +typedef void *(*dir_maker_func)(apr_pool_t *, char *); +typedef void *(*merger_func)(apr_pool_t *, void *, void *); + +/* A list of the merge_dir_config functions of all loaded modules, sorted + * by module_index. + * Using this list in ap_merge_per_dir_configs() is faster than following + * the module->next linked list because of better memory locality (resulting + * in better cache usage). + */ +static merger_func *merger_func_cache; + +/* maximum nesting level for config directories */ +#ifndef AP_MAX_INCLUDE_DIR_DEPTH +#define AP_MAX_INCLUDE_DIR_DEPTH (128) +#endif + +/* Dealing with config vectors. These are associated with per-directory, + * per-server, and per-request configuration, and have a void* pointer for + * each modules. The nature of the structure pointed to is private to the + * module in question... the core doesn't (and can't) know. However, there + * are defined interfaces which allow it to create instances of its private + * per-directory and per-server structures, and to merge the per-directory + * structures of a directory and its subdirectory (producing a new one in + * which the defaults applying to the base directory have been properly + * overridden). + */ + +static ap_conf_vector_t *create_empty_config(apr_pool_t *p) +{ + void *conf_vector = apr_pcalloc(p, sizeof(void *) * conf_vector_length); + return conf_vector; +} + +static ap_conf_vector_t *create_default_per_dir_config(apr_pool_t *p) +{ + void **conf_vector = apr_pcalloc(p, sizeof(void *) * conf_vector_length); + module *modp; + + for (modp = ap_top_module; modp; modp = modp->next) { + dir_maker_func df = modp->create_dir_config; + + if (df) + conf_vector[modp->module_index] = (*df)(p, NULL); + } + + return (ap_conf_vector_t *)conf_vector; +} + +AP_CORE_DECLARE(ap_conf_vector_t *) ap_merge_per_dir_configs(apr_pool_t *p, + ap_conf_vector_t *base, + ap_conf_vector_t *new_conf) +{ + void **conf_vector = apr_palloc(p, sizeof(void *) * conf_vector_length); + void **base_vector = (void **)base; + void **new_vector = (void **)new_conf; + int i; + + for (i = 0; i < total_modules; i++) { + if (!new_vector[i]) { + conf_vector[i] = base_vector[i]; + } + else { + const merger_func df = merger_func_cache[i]; + if (df && base_vector[i]) { + conf_vector[i] = (*df)(p, base_vector[i], new_vector[i]); + } + else + conf_vector[i] = new_vector[i]; + } + } + + return (ap_conf_vector_t *)conf_vector; +} + +static ap_conf_vector_t *create_server_config(apr_pool_t *p, server_rec *s) +{ + void **conf_vector = apr_pcalloc(p, sizeof(void *) * conf_vector_length); + module *modp; + + for (modp = ap_top_module; modp; modp = modp->next) { + if (modp->create_server_config) + conf_vector[modp->module_index] = (*modp->create_server_config)(p, s); + } + + return (ap_conf_vector_t *)conf_vector; +} + +static void merge_server_configs(apr_pool_t *p, ap_conf_vector_t *base, + server_rec *virt) +{ + /* Can reuse the 'virt' vector for the spine of it, since we don't + * have to deal with the moral equivalent of .htaccess files here... + */ + + void **base_vector = (void **)base; + void **virt_vector = (void **)virt->module_config; + module *modp; + + for (modp = ap_top_module; modp; modp = modp->next) { + merger_func df = modp->merge_server_config; + int i = modp->module_index; + + if (!virt_vector[i]) { + if (df && modp->create_server_config + && (ap_get_module_flags(modp) & + AP_MODULE_FLAG_ALWAYS_MERGE)) { + virt_vector[i] = (*modp->create_server_config)(p, virt); + } + else { + virt_vector[i] = base_vector[i]; + df = NULL; + } + } + if (df) { + virt_vector[i] = (*df)(p, base_vector[i], virt_vector[i]); + } + } +} + +AP_CORE_DECLARE(ap_conf_vector_t *) ap_create_request_config(apr_pool_t *p) +{ + return create_empty_config(p); +} + +AP_CORE_DECLARE(ap_conf_vector_t *) ap_create_conn_config(apr_pool_t *p) +{ + return create_empty_config(p); +} + +AP_CORE_DECLARE(ap_conf_vector_t *) ap_create_per_dir_config(apr_pool_t *p) +{ + return create_empty_config(p); +} + +/* Invoke the filter_init_func for all filters with FILTERS where f->r + * matches R. Restricting to a matching R avoids re-running init + * functions for filters configured for r->main where r is a + * subrequest. */ +static int invoke_filter_init(request_rec *r, ap_filter_t *filters) +{ + while (filters) { + if (filters->frec->filter_init_func && filters->r == r) { + int result = filters->frec->filter_init_func(filters); + if (result != OK) { + return result; + } + } + filters = filters->next; + } + return OK; +} + +AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r) +{ + const char *handler; + const char *p; + int result; + const char *old_handler = r->handler; + const char *ignore; + + /* + * The new insert_filter stage makes the most sense here. We only use + * it when we are going to run the request, so we must insert filters + * if any are available. Since the goal of this phase is to allow all + * modules to insert a filter if they want to, this filter returns + * void. I just can't see any way that this filter can reasonably + * fail, either your modules inserts something or it doesn't. rbb + */ + ap_run_insert_filter(r); + + /* Before continuing, allow each filter that is in the two chains to + * run their init function to let them do any magic before we could + * start generating data. + */ + result = invoke_filter_init(r, r->input_filters); + if (result != OK) { + return result; + } + result = invoke_filter_init(r, r->output_filters); + if (result != OK) { + return result; + } + + if (!r->handler) { + if (r->content_type) { + handler = r->content_type; + if ((p=ap_strchr_c(handler, ';')) != NULL) { + char *new_handler = (char *)apr_pmemdup(r->pool, handler, + p - handler + 1); + char *p2 = new_handler + (p - handler); + handler = new_handler; + + /* exclude media type arguments */ + while (p2 > handler && p2[-1] == ' ') + --p2; /* strip trailing spaces */ + + *p2='\0'; + } + } + else { + handler = AP_DEFAULT_HANDLER_NAME; + } + + r->handler = handler; + } + + result = ap_run_handler(r); + + r->handler = old_handler; + + if (result == DECLINED && r->handler && r->filename) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(00523) + "handler \"%s\" not found for: %s", r->handler, r->filename); + } + if ((result != OK) && (result != DONE) && (result != DECLINED) && (result != SUSPENDED) + && (result != AP_FILTER_ERROR) /* ap_die() knows about this specifically */ + && !ap_is_HTTP_VALID_RESPONSE(result)) { + /* If a module is deliberately returning something else + * (request_rec in non-HTTP or proprietary extension?) + * let it set a note to allow it explicitly. + * Otherwise, a return code that is neither reserved nor HTTP + * is a bug, as in PR#31759. + */ + ignore = apr_table_get(r->notes, "HTTP_IGNORE_RANGE"); + if (!ignore) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00524) + "Handler for %s returned invalid result code %d", + r->handler, result); + result = HTTP_INTERNAL_SERVER_ERROR; + } + } + + return result == DECLINED ? HTTP_INTERNAL_SERVER_ERROR : result; +} + +AP_DECLARE(int) ap_method_is_limited(cmd_parms *cmd, const char *method) +{ + int methnum; + + methnum = ap_method_number_of(method); + + /* + * A method number either hardcoded into apache or + * added by a module and registered. + */ + if (methnum != M_INVALID) { + return (cmd->limited & (AP_METHOD_BIT << methnum)) ? 1 : 0; + } + + return 0; /* not found */ +} + +AP_DECLARE(void) ap_register_hooks(module *m, apr_pool_t *p) +{ + if (m->register_hooks) { + if (getenv("SHOW_HOOKS")) { + printf("Registering hooks for %s\n", m->name); + apr_hook_debug_enabled = 1; + } + + apr_hook_debug_current = m->name; + m->register_hooks(p); + } +} + +static void ap_add_module_commands(module *m, apr_pool_t *p); + +typedef struct ap_mod_list_struct ap_mod_list; +struct ap_mod_list_struct { + struct ap_mod_list_struct *next; + module *m; + const command_rec *cmd; +}; + +static void rebuild_conf_hash(apr_pool_t *p, int add_prelinked) +{ + module **m; + + ap_config_hash = apr_hash_make(p); + + apr_pool_cleanup_register(p, &ap_config_hash, ap_pool_cleanup_set_null, + apr_pool_cleanup_null); + if (add_prelinked) { + for (m = ap_prelinked_modules; *m != NULL; m++) { + ap_add_module_commands(*m, p); + } + } +} + +static void ap_add_module_commands(module *m, apr_pool_t *p) +{ + apr_pool_t *tpool; + ap_mod_list *mln; + const command_rec *cmd; + char *dir; + + cmd = m->cmds; + + if (ap_config_hash == NULL) { + rebuild_conf_hash(p, 0); + } + + tpool = apr_hash_pool_get(ap_config_hash); + + while (cmd && cmd->name) { + mln = apr_palloc(tpool, sizeof(ap_mod_list)); + mln->cmd = cmd; + mln->m = m; + dir = apr_pstrdup(tpool, cmd->name); + + ap_str_tolower(dir); + + mln->next = apr_hash_get(ap_config_hash, dir, APR_HASH_KEY_STRING); + apr_hash_set(ap_config_hash, dir, APR_HASH_KEY_STRING, mln); + ++cmd; + } +} + + +/* One-time setup for precompiled modules --- NOT to be done on restart */ + +AP_DECLARE(const char *) ap_add_module(module *m, apr_pool_t *p, + const char *sym_name) +{ + ap_module_symbol_t *sym = ap_prelinked_module_symbols; + + /* This could be called from a LoadModule httpd.conf command, + * after the file has been linked and the module structure within it + * teased out... + */ + + if (m->version != MODULE_MAGIC_NUMBER_MAJOR) { + return apr_psprintf(p, "Module \"%s\" is not compatible with this " + "version of Apache (found %d, need %d). Please " + "contact the vendor for the correct version.", + m->name, m->version, MODULE_MAGIC_NUMBER_MAJOR); + } + + if (m->module_index == -1) { + if (dynamic_modules >= DYNAMIC_MODULE_LIMIT) { + return apr_psprintf(p, "Module \"%s\" could not be loaded, " + "because the dynamic module limit was " + "reached. Please increase " + "DYNAMIC_MODULE_LIMIT and recompile.", m->name); + } + /* + * If this fails some module forgot to call ap_reserve_module_slots*. + */ + ap_assert(total_modules < conf_vector_length); + + m->module_index = total_modules++; + dynamic_modules++; + + } + else if (!sym_name) { + while (sym->modp != NULL) { + if (sym->modp == m) { + sym_name = sym->name; + break; + } + sym++; + } + } + + if (m->next == NULL) { + m->next = ap_top_module; + ap_top_module = m; + } + + if (sym_name) { + int len = strlen(sym_name); + int slen = strlen("_module"); + if (len > slen && !strcmp(sym_name + len - slen, "_module")) { + len -= slen; + } + + ap_module_short_names[m->module_index] = ap_malloc(len + 1); + memcpy(ap_module_short_names[m->module_index], sym_name, len); + ap_module_short_names[m->module_index][len] = '\0'; + merger_func_cache[m->module_index] = m->merge_dir_config; + } + + + /* Some C compilers put a complete path into __FILE__, but we want + * only the filename (e.g. mod_includes.c). So check for path + * components (Unix and DOS), and remove them. + */ + + if (ap_strrchr_c(m->name, '/')) + m->name = 1 + ap_strrchr_c(m->name, '/'); + + if (ap_strrchr_c(m->name, '\\')) + m->name = 1 + ap_strrchr_c(m->name, '\\'); + +#ifdef _OSD_POSIX + /* __FILE__ = + * "*POSIX(/home/martin/apache/src/modules/standard/mod_info.c)" + */ + + /* We cannot fix the string in-place, because it's const */ + if (m->name[strlen(m->name)-1] == ')') { + char *tmp = ap_malloc(strlen(m->name)); /* FIXME: memory leak, albeit a small one */ + memcpy(tmp, m->name, strlen(m->name)-1); + tmp[strlen(m->name)-1] = '\0'; + m->name = tmp; + } +#endif /*_OSD_POSIX*/ + + ap_add_module_commands(m, p); + /* FIXME: is this the right place to call this? + * It doesn't appear to be + */ + ap_register_hooks(m, p); + + return NULL; +} + +/* + * remove_module undoes what add_module did. There are some caveats: + * when the module is removed, its slot is lost so all the current + * per-dir and per-server configurations are invalid. So we should + * only ever call this function when you are invalidating almost + * all our current data. I.e. when doing a restart. + */ + +AP_DECLARE(void) ap_remove_module(module *m) +{ + module *modp; + + modp = ap_top_module; + if (modp == m) { + /* We are the top module, special case */ + ap_top_module = modp->next; + m->next = NULL; + } + else { + /* Not the top module, find use. When found modp will + * point to the module _before_ us in the list + */ + + while (modp && modp->next != m) { + modp = modp->next; + } + + if (!modp) { + /* Uh-oh, this module doesn't exist */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00525) + "Cannot remove module %s: not found in module list", + m->name); + return; + } + + /* Eliminate us from the module list */ + modp->next = modp->next->next; + } + + free(ap_module_short_names[m->module_index]); + ap_module_short_names[m->module_index] = NULL; + merger_func_cache[m->module_index] = NULL; + + m->module_index = -1; /* simulate being unloaded, should + * be unnecessary */ + dynamic_modules--; + total_modules--; +} + +AP_DECLARE(const char *) ap_add_loaded_module(module *mod, apr_pool_t *p, + const char *short_name) +{ + module **m; + const char *error; + + /* + * Add module pointer to top of chained module list + */ + error = ap_add_module(mod, p, short_name); + if (error) { + return error; + } + + /* + * And module pointer to list of loaded modules + * + * Notes: 1. ap_add_module() would already complain if no more space + * exists for adding a dynamically loaded module + * 2. ap_add_module() accepts double inclusion, so we have + * to accept this, too. + */ + for (m = ap_loaded_modules; *m != NULL; m++) + ; + *m++ = mod; + *m = NULL; + + return NULL; +} + +AP_DECLARE(void) ap_remove_loaded_module(module *mod) +{ + module **m; + module **m2; + int done; + + /* + * Remove module pointer from chained module list + */ + ap_remove_module(mod); + + /* + * Remove module pointer from list of loaded modules + * + * Note: 1. We cannot determine if the module was successfully + * removed by ap_remove_module(). + * 2. We have not to complain explicitly when the module + * is not found because ap_remove_module() did it + * for us already. + */ + for (m = m2 = ap_loaded_modules, done = 0; *m2 != NULL; m2++) { + if (*m2 == mod && done == 0) + done = 1; + else + *m++ = *m2; + } + + *m = NULL; +} + +AP_DECLARE(const char *) ap_setup_prelinked_modules(process_rec *process) +{ + module **m; + module **m2; + const char *error; + + apr_hook_global_pool=process->pconf; + + rebuild_conf_hash(process->pconf, 0); + + /* + * Initialise total_modules variable and module indices + */ + total_modules = 0; + for (m = ap_preloaded_modules; *m != NULL; m++) + (*m)->module_index = total_modules++; + + max_modules = total_modules + DYNAMIC_MODULE_LIMIT + 1; + conf_vector_length = max_modules; + + /* + * Initialise list of loaded modules and short names + */ + ap_loaded_modules = (module **)apr_palloc(process->pool, + sizeof(module *) * conf_vector_length); + if (!ap_module_short_names) + ap_module_short_names = ap_calloc(sizeof(char *), conf_vector_length); + + if (!merger_func_cache) + merger_func_cache = ap_calloc(sizeof(merger_func), conf_vector_length); + + if (ap_loaded_modules == NULL || ap_module_short_names == NULL + || merger_func_cache == NULL) + return "Ouch! Out of memory in ap_setup_prelinked_modules()!"; + + for (m = ap_preloaded_modules, m2 = ap_loaded_modules; *m != NULL; ) + *m2++ = *m++; + + *m2 = NULL; + + /* + * Initialize chain of linked (=activate) modules + */ + for (m = ap_prelinked_modules; *m != NULL; m++) { + error = ap_add_module(*m, process->pconf, NULL); + if (error) { + return error; + } + } + + apr_hook_sort_all(); + + return NULL; +} + +AP_DECLARE(const char *) ap_find_module_name(module *m) +{ + return m->name; +} + +AP_DECLARE(const char *) ap_find_module_short_name(int module_index) +{ + if (module_index < 0 || module_index >= conf_vector_length) + return NULL; + return ap_module_short_names[module_index]; +} + +AP_DECLARE(module *) ap_find_linked_module(const char *name) +{ + module *modp; + + for (modp = ap_top_module; modp; modp = modp->next) { + if (strcmp(modp->name, name) == 0) + return modp; + } + + return NULL; +} + +/***************************************************************** + * + * Resource, access, and .htaccess config files now parsed by a common + * command loop. + * + * Let's begin with the basics; parsing the line and + * invoking the function... + */ + +#define AP_MAX_ARGC 64 + +static const char *invoke_cmd(const command_rec *cmd, cmd_parms *parms, + void *mconfig, const char *args) +{ + int override_list_ok = 0; + char *w, *w2, *w3; + const char *errmsg = NULL; + + /* Have we been provided a list of acceptable directives? */ + if (parms->override_list != NULL) { + if (apr_table_get(parms->override_list, cmd->name) != NULL) { + override_list_ok = 1; + } + } + + if ((parms->override & cmd->req_override) == 0 && !override_list_ok) { + if (parms->override & NONFATAL_OVERRIDE) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, parms->temp_pool, + APLOGNO(02295) + "%s in .htaccess forbidden by AllowOverride", + cmd->name); + return NULL; + } + else if (parms->directive && parms->directive->parent) { + return apr_pstrcat(parms->pool, cmd->name, " not allowed in ", + parms->directive->parent->directive, ">", + " context", NULL); + } + else { + return apr_pstrcat(parms->pool, cmd->name, + " not allowed here", NULL); + } + } + + parms->info = cmd->cmd_data; + parms->cmd = cmd; + + switch (cmd->args_how) { + case RAW_ARGS: +#ifdef RESOLVE_ENV_PER_TOKEN + args = ap_resolve_env(parms->pool,args); +#endif + return cmd->AP_RAW_ARGS(parms, mconfig, args); + + case TAKE_ARGV: + { + char *argv[AP_MAX_ARGC]; + int argc = 0; + + do { + w = ap_getword_conf(parms->pool, &args); + if (*w == '\0' && *args == '\0') { + break; + } + argv[argc] = w; + argc++; + } while (argc < AP_MAX_ARGC && *args != '\0'); + + return cmd->AP_TAKE_ARGV(parms, mconfig, argc, argv); + } + + case NO_ARGS: + if (*args != 0) + return apr_pstrcat(parms->pool, cmd->name, " takes no arguments", + NULL); + + return cmd->AP_NO_ARGS(parms, mconfig); + + case TAKE1: + w = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, " takes one argument", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE1(parms, mconfig, w); + + case TAKE2: + w = ap_getword_conf(parms->pool, &args); + w2 = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *w2 == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, " takes two arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE2(parms, mconfig, w, w2); + + case TAKE12: + w = ap_getword_conf(parms->pool, &args); + w2 = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, " takes 1-2 arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE2(parms, mconfig, w, *w2 ? w2 : NULL); + + case TAKE3: + w = ap_getword_conf(parms->pool, &args); + w2 = ap_getword_conf(parms->pool, &args); + w3 = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *w2 == '\0' || *w3 == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, " takes three arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); + + case TAKE23: + w = ap_getword_conf(parms->pool, &args); + w2 = ap_getword_conf(parms->pool, &args); + w3 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + + if (*w == '\0' || *w2 == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, + " takes two or three arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); + + case TAKE123: + w = ap_getword_conf(parms->pool, &args); + w2 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + w3 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + + if (*w == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, + " takes one, two or three arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); + + case TAKE13: + w = ap_getword_conf(parms->pool, &args); + w2 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + w3 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + + if (*w == '\0' || (w2 && *w2 && !w3) || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, + " takes one or three arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); + + case ITERATE: + w = ap_getword_conf(parms->pool, &args); + + if (*w == '\0') + return apr_pstrcat(parms->pool, cmd->name, + " requires at least one argument", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + while (*w != '\0') { + errmsg = cmd->AP_TAKE1(parms, mconfig, w); + + if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0) + return errmsg; + + w = ap_getword_conf(parms->pool, &args); + } + + return errmsg; + + case ITERATE2: + w = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *args == 0) + return apr_pstrcat(parms->pool, cmd->name, + " requires at least two arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + while (*(w2 = ap_getword_conf(parms->pool, &args)) != '\0') { + + errmsg = cmd->AP_TAKE2(parms, mconfig, w, w2); + + if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0) + return errmsg; + } + + return errmsg; + + case FLAG: + /* + * This is safe to use temp_pool here, because the 'flag' itself is not + * forwarded as-is + */ + w = ap_getword_conf(parms->temp_pool, &args); + + if (*w == '\0' || (strcasecmp(w, "on") && strcasecmp(w, "off"))) + return apr_pstrcat(parms->pool, cmd->name, " must be On or Off", + NULL); + + return cmd->AP_FLAG(parms, mconfig, strcasecmp(w, "off") != 0); + + default: + return apr_pstrcat(parms->pool, cmd->name, + " is improperly configured internally (server bug)", + NULL); + } +} + +AP_CORE_DECLARE(const command_rec *) ap_find_command(const char *name, + const command_rec *cmds) +{ + while (cmds->name) { + if (!strcasecmp(name, cmds->name)) + return cmds; + + ++cmds; + } + + return NULL; +} + +AP_CORE_DECLARE(const command_rec *) ap_find_command_in_modules( + const char *cmd_name, module **mod) +{ + const command_rec *cmdp; + module *modp; + + for (modp = *mod; modp; modp = modp->next) { + if (modp->cmds && (cmdp = ap_find_command(cmd_name, modp->cmds))) { + *mod = modp; + return cmdp; + } + } + + return NULL; +} + +AP_CORE_DECLARE(void *) ap_set_config_vectors(server_rec *server, + ap_conf_vector_t *section_vector, + const char *section, + module *mod, apr_pool_t *pconf) +{ + void *section_config = ap_get_module_config(section_vector, mod); + void *server_config = ap_get_module_config(server->module_config, mod); + + if (!section_config && mod->create_dir_config) { + /* ### need to fix the create_dir_config functions' prototype... */ + section_config = (*mod->create_dir_config)(pconf, (char *)section); + ap_set_module_config(section_vector, mod, section_config); + } + + if (!server_config && mod->create_server_config) { + server_config = (*mod->create_server_config)(pconf, server); + ap_set_module_config(server->module_config, mod, server_config); + } + + return section_config; +} + +static const char *execute_now(char *cmd_line, const char *args, + cmd_parms *parms, + apr_pool_t *p, apr_pool_t *ptemp, + ap_directive_t **sub_tree, + ap_directive_t *parent); + +static const char *ap_build_config_sub(apr_pool_t *p, apr_pool_t *temp_pool, + const char *l, cmd_parms *parms, + ap_directive_t **current, + ap_directive_t **curr_parent, + ap_directive_t **conftree) +{ + const char *retval = NULL; + const char *args; + char *cmd_name; + ap_directive_t *newdir; + module *mod = ap_top_module; + const command_rec *cmd; + + if (*l == '#' || *l == '\0') + return NULL; + +#if RESOLVE_ENV_PER_TOKEN + args = l; +#else + args = ap_resolve_env(temp_pool, l); +#endif + + /* The first word is the name of a directive. We can safely use the + * 'temp_pool' for it. If it matches the name of a known directive, we + * can reference the string within the module if needed. Otherwise, we + * can still make a copy in the 'p' pool. */ + cmd_name = ap_getword_conf(temp_pool, &args); + if (*cmd_name == '\0') { + /* Note: this branch should not occur. An empty line should have + * triggered the exit further above. + */ + return NULL; + } + + if (cmd_name[1] != '/') { + char *lastc = cmd_name + strlen(cmd_name) - 1; + if (*lastc == '>') { + *lastc = '\0' ; + } + if (cmd_name[0] == '<' && *args == '\0') { + args = ">"; + } + } + + newdir = apr_pcalloc(p, sizeof(ap_directive_t)); + newdir->filename = parms->config_file->name; + newdir->line_num = parms->config_file->line_number; + newdir->args = apr_pstrdup(p, args); + + if ((cmd = ap_find_command_in_modules(cmd_name, &mod)) != NULL) { + newdir->directive = cmd->name; + + if (cmd->req_override & EXEC_ON_READ) { + ap_directive_t *sub_tree = NULL; + + parms->err_directive = newdir; + retval = execute_now(cmd_name, args, parms, p, temp_pool, + &sub_tree, *curr_parent); + if (*current) { + (*current)->next = sub_tree; + } + else { + *current = sub_tree; + if (*curr_parent) { + (*curr_parent)->first_child = (*current); + } + if (*current) { + (*current)->parent = (*curr_parent); + } + } + if (*current) { + if (!*conftree) { + /* Before walking *current to the end of the list, + * set the head to *current. + */ + *conftree = *current; + } + while ((*current)->next != NULL) { + (*current) = (*current)->next; + (*current)->parent = (*curr_parent); + } + } + return retval; + } + } + else { + /* No known directive found? Make a copy of what we have parsed. */ + newdir->directive = apr_pstrdup(p, cmd_name); + } + + + if (cmd_name[0] == '<') { + if (cmd_name[1] != '/') { + (*current) = ap_add_node(curr_parent, *current, newdir, 1); + } + else if (*curr_parent == NULL) { + parms->err_directive = newdir; + return apr_pstrcat(p, cmd_name, + " without matching <", cmd_name + 2, + " section", NULL); + } + else { + char *bracket = cmd_name + strlen(cmd_name) - 1; + + if (*bracket != '>') { + parms->err_directive = newdir; + return apr_pstrcat(p, cmd_name, + "> directive missing closing '>'", NULL); + } + + *bracket = '\0'; + + if (strcasecmp(cmd_name + 2, + (*curr_parent)->directive + 1) != 0) { + parms->err_directive = newdir; + return apr_pstrcat(p, "Expected </", + (*curr_parent)->directive + 1, "> but saw ", + cmd_name, ">", NULL); + } + + *bracket = '>'; + + /* done with this section; move up a level */ + *current = *curr_parent; + *curr_parent = (*current)->parent; + } + } + else { + *current = ap_add_node(curr_parent, *current, newdir, 0); + } + + return retval; +} + +#define VARBUF_INIT_LEN 200 +#define VARBUF_MAX_LEN (16*1024*1024) + +AP_DECLARE(const char *) ap_build_cont_config(apr_pool_t *p, + apr_pool_t *temp_pool, + cmd_parms *parms, + ap_directive_t **current, + ap_directive_t **curr_parent, + char *orig_directive) +{ + char *bracket; + const char *retval; + ap_directive_t *sub_tree = NULL; + apr_status_t rc; + struct ap_varbuf vb; + apr_size_t max_len = VARBUF_MAX_LEN; + if (p == temp_pool) + max_len = HUGE_STRING_LEN; /* lower limit for .htaccess */ + + bracket = apr_pstrcat(temp_pool, orig_directive + 1, ">", NULL); + ap_varbuf_init(temp_pool, &vb, VARBUF_INIT_LEN); + + while ((rc = ap_varbuf_cfg_getline(&vb, parms->config_file, max_len)) + == APR_SUCCESS) { + if (!memcmp(vb.buf, "</", 2) + && (strcasecmp(vb.buf + 2, bracket) == 0) + && (*curr_parent == NULL)) { + break; + } + retval = ap_build_config_sub(p, temp_pool, vb.buf, parms, current, + curr_parent, &sub_tree); + if (retval != NULL) + return retval; + + if (sub_tree == NULL) { + sub_tree = *curr_parent; + } + + if (sub_tree == NULL) { + sub_tree = *current; + } + } + ap_varbuf_free(&vb); + if (rc != APR_EOF && rc != APR_SUCCESS) + return ap_pcfg_strerror(temp_pool, parms->config_file, rc); + + *current = sub_tree; + return NULL; +} + +static const char *ap_walk_config_sub(const ap_directive_t *current, + cmd_parms *parms, + ap_conf_vector_t *section_vector) +{ + const command_rec *cmd; + ap_mod_list *ml; + char *dir = apr_pstrdup(parms->temp_pool, current->directive); + + ap_str_tolower(dir); + + ml = apr_hash_get(ap_config_hash, dir, APR_HASH_KEY_STRING); + + if (ml == NULL) { + parms->err_directive = current; + if (parms->override & NONFATAL_UNKNOWN) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, parms->temp_pool, + APLOGNO(02296) "Unknown directive %s " + "perhaps misspelled or defined by a module " + "not included in the server configuration", dir); + return NULL; + } + else { + return apr_pstrcat(parms->pool, "Invalid command '", + current->directive, + "', perhaps misspelled or defined by a module " + "not included in the server configuration", + NULL); + } + } + + for ( ; ml != NULL; ml = ml->next) { + void *dir_config = ap_set_config_vectors(parms->server, + section_vector, + parms->path, + ml->m, + parms->pool); + const char *retval; + cmd = ml->cmd; + + /* Once was enough? */ + if (cmd->req_override & EXEC_ON_READ) { + continue; + } + + retval = invoke_cmd(cmd, parms, dir_config, current->args); + + if (retval != NULL && strcmp(retval, DECLINE_CMD) != 0) { + /* If the directive in error has already been set, don't + * replace it. Otherwise, an error inside a container + * will be reported as occurring on the first line of the + * container. + */ + if (!parms->err_directive) { + parms->err_directive = current; + } + return retval; + } + } + + return NULL; +} + +AP_DECLARE(const char *) ap_walk_config(ap_directive_t *current, + cmd_parms *parms, + ap_conf_vector_t *section_vector) +{ + ap_conf_vector_t *oldconfig = parms->context; + + parms->context = section_vector; + + /* scan through all directives, executing each one */ + for (; current != NULL; current = current->next) { + const char *errmsg; + + parms->directive = current; + + /* actually parse the command and execute the correct function */ + errmsg = ap_walk_config_sub(current, parms, section_vector); + if (errmsg != NULL) { + /* restore the context (just in case) */ + parms->context = oldconfig; + return errmsg; + } + } + + parms->context = oldconfig; + return NULL; +} + +AP_DECLARE(const char *) ap_build_config(cmd_parms *parms, + apr_pool_t *p, apr_pool_t *temp_pool, + ap_directive_t **conftree) +{ + ap_directive_t *current = *conftree; + ap_directive_t *curr_parent = NULL; + const char *errmsg; + ap_directive_t **last_ptr = NULL; + apr_status_t rc; + struct ap_varbuf vb; + apr_size_t max_len = VARBUF_MAX_LEN; + if (p == temp_pool) + max_len = HUGE_STRING_LEN; /* lower limit for .htaccess */ + + ap_varbuf_init(temp_pool, &vb, VARBUF_INIT_LEN); + + if (current != NULL) { + /* If we have to traverse the whole tree again for every included + * config file, the required time grows as O(n^2) with the number of + * files. This can be a significant delay for large configurations. + * Therefore we cache a pointer to the last node. + */ + last_ptr = &(current->last); + + if (last_ptr && *last_ptr) { + current = *last_ptr; + } + + while (current->next) { + current = current->next; + } + + if (last_ptr) { + /* update cached pointer to last node */ + *last_ptr = current; + } + } + + while ((rc = ap_varbuf_cfg_getline(&vb, parms->config_file, max_len)) + == APR_SUCCESS) { + errmsg = ap_build_config_sub(p, temp_pool, vb.buf, parms, + ¤t, &curr_parent, conftree); + if (errmsg != NULL) + return errmsg; + + if (*conftree == NULL && curr_parent != NULL) { + *conftree = curr_parent; + } + + if (*conftree == NULL && current != NULL) { + *conftree = current; + } + } + ap_varbuf_free(&vb); + if (rc != APR_EOF && rc != APR_SUCCESS) + return ap_pcfg_strerror(temp_pool, parms->config_file, rc); + + if (curr_parent != NULL) { + errmsg = ""; + + while (curr_parent != NULL) { + errmsg = apr_psprintf(p, "%s%s%s:%u: %s> was not closed.", + errmsg, + *errmsg == '\0' ? "" : APR_EOL_STR, + curr_parent->filename, + curr_parent->line_num, + curr_parent->directive); + + parms->err_directive = curr_parent; + curr_parent = curr_parent->parent; + } + + return errmsg; + } + + return NULL; +} + +/* + * Generic command functions... + */ + +AP_DECLARE_NONSTD(const char *) ap_set_string_slot(cmd_parms *cmd, + void *struct_ptr, + const char *arg) +{ + int offset = (int)(long)cmd->info; + + *(const char **)((char *)struct_ptr + offset) = arg; + + return NULL; +} + +AP_DECLARE_NONSTD(const char *) ap_set_int_slot(cmd_parms *cmd, + void *struct_ptr, + const char *arg) +{ + char *endptr; + char *error_str = NULL; + int offset = (int)(long)cmd->info; + + *(int *)((char*)struct_ptr + offset) = strtol(arg, &endptr, 10); + + if ((*arg == '\0') || (*endptr != '\0')) { + error_str = apr_psprintf(cmd->pool, + "Invalid value for directive %s, expected integer", + cmd->directive->directive); + } + + return error_str; +} + +AP_DECLARE_NONSTD(const char *) ap_set_string_slot_lower(cmd_parms *cmd, + void *struct_ptr, + const char *arg_) +{ + char *arg = apr_pstrdup(cmd->pool,arg_); + int offset = (int)(long)cmd->info; + + ap_str_tolower(arg); + *(char **)((char *)struct_ptr + offset) = arg; + + return NULL; +} + +AP_DECLARE_NONSTD(const char *) ap_set_flag_slot(cmd_parms *cmd, + void *struct_ptr_v, int arg) +{ + int offset = (int)(long)cmd->info; + char *struct_ptr = (char *)struct_ptr_v; + + *(int *)(struct_ptr + offset) = arg ? 1 : 0; + + return NULL; +} + +AP_DECLARE_NONSTD(const char *) ap_set_flag_slot_char(cmd_parms *cmd, + void *struct_ptr_v, int arg) +{ + int offset = (int)(long)cmd->info; + char *struct_ptr = (char *)struct_ptr_v; + + *(struct_ptr + offset) = arg ? 1 : 0; + + return NULL; +} + + +AP_DECLARE_NONSTD(const char *) ap_set_file_slot(cmd_parms *cmd, void *struct_ptr, + const char *arg) +{ + /* Prepend server_root to relative arg. + * This allows most args to be independent of server_root, + * so the server can be moved or mirrored with less pain. + */ + const char *path; + int offset = (int)(long)cmd->info; + + path = ap_server_root_relative(cmd->pool, arg); + + if (!path) { + return apr_pstrcat(cmd->pool, "Invalid file path ", + arg, NULL); + } + + *(const char **) ((char*)struct_ptr + offset) = path; + + return NULL; +} + +AP_DECLARE_NONSTD(const char *) ap_set_deprecated(cmd_parms *cmd, + void *struct_ptr, + const char *arg) +{ + return cmd->cmd->errmsg; +} + +AP_DECLARE(void) ap_reset_module_loglevels(struct ap_logconf *l, int val) +{ + if (l->module_levels) + memset(l->module_levels, val, conf_vector_length); +} + +AP_DECLARE(void) ap_set_module_loglevel(apr_pool_t *pool, struct ap_logconf *l, + int index, int level) +{ + if (!l->module_levels) { + l->module_levels = apr_palloc(pool, conf_vector_length); + if (l->level == APLOG_UNSET) { + ap_reset_module_loglevels(l, APLOG_UNSET); + } + else { + ap_reset_module_loglevels(l, APLOG_NO_MODULE); + } + } + + l->module_levels[index] = level; +} + +/***************************************************************** + * + * Reading whole config files... + */ + +static cmd_parms default_parms = +{NULL, 0, 0, NULL, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; + +AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *file) +{ + char *newpath = NULL; + apr_status_t rv; + rv = apr_filepath_merge(&newpath, ap_server_root, file, + APR_FILEPATH_TRUENAME, p); + if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv) + || APR_STATUS_IS_ENOENT(rv) + || APR_STATUS_IS_ENOTDIR(rv))) { + return newpath; + } + else { + return NULL; + } +} + +AP_DECLARE(char *) ap_runtime_dir_relative(apr_pool_t *p, const char *file) +{ + char *newpath = NULL; + apr_status_t rv; + const char *runtime_dir = ap_runtime_dir ? ap_runtime_dir : ap_server_root_relative(p, DEFAULT_REL_RUNTIMEDIR); + + rv = apr_filepath_merge(&newpath, runtime_dir, file, + APR_FILEPATH_TRUENAME, p); + if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv) + || APR_STATUS_IS_ENOENT(rv) + || APR_STATUS_IS_ENOTDIR(rv))) { + return newpath; + } + else { + return NULL; + } +} + + +AP_DECLARE(const char *) ap_soak_end_container(cmd_parms *cmd, char *directive) +{ + struct ap_varbuf vb; + const char *args; + char *cmd_name; + apr_status_t rc; + apr_size_t max_len = VARBUF_MAX_LEN; + if (cmd->pool == cmd->temp_pool) + max_len = HUGE_STRING_LEN; /* lower limit for .htaccess */ + + ap_varbuf_init(cmd->temp_pool, &vb, VARBUF_INIT_LEN); + + while ((rc = ap_varbuf_cfg_getline(&vb, cmd->config_file, max_len)) + == APR_SUCCESS) { + args = vb.buf; + + cmd_name = ap_getword_conf(cmd->temp_pool, &args); + if (cmd_name[0] == '<') { + if (cmd_name[1] == '/') { + cmd_name[strlen(cmd_name) - 1] = '\0'; + + if (strcasecmp(cmd_name + 2, directive + 1) != 0) { + return apr_pstrcat(cmd->pool, "Expected </", + directive + 1, "> but saw ", + cmd_name, ">", NULL); + } + + ap_varbuf_free(&vb); + return NULL; /* found end of container */ + } + else { + const char *msg; + + if (*args == '\0' && cmd_name[strlen(cmd_name) - 1] == '>') { + cmd_name[strlen(cmd_name) - 1] = '\0'; + } + + if ((msg = ap_soak_end_container(cmd, cmd_name)) != NULL) { + return msg; + } + } + } + } + if (rc != APR_EOF && rc != APR_SUCCESS) + return ap_pcfg_strerror(cmd->temp_pool, cmd->config_file, rc); + + return apr_pstrcat(cmd->pool, "Expected </", + directive + 1, "> before end of configuration", + NULL); +} + +static const char *execute_now(char *cmd_line, const char *args, + cmd_parms *parms, + apr_pool_t *p, apr_pool_t *ptemp, + ap_directive_t **sub_tree, + ap_directive_t *parent) +{ + const command_rec *cmd; + ap_mod_list *ml; + char *dir = apr_pstrdup(parms->temp_pool, cmd_line); + + ap_str_tolower(dir); + + ml = apr_hash_get(ap_config_hash, dir, APR_HASH_KEY_STRING); + + if (ml == NULL) { + return apr_pstrcat(parms->pool, "Invalid command '", + cmd_line, + "', perhaps misspelled or defined by a module " + "not included in the server configuration", + NULL); + } + + for ( ; ml != NULL; ml = ml->next) { + const char *retval; + cmd = ml->cmd; + + retval = invoke_cmd(cmd, parms, sub_tree, args); + + if (retval != NULL) { + return retval; + } + } + + return NULL; +} + +/* This structure and the following functions are needed for the + * table-based config file reading. They are passed to the + * cfg_open_custom() routine. + */ + +/* Structure to be passed to cfg_open_custom(): it contains an + * index which is incremented from 0 to nelts on each call to + * cfg_getline() (which in turn calls arr_elts_getstr()) + * and an apr_array_header_t pointer for the string array. + */ +typedef struct { + apr_array_header_t *array; + int curr_idx; +} arr_elts_param_t; + + +/* arr_elts_getstr() returns the next line from the string array. */ +static apr_status_t arr_elts_getstr(void *buf, apr_size_t bufsiz, void *param) +{ + arr_elts_param_t *arr_param = (arr_elts_param_t *)param; + const char *elt; + + /* End of array reached? */ + if (++arr_param->curr_idx > arr_param->array->nelts) + return APR_EOF; + + /* return the line */ + elt = ((const char **)arr_param->array->elts)[arr_param->curr_idx - 1]; + if (apr_cpystrn(buf, elt, bufsiz) - (char *)buf >= bufsiz - 1) + return APR_ENOSPC; + return APR_SUCCESS; +} + + +/* arr_elts_close(): dummy close routine (makes sure no more lines can be read) */ +static apr_status_t arr_elts_close(void *param) +{ + arr_elts_param_t *arr_param = (arr_elts_param_t *)param; + + arr_param->curr_idx = arr_param->array->nelts; + + return APR_SUCCESS; +} + +static const char *process_command_config(server_rec *s, + apr_array_header_t *arr, + ap_directive_t **conftree, + apr_pool_t *p, + apr_pool_t *ptemp) +{ + const char *errmsg; + cmd_parms parms; + arr_elts_param_t arr_parms; + + arr_parms.curr_idx = 0; + arr_parms.array = arr; + + if (ap_config_hash == NULL) { + rebuild_conf_hash(s->process->pconf, 1); + } + + parms = default_parms; + parms.pool = p; + parms.temp_pool = ptemp; + parms.server = s; + parms.override = (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT); + parms.override_opts = OPT_ALL | OPT_SYM_OWNER | OPT_MULTI; + + parms.config_file = ap_pcfg_open_custom(p, "-c/-C directives", + &arr_parms, NULL, + arr_elts_getstr, arr_elts_close); + + errmsg = ap_build_config(&parms, p, ptemp, conftree); + ap_cfg_closefile(parms.config_file); + + if (errmsg) { + return apr_pstrcat(p, "Syntax error in -C/-c directive: ", errmsg, + NULL); + } + + return NULL; +} + +typedef struct { + const char *fname; +} fnames; + +static int fname_alphasort(const void *fn1, const void *fn2) +{ + const fnames *f1 = fn1; + const fnames *f2 = fn2; + + return strcmp(f1->fname,f2->fname); +} + +/** + * Used by -D DUMP_INCLUDES to output the config file "tree". + */ +static void dump_config_name(const char *fname, apr_pool_t *p) +{ + unsigned i, recursion, line_number; + void *data; + apr_file_t *out = NULL; + + apr_file_open_stdout(&out, p); + + /* ap_include_sentinel is defined by the core Include directive; use it to + * figure out how deep in the stack we are. + */ + apr_pool_userdata_get(&data, "ap_include_sentinel", p); + + if (data) { + recursion = *(unsigned *)data; + } else { + recursion = 0; + } + + /* Indent once for each level. */ + for (i = 0; i < (recursion + 1); ++i) { + apr_file_printf(out, " "); + } + + /* ap_include_lineno is similarly defined to tell us where in the last + * config file we were. + */ + apr_pool_userdata_get(&data, "ap_include_lineno", p); + + if (data) { + line_number = *(unsigned *)data; + } else { + line_number = 0; + } + + /* Print the line number and the name of the parsed file. */ + if (line_number > 0) { + apr_file_printf(out, "(%u)", line_number); + } else { + apr_file_printf(out, "(*)"); + } + + apr_file_printf(out, " %s\n", fname); +} + +AP_DECLARE(const char *) ap_process_resource_config(server_rec *s, + const char *fname, + ap_directive_t **conftree, + apr_pool_t *p, + apr_pool_t *ptemp) +{ + ap_configfile_t *cfp; + cmd_parms parms; + apr_status_t rv; + const char *error; + + parms = default_parms; + parms.pool = p; + parms.temp_pool = ptemp; + parms.server = s; + parms.override = (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT); + parms.override_opts = OPT_ALL | OPT_SYM_OWNER | OPT_MULTI; + + rv = ap_pcfg_openfile(&cfp, p, fname); + if (rv != APR_SUCCESS) { + return apr_psprintf(p, "Could not open configuration file %s: %pm", + fname, &rv); + } + + if (ap_exists_config_define("DUMP_INCLUDES")) { + dump_config_name(fname, p); + } + + parms.config_file = cfp; + error = ap_build_config(&parms, p, ptemp, conftree); + ap_cfg_closefile(cfp); + + if (error) { + if (parms.err_directive) + return apr_psprintf(p, "Syntax error on line %d of %s: %s", + parms.err_directive->line_num, + parms.err_directive->filename, error); + else + return error; + } + + return NULL; +} + +static const char *process_resource_config_nofnmatch(server_rec *s, + const char *fname, + ap_directive_t **conftree, + apr_pool_t *p, + apr_pool_t *ptemp, + unsigned depth, + int optional) +{ + const char *error; + apr_status_t rv; + + if (ap_is_directory(ptemp, fname)) { + apr_dir_t *dirp; + apr_finfo_t dirent; + int current; + apr_array_header_t *candidates = NULL; + fnames *fnew; + char *path = apr_pstrdup(ptemp, fname); + + if (++depth > AP_MAX_INCLUDE_DIR_DEPTH) { + return apr_psprintf(p, "Directory %s exceeds the maximum include " + "directory nesting level of %u. You have " + "probably a recursion somewhere.", path, + AP_MAX_INCLUDE_DIR_DEPTH); + } + + /* + * first course of business is to grok all the directory + * entries here and store 'em away. Recall we need full pathnames + * for this. + */ + rv = apr_dir_open(&dirp, path, ptemp); + if (rv != APR_SUCCESS) { + return apr_psprintf(p, "Could not open config directory %s: %pm", + path, &rv); + } + + candidates = apr_array_make(ptemp, 1, sizeof(fnames)); + while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) { + /* strip out '.' and '..' */ + if (strcmp(dirent.name, ".") + && strcmp(dirent.name, "..")) { + fnew = (fnames *) apr_array_push(candidates); + fnew->fname = ap_make_full_path(ptemp, path, dirent.name); + } + } + + apr_dir_close(dirp); + if (candidates->nelts != 0) { + qsort((void *) candidates->elts, candidates->nelts, + sizeof(fnames), fname_alphasort); + + /* + * Now recurse these... we handle errors and subdirectories + * via the recursion, which is nice + */ + for (current = 0; current < candidates->nelts; ++current) { + fnew = &((fnames *) candidates->elts)[current]; + error = process_resource_config_nofnmatch(s, fnew->fname, + conftree, p, ptemp, + depth, optional); + if (error) { + return error; + } + } + } + + return NULL; + } + else if (optional) { + /* If the optinal flag is set (like for IncludeOptional) we can + * tolerate that no file or directory is present and bail out. + */ + apr_finfo_t finfo; + if (apr_stat(&finfo, fname, APR_FINFO_TYPE, ptemp) != APR_SUCCESS + || finfo.filetype == APR_NOFILE) + return NULL; + } + + return ap_process_resource_config(s, fname, conftree, p, ptemp); +} + +static const char *process_resource_config_fnmatch(server_rec *s, + const char *path, + const char *fname, + ap_directive_t **conftree, + apr_pool_t *p, + apr_pool_t *ptemp, + unsigned depth, + int optional) +{ + const char *rest; + apr_status_t rv; + apr_dir_t *dirp; + apr_finfo_t dirent; + apr_array_header_t *candidates = NULL; + fnames *fnew; + int current; + + /* find the first part of the filename */ + rest = ap_strchr_c(fname, '/'); + if (rest) { + fname = apr_pstrmemdup(ptemp, fname, rest - fname); + rest++; + } + + /* optimisation - if the filename isn't a wildcard, process it directly */ + if (!apr_fnmatch_test(fname)) { + path = ap_make_full_path(ptemp, path, fname); + if (!rest) { + return process_resource_config_nofnmatch(s, path, + conftree, p, + ptemp, 0, optional); + } + else { + return process_resource_config_fnmatch(s, path, rest, + conftree, p, + ptemp, 0, optional); + } + } + + /* + * first course of business is to grok all the directory + * entries here and store 'em away. Recall we need full pathnames + * for this. + */ + rv = apr_dir_open(&dirp, path, ptemp); + if (rv != APR_SUCCESS) { + /* If the directory doesn't exist and the optional flag is set + * there is no need to return an error. + */ + if (rv == APR_ENOENT && optional) { + return NULL; + } + return apr_psprintf(p, "Could not open config directory %s: %pm", + path, &rv); + } + + candidates = apr_array_make(ptemp, 1, sizeof(fnames)); + while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) { + /* strip out '.' and '..' */ + if (strcmp(dirent.name, ".") + && strcmp(dirent.name, "..") + && (apr_fnmatch(fname, dirent.name, + APR_FNM_PERIOD) == APR_SUCCESS)) { + const char *full_path = ap_make_full_path(ptemp, path, dirent.name); + /* If matching internal to path, and we happen to match something + * other than a directory, skip it + */ + if (rest && (dirent.filetype != APR_DIR)) { + continue; + } + fnew = (fnames *) apr_array_push(candidates); + fnew->fname = full_path; + } + } + + apr_dir_close(dirp); + if (candidates->nelts != 0) { + const char *error; + + qsort((void *) candidates->elts, candidates->nelts, + sizeof(fnames), fname_alphasort); + + /* + * Now recurse these... we handle errors and subdirectories + * via the recursion, which is nice + */ + for (current = 0; current < candidates->nelts; ++current) { + fnew = &((fnames *) candidates->elts)[current]; + if (!rest) { + error = process_resource_config_nofnmatch(s, fnew->fname, + conftree, p, + ptemp, 0, optional); + } + else { + error = process_resource_config_fnmatch(s, fnew->fname, rest, + conftree, p, + ptemp, 0, optional); + } + if (error) { + return error; + } + } + } + else { + + if (!optional) { + return apr_psprintf(p, "No matches for the wildcard '%s' in '%s', failing " + "(use IncludeOptional if required)", fname, path); + } + } + + return NULL; +} + +AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, + const char *fname, + ap_directive_t **conftree, + apr_pool_t *p, + apr_pool_t *ptemp, + int optional) +{ + /* XXX: lstat() won't work on the wildcard pattern... + */ + + /* don't require conf/httpd.conf if we have a -C or -c switch */ + if ((ap_server_pre_read_config->nelts + || ap_server_post_read_config->nelts) + && !(strcmp(fname, ap_server_root_relative(ptemp, SERVER_CONFIG_FILE)))) { + apr_finfo_t finfo; + + if (apr_stat(&finfo, fname, APR_FINFO_LINK | APR_FINFO_TYPE, ptemp) != APR_SUCCESS) + return NULL; + } + + if (!apr_fnmatch_test(fname)) { + return process_resource_config_nofnmatch(s, fname, conftree, p, ptemp, 0, optional); + } + else { + apr_status_t status; + const char *rootpath, *filepath = fname; + + /* locate the start of the directories proper */ + status = apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_TRUENAME, ptemp); + + /* we allow APR_SUCCESS and APR_EINCOMPLETE */ + if (APR_ERELATIVE == status) { + return apr_pstrcat(p, "Include must have an absolute path, ", fname, NULL); + } + else if (APR_EBADPATH == status) { + return apr_pstrcat(p, "Include has a bad path, ", fname, NULL); + } + + /* walk the filepath */ + return process_resource_config_fnmatch(s, rootpath, filepath, conftree, p, ptemp, + 0, optional); + } +} + +AP_DECLARE(int) ap_process_config_tree(server_rec *s, + ap_directive_t *conftree, + apr_pool_t *p, + apr_pool_t *ptemp) +{ + const char *errmsg; + cmd_parms parms; + + parms = default_parms; + parms.pool = p; + parms.temp_pool = ptemp; + parms.server = s; + parms.override = (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT); + parms.override_opts = OPT_ALL | OPT_SYM_OWNER | OPT_MULTI; + parms.limited = -1; + + errmsg = ap_walk_config(conftree, &parms, s->lookup_defaults); + if (errmsg) { + if (parms.err_directive) + ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0, p, APLOGNO(00526) + "Syntax error on line %d of %s:", + parms.err_directive->line_num, + parms.err_directive->filename); + ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0, p, "%s", errmsg); + return HTTP_INTERNAL_SERVER_ERROR; + } + + return OK; +} + +apr_status_t ap_open_htaccess(request_rec *r, const char *dir_name, + const char *access_name, + ap_configfile_t **conffile, + const char **full_name) +{ + *full_name = ap_make_full_path(r->pool, dir_name, access_name); + return ap_pcfg_openfile(conffile, r->pool, *full_name); +} + +AP_CORE_DECLARE(int) ap_parse_htaccess(ap_conf_vector_t **result, + request_rec *r, int override, + int override_opts, apr_table_t *override_list, + const char *d, const char *access_names) +{ + ap_configfile_t *f = NULL; + cmd_parms parms; + const char *filename; + const struct htaccess_result *cache; + struct htaccess_result *new; + ap_conf_vector_t *dc = NULL; + apr_status_t status; + + /* firstly, search cache */ + for (cache = r->htaccess; cache != NULL; cache = cache->next) { + if (cache->override == override && strcmp(cache->dir, d) == 0) { + *result = cache->htaccess; + return OK; + } + } + + parms = default_parms; + parms.override = override; + parms.override_opts = override_opts; + parms.override_list = override_list; + parms.pool = r->pool; + parms.temp_pool = r->pool; + parms.server = r->server; + parms.path = apr_pstrdup(r->pool, d); + + /* loop through the access names and find the first one */ + while (access_names[0]) { + const char *access_name = ap_getword_conf(r->pool, &access_names); + + filename = NULL; + status = ap_run_open_htaccess(r, d, access_name, &f, &filename); + if (status == APR_SUCCESS) { + const char *errmsg; + ap_directive_t *temptree = NULL; + + dc = ap_create_per_dir_config(r->pool); + + parms.config_file = f; + errmsg = ap_build_config(&parms, r->pool, r->pool, &temptree); + if (errmsg == NULL) + errmsg = ap_walk_config(temptree, &parms, dc); + + ap_cfg_closefile(f); + + if (errmsg) { + ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, + "%s: %s", filename, errmsg); + return HTTP_INTERNAL_SERVER_ERROR; + } + + *result = dc; + break; + } + else { + if (!APR_STATUS_IS_ENOENT(status) + && !APR_STATUS_IS_ENOTDIR(status)) { + ap_log_rerror(APLOG_MARK, APLOG_CRIT, status, r, APLOGNO(00529) + "%s pcfg_openfile: unable to check htaccess file, " + "ensure it is readable and that '%s' " + "is executable", + filename, d); + apr_table_setn(r->notes, "error-notes", + "Server unable to read htaccess file, denying " + "access to be safe"); + return HTTP_FORBIDDEN; + } + } + } + + /* cache it */ + new = apr_palloc(r->pool, sizeof(struct htaccess_result)); + new->dir = parms.path; + new->override = override; + new->override_opts = override_opts; + new->htaccess = dc; + + /* add to head of list */ + new->next = r->htaccess; + r->htaccess = new; + + return OK; +} + +AP_CORE_DECLARE(const char *) ap_init_virtual_host(apr_pool_t *p, + const char *hostname, + server_rec *main_server, + server_rec **ps) +{ + server_rec *s = (server_rec *) apr_pcalloc(p, sizeof(server_rec)); + + /* TODO: this crap belongs in http_core */ + s->process = main_server->process; + s->server_admin = NULL; + s->server_hostname = NULL; + s->server_scheme = NULL; + s->error_fname = NULL; + s->timeout = 0; + s->keep_alive_timeout = 0; + s->keep_alive = -1; + s->keep_alive_max = -1; + s->error_log = main_server->error_log; + s->log.level = APLOG_UNSET; + s->log.module_levels = NULL; + /* useful default, otherwise we get a port of 0 on redirects */ + s->port = main_server->port; + s->next = NULL; + + s->is_virtual = 1; + s->names = apr_array_make(p, 4, sizeof(char **)); + s->wild_names = apr_array_make(p, 4, sizeof(char **)); + + s->module_config = create_empty_config(p); + s->lookup_defaults = ap_create_per_dir_config(p); + + s->limit_req_line = main_server->limit_req_line; + s->limit_req_fieldsize = main_server->limit_req_fieldsize; + s->limit_req_fields = main_server->limit_req_fields; + + *ps = s; + + return ap_parse_vhost_addrs(p, hostname, s); +} + +AP_DECLARE(struct ap_logconf *) ap_new_log_config(apr_pool_t *p, + const struct ap_logconf *old) +{ + struct ap_logconf *l = apr_pcalloc(p, sizeof(struct ap_logconf)); + if (old) { + l->level = old->level; + if (old->module_levels) { + l->module_levels = + apr_pmemdup(p, old->module_levels, conf_vector_length); + } + } + else { + l->level = APLOG_UNSET; + } + return l; +} + +AP_DECLARE(void) ap_merge_log_config(const struct ap_logconf *old_conf, + struct ap_logconf *new_conf) +{ + if (new_conf->level != APLOG_UNSET) { + /* Setting the main loglevel resets all per-module log levels. + * I.e. if new->level has been set, we must ignore old->module_levels. + */ + return; + } + + new_conf->level = old_conf->level; + if (new_conf->module_levels == NULL) { + new_conf->module_levels = old_conf->module_levels; + } + else if (old_conf->module_levels != NULL) { + int i; + for (i = 0; i < conf_vector_length; i++) { + if (new_conf->module_levels[i] == APLOG_UNSET) + new_conf->module_levels[i] = old_conf->module_levels[i]; + } + } +} + +AP_DECLARE(void) ap_fixup_virtual_hosts(apr_pool_t *p, server_rec *main_server) +{ + server_rec *virt; + core_dir_config *dconf = + ap_get_core_module_config(main_server->lookup_defaults); + dconf->log = &main_server->log; + + for (virt = main_server->next; virt; virt = virt->next) { + merge_server_configs(p, main_server->module_config, virt); + + virt->lookup_defaults = + ap_merge_per_dir_configs(p, main_server->lookup_defaults, + virt->lookup_defaults); + + if (virt->server_admin == NULL) + virt->server_admin = main_server->server_admin; + + if (virt->timeout == 0) + virt->timeout = main_server->timeout; + + if (virt->keep_alive_timeout == 0) + virt->keep_alive_timeout = main_server->keep_alive_timeout; + + if (virt->keep_alive == -1) + virt->keep_alive = main_server->keep_alive; + + if (virt->keep_alive_max == -1) + virt->keep_alive_max = main_server->keep_alive_max; + + ap_merge_log_config(&main_server->log, &virt->log); + + dconf = ap_get_core_module_config(virt->lookup_defaults); + dconf->log = &virt->log; + + /* XXX: this is really something that should be dealt with by a + * post-config api phase + */ + ap_core_reorder_directories(p, virt); + } + + ap_core_reorder_directories(p, main_server); +} + +/***************************************************************** + * + * Getting *everything* configured... + */ + +static void init_config_globals(apr_pool_t *p) +{ + /* Global virtual host hash bucket pointers. Init to null. */ + ap_init_vhost_config(p); +} + +static server_rec *init_server_config(process_rec *process, apr_pool_t *p) +{ + apr_status_t rv; + server_rec *s = (server_rec *) apr_pcalloc(p, sizeof(server_rec)); + + apr_file_open_stderr(&s->error_log, p); + s->process = process; + s->port = 0; + s->server_admin = DEFAULT_ADMIN; + s->server_hostname = NULL; + s->server_scheme = NULL; + s->error_fname = DEFAULT_ERRORLOG; + s->log.level = DEFAULT_LOGLEVEL; + s->log.module_levels = NULL; + s->limit_req_line = DEFAULT_LIMIT_REQUEST_LINE; + s->limit_req_fieldsize = DEFAULT_LIMIT_REQUEST_FIELDSIZE; + s->limit_req_fields = DEFAULT_LIMIT_REQUEST_FIELDS; + s->timeout = apr_time_from_sec(DEFAULT_TIMEOUT); + s->keep_alive_timeout = apr_time_from_sec(DEFAULT_KEEPALIVE_TIMEOUT); + s->keep_alive_max = DEFAULT_KEEPALIVE; + s->keep_alive = 1; + s->next = NULL; + s->addrs = apr_pcalloc(p, sizeof(server_addr_rec)); + + /* NOT virtual host; don't match any real network interface */ + rv = apr_sockaddr_info_get(&s->addrs->host_addr, + NULL, APR_UNSPEC, 0, 0, p); + if (rv != APR_SUCCESS) { + /* should we test here for rv being an EAIERR? */ + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, rv, NULL, APLOGNO(00530) + "initialisation: bug or getaddrinfo fail"); + return NULL; + } + + s->addrs->host_port = 0; /* matches any port */ + s->addrs->virthost = ""; /* must be non-NULL */ + s->names = s->wild_names = NULL; + + s->module_config = create_server_config(p, s); + s->lookup_defaults = create_default_per_dir_config(p); + + return s; +} + + +static apr_status_t reset_conf_vector_length(void *dummy) +{ + reserved_module_slots = 0; + conf_vector_length = max_modules; + return APR_SUCCESS; +} + +static int conf_vector_length_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + /* + * We have loaded all modules that are loaded by EXEC_ON_READ directives. + * From now on we reduce the size of the config vectors to what we need, + * plus what has been reserved (e.g. by mod_perl) for additional modules + * loaded later on. + * If max_modules is too small, ap_add_module() will abort. + */ + if (total_modules + reserved_module_slots < max_modules) { + conf_vector_length = total_modules + reserved_module_slots; + } + apr_pool_cleanup_register(pconf, NULL, reset_conf_vector_length, + apr_pool_cleanup_null); + return OK; +} + + +AP_CORE_DECLARE(void) ap_register_config_hooks(apr_pool_t *p) +{ + ap_hook_pre_config(conf_vector_length_pre_config, NULL, NULL, + APR_HOOK_REALLY_LAST); +} + +AP_DECLARE(server_rec*) ap_read_config(process_rec *process, apr_pool_t *ptemp, + const char *filename, + ap_directive_t **conftree) +{ + const char *confname, *error; + apr_pool_t *p = process->pconf; + server_rec *s = init_server_config(process, p); + if (s == NULL) { + return s; + } + + init_config_globals(p); + + if (ap_exists_config_define("DUMP_INCLUDES")) { + apr_file_t *out = NULL; + apr_file_open_stdout(&out, p); + + /* Included files will be dumped as the config is walked; print a + * header. + */ + apr_file_printf(out, "Included configuration files:\n"); + } + + /* All server-wide config files now have the SAME syntax... */ + error = process_command_config(s, ap_server_pre_read_config, conftree, + p, ptemp); + if (error) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, 0, NULL, "%s: %s", + ap_server_argv0, error); + return NULL; + } + + /* process_command_config may change the ServerRoot so + * compute this config file name afterwards. + */ + confname = ap_server_root_relative(p, filename); + + if (!confname) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, + APR_EBADPATH, NULL, APLOGNO(00532) "Invalid config file path %s", + filename); + return NULL; + } + + error = ap_process_resource_config(s, confname, conftree, p, ptemp); + if (error) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, 0, NULL, + "%s: %s", ap_server_argv0, error); + return NULL; + } + + error = ap_check_mpm(); + if (error) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, 0, NULL, APLOGNO(00534) + "%s: Configuration error: %s", ap_server_argv0, error); + return NULL; + } + + error = process_command_config(s, ap_server_post_read_config, conftree, + p, ptemp); + + if (error) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, 0, NULL, "%s: %s", + ap_server_argv0, error); + return NULL; + } + + return s; +} + +AP_DECLARE(void) ap_single_module_configure(apr_pool_t *p, server_rec *s, + module *m) +{ + if (m->create_server_config) + ap_set_module_config(s->module_config, m, + (*m->create_server_config)(p, s)); + + if (m->create_dir_config) + ap_set_module_config(s->lookup_defaults, m, + (*m->create_dir_config)(p, NULL)); +} + +AP_DECLARE(void) ap_run_rewrite_args(process_rec *process) +{ + module *m; + + for (m = ap_top_module; m; m = m->next) { + if (m->rewrite_args) { + (*m->rewrite_args)(process); + } + } +} + +/******************************************************************** + * Configuration directives are restricted in terms of where they may + * appear in the main configuration files and/or .htaccess files according + * to the bitmask req_override in the command_rec structure. + * If any of the overrides set in req_override are also allowed in the + * context in which the command is read, then the command is allowed. + * The context is determined as follows: + * + * inside *.conf --> override = (RSRC_CONF|OR_ALL)&~(OR_AUTHCFG|OR_LIMIT); + * within <Directory> or <Location> --> override = OR_ALL|ACCESS_CONF; + * within .htaccess --> override = AllowOverride for current directory; + * + * the result is, well, a rather confusing set of possibilities for when + * a particular directive is allowed to be used. This procedure prints + * in English where the given (pc) directive can be used. + */ +static void show_overrides(const command_rec *pc, module *pm) +{ + int n = 0; + + printf("\tAllowed in *.conf "); + if ((pc->req_override & (OR_OPTIONS | OR_FILEINFO | OR_INDEXES)) + || ((pc->req_override & RSRC_CONF) + && ((pc->req_override & (ACCESS_CONF | OR_AUTHCFG | OR_LIMIT))))) { + printf("anywhere"); + } + else if (pc->req_override & RSRC_CONF) { + printf("only outside <Directory>, <Files>, <Location>, or <If>"); + } + else { + printf("only inside <Directory>, <Files>, <Location>, or <If>"); + } + + /* Warn if the directive is allowed inside <Directory> or .htaccess + * but module doesn't support per-dir configuration + */ + if ((pc->req_override & (OR_ALL | ACCESS_CONF)) && !pm->create_dir_config) + printf(" [no per-dir config]"); + + if (pc->req_override & OR_ALL) { + printf(" and in .htaccess\n\twhen AllowOverride"); + + if ((pc->req_override & OR_ALL) == OR_ALL) { + printf(" isn't None"); + } + else { + printf(" includes "); + + if (pc->req_override & OR_AUTHCFG) { + if (n++) + printf(" or "); + + printf("AuthConfig"); + } + + if (pc->req_override & OR_LIMIT) { + if (n++) + printf(" or "); + + printf("Limit"); + } + + if (pc->req_override & OR_OPTIONS) { + if (n++) + printf(" or "); + + printf("Options"); + } + + if (pc->req_override & OR_FILEINFO) { + if (n++) + printf(" or "); + + printf("FileInfo"); + } + + if (pc->req_override & OR_INDEXES) { + if (n++) + printf(" or "); + + printf("Indexes"); + } + } + } + + printf("\n"); +} + +/* Show the preloaded configuration directives, the help string explaining + * the directive arguments, in what module they are handled, and in + * what parts of the configuration they are allowed. Used for httpd -L. + */ +AP_DECLARE(void) ap_show_directives(void) +{ + const command_rec *pc; + int n; + + for (n = 0; ap_loaded_modules[n]; ++n) { + for (pc = ap_loaded_modules[n]->cmds; pc && pc->name; ++pc) { + printf("%s (%s)\n", pc->name, ap_loaded_modules[n]->name); + + if (pc->errmsg) + printf("\t%s\n", pc->errmsg); + + show_overrides(pc, ap_loaded_modules[n]); + } + } +} + +/* Show the preloaded module names. Used for httpd -l. */ +AP_DECLARE(void) ap_show_modules(void) +{ + int n; + + printf("Compiled in modules:\n"); + for (n = 0; ap_loaded_modules[n]; ++n) + printf(" %s\n", ap_loaded_modules[n]->name); +} + +AP_DECLARE(int) ap_exists_directive(apr_pool_t *p, const char *name) +{ + char *lname = apr_pstrdup(p, name); + + ap_str_tolower(lname); + + return ap_config_hash && + apr_hash_get(ap_config_hash, lname, APR_HASH_KEY_STRING) != NULL; +} + +AP_DECLARE(void *) ap_retained_data_get(const char *key) +{ + void *retained; + + apr_pool_userdata_get((void *)&retained, key, ap_pglobal); + return retained; +} + +AP_DECLARE(void *) ap_retained_data_create(const char *key, apr_size_t size) +{ + void *retained; + + retained = apr_pcalloc(ap_pglobal, size); + apr_pool_userdata_set((const void *)retained, key, apr_pool_cleanup_null, ap_pglobal); + return retained; +} + +static int count_directives_sub(const char *directive, ap_directive_t *current) +{ + int count = 0; + while (current != NULL) { + if (current->first_child != NULL) + count += count_directives_sub(directive, current->first_child); + if (strcasecmp(current->directive, directive) == 0) + count++; + current = current->next; + } + return count; +} + +AP_DECLARE(void) ap_reserve_module_slots(int count) +{ + reserved_module_slots += count; +} + +AP_DECLARE(void) ap_reserve_module_slots_directive(const char *directive) +{ + ap_reserve_module_slots(count_directives_sub(directive, ap_conftree)); +} diff --git a/server/config.m4 b/server/config.m4 new file mode 100644 index 0000000..dde51ed --- /dev/null +++ b/server/config.m4 @@ -0,0 +1,19 @@ +dnl ## Check for libraries + +dnl ## Check for header files + +AC_CHECK_HEADERS(bstring.h unistd.h) + +dnl ## Check for typedefs, structures, and compiler characteristics. + +dnl ## Check for library functions + +AC_CHECK_FUNCS(syslog) + +dnl Obsolete scoreboard code uses this. + AC_CHECK_HEADERS(sys/times.h) + AC_CHECK_FUNCS(times) + +dnl Add expr header files to INCLUDES +# util_expr needs header files in server source dir +APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/server]) diff --git a/server/connection.c b/server/connection.c new file mode 100644 index 0000000..1974537 --- /dev/null +++ b/server/connection.c @@ -0,0 +1,221 @@ +/* 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 "apr.h" +#include "apr_strings.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_connection.h" +#include "http_request.h" +#include "http_protocol.h" +#include "ap_mpm.h" +#include "http_config.h" +#include "http_core.h" +#include "http_vhost.h" +#include "scoreboard.h" +#include "http_log.h" +#include "util_filter.h" + +APR_HOOK_STRUCT( + APR_HOOK_LINK(create_connection) + APR_HOOK_LINK(process_connection) + APR_HOOK_LINK(pre_connection) + APR_HOOK_LINK(pre_close_connection) +) +AP_IMPLEMENT_HOOK_RUN_FIRST(conn_rec *,create_connection, + (apr_pool_t *p, server_rec *server, apr_socket_t *csd, long conn_id, void *sbh, apr_bucket_alloc_t *alloc), + (p, server, csd, conn_id, sbh, alloc), NULL) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,process_connection,(conn_rec *c),(c),DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int,pre_connection,(conn_rec *c, void *csd),(c, csd),OK,DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int,pre_close_connection,(conn_rec *c),(c),OK,DECLINED) + +/* + * More machine-dependent networking gooo... on some systems, + * you've got to be *really* sure that all the packets are acknowledged + * before closing the connection, since the client will not be able + * to see the last response if their TCP buffer is flushed by a RST + * packet from us, which is what the server's TCP stack will send + * if it receives any request data after closing the connection. + * + * In an ideal world, this function would be accomplished by simply + * setting the socket option SO_LINGER and handling it within the + * server's TCP stack while the process continues on to the next request. + * Unfortunately, it seems that most (if not all) operating systems + * block the server process on close() when SO_LINGER is used. + * For those that don't, see USE_SO_LINGER below. For the rest, + * we have created a home-brew lingering_close. + * + * Many operating systems tend to block, puke, or otherwise mishandle + * calls to shutdown only half of the connection. You should define + * NO_LINGCLOSE in ap_config.h if such is the case for your system. + */ +#ifndef MAX_SECS_TO_LINGER +#define MAX_SECS_TO_LINGER 30 +#endif + +AP_CORE_DECLARE(apr_status_t) ap_shutdown_conn(conn_rec *c, int flush) +{ + apr_status_t rv; + apr_bucket_brigade *bb; + apr_bucket *b; + + bb = apr_brigade_create(c->pool, c->bucket_alloc); + + if (flush) { + /* FLUSH bucket */ + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + } + + /* End Of Connection bucket */ + b = ap_bucket_eoc_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + + rv = ap_pass_brigade(c->output_filters, bb); + apr_brigade_destroy(bb); + return rv; +} + +AP_CORE_DECLARE(void) ap_flush_conn(conn_rec *c) +{ + (void)ap_shutdown_conn(c, 1); +} + +AP_DECLARE(int) ap_prep_lingering_close(conn_rec *c) +{ + /* Give protocol handlers one last chance to raise their voice */ + ap_run_pre_close_connection(c); + + if (c->sbh) { + ap_update_child_status(c->sbh, SERVER_CLOSING, NULL); + } + return 0; +} + +/* we now proceed to read from the client until we get EOF, or until + * MAX_SECS_TO_LINGER has passed. The reasons for doing this are + * documented in a draft: + * + * http://tools.ietf.org/html/draft-ietf-http-connection-00.txt + * + * in a nutshell -- if we don't make this effort we risk causing + * TCP RST packets to be sent which can tear down a connection before + * all the response data has been sent to the client. + */ +#define SECONDS_TO_LINGER 2 + +AP_DECLARE(int) ap_start_lingering_close(conn_rec *c) +{ + apr_socket_t *csd = ap_get_conn_socket(c); + + if (!csd) { + return 1; + } + + if (ap_prep_lingering_close(c)) { + return 1; + } + + /* Close the connection, being careful to send out whatever is still + * in our buffers. If possible, try to avoid a hard close until the + * client has ACKed our FIN and/or has stopped sending us data. + */ + + /* Send any leftover data to the client, but never try to again */ + ap_flush_conn(c); + +#ifdef NO_LINGCLOSE + apr_socket_close(csd); + return 1; +#else + /* Shut down the socket for write, which will send a FIN + * to the peer. + */ + if (c->aborted + || apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE) != APR_SUCCESS) { + apr_socket_close(csd); + return 1; + } + return 0; +#endif +} + +AP_DECLARE(void) ap_lingering_close(conn_rec *c) +{ + char dummybuf[512]; + apr_size_t nbytes; + apr_time_t now, timeup = 0; + apr_socket_t *csd = ap_get_conn_socket(c); + + if (ap_start_lingering_close(c)) { + return; + } + + /* Read available data from the client whilst it continues sending + * it, for a maximum time of MAX_SECS_TO_LINGER. If the client + * does not send any data within 2 seconds (a value pulled from + * Apache 1.3 which seems to work well), give up. + */ + apr_socket_timeout_set(csd, apr_time_from_sec(SECONDS_TO_LINGER)); + apr_socket_opt_set(csd, APR_INCOMPLETE_READ, 1); + + /* The common path here is that the initial apr_socket_recv() call + * will return 0 bytes read; so that case must avoid the expensive + * apr_time_now() call and time arithmetic. */ + + do { + nbytes = sizeof(dummybuf); + if (apr_socket_recv(csd, dummybuf, &nbytes) || nbytes == 0) + break; + + now = apr_time_now(); + if (timeup == 0) { + /* + * First time through; + * calculate now + 30 seconds (MAX_SECS_TO_LINGER). + * + * If some module requested a shortened waiting period, only wait for + * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain + * DoS attacks. + */ + if (apr_table_get(c->notes, "short-lingering-close")) { + timeup = now + apr_time_from_sec(SECONDS_TO_LINGER); + } + else { + timeup = now + apr_time_from_sec(MAX_SECS_TO_LINGER); + } + continue; + } + } while (now < timeup); + + apr_socket_close(csd); +} + +AP_CORE_DECLARE(void) ap_process_connection(conn_rec *c, void *csd) +{ + int rc; + ap_update_vhost_given_ip(c); + + rc = ap_run_pre_connection(c, csd); + if (rc != OK && rc != DONE) { + c->aborted = 1; + } + + if (!c->aborted) { + ap_run_process_connection(c); + } +} diff --git a/server/core.c b/server/core.c new file mode 100644 index 0000000..e2a91c7 --- /dev/null +++ b/server/core.c @@ -0,0 +1,5473 @@ +/* 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 "apr.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_fnmatch.h" +#include "apr_hash.h" +#include "apr_thread_proc.h" /* for RLIMIT stuff */ +#include "apr_random.h" + +#define APR_WANT_IOVEC +#define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" /* For index_of_response(). Grump. */ +#include "http_request.h" +#include "http_vhost.h" +#include "http_main.h" /* For the default_handler below... */ +#include "http_log.h" +#include "util_md5.h" +#include "http_connection.h" +#include "apr_buckets.h" +#include "util_filter.h" +#include "util_ebcdic.h" +#include "util_mutex.h" +#include "util_time.h" +#include "mpm_common.h" +#include "scoreboard.h" +#include "mod_core.h" +#include "mod_proxy.h" +#include "ap_listen.h" +#include "ap_regex.h" + +#include "mod_so.h" /* for ap_find_loaded_module_symbol */ + +#if defined(RLIMIT_CPU) || defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS) || defined (RLIMIT_NPROC) +#include "unixd.h" +#endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* LimitRequestBody handling */ +#define AP_LIMIT_REQ_BODY_UNSET ((apr_off_t) -1) +#define AP_DEFAULT_LIMIT_REQ_BODY ((apr_off_t) 0) + +/* LimitXMLRequestBody handling */ +#define AP_LIMIT_UNSET ((long) -1) +#define AP_DEFAULT_LIMIT_XML_BODY ((apr_size_t)1000000) + +#define AP_MIN_SENDFILE_BYTES (256) + +/* maximum include nesting level */ +#ifndef AP_MAX_INCLUDE_DEPTH +#define AP_MAX_INCLUDE_DEPTH (128) +#endif + +/* valid in core-conf, but not in runtime r->used_path_info */ +#define AP_ACCEPT_PATHINFO_UNSET 3 + +#define AP_CONTENT_MD5_OFF 0 +#define AP_CONTENT_MD5_ON 1 +#define AP_CONTENT_MD5_UNSET 2 + +APR_HOOK_STRUCT( + APR_HOOK_LINK(get_mgmt_items) + APR_HOOK_LINK(insert_network_bucket) +) + +AP_IMPLEMENT_HOOK_RUN_ALL(int, get_mgmt_items, + (apr_pool_t *p, const char *val, apr_hash_t *ht), + (p, val, ht), OK, DECLINED) + +AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, insert_network_bucket, + (conn_rec *c, apr_bucket_brigade *bb, + apr_socket_t *socket), + (c, bb, socket), AP_DECLINED) + +/* Server core module... This module provides support for really basic + * server operations, including options and commands which control the + * operation of other modules. Consider this the bureaucracy module. + * + * The core module also defines handlers, etc., to handle just enough + * to allow a server with the core module ONLY to actually serve documents. + * + * This file could almost be mod_core.c, except for the stuff which affects + * the http_conf_globals. + */ + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +/* Handles for core filters */ +AP_DECLARE_DATA ap_filter_rec_t *ap_subreq_core_filter_handle; +AP_DECLARE_DATA ap_filter_rec_t *ap_core_output_filter_handle; +AP_DECLARE_DATA ap_filter_rec_t *ap_content_length_filter_handle; +AP_DECLARE_DATA ap_filter_rec_t *ap_core_input_filter_handle; + +/* Provide ap_document_root_check storage and default value = true */ +AP_DECLARE_DATA int ap_document_root_check = 1; + +/* magic pointer for ErrorDocument xxx "default" */ +static char errordocument_default; + +static apr_array_header_t *saved_server_config_defines = NULL; +static apr_table_t *server_config_defined_vars = NULL; + +AP_DECLARE_DATA int ap_main_state = AP_SQ_MS_INITIAL_STARTUP; +AP_DECLARE_DATA int ap_run_mode = AP_SQ_RM_UNKNOWN; +AP_DECLARE_DATA int ap_config_generation = 0; + +static void *create_core_dir_config(apr_pool_t *a, char *dir) +{ + core_dir_config *conf; + + conf = (core_dir_config *)apr_pcalloc(a, sizeof(core_dir_config)); + + /* conf->r and conf->d[_*] are initialized by dirsection() or left NULL */ + + conf->opts = dir ? OPT_UNSET : OPT_UNSET|OPT_SYM_LINKS; + conf->opts_add = conf->opts_remove = OPT_NONE; + conf->override = OR_UNSET|OR_NONE; + conf->override_opts = OPT_UNSET | OPT_ALL | OPT_SYM_OWNER | OPT_MULTI; + + conf->content_md5 = AP_CONTENT_MD5_UNSET; + conf->accept_path_info = AP_ACCEPT_PATHINFO_UNSET; + + conf->use_canonical_name = USE_CANONICAL_NAME_UNSET; + conf->use_canonical_phys_port = USE_CANONICAL_PHYS_PORT_UNSET; + + conf->hostname_lookups = HOSTNAME_LOOKUP_UNSET; + + /* + * left as NULL (we use apr_pcalloc): + * conf->limit_cpu = NULL; + * conf->limit_mem = NULL; + * conf->limit_nproc = NULL; + * conf->sec_file = NULL; + * conf->sec_if = NULL; + */ + + conf->limit_req_body = AP_LIMIT_REQ_BODY_UNSET; + conf->limit_xml_body = AP_LIMIT_UNSET; + + conf->server_signature = srv_sig_unset; + + conf->add_default_charset = ADD_DEFAULT_CHARSET_UNSET; + conf->add_default_charset_name = DEFAULT_ADD_DEFAULT_CHARSET_NAME; + + /* Overriding all negotiation + * Set NULL by apr_pcalloc: + * conf->mime_type = NULL; + * conf->handler = NULL; + * conf->output_filters = NULL; + * conf->input_filters = NULL; + */ + + /* + * Flag for use of inodes in ETags. + */ + conf->etag_bits = ETAG_UNSET; + conf->etag_add = ETAG_UNSET; + conf->etag_remove = ETAG_UNSET; + + conf->enable_mmap = ENABLE_MMAP_UNSET; + conf->enable_sendfile = ENABLE_SENDFILE_UNSET; + conf->allow_encoded_slashes = 0; + conf->decode_encoded_slashes = 0; + + conf->max_ranges = AP_MAXRANGES_UNSET; + conf->max_overlaps = AP_MAXRANGES_UNSET; + conf->max_reversals = AP_MAXRANGES_UNSET; + + conf->cgi_pass_auth = AP_CGI_PASS_AUTH_UNSET; + conf->qualify_redirect_url = AP_CORE_CONFIG_UNSET; + + return (void *)conf; +} + +static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) +{ + core_dir_config *base = (core_dir_config *)basev; + core_dir_config *new = (core_dir_config *)newv; + core_dir_config *conf; + + /* Create this conf by duplicating the base, replacing elements + * (or creating copies for merging) where new-> values exist. + */ + conf = (core_dir_config *)apr_pmemdup(a, base, sizeof(core_dir_config)); + + conf->d = new->d; + conf->d_is_fnmatch = new->d_is_fnmatch; + conf->d_components = new->d_components; + conf->r = new->r; + conf->refs = new->refs; + conf->condition = new->condition; + + if (new->opts & OPT_UNSET) { + /* there was no explicit setting of new->opts, so we merge + * preserve the invariant (opts_add & opts_remove) == 0 + */ + conf->opts_add = (conf->opts_add & ~new->opts_remove) | new->opts_add; + conf->opts_remove = (conf->opts_remove & ~new->opts_add) + | new->opts_remove; + conf->opts = (conf->opts & ~conf->opts_remove) | conf->opts_add; + + /* If Includes was enabled with exec in the base config, but + * was enabled without exec in the new config, then disable + * exec in the merged set. */ + if (((base->opts & (OPT_INCLUDES|OPT_INC_WITH_EXEC)) + == (OPT_INCLUDES|OPT_INC_WITH_EXEC)) + && ((new->opts & (OPT_INCLUDES|OPT_INC_WITH_EXEC)) + == OPT_INCLUDES)) { + conf->opts &= ~OPT_INC_WITH_EXEC; + } + } + else { + /* otherwise we just copy, because an explicit opts setting + * overrides all earlier +/- modifiers + */ + conf->opts = new->opts; + conf->opts_add = new->opts_add; + conf->opts_remove = new->opts_remove; + } + + if (!(new->override & OR_UNSET)) { + conf->override = new->override; + } + + if (!(new->override_opts & OPT_UNSET)) { + conf->override_opts = new->override_opts; + } + + if (new->override_list != NULL) { + conf->override_list = new->override_list; + } + + if (conf->response_code_exprs == NULL) { + conf->response_code_exprs = new->response_code_exprs; + } + else if (new->response_code_exprs != NULL) { + conf->response_code_exprs = apr_hash_overlay(a, + new->response_code_exprs, conf->response_code_exprs); + } + /* Otherwise we simply use the base->response_code_exprs array + */ + + if (new->hostname_lookups != HOSTNAME_LOOKUP_UNSET) { + conf->hostname_lookups = new->hostname_lookups; + } + + if (new->content_md5 != AP_CONTENT_MD5_UNSET) { + conf->content_md5 = new->content_md5; + } + + if (new->accept_path_info != AP_ACCEPT_PATHINFO_UNSET) { + conf->accept_path_info = new->accept_path_info; + } + + if (new->use_canonical_name != USE_CANONICAL_NAME_UNSET) { + conf->use_canonical_name = new->use_canonical_name; + } + + if (new->use_canonical_phys_port != USE_CANONICAL_PHYS_PORT_UNSET) { + conf->use_canonical_phys_port = new->use_canonical_phys_port; + } + +#ifdef RLIMIT_CPU + if (new->limit_cpu) { + conf->limit_cpu = new->limit_cpu; + } +#endif + +#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS) + if (new->limit_mem) { + conf->limit_mem = new->limit_mem; + } +#endif + +#ifdef RLIMIT_NPROC + if (new->limit_nproc) { + conf->limit_nproc = new->limit_nproc; + } +#endif + + if (new->limit_req_body != AP_LIMIT_REQ_BODY_UNSET) { + conf->limit_req_body = new->limit_req_body; + } + + if (new->limit_xml_body != AP_LIMIT_UNSET) + conf->limit_xml_body = new->limit_xml_body; + + if (!conf->sec_file) { + conf->sec_file = new->sec_file; + } + else if (new->sec_file) { + /* If we merge, the merge-result must have its own array + */ + conf->sec_file = apr_array_append(a, base->sec_file, new->sec_file); + } + /* Otherwise we simply use the base->sec_file array + */ + + if (!conf->sec_if) { + conf->sec_if = new->sec_if; + } + else if (new->sec_if) { + /* If we merge, the merge-result must have its own array + */ + conf->sec_if = apr_array_append(a, base->sec_if, new->sec_if); + } + /* Otherwise we simply use the base->sec_if array + */ + + if (new->server_signature != srv_sig_unset) { + conf->server_signature = new->server_signature; + } + + if (new->add_default_charset != ADD_DEFAULT_CHARSET_UNSET) { + conf->add_default_charset = new->add_default_charset; + conf->add_default_charset_name = new->add_default_charset_name; + } + + /* Overriding all negotiation + */ + if (new->mime_type) { + conf->mime_type = new->mime_type; + } + + if (new->handler) { + conf->handler = new->handler; + } + if (new->expr_handler) { + conf->expr_handler = new->expr_handler; + } + + if (new->output_filters) { + conf->output_filters = new->output_filters; + } + + if (new->input_filters) { + conf->input_filters = new->input_filters; + } + + /* + * Now merge the setting of the FileETag directive. + */ + if (new->etag_bits == ETAG_UNSET) { + conf->etag_add = + (conf->etag_add & (~ new->etag_remove)) | new->etag_add; + conf->etag_remove = + (conf->etag_remove & (~ new->etag_add)) | new->etag_remove; + conf->etag_bits = + (conf->etag_bits & (~ conf->etag_remove)) | conf->etag_add; + } + else { + conf->etag_bits = new->etag_bits; + conf->etag_add = new->etag_add; + conf->etag_remove = new->etag_remove; + } + + if (conf->etag_bits != ETAG_NONE) { + conf->etag_bits &= (~ ETAG_NONE); + } + + if (new->enable_mmap != ENABLE_MMAP_UNSET) { + conf->enable_mmap = new->enable_mmap; + } + + if (new->enable_sendfile != ENABLE_SENDFILE_UNSET) { + conf->enable_sendfile = new->enable_sendfile; + } + + conf->allow_encoded_slashes = new->allow_encoded_slashes; + conf->decode_encoded_slashes = new->decode_encoded_slashes; + + if (new->log) { + if (!conf->log) { + conf->log = new->log; + } + else { + conf->log = ap_new_log_config(a, new->log); + ap_merge_log_config(base->log, conf->log); + } + } + + conf->max_ranges = new->max_ranges != AP_MAXRANGES_UNSET ? new->max_ranges : base->max_ranges; + conf->max_overlaps = new->max_overlaps != AP_MAXRANGES_UNSET ? new->max_overlaps : base->max_overlaps; + conf->max_reversals = new->max_reversals != AP_MAXRANGES_UNSET ? new->max_reversals : base->max_reversals; + + conf->cgi_pass_auth = new->cgi_pass_auth != AP_CGI_PASS_AUTH_UNSET ? new->cgi_pass_auth : base->cgi_pass_auth; + + if (new->cgi_var_rules) { + if (!conf->cgi_var_rules) { + conf->cgi_var_rules = new->cgi_var_rules; + } + else { + conf->cgi_var_rules = apr_hash_overlay(a, new->cgi_var_rules, conf->cgi_var_rules); + } + } + + AP_CORE_MERGE_FLAG(qualify_redirect_url, conf, base, new); + + return (void*)conf; +} + +#if APR_HAS_SO_ACCEPTFILTER +#ifndef ACCEPT_FILTER_NAME +#define ACCEPT_FILTER_NAME "httpready" +#ifdef __FreeBSD_version +#if __FreeBSD_version < 411000 /* httpready broken before 4.1.1 */ +#undef ACCEPT_FILTER_NAME +#define ACCEPT_FILTER_NAME "dataready" +#endif +#endif +#endif +#endif + +static void *create_core_server_config(apr_pool_t *a, server_rec *s) +{ + core_server_config *conf; + int is_virtual = s->is_virtual; + + conf = (core_server_config *)apr_pcalloc(a, sizeof(core_server_config)); + + /* global-default / global-only settings */ + + if (!is_virtual) { + conf->ap_document_root = DOCUMENT_LOCATION; + conf->access_name = DEFAULT_ACCESS_FNAME; + + /* A mapping only makes sense in the global context */ + conf->accf_map = apr_table_make(a, 5); +#if APR_HAS_SO_ACCEPTFILTER + apr_table_setn(conf->accf_map, "http", ACCEPT_FILTER_NAME); + apr_table_setn(conf->accf_map, "https", "dataready"); +#elif defined(WIN32) + /* 'data' is disabled on Windows due to a DoS vuln (PR 59970) */ + apr_table_setn(conf->accf_map, "http", "connect"); + apr_table_setn(conf->accf_map, "https", "connect"); +#else + apr_table_setn(conf->accf_map, "http", "data"); + apr_table_setn(conf->accf_map, "https", "data"); +#endif + } + /* pcalloc'ed - we have NULL's/0's + else ** is_virtual ** { + conf->ap_document_root = NULL; + conf->access_name = NULL; + conf->accf_map = NULL; + } + */ + + /* initialization, no special case for global context */ + + conf->sec_dir = apr_array_make(a, 40, sizeof(ap_conf_vector_t *)); + conf->sec_url = apr_array_make(a, 40, sizeof(ap_conf_vector_t *)); + + /* pcalloc'ed - we have NULL's/0's + conf->gprof_dir = NULL; + + ** recursion stopper; 0 == unset + conf->redirect_limit = 0; + conf->subreq_limit = 0; + + conf->protocol = NULL; + */ + + conf->trace_enable = AP_TRACE_UNSET; + + conf->protocols = apr_array_make(a, 5, sizeof(const char *)); + conf->protocols_honor_order = -1; + + return (void *)conf; +} + +static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv) +{ + core_server_config *base = (core_server_config *)basev; + core_server_config *virt = (core_server_config *)virtv; + core_server_config *conf = (core_server_config *) + apr_pmemdup(p, base, sizeof(core_server_config)); + + if (virt->ap_document_root) + conf->ap_document_root = virt->ap_document_root; + + if (virt->access_name) + conf->access_name = virt->access_name; + + /* XXX optimize to keep base->sec_ pointers if virt->sec_ array is empty */ + conf->sec_dir = apr_array_append(p, base->sec_dir, virt->sec_dir); + conf->sec_url = apr_array_append(p, base->sec_url, virt->sec_url); + + if (virt->redirect_limit) + conf->redirect_limit = virt->redirect_limit; + + if (virt->subreq_limit) + conf->subreq_limit = virt->subreq_limit; + + if (virt->trace_enable != AP_TRACE_UNSET) + conf->trace_enable = virt->trace_enable; + + if (virt->http09_enable != AP_HTTP09_UNSET) + conf->http09_enable = virt->http09_enable; + + if (virt->http_conformance != AP_HTTP_CONFORMANCE_UNSET) + conf->http_conformance = virt->http_conformance; + + if (virt->http_methods != AP_HTTP_METHODS_UNSET) + conf->http_methods = virt->http_methods; + + /* no action for virt->accf_map, not allowed per-vhost */ + + if (virt->protocol) + conf->protocol = virt->protocol; + + if (virt->gprof_dir) + conf->gprof_dir = virt->gprof_dir; + + if (virt->error_log_format) + conf->error_log_format = virt->error_log_format; + + if (virt->error_log_conn) + conf->error_log_conn = virt->error_log_conn; + + if (virt->error_log_req) + conf->error_log_req = virt->error_log_req; + + conf->merge_trailers = (virt->merge_trailers != AP_MERGE_TRAILERS_UNSET) + ? virt->merge_trailers + : base->merge_trailers; + + conf->protocols = ((virt->protocols->nelts > 0)? + virt->protocols : base->protocols); + conf->protocols_honor_order = ((virt->protocols_honor_order < 0)? + base->protocols_honor_order : + virt->protocols_honor_order); + + return conf; +} + +/* Add per-directory configuration entry (for <directory> section); + * these are part of the core server config. + */ + +AP_CORE_DECLARE(void) ap_add_per_dir_conf(server_rec *s, void *dir_config) +{ + core_server_config *sconf = ap_get_core_module_config(s->module_config); + void **new_space = (void **)apr_array_push(sconf->sec_dir); + + *new_space = dir_config; +} + +AP_CORE_DECLARE(void) ap_add_per_url_conf(server_rec *s, void *url_config) +{ + core_server_config *sconf = ap_get_core_module_config(s->module_config); + void **new_space = (void **)apr_array_push(sconf->sec_url); + + *new_space = url_config; +} + +AP_CORE_DECLARE(void) ap_add_file_conf(apr_pool_t *p, core_dir_config *conf, + void *url_config) +{ + void **new_space; + + if (!conf->sec_file) + conf->sec_file = apr_array_make(p, 2, sizeof(ap_conf_vector_t *)); + + new_space = (void **)apr_array_push(conf->sec_file); + *new_space = url_config; +} + +AP_CORE_DECLARE(const char *) ap_add_if_conf(apr_pool_t *p, + core_dir_config *conf, + void *if_config) +{ + void **new_space; + core_dir_config *new = ap_get_module_config(if_config, &core_module); + + if (!conf->sec_if) { + conf->sec_if = apr_array_make(p, 2, sizeof(ap_conf_vector_t *)); + } + if (new->condition_ifelse & AP_CONDITION_ELSE) { + int have_if = 0; + if (conf->sec_if->nelts > 0) { + core_dir_config *last; + ap_conf_vector_t *lastelt = APR_ARRAY_IDX(conf->sec_if, + conf->sec_if->nelts - 1, + ap_conf_vector_t *); + last = ap_get_module_config(lastelt, &core_module); + if (last->condition_ifelse & AP_CONDITION_IF) + have_if = 1; + } + if (!have_if) + return "<Else> or <ElseIf> section without previous <If> or " + "<ElseIf> section in same scope"; + } + + new_space = (void **)apr_array_push(conf->sec_if); + *new_space = if_config; + return NULL; +} + + +/* We need to do a stable sort, qsort isn't stable. So to make it stable + * we'll be maintaining the original index into the list, and using it + * as the minor key during sorting. The major key is the number of + * components (where the root component is zero). + */ +struct reorder_sort_rec { + ap_conf_vector_t *elt; + int orig_index; +}; + +static int reorder_sorter(const void *va, const void *vb) +{ + const struct reorder_sort_rec *a = va; + const struct reorder_sort_rec *b = vb; + core_dir_config *core_a; + core_dir_config *core_b; + + core_a = ap_get_core_module_config(a->elt); + core_b = ap_get_core_module_config(b->elt); + + /* a regex always sorts after a non-regex + */ + if (!core_a->r && core_b->r) { + return -1; + } + else if (core_a->r && !core_b->r) { + return 1; + } + + /* we always sort next by the number of components + */ + if (core_a->d_components < core_b->d_components) { + return -1; + } + else if (core_a->d_components > core_b->d_components) { + return 1; + } + + /* They have the same number of components, we now have to compare + * the minor key to maintain the original order (from the config.) + */ + return a->orig_index - b->orig_index; +} + +void ap_core_reorder_directories(apr_pool_t *p, server_rec *s) +{ + core_server_config *sconf; + apr_array_header_t *sec_dir; + struct reorder_sort_rec *sortbin; + int nelts; + ap_conf_vector_t **elts; + int i; + apr_pool_t *tmp; + + sconf = ap_get_core_module_config(s->module_config); + sec_dir = sconf->sec_dir; + nelts = sec_dir->nelts; + elts = (ap_conf_vector_t **)sec_dir->elts; + + if (!nelts) { + /* simple case of already being sorted... */ + /* We're not checking this condition to be fast... we're checking + * it to avoid trying to palloc zero bytes, which can trigger some + * memory debuggers to barf + */ + return; + } + + /* we have to allocate tmp space to do a stable sort */ + apr_pool_create(&tmp, p); + sortbin = apr_palloc(tmp, sec_dir->nelts * sizeof(*sortbin)); + for (i = 0; i < nelts; ++i) { + sortbin[i].orig_index = i; + sortbin[i].elt = elts[i]; + } + + qsort(sortbin, nelts, sizeof(*sortbin), reorder_sorter); + + /* and now copy back to the original array */ + for (i = 0; i < nelts; ++i) { + elts[i] = sortbin[i].elt; + } + + apr_pool_destroy(tmp); +} + +/***************************************************************** + * + * There are some elements of the core config structures in which + * other modules have a legitimate interest (this is ugly, but necessary + * to preserve NCSA back-compatibility). So, we have a bunch of accessors + * here... + */ + +AP_DECLARE(int) ap_allow_options(request_rec *r) +{ + core_dir_config *conf = + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + + return conf->opts; +} + +AP_DECLARE(int) ap_allow_overrides(request_rec *r) +{ + core_dir_config *conf; + conf = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + + return conf->override; +} + +/* + * Optional function coming from mod_authn_core, used for + * retrieving the type of autorization + */ +static APR_OPTIONAL_FN_TYPE(authn_ap_auth_type) *authn_ap_auth_type; + +AP_DECLARE(const char *) ap_auth_type(request_rec *r) +{ + if (authn_ap_auth_type) { + return authn_ap_auth_type(r); + } + return NULL; +} + +/* + * Optional function coming from mod_authn_core, used for + * retrieving the authorization realm + */ +static APR_OPTIONAL_FN_TYPE(authn_ap_auth_name) *authn_ap_auth_name; + +AP_DECLARE(const char *) ap_auth_name(request_rec *r) +{ + if (authn_ap_auth_name) { + return authn_ap_auth_name(r); + } + return NULL; +} + +/* + * Optional function coming from mod_access_compat, used to determine how + access control interacts with authentication/authorization + */ +static APR_OPTIONAL_FN_TYPE(access_compat_ap_satisfies) *access_compat_ap_satisfies; + +AP_DECLARE(int) ap_satisfies(request_rec *r) +{ + if (access_compat_ap_satisfies) { + return access_compat_ap_satisfies(r); + } + return SATISFY_NOSPEC; +} + +AP_DECLARE(const char *) ap_document_root(request_rec *r) /* Don't use this! */ +{ + core_server_config *sconf; + core_request_config *rconf = ap_get_core_module_config(r->request_config); + if (rconf->document_root) + return rconf->document_root; + sconf = ap_get_core_module_config(r->server->module_config); + return sconf->ap_document_root; +} + +AP_DECLARE(const char *) ap_context_prefix(request_rec *r) +{ + core_request_config *conf = ap_get_core_module_config(r->request_config); + if (conf->context_prefix) + return conf->context_prefix; + else + return ""; +} + +AP_DECLARE(const char *) ap_context_document_root(request_rec *r) +{ + core_request_config *conf = ap_get_core_module_config(r->request_config); + if (conf->context_document_root) + return conf->context_document_root; + else + return ap_document_root(r); +} + +AP_DECLARE(void) ap_set_document_root(request_rec *r, const char *document_root) +{ + core_request_config *conf = ap_get_core_module_config(r->request_config); + conf->document_root = document_root; +} + +AP_DECLARE(void) ap_set_context_info(request_rec *r, const char *context_prefix, + const char *context_document_root) +{ + core_request_config *conf = ap_get_core_module_config(r->request_config); + if (context_prefix) + conf->context_prefix = context_prefix; + if (context_document_root) + conf->context_document_root = context_document_root; +} + +/* Should probably just get rid of this... the only code that cares is + * part of the core anyway (and in fact, it isn't publicised to other + * modules). + */ + +char *ap_response_code_string(request_rec *r, int error_index) +{ + core_dir_config *dirconf; + core_request_config *reqconf = ap_get_core_module_config(r->request_config); + const char *err; + const char *response; + ap_expr_info_t *expr; + + /* check for string registered via ap_custom_response() first */ + if (reqconf->response_code_strings != NULL + && reqconf->response_code_strings[error_index] != NULL) { + return reqconf->response_code_strings[error_index]; + } + + /* check for string specified via ErrorDocument */ + dirconf = ap_get_core_module_config(r->per_dir_config); + + if (!dirconf->response_code_exprs) { + return NULL; + } + + expr = apr_hash_get(dirconf->response_code_exprs, &error_index, + sizeof(error_index)); + if (!expr) { + return NULL; + } + + /* special token to indicate revert back to default */ + if ((char *) expr == &errordocument_default) { + return NULL; + } + + err = NULL; + response = ap_expr_str_exec(r, expr, &err); + if (err) { + ap_log_rerror( + APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02841) "core: ErrorDocument: can't " + "evaluate require expression: %s", err); + return NULL; + } + + /* alas, duplication required as we return not-const */ + return apr_pstrdup(r->pool, response); +} + + +/* Code from Harald Hanche-Olsen <hanche@imf.unit.no> */ +static APR_INLINE int do_double_reverse (int double_reverse, + const char *remote_host, + apr_sockaddr_t *client_addr, + apr_pool_t *pool) +{ + apr_sockaddr_t *sa; + apr_status_t rv; + + if (double_reverse) { + /* already done */ + return double_reverse; + } + + if (remote_host == NULL || remote_host[0] == '\0') { + /* single reverse failed, so don't bother */ + return -1; + } + + rv = apr_sockaddr_info_get(&sa, remote_host, APR_UNSPEC, 0, 0, pool); + if (rv == APR_SUCCESS) { + while (sa) { + if (apr_sockaddr_equal(sa, client_addr)) { + return 1; + } + + sa = sa->next; + } + } + + return -1; +} + +AP_DECLARE(const char *) ap_get_remote_host(conn_rec *conn, void *dir_config, + int type, int *str_is_ip) +{ + int hostname_lookups; + int ignored_str_is_ip; + + if (!str_is_ip) { /* caller doesn't want to know */ + str_is_ip = &ignored_str_is_ip; + } + *str_is_ip = 0; + + /* If we haven't checked the host name, and we want to */ + if (dir_config) { + hostname_lookups = ((core_dir_config *)ap_get_core_module_config(dir_config)) + ->hostname_lookups; + + if (hostname_lookups == HOSTNAME_LOOKUP_UNSET) { + hostname_lookups = HOSTNAME_LOOKUP_OFF; + } + } + else { + /* the default */ + hostname_lookups = HOSTNAME_LOOKUP_OFF; + } + + if (type != REMOTE_NOLOOKUP + && conn->remote_host == NULL + && (type == REMOTE_DOUBLE_REV + || hostname_lookups != HOSTNAME_LOOKUP_OFF)) { + + if (apr_getnameinfo(&conn->remote_host, conn->client_addr, 0) + == APR_SUCCESS) { + ap_str_tolower(conn->remote_host); + + if (hostname_lookups == HOSTNAME_LOOKUP_DOUBLE) { + conn->double_reverse = do_double_reverse(conn->double_reverse, + conn->remote_host, + conn->client_addr, + conn->pool); + if (conn->double_reverse != 1) { + conn->remote_host = NULL; + } + } + } + + /* if failed, set it to the NULL string to indicate error */ + if (conn->remote_host == NULL) { + conn->remote_host = ""; + } + } + + if (type == REMOTE_DOUBLE_REV) { + conn->double_reverse = do_double_reverse(conn->double_reverse, + conn->remote_host, + conn->client_addr, conn->pool); + if (conn->double_reverse == -1) { + return NULL; + } + } + + /* + * Return the desired information; either the remote DNS name, if found, + * or either NULL (if the hostname was requested) or the IP address + * (if any identifier was requested). + */ + if (conn->remote_host != NULL && conn->remote_host[0] != '\0') { + return conn->remote_host; + } + else { + if (type == REMOTE_HOST || type == REMOTE_DOUBLE_REV) { + return NULL; + } + else { + *str_is_ip = 1; + return conn->client_ip; + } + } +} + +AP_DECLARE(const char *) ap_get_useragent_host(request_rec *r, + int type, int *str_is_ip) +{ + conn_rec *conn = r->connection; + int hostname_lookups; + int ignored_str_is_ip; + + /* Guard here when examining the host before the read_request hook + * has populated an r->useragent_addr + */ + if (!r->useragent_addr || (r->useragent_addr == conn->client_addr)) { + return ap_get_remote_host(conn, r->per_dir_config, type, str_is_ip); + } + + if (!str_is_ip) { /* caller doesn't want to know */ + str_is_ip = &ignored_str_is_ip; + } + *str_is_ip = 0; + + hostname_lookups = ((core_dir_config *) + ap_get_core_module_config(r->per_dir_config)) + ->hostname_lookups; + if (hostname_lookups == HOSTNAME_LOOKUP_UNSET) { + hostname_lookups = HOSTNAME_LOOKUP_OFF; + } + + if (type != REMOTE_NOLOOKUP + && r->useragent_host == NULL + && (type == REMOTE_DOUBLE_REV + || hostname_lookups != HOSTNAME_LOOKUP_OFF)) { + + if (apr_getnameinfo(&r->useragent_host, r->useragent_addr, 0) + == APR_SUCCESS) { + ap_str_tolower(r->useragent_host); + + if (hostname_lookups == HOSTNAME_LOOKUP_DOUBLE) { + r->double_reverse = do_double_reverse(r->double_reverse, + r->useragent_host, + r->useragent_addr, + r->pool); + if (r->double_reverse != 1) { + r->useragent_host = NULL; + } + } + } + + /* if failed, set it to the NULL string to indicate error */ + if (r->useragent_host == NULL) { + r->useragent_host = ""; + } + } + + if (type == REMOTE_DOUBLE_REV) { + r->double_reverse = do_double_reverse(r->double_reverse, + r->useragent_host, + r->useragent_addr, r->pool); + if (r->double_reverse == -1) { + return NULL; + } + } + + /* + * Return the desired information; either the remote DNS name, if found, + * or either NULL (if the hostname was requested) or the IP address + * (if any identifier was requested). + */ + if (r->useragent_host != NULL && r->useragent_host[0] != '\0') { + return r->useragent_host; + } + else { + if (type == REMOTE_HOST || type == REMOTE_DOUBLE_REV) { + return NULL; + } + else { + *str_is_ip = 1; + return r->useragent_ip; + } + } +} + +/* + * Optional function coming from mod_ident, used for looking up ident user + */ +static APR_OPTIONAL_FN_TYPE(ap_ident_lookup) *ident_lookup; + +AP_DECLARE(const char *) ap_get_remote_logname(request_rec *r) +{ + if (r->connection->remote_logname != NULL) { + return r->connection->remote_logname; + } + + if (ident_lookup) { + return ident_lookup(r); + } + + return NULL; +} + +/* There are two options regarding what the "name" of a server is. The + * "canonical" name as defined by ServerName and Port, or the "client's + * name" as supplied by a possible Host: header or full URI. + * + * The DNS option to UseCanonicalName causes this routine to do a + * reverse lookup on the local IP address of the connection and use + * that for the ServerName. This makes its value more reliable while + * at the same time allowing Demon's magic virtual hosting to work. + * The assumption is that DNS lookups are sufficiently quick... + * -- fanf 1998-10-03 + */ +AP_DECLARE(const char *) ap_get_server_name(request_rec *r) +{ + conn_rec *conn = r->connection; + core_dir_config *d; + const char *retval; + + d = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + + switch (d->use_canonical_name) { + case USE_CANONICAL_NAME_ON: + retval = r->server->server_hostname; + break; + case USE_CANONICAL_NAME_DNS: + if (conn->local_host == NULL) { + if (apr_getnameinfo(&conn->local_host, + conn->local_addr, 0) != APR_SUCCESS) + conn->local_host = apr_pstrdup(conn->pool, + r->server->server_hostname); + else { + ap_str_tolower(conn->local_host); + } + } + retval = conn->local_host; + break; + case USE_CANONICAL_NAME_OFF: + case USE_CANONICAL_NAME_UNSET: + retval = r->hostname ? r->hostname : r->server->server_hostname; + break; + default: + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00109) + "ap_get_server_name: Invalid UCN Option somehow"); + retval = "localhost"; + break; + } + return retval; +} + +/* + * Get the current server name from the request for the purposes + * of using in a URL. If the server name is an IPv6 literal + * address, it will be returned in URL format (e.g., "[fe80::1]"). + */ +AP_DECLARE(const char *) ap_get_server_name_for_url(request_rec *r) +{ + const char *plain_server_name = ap_get_server_name(r); + +#if APR_HAVE_IPV6 + if (ap_strchr_c(plain_server_name, ':')) { /* IPv6 literal? */ + return apr_pstrcat(r->pool, "[", plain_server_name, "]", NULL); + } +#endif + return plain_server_name; +} + +AP_DECLARE(apr_port_t) ap_get_server_port(const request_rec *r) +{ + apr_port_t port; + core_dir_config *d = + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + + switch (d->use_canonical_name) { + case USE_CANONICAL_NAME_OFF: + case USE_CANONICAL_NAME_DNS: + case USE_CANONICAL_NAME_UNSET: + if (d->use_canonical_phys_port == USE_CANONICAL_PHYS_PORT_ON) + port = r->parsed_uri.port_str ? r->parsed_uri.port : + r->connection->local_addr->port ? r->connection->local_addr->port : + r->server->port ? r->server->port : + ap_default_port(r); + else /* USE_CANONICAL_PHYS_PORT_OFF or USE_CANONICAL_PHYS_PORT_UNSET */ + port = r->parsed_uri.port_str ? r->parsed_uri.port : + r->server->port ? r->server->port : + ap_default_port(r); + break; + case USE_CANONICAL_NAME_ON: + /* With UseCanonicalName on (and in all versions prior to 1.3) + * Apache will use the hostname and port specified in the + * ServerName directive to construct a canonical name for the + * server. (If no port was specified in the ServerName + * directive, Apache uses the port supplied by the client if + * any is supplied, and finally the default port for the protocol + * used. + */ + if (d->use_canonical_phys_port == USE_CANONICAL_PHYS_PORT_ON) + port = r->server->port ? r->server->port : + r->connection->local_addr->port ? r->connection->local_addr->port : + ap_default_port(r); + else /* USE_CANONICAL_PHYS_PORT_OFF or USE_CANONICAL_PHYS_PORT_UNSET */ + port = r->server->port ? r->server->port : + ap_default_port(r); + break; + default: + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00110) + "ap_get_server_port: Invalid UCN Option somehow"); + port = ap_default_port(r); + break; + } + + return port; +} + +AP_DECLARE(char *) ap_construct_url(apr_pool_t *p, const char *uri, + request_rec *r) +{ + unsigned port = ap_get_server_port(r); + const char *host = ap_get_server_name_for_url(r); + + if (ap_is_default_port(port, r)) { + return apr_pstrcat(p, ap_http_scheme(r), "://", host, uri, NULL); + } + + return apr_psprintf(p, "%s://%s:%u%s", ap_http_scheme(r), host, port, uri); +} + +AP_DECLARE(apr_off_t) ap_get_limit_req_body(const request_rec *r) +{ + core_dir_config *d = + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + + if (d->limit_req_body == AP_LIMIT_REQ_BODY_UNSET) { + return AP_DEFAULT_LIMIT_REQ_BODY; + } + + return d->limit_req_body; +} + + +/***************************************************************** + * + * Commands... this module handles almost all of the NCSA httpd.conf + * commands, but most of the old srm.conf is in the modules. + */ + + +/* returns a parent if it matches the given directive */ +static const ap_directive_t * find_parent(const ap_directive_t *dirp, + const char *what) +{ + while (dirp->parent != NULL) { + dirp = dirp->parent; + + /* ### it would be nice to have atom-ized directives */ + if (strcasecmp(dirp->directive, what) == 0) + return dirp; + } + + return NULL; +} + +AP_DECLARE(const char *) ap_check_cmd_context(cmd_parms *cmd, + unsigned forbidden) +{ + const char *gt = (cmd->cmd->name[0] == '<' + && cmd->cmd->name[strlen(cmd->cmd->name)-1] != '>') + ? ">" : ""; + const ap_directive_t *found; + + if ((forbidden & NOT_IN_VIRTUALHOST) && cmd->server->is_virtual) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, + " cannot occur within <VirtualHost> section", NULL); + } + + if ((forbidden & NOT_IN_DIR_CONTEXT) && cmd->limited != -1) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, + " cannot occur within <Limit> or <LimitExcept> " + "section", NULL); + } + + if ((forbidden & NOT_IN_HTACCESS) && (cmd->pool == cmd->temp_pool)) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, + " cannot occur within htaccess files", NULL); + } + + if ((forbidden & NOT_IN_DIR_LOC_FILE) == NOT_IN_DIR_LOC_FILE) { + if (cmd->path != NULL) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, + " cannot occur within directory context", NULL); + } + if (cmd->cmd->req_override & EXEC_ON_READ) { + /* EXEC_ON_READ must be NOT_IN_DIR_LOC_FILE, if not, it will + * (deliberately) segfault below in the individual tests... + */ + return NULL; + } + } + + if (((forbidden & NOT_IN_DIRECTORY) + && ((found = find_parent(cmd->directive, "<Directory")) + || (found = find_parent(cmd->directive, "<DirectoryMatch")))) + || ((forbidden & NOT_IN_LOCATION) + && ((found = find_parent(cmd->directive, "<Location")) + || (found = find_parent(cmd->directive, "<LocationMatch")))) + || ((forbidden & NOT_IN_FILES) + && ((found = find_parent(cmd->directive, "<Files")) + || (found = find_parent(cmd->directive, "<FilesMatch")) + || (found = find_parent(cmd->directive, "<If")) + || (found = find_parent(cmd->directive, "<ElseIf")) + || (found = find_parent(cmd->directive, "<Else")))) + || ((forbidden & NOT_IN_PROXY) + && ((found = find_parent(cmd->directive, "<Proxy")) + || (found = find_parent(cmd->directive, "<ProxyMatch"))))) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, + " cannot occur within ", found->directive, + "> section", NULL); + } + + return NULL; +} + +static const char *set_access_name(cmd_parms *cmd, void *dummy, + const char *arg) +{ + void *sconf = cmd->server->module_config; + core_server_config *conf = ap_get_core_module_config(sconf); + + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + if (err != NULL) { + return err; + } + + conf->access_name = apr_pstrdup(cmd->pool, arg); + return NULL; +} + +AP_DECLARE(const char *) ap_resolve_env(apr_pool_t *p, const char * word) +{ +# define SMALL_EXPANSION 5 + struct sll { + struct sll *next; + const char *string; + apr_size_t len; + } *result, *current, sresult[SMALL_EXPANSION]; + char *res_buf, *cp; + const char *s, *e, *ep; + unsigned spc; + apr_size_t outlen; + + s = ap_strchr_c(word, '$'); + if (!s) { + return word; + } + + /* well, actually something to do */ + ep = word + strlen(word); + spc = 0; + result = current = &(sresult[spc++]); + current->next = NULL; + current->string = word; + current->len = s - word; + outlen = current->len; + + do { + /* prepare next entry */ + if (current->len) { + current->next = (spc < SMALL_EXPANSION) + ? &(sresult[spc++]) + : (struct sll *)apr_palloc(p, + sizeof(*current->next)); + current = current->next; + current->next = NULL; + current->len = 0; + } + + if (*s == '$') { + if (s[1] == '{' && (e = ap_strchr_c(s+2, '}'))) { + char *name = apr_pstrmemdup(p, s+2, e-s-2); + word = NULL; + if (server_config_defined_vars) + word = apr_table_get(server_config_defined_vars, name); + if (!word) + word = apr_pstrdup(p, getenv(name)); + if (word) { + current->string = word; + current->len = strlen(word); + outlen += current->len; + } + else { + if (ap_strchr(name, ':') == 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(00111) + "Config variable ${%s} is not defined", + name); + current->string = s; + current->len = e - s + 1; + outlen += current->len; + } + s = e + 1; + } + else { + current->string = s++; + current->len = 1; + ++outlen; + } + } + else { + word = s; + s = ap_strchr_c(s, '$'); + current->string = word; + current->len = s ? s - word : ep - word; + outlen += current->len; + } + } while (s && *s); + + /* assemble result */ + res_buf = cp = apr_palloc(p, outlen + 1); + do { + if (result->len) { + memcpy(cp, result->string, result->len); + cp += result->len; + } + result = result->next; + } while (result); + res_buf[outlen] = '\0'; + + return res_buf; +} + +static int reset_config_defines(void *dummy) +{ + ap_server_config_defines = saved_server_config_defines; + saved_server_config_defines = NULL; + server_config_defined_vars = NULL; + return OK; +} + +/* + * Make sure we can revert the effects of Define/UnDefine when restarting. + * This function must be called once per loading of the config, before + * ap_server_config_defines is changed. This may be during reading of the + * config, which is even before the pre_config hook is run (due to + * EXEC_ON_READ for Define/UnDefine). + */ +static void init_config_defines(apr_pool_t *pconf) +{ + saved_server_config_defines = ap_server_config_defines; + /* Use apr_array_copy instead of apr_array_copy_hdr because it does not + * protect from the way unset_define removes entries. + */ + ap_server_config_defines = apr_array_copy(pconf, ap_server_config_defines); +} + +static const char *set_define(cmd_parms *cmd, void *dummy, + const char *name, const char *value) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS); + if (err) + return err; + if (ap_strchr_c(name, ':') != NULL) { + return "Variable name must not contain ':'"; + } + + if (!saved_server_config_defines) { + init_config_defines(cmd->pool); + } + if (!ap_exists_config_define(name)) { + *(const char **)apr_array_push(ap_server_config_defines) = name; + } + if (value) { + if (!server_config_defined_vars) { + server_config_defined_vars = apr_table_make(cmd->pool, 5); + } + apr_table_setn(server_config_defined_vars, name, value); + } + + return NULL; +} + +static const char *unset_define(cmd_parms *cmd, void *dummy, + const char *name) +{ + int i; + const char **defines; + const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS); + if (err) + return err; + if (ap_strchr_c(name, ':') != NULL) { + return "Variable name must not contain ':'"; + } + + if (!saved_server_config_defines) { + init_config_defines(cmd->pool); + } + + defines = (const char **)ap_server_config_defines->elts; + for (i = 0; i < ap_server_config_defines->nelts; i++) { + if (strcmp(defines[i], name) == 0) { + defines[i] = *(const char **)apr_array_pop(ap_server_config_defines); + break; + } + } + + if (server_config_defined_vars) { + apr_table_unset(server_config_defined_vars, name); + } + + return NULL; +} + +static const char *generate_error(cmd_parms *cmd, void *dummy, + const char *arg) +{ + if (!arg || !*arg) { + return "The Error directive was used with no message."; + } + + if (*arg == '"' || *arg == '\'') { /* strip off quotes */ + apr_size_t len = strlen(arg); + char last = *(arg + len - 1); + + if (*arg == last) { + return apr_pstrndup(cmd->pool, arg + 1, len - 2); + } + } + + return arg; +} + +#ifdef GPROF +static const char *set_gprof_dir(cmd_parms *cmd, void *dummy, const char *arg) +{ + void *sconf = cmd->server->module_config; + core_server_config *conf = ap_get_core_module_config(sconf); + + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + if (err != NULL) { + return err; + } + + conf->gprof_dir = arg; + return NULL; +} +#endif /*GPROF*/ + +static const char *set_add_default_charset(cmd_parms *cmd, + void *d_, const char *arg) +{ + core_dir_config *d = d_; + + if (!strcasecmp(arg, "Off")) { + d->add_default_charset = ADD_DEFAULT_CHARSET_OFF; + } + else if (!strcasecmp(arg, "On")) { + d->add_default_charset = ADD_DEFAULT_CHARSET_ON; + d->add_default_charset_name = DEFAULT_ADD_DEFAULT_CHARSET_NAME; + } + else { + d->add_default_charset = ADD_DEFAULT_CHARSET_ON; + d->add_default_charset_name = arg; + } + + return NULL; +} + +static const char *set_document_root(cmd_parms *cmd, void *dummy, + const char *arg) +{ + void *sconf = cmd->server->module_config; + core_server_config *conf = ap_get_core_module_config(sconf); + + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + if (err != NULL) { + return err; + } + + /* When ap_document_root_check is false; skip all the stuff below */ + if (!ap_document_root_check) { + conf->ap_document_root = arg; + return NULL; + } + + /* Make it absolute, relative to ServerRoot */ + arg = ap_server_root_relative(cmd->pool, arg); + if (arg == NULL) { + return "DocumentRoot must be a directory"; + } + + /* TODO: ap_configtestonly */ + if (apr_filepath_merge((char**)&conf->ap_document_root, NULL, arg, + APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS + || !ap_is_directory(cmd->temp_pool, arg)) { + if (cmd->server->is_virtual) { + ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0, + cmd->pool, APLOGNO(00112) + "Warning: DocumentRoot [%s] does not exist", + arg); + conf->ap_document_root = arg; + } + else { + return apr_psprintf(cmd->pool, + "DocumentRoot '%s' is not a directory, or is not readable", + arg); + } + } + return NULL; +} + +AP_DECLARE(void) ap_custom_response(request_rec *r, int status, + const char *string) +{ + core_request_config *conf = ap_get_core_module_config(r->request_config); + int idx; + + if (conf->response_code_strings == NULL) { + conf->response_code_strings = + apr_pcalloc(r->pool, + sizeof(*conf->response_code_strings) * RESPONSE_CODES); + } + + idx = ap_index_of_response(status); + + conf->response_code_strings[idx] = + ((ap_is_url(string) || (*string == '/')) && (*string != '"')) ? + apr_pstrdup(r->pool, string) : apr_pstrcat(r->pool, "\"", string, NULL); +} + +static const char *set_error_document(cmd_parms *cmd, void *conf_, + const char *errno_str, const char *msg) +{ + core_dir_config *conf = conf_; + int error_number, index_number, idx500; + enum { MSG, LOCAL_PATH, REMOTE_PATH } what = MSG; + + /* 1st parameter should be a 3 digit number, which we recognize; + * convert it into an array index + */ + error_number = atoi(errno_str); + idx500 = ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR); + + if (error_number == HTTP_INTERNAL_SERVER_ERROR) { + index_number = idx500; + } + else if ((index_number = ap_index_of_response(error_number)) == idx500) { + return apr_pstrcat(cmd->pool, "Unsupported HTTP response code ", + errno_str, NULL); + } + + /* Heuristic to determine second argument. */ + if (ap_strchr_c(msg,' ')) + what = MSG; + else if (msg[0] == '/') + what = LOCAL_PATH; + else if (ap_is_url(msg)) + what = REMOTE_PATH; + else + what = MSG; + + /* The entry should be ignored if it is a full URL for a 401 error */ + + if (error_number == 401 && what == REMOTE_PATH) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server, APLOGNO(00113) + "%s:%d cannot use a full URL in a 401 ErrorDocument " + "directive --- ignoring!", cmd->directive->filename, cmd->directive->line_num); + } + else { /* Store it... */ + if (conf->response_code_exprs == NULL) { + conf->response_code_exprs = apr_hash_make(cmd->pool); + } + + if (strcasecmp(msg, "default") == 0) { + /* special case: ErrorDocument 404 default restores the + * canned server error response + */ + apr_hash_set(conf->response_code_exprs, + apr_pmemdup(cmd->pool, &index_number, sizeof(index_number)), + sizeof(index_number), &errordocument_default); + } + else { + ap_expr_info_t *expr; + const char *expr_err = NULL; + + /* hack. Prefix a " if it is a msg; as that is what + * http_protocol.c relies on to distinguish between + * a msg and a (local) path. + */ + const char *response = + (what == MSG) ? apr_pstrcat(cmd->pool, "\"", msg, NULL) : + apr_pstrdup(cmd->pool, msg); + + expr = ap_expr_parse_cmd(cmd, response, AP_EXPR_FLAG_STRING_RESULT, + &expr_err, NULL); + + if (expr_err) { + return apr_pstrcat(cmd->temp_pool, + "Cannot parse expression in ErrorDocument: ", + expr_err, NULL); + } + + apr_hash_set(conf->response_code_exprs, + apr_pmemdup(cmd->pool, &index_number, sizeof(index_number)), + sizeof(index_number), expr); + + } + } + + return NULL; +} + +static const char *set_allow_opts(cmd_parms *cmd, allow_options_t *opts, + const char *l) +{ + allow_options_t opt; + int first = 1; + + char *w, *p = (char *) l; + char *tok_state; + + while ((w = apr_strtok(p, ",", &tok_state)) != NULL) { + + if (first) { + p = NULL; + *opts = OPT_NONE; + first = 0; + } + + if (!strcasecmp(w, "Indexes")) { + opt = OPT_INDEXES; + } + else if (!strcasecmp(w, "Includes")) { + /* If Includes is permitted, both Includes and + * IncludesNOEXEC may be changed. */ + opt = (OPT_INCLUDES | OPT_INC_WITH_EXEC); + } + else if (!strcasecmp(w, "IncludesNOEXEC")) { + opt = OPT_INCLUDES; + } + else if (!strcasecmp(w, "FollowSymLinks")) { + opt = OPT_SYM_LINKS; + } + else if (!strcasecmp(w, "SymLinksIfOwnerMatch")) { + opt = OPT_SYM_OWNER; + } + else if (!strcasecmp(w, "ExecCGI")) { + opt = OPT_EXECCGI; + } + else if (!strcasecmp(w, "MultiViews")) { + opt = OPT_MULTI; + } + else if (!strcasecmp(w, "RunScripts")) { /* AI backcompat. Yuck */ + opt = OPT_MULTI|OPT_EXECCGI; + } + else if (!strcasecmp(w, "None")) { + opt = OPT_NONE; + } + else if (!strcasecmp(w, "All")) { + opt = OPT_ALL; + } + else { + return apr_pstrcat(cmd->pool, "Illegal option ", w, NULL); + } + + *opts |= opt; + } + + (*opts) &= (~OPT_UNSET); + + return NULL; +} + +static const char *set_override(cmd_parms *cmd, void *d_, const char *l) +{ + core_dir_config *d = d_; + char *w; + char *k, *v; + const char *err; + + /* Throw a warning if we're in <Location> or <Files> */ + if (ap_check_cmd_context(cmd, NOT_IN_LOCATION | NOT_IN_FILES)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00114) + "Useless use of AllowOverride in line %d of %s.", + cmd->directive->line_num, cmd->directive->filename); + } + if ((err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS)) != NULL) + return err; + + d->override = OR_NONE; + while (l[0]) { + w = ap_getword_conf(cmd->temp_pool, &l); + + k = w; + v = strchr(k, '='); + if (v) { + *v++ = '\0'; + } + + if (!strcasecmp(w, "Limit")) { + d->override |= OR_LIMIT; + } + else if (!strcasecmp(k, "Options")) { + d->override |= OR_OPTIONS; + if (v) + set_allow_opts(cmd, &(d->override_opts), v); + else + d->override_opts = OPT_ALL; + } + else if (!strcasecmp(w, "FileInfo")) { + d->override |= OR_FILEINFO; + } + else if (!strcasecmp(w, "AuthConfig")) { + d->override |= OR_AUTHCFG; + } + else if (!strcasecmp(w, "Indexes")) { + d->override |= OR_INDEXES; + } + else if (!strcasecmp(w, "Nonfatal")) { + if (!strcasecmp(v, "Override")) { + d->override |= NONFATAL_OVERRIDE; + } + else if (!strcasecmp(v, "Unknown")) { + d->override |= NONFATAL_UNKNOWN; + } + else if (!strcasecmp(v, "All")) { + d->override |= NONFATAL_ALL; + } + } + else if (!strcasecmp(w, "None")) { + d->override = OR_NONE; + } + else if (!strcasecmp(w, "All")) { + d->override = OR_ALL; + } + else { + return apr_pstrcat(cmd->pool, "Illegal override option ", w, NULL); + } + + d->override &= ~OR_UNSET; + } + + return NULL; +} + +static const char *set_cgi_pass_auth(cmd_parms *cmd, void *d_, int flag) +{ + core_dir_config *d = d_; + + d->cgi_pass_auth = flag ? AP_CGI_PASS_AUTH_ON : AP_CGI_PASS_AUTH_OFF; + + return NULL; +} + +static const char *set_cgi_var(cmd_parms *cmd, void *d_, + const char *var, const char *rule_) +{ + core_dir_config *d = d_; + char *rule = apr_pstrdup(cmd->pool, rule_); + + ap_str_tolower(rule); + + if (!strcmp(var, "REQUEST_URI")) { + if (strcmp(rule, "current-uri") && strcmp(rule, "original-uri")) { + return "Valid rules for REQUEST_URI are 'current-uri' and 'original-uri'"; + } + } + else { + return apr_pstrcat(cmd->pool, "Unrecognized CGI variable: \"", + var, "\"", NULL); + } + + if (!d->cgi_var_rules) { + d->cgi_var_rules = apr_hash_make(cmd->pool); + } + apr_hash_set(d->cgi_var_rules, var, APR_HASH_KEY_STRING, rule); + return NULL; +} + +static const char *set_qualify_redirect_url(cmd_parms *cmd, void *d_, int flag) +{ + core_dir_config *d = d_; + + d->qualify_redirect_url = flag ? AP_CORE_CONFIG_ON : AP_CORE_CONFIG_OFF; + + return NULL; +} + +static const char *set_override_list(cmd_parms *cmd, void *d_, int argc, char *const argv[]) +{ + core_dir_config *d = d_; + int i; + const char *err; + + /* Throw a warning if we're in <Location> or <Files> */ + if (ap_check_cmd_context(cmd, NOT_IN_LOCATION | NOT_IN_FILES)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00115) + "Useless use of AllowOverrideList at %s:%d", + cmd->directive->filename, cmd->directive->line_num); + } + if ((err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS)) != NULL) + return err; + + d->override_list = apr_table_make(cmd->pool, argc); + + for (i = 0; i < argc; i++) { + if (!strcasecmp(argv[i], "None")) { + if (argc != 1) { + return "'None' not allowed with other directives in " + "AllowOverrideList"; + } + return NULL; + } + else { + const command_rec *result = NULL; + module *mod = ap_top_module; + + result = ap_find_command_in_modules(argv[i], &mod); + if (result == NULL) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, + APLOGNO(00116) "Discarding unrecognized " + "directive `%s' in AllowOverrideList at %s:%d", + argv[i], cmd->directive->filename, + cmd->directive->line_num); + continue; + } + else if ((result->req_override & (OR_ALL|ACCESS_CONF)) == 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, + APLOGNO(02304) "Discarding directive `%s' not " + "allowed in AllowOverrideList at %s:%d", + argv[i], cmd->directive->filename, + cmd->directive->line_num); + continue; + } + else { + apr_table_setn(d->override_list, argv[i], "1"); + } + } + } + + return NULL; +} + +static const char *set_options(cmd_parms *cmd, void *d_, const char *l) +{ + core_dir_config *d = d_; + allow_options_t opt; + int first = 1; + int merge = 0; + int all_none = 0; + char action; + + while (l[0]) { + char *w = ap_getword_conf(cmd->temp_pool, &l); + action = '\0'; + + if (*w == '+' || *w == '-') { + action = *(w++); + if (!merge && !first && !all_none) { + return "Either all Options must start with + or -, or no Option may."; + } + merge = 1; + } + else if (first) { + d->opts = OPT_NONE; + } + else if (merge) { + return "Either all Options must start with + or -, or no Option may."; + } + + if (!strcasecmp(w, "Indexes")) { + opt = OPT_INDEXES; + } + else if (!strcasecmp(w, "Includes")) { + opt = (OPT_INCLUDES | OPT_INC_WITH_EXEC); + } + else if (!strcasecmp(w, "IncludesNOEXEC")) { + opt = OPT_INCLUDES; + } + else if (!strcasecmp(w, "FollowSymLinks")) { + opt = OPT_SYM_LINKS; + } + else if (!strcasecmp(w, "SymLinksIfOwnerMatch")) { + opt = OPT_SYM_OWNER; + } + else if (!strcasecmp(w, "ExecCGI")) { + opt = OPT_EXECCGI; + } + else if (!strcasecmp(w, "MultiViews")) { + opt = OPT_MULTI; + } + else if (!strcasecmp(w, "RunScripts")) { /* AI backcompat. Yuck */ + opt = OPT_MULTI|OPT_EXECCGI; + } + else if (!strcasecmp(w, "None")) { + if (!first) { + return "'Options None' must be the first Option given."; + } + else if (merge) { /* Only works since None may not follow any other option. */ + return "You may not use 'Options +None' or 'Options -None'."; + } + opt = OPT_NONE; + all_none = 1; + } + else if (!strcasecmp(w, "All")) { + if (!first) { + return "'Options All' must be the first option given."; + } + else if (merge) { /* Only works since All may not follow any other option. */ + return "You may not use 'Options +All' or 'Options -All'."; + } + opt = OPT_ALL; + all_none = 1; + } + else { + return apr_pstrcat(cmd->pool, "Illegal option ", w, NULL); + } + + if ( (cmd->override_opts & opt) != opt ) { + return apr_pstrcat(cmd->pool, "Option ", w, " not allowed here", NULL); + } + else if (action == '-') { + /* we ensure the invariant (d->opts_add & d->opts_remove) == 0 */ + d->opts_remove |= opt; + d->opts_add &= ~opt; + d->opts &= ~opt; + } + else if (action == '+') { + d->opts_add |= opt; + d->opts_remove &= ~opt; + d->opts |= opt; + } + else { + d->opts |= opt; + } + + first = 0; + } + + return NULL; +} + +static const char *set_default_type(cmd_parms *cmd, void *d_, + const char *arg) +{ + if ((strcasecmp(arg, "off") != 0) && (strcasecmp(arg, "none") != 0)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00117) + "Ignoring deprecated use of DefaultType in line %d of %s.", + cmd->directive->line_num, cmd->directive->filename); + } + + return NULL; +} + +static const char *set_sethandler(cmd_parms *cmd, + void *d_, + const char *arg_) +{ + core_dir_config *dirconf = d_; + const char *err; + dirconf->expr_handler = ap_expr_parse_cmd(cmd, arg_, + AP_EXPR_FLAG_STRING_RESULT, + &err, NULL); + if (err) { + return apr_pstrcat(cmd->pool, + "Can't parse expression : ", err, NULL); + } + return NULL; +} + +/* + * Note what data should be used when forming file ETag values. + * It would be nicer to do this as an ITERATE, but then we couldn't + * remember the +/- state properly. + */ +static const char *set_etag_bits(cmd_parms *cmd, void *mconfig, + const char *args_p) +{ + core_dir_config *cfg; + etag_components_t bit; + char action; + char *token; + const char *args; + int valid; + int first; + int explicit; + + cfg = (core_dir_config *)mconfig; + + args = args_p; + first = 1; + explicit = 0; + while (args[0] != '\0') { + action = '*'; + bit = ETAG_UNSET; + valid = 1; + token = ap_getword_conf(cmd->temp_pool, &args); + if ((*token == '+') || (*token == '-')) { + action = *token; + token++; + } + else { + /* + * The occurrence of an absolute setting wipes + * out any previous relative ones. The first such + * occurrence forgets any inherited ones, too. + */ + if (first) { + cfg->etag_bits = ETAG_UNSET; + cfg->etag_add = ETAG_UNSET; + cfg->etag_remove = ETAG_UNSET; + first = 0; + } + } + + if (strcasecmp(token, "None") == 0) { + if (action != '*') { + valid = 0; + } + else { + cfg->etag_bits = bit = ETAG_NONE; + explicit = 1; + } + } + else if (strcasecmp(token, "All") == 0) { + if (action != '*') { + valid = 0; + } + else { + explicit = 1; + cfg->etag_bits = bit = ETAG_ALL; + } + } + else if (strcasecmp(token, "Size") == 0) { + bit = ETAG_SIZE; + } + else if ((strcasecmp(token, "LMTime") == 0) + || (strcasecmp(token, "MTime") == 0) + || (strcasecmp(token, "LastModified") == 0)) { + bit = ETAG_MTIME; + } + else if (strcasecmp(token, "INode") == 0) { + bit = ETAG_INODE; + } + else { + return apr_pstrcat(cmd->pool, "Unknown keyword '", + token, "' for ", cmd->cmd->name, + " directive", NULL); + } + + if (! valid) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, " keyword '", + token, "' cannot be used with '+' or '-'", + NULL); + } + + if (action == '+') { + /* + * Make sure it's in the 'add' list and absent from the + * 'subtract' list. + */ + cfg->etag_add |= bit; + cfg->etag_remove &= (~ bit); + } + else if (action == '-') { + cfg->etag_remove |= bit; + cfg->etag_add &= (~ bit); + } + else { + /* + * Non-relative values wipe out any + or - values + * accumulated so far. + */ + cfg->etag_bits |= bit; + cfg->etag_add = ETAG_UNSET; + cfg->etag_remove = ETAG_UNSET; + explicit = 1; + } + } + + /* + * Any setting at all will clear the 'None' and 'Unset' bits. + */ + + if (cfg->etag_add != ETAG_UNSET) { + cfg->etag_add &= (~ ETAG_UNSET); + } + + if (cfg->etag_remove != ETAG_UNSET) { + cfg->etag_remove &= (~ ETAG_UNSET); + } + + if (explicit) { + cfg->etag_bits &= (~ ETAG_UNSET); + + if ((cfg->etag_bits & ETAG_NONE) != ETAG_NONE) { + cfg->etag_bits &= (~ ETAG_NONE); + } + } + + return NULL; +} + +static const char *set_enable_mmap(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_dir_config *d = d_; + + if (strcasecmp(arg, "on") == 0) { + d->enable_mmap = ENABLE_MMAP_ON; + } + else if (strcasecmp(arg, "off") == 0) { + d->enable_mmap = ENABLE_MMAP_OFF; + } + else { + return "parameter must be 'on' or 'off'"; + } + + return NULL; +} + +static const char *set_enable_sendfile(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_dir_config *d = d_; + + if (strcasecmp(arg, "on") == 0) { + d->enable_sendfile = ENABLE_SENDFILE_ON; + } + else if (strcasecmp(arg, "off") == 0) { + d->enable_sendfile = ENABLE_SENDFILE_OFF; + } + else { + return "parameter must be 'on' or 'off'"; + } + + return NULL; +} + + +/* + * Report a missing-'>' syntax error. + */ +static char *unclosed_directive(cmd_parms *cmd) +{ + return apr_pstrcat(cmd->pool, cmd->cmd->name, + "> directive missing closing '>'", NULL); +} + +/* + * Report a missing args in '<Foo >' syntax error. + */ +static char *missing_container_arg(cmd_parms *cmd) +{ + return apr_pstrcat(cmd->pool, cmd->cmd->name, + "> directive requires additional arguments", NULL); +} + +AP_CORE_DECLARE_NONSTD(const char *) ap_limit_section(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + const char *endp = ap_strrchr_c(arg, '>'); + const char *limited_methods; + void *tog = cmd->cmd->cmd_data; + apr_int64_t limited = 0; + apr_int64_t old_limited = cmd->limited; + const char *errmsg; + + if (endp == NULL) { + return unclosed_directive(cmd); + } + + limited_methods = apr_pstrmemdup(cmd->temp_pool, arg, endp - arg); + + if (!limited_methods[0]) { + return missing_container_arg(cmd); + } + + while (limited_methods[0]) { + char *method = ap_getword_conf(cmd->temp_pool, &limited_methods); + int methnum; + + /* check for builtin or module registered method number */ + methnum = ap_method_number_of(method); + + if (methnum == M_TRACE && !tog) { + return "TRACE cannot be controlled by <Limit>, see TraceEnable"; + } + else if (methnum == M_INVALID) { + /* method has not been registered yet, but resource restriction + * is always checked before method handling, so register it. + */ + if (cmd->pool == cmd->temp_pool) { + /* In .htaccess, we can't globally register new methods. */ + return apr_psprintf(cmd->pool, "Could not register method '%s' " + "for %s from .htaccess configuration", + method, cmd->cmd->name); + } + methnum = ap_method_register(cmd->pool, + apr_pstrdup(cmd->pool, method)); + } + + limited |= (AP_METHOD_BIT << methnum); + } + + /* Killing two features with one function, + * if (tog == NULL) <Limit>, else <LimitExcept> + */ + limited = tog ? ~limited : limited; + + if (!(old_limited & limited)) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + "> directive excludes all methods", NULL); + } + else if ((old_limited & limited) == old_limited) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + "> directive specifies methods already excluded", + NULL); + } + + cmd->limited &= limited; + + errmsg = ap_walk_config(cmd->directive->first_child, cmd, cmd->context); + + cmd->limited = old_limited; + + return errmsg; +} + +/* XXX: Bogus - need to do this differently (at least OS2/Netware suffer + * the same problem!!! + * We use this in <DirectoryMatch> and <FilesMatch>, to ensure that + * people don't get bitten by wrong-cased regex matches + */ + +#ifdef WIN32 +#define USE_ICASE AP_REG_ICASE +#else +#define USE_ICASE 0 +#endif + +static const char *dirsection(cmd_parms *cmd, void *mconfig, const char *arg) +{ + const char *errmsg; + const char *endp = ap_strrchr_c(arg, '>'); + int old_overrides = cmd->override; + char *old_path = cmd->path; + core_dir_config *conf; + ap_conf_vector_t *new_dir_conf = ap_create_per_dir_config(cmd->pool); + ap_regex_t *r = NULL; + const command_rec *thiscmd = cmd->cmd; + + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + if (err != NULL) { + return err; + } + + if (endp == NULL) { + return unclosed_directive(cmd); + } + + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); + + if (!arg[0]) { + return missing_container_arg(cmd); + } + + cmd->path = ap_getword_conf(cmd->pool, &arg); + cmd->override = OR_ALL|ACCESS_CONF; + + if (!strcmp(cmd->path, "~")) { + cmd->path = ap_getword_conf(cmd->pool, &arg); + if (!cmd->path) + return "<Directory ~ > block must specify a path"; + r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE); + if (!r) { + return "Regex could not be compiled"; + } + } + else if (thiscmd->cmd_data) { /* <DirectoryMatch> */ + r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE); + if (!r) { + return "Regex could not be compiled"; + } + } + else if (strcmp(cmd->path, "/") != 0) + { + char *newpath; + + /* + * Ensure that the pathname is canonical, and append the trailing / + */ + apr_status_t rv = apr_filepath_merge(&newpath, NULL, cmd->path, + APR_FILEPATH_TRUENAME, cmd->pool); + if (rv != APR_SUCCESS && rv != APR_EPATHWILD) { + return apr_pstrcat(cmd->pool, "<Directory \"", cmd->path, + "\"> path is invalid.", NULL); + } + + cmd->path = newpath; + if (cmd->path[strlen(cmd->path) - 1] != '/') + cmd->path = apr_pstrcat(cmd->pool, cmd->path, "/", NULL); + } + + /* initialize our config and fetch it */ + conf = ap_set_config_vectors(cmd->server, new_dir_conf, cmd->path, + &core_module, cmd->pool); + + errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_dir_conf); + if (errmsg != NULL) + return errmsg; + + conf->r = r; + conf->d = cmd->path; + conf->d_is_fnmatch = (apr_fnmatch_test(conf->d) != 0); + + if (r) { + conf->refs = apr_array_make(cmd->pool, 8, sizeof(char *)); + ap_regname(r, conf->refs, AP_REG_MATCH, 1); + } + + /* Make this explicit - the "/" root has 0 elements, that is, we + * will always merge it, and it will always sort and merge first. + * All others are sorted and tested by the number of slashes. + */ + if (strcmp(conf->d, "/") == 0) + conf->d_components = 0; + else + conf->d_components = ap_count_dirs(conf->d); + + ap_add_per_dir_conf(cmd->server, new_dir_conf); + + if (*arg != '\0') { + return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name, + "> arguments not (yet) supported.", NULL); + } + + cmd->path = old_path; + cmd->override = old_overrides; + + return NULL; +} + +static const char *urlsection(cmd_parms *cmd, void *mconfig, const char *arg) +{ + const char *errmsg; + const char *endp = ap_strrchr_c(arg, '>'); + int old_overrides = cmd->override; + char *old_path = cmd->path; + core_dir_config *conf; + ap_regex_t *r = NULL; + const command_rec *thiscmd = cmd->cmd; + ap_conf_vector_t *new_url_conf = ap_create_per_dir_config(cmd->pool); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + if (err != NULL) { + return err; + } + + if (endp == NULL) { + return unclosed_directive(cmd); + } + + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); + + if (!arg[0]) { + return missing_container_arg(cmd); + } + + cmd->path = ap_getword_conf(cmd->pool, &arg); + cmd->override = OR_ALL|ACCESS_CONF; + + if (thiscmd->cmd_data) { /* <LocationMatch> */ + r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED); + if (!r) { + return "Regex could not be compiled"; + } + } + else if (!strcmp(cmd->path, "~")) { + cmd->path = ap_getword_conf(cmd->pool, &arg); + r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED); + if (!r) { + return "Regex could not be compiled"; + } + } + + /* initialize our config and fetch it */ + conf = ap_set_config_vectors(cmd->server, new_url_conf, cmd->path, + &core_module, cmd->pool); + + errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_url_conf); + if (errmsg != NULL) + return errmsg; + + conf->d = apr_pstrdup(cmd->pool, cmd->path); /* No mangling, please */ + conf->d_is_fnmatch = apr_fnmatch_test(conf->d) != 0; + conf->r = r; + + if (r) { + conf->refs = apr_array_make(cmd->pool, 8, sizeof(char *)); + ap_regname(r, conf->refs, AP_REG_MATCH, 1); + } + + ap_add_per_url_conf(cmd->server, new_url_conf); + + if (*arg != '\0') { + return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name, + "> arguments not (yet) supported.", NULL); + } + + cmd->path = old_path; + cmd->override = old_overrides; + + return NULL; +} + +static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg) +{ + const char *errmsg; + const char *endp = ap_strrchr_c(arg, '>'); + int old_overrides = cmd->override; + char *old_path = cmd->path; + core_dir_config *conf; + ap_regex_t *r = NULL; + const command_rec *thiscmd = cmd->cmd; + ap_conf_vector_t *new_file_conf = ap_create_per_dir_config(cmd->pool); + const char *err = ap_check_cmd_context(cmd, + NOT_IN_LOCATION | NOT_IN_LIMIT); + + if (err != NULL) { + return err; + } + + if (endp == NULL) { + return unclosed_directive(cmd); + } + + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); + + if (!arg[0]) { + return missing_container_arg(cmd); + } + + cmd->path = ap_getword_conf(cmd->pool, &arg); + /* Only if not an .htaccess file */ + if (!old_path) { + cmd->override = OR_ALL|ACCESS_CONF; + } + + if (thiscmd->cmd_data) { /* <FilesMatch> */ + r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE); + if (!r) { + return "Regex could not be compiled"; + } + } + else if (!strcmp(cmd->path, "~")) { + cmd->path = ap_getword_conf(cmd->pool, &arg); + r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE); + if (!r) { + return "Regex could not be compiled"; + } + } + else { + char *newpath; + /* Ensure that the pathname is canonical, but we + * can't test the case/aliases without a fixed path */ + if (apr_filepath_merge(&newpath, "", cmd->path, + 0, cmd->pool) != APR_SUCCESS) + return apr_pstrcat(cmd->pool, "<Files \"", cmd->path, + "\"> is invalid.", NULL); + cmd->path = newpath; + } + + /* initialize our config and fetch it */ + conf = ap_set_config_vectors(cmd->server, new_file_conf, cmd->path, + &core_module, cmd->pool); + + errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_file_conf); + if (errmsg != NULL) + return errmsg; + + conf->d = cmd->path; + conf->d_is_fnmatch = apr_fnmatch_test(conf->d) != 0; + conf->r = r; + + if (r) { + conf->refs = apr_array_make(cmd->pool, 8, sizeof(char *)); + ap_regname(r, conf->refs, AP_REG_MATCH, 1); + } + + ap_add_file_conf(cmd->pool, (core_dir_config *)mconfig, new_file_conf); + + if (*arg != '\0') { + return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name, + "> arguments not (yet) supported.", NULL); + } + + cmd->path = old_path; + cmd->override = old_overrides; + + return NULL; +} + +#define COND_IF ((void *)1) +#define COND_ELSE ((void *)2) +#define COND_ELSEIF ((void *)3) + +static const char *ifsection(cmd_parms *cmd, void *mconfig, const char *arg) +{ + const char *errmsg; + const char *endp = ap_strrchr_c(arg, '>'); + int old_overrides = cmd->override; + char *old_path = cmd->path; + core_dir_config *conf; + const command_rec *thiscmd = cmd->cmd; + ap_conf_vector_t *new_if_conf = ap_create_per_dir_config(cmd->pool); + const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT); + const char *condition; + const char *expr_err; + + if (err != NULL) { + return err; + } + + if (endp == NULL) { + return unclosed_directive(cmd); + } + + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); + + /* + * Set a dummy value so that other directives notice that they are inside + * a config section. + */ + cmd->path = "*If"; + /* Only if not an .htaccess file */ + if (!old_path) { + cmd->override = OR_ALL|ACCESS_CONF; + } + + /* initialize our config and fetch it */ + conf = ap_set_config_vectors(cmd->server, new_if_conf, cmd->path, + &core_module, cmd->pool); + + if (cmd->cmd->cmd_data == COND_IF) + conf->condition_ifelse = AP_CONDITION_IF; + else if (cmd->cmd->cmd_data == COND_ELSEIF) + conf->condition_ifelse = AP_CONDITION_ELSEIF; + else if (cmd->cmd->cmd_data == COND_ELSE) + conf->condition_ifelse = AP_CONDITION_ELSE; + else + ap_assert(0); + + if (conf->condition_ifelse == AP_CONDITION_ELSE) { + if (arg[0]) + return "<Else> does not take an argument"; + } + else { + if (!arg[0]) + return missing_container_arg(cmd); + condition = ap_getword_conf(cmd->pool, &arg); + conf->condition = ap_expr_parse_cmd(cmd, condition, 0, &expr_err, NULL); + if (expr_err) + return apr_psprintf(cmd->pool, "Cannot parse condition clause: %s", + expr_err); + } + + errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_if_conf); + if (errmsg != NULL) + return errmsg; + + conf->d = cmd->path; + conf->d_is_fnmatch = 0; + conf->r = NULL; + + errmsg = ap_add_if_conf(cmd->pool, (core_dir_config *)mconfig, new_if_conf); + if (errmsg != NULL) + return errmsg; + + if (*arg != '\0') { + return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name, + "> arguments not supported.", NULL); + } + + cmd->path = old_path; + cmd->override = old_overrides; + + return NULL; +} + +static module *find_module(server_rec *s, const char *name) +{ + module *found = ap_find_linked_module(name); + + /* search prelinked stuff */ + if (!found) { + ap_module_symbol_t *current = ap_prelinked_module_symbols; + + for (; current->name; ++current) { + if (!strcmp(current->name, name)) { + found = current->modp; + break; + } + } + } + + /* search dynamic stuff */ + if (!found) { + APR_OPTIONAL_FN_TYPE(ap_find_loaded_module_symbol) *check_symbol = + APR_RETRIEVE_OPTIONAL_FN(ap_find_loaded_module_symbol); + + if (check_symbol) { + /* + * There are two phases where calling ap_find_loaded_module_symbol + * is problematic: + * + * During reading of the config, ap_server_conf is invalid but s + * points to the main server config, if passed from cmd->server + * of an EXEC_ON_READ directive. + * + * During config parsing, s may be a virtual host that would cause + * a segfault in mod_so if passed to ap_find_loaded_module_symbol, + * because mod_so's server config for vhosts is initialized later. + * But ap_server_conf is already set at this time. + * + * Therefore we use s if it is not virtual and ap_server_conf if + * s is virtual. + */ + found = check_symbol(s->is_virtual ? ap_server_conf : s, name); + } + } + + return found; +} + +/* Callback function type used by start_cond_section. */ +typedef int (*test_cond_section_fn)(cmd_parms *cmd, const char *arg); + +/* Implementation of <IfXXXXX>-style conditional sections. Callback + * to test condition must be in cmd->info, matching function type + * test_cond_section_fn. */ +static const char *start_cond_section(cmd_parms *cmd, void *mconfig, const char *arg) +{ + const char *endp = ap_strrchr_c(arg, '>'); + int result, not = (arg[0] == '!'); + test_cond_section_fn testfn = (test_cond_section_fn)cmd->info; + const char *arg1; + + if (endp == NULL) { + return unclosed_directive(cmd); + } + + arg = apr_pstrmemdup(cmd->temp_pool, arg, endp - arg); + + if (not) { + arg++; + } + + arg1 = ap_getword_conf(cmd->temp_pool, &arg); + + if (!arg1[0]) { + return missing_container_arg(cmd); + } + + result = testfn(cmd, arg1); + + if ((!not && result) || (not && !result)) { + ap_directive_t *parent = NULL; + ap_directive_t *current = NULL; + const char *retval; + + retval = ap_build_cont_config(cmd->pool, cmd->temp_pool, cmd, + ¤t, &parent, (char *)cmd->cmd->name); + *(ap_directive_t **)mconfig = current; + return retval; + } + else { + *(ap_directive_t **)mconfig = NULL; + return ap_soak_end_container(cmd, (char *)cmd->cmd->name); + } +} + +/* Callback to implement <IfModule> test for start_cond_section. */ +static int test_ifmod_section(cmd_parms *cmd, const char *arg) +{ + return find_module(cmd->server, arg) != NULL; +} + +AP_DECLARE(int) ap_exists_config_define(const char *name) +{ + return ap_array_str_contains(ap_server_config_defines, name); +} + +static int test_ifdefine_section(cmd_parms *cmd, const char *arg) +{ + return ap_exists_config_define(arg); +} + +static int test_iffile_section(cmd_parms *cmd, const char *arg) +{ + const char *relative; + apr_finfo_t sb; + + relative = ap_server_root_relative(cmd->temp_pool, arg); + return (apr_stat(&sb, relative, 0, cmd->pool) == APR_SUCCESS); +} + +static int test_ifdirective_section(cmd_parms *cmd, const char *arg) +{ + return ap_exists_directive(cmd->temp_pool, arg); +} + +static int test_ifsection_section(cmd_parms *cmd, const char *arg) +{ + const char *name = apr_pstrcat(cmd->temp_pool, "<", arg, NULL); + return ap_exists_directive(cmd->temp_pool, name); +} + +/* httpd.conf commands... beginning with the <VirtualHost> business */ + +static const char *virtualhost_section(cmd_parms *cmd, void *dummy, + const char *arg) +{ + server_rec *main_server = cmd->server, *s; + const char *errmsg; + const char *endp = ap_strrchr_c(arg, '>'); + apr_pool_t *p = cmd->pool; + + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + if (endp == NULL) { + return unclosed_directive(cmd); + } + + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); + + if (!arg[0]) { + return missing_container_arg(cmd); + } + + /* FIXME: There's another feature waiting to happen here -- since you + can now put multiple addresses/names on a single <VirtualHost> + you might want to use it to group common definitions and then + define other "subhosts" with their individual differences. But + personally I'd rather just do it with a macro preprocessor. -djg */ + if (main_server->is_virtual) { + return "<VirtualHost> doesn't nest!"; + } + + errmsg = ap_init_virtual_host(p, arg, main_server, &s); + if (errmsg) { + return errmsg; + } + + s->next = main_server->next; + main_server->next = s; + + s->defn_name = cmd->directive->filename; + s->defn_line_number = cmd->directive->line_num; + + cmd->server = s; + + errmsg = ap_walk_config(cmd->directive->first_child, cmd, + s->lookup_defaults); + + cmd->server = main_server; + + return errmsg; +} + +static const char *set_regex_default_options(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + const command_rec *thiscmd = cmd->cmd; + int cflags, cflag; + + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + cflags = ap_regcomp_get_default_cflags(); + while (*arg) { + const char *name = ap_getword_conf(cmd->pool, &arg); + int how = 0; + + if (strcasecmp(name, "none") == 0) { + cflags = 0; + continue; + } + + if (*name == '+') { + name++; + how = +1; + } + else if (*name == '-') { + name++; + how = -1; + } + + cflag = ap_regcomp_default_cflag_by_name(name); + if (!cflag) { + return apr_psprintf(cmd->pool, "%s: option '%s' unknown", + thiscmd->name, name); + } + + if (how > 0) { + cflags |= cflag; + } + else if (how < 0) { + cflags &= ~cflag; + } + else { + cflags = cflag; + } + } + ap_regcomp_set_default_cflags(cflags); + + return NULL; +} + +static const char *set_server_alias(cmd_parms *cmd, void *dummy, + const char *arg) +{ + if (!cmd->server->names) { + return "ServerAlias only used in <VirtualHost>"; + } + + while (*arg) { + char **item, *name = ap_getword_conf(cmd->pool, &arg); + + if (ap_is_matchexp(name)) { + item = (char **)apr_array_push(cmd->server->wild_names); + } + else { + item = (char **)apr_array_push(cmd->server->names); + } + + *item = name; + } + + return NULL; +} + +static const char *set_accf_map(cmd_parms *cmd, void *dummy, + const char *iproto, const char* iaccf) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + char* proto; + char* accf; + if (err != NULL) { + return err; + } + + proto = apr_pstrdup(cmd->pool, iproto); + ap_str_tolower(proto); + accf = apr_pstrdup(cmd->pool, iaccf); + ap_str_tolower(accf); + apr_table_setn(conf->accf_map, proto, accf); + + return NULL; +} + +AP_DECLARE(const char*) ap_get_server_protocol(server_rec* s) +{ + core_server_config *conf = ap_get_core_module_config(s->module_config); + return conf->protocol; +} + +AP_DECLARE(void) ap_set_server_protocol(server_rec* s, const char* proto) +{ + core_server_config *conf = ap_get_core_module_config(s->module_config); + conf->protocol = proto; +} + +static const char *set_protocol(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + char* proto; + + if (err != NULL) { + return err; + } + + proto = apr_pstrdup(cmd->pool, arg); + ap_str_tolower(proto); + conf->protocol = proto; + + return NULL; +} + +static const char *set_server_string_slot(cmd_parms *cmd, void *dummy, + const char *arg) +{ + /* This one's pretty generic... */ + + int offset = (int)(long)cmd->info; + char *struct_ptr = (char *)cmd->server; + + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + if (err != NULL) { + return err; + } + + *(const char **)(struct_ptr + offset) = arg; + return NULL; +} + +/* + * The ServerName directive takes one argument with format + * [scheme://]fully-qualified-domain-name[:port], for instance + * ServerName www.example.com + * ServerName www.example.com:80 + * ServerName https://www.example.com:443 + */ + +static const char *server_hostname_port(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + const char *portstr, *part; + char *scheme; + int port; + + if (err != NULL) { + return err; + } + + if (apr_fnmatch_test(arg)) + return apr_pstrcat(cmd->temp_pool, "Invalid ServerName \"", arg, + "\" use ServerAlias to set multiple server names.", NULL); + + part = ap_strstr_c(arg, "://"); + + if (part) { + scheme = apr_pstrndup(cmd->pool, arg, part - arg); + ap_str_tolower(scheme); + cmd->server->server_scheme = (const char *)scheme; + part += 3; + } else { + part = arg; + } + + portstr = ap_strchr_c(part, ':'); + if (portstr) { + cmd->server->server_hostname = apr_pstrndup(cmd->pool, part, + portstr - part); + portstr++; + port = atoi(portstr); + if (port <= 0 || port >= 65536) { /* 65536 == 1<<16 */ + return apr_pstrcat(cmd->temp_pool, "The port number \"", arg, + "\" is outside the appropriate range " + "(i.e., 1..65535).", NULL); + } + } + else { + cmd->server->server_hostname = apr_pstrdup(cmd->pool, part); + port = 0; + } + + cmd->server->port = port; + return NULL; +} + +static const char *set_signature_flag(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_dir_config *d = d_; + + if (strcasecmp(arg, "On") == 0) { + d->server_signature = srv_sig_on; + } + else if (strcasecmp(arg, "Off") == 0) { + d->server_signature = srv_sig_off; + } + else if (strcasecmp(arg, "EMail") == 0) { + d->server_signature = srv_sig_withmail; + } + else { + return "ServerSignature: use one of: off | on | email"; + } + + return NULL; +} + +static const char *set_server_root(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + if ((apr_filepath_merge((char**)&ap_server_root, NULL, arg, + APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS) + || !ap_is_directory(cmd->temp_pool, ap_server_root)) { + return "ServerRoot must be a valid directory"; + } + + return NULL; +} + +static const char *set_runtime_dir(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + if ((apr_filepath_merge((char**)&ap_runtime_dir, NULL, + ap_server_root_relative(cmd->temp_pool, arg), + APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS) + || !ap_is_directory(cmd->temp_pool, ap_runtime_dir)) { + return "DefaultRuntimeDir must be a valid directory, absolute or relative to ServerRoot"; + } + + return NULL; +} + +static const char *set_timeout(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + + if (err != NULL) { + return err; + } + + cmd->server->timeout = apr_time_from_sec(atoi(arg)); + return NULL; +} + +static const char *set_allow2f(cmd_parms *cmd, void *d_, const char *arg) +{ + core_dir_config *d = d_; + + if (0 == strcasecmp(arg, "on")) { + d->allow_encoded_slashes = 1; + d->decode_encoded_slashes = 1; /* for compatibility with 2.0 & 2.2 */ + } else if (0 == strcasecmp(arg, "off")) { + d->allow_encoded_slashes = 0; + d->decode_encoded_slashes = 0; + } else if (0 == strcasecmp(arg, "nodecode")) { + d->allow_encoded_slashes = 1; + d->decode_encoded_slashes = 0; + } else { + return apr_pstrcat(cmd->pool, + cmd->cmd->name, " must be On, Off, or NoDecode", + NULL); + } + return NULL; +} + +static const char *set_hostname_lookups(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_dir_config *d = d_; + + if (!strcasecmp(arg, "on")) { + d->hostname_lookups = HOSTNAME_LOOKUP_ON; + } + else if (!strcasecmp(arg, "off")) { + d->hostname_lookups = HOSTNAME_LOOKUP_OFF; + } + else if (!strcasecmp(arg, "double")) { + d->hostname_lookups = HOSTNAME_LOOKUP_DOUBLE; + } + else { + return "parameter must be 'on', 'off', or 'double'"; + } + + return NULL; +} + +static const char *set_serverpath(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + + if (err != NULL) { + return err; + } + + cmd->server->path = arg; + cmd->server->pathlen = (int)strlen(arg); + return NULL; +} + +static const char *set_content_md5(cmd_parms *cmd, void *d_, int arg) +{ + core_dir_config *d = d_; + + d->content_md5 = arg ? AP_CONTENT_MD5_ON : AP_CONTENT_MD5_OFF; + return NULL; +} + +static const char *set_accept_path_info(cmd_parms *cmd, void *d_, const char *arg) +{ + core_dir_config *d = d_; + + if (strcasecmp(arg, "on") == 0) { + d->accept_path_info = AP_REQ_ACCEPT_PATH_INFO; + } + else if (strcasecmp(arg, "off") == 0) { + d->accept_path_info = AP_REQ_REJECT_PATH_INFO; + } + else if (strcasecmp(arg, "default") == 0) { + d->accept_path_info = AP_REQ_DEFAULT_PATH_INFO; + } + else { + return "AcceptPathInfo must be set to on, off or default"; + } + + return NULL; +} + +static const char *set_use_canonical_name(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_dir_config *d = d_; + + if (strcasecmp(arg, "on") == 0) { + d->use_canonical_name = USE_CANONICAL_NAME_ON; + } + else if (strcasecmp(arg, "off") == 0) { + d->use_canonical_name = USE_CANONICAL_NAME_OFF; + } + else if (strcasecmp(arg, "dns") == 0) { + d->use_canonical_name = USE_CANONICAL_NAME_DNS; + } + else { + return "parameter must be 'on', 'off', or 'dns'"; + } + + return NULL; +} + +static const char *set_use_canonical_phys_port(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_dir_config *d = d_; + + if (strcasecmp(arg, "on") == 0) { + d->use_canonical_phys_port = USE_CANONICAL_PHYS_PORT_ON; + } + else if (strcasecmp(arg, "off") == 0) { + d->use_canonical_phys_port = USE_CANONICAL_PHYS_PORT_OFF; + } + else { + return "parameter must be 'on' or 'off'"; + } + + return NULL; +} + +static const char *include_config (cmd_parms *cmd, void *dummy, + const char *name) +{ + ap_directive_t *conftree = NULL; + const char *conffile, *error; + unsigned *recursion; + int optional = cmd->cmd->cmd_data ? 1 : 0; + void *data; + + /* NOTE: ap_include_sentinel is also used by ap_process_resource_config() + * during DUMP_INCLUDES; don't change its type or remove it without updating + * the other. + */ + apr_pool_userdata_get(&data, "ap_include_sentinel", cmd->pool); + if (data) { + recursion = data; + } + else { + data = recursion = apr_palloc(cmd->pool, sizeof(*recursion)); + *recursion = 0; + apr_pool_userdata_setn(data, "ap_include_sentinel", NULL, cmd->pool); + } + + if (++*recursion > AP_MAX_INCLUDE_DEPTH) { + *recursion = 0; + return apr_psprintf(cmd->pool, "Exceeded maximum include depth of %u, " + "There appears to be a recursion.", + AP_MAX_INCLUDE_DEPTH); + } + + conffile = ap_server_root_relative(cmd->pool, name); + if (!conffile) { + *recursion = 0; + return apr_pstrcat(cmd->pool, "Invalid Include path ", + name, NULL); + } + + if (ap_exists_config_define("DUMP_INCLUDES")) { + unsigned *line_number; + + /* NOTE: ap_include_lineno is used by ap_process_resource_config() + * during DUMP_INCLUDES; don't change its type or remove it without + * updating the other. + */ + apr_pool_userdata_get(&data, "ap_include_lineno", cmd->pool); + if (data) { + line_number = data; + } else { + data = line_number = apr_palloc(cmd->pool, sizeof(*line_number)); + apr_pool_userdata_setn(data, "ap_include_lineno", NULL, cmd->pool); + } + + *line_number = cmd->config_file->line_number; + } + + error = ap_process_fnmatch_configs(cmd->server, conffile, &conftree, + cmd->pool, cmd->temp_pool, + optional); + if (error) { + *recursion = 0; + return error; + } + + *(ap_directive_t **)dummy = conftree; + + /* recursion level done */ + if (*recursion) { + --*recursion; + } + + return NULL; +} + +static const char *set_loglevel(cmd_parms *cmd, void *config_, const char *arg_) +{ + char *level_str; + int level; + module *module; + char *arg = apr_pstrdup(cmd->temp_pool, arg_); + struct ap_logconf *log; + const char *err; + + if (cmd->path) { + core_dir_config *dconf = config_; + if (!dconf->log) { + dconf->log = ap_new_log_config(cmd->pool, NULL); + } + log = dconf->log; + } + else { + log = &cmd->server->log; + } + + if (arg == NULL) + return "LogLevel requires level keyword or module loglevel specifier"; + + level_str = ap_strrchr(arg, ':'); + + if (level_str == NULL) { + err = ap_parse_log_level(arg, &log->level); + if (err != NULL) + return err; + ap_reset_module_loglevels(log, APLOG_NO_MODULE); + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, cmd->server, + "Setting LogLevel for all modules to %s", arg); + return NULL; + } + + *level_str++ = '\0'; + if (!*level_str) { + return apr_psprintf(cmd->temp_pool, "Module specifier '%s' must be " + "followed by a log level keyword", arg); + } + + err = ap_parse_log_level(level_str, &level); + if (err != NULL) + return apr_psprintf(cmd->temp_pool, "%s:%s: %s", arg, level_str, err); + + if ((module = find_module(cmd->server, arg)) == NULL) { + char *name = apr_psprintf(cmd->temp_pool, "%s_module", arg); + ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, cmd->server, + "Cannot find module '%s', trying '%s'", arg, name); + module = find_module(cmd->server, name); + } + + if (module == NULL) { + return apr_psprintf(cmd->temp_pool, "Cannot find module %s", arg); + } + + ap_set_module_loglevel(cmd->pool, log, module->module_index, level); + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, cmd->server, + "Setting LogLevel for module %s to %s", module->name, + level_str); + + return NULL; +} + +AP_DECLARE(const char *) ap_psignature(const char *prefix, request_rec *r) +{ + char sport[20]; + core_dir_config *conf; + + conf = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + if ((conf->server_signature == srv_sig_off) + || (conf->server_signature == srv_sig_unset)) { + return ""; + } + + apr_snprintf(sport, sizeof sport, "%u", (unsigned) ap_get_server_port(r)); + + if (conf->server_signature == srv_sig_withmail) { + return apr_pstrcat(r->pool, prefix, "<address>", + ap_get_server_banner(), + " Server at <a href=\"", + ap_is_url(r->server->server_admin) ? "" : "mailto:", + ap_escape_html(r->pool, r->server->server_admin), + "\">", + ap_escape_html(r->pool, ap_get_server_name(r)), + "</a> Port ", sport, + "</address>\n", NULL); + } + + return apr_pstrcat(r->pool, prefix, "<address>", ap_get_server_banner(), + " Server at ", + ap_escape_html(r->pool, ap_get_server_name(r)), + " Port ", sport, + "</address>\n", NULL); +} + +/* + * Handle a request to include the server's OS platform in the Server + * response header field (the ServerTokens directive). Unfortunately + * this requires a new global in order to communicate the setting back to + * http_main so it can insert the information in the right place in the + * string. + */ + +static char *server_banner = NULL; +static int banner_locked = 0; +static const char *server_description = NULL; + +enum server_token_type { + SrvTk_MAJOR, /* eg: Apache/2 */ + SrvTk_MINOR, /* eg. Apache/2.0 */ + SrvTk_MINIMAL, /* eg: Apache/2.0.41 */ + SrvTk_OS, /* eg: Apache/2.0.41 (UNIX) */ + SrvTk_FULL, /* eg: Apache/2.0.41 (UNIX) PHP/4.2.2 FooBar/1.2b */ + SrvTk_PRODUCT_ONLY /* eg: Apache */ +}; +static enum server_token_type ap_server_tokens = SrvTk_FULL; + +static apr_status_t reset_banner(void *dummy) +{ + banner_locked = 0; + ap_server_tokens = SrvTk_FULL; + server_banner = NULL; + server_description = NULL; + return APR_SUCCESS; +} + +AP_DECLARE(void) ap_get_server_revision(ap_version_t *version) +{ + version->major = AP_SERVER_MAJORVERSION_NUMBER; + version->minor = AP_SERVER_MINORVERSION_NUMBER; + version->patch = AP_SERVER_PATCHLEVEL_NUMBER; + version->add_string = AP_SERVER_ADD_STRING; +} + +AP_DECLARE(const char *) ap_get_server_description(void) +{ + return server_description ? server_description : + AP_SERVER_BASEVERSION " (" PLATFORM ")"; +} + +AP_DECLARE(const char *) ap_get_server_banner(void) +{ + return server_banner ? server_banner : AP_SERVER_BASEVERSION; +} + +AP_DECLARE(void) ap_add_version_component(apr_pool_t *pconf, const char *component) +{ + if (! banner_locked) { + /* + * If the version string is null, register our cleanup to reset the + * pointer on pool destruction. We also know that, if NULL, + * we are adding the original SERVER_BASEVERSION string. + */ + if (server_banner == NULL) { + apr_pool_cleanup_register(pconf, NULL, reset_banner, + apr_pool_cleanup_null); + server_banner = apr_pstrdup(pconf, component); + } + else { + /* + * Tack the given component identifier to the end of + * the existing string. + */ + server_banner = apr_pstrcat(pconf, server_banner, " ", + component, NULL); + } + } + server_description = apr_pstrcat(pconf, server_description, " ", + component, NULL); +} + +/* + * This routine adds the real server base identity to the banner string, + * and then locks out changes until the next reconfig. + */ +static void set_banner(apr_pool_t *pconf) +{ + if (ap_server_tokens == SrvTk_PRODUCT_ONLY) { + ap_add_version_component(pconf, AP_SERVER_BASEPRODUCT); + } + else if (ap_server_tokens == SrvTk_MINIMAL) { + ap_add_version_component(pconf, AP_SERVER_BASEVERSION); + } + else if (ap_server_tokens == SrvTk_MINOR) { + ap_add_version_component(pconf, AP_SERVER_BASEPRODUCT "/" AP_SERVER_MINORREVISION); + } + else if (ap_server_tokens == SrvTk_MAJOR) { + ap_add_version_component(pconf, AP_SERVER_BASEPRODUCT "/" AP_SERVER_MAJORVERSION); + } + else { + ap_add_version_component(pconf, AP_SERVER_BASEVERSION " (" PLATFORM ")"); + } + + /* + * Lock the server_banner string if we're not displaying + * the full set of tokens + */ + if (ap_server_tokens != SrvTk_FULL) { + banner_locked++; + } + server_description = AP_SERVER_BASEVERSION " (" PLATFORM ")"; +} + +static const char *set_serv_tokens(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + if (!strcasecmp(arg, "OS")) { + ap_server_tokens = SrvTk_OS; + } + else if (!strcasecmp(arg, "Min") || !strcasecmp(arg, "Minimal")) { + ap_server_tokens = SrvTk_MINIMAL; + } + else if (!strcasecmp(arg, "Major")) { + ap_server_tokens = SrvTk_MAJOR; + } + else if (!strcasecmp(arg, "Minor") ) { + ap_server_tokens = SrvTk_MINOR; + } + else if (!strcasecmp(arg, "Prod") || !strcasecmp(arg, "ProductOnly")) { + ap_server_tokens = SrvTk_PRODUCT_ONLY; + } + else if (!strcasecmp(arg, "Full")) { + ap_server_tokens = SrvTk_FULL; + } + else { + return "ServerTokens takes 1 argument: 'Prod(uctOnly)', 'Major', 'Minor', 'Min(imal)', 'OS', or 'Full'"; + } + + return NULL; +} + +static const char *set_limit_req_line(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + int lim; + + if (err != NULL) { + return err; + } + + lim = atoi(arg); + if (lim < 0) { + return apr_pstrcat(cmd->temp_pool, "LimitRequestLine \"", arg, + "\" must be a non-negative integer", NULL); + } + + cmd->server->limit_req_line = lim; + return NULL; +} + +static const char *set_limit_req_fieldsize(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + int lim; + + if (err != NULL) { + return err; + } + + lim = atoi(arg); + if (lim < 0) { + return apr_pstrcat(cmd->temp_pool, "LimitRequestFieldsize \"", arg, + "\" must be a non-negative integer", + NULL); + } + + cmd->server->limit_req_fieldsize = lim; + return NULL; +} + +static const char *set_limit_req_fields(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + int lim; + + if (err != NULL) { + return err; + } + + lim = atoi(arg); + if (lim < 0) { + return apr_pstrcat(cmd->temp_pool, "LimitRequestFields \"", arg, + "\" must be a non-negative integer (0 = no limit)", + NULL); + } + + cmd->server->limit_req_fields = lim; + return NULL; +} + +static const char *set_limit_req_body(cmd_parms *cmd, void *conf_, + const char *arg) +{ + core_dir_config *conf = conf_; + char *errp; + + if (APR_SUCCESS != apr_strtoff(&conf->limit_req_body, arg, &errp, 10)) { + return "LimitRequestBody argument is not parsable."; + } + if (*errp || conf->limit_req_body < 0) { + return "LimitRequestBody requires a non-negative integer."; + } + + return NULL; +} + +static const char *set_limit_xml_req_body(cmd_parms *cmd, void *conf_, + const char *arg) +{ + core_dir_config *conf = conf_; + + conf->limit_xml_body = atol(arg); + if (conf->limit_xml_body < 0) + return "LimitXMLRequestBody requires a non-negative integer."; + + return NULL; +} + +static const char *set_max_ranges(cmd_parms *cmd, void *conf_, const char *arg) +{ + core_dir_config *conf = conf_; + int val = 0; + + if (!strcasecmp(arg, "none")) { + val = AP_MAXRANGES_NORANGES; + } + else if (!strcasecmp(arg, "default")) { + val = AP_MAXRANGES_DEFAULT; + } + else if (!strcasecmp(arg, "unlimited")) { + val = AP_MAXRANGES_UNLIMITED; + } + else { + val = atoi(arg); + if (val <= 0) + return "MaxRanges requires 'none', 'default', 'unlimited' or " + "a positive integer"; + } + + conf->max_ranges = val; + + return NULL; +} + +static const char *set_max_overlaps(cmd_parms *cmd, void *conf_, const char *arg) +{ + core_dir_config *conf = conf_; + int val = 0; + + if (!strcasecmp(arg, "none")) { + val = AP_MAXRANGES_NORANGES; + } + else if (!strcasecmp(arg, "default")) { + val = AP_MAXRANGES_DEFAULT; + } + else if (!strcasecmp(arg, "unlimited")) { + val = AP_MAXRANGES_UNLIMITED; + } + else { + val = atoi(arg); + if (val <= 0) + return "MaxRangeOverlaps requires 'none', 'default', 'unlimited' or " + "a positive integer"; + } + + conf->max_overlaps = val; + + return NULL; +} + +static const char *set_max_reversals(cmd_parms *cmd, void *conf_, const char *arg) +{ + core_dir_config *conf = conf_; + int val = 0; + + if (!strcasecmp(arg, "none")) { + val = AP_MAXRANGES_NORANGES; + } + else if (!strcasecmp(arg, "default")) { + val = AP_MAXRANGES_DEFAULT; + } + else if (!strcasecmp(arg, "unlimited")) { + val = AP_MAXRANGES_UNLIMITED; + } + else { + val = atoi(arg); + if (val <= 0) + return "MaxRangeReversals requires 'none', 'default', 'unlimited' or " + "a positive integer"; + } + + conf->max_reversals = val; + + return NULL; +} + +AP_DECLARE(apr_size_t) ap_get_limit_xml_body(const request_rec *r) +{ + core_dir_config *conf; + + conf = ap_get_core_module_config(r->per_dir_config); + if (conf->limit_xml_body == AP_LIMIT_UNSET) + return AP_DEFAULT_LIMIT_XML_BODY; + + return (apr_size_t)conf->limit_xml_body; +} + +#if !defined (RLIMIT_CPU) || !(defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS)) || !defined (RLIMIT_NPROC) +static const char *no_set_limit(cmd_parms *cmd, void *conf_, + const char *arg, const char *arg2) +{ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, cmd->server, APLOGNO(00118) + "%s not supported on this platform", cmd->cmd->name); + + return NULL; +} +#endif + +#ifdef RLIMIT_CPU +static const char *set_limit_cpu(cmd_parms *cmd, void *conf_, + const char *arg, const char *arg2) +{ + core_dir_config *conf = conf_; + + ap_unixd_set_rlimit(cmd, &conf->limit_cpu, arg, arg2, RLIMIT_CPU); + return NULL; +} +#endif + +#if defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS) +static const char *set_limit_mem(cmd_parms *cmd, void *conf_, + const char *arg, const char * arg2) +{ + core_dir_config *conf = conf_; + +#if defined(RLIMIT_AS) + ap_unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2 ,RLIMIT_AS); +#elif defined(RLIMIT_DATA) + ap_unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2, RLIMIT_DATA); +#elif defined(RLIMIT_VMEM) + ap_unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2, RLIMIT_VMEM); +#endif + + return NULL; +} +#endif + +#ifdef RLIMIT_NPROC +static const char *set_limit_nproc(cmd_parms *cmd, void *conf_, + const char *arg, const char * arg2) +{ + core_dir_config *conf = conf_; + + ap_unixd_set_rlimit(cmd, &conf->limit_nproc, arg, arg2, RLIMIT_NPROC); + return NULL; +} +#endif + +static const char *set_recursion_limit(cmd_parms *cmd, void *dummy, + const char *arg1, const char *arg2) +{ + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + int limit = atoi(arg1); + + if (limit <= 0) { + return "The recursion limit must be greater than zero."; + } + if (limit < 4) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00119) + "Limiting internal redirects to very low numbers may " + "cause normal requests to fail."); + } + + conf->redirect_limit = limit; + + if (arg2) { + limit = atoi(arg2); + + if (limit <= 0) { + return "The recursion limit must be greater than zero."; + } + if (limit < 4) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00120) + "Limiting the subrequest depth to a very low level may" + " cause normal requests to fail."); + } + } + + conf->subreq_limit = limit; + + return NULL; +} + +static void log_backtrace(const request_rec *r) +{ + const request_rec *top = r; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00121) + "r->uri = %s", r->uri ? r->uri : "(unexpectedly NULL)"); + + while (top && (top->prev || top->main)) { + if (top->prev) { + top = top->prev; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00122) + "redirected from r->uri = %s", + top->uri ? top->uri : "(unexpectedly NULL)"); + } + + if (!top->prev && top->main) { + top = top->main; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00123) + "subrequested from r->uri = %s", + top->uri ? top->uri : "(unexpectedly NULL)"); + } + } +} + +/* + * check whether redirect limit is reached + */ +AP_DECLARE(int) ap_is_recursion_limit_exceeded(const request_rec *r) +{ + core_server_config *conf = + ap_get_core_module_config(r->server->module_config); + const request_rec *top = r; + int redirects = 0, subreqs = 0; + int rlimit = conf->redirect_limit + ? conf->redirect_limit + : AP_DEFAULT_MAX_INTERNAL_REDIRECTS; + int slimit = conf->subreq_limit + ? conf->subreq_limit + : AP_DEFAULT_MAX_SUBREQ_DEPTH; + + + while (top->prev || top->main) { + if (top->prev) { + if (++redirects >= rlimit) { + /* uuh, too much. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00124) + "Request exceeded the limit of %d internal " + "redirects due to probable configuration error. " + "Use 'LimitInternalRecursion' to increase the " + "limit if necessary. Use 'LogLevel debug' to get " + "a backtrace.", rlimit); + + /* post backtrace */ + log_backtrace(r); + + /* return failure */ + return 1; + } + + top = top->prev; + } + + if (!top->prev && top->main) { + if (++subreqs >= slimit) { + /* uuh, too much. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00125) + "Request exceeded the limit of %d subrequest " + "nesting levels due to probable configuration " + "error. Use 'LimitInternalRecursion' to increase " + "the limit if necessary. Use 'LogLevel debug' to " + "get a backtrace.", slimit); + + /* post backtrace */ + log_backtrace(r); + + /* return failure */ + return 1; + } + + top = top->main; + } + } + + /* recursion state: ok */ + return 0; +} + +static const char *set_trace_enable(cmd_parms *cmd, void *dummy, + const char *arg1) +{ + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + + if (strcasecmp(arg1, "on") == 0) { + conf->trace_enable = AP_TRACE_ENABLE; + } + else if (strcasecmp(arg1, "off") == 0) { + conf->trace_enable = AP_TRACE_DISABLE; + } + else if (strcasecmp(arg1, "extended") == 0) { + conf->trace_enable = AP_TRACE_EXTENDED; + } + else { + return "TraceEnable must be one of 'on', 'off', or 'extended'"; + } + + return NULL; +} + +static const char *set_protocols(cmd_parms *cmd, void *dummy, + const char *arg) +{ + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + const char **np; + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + + if (err) { + return err; + } + + np = (const char **)apr_array_push(conf->protocols); + *np = arg; + + return NULL; +} + +static const char *set_protocols_honor_order(cmd_parms *cmd, void *dummy, + const char *arg) +{ + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT); + + if (err) { + return err; + } + + if (strcasecmp(arg, "on") == 0) { + conf->protocols_honor_order = 1; + } + else if (strcasecmp(arg, "off") == 0) { + conf->protocols_honor_order = 0; + } + else { + return "ProtocolsHonorOrder must be 'on' or 'off'"; + } + + return NULL; +} + +static const char *set_http_protocol_options(cmd_parms *cmd, void *dummy, + const char *arg) +{ + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + + if (strcasecmp(arg, "allow0.9") == 0) + conf->http09_enable |= AP_HTTP09_ENABLE; + else if (strcasecmp(arg, "require1.0") == 0) + conf->http09_enable |= AP_HTTP09_DISABLE; + else if (strcasecmp(arg, "strict") == 0) + conf->http_conformance |= AP_HTTP_CONFORMANCE_STRICT; + else if (strcasecmp(arg, "unsafe") == 0) + conf->http_conformance |= AP_HTTP_CONFORMANCE_UNSAFE; + else if (strcasecmp(arg, "registeredmethods") == 0) + conf->http_methods |= AP_HTTP_METHODS_REGISTERED; + else if (strcasecmp(arg, "lenientmethods") == 0) + conf->http_methods |= AP_HTTP_METHODS_LENIENT; + else + return "HttpProtocolOptions accepts " + "'Unsafe' or 'Strict' (default), " + "'RegisteredMethods' or 'LenientMethods' (default), and " + "'Require1.0' or 'Allow0.9' (default)"; + + if ((conf->http09_enable & AP_HTTP09_ENABLE) + && (conf->http09_enable & AP_HTTP09_DISABLE)) + return "HttpProtocolOptions 'Allow0.9' and 'Require1.0'" + " are mutually exclusive"; + + if ((conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) + && (conf->http_conformance & AP_HTTP_CONFORMANCE_UNSAFE)) + return "HttpProtocolOptions 'Strict' and 'Unsafe'" + " are mutually exclusive"; + + if ((conf->http_methods & AP_HTTP_METHODS_REGISTERED) + && (conf->http_methods & AP_HTTP_METHODS_LENIENT)) + return "HttpProtocolOptions 'RegisteredMethods' and 'LenientMethods'" + " are mutually exclusive"; + + return NULL; +} + +static const char *set_http_method(cmd_parms *cmd, void *conf, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) + return err; + ap_method_register(cmd->pool, arg); + return NULL; +} + +static apr_hash_t *errorlog_hash; + +static int log_constant_item(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + char *end = apr_cpystrn(buf, arg, buflen); + return end - buf; +} + +static char *parse_errorlog_misc_string(apr_pool_t *p, + ap_errorlog_format_item *it, + const char **sa) +{ + const char *s; + char scratch[MAX_STRING_LEN]; + char *d = scratch; + /* + * non-leading white space terminates this string to allow the next field + * separator to be inserted + */ + int at_start = 1; + + it->func = log_constant_item; + s = *sa; + + while (*s && *s != '%' && (*s != ' ' || at_start) && d < scratch + MAX_STRING_LEN) { + if (*s != '\\') { + if (*s != ' ') { + at_start = 0; + } + *d++ = *s++; + } + else { + s++; + switch (*s) { + case 'r': + *d++ = '\r'; + s++; + break; + case 'n': + *d++ = '\n'; + s++; + break; + case 't': + *d++ = '\t'; + s++; + break; + case '\0': + /* handle end of string */ + *d++ = '\\'; + break; + default: + /* copy next char verbatim */ + *d++ = *s++; + break; + } + } + } + *d = '\0'; + it->arg = apr_pstrdup(p, scratch); + + *sa = s; + return NULL; +} + +static char *parse_errorlog_item(apr_pool_t *p, ap_errorlog_format_item *it, + const char **sa) +{ + const char *s = *sa; + ap_errorlog_handler *handler; + int i; + + if (*s != '%') { + if (*s == ' ') { + it->flags |= AP_ERRORLOG_FLAG_FIELD_SEP; + } + return parse_errorlog_misc_string(p, it, sa); + } + + ++s; + + if (*s == ' ') { + /* percent-space (% ) is a field separator */ + it->flags |= AP_ERRORLOG_FLAG_FIELD_SEP; + *sa = ++s; + /* recurse */ + return parse_errorlog_item(p, it, sa); + } + else if (*s == '%') { + it->arg = "%"; + it->func = log_constant_item; + *sa = ++s; + return NULL; + } + + while (*s) { + switch (*s) { + case '{': + ++s; + it->arg = ap_getword(p, &s, '}'); + break; + case '+': + ++s; + it->flags |= AP_ERRORLOG_FLAG_REQUIRED; + break; + case '-': + ++s; + it->flags |= AP_ERRORLOG_FLAG_NULL_AS_HYPHEN; + 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 (apr_isdigit(*++s)) + i = i * 10 + (*s) - '0'; + it->min_loglevel = i; + break; + case 'M': + it->func = NULL; + it->flags |= AP_ERRORLOG_FLAG_MESSAGE; + *sa = ++s; + return NULL; + default: + handler = (ap_errorlog_handler *)apr_hash_get(errorlog_hash, s, 1); + if (!handler) { + char dummy[2]; + + dummy[0] = *s; + dummy[1] = '\0'; + return apr_pstrcat(p, "Unrecognized error log format directive %", + dummy, NULL); + } + it->func = handler->func; + *sa = ++s; + return NULL; + } + } + + return "Ran off end of error log format parsing args to some directive"; +} + +static apr_array_header_t *parse_errorlog_string(apr_pool_t *p, + const char *s, + const char **err, + int is_main_fmt) +{ + apr_array_header_t *a = apr_array_make(p, 30, + sizeof(ap_errorlog_format_item)); + char *res; + int seen_msg_fmt = 0; + + while (s && *s) { + ap_errorlog_format_item *item = + (ap_errorlog_format_item *)apr_array_push(a); + memset(item, 0, sizeof(*item)); + res = parse_errorlog_item(p, item, &s); + if (res) { + *err = res; + return NULL; + } + if (item->flags & AP_ERRORLOG_FLAG_MESSAGE) { + if (!is_main_fmt) { + *err = "%M cannot be used in once-per-request or " + "once-per-connection formats"; + return NULL; + } + seen_msg_fmt = 1; + } + if (is_main_fmt && item->flags & AP_ERRORLOG_FLAG_REQUIRED) { + *err = "The '+' flag cannot be used in the main error log format"; + return NULL; + } + if (!is_main_fmt && item->min_loglevel) { + *err = "The loglevel cannot be used as a condition in " + "once-per-request or once-per-connection formats"; + return NULL; + } + if (item->min_loglevel > APLOG_TRACE8) { + *err = "The specified loglevel modifier is out of range"; + return NULL; + } + } + + if (is_main_fmt && !seen_msg_fmt) { + *err = "main ErrorLogFormat must contain message format string '%M'"; + return NULL; + } + + return a; +} + +static const char *set_errorlog_format(cmd_parms *cmd, void *dummy, + const char *arg1, const char *arg2) +{ + const char *err_string = NULL; + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + + if (!arg2) { + conf->error_log_format = parse_errorlog_string(cmd->pool, arg1, + &err_string, 1); + } + else if (!strcasecmp(arg1, "connection")) { + if (!conf->error_log_conn) { + conf->error_log_conn = apr_array_make(cmd->pool, 5, + sizeof(apr_array_header_t *)); + } + + if (*arg2) { + apr_array_header_t **e; + e = (apr_array_header_t **) apr_array_push(conf->error_log_conn); + *e = parse_errorlog_string(cmd->pool, arg2, &err_string, 0); + } + } + else if (!strcasecmp(arg1, "request")) { + if (!conf->error_log_req) { + conf->error_log_req = apr_array_make(cmd->pool, 5, + sizeof(apr_array_header_t *)); + } + + if (*arg2) { + apr_array_header_t **e; + e = (apr_array_header_t **) apr_array_push(conf->error_log_req); + *e = parse_errorlog_string(cmd->pool, arg2, &err_string, 0); + } + } + else { + err_string = "ErrorLogFormat type must be one of request, connection"; + } + + return err_string; +} + +AP_DECLARE(void) ap_register_errorlog_handler(apr_pool_t *p, char *tag, + ap_errorlog_handler_fn_t *handler, + int flags) +{ + ap_errorlog_handler *log_struct = apr_palloc(p, sizeof(*log_struct)); + log_struct->func = handler; + log_struct->flags = flags; + + apr_hash_set(errorlog_hash, tag, 1, (const void *)log_struct); +} + + +static const char *set_merge_trailers(cmd_parms *cmd, void *dummy, int arg) +{ + core_server_config *conf = ap_get_module_config(cmd->server->module_config, + &core_module); + conf->merge_trailers = (arg ? AP_MERGE_TRAILERS_ENABLE : + AP_MERGE_TRAILERS_DISABLE); + + return NULL; +} + +/* Note --- ErrorDocument will now work from .htaccess files. + * The AllowOverride of Fileinfo allows webmasters to turn it off + */ + +static const command_rec core_cmds[] = { + +/* Old access config file commands */ + +AP_INIT_RAW_ARGS("<Directory", dirsection, NULL, RSRC_CONF, + "Container for directives affecting resources located in the specified " + "directories"), +AP_INIT_RAW_ARGS("<Location", urlsection, NULL, RSRC_CONF, + "Container for directives affecting resources accessed through the " + "specified URL paths"), +AP_INIT_RAW_ARGS("<VirtualHost", virtualhost_section, NULL, RSRC_CONF, + "Container to map directives to a particular virtual host, takes one or " + "more host addresses"), +AP_INIT_RAW_ARGS("<Files", filesection, NULL, OR_ALL, + "Container for directives affecting files matching specified patterns"), +AP_INIT_RAW_ARGS("<Limit", ap_limit_section, NULL, OR_LIMIT | OR_AUTHCFG, + "Container for authentication directives when accessed using specified HTTP " + "methods"), +AP_INIT_RAW_ARGS("<LimitExcept", ap_limit_section, (void*)1, + OR_LIMIT | OR_AUTHCFG, + "Container for authentication directives to be applied when any HTTP " + "method other than those specified is used to access the resource"), +AP_INIT_RAW_ARGS("<IfModule", start_cond_section, (void *)test_ifmod_section, + EXEC_ON_READ | OR_ALL, + "Container for directives based on existence of specified modules"), +AP_INIT_RAW_ARGS("<IfDefine", start_cond_section, (void *)test_ifdefine_section, + EXEC_ON_READ | OR_ALL, + "Container for directives based on existence of command line defines"), +AP_INIT_RAW_ARGS("<IfFile", start_cond_section, (void *)test_iffile_section, + EXEC_ON_READ | OR_ALL, + "Container for directives based on existence of files on disk"), +AP_INIT_RAW_ARGS("<IfDirective", start_cond_section, (void *)test_ifdirective_section, + EXEC_ON_READ | OR_ALL, + "Container for directives based on existence of named directive"), +AP_INIT_RAW_ARGS("<IfSection", start_cond_section, (void *)test_ifsection_section, + EXEC_ON_READ | OR_ALL, + "Container for directives based on existence of named section"), +AP_INIT_RAW_ARGS("<DirectoryMatch", dirsection, (void*)1, RSRC_CONF, + "Container for directives affecting resources located in the " + "specified directories"), +AP_INIT_RAW_ARGS("<LocationMatch", urlsection, (void*)1, RSRC_CONF, + "Container for directives affecting resources accessed through the " + "specified URL paths"), +AP_INIT_RAW_ARGS("<FilesMatch", filesection, (void*)1, OR_ALL, + "Container for directives affecting files matching specified patterns"), +#ifdef GPROF +AP_INIT_TAKE1("GprofDir", set_gprof_dir, NULL, RSRC_CONF, + "Directory to plop gmon.out files"), +#endif +AP_INIT_TAKE1("AddDefaultCharset", set_add_default_charset, NULL, OR_FILEINFO, + "The name of the default charset to add to any Content-Type without one or 'Off' to disable"), +AP_INIT_TAKE1("AcceptPathInfo", set_accept_path_info, NULL, OR_FILEINFO, + "Set to on or off for PATH_INFO to be accepted by handlers, or default for the per-handler preference"), +AP_INIT_TAKE12("Define", set_define, NULL, EXEC_ON_READ|ACCESS_CONF|RSRC_CONF, + "Define a variable, optionally to a value. Same as passing -D to the command line."), +AP_INIT_TAKE1("UnDefine", unset_define, NULL, EXEC_ON_READ|ACCESS_CONF|RSRC_CONF, + "Undefine the existence of a variable. Undo a Define."), +AP_INIT_RAW_ARGS("Error", generate_error, NULL, OR_ALL, + "Generate error message from within configuration"), +AP_INIT_RAW_ARGS("<If", ifsection, COND_IF, OR_ALL, + "Container for directives to be conditionally applied"), +AP_INIT_RAW_ARGS("<ElseIf", ifsection, COND_ELSEIF, OR_ALL, + "Container for directives to be conditionally applied"), +AP_INIT_RAW_ARGS("<Else", ifsection, COND_ELSE, OR_ALL, + "Container for directives to be conditionally applied"), + +/* Old resource config file commands */ + +AP_INIT_RAW_ARGS("AccessFileName", set_access_name, NULL, RSRC_CONF, + "Name(s) of per-directory config files (default: .htaccess)"), +AP_INIT_TAKE1("DocumentRoot", set_document_root, NULL, RSRC_CONF, + "Root directory of the document tree"), +AP_INIT_TAKE2("ErrorDocument", set_error_document, NULL, OR_FILEINFO, + "Change responses for HTTP errors"), +AP_INIT_RAW_ARGS("AllowOverride", set_override, NULL, ACCESS_CONF, + "Controls what groups of directives can be configured by per-directory " + "config files"), +AP_INIT_TAKE_ARGV("AllowOverrideList", set_override_list, NULL, ACCESS_CONF, + "Controls what individual directives can be configured by per-directory " + "config files"), +AP_INIT_RAW_ARGS("Options", set_options, NULL, OR_OPTIONS, + "Set a number of attributes for a given directory"), +AP_INIT_TAKE1("DefaultType", set_default_type, NULL, OR_FILEINFO, + "the default media type for otherwise untyped files (DEPRECATED)"), +AP_INIT_RAW_ARGS("FileETag", set_etag_bits, NULL, OR_FILEINFO, + "Specify components used to construct a file's ETag"), +AP_INIT_TAKE1("EnableMMAP", set_enable_mmap, NULL, OR_FILEINFO, + "Controls whether memory-mapping may be used to read files"), +AP_INIT_TAKE1("EnableSendfile", set_enable_sendfile, NULL, OR_FILEINFO, + "Controls whether sendfile may be used to transmit files"), + +/* Old server config file commands */ + +AP_INIT_TAKE1("Protocol", set_protocol, NULL, RSRC_CONF, + "Set the Protocol for httpd to use."), +AP_INIT_TAKE2("AcceptFilter", set_accf_map, NULL, RSRC_CONF, + "Set the Accept Filter to use for a protocol"), +AP_INIT_TAKE1("Port", ap_set_deprecated, NULL, RSRC_CONF, + "Port was replaced with Listen in Apache 2.0"), +AP_INIT_TAKE1("HostnameLookups", set_hostname_lookups, NULL, + ACCESS_CONF|RSRC_CONF, + "\"on\" to enable, \"off\" to disable reverse DNS lookups, or \"double\" to " + "enable double-reverse DNS lookups"), +AP_INIT_TAKE1("ServerAdmin", set_server_string_slot, + (void *)APR_OFFSETOF(server_rec, server_admin), RSRC_CONF, + "The email address of the server administrator"), +AP_INIT_TAKE1("ServerName", server_hostname_port, NULL, RSRC_CONF, + "The hostname and port of the server"), +AP_INIT_TAKE1("ServerSignature", set_signature_flag, NULL, OR_ALL, + "En-/disable server signature (on|off|email)"), +AP_INIT_TAKE1("ServerRoot", set_server_root, NULL, RSRC_CONF | EXEC_ON_READ, + "Common directory of server-related files (logs, confs, etc.)"), +AP_INIT_TAKE1("DefaultRuntimeDir", set_runtime_dir, NULL, RSRC_CONF | EXEC_ON_READ, + "Common directory for run-time files (shared memory, locks, etc.)"), +AP_INIT_TAKE1("ErrorLog", set_server_string_slot, + (void *)APR_OFFSETOF(server_rec, error_fname), RSRC_CONF, + "The filename of the error log"), +AP_INIT_TAKE12("ErrorLogFormat", set_errorlog_format, NULL, RSRC_CONF, + "Format string for the ErrorLog"), +AP_INIT_RAW_ARGS("ServerAlias", set_server_alias, NULL, RSRC_CONF, + "A name or names alternately used to access the server"), +AP_INIT_TAKE1("ServerPath", set_serverpath, NULL, RSRC_CONF, + "The pathname the server can be reached at"), +AP_INIT_TAKE1("Timeout", set_timeout, NULL, RSRC_CONF, + "Timeout duration (sec)"), +AP_INIT_FLAG("ContentDigest", set_content_md5, NULL, OR_OPTIONS, + "whether or not to send a Content-MD5 header with each request"), +AP_INIT_TAKE1("UseCanonicalName", set_use_canonical_name, NULL, + RSRC_CONF|ACCESS_CONF, + "How to work out the ServerName : Port when constructing URLs"), +AP_INIT_TAKE1("UseCanonicalPhysicalPort", set_use_canonical_phys_port, NULL, + RSRC_CONF|ACCESS_CONF, + "Whether to use the physical Port when constructing URLs"), +/* TODO: RlimitFoo should all be part of mod_cgi, not in the core */ +/* TODO: ListenBacklog in MPM */ +AP_INIT_TAKE1("Include", include_config, NULL, + (RSRC_CONF | ACCESS_CONF | EXEC_ON_READ), + "Name(s) of the config file(s) to be included; fails if the wildcard does " + "not match at least one file"), +AP_INIT_TAKE1("IncludeOptional", include_config, (void*)1, + (RSRC_CONF | ACCESS_CONF | EXEC_ON_READ), + "Name or pattern of the config file(s) to be included; ignored if the file " + "does not exist or the pattern does not match any files"), +AP_INIT_ITERATE("LogLevel", set_loglevel, NULL, RSRC_CONF|ACCESS_CONF, + "Level of verbosity in error logging"), +AP_INIT_TAKE1("NameVirtualHost", ap_set_name_virtual_host, NULL, RSRC_CONF, + "A numeric IP address:port, or the name of a host"), +AP_INIT_TAKE1("ServerTokens", set_serv_tokens, NULL, RSRC_CONF, + "Determine tokens displayed in the Server: header - Min(imal), " + "Major, Minor, Prod(uctOnly), OS, or Full"), +AP_INIT_TAKE1("LimitRequestLine", set_limit_req_line, NULL, RSRC_CONF, + "Limit on maximum size of an HTTP request line"), +AP_INIT_TAKE1("LimitRequestFieldsize", set_limit_req_fieldsize, NULL, + RSRC_CONF, + "Limit on maximum size of an HTTP request header field"), +AP_INIT_TAKE1("LimitRequestFields", set_limit_req_fields, NULL, RSRC_CONF, + "Limit (0 = unlimited) on max number of header fields in a request message"), +AP_INIT_TAKE1("LimitRequestBody", set_limit_req_body, + (void*)APR_OFFSETOF(core_dir_config, limit_req_body), OR_ALL, + "Limit (in bytes) on maximum size of request message body"), +AP_INIT_TAKE1("LimitXMLRequestBody", set_limit_xml_req_body, NULL, OR_ALL, + "Limit (in bytes) on maximum size of an XML-based request " + "body"), +AP_INIT_RAW_ARGS("Mutex", ap_set_mutex, NULL, RSRC_CONF, + "mutex (or \"default\") and mechanism"), + +AP_INIT_TAKE1("MaxRanges", set_max_ranges, NULL, RSRC_CONF|ACCESS_CONF, + "Maximum number of Ranges in a request before returning the entire " + "resource, or 0 for unlimited"), +AP_INIT_TAKE1("MaxRangeOverlaps", set_max_overlaps, NULL, RSRC_CONF|ACCESS_CONF, + "Maximum number of overlaps in Ranges in a request before returning the entire " + "resource, or 0 for unlimited"), +AP_INIT_TAKE1("MaxRangeReversals", set_max_reversals, NULL, RSRC_CONF|ACCESS_CONF, + "Maximum number of reversals in Ranges in a request before returning the entire " + "resource, or 0 for unlimited"), +/* System Resource Controls */ +#ifdef RLIMIT_CPU +AP_INIT_TAKE12("RLimitCPU", set_limit_cpu, + (void*)APR_OFFSETOF(core_dir_config, limit_cpu), + OR_ALL, "Soft/hard limits for max CPU usage in seconds"), +#else +AP_INIT_TAKE12("RLimitCPU", no_set_limit, NULL, + OR_ALL, "Soft/hard limits for max CPU usage in seconds"), +#endif +#if defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined (RLIMIT_AS) +AP_INIT_TAKE12("RLimitMEM", set_limit_mem, + (void*)APR_OFFSETOF(core_dir_config, limit_mem), + OR_ALL, "Soft/hard limits for max memory usage per process"), +#else +AP_INIT_TAKE12("RLimitMEM", no_set_limit, NULL, + OR_ALL, "Soft/hard limits for max memory usage per process"), +#endif +#ifdef RLIMIT_NPROC +AP_INIT_TAKE12("RLimitNPROC", set_limit_nproc, + (void*)APR_OFFSETOF(core_dir_config, limit_nproc), + OR_ALL, "soft/hard limits for max number of processes per uid"), +#else +AP_INIT_TAKE12("RLimitNPROC", no_set_limit, NULL, + OR_ALL, "soft/hard limits for max number of processes per uid"), +#endif + +AP_INIT_RAW_ARGS("RegexDefaultOptions", set_regex_default_options, NULL, RSRC_CONF, + "default options for regexes (prefixed by '+' to add, '-' to del)"), + +/* internal recursion stopper */ +AP_INIT_TAKE12("LimitInternalRecursion", set_recursion_limit, NULL, RSRC_CONF, + "maximum recursion depth of internal redirects and subrequests"), + +AP_INIT_FLAG("CGIPassAuth", set_cgi_pass_auth, NULL, OR_AUTHCFG, + "Controls whether HTTP authorization headers, normally hidden, will " + "be passed to scripts"), +AP_INIT_TAKE2("CGIVar", set_cgi_var, NULL, OR_FILEINFO, + "Controls how some CGI variables are set"), +AP_INIT_FLAG("QualifyRedirectURL", set_qualify_redirect_url, NULL, OR_FILEINFO, + "Controls whether HTTP authorization headers, normally hidden, will " + "be passed to scripts"), + +AP_INIT_TAKE1("ForceType", ap_set_string_slot_lower, + (void *)APR_OFFSETOF(core_dir_config, mime_type), OR_FILEINFO, + "a mime type that overrides other configured type"), +AP_INIT_TAKE1("SetHandler", set_sethandler, NULL, OR_FILEINFO, + "a handler name that overrides any other configured handler"), +AP_INIT_TAKE1("SetOutputFilter", ap_set_string_slot, + (void *)APR_OFFSETOF(core_dir_config, output_filters), OR_FILEINFO, + "filter (or ; delimited list of filters) to be run on the request content"), +AP_INIT_TAKE1("SetInputFilter", ap_set_string_slot, + (void *)APR_OFFSETOF(core_dir_config, input_filters), OR_FILEINFO, + "filter (or ; delimited list of filters) to be run on the request body"), +AP_INIT_TAKE1("AllowEncodedSlashes", set_allow2f, NULL, RSRC_CONF, + "Allow URLs containing '/' encoded as '%2F'"), + +/* scoreboard.c directives */ +AP_INIT_TAKE1("ScoreBoardFile", ap_set_scoreboard, NULL, RSRC_CONF, + "A file for Apache to maintain runtime process management information"), +AP_INIT_FLAG("ExtendedStatus", ap_set_extended_status, NULL, RSRC_CONF, + "\"On\" to track extended status information, \"Off\" to disable"), +AP_INIT_FLAG("SeeRequestTail", ap_set_reqtail, NULL, RSRC_CONF, + "For extended status, \"On\" to see the last 63 chars of " + "the request line, \"Off\" (default) to see the first 63"), + +/* + * These are default configuration directives that mpms can/should + * pay attention to. + * XXX These are not for all platforms, and even some Unix MPMs might not want + * some directives. + */ +AP_INIT_TAKE1("PidFile", ap_mpm_set_pidfile, NULL, RSRC_CONF, + "A file for logging the server process ID"), +AP_INIT_TAKE1("MaxRequestsPerChild", ap_mpm_set_max_requests, NULL, RSRC_CONF, + "Maximum number of connections a particular child serves before " + "dying. (DEPRECATED, use MaxConnectionsPerChild)"), +AP_INIT_TAKE1("MaxConnectionsPerChild", ap_mpm_set_max_requests, NULL, RSRC_CONF, + "Maximum number of connections a particular child serves before dying."), +AP_INIT_TAKE1("CoreDumpDirectory", ap_mpm_set_coredumpdir, NULL, RSRC_CONF, + "The location of the directory Apache changes to before dumping core"), +AP_INIT_TAKE1("MaxMemFree", ap_mpm_set_max_mem_free, NULL, RSRC_CONF, + "Maximum number of 1k blocks a particular child's allocator may hold."), +AP_INIT_TAKE1("ThreadStackSize", ap_mpm_set_thread_stacksize, NULL, RSRC_CONF, + "Size in bytes of stack used by threads handling client connections"), +#if AP_ENABLE_EXCEPTION_HOOK +AP_INIT_TAKE1("EnableExceptionHook", ap_mpm_set_exception_hook, NULL, RSRC_CONF, + "Controls whether exception hook may be called after a crash"), +#endif +AP_INIT_TAKE1("TraceEnable", set_trace_enable, NULL, RSRC_CONF, + "'on' (default), 'off' or 'extended' to trace request body content"), +AP_INIT_FLAG("MergeTrailers", set_merge_trailers, NULL, RSRC_CONF, + "merge request trailers into request headers or not"), +AP_INIT_ITERATE("Protocols", set_protocols, NULL, RSRC_CONF, + "Controls which protocols are allowed"), +AP_INIT_TAKE1("ProtocolsHonorOrder", set_protocols_honor_order, NULL, RSRC_CONF, + "'off' (default) or 'on' to respect given order of protocols, " + "by default the client specified order determines selection"), +AP_INIT_ITERATE("HttpProtocolOptions", set_http_protocol_options, NULL, RSRC_CONF, + "'Allow0.9' or 'Require1.0' (default); " + "'RegisteredMethods' or 'LenientMethods' (default); " + "'Unsafe' or 'Strict' (default). Sets HTTP acceptance rules"), +AP_INIT_ITERATE("RegisterHttpMethod", set_http_method, NULL, RSRC_CONF, + "Registers non-standard HTTP methods"), +{ NULL } +}; + +/***************************************************************** + * + * Core handlers for various phases of server operation... + */ + +AP_DECLARE_NONSTD(int) ap_core_translate(request_rec *r) +{ + apr_status_t rv; + char *path; + + /* XXX this seems too specific, this should probably become + * some general-case test + */ + if (r->proxyreq) { + return HTTP_FORBIDDEN; + } + if (!r->uri || ((r->uri[0] != '/') && strcmp(r->uri, "*"))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00126) + "Invalid URI in request %s", r->the_request); + return HTTP_BAD_REQUEST; + } + + if (r->server->path + && !strncmp(r->uri, r->server->path, r->server->pathlen) + && (r->server->path[r->server->pathlen - 1] == '/' + || r->uri[r->server->pathlen] == '/' + || r->uri[r->server->pathlen] == '\0')) + { + path = r->uri + r->server->pathlen; + } + else { + path = r->uri; + } + /* + * Make sure that we do not mess up the translation by adding two + * /'s in a row. This happens under windows when the document + * root ends with a / + */ + /* skip all leading /'s (e.g. http://localhost///foo) + * so we are looking at only the relative path. + */ + while (*path == '/') { + ++path; + } + if ((rv = apr_filepath_merge(&r->filename, ap_document_root(r), path, + APR_FILEPATH_TRUENAME + | APR_FILEPATH_SECUREROOT, r->pool)) + != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00127) + "Cannot map %s to file", r->the_request); + return HTTP_FORBIDDEN; + } + r->canonical_filename = r->filename; + + return OK; +} + +/***************************************************************** + * + * Test the filesystem name through directory_walk and file_walk + */ +static int core_map_to_storage(request_rec *r) +{ + int access_status; + + if ((access_status = ap_directory_walk(r))) { + return access_status; + } + + if ((access_status = ap_file_walk(r))) { + return access_status; + } + + return OK; +} + + +static int do_nothing(request_rec *r) { return OK; } + +static int core_override_type(request_rec *r) +{ + core_dir_config *conf = + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + + /* Check for overrides with ForceType / SetHandler + */ + if (conf->mime_type && strcmp(conf->mime_type, "none")) + ap_set_content_type(r, (char*) conf->mime_type); + + if (conf->expr_handler) { + const char *err; + const char *val; + val = ap_expr_str_exec(r, conf->expr_handler, &err); + if (err) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03154) + "Can't evaluate handler expression: %s", err); + return HTTP_INTERNAL_SERVER_ERROR; + } + + if (val != ap_strstr_c(val, "proxy:unix")) { + /* Retained for compatibility -- but not for UDS */ + char *tmp = apr_pstrdup(r->pool, val); + ap_str_tolower(tmp); + val = tmp; + } + + if (strcmp(val, "none")) { + r->handler = val; + } + } + else if (conf->handler && strcmp(conf->handler, "none")) { + r->handler = conf->handler; + } + + /* Deal with the poor soul who is trying to force path_info to be + * accepted within the core_handler, where they will let the subreq + * address its contents. This is toggled by the user in the very + * beginning of the fixup phase (here!), so modules should override the user's + * discretion in their own module fixup phase. It is tristate, if + * the user doesn't specify, the result is AP_REQ_DEFAULT_PATH_INFO. + * (which the module may interpret to its own customary behavior.) + * It won't be touched if the value is no longer AP_ACCEPT_PATHINFO_UNSET, + * so any module changing the value prior to the fixup phase + * OVERRIDES the user's choice. + */ + if ((r->used_path_info == AP_REQ_DEFAULT_PATH_INFO) + && (conf->accept_path_info != AP_ACCEPT_PATHINFO_UNSET)) { + /* No module knew better, and the user coded AcceptPathInfo */ + r->used_path_info = conf->accept_path_info; + } + + return OK; +} + +static int default_handler(request_rec *r) +{ + conn_rec *c = r->connection; + apr_bucket_brigade *bb; + apr_bucket *e; + core_dir_config *d; + int errstatus; + apr_file_t *fd = NULL; + apr_status_t status; + /* XXX if/when somebody writes a content-md5 filter we either need to + * remove this support or coordinate when to use the filter vs. + * when to use this code + * The current choice of when to compute the md5 here matches the 1.3 + * support fairly closely (unlike 1.3, we don't handle computing md5 + * when the charset is translated). + */ + int bld_content_md5; + + d = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + bld_content_md5 = (d->content_md5 == AP_CONTENT_MD5_ON) + && r->output_filters->frec->ftype != AP_FTYPE_RESOURCE; + + ap_allow_standard_methods(r, MERGE_ALLOW, M_GET, M_OPTIONS, M_POST, -1); + + /* If filters intend to consume the request body, they must + * register an InputFilter to slurp the contents of the POST + * data from the POST input stream. It no longer exists when + * the output filters are invoked by the default handler. + */ + if ((errstatus = ap_discard_request_body(r)) != OK) { + return errstatus; + } + + if (r->method_number == M_GET || r->method_number == M_POST) { + if (r->finfo.filetype == APR_NOFILE) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00128) + "File does not exist: %s", + apr_pstrcat(r->pool, r->filename, r->path_info, NULL)); + return HTTP_NOT_FOUND; + } + + /* Don't try to serve a dir. Some OSs do weird things with + * raw I/O on a dir. + */ + if (r->finfo.filetype == APR_DIR) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00129) + "Attempt to serve directory: %s", r->filename); + return HTTP_NOT_FOUND; + } + + if ((r->used_path_info != AP_REQ_ACCEPT_PATH_INFO) && + r->path_info && *r->path_info) + { + /* default to reject */ + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00130) + "File does not exist: %s", + apr_pstrcat(r->pool, r->filename, r->path_info, NULL)); + return HTTP_NOT_FOUND; + } + + /* We understood the (non-GET) method, but it might not be legal for + this particular resource. Check to see if the 'deliver_script' + flag is set. If so, then we go ahead and deliver the file since + it isn't really content (only GET normally returns content). + + Note: based on logic further above, the only possible non-GET + method at this point is POST. In the future, we should enable + script delivery for all methods. */ + if (r->method_number != M_GET) { + core_request_config *req_cfg; + + req_cfg = ap_get_core_module_config(r->request_config); + if (!req_cfg->deliver_script) { + /* The flag hasn't been set for this request. Punt. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00131) + "This resource does not accept the %s method.", + r->method); + return HTTP_METHOD_NOT_ALLOWED; + } + } + + + if ((status = apr_file_open(&fd, r->filename, APR_READ | APR_BINARY +#if APR_HAS_SENDFILE + | AP_SENDFILE_ENABLED(d->enable_sendfile) +#endif + , 0, r->pool)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00132) + "file permissions deny server access: %s", r->filename); + return HTTP_FORBIDDEN; + } + + ap_update_mtime(r, r->finfo.mtime); + ap_set_last_modified(r); + ap_set_etag(r); + ap_set_accept_ranges(r); + ap_set_content_length(r, r->finfo.size); + if (bld_content_md5) { + apr_table_setn(r->headers_out, "Content-MD5", + ap_md5digest(r->pool, fd)); + } + + bb = apr_brigade_create(r->pool, c->bucket_alloc); + + if ((errstatus = ap_meets_conditions(r)) != OK) { + apr_file_close(fd); + r->status = errstatus; + } + else { + e = apr_brigade_insert_file(bb, fd, 0, r->finfo.size, r->pool); + +#if APR_HAS_MMAP + if (d->enable_mmap == ENABLE_MMAP_OFF) { + (void)apr_bucket_file_enable_mmap(e, 0); + } +#endif + } + + e = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + + status = ap_pass_brigade(r->output_filters, bb); + if (status == APR_SUCCESS + || r->status != HTTP_OK + || c->aborted) { + return OK; + } + else { + /* no way to know what type of error occurred */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00133) + "default_handler: ap_pass_brigade returned %i", + status); + return AP_FILTER_ERROR; + } + } + else { /* unusual method (not GET or POST) */ + if (r->method_number == M_INVALID) { + /* See if this looks like an undecrypted SSL handshake attempt. + * It's safe to look a couple bytes into the_request if it exists, as it's + * always allocated at least MIN_LINE_ALLOC (80) bytes. + */ + if (r->the_request + && r->the_request[0] == 0x16 + && (r->the_request[1] == 0x2 || r->the_request[1] == 0x3)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00134) + "Invalid method in request %s - possible attempt to establish SSL connection on non-SSL port", r->the_request); + } else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00135) + "Invalid method in request %s", r->the_request); + } + return HTTP_NOT_IMPLEMENTED; + } + + if (r->method_number == M_OPTIONS) { + return ap_send_http_options(r); + } + return HTTP_METHOD_NOT_ALLOWED; + } +} + +/* Optional function coming from mod_logio, used for logging of output + * traffic + */ +APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *ap__logio_add_bytes_out; +APR_OPTIONAL_FN_TYPE(authz_some_auth_required) *ap__authz_ap_some_auth_required; + +/* Insist that at least one module will undertake to provide system + * security by dropping startup privileges. + */ +static int sys_privileges = 0; +AP_DECLARE(int) ap_sys_privileges_handlers(int inc) +{ + sys_privileges += inc; + return sys_privileges; +} + +static int check_errorlog_dir(apr_pool_t *p, server_rec *s) +{ + if (!s->error_fname || s->error_fname[0] == '|' + || strcmp(s->error_fname, "syslog") == 0 + || strncmp(s->error_fname, "syslog:", 7) == 0) { + return APR_SUCCESS; + } + else { + char *abs = ap_server_root_relative(p, s->error_fname); + char *dir = ap_make_dirstr_parent(p, abs); + apr_finfo_t finfo; + apr_status_t rv = apr_stat(&finfo, dir, APR_FINFO_TYPE, p); + if (rv == APR_SUCCESS && finfo.filetype != APR_DIR) + rv = APR_ENOTDIR; + if (rv != APR_SUCCESS) { + const char *desc = "main error log"; + if (s->defn_name) + desc = apr_psprintf(p, "error log of vhost defined at %s:%d", + s->defn_name, s->defn_line_number); + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, rv, + ap_server_conf, APLOGNO(02291) + "Cannot access directory '%s' for %s", dir, desc); + return !OK; + } + } + return OK; +} + +static int core_check_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + int rv = OK; + while (s) { + if (check_errorlog_dir(ptemp, s) != OK) + rv = !OK; + s = s->next; + } + return rv; +} + + +static int core_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ + ap_mutex_init(pconf); + + if (!saved_server_config_defines) + init_config_defines(pconf); + apr_pool_cleanup_register(pconf, NULL, reset_config_defines, + apr_pool_cleanup_null); + + ap_regcomp_set_default_cflags(AP_REG_DOLLAR_ENDONLY); + + mpm_common_pre_config(pconf); + + return OK; +} + +static int core_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + ap__logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out); + ident_lookup = APR_RETRIEVE_OPTIONAL_FN(ap_ident_lookup); + ap__authz_ap_some_auth_required = APR_RETRIEVE_OPTIONAL_FN(authz_some_auth_required); + authn_ap_auth_type = APR_RETRIEVE_OPTIONAL_FN(authn_ap_auth_type); + authn_ap_auth_name = APR_RETRIEVE_OPTIONAL_FN(authn_ap_auth_name); + access_compat_ap_satisfies = APR_RETRIEVE_OPTIONAL_FN(access_compat_ap_satisfies); + + set_banner(pconf); + ap_setup_make_content_type(pconf); + ap_setup_auth_internal(ptemp); + if (!sys_privileges) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(00136) + "Server MUST relinquish startup privileges before " + "accepting connections. Please ensure mod_unixd " + "or other system security module is loaded."); + return !OK; + } + apr_pool_cleanup_register(pconf, NULL, ap_mpm_end_gen_helper, + apr_pool_cleanup_null); + return OK; +} + +static void core_insert_filter(request_rec *r) +{ + core_dir_config *conf = (core_dir_config *) + ap_get_core_module_config(r->per_dir_config); + const char *filter, *filters = conf->output_filters; + + if (filters) { + while (*filters && (filter = ap_getword(r->pool, &filters, ';'))) { + ap_add_output_filter(filter, NULL, r, r->connection); + } + } + + filters = conf->input_filters; + if (filters) { + while (*filters && (filter = ap_getword(r->pool, &filters, ';'))) { + ap_add_input_filter(filter, NULL, r, r->connection); + } + } +} + +static apr_size_t num_request_notes = AP_NUM_STD_NOTES; + +static apr_status_t reset_request_notes(void *dummy) +{ + num_request_notes = AP_NUM_STD_NOTES; + return APR_SUCCESS; +} + +AP_DECLARE(apr_size_t) ap_register_request_note(void) +{ + apr_pool_cleanup_register(apr_hook_global_pool, NULL, reset_request_notes, + apr_pool_cleanup_null); + return num_request_notes++; +} + +AP_DECLARE(void **) ap_get_request_note(request_rec *r, apr_size_t note_num) +{ + core_request_config *req_cfg; + + if (note_num >= num_request_notes) { + return NULL; + } + + req_cfg = (core_request_config *) + ap_get_core_module_config(r->request_config); + + if (!req_cfg) { + return NULL; + } + + return &(req_cfg->notes[note_num]); +} + +AP_DECLARE(apr_socket_t *) ap_get_conn_socket(conn_rec *c) +{ + return ap_get_core_module_config(c->conn_config); +} + +static int core_create_req(request_rec *r) +{ + /* Alloc the config struct and the array of request notes in + * a single block for efficiency + */ + core_request_config *req_cfg; + + req_cfg = apr_pcalloc(r->pool, sizeof(core_request_config) + + sizeof(void *) * num_request_notes); + req_cfg->notes = (void **)((char *)req_cfg + sizeof(core_request_config)); + + /* ### temporarily enable script delivery as the default */ + req_cfg->deliver_script = 1; + + if (r->main) { + core_request_config *main_req_cfg = (core_request_config *) + ap_get_core_module_config(r->main->request_config); + req_cfg->bb = main_req_cfg->bb; + } + else { + req_cfg->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + } + + ap_set_core_module_config(r->request_config, req_cfg); + + return OK; +} + +static int core_create_proxy_req(request_rec *r, request_rec *pr) +{ + return core_create_req(pr); +} + +static conn_rec *core_create_conn(apr_pool_t *ptrans, server_rec *server, + apr_socket_t *csd, long id, void *sbh, + apr_bucket_alloc_t *alloc) +{ + apr_status_t rv; + conn_rec *c = (conn_rec *) apr_pcalloc(ptrans, sizeof(conn_rec)); + + c->sbh = sbh; + ap_update_child_status(c->sbh, SERVER_BUSY_READ, NULL); + + /* Got a connection structure, so initialize what fields we can + * (the rest are zeroed out by pcalloc). + */ + c->conn_config = ap_create_conn_config(ptrans); + c->notes = apr_table_make(ptrans, 5); + + c->pool = ptrans; + if ((rv = apr_socket_addr_get(&c->local_addr, APR_LOCAL, csd)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, APLOGNO(00137) + "apr_socket_addr_get(APR_LOCAL)"); + apr_socket_close(csd); + return NULL; + } + + apr_sockaddr_ip_get(&c->local_ip, c->local_addr); + if ((rv = apr_socket_addr_get(&c->client_addr, APR_REMOTE, csd)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, APLOGNO(00138) + "apr_socket_addr_get(APR_REMOTE)"); + apr_socket_close(csd); + return NULL; + } + + apr_sockaddr_ip_get(&c->client_ip, c->client_addr); + c->base_server = server; + + c->id = id; + c->bucket_alloc = alloc; + + c->clogging_input_filters = 0; + + return c; +} + +static int core_pre_connection(conn_rec *c, void *csd) +{ + core_net_rec *net = apr_palloc(c->pool, sizeof(*net)); + apr_status_t rv; + + /* The Nagle algorithm says that we should delay sending partial + * packets in hopes of getting more data. We don't want to do + * this; we are not telnet. There are bad interactions between + * persistent connections and Nagle's algorithm that have very severe + * performance penalties. (Failing to disable Nagle is not much of a + * problem with simple HTTP.) + */ + rv = apr_socket_opt_set(csd, APR_TCP_NODELAY, 1); + if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) { + /* expected cause is that the client disconnected already, + * hence the debug level + */ + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00139) + "apr_socket_opt_set(APR_TCP_NODELAY)"); + } + + /* The core filter requires the timeout mode to be set, which + * incidentally sets the socket to be nonblocking. If this + * is not initialized correctly, Linux - for example - will + * be initially blocking, while Solaris will be non blocking + * and any initial read will fail. + */ + rv = apr_socket_timeout_set(csd, c->base_server->timeout); + if (rv != APR_SUCCESS) { + /* expected cause is that the client disconnected already */ + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00140) + "apr_socket_timeout_set"); + } + + net->c = c; + net->in_ctx = NULL; + net->out_ctx = NULL; + net->client_socket = csd; + + ap_set_core_module_config(net->c->conn_config, csd); + ap_add_input_filter_handle(ap_core_input_filter_handle, net, NULL, net->c); + ap_add_output_filter_handle(ap_core_output_filter_handle, net, NULL, net->c); + return DONE; +} + +AP_DECLARE(int) ap_state_query(int query) +{ + switch (query) { + case AP_SQ_MAIN_STATE: + return ap_main_state; + case AP_SQ_RUN_MODE: + return ap_run_mode; + case AP_SQ_CONFIG_GEN: + return ap_config_generation; + default: + return AP_SQ_NOT_SUPPORTED; + } +} + +static apr_random_t *rng = NULL; +#if APR_HAS_THREADS +static apr_thread_mutex_t *rng_mutex = NULL; +#endif + +static void core_child_init(apr_pool_t *pchild, server_rec *s) +{ + apr_proc_t proc; +#if APR_HAS_THREADS + int threaded_mpm; + if (ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm) == APR_SUCCESS + && threaded_mpm) + { + apr_thread_mutex_create(&rng_mutex, APR_THREAD_MUTEX_DEFAULT, pchild); + } +#endif + /* The MPMs use plain fork() and not apr_proc_fork(), so we have to call + * apr_random_after_fork() manually in the child + */ + proc.pid = getpid(); + apr_random_after_fork(&proc); +} + +static void core_optional_fn_retrieve(void) +{ + ap_init_scoreboard(NULL); +} + +AP_CORE_DECLARE(void) ap_random_parent_after_fork(void) +{ + /* + * To ensure that the RNG state in the parent changes after the fork, we + * pull some data from the RNG and discard it. This ensures that the RNG + * states in the children are different even after the pid wraps around. + * As we only use apr_random for insecure random bytes, pulling 2 bytes + * should be enough. + * XXX: APR should probably have some dedicated API to do this, but it + * XXX: currently doesn't. + */ + apr_uint16_t data; + apr_random_insecure_bytes(rng, &data, sizeof(data)); +} + +AP_CORE_DECLARE(void) ap_init_rng(apr_pool_t *p) +{ + unsigned char seed[8]; + apr_status_t rv; + rng = apr_random_standard_new(p); + do { + rv = apr_generate_random_bytes(seed, sizeof(seed)); + if (rv != APR_SUCCESS) + goto error; + apr_random_add_entropy(rng, seed, sizeof(seed)); + rv = apr_random_insecure_ready(rng); + } while (rv == APR_ENOTENOUGHENTROPY); + if (rv == APR_SUCCESS) + return; +error: + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00141) + "Could not initialize random number generator"); + exit(1); +} + +AP_DECLARE(void) ap_random_insecure_bytes(void *buf, apr_size_t size) +{ +#if APR_HAS_THREADS + if (rng_mutex) + apr_thread_mutex_lock(rng_mutex); +#endif + /* apr_random_insecure_bytes can only fail with APR_ENOTENOUGHENTROPY, + * and we have ruled that out during initialization. Therefore we don't + * need to check the return code. + */ + apr_random_insecure_bytes(rng, buf, size); +#if APR_HAS_THREADS + if (rng_mutex) + apr_thread_mutex_unlock(rng_mutex); +#endif +} + +/* + * Finding a random number in a range. + * n' = a + n(b-a+1)/(M+1) + * where: + * n' = random number in range + * a = low end of range + * b = high end of range + * n = random number of 0..M + * M = maxint + * Algorithm 'borrowed' from PHP's rand() function. + */ +#define RAND_RANGE(__n, __min, __max, __tmax) \ +(__n) = (__min) + (long) ((double) ((__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0))) +AP_DECLARE(apr_uint32_t) ap_random_pick(apr_uint32_t min, apr_uint32_t max) +{ + apr_uint32_t number; +#if (!__GNUC__ || __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || \ + !__sparc__ || APR_SIZEOF_VOIDP != 8) + /* This triggers a gcc bug on sparc/64bit with gcc < 4.8, PR 52900 */ + if (max < 16384) { + apr_uint16_t num16; + ap_random_insecure_bytes(&num16, sizeof(num16)); + RAND_RANGE(num16, min, max, APR_UINT16_MAX); + number = num16; + } + else +#endif + { + ap_random_insecure_bytes(&number, sizeof(number)); + RAND_RANGE(number, min, max, APR_UINT32_MAX); + } + return number; +} + +static apr_status_t core_insert_network_bucket(conn_rec *c, + apr_bucket_brigade *bb, + apr_socket_t *socket) +{ + apr_bucket *e = apr_bucket_socket_create(socket, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + return APR_SUCCESS; +} + +static apr_status_t core_dirwalk_stat(apr_finfo_t *finfo, request_rec *r, + apr_int32_t wanted) +{ + return apr_stat(finfo, r->filename, wanted, r->pool); +} + +static void core_dump_config(apr_pool_t *p, server_rec *s) +{ + core_server_config *sconf = ap_get_core_module_config(s->module_config); + apr_file_t *out = NULL; + const char *tmp; + const char **defines; + int i; + if (!ap_exists_config_define("DUMP_RUN_CFG")) + return; + + apr_file_open_stdout(&out, p); + apr_file_printf(out, "ServerRoot: \"%s\"\n", ap_server_root); + tmp = ap_server_root_relative(p, sconf->ap_document_root); + apr_file_printf(out, "Main DocumentRoot: \"%s\"\n", tmp); + if (s->error_fname[0] != '|' + && strcmp(s->error_fname, "syslog") != 0 + && strncmp(s->error_fname, "syslog:", 7) != 0) + tmp = ap_server_root_relative(p, s->error_fname); + else + tmp = s->error_fname; + apr_file_printf(out, "Main ErrorLog: \"%s\"\n", tmp); + if (ap_scoreboard_fname) { + tmp = ap_server_root_relative(p, ap_scoreboard_fname); + apr_file_printf(out, "ScoreBoardFile: \"%s\"\n", tmp); + } + ap_dump_mutexes(p, s, out); + ap_mpm_dump_pidfile(p, out); + + defines = (const char **)ap_server_config_defines->elts; + for (i = 0; i < ap_server_config_defines->nelts; i++) { + const char *name = defines[i]; + const char *val = NULL; + if (server_config_defined_vars) + val = apr_table_get(server_config_defined_vars, name); + if (val) + apr_file_printf(out, "Define: %s=%s\n", name, val); + else + apr_file_printf(out, "Define: %s\n", name); + } +} + +static int core_upgrade_handler(request_rec *r) +{ + conn_rec *c = r->connection; + const char *upgrade; + + if (c->master) { + /* Not possible to perform an HTTP/1.1 upgrade from a slave + * connection. */ + return DECLINED; + } + + upgrade = apr_table_get(r->headers_in, "Upgrade"); + if (upgrade && *upgrade) { + const char *conn = apr_table_get(r->headers_in, "Connection"); + if (ap_find_token(r->pool, conn, "upgrade")) { + apr_array_header_t *offers = NULL; + const char *err; + + err = ap_parse_token_list_strict(r->pool, upgrade, &offers, 0); + if (err) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02910) + "parsing Upgrade header: %s", err); + return DECLINED; + } + + if (offers && offers->nelts > 0) { + const char *protocol = ap_select_protocol(c, r, NULL, offers); + if (protocol && strcmp(protocol, ap_get_protocol(c))) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02909) + "Upgrade selects '%s'", protocol); + /* Let the client know what we are upgrading to. */ + apr_table_clear(r->headers_out); + apr_table_setn(r->headers_out, "Upgrade", protocol); + apr_table_setn(r->headers_out, "Connection", "Upgrade"); + + r->status = HTTP_SWITCHING_PROTOCOLS; + r->status_line = ap_get_status_line(r->status); + ap_send_interim_response(r, 1); + + ap_switch_protocol(c, r, r->server, protocol); + + /* make sure httpd closes the connection after this */ + c->keepalive = AP_CONN_CLOSE; + return DONE; + } + } + } + } + else if (!c->keepalives) { + /* first request on a master connection, if we have protocols other + * than the current one enabled here, announce them to the + * client. If the client is already talking a protocol with requests + * on slave connections, leave it be. */ + const apr_array_header_t *upgrades; + ap_get_protocol_upgrades(c, r, NULL, 0, &upgrades); + if (upgrades && upgrades->nelts > 0) { + char *protocols = apr_array_pstrcat(r->pool, upgrades, ','); + apr_table_setn(r->headers_out, "Upgrade", protocols); + apr_table_setn(r->headers_out, "Connection", "Upgrade"); + } + } + + return DECLINED; +} + +static int core_upgrade_storage(request_rec *r) +{ + if ((r->method_number == M_OPTIONS) && r->uri && (r->uri[0] == '*') && + (r->uri[1] == '\0')) { + return core_upgrade_handler(r); + } + return DECLINED; +} + +static void register_hooks(apr_pool_t *p) +{ + errorlog_hash = apr_hash_make(p); + ap_register_log_hooks(p); + ap_register_config_hooks(p); + ap_expr_init(p); + + /* create_connection and pre_connection should always be hooked + * APR_HOOK_REALLY_LAST by core to give other modules the opportunity + * to install alternate network transports and stop other functions + * from being run. + */ + ap_hook_create_connection(core_create_conn, NULL, NULL, + APR_HOOK_REALLY_LAST); + ap_hook_pre_connection(core_pre_connection, NULL, NULL, + APR_HOOK_REALLY_LAST); + + ap_hook_pre_config(core_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_post_config(core_post_config,NULL,NULL,APR_HOOK_REALLY_FIRST); + ap_hook_check_config(core_check_config,NULL,NULL,APR_HOOK_FIRST); + ap_hook_test_config(core_dump_config,NULL,NULL,APR_HOOK_FIRST); + ap_hook_translate_name(ap_core_translate,NULL,NULL,APR_HOOK_REALLY_LAST); + ap_hook_map_to_storage(core_upgrade_storage,NULL,NULL,APR_HOOK_REALLY_FIRST); + ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST); + ap_hook_open_logs(ap_open_logs,NULL,NULL,APR_HOOK_REALLY_FIRST); + ap_hook_child_init(core_child_init,NULL,NULL,APR_HOOK_REALLY_FIRST); + ap_hook_child_init(ap_logs_child_init,NULL,NULL,APR_HOOK_MIDDLE); + ap_hook_handler(core_upgrade_handler,NULL,NULL,APR_HOOK_REALLY_FIRST); + ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST); + /* FIXME: I suspect we can eliminate the need for these do_nothings - Ben */ + ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST); + ap_hook_fixups(core_override_type,NULL,NULL,APR_HOOK_REALLY_FIRST); + ap_hook_create_request(core_create_req, NULL, NULL, APR_HOOK_MIDDLE); + APR_OPTIONAL_HOOK(proxy, create_req, core_create_proxy_req, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_pre_mpm(ap_create_scoreboard, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_status(ap_core_child_status, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_insert_network_bucket(core_insert_network_bucket, NULL, NULL, + APR_HOOK_REALLY_LAST); + ap_hook_dirwalk_stat(core_dirwalk_stat, NULL, NULL, APR_HOOK_REALLY_LAST); + ap_hook_open_htaccess(ap_open_htaccess, NULL, NULL, APR_HOOK_REALLY_LAST); + ap_hook_optional_fn_retrieve(core_optional_fn_retrieve, NULL, NULL, + APR_HOOK_MIDDLE); + + /* register the core's insert_filter hook and register core-provided + * filters + */ + ap_hook_insert_filter(core_insert_filter, NULL, NULL, APR_HOOK_MIDDLE); + + ap_core_input_filter_handle = + ap_register_input_filter("CORE_IN", ap_core_input_filter, + NULL, AP_FTYPE_NETWORK); + ap_content_length_filter_handle = + ap_register_output_filter("CONTENT_LENGTH", ap_content_length_filter, + NULL, AP_FTYPE_PROTOCOL); + ap_core_output_filter_handle = + ap_register_output_filter("CORE", ap_core_output_filter, + NULL, AP_FTYPE_NETWORK); + ap_subreq_core_filter_handle = + ap_register_output_filter("SUBREQ_CORE", ap_sub_req_output_filter, + NULL, AP_FTYPE_CONTENT_SET); + ap_old_write_func = + ap_register_output_filter("OLD_WRITE", ap_old_write_filter, + NULL, AP_FTYPE_RESOURCE - 10); +} + +AP_DECLARE_MODULE(core) = { + MPM20_MODULE_STUFF, + AP_PLATFORM_REWRITE_ARGS_HOOK, /* hook to run before apache parses args */ + create_core_dir_config, /* create per-directory config structure */ + merge_core_dir_configs, /* merge per-directory config structures */ + create_core_server_config, /* create per-server config structure */ + merge_core_server_configs, /* merge per-server config structures */ + core_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; + diff --git a/server/core_filters.c b/server/core_filters.c new file mode 100644 index 0000000..a6c2bd6 --- /dev/null +++ b/server/core_filters.c @@ -0,0 +1,923 @@ +/* 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. + */ + +/** + * @file core_filters.c + * @brief Core input/output network filters. + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_fnmatch.h" +#include "apr_hash.h" +#include "apr_thread_proc.h" /* for RLIMIT stuff */ + +#define APR_WANT_IOVEC +#define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" /* For index_of_response(). Grump. */ +#include "http_request.h" +#include "http_vhost.h" +#include "http_main.h" /* For the default_handler below... */ +#include "http_log.h" +#include "util_md5.h" +#include "http_connection.h" +#include "apr_buckets.h" +#include "util_filter.h" +#include "util_ebcdic.h" +#include "mpm_common.h" +#include "scoreboard.h" +#include "mod_core.h" +#include "ap_listen.h" + +#include "mod_so.h" /* for ap_find_loaded_module_symbol */ + +#define AP_MIN_SENDFILE_BYTES (256) + +/** + * Remove all zero length buckets from the brigade. + */ +#define BRIGADE_NORMALIZE(b) \ +do { \ + apr_bucket *e = APR_BRIGADE_FIRST(b); \ + do { \ + if (e->length == 0 && !APR_BUCKET_IS_METADATA(e)) { \ + apr_bucket *d; \ + d = APR_BUCKET_NEXT(e); \ + apr_bucket_delete(e); \ + e = d; \ + } \ + else { \ + e = APR_BUCKET_NEXT(e); \ + } \ + } while (!APR_BRIGADE_EMPTY(b) && (e != APR_BRIGADE_SENTINEL(b))); \ +} while (0) + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +struct core_output_filter_ctx { + apr_bucket_brigade *buffered_bb; + apr_bucket_brigade *tmp_flush_bb; + apr_pool_t *deferred_write_pool; + apr_size_t bytes_written; +}; + +struct core_filter_ctx { + apr_bucket_brigade *b; + apr_bucket_brigade *tmpbb; +}; + + +apr_status_t ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b, + ap_input_mode_t mode, apr_read_type_e block, + apr_off_t readbytes) +{ + apr_status_t rv; + core_net_rec *net = f->ctx; + core_ctx_t *ctx = net->in_ctx; + const char *str; + apr_size_t len; + + if (mode == AP_MODE_INIT) { + /* + * this mode is for filters that might need to 'initialize' + * a connection before reading request data from a client. + * NNTP over SSL for example needs to handshake before the + * server sends the welcome message. + * such filters would have changed the mode before this point + * is reached. however, protocol modules such as NNTP should + * not need to know anything about SSL. given the example, if + * SSL is not in the filter chain, AP_MODE_INIT is a noop. + */ + return APR_SUCCESS; + } + + if (!ctx) + { + net->in_ctx = ctx = apr_palloc(f->c->pool, sizeof(*ctx)); + ctx->b = apr_brigade_create(f->c->pool, f->c->bucket_alloc); + ctx->tmpbb = apr_brigade_create(f->c->pool, f->c->bucket_alloc); + /* seed the brigade with the client socket. */ + rv = ap_run_insert_network_bucket(f->c, ctx->b, net->client_socket); + if (rv != APR_SUCCESS) + return rv; + } + else if (APR_BRIGADE_EMPTY(ctx->b)) { + return APR_EOF; + } + + /* ### This is bad. */ + BRIGADE_NORMALIZE(ctx->b); + + /* check for empty brigade again *AFTER* BRIGADE_NORMALIZE() + * If we have lost our socket bucket (see above), we are EOF. + * + * Ideally, this should be returning SUCCESS with EOS bucket, but + * some higher-up APIs (spec. read_request_line via ap_rgetline) + * want an error code. */ + if (APR_BRIGADE_EMPTY(ctx->b)) { + return APR_EOF; + } + + if (mode == AP_MODE_GETLINE) { + /* we are reading a single LF line, e.g. the HTTP headers */ + rv = apr_brigade_split_line(b, ctx->b, block, HUGE_STRING_LEN); + /* We should treat EAGAIN here the same as we do for EOF (brigade is + * empty). We do this by returning whatever we have read. This may + * or may not be bogus, but is consistent (for now) with EOF logic. + */ + if (APR_STATUS_IS_EAGAIN(rv) && block == APR_NONBLOCK_READ) { + rv = APR_SUCCESS; + } + return rv; + } + + /* ### AP_MODE_PEEK is a horrific name for this mode because we also + * eat any CRLFs that we see. That's not the obvious intention of + * this mode. Determine whether anyone actually uses this or not. */ + if (mode == AP_MODE_EATCRLF) { + apr_bucket *e; + const char *c; + + /* The purpose of this loop is to ignore any CRLF (or LF) at the end + * of a request. Many browsers send extra lines at the end of POST + * requests. We use the PEEK method to determine if there is more + * data on the socket, so that we know if we should delay sending the + * end of one request until we have served the second request in a + * pipelined situation. We don't want to actually delay sending a + * response if the server finds a CRLF (or LF), becuause that doesn't + * mean that there is another request, just a blank line. + */ + while (1) { + if (APR_BRIGADE_EMPTY(ctx->b)) + return APR_EOF; + + e = APR_BRIGADE_FIRST(ctx->b); + + rv = apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ); + + if (rv != APR_SUCCESS) + return rv; + + c = str; + while (c < str + len) { + if (*c == APR_ASCII_LF) + c++; + else if (*c == APR_ASCII_CR && *(c + 1) == APR_ASCII_LF) + c += 2; + else + return APR_SUCCESS; + } + + /* If we reach here, we were a bucket just full of CRLFs, so + * just toss the bucket. */ + /* FIXME: Is this the right thing to do in the core? */ + apr_bucket_delete(e); + } + return APR_SUCCESS; + } + + /* If mode is EXHAUSTIVE, we want to just read everything until the end + * of the brigade, which in this case means the end of the socket. + * To do this, we attach the brigade that has currently been setaside to + * the brigade that was passed down, and send that brigade back. + * + * NOTE: This is VERY dangerous to use, and should only be done with + * extreme caution. FWLIW, this would be needed by an MPM like Perchild; + * such an MPM can easily request the socket and all data that has been + * read, which means that it can pass it to the correct child process. + */ + if (mode == AP_MODE_EXHAUSTIVE) { + apr_bucket *e; + + /* Tack on any buckets that were set aside. */ + APR_BRIGADE_CONCAT(b, ctx->b); + + /* Since we've just added all potential buckets (which will most + * likely simply be the socket bucket) we know this is the end, + * so tack on an EOS too. */ + /* We have read until the brigade was empty, so we know that we + * must be EOS. */ + e = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(b, e); + return APR_SUCCESS; + } + + /* read up to the amount they specified. */ + if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE) { + apr_bucket *e; + + AP_DEBUG_ASSERT(readbytes > 0); + + e = APR_BRIGADE_FIRST(ctx->b); + rv = apr_bucket_read(e, &str, &len, block); + + if (APR_STATUS_IS_EAGAIN(rv) && block == APR_NONBLOCK_READ) { + /* getting EAGAIN for a blocking read is an error; for a + * non-blocking read, return an empty brigade. */ + return APR_SUCCESS; + } + else if (rv != APR_SUCCESS) { + return rv; + } + else if (block == APR_BLOCK_READ && len == 0) { + /* We wanted to read some bytes in blocking mode. We read + * 0 bytes. Hence, we now assume we are EOS. + * + * When we are in normal mode, return an EOS bucket to the + * caller. + * When we are in speculative mode, leave ctx->b empty, so + * that the next call returns an EOS bucket. + */ + apr_bucket_delete(e); + + if (mode == AP_MODE_READBYTES) { + e = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(b, e); + } + return APR_SUCCESS; + } + + /* Have we read as much data as we wanted (be greedy)? */ + if (len < readbytes) { + apr_size_t bucket_len; + + rv = APR_SUCCESS; + /* We already registered the data in e in len */ + e = APR_BUCKET_NEXT(e); + while ((len < readbytes) && (rv == APR_SUCCESS) + && (e != APR_BRIGADE_SENTINEL(ctx->b))) { + /* Check for the availability of buckets with known length */ + if (e->length != -1) { + len += e->length; + e = APR_BUCKET_NEXT(e); + } + else { + /* + * Read from bucket, but non blocking. If there isn't any + * more data, well than this is fine as well, we will + * not wait for more since we already got some and we are + * only checking if there isn't more. + */ + rv = apr_bucket_read(e, &str, &bucket_len, + APR_NONBLOCK_READ); + if (rv == APR_SUCCESS) { + len += bucket_len; + e = APR_BUCKET_NEXT(e); + } + } + } + } + + /* We can only return at most what we read. */ + if (len < readbytes) { + readbytes = len; + } + + rv = apr_brigade_partition(ctx->b, readbytes, &e); + if (rv != APR_SUCCESS) { + return rv; + } + + /* Must do move before CONCAT */ + ctx->tmpbb = apr_brigade_split_ex(ctx->b, e, ctx->tmpbb); + + if (mode == AP_MODE_READBYTES) { + APR_BRIGADE_CONCAT(b, ctx->b); + } + else if (mode == AP_MODE_SPECULATIVE) { + apr_bucket *copy_bucket; + + for (e = APR_BRIGADE_FIRST(ctx->b); + e != APR_BRIGADE_SENTINEL(ctx->b); + e = APR_BUCKET_NEXT(e)) + { + rv = apr_bucket_copy(e, ©_bucket); + if (rv != APR_SUCCESS) { + return rv; + } + APR_BRIGADE_INSERT_TAIL(b, copy_bucket); + } + } + + /* Take what was originally there and place it back on ctx->b */ + APR_BRIGADE_CONCAT(ctx->b, ctx->tmpbb); + } + return APR_SUCCESS; +} + +static void setaside_remaining_output(ap_filter_t *f, + core_output_filter_ctx_t *ctx, + apr_bucket_brigade *bb, + conn_rec *c); + +static apr_status_t send_brigade_nonblocking(apr_socket_t *s, + apr_bucket_brigade *bb, + apr_size_t *bytes_written, + conn_rec *c); + +static void remove_empty_buckets(apr_bucket_brigade *bb); + +static apr_status_t send_brigade_blocking(apr_socket_t *s, + apr_bucket_brigade *bb, + apr_size_t *bytes_written, + conn_rec *c); + +static apr_status_t writev_nonblocking(apr_socket_t *s, + struct iovec *vec, apr_size_t nvec, + apr_bucket_brigade *bb, + apr_size_t *cumulative_bytes_written, + conn_rec *c); + +#if APR_HAS_SENDFILE +static apr_status_t sendfile_nonblocking(apr_socket_t *s, + apr_bucket *bucket, + apr_size_t *cumulative_bytes_written, + conn_rec *c); +#endif + +/* XXX: Should these be configurable parameters? */ +#define THRESHOLD_MIN_WRITE 4096 +#define THRESHOLD_MAX_BUFFER 65536 +#define MAX_REQUESTS_IN_PIPELINE 5 + +/* Optional function coming from mod_logio, used for logging of output + * traffic + */ +extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *ap__logio_add_bytes_out; + +apr_status_t ap_core_output_filter(ap_filter_t *f, apr_bucket_brigade *new_bb) +{ + conn_rec *c = f->c; + core_net_rec *net = f->ctx; + core_output_filter_ctx_t *ctx = net->out_ctx; + apr_bucket_brigade *bb = NULL; + apr_bucket *bucket, *next, *flush_upto = NULL; + apr_size_t bytes_in_brigade, non_file_bytes_in_brigade; + int eor_buckets_in_brigade, morphing_bucket_in_brigade; + apr_status_t rv; + int loglevel = ap_get_conn_module_loglevel(c, APLOG_MODULE_INDEX); + + /* Fail quickly if the connection has already been aborted. */ + if (c->aborted) { + if (new_bb != NULL) { + apr_brigade_cleanup(new_bb); + } + return APR_ECONNABORTED; + } + + if (ctx == NULL) { + ctx = apr_pcalloc(c->pool, sizeof(*ctx)); + net->out_ctx = (core_output_filter_ctx_t *)ctx; + /* + * Need to create tmp brigade with correct lifetime. Passing + * NULL to apr_brigade_split_ex would result in a brigade + * allocated from bb->pool which might be wrong. + */ + ctx->tmp_flush_bb = apr_brigade_create(c->pool, c->bucket_alloc); + /* same for buffered_bb and ap_save_brigade */ + ctx->buffered_bb = apr_brigade_create(c->pool, c->bucket_alloc); + } + + if (new_bb != NULL) + bb = new_bb; + + if ((ctx->buffered_bb != NULL) && + !APR_BRIGADE_EMPTY(ctx->buffered_bb)) { + if (new_bb != NULL) { + APR_BRIGADE_PREPEND(bb, ctx->buffered_bb); + } + else { + bb = ctx->buffered_bb; + } + c->data_in_output_filters = 0; + } + else if (new_bb == NULL) { + return APR_SUCCESS; + } + + /* Scan through the brigade and decide whether to attempt a write, + * and how much to write, based on the following rules: + * + * 1) The new_bb is null: Do a nonblocking write of as much as + * possible: do a nonblocking write of as much data as possible, + * then save the rest in ctx->buffered_bb. (If new_bb == NULL, + * it probably means that the MPM is doing asynchronous write + * completion and has just determined that this connection + * is writable.) + * + * 2) Determine if and up to which bucket we need to do a blocking + * write: + * + * a) The brigade contains a flush bucket: Do a blocking write + * of everything up that point. + * + * b) The request is in CONN_STATE_HANDLER state, and the brigade + * contains at least THRESHOLD_MAX_BUFFER bytes in non-file + * buckets: Do blocking writes until the amount of data in the + * buffer is less than THRESHOLD_MAX_BUFFER. (The point of this + * rule is to provide flow control, in case a handler is + * streaming out lots of data faster than the data can be + * sent to the client.) + * + * c) The request is in CONN_STATE_HANDLER state, and the brigade + * contains at least MAX_REQUESTS_IN_PIPELINE EOR buckets: + * Do blocking writes until less than MAX_REQUESTS_IN_PIPELINE EOR + * buckets are left. (The point of this rule is to prevent too many + * FDs being kept open by pipelined requests, possibly allowing a + * DoS). + * + * d) The brigade contains a morphing bucket: If there was no other + * reason to do a blocking write yet, try reading the bucket. If its + * contents fit into memory before THRESHOLD_MAX_BUFFER is reached, + * everything is fine. Otherwise we need to do a blocking write the + * up to and including the morphing bucket, because ap_save_brigade() + * would read the whole bucket into memory later on. + * + * 3) Actually do the blocking write up to the last bucket determined + * by rules 2a-d. The point of doing only one flush is to make as + * few calls to writev() as possible. + * + * 4) If the brigade contains at least THRESHOLD_MIN_WRITE + * bytes: Do a nonblocking write of as much data as possible, + * then save the rest in ctx->buffered_bb. + */ + + if (new_bb == NULL) { + rv = send_brigade_nonblocking(net->client_socket, bb, + &(ctx->bytes_written), c); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) { + /* The client has aborted the connection */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, + "core_output_filter: writing data to the network"); + apr_brigade_cleanup(bb); + c->aborted = 1; + return rv; + } + setaside_remaining_output(f, ctx, bb, c); + return APR_SUCCESS; + } + + bytes_in_brigade = 0; + non_file_bytes_in_brigade = 0; + eor_buckets_in_brigade = 0; + morphing_bucket_in_brigade = 0; + + for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); + bucket = next) { + next = APR_BUCKET_NEXT(bucket); + + if (!APR_BUCKET_IS_METADATA(bucket)) { + if (bucket->length == (apr_size_t)-1) { + /* + * A setaside of morphing buckets would read everything into + * memory. Instead, we will flush everything up to and + * including this bucket. + */ + morphing_bucket_in_brigade = 1; + } + else { + bytes_in_brigade += bucket->length; + if (!APR_BUCKET_IS_FILE(bucket)) + non_file_bytes_in_brigade += bucket->length; + } + } + else if (AP_BUCKET_IS_EOR(bucket)) { + eor_buckets_in_brigade++; + } + + if (APR_BUCKET_IS_FLUSH(bucket) + || non_file_bytes_in_brigade >= THRESHOLD_MAX_BUFFER + || morphing_bucket_in_brigade + || eor_buckets_in_brigade > MAX_REQUESTS_IN_PIPELINE) { + /* this segment of the brigade MUST be sent before returning. */ + + if (loglevel >= APLOG_TRACE6) { + char *reason = APR_BUCKET_IS_FLUSH(bucket) ? + "FLUSH bucket" : + (non_file_bytes_in_brigade >= THRESHOLD_MAX_BUFFER) ? + "THRESHOLD_MAX_BUFFER" : + morphing_bucket_in_brigade ? "morphing bucket" : + "MAX_REQUESTS_IN_PIPELINE"; + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, + "will flush because of %s", reason); + ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, + "seen in brigade%s: bytes: %" APR_SIZE_T_FMT + ", non-file bytes: %" APR_SIZE_T_FMT ", eor " + "buckets: %d, morphing buckets: %d", + flush_upto == NULL ? " so far" + : " since last flush point", + bytes_in_brigade, + non_file_bytes_in_brigade, + eor_buckets_in_brigade, + morphing_bucket_in_brigade); + } + /* + * Defer the actual blocking write to avoid doing many writes. + */ + flush_upto = next; + + bytes_in_brigade = 0; + non_file_bytes_in_brigade = 0; + eor_buckets_in_brigade = 0; + morphing_bucket_in_brigade = 0; + } + } + + if (flush_upto != NULL) { + ctx->tmp_flush_bb = apr_brigade_split_ex(bb, flush_upto, + ctx->tmp_flush_bb); + if (loglevel >= APLOG_TRACE8) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, + "flushing now"); + } + rv = send_brigade_blocking(net->client_socket, bb, + &(ctx->bytes_written), c); + if (rv != APR_SUCCESS) { + /* The client has aborted the connection */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, + "core_output_filter: writing data to the network"); + apr_brigade_cleanup(bb); + c->aborted = 1; + return rv; + } + if (loglevel >= APLOG_TRACE8) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, + "total bytes written: %" APR_SIZE_T_FMT, + ctx->bytes_written); + } + APR_BRIGADE_CONCAT(bb, ctx->tmp_flush_bb); + } + + if (loglevel >= APLOG_TRACE8) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, + "brigade contains: bytes: %" APR_SIZE_T_FMT + ", non-file bytes: %" APR_SIZE_T_FMT + ", eor buckets: %d, morphing buckets: %d", + bytes_in_brigade, non_file_bytes_in_brigade, + eor_buckets_in_brigade, morphing_bucket_in_brigade); + } + + if (bytes_in_brigade >= THRESHOLD_MIN_WRITE) { + rv = send_brigade_nonblocking(net->client_socket, bb, + &(ctx->bytes_written), c); + if ((rv != APR_SUCCESS) && (!APR_STATUS_IS_EAGAIN(rv))) { + /* The client has aborted the connection */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, + "core_output_filter: writing data to the network"); + apr_brigade_cleanup(bb); + c->aborted = 1; + return rv; + } + if (loglevel >= APLOG_TRACE8) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, + "tried nonblocking write, total bytes " + "written: %" APR_SIZE_T_FMT, + ctx->bytes_written); + } + } + + setaside_remaining_output(f, ctx, bb, c); + return APR_SUCCESS; +} + +/* + * This function assumes that either ctx->buffered_bb == NULL, or + * ctx->buffered_bb is empty, or ctx->buffered_bb == bb + */ +static void setaside_remaining_output(ap_filter_t *f, + core_output_filter_ctx_t *ctx, + apr_bucket_brigade *bb, + conn_rec *c) +{ + if (bb == NULL) { + return; + } + remove_empty_buckets(bb); + if (!APR_BRIGADE_EMPTY(bb)) { + c->data_in_output_filters = 1; + if (bb != ctx->buffered_bb) { + if (!ctx->deferred_write_pool) { + apr_pool_create(&ctx->deferred_write_pool, c->pool); + apr_pool_tag(ctx->deferred_write_pool, "deferred_write"); + } + ap_save_brigade(f, &(ctx->buffered_bb), &bb, + ctx->deferred_write_pool); + } + } + else if (ctx->deferred_write_pool) { + /* + * There are no more requests in the pipeline. We can just clear the + * pool. + */ + apr_pool_clear(ctx->deferred_write_pool); + } +} + +#ifndef APR_MAX_IOVEC_SIZE +#define MAX_IOVEC_TO_WRITE 16 +#else +#if APR_MAX_IOVEC_SIZE > 16 +#define MAX_IOVEC_TO_WRITE 16 +#else +#define MAX_IOVEC_TO_WRITE APR_MAX_IOVEC_SIZE +#endif +#endif + +static apr_status_t send_brigade_nonblocking(apr_socket_t *s, + apr_bucket_brigade *bb, + apr_size_t *bytes_written, + conn_rec *c) +{ + apr_bucket *bucket, *next; + apr_status_t rv; + struct iovec vec[MAX_IOVEC_TO_WRITE]; + apr_size_t nvec = 0; + + remove_empty_buckets(bb); + + for (bucket = APR_BRIGADE_FIRST(bb); + bucket != APR_BRIGADE_SENTINEL(bb); + bucket = next) { + next = APR_BUCKET_NEXT(bucket); +#if APR_HAS_SENDFILE + if (APR_BUCKET_IS_FILE(bucket)) { + apr_bucket_file *file_bucket = (apr_bucket_file *)(bucket->data); + apr_file_t *fd = file_bucket->fd; + /* Use sendfile to send this file unless: + * - the platform doesn't support sendfile, + * - the file is too small for sendfile to be useful, or + * - sendfile is disabled in the httpd config via "EnableSendfile off" + */ + + if ((apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) && + (bucket->length >= AP_MIN_SENDFILE_BYTES)) { + if (nvec > 0) { + (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 1); + rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); + if (rv != APR_SUCCESS) { + (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0); + return rv; + } + } + rv = sendfile_nonblocking(s, bucket, bytes_written, c); + if (nvec > 0) { + (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0); + nvec = 0; + } + if (rv != APR_SUCCESS) { + return rv; + } + break; + } + } +#endif /* APR_HAS_SENDFILE */ + /* didn't sendfile */ + if (!APR_BUCKET_IS_METADATA(bucket)) { + const char *data; + apr_size_t length; + + /* Non-blocking read first, in case this is a morphing + * bucket type. */ + rv = apr_bucket_read(bucket, &data, &length, APR_NONBLOCK_READ); + if (APR_STATUS_IS_EAGAIN(rv)) { + /* Read would block; flush any pending data and retry. */ + if (nvec) { + rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); + if (rv) { + return rv; + } + nvec = 0; + } + + rv = apr_bucket_read(bucket, &data, &length, APR_BLOCK_READ); + } + if (rv != APR_SUCCESS) { + return rv; + } + + /* reading may have split the bucket, so recompute next: */ + next = APR_BUCKET_NEXT(bucket); + vec[nvec].iov_base = (char *)data; + vec[nvec].iov_len = length; + nvec++; + if (nvec == MAX_IOVEC_TO_WRITE) { + rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); + nvec = 0; + if (rv != APR_SUCCESS) { + return rv; + } + break; + } + } + } + + if (nvec > 0) { + rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); + if (rv != APR_SUCCESS) { + return rv; + } + } + + remove_empty_buckets(bb); + + return APR_SUCCESS; +} + +static void remove_empty_buckets(apr_bucket_brigade *bb) +{ + apr_bucket *bucket; + while (((bucket = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) && + (APR_BUCKET_IS_METADATA(bucket) || (bucket->length == 0))) { + apr_bucket_delete(bucket); + } +} + +static apr_status_t send_brigade_blocking(apr_socket_t *s, + apr_bucket_brigade *bb, + apr_size_t *bytes_written, + conn_rec *c) +{ + apr_status_t rv; + + rv = APR_SUCCESS; + while (!APR_BRIGADE_EMPTY(bb)) { + rv = send_brigade_nonblocking(s, bb, bytes_written, c); + if (rv != APR_SUCCESS) { + if (APR_STATUS_IS_EAGAIN(rv)) { + /* Wait until we can send more data */ + apr_int32_t nsds; + apr_interval_time_t timeout; + apr_pollfd_t pollset; + + pollset.p = c->pool; + pollset.desc_type = APR_POLL_SOCKET; + pollset.reqevents = APR_POLLOUT; + pollset.desc.s = s; + apr_socket_timeout_get(s, &timeout); + do { + rv = apr_poll(&pollset, 1, &nsds, timeout); + } while (APR_STATUS_IS_EINTR(rv)); + if (rv != APR_SUCCESS) { + break; + } + } + else { + break; + } + } + } + return rv; +} + +static apr_status_t writev_nonblocking(apr_socket_t *s, + struct iovec *vec, apr_size_t nvec, + apr_bucket_brigade *bb, + apr_size_t *cumulative_bytes_written, + conn_rec *c) +{ + apr_status_t rv = APR_SUCCESS, arv; + apr_size_t bytes_written = 0, bytes_to_write = 0; + apr_size_t i, offset; + apr_interval_time_t old_timeout; + + arv = apr_socket_timeout_get(s, &old_timeout); + if (arv != APR_SUCCESS) { + return arv; + } + arv = apr_socket_timeout_set(s, 0); + if (arv != APR_SUCCESS) { + return arv; + } + + for (i = 0; i < nvec; i++) { + bytes_to_write += vec[i].iov_len; + } + offset = 0; + while (bytes_written < bytes_to_write) { + apr_size_t n = 0; + rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n); + if (n > 0) { + bytes_written += n; + for (i = offset; i < nvec; ) { + apr_bucket *bucket = APR_BRIGADE_FIRST(bb); + if (APR_BUCKET_IS_METADATA(bucket)) { + apr_bucket_delete(bucket); + } + else if (n >= vec[i].iov_len) { + apr_bucket_delete(bucket); + offset++; + n -= vec[i++].iov_len; + } + else { + apr_bucket_split(bucket, n); + apr_bucket_delete(bucket); + vec[i].iov_len -= n; + vec[i].iov_base = (char *) vec[i].iov_base + n; + break; + } + } + } + if (rv != APR_SUCCESS) { + break; + } + } + if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) { + ap__logio_add_bytes_out(c, bytes_written); + } + *cumulative_bytes_written += bytes_written; + + arv = apr_socket_timeout_set(s, old_timeout); + if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { + return arv; + } + else { + return rv; + } +} + +#if APR_HAS_SENDFILE + +static apr_status_t sendfile_nonblocking(apr_socket_t *s, + apr_bucket *bucket, + apr_size_t *cumulative_bytes_written, + conn_rec *c) +{ + apr_status_t rv = APR_SUCCESS; + apr_bucket_file *file_bucket; + apr_file_t *fd; + apr_size_t file_length; + apr_off_t file_offset; + apr_size_t bytes_written = 0; + + if (!APR_BUCKET_IS_FILE(bucket)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server, APLOGNO(00006) + "core_filter: sendfile_nonblocking: " + "this should never happen"); + return APR_EGENERAL; + } + file_bucket = (apr_bucket_file *)(bucket->data); + fd = file_bucket->fd; + file_length = bucket->length; + file_offset = bucket->start; + + if (bytes_written < file_length) { + apr_size_t n = file_length - bytes_written; + apr_status_t arv; + apr_interval_time_t old_timeout; + + arv = apr_socket_timeout_get(s, &old_timeout); + if (arv != APR_SUCCESS) { + return arv; + } + arv = apr_socket_timeout_set(s, 0); + if (arv != APR_SUCCESS) { + return arv; + } + rv = apr_socket_sendfile(s, fd, NULL, &file_offset, &n, 0); + if (rv == APR_SUCCESS) { + bytes_written += n; + file_offset += n; + } + arv = apr_socket_timeout_set(s, old_timeout); + if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { + rv = arv; + } + } + if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) { + ap__logio_add_bytes_out(c, bytes_written); + } + *cumulative_bytes_written += bytes_written; + if ((bytes_written < file_length) && (bytes_written > 0)) { + apr_bucket_split(bucket, bytes_written); + apr_bucket_delete(bucket); + } + else if (bytes_written == file_length) { + apr_bucket_delete(bucket); + } + return rv; +} + +#endif diff --git a/server/eoc_bucket.c b/server/eoc_bucket.c new file mode 100644 index 0000000..42b4e51 --- /dev/null +++ b/server/eoc_bucket.c @@ -0,0 +1,55 @@ +/* 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_connection.h" + +static apr_status_t eoc_bucket_read(apr_bucket *b, const char **str, + apr_size_t *len, apr_read_type_e block) +{ + *str = NULL; + *len = 0; + return APR_SUCCESS; +} + +AP_DECLARE(apr_bucket *) ap_bucket_eoc_make(apr_bucket *b) +{ + b->length = 0; + b->start = 0; + b->data = NULL; + b->type = &ap_bucket_type_eoc; + + return b; +} + +AP_DECLARE(apr_bucket *) ap_bucket_eoc_create(apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + return ap_bucket_eoc_make(b); +} + +AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_eoc = { + "EOC", 5, APR_BUCKET_METADATA, + apr_bucket_destroy_noop, + eoc_bucket_read, + apr_bucket_setaside_noop, + apr_bucket_split_notimpl, + apr_bucket_simple_copy +}; diff --git a/server/eor_bucket.c b/server/eor_bucket.c new file mode 100644 index 0000000..4d3e1ec --- /dev/null +++ b/server/eor_bucket.c @@ -0,0 +1,102 @@ +/* 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_request.h" +#include "http_protocol.h" +#include "scoreboard.h" + +static apr_status_t eor_bucket_cleanup(void *data) +{ + apr_bucket *b = (apr_bucket *)data; + request_rec *r = (request_rec *)b->data; + + if (r != NULL) { + /* + * If eor_bucket_destroy is called after us, this prevents + * eor_bucket_destroy from trying to destroy the pool again. + */ + b->data = NULL; + /* Update child status and log the transaction */ + ap_update_child_status(r->connection->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + if (ap_extended_status) { + ap_increment_counts(r->connection->sbh, r); + } + } + return APR_SUCCESS; +} + +static apr_status_t eor_bucket_read(apr_bucket *b, const char **str, + apr_size_t *len, apr_read_type_e block) +{ + *str = NULL; + *len = 0; + return APR_SUCCESS; +} + +AP_DECLARE(apr_bucket *) ap_bucket_eor_make(apr_bucket *b, request_rec *r) +{ + b->length = 0; + b->start = 0; + b->data = r; + b->type = &ap_bucket_type_eor; + + return b; +} + +AP_DECLARE(apr_bucket *) ap_bucket_eor_create(apr_bucket_alloc_t *list, + request_rec *r) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + if (r) { + /* + * Register a cleanup for the request pool as the eor bucket could + * have been allocated from a different pool then the request pool + * e.g. the parent pool of the request pool. In this case + * eor_bucket_destroy might be called at a point of time when the + * request pool had been already destroyed. + * We need to use a pre-cleanup here because a module may create a + * sub-pool which is still needed during the log_transaction hook. + */ + apr_pool_pre_cleanup_register(r->pool, (void *)b, eor_bucket_cleanup); + } + return ap_bucket_eor_make(b, r); +} + +static void eor_bucket_destroy(void *data) +{ + request_rec *r = (request_rec *)data; + + if (r) { + /* eor_bucket_cleanup will be called when the pool gets destroyed */ + apr_pool_destroy(r->pool); + } +} + +AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_eor = { + "EOR", 5, APR_BUCKET_METADATA, + eor_bucket_destroy, + eor_bucket_read, + apr_bucket_setaside_noop, + apr_bucket_split_notimpl, + apr_bucket_simple_copy +}; + diff --git a/server/error_bucket.c b/server/error_bucket.c new file mode 100644 index 0000000..52b55c3 --- /dev/null +++ b/server/error_bucket.c @@ -0,0 +1,77 @@ +/* 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 "http_protocol.h" +#include "apr_buckets.h" +#include "apr_strings.h" +#if APR_HAVE_STRINGS_H +#include <strings.h> +#endif + +static apr_status_t error_bucket_read(apr_bucket *b, const char **str, + apr_size_t *len, apr_read_type_e block) +{ + *str = NULL; + *len = 0; + return APR_SUCCESS; +} + +static void error_bucket_destroy(void *data) +{ + ap_bucket_error *h = data; + + if (apr_bucket_shared_destroy(h)) { + apr_bucket_free(h); + } +} + +AP_DECLARE(apr_bucket *) ap_bucket_error_make(apr_bucket *b, int error, + const char *buf, apr_pool_t *p) +{ + ap_bucket_error *h; + + h = apr_bucket_alloc(sizeof(*h), b->list); + h->status = error; + h->data = apr_pstrdup(p, buf); + + b = apr_bucket_shared_make(b, h, 0, 0); + b->type = &ap_bucket_type_error; + return b; +} + +AP_DECLARE(apr_bucket *) ap_bucket_error_create(int error, const char *buf, + apr_pool_t *p, + apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + if (!ap_is_HTTP_VALID_RESPONSE(error)) { + error = HTTP_INTERNAL_SERVER_ERROR; + } + return ap_bucket_error_make(b, error, buf, p); +} + +AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_error = { + "ERROR", 5, APR_BUCKET_METADATA, + error_bucket_destroy, + error_bucket_read, + apr_bucket_setaside_notimpl, + apr_bucket_split_notimpl, + apr_bucket_shared_copy +}; diff --git a/server/gen_test_char.c b/server/gen_test_char.c new file mode 100644 index 0000000..48ae6f4 --- /dev/null +++ b/server/gen_test_char.c @@ -0,0 +1,173 @@ +/* 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. + */ + +#ifdef CROSS_COMPILE + +#include <ctype.h> +#define apr_isalnum(c) (isalnum(((unsigned char)(c)))) +#define apr_isalpha(c) (isalpha(((unsigned char)(c)))) +#define apr_iscntrl(c) (iscntrl(((unsigned char)(c)))) +#define apr_isprint(c) (isprint(((unsigned char)(c)))) +#define APR_HAVE_STDIO_H 1 +#define APR_HAVE_STRING_H 1 + +#else + +#include "apr.h" +#include "apr_lib.h" + +#endif + +#if defined(WIN32) || defined(OS2) +#define NEED_ENHANCED_ESCAPES +#endif + +#if APR_HAVE_STDIO_H +#include <stdio.h> +#endif +#if APR_HAVE_STRING_H +#include <string.h> +#endif + +/* A bunch of functions in util.c scan strings looking for certain characters. + * To make that more efficient we encode a lookup table. + */ +#define T_ESCAPE_SHELL_CMD (0x01) +#define T_ESCAPE_PATH_SEGMENT (0x02) +#define T_OS_ESCAPE_PATH (0x04) +#define T_HTTP_TOKEN_STOP (0x08) +#define T_ESCAPE_LOGITEM (0x10) +#define T_ESCAPE_FORENSIC (0x20) +#define T_ESCAPE_URLENCODED (0x40) +#define T_HTTP_CTRLS (0x80) +#define T_VCHAR_OBSTEXT (0x100) + +int main(int argc, char *argv[]) +{ + unsigned c; + unsigned short flags; + + printf("/* this file is automatically generated by gen_test_char, " + "do not edit */\n" + "#define T_ESCAPE_SHELL_CMD (%u)\n" + "#define T_ESCAPE_PATH_SEGMENT (%u)\n" + "#define T_OS_ESCAPE_PATH (%u)\n" + "#define T_HTTP_TOKEN_STOP (%u)\n" + "#define T_ESCAPE_LOGITEM (%u)\n" + "#define T_ESCAPE_FORENSIC (%u)\n" + "#define T_ESCAPE_URLENCODED (%u)\n" + "#define T_HTTP_CTRLS (%u)\n" + "#define T_VCHAR_OBSTEXT (%u)\n" + "\n" + "static const unsigned short test_char_table[256] = {", + T_ESCAPE_SHELL_CMD, + T_ESCAPE_PATH_SEGMENT, + T_OS_ESCAPE_PATH, + T_HTTP_TOKEN_STOP, + T_ESCAPE_LOGITEM, + T_ESCAPE_FORENSIC, + T_ESCAPE_URLENCODED, + T_HTTP_CTRLS, + T_VCHAR_OBSTEXT); + + for (c = 0; c < 256; ++c) { + flags = 0; + if (c % 8 == 0) + printf("\n "); + + /* escape_shell_cmd */ +#ifdef NEED_ENHANCED_ESCAPES + /* Win32/OS2 have many of the same vulnerable characters + * as Unix sh, plus the carriage return and percent char. + * The proper escaping of these characters varies from unix + * since Win32/OS2 use carets or doubled-double quotes, + * and neither lf nor cr can be escaped. We escape unix + * specific as well, to assure that cross-compiled unix + * applications behave similarly when invoked on win32/os2. + * + * Rem please keep in-sync with apr's list in win32/filesys.c + */ + if (c && strchr("&;`'\"|*?~<>^()[]{}$\\\n\r%", c)) { + flags |= T_ESCAPE_SHELL_CMD; + } +#else + if (c && strchr("&;`'\"|*?~<>^()[]{}$\\\n", c)) { + flags |= T_ESCAPE_SHELL_CMD; + } +#endif + + if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:@&=~", c)) { + flags |= T_ESCAPE_PATH_SEGMENT; + } + + if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:;@&=/~", c)) { + flags |= T_OS_ESCAPE_PATH; + } + + if (!apr_isalnum(c) && !strchr(".-*_ ", c)) { + flags |= T_ESCAPE_URLENCODED; + } + + /* Stop for any non-'token' character, including ctrls, obs-text, + * and "tspecials" (RFC2068) a.k.a. "separators" (RFC2616), which + * is easer to express as characters remaining in the ASCII token set + */ + if (!c || !(apr_isalnum(c) || strchr("!#$%&'*+-.^_`|~", c))) { + flags |= T_HTTP_TOKEN_STOP; + } + + /* Catch CTRLs other than VCHAR, HT and SP, and obs-text (RFC7230 3.2) + * This includes only the C0 plane, not C1 (which is obs-text itself.) + * XXX: We should verify that all ASCII C0 ctrls/DEL corresponding to + * the current EBCDIC translation are captured, and ASCII C1 ctrls + * corresponding are all permitted (as they fall under obs-text rule) + */ + if (!c || (apr_iscntrl(c) && c != '\t')) { + flags |= T_HTTP_CTRLS; + } + + /* From RFC3986, the specific sets of gen-delims, sub-delims (2.2), + * and unreserved (2.3) that are possible somewhere within a URI. + * Spec requires all others to be %XX encoded, including obs-text. + */ + if (c && !apr_iscntrl(c) && c != ' ') { + flags |= T_VCHAR_OBSTEXT; + } + + /* For logging, escape all control characters, + * double quotes (because they delimit the request in the log file) + * backslashes (because we use backslash for escaping) + * and 8-bit chars with the high bit set + */ + if (c && (!apr_isprint(c) || c == '"' || c == '\\' || apr_iscntrl(c))) { + flags |= T_ESCAPE_LOGITEM; + } + + /* For forensic logging, escape all control characters, top bit set, + * :, | (used as delimiters) and % (used for escaping). + */ + if (!apr_isprint(c) || c == ':' || c == '|' || c == '%' + || apr_iscntrl(c) || !c) { + flags |= T_ESCAPE_FORENSIC; + } + + printf("0x%03x%c", flags, (c < 255) ? ',' : ' '); + } + + printf("\n};\n"); + + return 0; +} diff --git a/server/gen_test_char.dep b/server/gen_test_char.dep new file mode 100644 index 0000000..2bd9f6c --- /dev/null +++ b/server/gen_test_char.dep @@ -0,0 +1,7 @@ +# Microsoft Developer Studio Generated Dependency File, included by gen_test_char.mak + +.\gen_test_char.c : \ + "..\srclib\apr\include\apr.h"\ + "..\srclib\apr\include\apr_errno.h"\ + "..\srclib\apr\include\apr_lib.h"\ + diff --git a/server/gen_test_char.dsp b/server/gen_test_char.dsp new file mode 100644 index 0000000..cc0b943 --- /dev/null +++ b/server/gen_test_char.dsp @@ -0,0 +1,94 @@ +# Microsoft Developer Studio Project File - Name="gen_test_char" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=gen_test_char - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "gen_test_char.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "gen_test_char.mak" CFG="gen_test_char - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "gen_test_char - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "gen_test_char - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "gen_test_char - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /I "..\include" /I "..\srclib\apr\include" /I "..\srclib\apr-util\include" /I "..\os\win32" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Fd"Release\gen_test_char" /FD /c +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:console /pdb:"Release\gen_test_char.pdb" +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib /nologo /subsystem:console /pdb:"Release\gen_test_char.pdb" /opt:ref +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "gen_test_char - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "..\include" /I "..\srclib\apr\include" /I "..\srclib\apr-util\include" /I "..\os\win32" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Fd"Debug\gen_test_char" /FD /c +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:console /incremental:no /pdb:"Debug\gen_test_char.pdb" /debug /pdbtype:sept +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib /nologo /subsystem:console /incremental:no /pdb:"Debug\gen_test_char.pdb" /debug +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "gen_test_char - Win32 Release" +# Name "gen_test_char - Win32 Debug" +# Begin Source File + +SOURCE=.\gen_test_char.c +# End Source File +# End Target +# End Project diff --git a/server/gen_test_char.mak b/server/gen_test_char.mak new file mode 100644 index 0000000..d6e7bfb --- /dev/null +++ b/server/gen_test_char.mak @@ -0,0 +1,234 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on gen_test_char.dsp +!IF "$(CFG)" == "" +CFG=gen_test_char - Win32 Debug +!MESSAGE No configuration specified. Defaulting to gen_test_char - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "gen_test_char - Win32 Release" && "$(CFG)" != "gen_test_char - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "gen_test_char.mak" CFG="gen_test_char - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "gen_test_char - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "gen_test_char - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "gen_test_char - Win32 Release" + +OUTDIR=. +INTDIR=.\Release +# Begin Custom Macros +OutDir=. +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\gen_test_char.exe" + +!ELSE + +ALL : "libapr - Win32 Release" "$(OUTDIR)\gen_test_char.exe" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\gen_test_char.idb" + -@erase "$(INTDIR)\gen_test_char.obj" + -@erase "$(OUTDIR)\gen_test_char.exe" + +"$(INTDIR)" : + if not exist "$(INTDIR)/$(NULL)" mkdir "$(INTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /O2 /I "..\include" /I "..\srclib\apr\include" /I "..\srclib\apr-util\include" /I "..\os\win32" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\gen_test_char" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +RSC=rc.exe +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\gen_test_char.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:console /incremental:no /pdb:"$(OUTDIR)\Release\gen_test_char.pdb" /out:"$(OUTDIR)\gen_test_char.exe" /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\gen_test_char.obj" \ + "..\srclib\apr\Release\libapr-1.lib" + +"$(OUTDIR)\gen_test_char.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ELSEIF "$(CFG)" == "gen_test_char - Win32 Debug" + +OUTDIR=. +INTDIR=.\Debug +# Begin Custom Macros +OutDir=. +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\gen_test_char.exe" + +!ELSE + +ALL : "libapr - Win32 Debug" "$(OUTDIR)\gen_test_char.exe" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\gen_test_char.idb" + -@erase "$(INTDIR)\gen_test_char.obj" + -@erase "$(OUTDIR)\Debug\gen_test_char.pdb" + -@erase "$(OUTDIR)\gen_test_char.exe" + +"$(INTDIR)" : + if not exist "$(INTDIR)/$(NULL)" mkdir "$(INTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "..\include" /I "..\srclib\apr\include" /I "..\srclib\apr-util\include" /I "..\os\win32" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\gen_test_char" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +RSC=rc.exe +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\gen_test_char.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:console /incremental:no /pdb:"$(OUTDIR)\Debug\gen_test_char.pdb" /debug /out:"$(OUTDIR)\gen_test_char.exe" +LINK32_OBJS= \ + "$(INTDIR)\gen_test_char.obj" \ + "..\srclib\apr\Debug\libapr-1.lib" + +"$(OUTDIR)\gen_test_char.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("gen_test_char.dep") +!INCLUDE "gen_test_char.dep" +!ELSE +!MESSAGE Warning: cannot find "gen_test_char.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "gen_test_char - Win32 Release" || "$(CFG)" == "gen_test_char - Win32 Debug" + +!IF "$(CFG)" == "gen_test_char - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\server" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\server" + +!ELSEIF "$(CFG)" == "gen_test_char - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\server" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\server" + +!ENDIF + +SOURCE=.\gen_test_char.c + +"$(INTDIR)\gen_test_char.obj" : $(SOURCE) "$(INTDIR)" + + + +!ENDIF + diff --git a/server/listen.c b/server/listen.c new file mode 100644 index 0000000..a8e9e6f --- /dev/null +++ b/server/listen.c @@ -0,0 +1,938 @@ +/* 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 "apr_network_io.h" +#include "apr_strings.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_main.h" +#include "http_config.h" +#include "http_core.h" +#include "ap_listen.h" +#include "http_log.h" +#include "mpm_common.h" + +#include <stdlib.h> +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +AP_DECLARE_DATA ap_listen_rec *ap_listeners = NULL; + +/* Let ap_num_listen_buckets be global so that it can + * be printed by ap_log_mpm_common(), but keep the listeners + * buckets static since it is used only here to close them + * all (including duplicated) with ap_close_listeners(). + */ +AP_DECLARE_DATA int ap_num_listen_buckets; +static ap_listen_rec **ap_listen_buckets; + +/* Determine once, at runtime, whether or not SO_REUSEPORT + * is usable on this platform, and hence whether or not + * listeners can be duplicated (if configured). + */ +AP_DECLARE_DATA int ap_have_so_reuseport = -1; + +static ap_listen_rec *old_listeners; +static int ap_listenbacklog; +static int ap_listencbratio; +static int send_buffer_size; +static int receive_buffer_size; + +/* TODO: make_sock is just begging and screaming for APR abstraction */ +static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) +{ + apr_socket_t *s = server->sd; + int one = 1; +#if APR_HAVE_IPV6 +#ifdef AP_ENABLE_V4_MAPPED + int v6only_setting = 0; +#else + int v6only_setting = 1; +#endif +#endif + apr_status_t stat; + +#ifndef WIN32 + stat = apr_socket_opt_set(s, APR_SO_REUSEADDR, one); + if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00067) + "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)", + server->bind_addr); + apr_socket_close(s); + return stat; + } +#endif + + stat = apr_socket_opt_set(s, APR_SO_KEEPALIVE, one); + if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00068) + "make_sock: for address %pI, apr_socket_opt_set: (SO_KEEPALIVE)", + server->bind_addr); + apr_socket_close(s); + return stat; + } + +#if APR_HAVE_IPV6 + if (server->bind_addr->family == APR_INET6) { + stat = apr_socket_opt_set(s, APR_IPV6_V6ONLY, v6only_setting); + if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00069) + "make_sock: for address %pI, apr_socket_opt_set: " + "(IPV6_V6ONLY)", + server->bind_addr); + apr_socket_close(s); + return stat; + } + } +#endif + + /* + * To send data over high bandwidth-delay connections at full + * speed we must force the TCP window to open wide enough to keep the + * pipe full. The default window size on many systems + * is only 4kB. Cross-country WAN connections of 100ms + * at 1Mb/s are not impossible for well connected sites. + * If we assume 100ms cross-country latency, + * a 4kB buffer limits throughput to 40kB/s. + * + * To avoid this problem I've added the SendBufferSize directive + * to allow the web master to configure send buffer size. + * + * The trade-off of larger buffers is that more kernel memory + * is consumed. YMMV, know your customers and your network! + * + * -John Heidemann <johnh@isi.edu> 25-Oct-96 + * + * If no size is specified, use the kernel default. + */ + if (send_buffer_size) { + stat = apr_socket_opt_set(s, APR_SO_SNDBUF, send_buffer_size); + if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p, APLOGNO(00070) + "make_sock: failed to set SendBufferSize for " + "address %pI, using default", + server->bind_addr); + /* not a fatal error */ + } + } + if (receive_buffer_size) { + stat = apr_socket_opt_set(s, APR_SO_RCVBUF, receive_buffer_size); + if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p, APLOGNO(00071) + "make_sock: failed to set ReceiveBufferSize for " + "address %pI, using default", + server->bind_addr); + /* not a fatal error */ + } + } + +#if APR_TCP_NODELAY_INHERITED + ap_sock_disable_nagle(s); +#endif + +#if defined(SO_REUSEPORT) + if (ap_have_so_reuseport && ap_listencbratio > 0) { + int thesock; + apr_os_sock_get(&thesock, s); + if (setsockopt(thesock, SOL_SOCKET, SO_REUSEPORT, + (void *)&one, sizeof(int)) < 0) { + stat = apr_get_netos_error(); + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(02638) + "make_sock: for address %pI, apr_socket_opt_set: " + "(SO_REUSEPORT)", + server->bind_addr); + apr_socket_close(s); + return stat; + } + } +#endif + + if ((stat = apr_socket_bind(s, server->bind_addr)) != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, stat, p, APLOGNO(00072) + "make_sock: could not bind to address %pI", + server->bind_addr); + apr_socket_close(s); + return stat; + } + + if ((stat = apr_socket_listen(s, ap_listenbacklog)) != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, stat, p, APLOGNO(00073) + "make_sock: unable to listen for connections " + "on address %pI", + server->bind_addr); + apr_socket_close(s); + return stat; + } + +#ifdef WIN32 + /* I seriously doubt that this would work on Unix; I have doubts that + * it entirely solves the problem on Win32. However, since setting + * reuseaddr on the listener -prior- to binding the socket has allowed + * us to attach to the same port as an already running instance of + * Apache, or even another web server, we cannot identify that this + * port was exclusively granted to this instance of Apache. + * + * So set reuseaddr, but do not attempt to do so until we have the + * parent listeners successfully bound. + */ + stat = apr_socket_opt_set(s, APR_SO_REUSEADDR, one); + if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00074) + "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)", + server->bind_addr); + apr_socket_close(s); + return stat; + } +#endif + + server->sd = s; + server->active = 1; + + server->accept_func = NULL; + + return APR_SUCCESS; +} + +static const char* find_accf_name(server_rec *s, const char *proto) +{ + const char* accf; + core_server_config *conf = ap_get_core_module_config(s->module_config); + if (!proto) { + return NULL; + } + + accf = apr_table_get(conf->accf_map, proto); + + if (accf && !strcmp("none", accf)) { + return NULL; + } + + return accf; +} + +static void ap_apply_accept_filter(apr_pool_t *p, ap_listen_rec *lis, + server_rec *server) +{ + apr_socket_t *s = lis->sd; + const char *accf; + apr_status_t rv; + const char *proto; + + proto = lis->protocol; + + if (!proto) { + proto = ap_get_server_protocol(server); + } + + + accf = find_accf_name(server, proto); + + if (accf) { +#if APR_HAS_SO_ACCEPTFILTER + /* In APR 1.x, the 2nd and 3rd parameters are char * instead of + * const char *, so make a copy of those args here. + */ + rv = apr_socket_accept_filter(s, apr_pstrdup(p, accf), + apr_pstrdup(p, "")); + if (rv != APR_SUCCESS && !APR_STATUS_IS_ENOTIMPL(rv)) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p, APLOGNO(00075) + "Failed to enable the '%s' Accept Filter", + accf); + } +#else + rv = apr_socket_opt_set(s, APR_TCP_DEFER_ACCEPT, 30); + if (rv != APR_SUCCESS && !APR_STATUS_IS_ENOTIMPL(rv)) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p, APLOGNO(00076) + "Failed to enable APR_TCP_DEFER_ACCEPT"); + } +#endif + } +} + +static apr_status_t close_listeners_on_exec(void *v) +{ + ap_close_listeners(); + return APR_SUCCESS; +} + +static int find_listeners(ap_listen_rec **from, ap_listen_rec **to, + const char *addr, apr_port_t port) +{ + int found = 0; + + while (*from) { + apr_sockaddr_t *sa = (*from)->bind_addr; + + /* Some listeners are not real so they will not have a bind_addr. */ + if (sa) { + ap_listen_rec *new; + apr_port_t oldport; + + oldport = sa->port; + /* If both ports are equivalent, then if their names are equivalent, + * then we will re-use the existing record. + */ + if (port == oldport && + ((!addr && !sa->hostname) || + ((addr && sa->hostname) && !strcmp(sa->hostname, addr)))) { + found = 1; + if (!to) { + break; + } + new = *from; + *from = new->next; + new->next = *to; + *to = new; + continue; + } + } + + from = &(*from)->next; + } + + return found; +} + +static const char *alloc_listener(process_rec *process, const char *addr, + apr_port_t port, const char* proto, + void *slave) +{ + ap_listen_rec *last; + apr_status_t status; + apr_sockaddr_t *sa; + + /* see if we've got a listener for this address:port, which is an error */ + if (find_listeners(&ap_listeners, NULL, addr, port)) { + return "Cannot define multiple Listeners on the same IP:port"; + } + + /* see if we've got an old listener for this address:port */ + if (find_listeners(&old_listeners, &ap_listeners, addr, port)) { + if (ap_listeners->slave != slave) { + return "Cannot define a slave on the same IP:port as a Listener"; + } + return NULL; + } + + if ((status = apr_sockaddr_info_get(&sa, addr, APR_UNSPEC, port, 0, + process->pool)) + != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool, APLOGNO(00077) + "alloc_listener: failed to set up sockaddr for %s", + addr); + return "Listen setup failed"; + } + + /* Initialize to our last configured ap_listener. */ + last = ap_listeners; + while (last && last->next) { + last = last->next; + } + + while (sa) { + ap_listen_rec *new; + + /* this has to survive restarts */ + new = apr_palloc(process->pool, sizeof(ap_listen_rec)); + new->active = 0; + new->next = 0; + new->bind_addr = sa; + new->protocol = apr_pstrdup(process->pool, proto); + + /* Go to the next sockaddr. */ + sa = sa->next; + + status = apr_socket_create(&new->sd, new->bind_addr->family, + SOCK_STREAM, 0, process->pool); + +#if APR_HAVE_IPV6 + /* What could happen is that we got an IPv6 address, but this system + * doesn't actually support IPv6. Try the next address. + */ + if (status != APR_SUCCESS && !addr && + new->bind_addr->family == APR_INET6) { + continue; + } +#endif + if (status != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool, APLOGNO(00078) + "alloc_listener: failed to get a socket for %s", + addr); + return "Listen setup failed"; + } + + /* We need to preserve the order returned by getaddrinfo() */ + if (last == NULL) { + ap_listeners = last = new; + } else { + last->next = new; + last = new; + } + new->slave = slave; + } + + return NULL; +} +/* Evaluates to true if the (apr_sockaddr_t *) addr argument is the + * IPv4 match-any-address, 0.0.0.0. */ +#define IS_INADDR_ANY(addr) ((addr)->family == APR_INET \ + && (addr)->sa.sin.sin_addr.s_addr == INADDR_ANY) + +/* Evaluates to true if the (apr_sockaddr_t *) addr argument is the + * IPv6 match-any-address, [::]. */ +#define IS_IN6ADDR_ANY(addr) ((addr)->family == APR_INET6 \ + && IN6_IS_ADDR_UNSPECIFIED(&(addr)->sa.sin6.sin6_addr)) + +/** + * Create, open, listen, and bind all sockets. + * @param process The process record for the currently running server + * @return The number of open sockets + */ +static int open_listeners(apr_pool_t *pool) +{ + ap_listen_rec *lr; + ap_listen_rec *next; + ap_listen_rec *previous; + int num_open; + const char *userdata_key = "ap_open_listeners"; + void *data; +#if AP_NONBLOCK_WHEN_MULTI_LISTEN + int use_nonblock; +#endif + + /* Don't allocate a default listener. If we need to listen to a + * port, then the user needs to have a Listen directive in their + * config file. + */ + num_open = 0; + previous = NULL; + for (lr = ap_listeners; lr; previous = lr, lr = lr->next) { + if (lr->active) { + ++num_open; + } + else { +#if APR_HAVE_IPV6 + ap_listen_rec *cur; + int v6only_setting; + int skip = 0; + + /* If we have the unspecified IPv4 address (0.0.0.0) and + * the unspecified IPv6 address (::) is next, we need to + * swap the order of these in the list. We always try to + * bind to IPv6 first, then IPv4, since an IPv6 socket + * might be able to receive IPv4 packets if V6ONLY is not + * enabled, but never the other way around. + * Note: In some configurations, the unspecified IPv6 address + * could be even later in the list. This logic only corrects + * the situation where it is next in the list, such as when + * apr_sockaddr_info_get() returns an IPv4 and an IPv6 address, + * in that order. + */ + if (lr->next != NULL + && IS_INADDR_ANY(lr->bind_addr) + && lr->bind_addr->port == lr->next->bind_addr->port + && IS_IN6ADDR_ANY(lr->next->bind_addr)) { + /* Exchange lr and lr->next */ + next = lr->next; + lr->next = next->next; + next->next = lr; + if (previous) { + previous->next = next; + } + else { + ap_listeners = next; + } + lr = next; + } + + /* If we are trying to bind to 0.0.0.0 and a previous listener + * was :: on the same port and in turn that socket does not have + * the IPV6_V6ONLY flag set; we must skip the current attempt to + * listen (which would generate an error). IPv4 will be handled + * on the established IPv6 socket. + */ + if (IS_INADDR_ANY(lr->bind_addr) && previous) { + for (cur = ap_listeners; cur != lr; cur = cur->next) { + if (lr->bind_addr->port == cur->bind_addr->port + && IS_IN6ADDR_ANY(cur->bind_addr) + && apr_socket_opt_get(cur->sd, APR_IPV6_V6ONLY, + &v6only_setting) == APR_SUCCESS + && v6only_setting == 0) { + + /* Remove the current listener from the list */ + previous->next = lr->next; + lr = previous; /* maintain current value of previous after + * post-loop expression is evaluated + */ + skip = 1; + break; + } + } + if (skip) { + continue; + } + } +#endif + if (make_sock(pool, lr) == APR_SUCCESS) { + ++num_open; + } + else { +#if APR_HAVE_IPV6 + /* If we tried to bind to ::, and the next listener is + * on 0.0.0.0 with the same port, don't give a fatal + * error. The user will still get a warning from make_sock + * though. + */ + if (lr->next != NULL + && IS_IN6ADDR_ANY(lr->bind_addr) + && lr->bind_addr->port == lr->next->bind_addr->port + && IS_INADDR_ANY(lr->next->bind_addr)) { + + /* Remove the current listener from the list */ + if (previous) { + previous->next = lr->next; + } + else { + ap_listeners = lr->next; + } + + /* Although we've removed ourselves from the list, + * we need to make sure that the next iteration won't + * consider "previous" a working IPv6 '::' socket. + * Changing the family is enough to make sure the + * conditions before make_sock() fail. + */ + lr->bind_addr->family = AF_INET; + + continue; + } +#endif + /* fatal error */ + return -1; + } + } + } + + /* close the old listeners */ + ap_close_listeners_ex(old_listeners); + old_listeners = NULL; + +#if AP_NONBLOCK_WHEN_MULTI_LISTEN + /* if multiple listening sockets, make them non-blocking so that + * if select()/poll() reports readability for a reset connection that + * is already forgotten about by the time we call accept, we won't + * be hung until another connection arrives on that port + */ + use_nonblock = (ap_listeners && ap_listeners->next); + for (lr = ap_listeners; lr; lr = lr->next) { + apr_status_t status; + + status = apr_socket_opt_set(lr->sd, APR_SO_NONBLOCK, use_nonblock); + if (status != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, status, pool, APLOGNO(00079) + "unable to control socket non-blocking status"); + return -1; + } + } +#endif /* AP_NONBLOCK_WHEN_MULTI_LISTEN */ + + /* we come through here on both passes of the open logs phase + * only register the cleanup once... otherwise we try to close + * listening sockets twice when cleaning up prior to exec + */ + apr_pool_userdata_get(&data, userdata_key, pool); + if (!data) { + apr_pool_userdata_set((const void *)1, userdata_key, + apr_pool_cleanup_null, pool); + apr_pool_cleanup_register(pool, NULL, apr_pool_cleanup_null, + close_listeners_on_exec); + } + + return num_open ? 0 : -1; +} + +AP_DECLARE(int) ap_setup_listeners(server_rec *s) +{ + server_rec *ls; + server_addr_rec *addr; + ap_listen_rec *lr; + int num_listeners = 0; + const char* proto; + int found; + + for (ls = s; ls; ls = ls->next) { + proto = ap_get_server_protocol(ls); + if (!proto) { + found = 0; + /* No protocol was set for this vhost, + * use the default for this listener. + */ + for (addr = ls->addrs; addr && !found; addr = addr->next) { + for (lr = ap_listeners; lr; lr = lr->next) { + if (apr_sockaddr_equal(lr->bind_addr, addr->host_addr) && + lr->bind_addr->port == addr->host_port) { + ap_set_server_protocol(ls, lr->protocol); + found = 1; + break; + } + } + } + + if (!found) { + /* TODO: set protocol defaults per-Port, eg 25=smtp */ + ap_set_server_protocol(ls, "http"); + } + } + } + + if (open_listeners(s->process->pool)) { + return 0; + } + + for (lr = ap_listeners; lr; lr = lr->next) { + num_listeners++; + found = 0; + for (ls = s; ls && !found; ls = ls->next) { + for (addr = ls->addrs; addr && !found; addr = addr->next) { + if (apr_sockaddr_equal(lr->bind_addr, addr->host_addr) && + lr->bind_addr->port == addr->host_port) { + found = 1; + ap_apply_accept_filter(s->process->pool, lr, ls); + } + } + } + + if (!found) { + ap_apply_accept_filter(s->process->pool, lr, s); + } + } + + return num_listeners; +} + +AP_DECLARE(apr_status_t) ap_duplicate_listeners(apr_pool_t *p, server_rec *s, + ap_listen_rec ***buckets, + int *num_buckets) +{ + static int warn_once; + int i; + apr_status_t stat; + int use_nonblock = 0; + ap_listen_rec *lr; + + if (*num_buckets < 1) { + *num_buckets = 1; + if (ap_listencbratio > 0) { +#ifdef _SC_NPROCESSORS_ONLN + if (ap_have_so_reuseport) { + int num_online_cores = sysconf(_SC_NPROCESSORS_ONLN), + val = num_online_cores / ap_listencbratio; + if (val > 1) { + *num_buckets = val; + } + ap_log_perror(APLOG_MARK, APLOG_INFO, 0, p, APLOGNO(02819) + "Using %i listeners bucket(s) based on %i " + "online CPU cores and a ratio of %i", + *num_buckets, num_online_cores, + ap_listencbratio); + } + else +#endif + if (!warn_once) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, p, APLOGNO(02820) + "ListenCoresBucketsRatio ignored without " + "SO_REUSEPORT and _SC_NPROCESSORS_ONLN " + "support: using a single listeners bucket"); + warn_once = 1; + } + } + } + + *buckets = apr_pcalloc(p, *num_buckets * sizeof(ap_listen_rec *)); + (*buckets)[0] = ap_listeners; + + for (i = 1; i < *num_buckets; i++) { + ap_listen_rec *last = NULL; + lr = ap_listeners; + while (lr) { + ap_listen_rec *duplr; + char *hostname; + apr_port_t port; + apr_sockaddr_t *sa; + duplr = apr_palloc(p, sizeof(ap_listen_rec)); + duplr->slave = NULL; + duplr->protocol = apr_pstrdup(p, lr->protocol); + hostname = apr_pstrdup(p, lr->bind_addr->hostname); + port = lr->bind_addr->port; + apr_sockaddr_info_get(&sa, hostname, APR_UNSPEC, port, 0, p); + duplr->bind_addr = sa; + duplr->next = NULL; + stat = apr_socket_create(&duplr->sd, duplr->bind_addr->family, + SOCK_STREAM, 0, p); + if (stat != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, p, APLOGNO(02640) + "ap_duplicate_listeners: for address %pI, " + "cannot duplicate a new socket!", + duplr->bind_addr); + return stat; + } + make_sock(p, duplr); +#if AP_NONBLOCK_WHEN_MULTI_LISTEN + use_nonblock = (ap_listeners && ap_listeners->next); + stat = apr_socket_opt_set(duplr->sd, APR_SO_NONBLOCK, use_nonblock); + if (stat != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(02641) + "unable to control socket non-blocking status"); + return stat; + } +#endif + ap_apply_accept_filter(p, duplr, s); + + if (last == NULL) { + (*buckets)[i] = last = duplr; + } + else { + last->next = duplr; + last = duplr; + } + lr = lr->next; + } + } + + ap_listen_buckets = *buckets; + ap_num_listen_buckets = *num_buckets; + return APR_SUCCESS; +} + +AP_DECLARE_NONSTD(void) ap_close_listeners(void) +{ + int i; + + ap_close_listeners_ex(ap_listeners); + + /* Start from index 1 since either ap_duplicate_listeners() + * was called and ap_listen_buckets[0] == ap_listeners, or + * it wasn't and ap_num_listen_buckets == 0. + */ + for (i = 1; i < ap_num_listen_buckets; i++) { + ap_close_listeners_ex(ap_listen_buckets[i]); + } +} + +AP_DECLARE_NONSTD(void) ap_close_listeners_ex(ap_listen_rec *listeners) +{ + ap_listen_rec *lr; + for (lr = listeners; lr; lr = lr->next) { + apr_socket_close(lr->sd); + lr->active = 0; + } +} + +AP_DECLARE_NONSTD(int) ap_close_selected_listeners(ap_slave_t *slave) +{ + ap_listen_rec *lr; + int n = 0; + + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->slave != slave) { + apr_socket_close(lr->sd); + lr->active = 0; + } + else { + ++n; + } + } + return n; +} + +AP_DECLARE(void) ap_listen_pre_config(void) +{ + old_listeners = ap_listeners; + ap_listeners = NULL; + ap_listen_buckets = NULL; + ap_num_listen_buckets = 0; + ap_listenbacklog = DEFAULT_LISTENBACKLOG; + ap_listencbratio = 0; + + /* Check once whether or not SO_REUSEPORT is supported. */ + if (ap_have_so_reuseport < 0) { + /* This is limited to Linux with defined SO_REUSEPORT (ie. 3.9+) for + * now since the implementation evenly distributes connections across + * all the listening threads/processes. + * + * *BSDs have SO_REUSEPORT too but with a different semantic: the first + * wildcard address bound socket or the last non-wildcard address bound + * socket will receive connections (no evenness garantee); the rest of + * the sockets bound to the same port will not. + * This can't (always) work for httpd. + * + * TODO: latests DragonFlyBSD's SO_REUSEPORT (seems to?) have the same + * semantic as Linux, so we may need HAVE_SO_REUSEPORT available from + * configure.in some day. + */ +#if defined(SO_REUSEPORT) && defined(__linux__) + apr_socket_t *sock; + if (apr_socket_create(&sock, APR_UNSPEC, SOCK_STREAM, 0, + ap_pglobal) == APR_SUCCESS) { + int thesock, on = 1; + apr_os_sock_get(&thesock, sock); + ap_have_so_reuseport = (setsockopt(thesock, SOL_SOCKET, + SO_REUSEPORT, (void *)&on, + sizeof(int)) == 0); + apr_socket_close(sock); + } + else +#endif + ap_have_so_reuseport = 0; + + } +} + +AP_DECLARE_NONSTD(const char *) ap_set_listener(cmd_parms *cmd, void *dummy, + int argc, char *const argv[]) +{ + char *host, *scope_id, *proto; + apr_port_t port; + apr_status_t rv; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + if (argc < 1 || argc > 2) { + return "Listen requires 1 or 2 arguments."; + } + + rv = apr_parse_addr_port(&host, &scope_id, &port, argv[0], cmd->pool); + if (rv != APR_SUCCESS) { + return "Invalid address or port"; + } + + if (host && !strcmp(host, "*")) { + host = NULL; + } + + if (scope_id) { + /* XXX scope id support is useful with link-local IPv6 addresses */ + return "Scope id is not supported"; + } + + if (!port) { + return "Port must be specified"; + } + + if (argc != 2) { + if (port == 443) { + proto = "https"; + } else { + proto = "http"; + } + } + else { + proto = apr_pstrdup(cmd->pool, argv[1]); + ap_str_tolower(proto); + } + + return alloc_listener(cmd->server->process, host, port, proto, NULL); +} + +AP_DECLARE_NONSTD(const char *) ap_set_listenbacklog(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + int b; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + b = atoi(arg); + if (b < 1) { + return "ListenBacklog must be > 0"; + } + + ap_listenbacklog = b; + return NULL; +} + +AP_DECLARE_NONSTD(const char *) ap_set_listencbratio(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + int b; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + b = atoi(arg); + if (b < 1) { + return "ListenCoresBucketsRatio must be > 0"; + } + + ap_listencbratio = b; + return NULL; +} + +AP_DECLARE_NONSTD(const char *) ap_set_send_buffer_size(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + int s = atoi(arg); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + if (s < 512 && s != 0) { + return "SendBufferSize must be >= 512 bytes, or 0 for system default."; + } + + send_buffer_size = s; + return NULL; +} + +AP_DECLARE_NONSTD(const char *) ap_set_receive_buffer_size(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + int s = atoi(arg); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + if (s < 512 && s != 0) { + return "ReceiveBufferSize must be >= 512 bytes, or 0 for system default."; + } + + receive_buffer_size = s; + return NULL; +} diff --git a/server/log.c b/server/log.c new file mode 100644 index 0000000..42d0b8f --- /dev/null +++ b/server/log.c @@ -0,0 +1,1957 @@ +/* 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_log.c: Dealing with the logs and errors + * + * Rob McCool + * + */ + +#include "apr.h" +#include "apr_general.h" /* for signal stuff */ +#include "apr_strings.h" +#include "apr_errno.h" +#include "apr_thread_proc.h" +#include "apr_lib.h" +#include "apr_signal.h" +#include "apr_portable.h" +#include "apr_base64.h" + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_STDARG_H +#include <stdarg.h> +#endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_PROCESS_H +#include <process.h> /* for getpid() on Win32 */ +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_main.h" +#include "util_time.h" +#include "ap_mpm.h" +#include "ap_listen.h" + +#if HAVE_GETTID +#include <sys/syscall.h> +#include <sys/types.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +typedef struct { + const char *t_name; + int t_val; +} TRANS; + +APR_HOOK_STRUCT( + APR_HOOK_LINK(error_log) + APR_HOOK_LINK(generate_log_id) +) + +int AP_DECLARE_DATA ap_default_loglevel = DEFAULT_LOGLEVEL; + +#ifdef HAVE_SYSLOG + +static const TRANS facilities[] = { + {"auth", LOG_AUTH}, +#ifdef LOG_AUTHPRIV + {"authpriv",LOG_AUTHPRIV}, +#endif +#ifdef LOG_CRON + {"cron", LOG_CRON}, +#endif +#ifdef LOG_DAEMON + {"daemon", LOG_DAEMON}, +#endif +#ifdef LOG_FTP + {"ftp", LOG_FTP}, +#endif +#ifdef LOG_KERN + {"kern", LOG_KERN}, +#endif +#ifdef LOG_LPR + {"lpr", LOG_LPR}, +#endif +#ifdef LOG_MAIL + {"mail", LOG_MAIL}, +#endif +#ifdef LOG_NEWS + {"news", LOG_NEWS}, +#endif +#ifdef LOG_SYSLOG + {"syslog", LOG_SYSLOG}, +#endif +#ifdef LOG_USER + {"user", LOG_USER}, +#endif +#ifdef LOG_UUCP + {"uucp", LOG_UUCP}, +#endif +#ifdef LOG_LOCAL0 + {"local0", LOG_LOCAL0}, +#endif +#ifdef LOG_LOCAL1 + {"local1", LOG_LOCAL1}, +#endif +#ifdef LOG_LOCAL2 + {"local2", LOG_LOCAL2}, +#endif +#ifdef LOG_LOCAL3 + {"local3", LOG_LOCAL3}, +#endif +#ifdef LOG_LOCAL4 + {"local4", LOG_LOCAL4}, +#endif +#ifdef LOG_LOCAL5 + {"local5", LOG_LOCAL5}, +#endif +#ifdef LOG_LOCAL6 + {"local6", LOG_LOCAL6}, +#endif +#ifdef LOG_LOCAL7 + {"local7", LOG_LOCAL7}, +#endif + {NULL, -1}, +}; +#endif + +static const TRANS priorities[] = { + {"emerg", APLOG_EMERG}, + {"alert", APLOG_ALERT}, + {"crit", APLOG_CRIT}, + {"error", APLOG_ERR}, + {"warn", APLOG_WARNING}, + {"notice", APLOG_NOTICE}, + {"info", APLOG_INFO}, + {"debug", APLOG_DEBUG}, + {"trace1", APLOG_TRACE1}, + {"trace2", APLOG_TRACE2}, + {"trace3", APLOG_TRACE3}, + {"trace4", APLOG_TRACE4}, + {"trace5", APLOG_TRACE5}, + {"trace6", APLOG_TRACE6}, + {"trace7", APLOG_TRACE7}, + {"trace8", APLOG_TRACE8}, + {NULL, -1}, +}; + +static apr_pool_t *stderr_pool = NULL; + +static apr_file_t *stderr_log = NULL; + +/* track pipe handles to close in child process */ +typedef struct read_handle_t { + struct read_handle_t *next; + apr_file_t *handle; +} read_handle_t; + +static read_handle_t *read_handles; + +/** + * @brief The piped logging structure. + * + * Piped logs are used to move functionality out of the main server. + * For example, log rotation is done with piped logs. + */ +struct piped_log { + /** The pool to use for the piped log */ + apr_pool_t *p; + /** The pipe between the server and the logging process */ + apr_file_t *read_fd, *write_fd; +#ifdef AP_HAVE_RELIABLE_PIPED_LOGS + /** The name of the program the logging process is running */ + char *program; + /** The pid of the logging process */ + apr_proc_t *pid; + /** How to reinvoke program when it must be replaced */ + apr_cmdtype_e cmdtype; +#endif +}; + +AP_DECLARE(apr_file_t *) ap_piped_log_read_fd(piped_log *pl) +{ + return pl->read_fd; +} + +AP_DECLARE(apr_file_t *) ap_piped_log_write_fd(piped_log *pl) +{ + return pl->write_fd; +} + +/* remember to close this handle in the child process + * + * On Win32 this makes zero sense, because we don't + * take the parent process's child procs. + * If the win32 parent instead passed each and every + * logger write handle from itself down to the child, + * and the parent manages all aspects of keeping the + * reliable pipe log children alive, this would still + * make no sense :) Cripple it on Win32. + */ +static void close_handle_in_child(apr_pool_t *p, apr_file_t *f) +{ +#ifndef WIN32 + read_handle_t *new_handle; + + new_handle = apr_pcalloc(p, sizeof(read_handle_t)); + new_handle->next = read_handles; + new_handle->handle = f; + read_handles = new_handle; +#endif +} + +void ap_logs_child_init(apr_pool_t *p, server_rec *s) +{ + read_handle_t *cur = read_handles; + + while (cur) { + apr_file_close(cur->handle); + cur = cur->next; + } +} + +AP_DECLARE(void) ap_open_stderr_log(apr_pool_t *p) +{ + apr_file_open_stderr(&stderr_log, p); +} + +AP_DECLARE(apr_status_t) ap_replace_stderr_log(apr_pool_t *p, + const char *fname) +{ + apr_file_t *stderr_file; + apr_status_t rc; + char *filename = ap_server_root_relative(p, fname); + if (!filename) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, + APR_EBADPATH, ap_server_conf, APLOGNO(00085) "Invalid -E error log file %s", + fname); + return APR_EBADPATH; + } + if ((rc = apr_file_open(&stderr_file, filename, + APR_APPEND | APR_WRITE | APR_CREATE | APR_LARGEFILE, + APR_OS_DEFAULT, p)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, ap_server_conf, APLOGNO(00086) + "%s: could not open error log file %s.", + ap_server_argv0, fname); + return rc; + } + if (!stderr_pool) { + /* This is safe provided we revert it when we are finished. + * We don't manager the callers pool! + */ + stderr_pool = p; + } + if ((rc = apr_file_open_stderr(&stderr_log, stderr_pool)) + == APR_SUCCESS) { + apr_file_flush(stderr_log); + if ((rc = apr_file_dup2(stderr_log, stderr_file, stderr_pool)) + == APR_SUCCESS) { + apr_file_close(stderr_file); + /* + * You might ponder why stderr_pool should survive? + * The trouble is, stderr_pool may have s_main->error_log, + * so we aren't in a position to destory stderr_pool until + * the next recycle. There's also an apparent bug which + * is not; if some folk decided to call this function before + * the core open error logs hook, this pool won't survive. + * Neither does the stderr logger, so this isn't a problem. + */ + } + } + /* Revert, see above */ + if (stderr_pool == p) + stderr_pool = NULL; + + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, NULL, APLOGNO(00087) + "unable to replace stderr with error log file"); + } + return rc; +} + +static void log_child_errfn(apr_pool_t *pool, apr_status_t err, + const char *description) +{ + ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL, APLOGNO(00088) + "%s", description); +} + +/* Create a child process running PROGNAME with a pipe connected to + * the childs stdin. The write-end of the pipe will be placed in + * *FPIN on successful return. If dummy_stderr is non-zero, the + * stderr for the child will be the same as the stdout of the parent. + * Otherwise the child will inherit the stderr from the parent. */ +static int log_child(apr_pool_t *p, const char *progname, + apr_file_t **fpin, apr_cmdtype_e cmdtype, + int dummy_stderr) +{ + /* Child process code for 'ErrorLog "|..."'; + * may want a common framework for this, since I expect it will + * be common for other foo-loggers to want this sort of thing... + */ + apr_status_t rc; + apr_procattr_t *procattr; + apr_proc_t *procnew; + apr_file_t *errfile; + + if (((rc = apr_procattr_create(&procattr, p)) == APR_SUCCESS) + && ((rc = apr_procattr_dir_set(procattr, + ap_server_root)) == APR_SUCCESS) + && ((rc = apr_procattr_cmdtype_set(procattr, cmdtype)) == APR_SUCCESS) + && ((rc = apr_procattr_io_set(procattr, + APR_FULL_BLOCK, + APR_NO_PIPE, + APR_NO_PIPE)) == APR_SUCCESS) + && ((rc = apr_procattr_error_check_set(procattr, 1)) == APR_SUCCESS) + && ((rc = apr_procattr_child_errfn_set(procattr, log_child_errfn)) + == APR_SUCCESS)) { + char **args; + + apr_tokenize_to_argv(progname, &args, p); + procnew = (apr_proc_t *)apr_pcalloc(p, sizeof(*procnew)); + + if (dummy_stderr) { + if ((rc = apr_file_open_stdout(&errfile, p)) == APR_SUCCESS) + rc = apr_procattr_child_err_set(procattr, errfile, NULL); + } + + rc = apr_proc_create(procnew, args[0], (const char * const *)args, + NULL, procattr, p); + + if (rc == APR_SUCCESS) { + apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT); + (*fpin) = procnew->in; + /* read handle to pipe not kept open, so no need to call + * close_handle_in_child() + */ + } + } + + return rc; +} + +/* Open the error log for the given server_rec. If IS_MAIN is + * non-zero, s is the main server. */ +static int open_error_log(server_rec *s, int is_main, apr_pool_t *p) +{ + const char *fname; + int rc; + + if (*s->error_fname == '|') { + apr_file_t *dummy = NULL; + apr_cmdtype_e cmdtype = APR_PROGRAM_ENV; + fname = s->error_fname + 1; + + /* In 2.4 favor PROGRAM_ENV, accept "||prog" syntax for compatibility + * and "|$cmd" to override the default. + * Any 2.2 backport would continue to favor SHELLCMD_ENV so there + * accept "||prog" to override, and "|$cmd" to ease conversion. + */ + if (*fname == '|') + ++fname; + if (*fname == '$') { + cmdtype = APR_SHELLCMD_ENV; + ++fname; + } + + /* Spawn a new child logger. If this is the main server_rec, + * the new child must use a dummy stderr since the current + * stderr might be a pipe to the old logger. Otherwise, the + * child inherits the parents stderr. */ + rc = log_child(p, fname, &dummy, cmdtype, is_main); + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, ap_server_conf, APLOGNO(00089) + "Couldn't start ErrorLog process '%s'.", + s->error_fname + 1); + return DONE; + } + + s->error_log = dummy; + } + +#ifdef HAVE_SYSLOG + else if (strcmp(s->error_fname, "syslog") == 0 + || strncmp(s->error_fname, "syslog:", 7) == 0) { + if ((fname = strchr(s->error_fname, ':'))) { + /* s->error_fname could be [level]:[tag] (see #60525) */ + const char *tag; + apr_size_t flen; + const TRANS *fac; + + fname++; + tag = ap_strchr_c(fname, ':'); + if (tag) { + flen = tag - fname; + tag++; + if (*tag == '\0') { + tag = ap_server_argv0; + } + } else { + flen = strlen(fname); + tag = ap_server_argv0; + } + if (flen == 0) { + /* Was something like syslog::foobar */ + openlog(tag, LOG_NDELAY|LOG_CONS|LOG_PID, LOG_LOCAL7); + } else { + for (fac = facilities; fac->t_name; fac++) { + if (!strncasecmp(fname, fac->t_name, flen)) { + openlog(tag, LOG_NDELAY|LOG_CONS|LOG_PID, + fac->t_val); + s->error_log = NULL; + return OK; + } + } + /* Huh? Invalid level name? */ + ap_log_error(APLOG_MARK, APLOG_STARTUP, APR_EBADPATH, NULL, APLOGNO(10036) + "%s: could not open syslog error log %s.", + ap_server_argv0, fname); + return DONE; + } + } + else { + openlog(ap_server_argv0, LOG_NDELAY|LOG_CONS|LOG_PID, LOG_LOCAL7); + } + + s->error_log = NULL; + } +#endif + else { + fname = ap_server_root_relative(p, s->error_fname); + if (!fname) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, APR_EBADPATH, ap_server_conf, APLOGNO(00090) + "%s: Invalid error log path %s.", + ap_server_argv0, s->error_fname); + return DONE; + } + if ((rc = apr_file_open(&s->error_log, fname, + APR_APPEND | APR_WRITE | APR_CREATE | APR_LARGEFILE, + APR_OS_DEFAULT, p)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, ap_server_conf, APLOGNO(00091) + "%s: could not open error log file %s.", + ap_server_argv0, fname); + return DONE; + } + } + + return OK; +} + +int ap_open_logs(apr_pool_t *pconf, apr_pool_t *p /* plog */, + apr_pool_t *ptemp, server_rec *s_main) +{ + apr_pool_t *stderr_p; + server_rec *virt, *q; + int replace_stderr; + + + /* Register to throw away the read_handles list when we + * cleanup plog. Upon fork() for the apache children, + * this read_handles list is closed so only the parent + * can relaunch a lost log child. These read handles + * are always closed on exec. + * We won't care what happens to our stderr log child + * between log phases, so we don't mind losing stderr's + * read_handle a little bit early. + */ + apr_pool_cleanup_register(p, &read_handles, ap_pool_cleanup_set_null, + apr_pool_cleanup_null); + + /* HERE we need a stdout log that outlives plog. + * We *presume* the parent of plog is a process + * or global pool which spans server restarts. + * Create our stderr_pool as a child of the plog's + * parent pool. + */ + apr_pool_create(&stderr_p, apr_pool_parent_get(p)); + apr_pool_tag(stderr_p, "stderr_pool"); + + if (open_error_log(s_main, 1, stderr_p) != OK) { + return DONE; + } + + replace_stderr = 1; + if (s_main->error_log) { + apr_status_t rv; + + /* Replace existing stderr with new log. */ + apr_file_flush(s_main->error_log); + rv = apr_file_dup2(stderr_log, s_main->error_log, stderr_p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s_main, APLOGNO(00092) + "unable to replace stderr with error_log"); + } + else { + /* We are done with stderr_pool, close it, killing + * the previous generation's stderr logger + */ + if (stderr_pool) + apr_pool_destroy(stderr_pool); + stderr_pool = stderr_p; + replace_stderr = 0; + /* + * Now that we have dup'ed s_main->error_log to stderr_log + * close it and set s_main->error_log to stderr_log. This avoids + * this fd being inherited by the next piped logger who would + * keep open the writing end of the pipe that this one uses + * as stdin. This in turn would prevent the piped logger from + * exiting. + */ + apr_file_close(s_main->error_log); + s_main->error_log = stderr_log; + } + } + /* note that stderr may still need to be replaced with something + * because it points to the old error log, or back to the tty + * of the submitter. + * XXX: This is BS - /dev/null is non-portable + * errno-as-apr_status_t is also non-portable + */ + +#ifdef WIN32 +#define NULL_DEVICE "nul" +#else +#define NULL_DEVICE "/dev/null" +#endif + + if (replace_stderr && freopen(NULL_DEVICE, "w", stderr) == NULL) { + ap_log_error(APLOG_MARK, APLOG_CRIT, errno, s_main, APLOGNO(00093) + "unable to replace stderr with %s", NULL_DEVICE); + } + + for (virt = s_main->next; virt; virt = virt->next) { + if (virt->error_fname) { + for (q=s_main; q != virt; q = q->next) { + if (q->error_fname != NULL + && strcmp(q->error_fname, virt->error_fname) == 0) { + break; + } + } + + if (q == virt) { + if (open_error_log(virt, 0, p) != OK) { + return DONE; + } + } + else { + virt->error_log = q->error_log; + } + } + else { + virt->error_log = s_main->error_log; + } + } + return OK; +} + +AP_DECLARE(void) ap_error_log2stderr(server_rec *s) { + apr_file_t *errfile = NULL; + + apr_file_open_stderr(&errfile, s->process->pool); + if (s->error_log != NULL) { + apr_file_dup2(s->error_log, errfile, s->process->pool); + } +} + +static int cpystrn(char *buf, const char *arg, int buflen) +{ + char *end; + if (!arg) + return 0; + end = apr_cpystrn(buf, arg, buflen); + return end - buf; +} + + +static int log_remote_address(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->r && !(arg && *arg == 'c')) + return apr_snprintf(buf, buflen, "%s:%d", info->r->useragent_ip, + info->r->useragent_addr ? info->r->useragent_addr->port : 0); + else if (info->c) + return apr_snprintf(buf, buflen, "%s:%d", info->c->client_ip, + info->c->client_addr ? info->c->client_addr->port : 0); + else + return 0; +} + +static int log_local_address(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->c) + return apr_snprintf(buf, buflen, "%s:%d", info->c->local_ip, + info->c->local_addr->port); + else + return 0; +} + +static int log_pid(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + pid_t pid = getpid(); + return apr_snprintf(buf, buflen, "%" APR_PID_T_FMT, pid); +} + +static int log_tid(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ +#if APR_HAS_THREADS + int result; +#endif +#if HAVE_GETTID + if (arg && *arg == 'g') { + pid_t tid = syscall(SYS_gettid); + if (tid == -1) + return 0; + return apr_snprintf(buf, buflen, "%"APR_PID_T_FMT, tid); + } +#endif +#if APR_HAS_THREADS + if (ap_mpm_query(AP_MPMQ_IS_THREADED, &result) == APR_SUCCESS + && result != AP_MPMQ_NOT_SUPPORTED) + { + apr_os_thread_t tid = apr_os_thread_current(); + return apr_snprintf(buf, buflen, "%pT", &tid); + } +#endif + return 0; +} + +static int log_ctime(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + int time_len = buflen; + int option = AP_CTIME_OPTION_NONE; + + while (arg && *arg) { + switch (*arg) { + case 'u': option |= AP_CTIME_OPTION_USEC; + break; + case 'c': option |= AP_CTIME_OPTION_COMPACT; + break; + } + arg++; + } + + ap_recent_ctime_ex(buf, apr_time_now(), option, &time_len); + + /* ap_recent_ctime_ex includes the trailing \0 in time_len */ + return time_len - 1; +} + +static int log_loglevel(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->level < 0) + return 0; + else + return cpystrn(buf, priorities[info->level].t_name, buflen); +} + +static int log_log_id(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + /* + * C: log conn log_id if available, + * c: log conn log id if available and not a once-per-request log line + * else: log request log id if available + */ + if (arg && !strcasecmp(arg, "c")) { + if (info->c && (*arg != 'C' || !info->r)) { + return cpystrn(buf, info->c->log_id, buflen); + } + } + else if (info->rmain) { + return cpystrn(buf, info->rmain->log_id, buflen); + } + return 0; +} + +static int log_keepalives(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (!info->c) + return 0; + + return apr_snprintf(buf, buflen, "%d", info->c->keepalives); +} + +static int log_module_name(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + return cpystrn(buf, ap_find_module_short_name(info->module_index), buflen); +} + +static int log_file_line(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->file == NULL) { + return 0; + } + else { + const char *file = info->file; +#if defined(_OSD_POSIX) || defined(WIN32) || defined(__MVS__) + char tmp[256]; + char *e = strrchr(file, '/'); +#ifdef WIN32 + if (!e) { + e = strrchr(file, '\\'); + } +#endif + + /* In OSD/POSIX, the compiler returns for __FILE__ + * a string like: __FILE__="*POSIX(/usr/include/stdio.h)" + * (it even returns an absolute path for sources in + * the current directory). Here we try to strip this + * down to the basename. + */ + if (e != NULL && e[1] != '\0') { + apr_snprintf(tmp, sizeof(tmp), "%s", &e[1]); + e = &tmp[strlen(tmp)-1]; + if (*e == ')') { + *e = '\0'; + } + file = tmp; + } +#else /* _OSD_POSIX || WIN32 */ + const char *p; + /* On Unix, __FILE__ may be an absolute path in a + * VPATH build. */ + if (file[0] == '/' && (p = ap_strrchr_c(file, '/')) != NULL) { + file = p + 1; + } +#endif /*_OSD_POSIX || WIN32 */ + return apr_snprintf(buf, buflen, "%s(%d)", file, info->line); + } +} + +static int log_apr_status(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + apr_status_t status = info->status; + int len; + if (!status) + return 0; + + if (status < APR_OS_START_EAIERR) { + len = apr_snprintf(buf, buflen, "(%d)", status); + } + else if (status < APR_OS_START_SYSERR) { + len = apr_snprintf(buf, buflen, "(EAI %d)", + status - APR_OS_START_EAIERR); + } + else if (status < 100000 + APR_OS_START_SYSERR) { + len = apr_snprintf(buf, buflen, "(OS %d)", + status - APR_OS_START_SYSERR); + } + else { + len = apr_snprintf(buf, buflen, "(os 0x%08x)", + status - APR_OS_START_SYSERR); + } + apr_strerror(status, buf + len, buflen - len); + len += strlen(buf + len); + return len; +} + +static int log_server_name(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->r) + return cpystrn(buf, ap_get_server_name((request_rec *)info->r), buflen); + + return 0; +} + +static int log_virtual_host(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->s) + return cpystrn(buf, info->s->server_hostname, buflen); + + return 0; +} + + +static int log_table_entry(const apr_table_t *table, const char *name, + char *buf, int buflen) +{ +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + const char *value; + char scratch[MAX_STRING_LEN]; + + if ((value = apr_table_get(table, name)) != NULL) { + ap_escape_errorlog_item(scratch, value, MAX_STRING_LEN); + return cpystrn(buf, scratch, buflen); + } + + return 0; +#else + return cpystrn(buf, apr_table_get(table, name), buflen); +#endif +} + +static int log_header(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->r) + return log_table_entry(info->r->headers_in, arg, buf, buflen); + + return 0; +} + +static int log_note(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + /* XXX: maybe escaping the entry is not necessary for notes? */ + if (info->r) + return log_table_entry(info->r->notes, arg, buf, buflen); + + return 0; +} + +static int log_env_var(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->r) + return log_table_entry(info->r->subprocess_env, arg, buf, buflen); + + return 0; +} + +static int core_generate_log_id(const conn_rec *c, const request_rec *r, + const char **idstring) +{ + apr_uint64_t id, tmp; + pid_t pid; + int len; + char *encoded; + + if (r && r->request_time) { + id = (apr_uint64_t)r->request_time; + } + else { + id = (apr_uint64_t)apr_time_now(); + } + + pid = getpid(); + if (sizeof(pid_t) > 2) { + tmp = pid; + tmp = tmp << 40; + id ^= tmp; + pid = pid >> 24; + tmp = pid; + tmp = tmp << 56; + id ^= tmp; + } + else { + tmp = pid; + tmp = tmp << 40; + id ^= tmp; + } +#if APR_HAS_THREADS + { + apr_uintptr_t tmp2 = (apr_uintptr_t)c->current_thread; + tmp = tmp2; + tmp = tmp << 32; + id ^= tmp; + } +#endif + + len = apr_base64_encode_len(sizeof(id)); /* includes trailing \0 */ + encoded = apr_palloc(r ? r->pool : c->pool, len); + apr_base64_encode(encoded, (char *)&id, sizeof(id)); + + /* Skip the last char, it is always '=' */ + encoded[len - 2] = '\0'; + + *idstring = encoded; + + return OK; +} + +static void add_log_id(const conn_rec *c, const request_rec *r) +{ + const char **id; + /* need to cast const away */ + if (r) { + id = &((request_rec *)r)->log_id; + } + else { + id = &((conn_rec *)c)->log_id; + } + + ap_run_generate_log_id(c, r, id); +} + +AP_DECLARE(void) ap_register_log_hooks(apr_pool_t *p) +{ + ap_hook_generate_log_id(core_generate_log_id, NULL, NULL, + APR_HOOK_REALLY_LAST); + + ap_register_errorlog_handler(p, "a", log_remote_address, 0); + ap_register_errorlog_handler(p, "A", log_local_address, 0); + ap_register_errorlog_handler(p, "e", log_env_var, 0); + ap_register_errorlog_handler(p, "E", log_apr_status, 0); + ap_register_errorlog_handler(p, "F", log_file_line, 0); + ap_register_errorlog_handler(p, "i", log_header, 0); + ap_register_errorlog_handler(p, "k", log_keepalives, 0); + ap_register_errorlog_handler(p, "l", log_loglevel, 0); + ap_register_errorlog_handler(p, "L", log_log_id, 0); + ap_register_errorlog_handler(p, "m", log_module_name, 0); + ap_register_errorlog_handler(p, "n", log_note, 0); + ap_register_errorlog_handler(p, "P", log_pid, 0); + ap_register_errorlog_handler(p, "t", log_ctime, 0); + ap_register_errorlog_handler(p, "T", log_tid, 0); + ap_register_errorlog_handler(p, "v", log_virtual_host, 0); + ap_register_errorlog_handler(p, "V", log_server_name, 0); +} + +/* + * This is used if no error log format is defined and during startup. + * It automatically omits the timestamp if logging to syslog. + */ +static int do_errorlog_default(const ap_errorlog_info *info, char *buf, + int buflen, int *errstr_start, int *errstr_end, + const char *errstr_fmt, va_list args) +{ + int len = 0; + int field_start = 0; + int item_len; +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + char scratch[MAX_STRING_LEN]; +#endif + + if (!info->using_syslog && !info->startup) { + buf[len++] = '['; + len += log_ctime(info, "u", buf + len, buflen - len); + buf[len++] = ']'; + buf[len++] = ' '; + } + + if (!info->startup) { + buf[len++] = '['; + len += log_module_name(info, NULL, buf + len, buflen - len); + buf[len++] = ':'; + len += log_loglevel(info, NULL, buf + len, buflen - len); + len += cpystrn(buf + len, "] [pid ", buflen - len); + + len += log_pid(info, NULL, buf + len, buflen - len); +#if APR_HAS_THREADS + field_start = len; + len += cpystrn(buf + len, ":tid ", buflen - len); + item_len = log_tid(info, NULL, buf + len, buflen - len); + if (!item_len) + len = field_start; + else + len += item_len; +#endif + buf[len++] = ']'; + buf[len++] = ' '; + } + + if (info->level >= APLOG_DEBUG) { + item_len = log_file_line(info, NULL, buf + len, buflen - len); + if (item_len) { + len += item_len; + len += cpystrn(buf + len, ": ", buflen - len); + } + } + + if (info->status) { + item_len = log_apr_status(info, NULL, buf + len, buflen - len); + if (item_len) { + len += item_len; + len += cpystrn(buf + len, ": ", buflen - len); + } + } + + /* + * useragent_ip/client_ip can be client or backend server. If we have + * a scoreboard handle, it is likely a client. + */ + if (info->r) { + len += apr_snprintf(buf + len, buflen - len, + info->r->connection->sbh ? "[client %s:%d] " : "[remote %s:%d] ", + info->r->useragent_ip, + info->r->useragent_addr ? info->r->useragent_addr->port : 0); + } + else if (info->c) { + len += apr_snprintf(buf + len, buflen - len, + info->c->sbh ? "[client %s:%d] " : "[remote %s:%d] ", + info->c->client_ip, + info->c->client_addr ? info->c->client_addr->port : 0); + } + + /* the actual error message */ + *errstr_start = len; +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + if (apr_vsnprintf(scratch, MAX_STRING_LEN, errstr_fmt, args)) { + len += ap_escape_errorlog_item(buf + len, scratch, + buflen - len); + + } +#else + len += apr_vsnprintf(buf + len, buflen - len, errstr_fmt, args); +#endif + *errstr_end = len; + + field_start = len; + len += cpystrn(buf + len, ", referer: ", buflen - len); + item_len = log_header(info, "Referer", buf + len, buflen - len); + if (item_len) + len += item_len; + else + len = field_start; + + return len; +} + +static int do_errorlog_format(apr_array_header_t *fmt, ap_errorlog_info *info, + char *buf, int buflen, int *errstr_start, + int *errstr_end, const char *err_fmt, va_list args) +{ +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + char scratch[MAX_STRING_LEN]; +#endif + int i; + int len = 0; + int field_start = 0; + int skipping = 0; + ap_errorlog_format_item *items = (ap_errorlog_format_item *)fmt->elts; + + AP_DEBUG_ASSERT(fmt->nelts > 0); + for (i = 0; i < fmt->nelts; ++i) { + ap_errorlog_format_item *item = &items[i]; + if (item->flags & AP_ERRORLOG_FLAG_FIELD_SEP) { + if (skipping) { + skipping = 0; + } + else { + field_start = len; + } + } + + if (item->flags & AP_ERRORLOG_FLAG_MESSAGE) { + /* the actual error message */ + *errstr_start = len; +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + if (apr_vsnprintf(scratch, MAX_STRING_LEN, err_fmt, args)) { + len += ap_escape_errorlog_item(buf + len, scratch, + buflen - len); + + } +#else + len += apr_vsnprintf(buf + len, buflen - len, err_fmt, args); +#endif + *errstr_end = len; + } + else if (skipping) { + continue; + } + else if (info->level != -1 && (int)item->min_loglevel > info->level) { + len = field_start; + skipping = 1; + } + else { + int item_len = (*item->func)(info, item->arg, buf + len, + buflen - len); + if (!item_len) { + if (item->flags & AP_ERRORLOG_FLAG_REQUIRED) { + /* required item is empty. skip whole line */ + buf[0] = '\0'; + return 0; + } + else if (item->flags & AP_ERRORLOG_FLAG_NULL_AS_HYPHEN) { + buf[len++] = '-'; + } + else { + len = field_start; + skipping = 1; + } + } + else { + len += item_len; + } + } + } + return len; +} + +static void write_logline(char *errstr, apr_size_t len, apr_file_t *logf, + int level) +{ + /* NULL if we are logging to syslog */ + if (logf) { + /* Truncate for the terminator (as apr_snprintf does) */ + if (len > MAX_STRING_LEN - sizeof(APR_EOL_STR)) { + len = MAX_STRING_LEN - sizeof(APR_EOL_STR); + } + strcpy(errstr + len, APR_EOL_STR); + apr_file_puts(errstr, logf); + apr_file_flush(logf); + } +#ifdef HAVE_SYSLOG + else { + syslog(level < LOG_PRIMASK ? level : APLOG_DEBUG, "%.*s", + (int)len, errstr); + } +#endif +} + +static void log_error_core(const char *file, int line, int module_index, + int level, + apr_status_t status, const server_rec *s, + const conn_rec *c, + const request_rec *r, apr_pool_t *pool, + const char *fmt, va_list args) +{ + char errstr[MAX_STRING_LEN]; + apr_file_t *logf = NULL; + int level_and_mask = level & APLOG_LEVELMASK; + const request_rec *rmain = NULL; + core_server_config *sconf = NULL; + ap_errorlog_info info; + + /* do we need to log once-per-req or once-per-conn info? */ + int log_conn_info = 0, log_req_info = 0; + apr_array_header_t **lines = NULL; + int done = 0; + int line_number = 0; + + if (r) { + AP_DEBUG_ASSERT(r->connection != NULL); + c = r->connection; + } + + if (s == NULL) { + /* + * If we are doing stderr logging (startup), don't log messages that are + * above the default server log level unless it is a startup/shutdown + * notice + */ +#ifndef DEBUG + if ((level_and_mask != APLOG_NOTICE) + && (level_and_mask > ap_default_loglevel)) { + return; + } +#endif + + logf = stderr_log; + } + else { + int configured_level = r ? ap_get_request_module_loglevel(r, module_index) : + c ? ap_get_conn_server_module_loglevel(c, s, module_index) : + ap_get_server_module_loglevel(s, module_index); + if (s->error_log) { + /* + * If we are doing normal logging, don't log messages that are + * above the module's log level unless it is a startup/shutdown notice + */ + if ((level_and_mask != APLOG_NOTICE) + && (level_and_mask > configured_level)) { + return; + } + + logf = s->error_log; + } + else { + /* + * If we are doing syslog logging, don't log messages that are + * above the module's log level (including a startup/shutdown notice) + */ + if (level_and_mask > configured_level) { + return; + } + } + + /* the faked server_rec from mod_cgid does not have s->module_config */ + if (s->module_config) { + sconf = ap_get_core_module_config(s->module_config); + if (c && !c->log_id) { + add_log_id(c, NULL); + if (sconf->error_log_conn && sconf->error_log_conn->nelts > 0) + log_conn_info = 1; + } + if (r) { + if (r->main) + rmain = r->main; + else + rmain = r; + + if (!rmain->log_id) { + /* XXX: do we need separate log ids for subrequests? */ + if (sconf->error_log_req && sconf->error_log_req->nelts > 0) + log_req_info = 1; + /* + * XXX: potential optimization: only create log id if %L is + * XXX: actually used + */ + add_log_id(c, rmain); + } + } + } + } + + info.s = s; + info.c = c; + info.pool = pool; + info.file = NULL; + info.line = 0; + info.status = 0; + info.using_syslog = (logf == NULL); + info.startup = ((level & APLOG_STARTUP) == APLOG_STARTUP); + info.format = fmt; + + while (!done) { + apr_array_header_t *log_format; + int len = 0, errstr_start = 0, errstr_end = 0; + /* XXX: potential optimization: format common prefixes only once */ + if (log_conn_info) { + /* once-per-connection info */ + if (line_number == 0) { + lines = (apr_array_header_t **)sconf->error_log_conn->elts; + info.r = NULL; + info.rmain = NULL; + info.level = -1; + info.module_index = APLOG_NO_MODULE; + } + + log_format = lines[line_number++]; + + if (line_number == sconf->error_log_conn->nelts) { + /* this is the last line of once-per-connection info */ + line_number = 0; + log_conn_info = 0; + } + } + else if (log_req_info) { + /* once-per-request info */ + if (line_number == 0) { + lines = (apr_array_header_t **)sconf->error_log_req->elts; + info.r = rmain; + info.rmain = rmain; + info.level = -1; + info.module_index = APLOG_NO_MODULE; + } + + log_format = lines[line_number++]; + + if (line_number == sconf->error_log_req->nelts) { + /* this is the last line of once-per-request info */ + line_number = 0; + log_req_info = 0; + } + } + else { + /* the actual error message */ + info.r = r; + info.rmain = rmain; + info.level = level_and_mask; + info.module_index = module_index; + info.file = file; + info.line = line; + info.status = status; + log_format = sconf ? sconf->error_log_format : NULL; + done = 1; + } + + /* + * prepare and log one line + */ + + if (log_format && !info.startup) { + len += do_errorlog_format(log_format, &info, errstr + len, + MAX_STRING_LEN - len, + &errstr_start, &errstr_end, fmt, args); + } + else { + len += do_errorlog_default(&info, errstr + len, MAX_STRING_LEN - len, + &errstr_start, &errstr_end, fmt, args); + } + + if (!*errstr) { + /* + * Don't log empty lines. This can happen with once-per-conn/req + * info if an item with AP_ERRORLOG_FLAG_REQUIRED is NULL. + */ + continue; + } + write_logline(errstr, len, logf, level_and_mask); + + if (done) { + /* + * We don't call the error_log hook for per-request/per-conn + * lines, and we only pass the actual log message, not the + * prefix and suffix. + */ + errstr[errstr_end] = '\0'; + ap_run_error_log(&info, errstr + errstr_start); + } + + *errstr = '\0'; + } +} + +/* For internal calls to log_error_core with self-composed arg lists */ +static void log_error_va_glue(const char *file, int line, int module_index, + int level, apr_status_t status, + const server_rec *s, const conn_rec *c, + const request_rec *r, apr_pool_t *pool, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log_error_core(file, line, module_index, level, status, s, c, r, pool, + fmt, args); + va_end(args); +} + +AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index, + int level, apr_status_t status, + const server_rec *s, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log_error_core(file, line, module_index, level, status, s, NULL, NULL, + NULL, fmt, args); + va_end(args); +} + +AP_DECLARE(void) ap_log_perror_(const char *file, int line, int module_index, + int level, apr_status_t status, apr_pool_t *p, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log_error_core(file, line, module_index, level, status, NULL, NULL, NULL, + p, fmt, args); + va_end(args); +} + +AP_DECLARE(void) ap_log_rerror_(const char *file, int line, int module_index, + int level, apr_status_t status, + const request_rec *r, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log_error_core(file, line, module_index, level, status, r->server, NULL, r, + NULL, fmt, args); + + /* + * IF APLOG_TOCLIENT is set, + * AND the error level is 'warning' or more severe, + * AND there isn't already error text associated with this request, + * THEN make the message text available to ErrorDocument and + * other error processors. + */ + va_end(args); + va_start(args,fmt); + if ((level & APLOG_TOCLIENT) + && ((level & APLOG_LEVELMASK) <= APLOG_WARNING) + && (apr_table_get(r->notes, "error-notes") == NULL)) { + apr_table_setn(r->notes, "error-notes", + ap_escape_html(r->pool, apr_pvsprintf(r->pool, fmt, + args))); + } + va_end(args); +} + +AP_DECLARE(void) ap_log_cserror_(const char *file, int line, int module_index, + int level, apr_status_t status, + const conn_rec *c, const server_rec *s, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log_error_core(file, line, module_index, level, status, s, c, + NULL, NULL, fmt, args); + va_end(args); +} + +AP_DECLARE(void) ap_log_cerror_(const char *file, int line, int module_index, + int level, apr_status_t status, + const conn_rec *c, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log_error_core(file, line, module_index, level, status, c->base_server, c, + NULL, NULL, fmt, args); + va_end(args); +} + +#define BYTES_LOGGED_PER_LINE 16 +#define LOG_BYTES_BUFFER_SIZE (BYTES_LOGGED_PER_LINE * 3 + 2) + +static void fmt_data(unsigned char *buf, const void *vdata, apr_size_t len, apr_size_t *off) +{ + const unsigned char *data = (const unsigned char *)vdata; + unsigned char *chars; + unsigned char *hex; + apr_size_t this_time = 0; + + memset(buf, ' ', LOG_BYTES_BUFFER_SIZE - 1); + buf[LOG_BYTES_BUFFER_SIZE - 1] = '\0'; + + chars = buf; /* start character dump here */ + hex = buf + BYTES_LOGGED_PER_LINE + 1; /* start hex dump here */ + while (*off < len && this_time < BYTES_LOGGED_PER_LINE) { + unsigned char c = data[*off]; + + if (apr_isprint(c) + && c != '\\') { /* backslash will be escaped later, which throws + * off the formatting + */ + *chars = c; + } + else { + *chars = '.'; + } + + if ((c >> 4) >= 10) { + *hex = 'a' + ((c >> 4) - 10); + } + else { + *hex = '0' + (c >> 4); + } + + if ((c & 0x0F) >= 10) { + *(hex + 1) = 'a' + ((c & 0x0F) - 10); + } + else { + *(hex + 1) = '0' + (c & 0x0F); + } + + chars += 1; + hex += 2; + *off += 1; + ++this_time; + } +} + +static void log_data_core(const char *file, int line, int module_index, + int level, const server_rec *s, + const conn_rec *c, const request_rec *r, + const char *label, const void *data, apr_size_t len, + unsigned int flags) +{ + unsigned char buf[LOG_BYTES_BUFFER_SIZE]; + apr_size_t off; + char prefix[20]; + + if (!(flags & AP_LOG_DATA_SHOW_OFFSET)) { + prefix[0] = '\0'; + } + + if (len > 0xffff) { /* bug in caller? */ + len = 0xffff; + } + + if (label) { + log_error_va_glue(file, line, module_index, level, APR_SUCCESS, s, + c, r, NULL, "%s (%" APR_SIZE_T_FMT " bytes)", + label, len); + } + + off = 0; + while (off < len) { + if (flags & AP_LOG_DATA_SHOW_OFFSET) { + apr_snprintf(prefix, sizeof prefix, "%04x: ", (unsigned int)off); + } + fmt_data(buf, data, len, &off); + log_error_va_glue(file, line, module_index, level, APR_SUCCESS, s, + c, r, NULL, "%s%s", prefix, buf); + } +} + +AP_DECLARE(void) ap_log_data_(const char *file, int line, + int module_index, int level, + const server_rec *s, const char *label, + const void *data, apr_size_t len, + unsigned int flags) +{ + log_data_core(file, line, module_index, level, s, NULL, NULL, label, + data, len, flags); +} + +AP_DECLARE(void) ap_log_rdata_(const char *file, int line, + int module_index, int level, + const request_rec *r, const char *label, + const void *data, apr_size_t len, + unsigned int flags) +{ + log_data_core(file, line, module_index, level, r->server, NULL, r, label, + data, len, flags); +} + +AP_DECLARE(void) ap_log_cdata_(const char *file, int line, + int module_index, int level, + const conn_rec *c, const char *label, + const void *data, apr_size_t len, + unsigned int flags) +{ + log_data_core(file, line, module_index, level, c->base_server, c, NULL, + label, data, len, flags); +} + +AP_DECLARE(void) ap_log_csdata_(const char *file, int line, int module_index, + int level, const conn_rec *c, const server_rec *s, + const char *label, const void *data, + apr_size_t len, unsigned int flags) +{ + log_data_core(file, line, module_index, level, s, c, NULL, label, data, + len, flags); +} + +AP_DECLARE(void) ap_log_command_line(apr_pool_t *plog, server_rec *s) +{ + int i; + process_rec *process = s->process; + char *result; + int len_needed = 0; + + /* Piece together the command line from the pieces + * in process->argv, with spaces in between. + */ + for (i = 0; i < process->argc; i++) { + len_needed += strlen(process->argv[i]) + 1; + } + + result = (char *) apr_palloc(plog, len_needed); + *result = '\0'; + + for (i = 0; i < process->argc; i++) { + strcat(result, process->argv[i]); + if ((i+1)< process->argc) { + strcat(result, " "); + } + } + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(00094) + "Command line: '%s'", result); +} + +/* grab bag function to log commonly logged and shared info */ +AP_DECLARE(void) ap_log_mpm_common(server_rec *s) +{ + ap_log_error(APLOG_MARK, APLOG_DEBUG , 0, s, APLOGNO(02639) + "Using SO_REUSEPORT: %s (%d)", + ap_have_so_reuseport ? "yes" : "no", + ap_num_listen_buckets); +} + +AP_DECLARE(void) ap_remove_pid(apr_pool_t *p, const char *rel_fname) +{ + apr_status_t rv; + const char *fname = ap_server_root_relative(p, rel_fname); + + if (fname != NULL) { + rv = apr_file_remove(fname, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00095) + "failed to remove PID file %s", fname); + } + else { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00096) + "removed PID file %s (pid=%" APR_PID_T_FMT ")", + fname, getpid()); + } + } +} + +AP_DECLARE(void) ap_log_pid(apr_pool_t *p, const char *filename) +{ + apr_file_t *pid_file = NULL; + apr_finfo_t finfo; + static pid_t saved_pid = -1; + pid_t mypid; + apr_status_t rv; + const char *fname; + + if (!filename) { + return; + } + + fname = ap_server_root_relative(p, filename); + if (!fname) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, APR_EBADPATH, + ap_server_conf, APLOGNO(00097) "Invalid PID file path %s, ignoring.", filename); + return; + } + + mypid = getpid(); + if (mypid != saved_pid + && apr_stat(&finfo, fname, APR_FINFO_MTIME, p) == APR_SUCCESS) { + /* AP_SIG_GRACEFUL and HUP call this on each restart. + * Only warn on first time through for this pid. + * + * XXX: Could just write first time through too, although + * that may screw up scripts written to do something + * based on the last modification time of the pid file. + */ + ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, p, APLOGNO(00098) + "pid file %s overwritten -- Unclean " + "shutdown of previous Apache run?", + fname); + } + + if ((rv = apr_file_open(&pid_file, fname, + APR_WRITE | APR_CREATE | APR_TRUNCATE, + APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD, p)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00099) + "could not create %s", fname); + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00100) + "%s: could not log pid to file %s", + ap_server_argv0, fname); + exit(1); + } + apr_file_printf(pid_file, "%" APR_PID_T_FMT APR_EOL_STR, mypid); + apr_file_close(pid_file); + saved_pid = mypid; +} + +AP_DECLARE(apr_status_t) ap_read_pid(apr_pool_t *p, const char *filename, + pid_t *mypid) +{ + const apr_size_t BUFFER_SIZE = sizeof(long) * 3 + 2; /* see apr_ltoa */ + apr_file_t *pid_file = NULL; + apr_status_t rv; + const char *fname; + char *buf, *endptr; + apr_size_t bytes_read; + + if (!filename) { + return APR_EGENERAL; + } + + fname = ap_server_root_relative(p, filename); + if (!fname) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, APR_EBADPATH, + ap_server_conf, APLOGNO(00101) "Invalid PID file path %s, ignoring.", filename); + return APR_EGENERAL; + } + + rv = apr_file_open(&pid_file, fname, APR_READ, APR_OS_DEFAULT, p); + if (rv != APR_SUCCESS) { + return rv; + } + + buf = apr_palloc(p, BUFFER_SIZE); + + rv = apr_file_read_full(pid_file, buf, BUFFER_SIZE - 1, &bytes_read); + if (rv != APR_SUCCESS && rv != APR_EOF) { + return rv; + } + + /* If we fill the buffer, we're probably reading a corrupt pid file. + * To be nice, let's also ensure the first char is a digit. */ + if (bytes_read == 0 || bytes_read == BUFFER_SIZE - 1 || !apr_isdigit(*buf)) { + return APR_EGENERAL; + } + + buf[bytes_read] = '\0'; + *mypid = strtol(buf, &endptr, 10); + + apr_file_close(pid_file); + return APR_SUCCESS; +} + +AP_DECLARE(void) ap_log_assert(const char *szExp, const char *szFile, + int nLine) +{ + char time_str[APR_CTIME_LEN]; + + apr_ctime(time_str, apr_time_now()); + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(00102) + "[%s] file %s, line %d, assertion \"%s\" failed", + time_str, szFile, nLine, szExp); +#if defined(WIN32) + DebugBreak(); +#else + /* unix assert does an abort leading to a core dump */ + abort(); +#endif +} + +/* piped log support */ + +#ifdef AP_HAVE_RELIABLE_PIPED_LOGS +/* forward declaration */ +static void piped_log_maintenance(int reason, void *data, apr_wait_t status); + +/* Spawn the piped logger process pl->program. */ +static apr_status_t piped_log_spawn(piped_log *pl) +{ + apr_procattr_t *procattr; + apr_proc_t *procnew = NULL; + apr_status_t status; + + if (((status = apr_procattr_create(&procattr, pl->p)) != APR_SUCCESS) || + ((status = apr_procattr_dir_set(procattr, ap_server_root)) + != APR_SUCCESS) || + ((status = apr_procattr_cmdtype_set(procattr, pl->cmdtype)) + != APR_SUCCESS) || + ((status = apr_procattr_child_in_set(procattr, + pl->read_fd, + pl->write_fd)) + != APR_SUCCESS) || + ((status = apr_procattr_child_errfn_set(procattr, log_child_errfn)) + != APR_SUCCESS) || + ((status = apr_procattr_error_check_set(procattr, 1)) != APR_SUCCESS)) { + /* Something bad happened, give up and go away. */ + ap_log_error(APLOG_MARK, APLOG_STARTUP, status, ap_server_conf, APLOGNO(00103) + "piped_log_spawn: unable to setup child process '%s'", + pl->program); + } + else { + char **args; + + apr_tokenize_to_argv(pl->program, &args, pl->p); + procnew = apr_pcalloc(pl->p, sizeof(apr_proc_t)); + status = apr_proc_create(procnew, args[0], (const char * const *) args, + NULL, procattr, pl->p); + + if (status == APR_SUCCESS) { + pl->pid = procnew; + /* procnew->in was dup2'd from pl->write_fd; + * since the original fd is still valid, close the copy to + * avoid a leak. */ + apr_file_close(procnew->in); + procnew->in = NULL; + apr_proc_other_child_register(procnew, piped_log_maintenance, pl, + pl->write_fd, pl->p); + close_handle_in_child(pl->p, pl->read_fd); + } + else { + /* Something bad happened, give up and go away. */ + ap_log_error(APLOG_MARK, APLOG_STARTUP, status, ap_server_conf, APLOGNO(00104) + "unable to start piped log program '%s'", + pl->program); + } + } + + return status; +} + + +static void piped_log_maintenance(int reason, void *data, apr_wait_t status) +{ + piped_log *pl = data; + apr_status_t rv; + int mpm_state; + + switch (reason) { + case APR_OC_REASON_DEATH: + case APR_OC_REASON_LOST: + pl->pid = NULL; /* in case we don't get it going again, this + * tells other logic not to try to kill it + */ + apr_proc_other_child_unregister(pl); + rv = ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00105) + "can't query MPM state; not restarting " + "piped log program '%s'", + pl->program); + } + else if (mpm_state != AP_MPMQ_STOPPING) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00106) + "piped log program '%s' failed unexpectedly", + pl->program); + if ((rv = piped_log_spawn(pl)) != APR_SUCCESS) { + /* what can we do? This could be the error log we're having + * problems opening up... */ + ap_log_error(APLOG_MARK, APLOG_STARTUP, rv, NULL, APLOGNO(00107) + "piped_log_maintenance: unable to respawn '%s'", + pl->program); + } + } + break; + + case APR_OC_REASON_UNWRITABLE: + /* We should not kill off the pipe here, since it may only be full. + * If it really is locked, we should kill it off manually. */ + break; + + case APR_OC_REASON_RESTART: + if (pl->pid != NULL) { + apr_proc_kill(pl->pid, SIGTERM); + pl->pid = NULL; + } + break; + + case APR_OC_REASON_UNREGISTER: + break; + } +} + + +static apr_status_t piped_log_cleanup_for_exec(void *data) +{ + piped_log *pl = data; + + apr_file_close(pl->read_fd); + apr_file_close(pl->write_fd); + return APR_SUCCESS; +} + + +static apr_status_t piped_log_cleanup(void *data) +{ + piped_log *pl = data; + + if (pl->pid != NULL) { + apr_proc_kill(pl->pid, SIGTERM); + } + return piped_log_cleanup_for_exec(data); +} + + +AP_DECLARE(piped_log *) ap_open_piped_log_ex(apr_pool_t *p, + const char *program, + apr_cmdtype_e cmdtype) +{ + piped_log *pl; + + pl = apr_palloc(p, sizeof (*pl)); + pl->p = p; + pl->program = apr_pstrdup(p, program); + pl->pid = NULL; + pl->cmdtype = cmdtype; + if (apr_file_pipe_create_ex(&pl->read_fd, + &pl->write_fd, + APR_FULL_BLOCK, p) != APR_SUCCESS) { + return NULL; + } + apr_pool_cleanup_register(p, pl, piped_log_cleanup, + piped_log_cleanup_for_exec); + if (piped_log_spawn(pl) != APR_SUCCESS) { + apr_pool_cleanup_kill(p, pl, piped_log_cleanup); + apr_file_close(pl->read_fd); + apr_file_close(pl->write_fd); + return NULL; + } + return pl; +} + +#else /* !AP_HAVE_RELIABLE_PIPED_LOGS */ + +static apr_status_t piped_log_cleanup(void *data) +{ + piped_log *pl = data; + + apr_file_close(pl->write_fd); + return APR_SUCCESS; +} + +AP_DECLARE(piped_log *) ap_open_piped_log_ex(apr_pool_t *p, + const char *program, + apr_cmdtype_e cmdtype) +{ + piped_log *pl; + apr_file_t *dummy = NULL; + int rc; + + rc = log_child(p, program, &dummy, cmdtype, 0); + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, ap_server_conf, APLOGNO(00108) + "Couldn't start piped log process '%s'.", + (program == NULL) ? "NULL" : program); + return NULL; + } + + pl = apr_palloc(p, sizeof (*pl)); + pl->p = p; + pl->read_fd = NULL; + pl->write_fd = dummy; + apr_pool_cleanup_register(p, pl, piped_log_cleanup, piped_log_cleanup); + + return pl; +} + +#endif + +AP_DECLARE(piped_log *) ap_open_piped_log(apr_pool_t *p, + const char *program) +{ + apr_cmdtype_e cmdtype = APR_PROGRAM_ENV; + + /* In 2.4 favor PROGRAM_ENV, accept "||prog" syntax for compatibility + * and "|$cmd" to override the default. + * Any 2.2 backport would continue to favor SHELLCMD_ENV so there + * accept "||prog" to override, and "|$cmd" to ease conversion. + */ + if (*program == '|') + ++program; + if (*program == '$') { + cmdtype = APR_SHELLCMD_ENV; + ++program; + } + + return ap_open_piped_log_ex(p, program, cmdtype); +} + +AP_DECLARE(void) ap_close_piped_log(piped_log *pl) +{ + apr_pool_cleanup_run(pl->p, pl, piped_log_cleanup); +} + +AP_DECLARE(const char *) ap_parse_log_level(const char *str, int *val) +{ + char *err = "Log level keyword must be one of emerg/alert/crit/error/warn/" + "notice/info/debug/trace1/.../trace8"; + int i = 0; + + if (str == NULL) + return err; + + while (priorities[i].t_name != NULL) { + if (!strcasecmp(str, priorities[i].t_name)) { + *val = priorities[i].t_val; + return NULL; + } + i++; + } + return err; +} + +AP_IMPLEMENT_HOOK_VOID(error_log, + (const ap_errorlog_info *info, const char *errstr), + (info, errstr)) + +AP_IMPLEMENT_HOOK_RUN_FIRST(int, generate_log_id, + (const conn_rec *c, const request_rec *r, + const char **id), + (c, r, id), DECLINED) diff --git a/server/main.c b/server/main.c new file mode 100644 index 0000000..f1bebeb --- /dev/null +++ b/server/main.c @@ -0,0 +1,851 @@ +/* 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 "apr.h" +#include "apr_strings.h" +#include "apr_getopt.h" +#include "apr_general.h" +#include "apr_lib.h" +#include "apr_md5.h" +#include "apr_time.h" +#include "apr_version.h" +#include "apu_version.h" + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" +#include "mod_core.h" +#include "http_request.h" +#include "http_vhost.h" +#include "apr_uri.h" +#include "util_ebcdic.h" +#include "ap_mpm.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* WARNING: Win32 binds http_main.c dynamically to the server. Please place + * extern functions and global data in another appropriate module. + * + * Most significant main() global data can be found in http_config.c + */ + +static void show_mpm_settings(void) +{ + int mpm_query_info; + apr_status_t retval; + + printf("Server MPM: %s\n", ap_show_mpm()); + + retval = ap_mpm_query(AP_MPMQ_IS_THREADED, &mpm_query_info); + + if (retval == APR_SUCCESS) { + printf(" threaded: "); + + if (mpm_query_info == AP_MPMQ_DYNAMIC) { + printf("yes (variable thread count)\n"); + } + else if (mpm_query_info == AP_MPMQ_STATIC) { + printf("yes (fixed thread count)\n"); + } + else { + printf("no\n"); + } + } + + retval = ap_mpm_query(AP_MPMQ_IS_FORKED, &mpm_query_info); + + if (retval == APR_SUCCESS) { + printf(" forked: "); + + if (mpm_query_info == AP_MPMQ_DYNAMIC) { + printf("yes (variable process count)\n"); + } + else if (mpm_query_info == AP_MPMQ_STATIC) { + printf("yes (fixed process count)\n"); + } + else { + printf("no\n"); + } + } +} + +static void show_compile_settings(void) +{ + printf("Server version: %s\n", ap_get_server_description()); + printf("Server built: %s\n", ap_get_server_built()); + printf("Server's Module Magic Number: %u:%u\n", + MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR); +#if APR_MAJOR_VERSION >= 2 + printf("Server loaded: APR %s\n", apr_version_string()); + printf("Compiled using: APR %s\n", APR_VERSION_STRING); +#else + printf("Server loaded: APR %s, APR-UTIL %s\n", + apr_version_string(), apu_version_string()); + printf("Compiled using: APR %s, APR-UTIL %s\n", + APR_VERSION_STRING, APU_VERSION_STRING); +#endif + /* sizeof(foo) is long on some platforms so we might as well + * make it long everywhere to keep the printf format + * consistent + */ + printf("Architecture: %ld-bit\n", 8 * (long)sizeof(void *)); + + show_mpm_settings(); + + printf("Server compiled with....\n"); +#ifdef BIG_SECURITY_HOLE + printf(" -D BIG_SECURITY_HOLE\n"); +#endif + +#ifdef SECURITY_HOLE_PASS_AUTHORIZATION + printf(" -D SECURITY_HOLE_PASS_AUTHORIZATION\n"); +#endif + +#ifdef OS + printf(" -D OS=\"" OS "\"\n"); +#endif + +#ifdef HAVE_SHMGET + printf(" -D HAVE_SHMGET\n"); +#endif + +#if APR_FILE_BASED_SHM + printf(" -D APR_FILE_BASED_SHM\n"); +#endif + +#if APR_HAS_SENDFILE + printf(" -D APR_HAS_SENDFILE\n"); +#endif + +#if APR_HAS_MMAP + printf(" -D APR_HAS_MMAP\n"); +#endif + +#ifdef NO_WRITEV + printf(" -D NO_WRITEV\n"); +#endif + +#ifdef NO_LINGCLOSE + printf(" -D NO_LINGCLOSE\n"); +#endif + +#if APR_HAVE_IPV6 + printf(" -D APR_HAVE_IPV6 (IPv4-mapped addresses "); +#ifdef AP_ENABLE_V4_MAPPED + printf("enabled)\n"); +#else + printf("disabled)\n"); +#endif +#endif + +#if APR_USE_FLOCK_SERIALIZE + printf(" -D APR_USE_FLOCK_SERIALIZE\n"); +#endif + +#if APR_USE_SYSVSEM_SERIALIZE + printf(" -D APR_USE_SYSVSEM_SERIALIZE\n"); +#endif + +#if APR_USE_POSIXSEM_SERIALIZE + printf(" -D APR_USE_POSIXSEM_SERIALIZE\n"); +#endif + +#if APR_USE_FCNTL_SERIALIZE + printf(" -D APR_USE_FCNTL_SERIALIZE\n"); +#endif + +#if APR_USE_PROC_PTHREAD_SERIALIZE + printf(" -D APR_USE_PROC_PTHREAD_SERIALIZE\n"); +#endif + +#if APR_USE_PTHREAD_SERIALIZE + printf(" -D APR_USE_PTHREAD_SERIALIZE\n"); +#endif + +#if APR_PROCESS_LOCK_IS_GLOBAL + printf(" -D APR_PROCESS_LOCK_IS_GLOBAL\n"); +#endif + +#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT + printf(" -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT\n"); +#endif + +#if APR_HAS_OTHER_CHILD + printf(" -D APR_HAS_OTHER_CHILD\n"); +#endif + +#ifdef AP_HAVE_RELIABLE_PIPED_LOGS + printf(" -D AP_HAVE_RELIABLE_PIPED_LOGS\n"); +#endif + +#ifdef BUFFERED_LOGS + printf(" -D BUFFERED_LOGS\n"); +#ifdef PIPE_BUF + printf(" -D PIPE_BUF=%ld\n",(long)PIPE_BUF); +#endif +#endif + + printf(" -D DYNAMIC_MODULE_LIMIT=%ld\n",(long)DYNAMIC_MODULE_LIMIT); + +#if APR_CHARSET_EBCDIC + printf(" -D APR_CHARSET_EBCDIC\n"); +#endif + +#ifdef NEED_HASHBANG_EMUL + printf(" -D NEED_HASHBANG_EMUL\n"); +#endif + +/* This list displays the compiled in default paths: */ +#ifdef HTTPD_ROOT + printf(" -D HTTPD_ROOT=\"" HTTPD_ROOT "\"\n"); +#endif + +#ifdef SUEXEC_BIN + printf(" -D SUEXEC_BIN=\"" SUEXEC_BIN "\"\n"); +#endif + +#ifdef DEFAULT_PIDLOG + printf(" -D DEFAULT_PIDLOG=\"" DEFAULT_PIDLOG "\"\n"); +#endif + +#ifdef DEFAULT_SCOREBOARD + printf(" -D DEFAULT_SCOREBOARD=\"" DEFAULT_SCOREBOARD "\"\n"); +#endif + +#ifdef DEFAULT_ERRORLOG + printf(" -D DEFAULT_ERRORLOG=\"" DEFAULT_ERRORLOG "\"\n"); +#endif + +#ifdef AP_TYPES_CONFIG_FILE + printf(" -D AP_TYPES_CONFIG_FILE=\"" AP_TYPES_CONFIG_FILE "\"\n"); +#endif + +#ifdef SERVER_CONFIG_FILE + printf(" -D SERVER_CONFIG_FILE=\"" SERVER_CONFIG_FILE "\"\n"); +#endif +} + +#define TASK_SWITCH_SLEEP 10000 + +static void destroy_and_exit_process(process_rec *process, + int process_exit_value) +{ + /* + * Sleep for TASK_SWITCH_SLEEP micro seconds to cause a task switch on + * OS layer and thus give possibly started piped loggers a chance to + * process their input. Otherwise it is possible that they get killed + * by us before they can do so. In this case maybe valueable log messages + * might get lost. + */ + apr_sleep(TASK_SWITCH_SLEEP); + ap_main_state = AP_SQ_MS_EXITING; + apr_pool_destroy(process->pool); /* and destroy all descendent pools */ + apr_terminate(); + exit(process_exit_value); +} + +/* APR callback invoked if allocation fails. */ +static int abort_on_oom(int retcode) +{ + ap_abort_on_oom(); + return retcode; /* unreachable, hopefully. */ +} + +/* Deregister all hooks when clearing pconf (pre_cleanup). + * TODO: have a hook to deregister and run them from here? + * ap_clear_auth_internal() is already a candidate. + */ +static apr_status_t deregister_all_hooks(void *unused) +{ + (void)unused; + ap_clear_auth_internal(); + apr_hook_deregister_all(); + return APR_SUCCESS; +} + +static void reset_process_pconf(process_rec *process) +{ + if (process->pconf) { + apr_pool_clear(process->pconf); + ap_server_conf = NULL; + } + else { + apr_pool_create(&process->pconf, process->pool); + apr_pool_tag(process->pconf, "pconf"); + } + apr_pool_pre_cleanup_register(process->pconf, NULL, deregister_all_hooks); +} + +static process_rec *init_process(int *argc, const char * const * *argv) +{ + process_rec *process; + apr_pool_t *cntx; + apr_status_t stat; + const char *failed = "apr_app_initialize()"; + + stat = apr_app_initialize(argc, argv, NULL); + if (stat == APR_SUCCESS) { + failed = "apr_pool_create()"; + stat = apr_pool_create(&cntx, NULL); + } + + if (stat != APR_SUCCESS) { + /* For all intents and purposes, this is impossibly unlikely, + * but APR doesn't exist yet, we can't use it for reporting + * these earliest two failures; + * + * XXX: Note the apr_ctime() and apr_time_now() calls. These + * work, today, against an uninitialized APR, but in the future + * (if they relied on global pools or mutexes, for example) then + * the datestamp logic will need to be replaced. + */ + char ctimebuff[APR_CTIME_LEN]; + apr_ctime(ctimebuff, apr_time_now()); + fprintf(stderr, "[%s] [crit] (%d) %s: %s failed " + "to initial context, exiting\n", + ctimebuff, stat, (*argv)[0], failed); + apr_terminate(); + exit(1); + } + + apr_pool_abort_set(abort_on_oom, cntx); + apr_pool_tag(cntx, "process"); + ap_open_stderr_log(cntx); + + /* Now we have initialized apr and our logger, no more + * exceptional error reporting required for the lifetime + * of this server process. + */ + + process = apr_palloc(cntx, sizeof(process_rec)); + process->pool = cntx; + + process->pconf = NULL; + reset_process_pconf(process); + + process->argc = *argc; + process->argv = *argv; + process->short_name = apr_filepath_name_get((*argv)[0]); + return process; +} + +static void usage(process_rec *process) +{ + const char *bin = process->argv[0]; + int pad_len = strlen(bin); + + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + "Usage: %s [-D name] [-d directory] [-f file]", bin); + + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " %*s [-C \"directive\"] [-c \"directive\"]", pad_len, " "); + +#ifdef WIN32 + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " %*s [-w] [-k start|restart|stop|shutdown] [-n service_name]", + pad_len, " "); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " %*s [-k install|config|uninstall] [-n service_name]", + pad_len, " "); +#else +/* XXX not all MPMs support signalling the server in general or graceful-stop + * in particular + */ + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " %*s [-k start|restart|graceful|graceful-stop|stop]", + pad_len, " "); +#endif + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " %*s [-v] [-V] [-h] [-l] [-L] [-t] [-T] [-S] [-X]", + pad_len, " "); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + "Options:"); + + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -D name : define a name for use in " + "<IfDefine name> directives"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -d directory : specify an alternate initial " + "ServerRoot"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -f file : specify an alternate ServerConfigFile"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -C \"directive\" : process directive before reading " + "config files"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -c \"directive\" : process directive after reading " + "config files"); + +#ifdef NETWARE + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -n name : set screen name"); +#endif +#ifdef WIN32 + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -n name : set service name and use its " + "ServerConfigFile and ServerRoot"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -k start : tell Apache to start"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -k restart : tell running Apache to do a graceful " + "restart"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -k stop|shutdown : tell running Apache to shutdown"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -k install : install an Apache service"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -k config : change startup Options of an Apache " + "service"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -k uninstall : uninstall an Apache service"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -w : hold open the console window on error"); +#endif + + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -e level : show startup errors of level " + "(see LogLevel)"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -E file : log startup errors to file"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -v : show version number"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -V : show compile settings"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -h : list available command line options " + "(this page)"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -l : list compiled in modules"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -L : list available configuration " + "directives"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -t -D DUMP_VHOSTS : show parsed vhost settings"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -t -D DUMP_RUN_CFG : show parsed run settings"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -S : a synonym for -t -D DUMP_VHOSTS -D DUMP_RUN_CFG"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -t -D DUMP_MODULES : show all loaded modules "); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -M : a synonym for -t -D DUMP_MODULES"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -t -D DUMP_INCLUDES: show all included configuration files"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -t : run syntax check for config files"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -T : start without DocumentRoot(s) check"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -X : debug mode (only one worker, do not detach)"); + + destroy_and_exit_process(process, 1); +} + +int main(int argc, const char * const argv[]) +{ + char c; + int showcompile = 0, showdirectives = 0; + const char *confname = SERVER_CONFIG_FILE; + const char *def_server_root = HTTPD_ROOT; + const char *temp_error_log = NULL; + const char *error; + process_rec *process; + apr_pool_t *pconf; + apr_pool_t *plog; /* Pool of log streams, reset _after_ each read of conf */ + apr_pool_t *ptemp; /* Pool for temporary config stuff, reset often */ + apr_pool_t *pcommands; /* Pool for -D, -C and -c switches */ + apr_getopt_t *opt; + apr_status_t rv; + module **mod; + const char *opt_arg; + APR_OPTIONAL_FN_TYPE(ap_signal_server) *signal_server; + int rc = OK; + + AP_MONCONTROL(0); /* turn off profiling of startup */ + + process = init_process(&argc, &argv); + ap_pglobal = process->pool; + pconf = process->pconf; + ap_server_argv0 = process->short_name; + ap_init_rng(ap_pglobal); + + /* Set up the OOM callback in the global pool, so all pools should + * by default inherit it. */ + apr_pool_abort_set(abort_on_oom, apr_pool_parent_get(process->pool)); + +#if APR_CHARSET_EBCDIC + if (ap_init_ebcdic(ap_pglobal) != APR_SUCCESS) { + destroy_and_exit_process(process, 1); + } +#endif + + apr_pool_create(&pcommands, ap_pglobal); + apr_pool_tag(pcommands, "pcommands"); + ap_server_pre_read_config = apr_array_make(pcommands, 1, + sizeof(const char *)); + ap_server_post_read_config = apr_array_make(pcommands, 1, + sizeof(const char *)); + ap_server_config_defines = apr_array_make(pcommands, 1, + sizeof(const char *)); + + error = ap_setup_prelinked_modules(process); + if (error) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, 0, NULL, APLOGNO(00012) + "%s: %s", ap_server_argv0, error); + destroy_and_exit_process(process, 1); + } + + ap_run_rewrite_args(process); + + /* Maintain AP_SERVER_BASEARGS list in http_main.h to allow the MPM + * to safely pass on our args from its rewrite_args() handler. + */ + apr_getopt_init(&opt, pcommands, process->argc, process->argv); + + while ((rv = apr_getopt(opt, AP_SERVER_BASEARGS, &c, &opt_arg)) + == APR_SUCCESS) { + const char **new; + + switch (c) { + case 'c': + new = (const char **)apr_array_push(ap_server_post_read_config); + *new = apr_pstrdup(pcommands, opt_arg); + break; + + case 'C': + new = (const char **)apr_array_push(ap_server_pre_read_config); + *new = apr_pstrdup(pcommands, opt_arg); + break; + + case 'd': + def_server_root = opt_arg; + break; + + case 'D': + new = (const char **)apr_array_push(ap_server_config_defines); + *new = apr_pstrdup(pcommands, opt_arg); + /* Setting -D DUMP_VHOSTS should work like setting -S */ + if (strcmp(opt_arg, "DUMP_VHOSTS") == 0) + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + /* Setting -D DUMP_RUN_CFG should work like setting -S */ + else if (strcmp(opt_arg, "DUMP_RUN_CFG") == 0) + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + /* Setting -D DUMP_MODULES is equivalent to setting -M */ + else if (strcmp(opt_arg, "DUMP_MODULES") == 0) + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + /* Setting -D DUMP_INCLUDES is a type of configuration dump */ + else if (strcmp(opt_arg, "DUMP_INCLUDES") == 0) + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + break; + + case 'e': + if (ap_parse_log_level(opt_arg, &ap_default_loglevel) != NULL) + usage(process); + break; + + case 'E': + temp_error_log = apr_pstrdup(process->pool, opt_arg); + break; + + case 'X': + new = (const char **)apr_array_push(ap_server_config_defines); + *new = "DEBUG"; + break; + + case 'f': + confname = opt_arg; + break; + + case 'v': + printf("Server version: %s\n", ap_get_server_description()); + printf("Server built: %s\n", ap_get_server_built()); + destroy_and_exit_process(process, 0); + + case 'l': + ap_show_modules(); + destroy_and_exit_process(process, 0); + + case 'L': + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + showdirectives = 1; + break; + + case 't': + if (ap_run_mode == AP_SQ_RM_UNKNOWN) + ap_run_mode = AP_SQ_RM_CONFIG_TEST; + break; + + case 'T': + ap_document_root_check = 0; + break; + + case 'S': + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + new = (const char **)apr_array_push(ap_server_config_defines); + *new = "DUMP_VHOSTS"; + new = (const char **)apr_array_push(ap_server_config_defines); + *new = "DUMP_RUN_CFG"; + break; + + case 'M': + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + new = (const char **)apr_array_push(ap_server_config_defines); + *new = "DUMP_MODULES"; + break; + + case 'V': + if (strcmp(ap_show_mpm(), "")) { /* MPM built-in? */ + show_compile_settings(); + destroy_and_exit_process(process, 0); + } + else { + showcompile = 1; + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + } + break; + + case 'h': + case '?': + usage(process); + } + } + + if (ap_run_mode == AP_SQ_RM_UNKNOWN) + ap_run_mode = AP_SQ_RM_NORMAL; + + /* bad cmdline option? then we die */ + if (rv != APR_EOF || opt->ind < opt->argc) { + usage(process); + } + + ap_main_state = AP_SQ_MS_CREATE_PRE_CONFIG; + apr_pool_create(&plog, ap_pglobal); + apr_pool_tag(plog, "plog"); + apr_pool_create(&ptemp, pconf); + apr_pool_tag(ptemp, "ptemp"); + + /* Note that we preflight the config file once + * before reading it _again_ in the main loop. + * This allows things, log files configuration + * for example, to settle down. + */ + + ap_server_root = def_server_root; + if (temp_error_log) { + ap_replace_stderr_log(process->pool, temp_error_log); + } + ap_server_conf = ap_read_config(process, ptemp, confname, &ap_conftree); + if (!ap_server_conf) { + if (showcompile) { + /* Well, we tried. Show as much as we can, but exit nonzero to + * indicate that something's not right. The cause should have + * already been logged. */ + show_compile_settings(); + } + destroy_and_exit_process(process, 1); + } + apr_pool_cleanup_register(pconf, &ap_server_conf, ap_pool_cleanup_set_null, + apr_pool_cleanup_null); + + if (showcompile) { /* deferred due to dynamically loaded MPM */ + show_compile_settings(); + destroy_and_exit_process(process, 0); + } + + /* sort hooks here to make sure pre_config hooks are sorted properly */ + apr_hook_sort_all(); + + if (ap_run_pre_config(pconf, plog, ptemp) != OK) { + ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0, + NULL, APLOGNO(00013) "Pre-configuration failed"); + destroy_and_exit_process(process, 1); + } + + rv = ap_process_config_tree(ap_server_conf, ap_conftree, + process->pconf, ptemp); + if (rv == OK) { + ap_fixup_virtual_hosts(pconf, ap_server_conf); + ap_fini_vhost_config(pconf, ap_server_conf); + /* + * Sort hooks again because ap_process_config_tree may have add modules + * and hence hooks. This happens with mod_perl and modules written in + * perl. + */ + apr_hook_sort_all(); + + if (ap_run_check_config(pconf, plog, ptemp, ap_server_conf) != OK) { + ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0, + NULL, APLOGNO(00014) "Configuration check failed"); + destroy_and_exit_process(process, 1); + } + + if (ap_run_mode != AP_SQ_RM_NORMAL) { + if (showdirectives) { /* deferred in case of DSOs */ + ap_show_directives(); + destroy_and_exit_process(process, 0); + } + else { + ap_run_test_config(pconf, ap_server_conf); + if (ap_run_mode == AP_SQ_RM_CONFIG_TEST) + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, "Syntax OK"); + } + destroy_and_exit_process(process, 0); + } + } + + /* If our config failed, deal with that here. */ + if (rv != OK) { + destroy_and_exit_process(process, 1); + } + + signal_server = APR_RETRIEVE_OPTIONAL_FN(ap_signal_server); + if (signal_server) { + int exit_status; + + if (signal_server(&exit_status, pconf) != 0) { + destroy_and_exit_process(process, exit_status); + } + } + + apr_pool_clear(plog); + + if ( ap_run_open_logs(pconf, plog, ptemp, ap_server_conf) != OK) { + ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, + 0, NULL, APLOGNO(00015) "Unable to open logs"); + destroy_and_exit_process(process, 1); + } + + if ( ap_run_post_config(pconf, plog, ptemp, ap_server_conf) != OK) { + ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0, + NULL, APLOGNO(00016) "Configuration Failed"); + destroy_and_exit_process(process, 1); + } + + apr_pool_destroy(ptemp); + + do { + ap_main_state = AP_SQ_MS_DESTROY_CONFIG; + reset_process_pconf(process); + + ap_main_state = AP_SQ_MS_CREATE_CONFIG; + ap_config_generation++; + for (mod = ap_prelinked_modules; *mod != NULL; mod++) { + ap_register_hooks(*mod, pconf); + } + + /* This is a hack until we finish the code so that it only reads + * the config file once and just operates on the tree already in + * memory. rbb + */ + ap_conftree = NULL; + apr_pool_create(&ptemp, pconf); + apr_pool_tag(ptemp, "ptemp"); + ap_server_root = def_server_root; + ap_server_conf = ap_read_config(process, ptemp, confname, &ap_conftree); + if (!ap_server_conf) { + destroy_and_exit_process(process, 1); + } + apr_pool_cleanup_register(pconf, &ap_server_conf, + ap_pool_cleanup_set_null, apr_pool_cleanup_null); + /* sort hooks here to make sure pre_config hooks are sorted properly */ + apr_hook_sort_all(); + + if (ap_run_pre_config(pconf, plog, ptemp) != OK) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, NULL, + APLOGNO(00017) "Pre-configuration failed, exiting"); + destroy_and_exit_process(process, 1); + } + + if (ap_process_config_tree(ap_server_conf, ap_conftree, process->pconf, + ptemp) != OK) { + destroy_and_exit_process(process, 1); + } + ap_fixup_virtual_hosts(pconf, ap_server_conf); + ap_fini_vhost_config(pconf, ap_server_conf); + /* + * Sort hooks again because ap_process_config_tree may have add modules + * and hence hooks. This happens with mod_perl and modules written in + * perl. + */ + apr_hook_sort_all(); + + if (ap_run_check_config(pconf, plog, ptemp, ap_server_conf) != OK) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, NULL, + APLOGNO(00018) "Configuration check failed, exiting"); + destroy_and_exit_process(process, 1); + } + + apr_pool_clear(plog); + if (ap_run_open_logs(pconf, plog, ptemp, ap_server_conf) != OK) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, NULL, + APLOGNO(00019) "Unable to open logs, exiting"); + destroy_and_exit_process(process, 1); + } + + if (ap_run_post_config(pconf, plog, ptemp, ap_server_conf) != OK) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, NULL, + APLOGNO(00020) "Configuration Failed, exiting"); + destroy_and_exit_process(process, 1); + } + + apr_pool_destroy(ptemp); + apr_pool_lock(pconf, 1); + + ap_run_optional_fn_retrieve(); + + ap_main_state = AP_SQ_MS_RUN_MPM; + rc = ap_run_mpm(pconf, plog, ap_server_conf); + + apr_pool_lock(pconf, 0); + + } while (rc == OK); + + if (rc == DONE) { + rc = OK; + } + else if (rc != OK) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, NULL, APLOGNO(02818) + "MPM run failed, exiting"); + } + destroy_and_exit_process(process, rc); + + /* NOTREACHED */ + return !OK; +} + +#ifdef AP_USING_AUTOCONF +/* This ugly little hack pulls any function referenced in exports.c into + * the web server. exports.c is generated during the build, and it + * has all of the APR functions specified by the apr/apr.exports and + * apr-util/aprutil.exports files. + */ +const void *ap_suck_in_APR(void); +const void *ap_suck_in_APR(void) +{ + extern const void *ap_ugly_hack; + + return ap_ugly_hack; +} +#endif diff --git a/server/mpm/MPM.NAMING b/server/mpm/MPM.NAMING new file mode 100644 index 0000000..c07884d --- /dev/null +++ b/server/mpm/MPM.NAMING @@ -0,0 +1,14 @@ + +The following MPMs currently exist: + + prefork ....... Multi Process Model with Preforking (Apache 1.3) + mpmt_os2 ...... Multi Process Model with Threading on OS/2 + Constant number of processes, variable number of threads. + One acceptor thread per process, multiple workers threads. + winnt ......... Single Process Model with Threading on Windows NT + event ......... Multi Process model with threads. One acceptor thread, + multiple worker threads, separate poller threads for idle + connections and asynchoneous write completion. + worker ........ Multi Process model with threads. One acceptor thread, + multiple worker threads. + netware ....... Multi-threaded MPM for Netware diff --git a/server/mpm/Makefile.in b/server/mpm/Makefile.in new file mode 100644 index 0000000..a158f8b --- /dev/null +++ b/server/mpm/Makefile.in @@ -0,0 +1,4 @@ + +SUBDIRS = $(MPM_SUBDIRS) + +include $(top_builddir)/build/rules.mk diff --git a/server/mpm/config.m4 b/server/mpm/config.m4 new file mode 100644 index 0000000..6d3ab86 --- /dev/null +++ b/server/mpm/config.m4 @@ -0,0 +1,128 @@ +dnl common platform checks needed by MPMs, methods for MPMs to state +dnl their support for the platform, functions to query MPM properties + +APR_CHECK_APR_DEFINE(APR_HAS_THREADS) + +have_threaded_sig_graceful=yes +case $host in + *-linux-*) + case `uname -r` in + 2.0* ) + dnl Threaded MPM's are not supported on Linux 2.0 + dnl as on 2.0 the linuxthreads library uses SIGUSR1 + dnl and SIGUSR2 internally + have_threaded_sig_graceful=no + ;; + esac + ;; +esac + +dnl See if APR supports APR_POLLSET_THREADSAFE. +dnl XXX This hack tests for the underlying functions used by APR when it supports +dnl XXX APR_POLLSET_THREADSAFE, and duplicates APR's Darwin version check. +dnl A run-time check for +dnl apr_pollset_create(,,APR_POLLSET_THREADSAFE) == APR_SUCCESS +dnl would be great but an in-tree apr (srclib/apr) hasn't been built yet. + +AC_CACHE_CHECK([whether APR supports thread-safe pollsets], [ac_cv_have_threadsafe_pollset], [ + case $host in + *-apple-darwin[[1-9]].*) + APR_SETIFNULL(ac_cv_func_kqueue, [no]) + ;; + esac + AC_CHECK_FUNCS(kqueue port_create epoll_create) + if test "$ac_cv_func_kqueue$ac_cv_func_port_create$ac_cv_func_epoll_create" != "nonono"; then + ac_cv_have_threadsafe_pollset=yes + else + ac_cv_have_threadsafe_pollset=no + fi +]) + +dnl See if APR has skiplist +dnl The base httpd prereq is APR 1.4.x, so we don't have to consider +dnl earlier versions. +case $APR_VERSION in + 1.4*) + apr_has_skiplist=no + ;; + *) + apr_has_skiplist=yes +esac + +dnl See if this is a forking platform w.r.t. MPMs +case $host in + *mingw32* | *os2-emx*) + forking_mpms_supported=no + ;; + *) + forking_mpms_supported=yes + ;; +esac + +dnl APACHE_MPM_SUPPORTED(name, supports-shared, is_threaded) +AC_DEFUN([APACHE_MPM_SUPPORTED],[ + if test "$2" = "yes"; then + eval "ap_supported_mpm_$1=shared" + ap_supported_shared_mpms="$ap_supported_shared_mpms $1 " + else + eval "ap_supported_mpm_$1=static" + fi + if test "$3" = "yes"; then + eval "ap_threaded_mpm_$1=yes" + fi +])dnl + +dnl APACHE_MPM_ENABLED(name) +AC_DEFUN([APACHE_MPM_ENABLED],[ + if ap_mpm_is_enabled $1; then + : + else + eval "ap_enabled_mpm_$1=yes" + ap_enabled_mpms="$ap_enabled_mpms $1 " + fi +])dnl + +ap_mpm_is_supported () +{ + eval "tmp=\$ap_supported_mpm_$1" + if test -z "$tmp"; then + return 1 + else + return 0 + fi +} + +ap_mpm_supports_shared () +{ + eval "tmp=\$ap_supported_mpm_$1" + if test "$tmp" = "shared"; then + return 0 + else + return 1 + fi +} + +ap_mpm_is_threaded () +{ + if test "$mpm_build" = "shared" -a ac_cv_define_APR_HAS_THREADS = "yes"; then + return 0 + fi + + for mpm in $ap_enabled_mpms; do + eval "tmp=\$ap_threaded_mpm_$mpm" + if test "$tmp" = "yes"; then + return 0 + fi + done + return 1 +} + +ap_mpm_is_enabled () +{ + eval "tmp=\$ap_enabled_mpm_$1" + if test "$tmp" = "yes"; then + return 0 + else + return 1 + fi +} diff --git a/server/mpm/config2.m4 b/server/mpm/config2.m4 new file mode 100644 index 0000000..d7e73ec --- /dev/null +++ b/server/mpm/config2.m4 @@ -0,0 +1,89 @@ +AC_MSG_CHECKING(which MPM to use by default) +AC_ARG_WITH(mpm, +APACHE_HELP_STRING(--with-mpm=MPM,Choose the process model for Apache to use by default. + MPM={event|worker|prefork|winnt} + This will be statically linked as the only available MPM unless + --enable-mpms-shared is also specified. +),[ + default_mpm=$withval + AC_MSG_RESULT($withval); +],[ + dnl Order of preference for default MPM: + dnl The Windows and OS/2 MPMs are used on those platforms. + dnl Everywhere else: event, worker, prefork + if ap_mpm_is_supported "winnt"; then + default_mpm=winnt + AC_MSG_RESULT(winnt) + elif ap_mpm_is_supported "mpmt_os2"; then + default_mpm=mpmt_os2 + AC_MSG_RESULT(mpmt_os2) + elif ap_mpm_is_supported "event"; then + default_mpm=event + AC_MSG_RESULT(event) + elif ap_mpm_is_supported "worker"; then + default_mpm=worker + AC_MSG_RESULT(worker - event is not supported) + else + default_mpm=prefork + AC_MSG_RESULT(prefork - event and worker are not supported) + fi +]) + +APACHE_MPM_ENABLED($default_mpm) + +AC_ARG_ENABLE(mpms-shared, +APACHE_HELP_STRING(--enable-mpms-shared=MPM-LIST,Space-separated list of MPM modules to enable for dynamic loading. MPM-LIST=list | "all"),[ + if test "$enableval" = "no"; then + mpm_build=static + else + mpm_build=shared +dnl Build just the default MPM if --enable-mpms-shared has no argument. + if test "$enableval" = "yes"; then + enableval=$default_mpm + fi + for i in $enableval; do + if test "$i" = "all"; then + for j in $ap_supported_shared_mpms; do + eval "enable_mpm_$j=shared" + APACHE_MPM_ENABLED($j) + done + else + i=`echo $i | sed 's/-/_/g'` + if ap_mpm_supports_shared $i; then + eval "enable_mpm_$i=shared" + APACHE_MPM_ENABLED($i) + else + AC_MSG_ERROR([MPM $i does not support dynamic loading.]) + fi + fi + done + fi +], [mpm_build=static]) + +for i in $ap_enabled_mpms; do + if ap_mpm_is_supported $i; then + : + else + AC_MSG_ERROR([MPM $i is not supported on this platform.]) + fi +done + +if test $mpm_build = "shared"; then + eval "tmp=\$enable_mpm_$default_mpm" + if test "$tmp" != "shared"; then + AC_MSG_ERROR([The default MPM ($default_mpm) must be included in --enable-mpms-shared. Use --with-mpm to change the default MPM.]) + fi +fi + +APACHE_FAST_OUTPUT(server/mpm/Makefile) + +if test $mpm_build = "shared"; then + MPM_LIB="" +else + MPM_LIB=server/mpm/$default_mpm/lib${default_mpm}.la + MODLIST="$MODLIST mpm_${default_mpm}" +fi + +MPM_SUBDIRS=$ap_enabled_mpms +APACHE_SUBST(MPM_SUBDIRS) +APACHE_SUBST(MPM_LIB) diff --git a/server/mpm/event/Makefile.in b/server/mpm/event/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/event/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/event/config.m4 b/server/mpm/event/config.m4 new file mode 100644 index 0000000..c891c75 --- /dev/null +++ b/server/mpm/event/config.m4 @@ -0,0 +1,15 @@ +AC_MSG_CHECKING(if event MPM supports this platform) +if test $forking_mpms_supported != yes; then + AC_MSG_RESULT(no - This is not a forking platform) +elif test $ac_cv_define_APR_HAS_THREADS != yes; then + AC_MSG_RESULT(no - APR does not support threads) +elif test $have_threaded_sig_graceful != yes; then + AC_MSG_RESULT(no - SIG_GRACEFUL cannot be used with a threaded MPM) +elif test $ac_cv_have_threadsafe_pollset != yes; then + AC_MSG_RESULT(no - APR_POLLSET_THREADSAFE is not supported) +elif test $apr_has_skiplist != yes; then + AC_MSG_RESULT(no - APR skiplist is not available, need APR 1.5.x or later) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(event, yes, yes) +fi diff --git a/server/mpm/event/config3.m4 b/server/mpm/event/config3.m4 new file mode 100644 index 0000000..09d3626 --- /dev/null +++ b/server/mpm/event/config3.m4 @@ -0,0 +1,7 @@ +dnl ## XXX - Need a more thorough check of the proper flags to use + +APACHE_SUBST(MOD_MPM_EVENT_LDADD) + +APACHE_MPM_MODULE(event, $enable_mpm_event, event.lo,[ + AC_CHECK_FUNCS(pthread_kill) +], , [\$(MOD_MPM_EVENT_LDADD)]) diff --git a/server/mpm/event/event.c b/server/mpm/event/event.c new file mode 100644 index 0000000..4cfb09c --- /dev/null +++ b/server/mpm/event/event.c @@ -0,0 +1,3912 @@ +/* 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 MPM tries to fix the 'keep alive problem' in HTTP. + * + * After a client completes the first request, the client can keep the + * connection open to send more requests with the same socket. This can save + * significant overhead in creating TCP connections. However, the major + * disadvantage is that Apache traditionally keeps an entire child + * process/thread waiting for data from the client. To solve this problem, + * this MPM has a dedicated thread for handling both the Listening sockets, + * and all sockets that are in a Keep Alive status. + * + * The MPM assumes the underlying apr_pollset implementation is somewhat + * threadsafe. This currently is only compatible with KQueue and EPoll. This + * enables the MPM to avoid extra high level locking or having to wake up the + * listener thread when a keep-alive socket needs to be sent to it. + * + * This MPM does not perform well on older platforms that do not have very good + * threading, like Linux with a 2.4 kernel, but this does not matter, since we + * require EPoll or KQueue. + * + * For FreeBSD, use 5.3. It is possible to run this MPM on FreeBSD 5.2.1, if + * you use libkse (see `man libmap.conf`). + * + * For NetBSD, use at least 2.0. + * + * For Linux, you should use a 2.6 kernel, and make sure your glibc has epoll + * support compiled in. + * + */ + +#include "apr.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "apr_file_io.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_thread_mutex.h" +#include "apr_poll.h" +#include "apr_ring.h" +#include "apr_queue.h" +#include "apr_atomic.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_version.h" + +#include <stdlib.h> + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if APR_HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifdef HAVE_SYS_PROCESSOR_H +#include <sys/processor.h> /* for bindprocessor() */ +#endif + +#if !APR_HAS_THREADS +#error The Event MPM requires APR threads, but they are unavailable. +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "http_protocol.h" +#include "ap_mpm.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "scoreboard.h" +#include "mpm_fdqueue.h" +#include "mpm_default.h" +#include "http_vhost.h" +#include "unixd.h" +#include "apr_skiplist.h" + +#include <signal.h> +#include <limits.h> /* for INT_MAX */ + + +/* Limit on the total --- clients will be locked out if more servers than + * this are needed. It is intended solely to keep the server from crashing + * when things get out of hand. + * + * We keep a hard maximum number of servers, for two reasons --- first off, + * in case something goes seriously wrong, we want to stop the fork bomb + * short of actually crashing the machine we're running on by filling some + * kernel table. Secondly, it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_SERVER_LIMIT +#define DEFAULT_SERVER_LIMIT 16 +#endif + +/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_SERVER_LIMIT +#define MAX_SERVER_LIMIT 20000 +#endif + +/* Limit on the threads per process. Clients will be locked out if more than + * this are needed. + * + * We keep this for one reason it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_THREAD_LIMIT +#define DEFAULT_THREAD_LIMIT 64 +#endif + +/* Admin can't tune ThreadLimit beyond MAX_THREAD_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_THREAD_LIMIT +#define MAX_THREAD_LIMIT 100000 +#endif + +#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid) + +#if !APR_VERSION_AT_LEAST(1,4,0) +#define apr_time_from_msec(x) (x * 1000) +#endif + +#ifndef MAX_SECS_TO_LINGER +#define MAX_SECS_TO_LINGER 30 +#endif +#define SECONDS_TO_LINGER 2 + +/* + * Actual definitions of config globals + */ + +#ifndef DEFAULT_WORKER_FACTOR +#define DEFAULT_WORKER_FACTOR 2 +#endif +#define WORKER_FACTOR_SCALE 16 /* scale factor to allow fractional values */ +static unsigned int worker_factor = DEFAULT_WORKER_FACTOR * WORKER_FACTOR_SCALE; + /* AsyncRequestWorkerFactor * 16 */ + +static int threads_per_child = 0; /* ThreadsPerChild */ +static int ap_daemons_to_start = 0; /* StartServers */ +static int min_spare_threads = 0; /* MinSpareThreads */ +static int max_spare_threads = 0; /* MaxSpareThreads */ +static int active_daemons_limit = 0; /* MaxRequestWorkers / ThreadsPerChild */ +static int active_daemons = 0; /* workers that still active, i.e. are + not shutting down gracefully */ +static int max_workers = 0; /* MaxRequestWorkers */ +static int server_limit = 0; /* ServerLimit */ +static int thread_limit = 0; /* ThreadLimit */ +static int had_healthy_child = 0; +static volatile int dying = 0; +static volatile int workers_may_exit = 0; +static volatile int start_thread_may_exit = 0; +static volatile int listener_may_exit = 0; +static int listener_is_wakeable = 0; /* Pollset supports APR_POLLSET_WAKEABLE */ +static int num_listensocks = 0; +static apr_int32_t conns_this_child; /* MaxConnectionsPerChild, only access + in listener thread */ +static apr_uint32_t connection_count = 0; /* Number of open connections */ +static apr_uint32_t lingering_count = 0; /* Number of connections in lingering close */ +static apr_uint32_t suspended_count = 0; /* Number of suspended connections */ +static apr_uint32_t clogged_count = 0; /* Number of threads processing ssl conns */ +static apr_uint32_t threads_shutdown = 0; /* Number of threads that have shutdown + early during graceful termination */ +static int resource_shortage = 0; +static fd_queue_t *worker_queue; +static fd_queue_info_t *worker_queue_info; + +static apr_thread_mutex_t *timeout_mutex; + +module AP_MODULE_DECLARE_DATA mpm_event_module; + +/* forward declare */ +struct event_srv_cfg_s; +typedef struct event_srv_cfg_s event_srv_cfg; + +static apr_pollfd_t *listener_pollfd; + +/* + * The pollset for sockets that are in any of the timeout queues. Currently + * we use the timeout_mutex to make sure that connections are added/removed + * atomically to/from both event_pollset and a timeout queue. Otherwise + * some confusion can happen under high load if timeout queues and pollset + * get out of sync. + * XXX: It should be possible to make the lock unnecessary in many or even all + * XXX: cases. + */ +static apr_pollset_t *event_pollset; + +typedef struct event_conn_state_t event_conn_state_t; + +/* + * The chain of connections to be shutdown by a worker thread (deferred), + * linked list updated atomically. + */ +static event_conn_state_t *volatile defer_linger_chain; + +struct event_conn_state_t { + /** APR_RING of expiration timeouts */ + APR_RING_ENTRY(event_conn_state_t) timeout_list; + /** the time when the entry was queued */ + apr_time_t queue_timestamp; + /** connection record this struct refers to */ + conn_rec *c; + /** request record (if any) this struct refers to */ + request_rec *r; + /** server config this struct refers to */ + event_srv_cfg *sc; + /** scoreboard handle for the conn_rec */ + ap_sb_handle_t *sbh; + /** is the current conn_rec suspended? (disassociated with + * a particular MPM thread; for suspend_/resume_connection + * hooks) + */ + int suspended; + /** memory pool to allocate from */ + apr_pool_t *p; + /** bucket allocator */ + apr_bucket_alloc_t *bucket_alloc; + /** poll file descriptor information */ + apr_pollfd_t pfd; + /** public parts of the connection state */ + conn_state_t pub; + /** chaining in defer_linger_chain */ + struct event_conn_state_t *chain; +}; + +APR_RING_HEAD(timeout_head_t, event_conn_state_t); + +struct timeout_queue { + struct timeout_head_t head; + apr_interval_time_t timeout; + apr_uint32_t count; /* for this queue */ + apr_uint32_t *total; /* for all chained/related queues */ + struct timeout_queue *next; /* chaining */ +}; +/* + * Several timeout queues that use different timeouts, so that we always can + * simply append to the end. + * write_completion_q uses vhost's TimeOut + * keepalive_q uses vhost's KeepAliveTimeOut + * linger_q uses MAX_SECS_TO_LINGER + * short_linger_q uses SECONDS_TO_LINGER + */ +static struct timeout_queue *write_completion_q, + *keepalive_q, + *linger_q, + *short_linger_q; +static volatile apr_time_t queues_next_expiry; + +/* Prevent extra poll/wakeup calls for timeouts close in the future (queues + * have the granularity of a second anyway). + * XXX: Wouldn't 0.5s (instead of 0.1s) be "enough"? + */ +#define TIMEOUT_FUDGE_FACTOR apr_time_from_msec(100) + +/* + * Macros for accessing struct timeout_queue. + * For TO_QUEUE_APPEND and TO_QUEUE_REMOVE, timeout_mutex must be held. + */ +static void TO_QUEUE_APPEND(struct timeout_queue *q, event_conn_state_t *el) +{ + apr_time_t q_expiry; + apr_time_t next_expiry; + + APR_RING_INSERT_TAIL(&q->head, el, event_conn_state_t, timeout_list); + ++*q->total; + ++q->count; + + /* Cheaply update the overall queues' next expiry according to the + * first entry of this queue (oldest), if necessary. + */ + el = APR_RING_FIRST(&q->head); + q_expiry = el->queue_timestamp + q->timeout; + next_expiry = queues_next_expiry; + if (!next_expiry || next_expiry > q_expiry + TIMEOUT_FUDGE_FACTOR) { + queues_next_expiry = q_expiry; + /* Unblock the poll()ing listener for it to update its timeout. */ + if (listener_is_wakeable) { + apr_pollset_wakeup(event_pollset); + } + } +} + +static void TO_QUEUE_REMOVE(struct timeout_queue *q, event_conn_state_t *el) +{ + APR_RING_REMOVE(el, timeout_list); + APR_RING_ELEM_INIT(el, timeout_list); + --*q->total; + --q->count; +} + +static struct timeout_queue *TO_QUEUE_MAKE(apr_pool_t *p, apr_time_t t, + struct timeout_queue *ref) +{ + struct timeout_queue *q; + + q = apr_pcalloc(p, sizeof *q); + APR_RING_INIT(&q->head, event_conn_state_t, timeout_list); + q->total = (ref) ? ref->total : apr_pcalloc(p, sizeof *q->total); + q->timeout = t; + + return q; +} + +#define TO_QUEUE_ELEM_INIT(el) \ + APR_RING_ELEM_INIT((el), timeout_list) + +/* The structure used to pass unique initialization info to each thread */ +typedef struct +{ + int pslot; /* process slot */ + int tslot; /* worker slot of the thread */ +} proc_info; + +/* Structure used to pass information to the thread responsible for + * creating the rest of the threads. + */ +typedef struct +{ + apr_thread_t **threads; + apr_thread_t *listener; + int child_num_arg; + apr_threadattr_t *threadattr; +} thread_starter; + +typedef enum +{ + PT_CSD, + PT_ACCEPT +} poll_type_e; + +typedef struct +{ + poll_type_e type; + void *baton; +} listener_poll_type; + +/* data retained by event across load/unload of the module + * allocated on first call to pre-config hook; located on + * subsequent calls to pre-config hook + */ +typedef struct event_retained_data { + ap_unixd_mpm_retained_data *mpm; + + int first_server_limit; + int first_thread_limit; + int sick_child_detected; + int maxclients_reported; + int near_maxclients_reported; + /* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers changes across AP_SIG_GRACEFUL restarts. + * We use this value to optimize routines that have to scan the entire + * scoreboard. + */ + int max_daemons_limit; + + /* + * All running workers, active and shutting down, including those that + * may be left from before a graceful restart. + * Not kept up-to-date when shutdown is pending. + */ + int total_daemons; + + /* + * idle_spawn_rate is the number of children that will be spawned on the + * next maintenance cycle if there aren't enough idle servers. It is + * maintained per listeners bucket, doubled up to MAX_SPAWN_RATE, and + * reset only when a cycle goes by without the need to spawn. + */ + int *idle_spawn_rate; +#ifndef MAX_SPAWN_RATE +#define MAX_SPAWN_RATE (32) +#endif + int hold_off_on_exponential_spawning; +} event_retained_data; +static event_retained_data *retained; + +typedef struct event_child_bucket { + ap_pod_t *pod; + ap_listen_rec *listeners; +} event_child_bucket; +static event_child_bucket *all_buckets, /* All listeners buckets */ + *my_bucket; /* Current child bucket */ + +struct event_srv_cfg_s { + struct timeout_queue *wc_q, + *ka_q; +}; + +#define ID_FROM_CHILD_THREAD(c, t) ((c * thread_limit) + t) + +/* The event MPM respects a couple of runtime flags that can aid + * in debugging. Setting the -DNO_DETACH flag will prevent the root process + * from detaching from its controlling terminal. Additionally, setting + * the -DONE_PROCESS flag (which implies -DNO_DETACH) will get you the + * child_main loop running in the process which originally started up. + * This gives you a pretty nice debugging environment. (You'll get a SIGHUP + * early in standalone_main; just continue through. This is the server + * trying to kill off any child processes which it might have lying + * around --- Apache doesn't keep track of their pids, it just sends + * SIGHUP to the process group, ignoring it in the root process. + * Continue through and you'll be fine.). + */ + +static int one_process = 0; + +#ifdef DEBUG_SIGSTOP +int raise_sigstop_flags; +#endif + +static apr_pool_t *pconf; /* Pool for config stuff */ +static apr_pool_t *pchild; /* Pool for httpd child stuff */ +static apr_pool_t *pruntime; /* Pool for MPM threads stuff */ + +static pid_t ap_my_pid; /* Linux getpid() doesn't work except in main + thread. Use this instead */ +static pid_t parent_pid; +static apr_os_thread_t *listener_os_thread; + +static int ap_child_slot; /* Current child process slot in scoreboard */ + +/* The LISTENER_SIGNAL signal will be sent from the main thread to the + * listener thread to wake it up for graceful termination (what a child + * process from an old generation does when the admin does "apachectl + * graceful"). This signal will be blocked in all threads of a child + * process except for the listener thread. + */ +#define LISTENER_SIGNAL SIGHUP + +/* An array of socket descriptors in use by each thread used to + * perform a non-graceful (forced) shutdown of the server. + */ +static apr_socket_t **worker_sockets; + +static volatile apr_uint32_t listensocks_disabled; + +static void disable_listensocks(void) +{ + int i; + if (apr_atomic_cas32(&listensocks_disabled, 1, 0) != 0) { + return; + } + if (event_pollset) { + for (i = 0; i < num_listensocks; i++) { + apr_pollset_remove(event_pollset, &listener_pollfd[i]); + } + } + ap_scoreboard_image->parent[ap_child_slot].not_accepting = 1; +} + +static void enable_listensocks(void) +{ + int i; + if (listener_may_exit + || apr_atomic_cas32(&listensocks_disabled, 0, 1) != 1) { + return; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00457) + "Accepting new connections again: " + "%u active conns (%u lingering/%u clogged/%u suspended), " + "%u idle workers", + apr_atomic_read32(&connection_count), + apr_atomic_read32(&lingering_count), + apr_atomic_read32(&clogged_count), + apr_atomic_read32(&suspended_count), + ap_queue_info_num_idlers(worker_queue_info)); + for (i = 0; i < num_listensocks; i++) + apr_pollset_add(event_pollset, &listener_pollfd[i]); + /* + * XXX: This is not yet optimal. If many workers suddenly become available, + * XXX: the parent may kill some processes off too soon. + */ + ap_scoreboard_image->parent[ap_child_slot].not_accepting = 0; +} + +static APR_INLINE apr_uint32_t listeners_disabled(void) +{ + return apr_atomic_read32(&listensocks_disabled); +} + +static APR_INLINE int connections_above_limit(void) +{ + apr_uint32_t i_count = ap_queue_info_num_idlers(worker_queue_info); + if (i_count > 0) { + apr_uint32_t c_count = apr_atomic_read32(&connection_count); + apr_uint32_t l_count = apr_atomic_read32(&lingering_count); + if (c_count <= l_count + /* Off by 'listeners_disabled()' to avoid flip flop */ + || c_count - l_count < (apr_uint32_t)threads_per_child + + (i_count - listeners_disabled()) * + (worker_factor / WORKER_FACTOR_SCALE)) { + return 0; + } + } + return 1; +} + +static void abort_socket_nonblocking(apr_socket_t *csd) +{ + apr_status_t rv; + apr_socket_timeout_set(csd, 0); +#if defined(SOL_SOCKET) && defined(SO_LINGER) + /* This socket is over now, and we don't want to block nor linger + * anymore, so reset it. A normal close could still linger in the + * system, while RST is fast, nonblocking, and what the peer will + * get if it sends us further data anyway. + */ + { + apr_os_sock_t osd = -1; + struct linger opt; + opt.l_onoff = 1; + opt.l_linger = 0; /* zero timeout is RST */ + apr_os_sock_get(&osd, csd); + setsockopt(osd, SOL_SOCKET, SO_LINGER, (void *)&opt, sizeof opt); + } +#endif + rv = apr_socket_close(csd); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00468) + "error closing socket"); + AP_DEBUG_ASSERT(0); + } +} + +static void close_worker_sockets(void) +{ + int i; + for (i = 0; i < threads_per_child; i++) { + apr_socket_t *csd = worker_sockets[i]; + if (csd) { + worker_sockets[i] = NULL; + abort_socket_nonblocking(csd); + } + } + for (;;) { + event_conn_state_t *cs = defer_linger_chain; + if (!cs) { + break; + } + if (apr_atomic_casptr((void *)&defer_linger_chain, cs->chain, + cs) != cs) { + /* Race lost, try again */ + continue; + } + cs->chain = NULL; + abort_socket_nonblocking(cs->pfd.desc.s); + } +} + +static void wakeup_listener(void) +{ + listener_may_exit = 1; + disable_listensocks(); + + /* Unblock the listener if it's poll()ing */ + if (event_pollset && listener_is_wakeable) { + apr_pollset_wakeup(event_pollset); + } + + /* unblock the listener if it's waiting for a worker */ + if (worker_queue_info) { + ap_queue_info_term(worker_queue_info); + } + + if (!listener_os_thread) { + /* XXX there is an obscure path that this doesn't handle perfectly: + * right after listener thread is created but before + * listener_os_thread is set, the first worker thread hits an + * error and starts graceful termination + */ + return; + } + /* + * we should just be able to "kill(ap_my_pid, LISTENER_SIGNAL)" on all + * platforms and wake up the listener thread since it is the only thread + * with SIGHUP unblocked, but that doesn't work on Linux + */ +#ifdef HAVE_PTHREAD_KILL + pthread_kill(*listener_os_thread, LISTENER_SIGNAL); +#else + kill(ap_my_pid, LISTENER_SIGNAL); +#endif +} + +#define ST_INIT 0 +#define ST_GRACEFUL 1 +#define ST_UNGRACEFUL 2 + +static int terminate_mode = ST_INIT; + +static void signal_threads(int mode) +{ + if (terminate_mode >= mode) { + return; + } + terminate_mode = mode; + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + /* in case we weren't called from the listener thread, wake up the + * listener thread + */ + wakeup_listener(); + + /* for ungraceful termination, let the workers exit now; + * for graceful termination, the listener thread will notify the + * workers to exit once it has stopped accepting new connections + */ + if (mode == ST_UNGRACEFUL) { + workers_may_exit = 1; + ap_queue_interrupt_all(worker_queue); + close_worker_sockets(); /* forcefully kill all current connections */ + } +} + +static int event_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = retained->max_daemons_limit; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_STATIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_DYNAMIC; + break; + case AP_MPMQ_IS_ASYNC: + *result = 1; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = server_limit; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = thread_limit; + break; + case AP_MPMQ_MAX_THREADS: + *result = threads_per_child; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = min_spare_threads; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = max_spare_threads; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = active_daemons_limit; + break; + case AP_MPMQ_MPM_STATE: + *result = retained->mpm->mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = retained->mpm->my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static void event_note_child_killed(int childnum, pid_t pid, ap_generation_t gen) +{ + if (childnum != -1) { /* child had a scoreboard slot? */ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[childnum].pid, + ap_scoreboard_image->parent[childnum].generation, + childnum, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[childnum].pid = 0; + } + else { + ap_run_child_status(ap_server_conf, pid, gen, -1, MPM_CHILD_EXITED); + } +} + +static void event_note_child_started(int slot, pid_t pid) +{ + ap_generation_t gen = retained->mpm->my_generation; + ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].generation = gen; + ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_STARTED); +} + +static const char *event_get_name(void) +{ + return "event"; +} + +/* a clean exit from a child with proper cleanup */ +static void clean_child_exit(int code) __attribute__ ((noreturn)); +static void clean_child_exit(int code) +{ + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + if (pchild) { + apr_pool_destroy(pchild); + } + + if (one_process) { + event_note_child_killed(/* slot */ 0, 0, 0); + } + + exit(code); +} + +static void just_die(int sig) +{ + clean_child_exit(0); +} + +/***************************************************************** + * Connection structures and accounting... + */ + +static int child_fatal; + +static apr_status_t decrement_connection_count(void *cs_) +{ + int is_last_connection; + event_conn_state_t *cs = cs_; + switch (cs->pub.state) { + case CONN_STATE_LINGER_NORMAL: + case CONN_STATE_LINGER_SHORT: + apr_atomic_dec32(&lingering_count); + break; + case CONN_STATE_SUSPENDED: + apr_atomic_dec32(&suspended_count); + break; + default: + break; + } + /* Unblock the listener if it's waiting for connection_count = 0, + * or if the listening sockets were disabled due to limits and can + * now accept new connections. + */ + is_last_connection = !apr_atomic_dec32(&connection_count); + if (listener_is_wakeable + && ((is_last_connection && listener_may_exit) + || (listeners_disabled() && !connections_above_limit()))) { + apr_pollset_wakeup(event_pollset); + } + return APR_SUCCESS; +} + +static void notify_suspend(event_conn_state_t *cs) +{ + ap_run_suspend_connection(cs->c, cs->r); + cs->c->sbh = NULL; + cs->suspended = 1; +} + +static void notify_resume(event_conn_state_t *cs, int cleanup) +{ + cs->suspended = 0; + cs->c->sbh = cleanup ? NULL : cs->sbh; + ap_run_resume_connection(cs->c, cs->r); +} + +/* + * Close our side of the connection, flushing data to the client first. + * Pre-condition: cs is not in any timeout queue and not in the pollset, + * timeout_mutex is not locked + * return: 0 if connection is fully closed, + * 1 if connection is lingering + * May only be called by worker thread. + */ +static int start_lingering_close_blocking(event_conn_state_t *cs) +{ + apr_socket_t *csd = cs->pfd.desc.s; + + if (ap_start_lingering_close(cs->c)) { + notify_suspend(cs); + apr_socket_close(csd); + ap_queue_info_push_pool(worker_queue_info, cs->p); + return DONE; + } + +#ifdef AP_DEBUG + { + apr_status_t rv; + rv = apr_socket_timeout_set(csd, 0); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + } +#else + apr_socket_timeout_set(csd, 0); +#endif + + cs->queue_timestamp = apr_time_now(); + /* + * If some module requested a shortened waiting period, only wait for + * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain + * DoS attacks. + */ + if (apr_table_get(cs->c->notes, "short-lingering-close")) { + cs->pub.state = CONN_STATE_LINGER_SHORT; + } + else { + cs->pub.state = CONN_STATE_LINGER_NORMAL; + } + apr_atomic_inc32(&lingering_count); + notify_suspend(cs); + + return OK; +} + +/* + * Defer flush and close of the connection by adding it to defer_linger_chain, + * for a worker to grab it and do the job (should that be blocking). + * Pre-condition: cs is not in any timeout queue and not in the pollset, + * timeout_mutex is not locked + * return: 1 connection is alive (but aside and about to linger) + * May be called by listener thread. + */ +static int start_lingering_close_nonblocking(event_conn_state_t *cs) +{ + event_conn_state_t *chain; + for (;;) { + cs->chain = chain = defer_linger_chain; + if (apr_atomic_casptr((void *)&defer_linger_chain, cs, + chain) != chain) { + /* Race lost, try again */ + continue; + } + return 1; + } +} + +/* + * forcibly close a lingering connection after the lingering period has + * expired + * Pre-condition: cs is not in any timeout queue and not in the pollset + * return: irrelevant (need same prototype as start_lingering_close) + */ +static int stop_lingering_close(event_conn_state_t *cs) +{ + apr_socket_t *csd = ap_get_conn_socket(cs->c); + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, + "socket abort in state %i", (int)cs->pub.state); + abort_socket_nonblocking(csd); + ap_queue_info_push_pool(worker_queue_info, cs->p); + if (dying) + ap_queue_interrupt_one(worker_queue); + return 0; +} + +/* + * This runs before any non-MPM cleanup code on the connection; + * if the connection is currently suspended as far as modules + * know, provide notification of resumption. + */ +static apr_status_t ptrans_pre_cleanup(void *dummy) +{ + event_conn_state_t *cs = dummy; + + if (cs->suspended) { + notify_resume(cs, 1); + } + return APR_SUCCESS; +} + +/* + * event_pre_read_request() and event_request_cleanup() track the + * current r for a given connection. + */ +static apr_status_t event_request_cleanup(void *dummy) +{ + conn_rec *c = dummy; + event_conn_state_t *cs = ap_get_module_config(c->conn_config, + &mpm_event_module); + + cs->r = NULL; + return APR_SUCCESS; +} + +static void event_pre_read_request(request_rec *r, conn_rec *c) +{ + event_conn_state_t *cs = ap_get_module_config(c->conn_config, + &mpm_event_module); + + cs->r = r; + cs->sc = ap_get_module_config(ap_server_conf->module_config, + &mpm_event_module); + apr_pool_cleanup_register(r->pool, c, event_request_cleanup, + apr_pool_cleanup_null); +} + +/* + * event_post_read_request() tracks the current server config for a + * given request. + */ +static int event_post_read_request(request_rec *r) +{ + conn_rec *c = r->connection; + event_conn_state_t *cs = ap_get_module_config(c->conn_config, + &mpm_event_module); + + /* To preserve legacy behaviour (consistent with other MPMs), use + * the keepalive timeout from the base server (first on this IP:port) + * when none is explicitly configured on this server. + */ + if (r->server->keep_alive_timeout_set) { + cs->sc = ap_get_module_config(r->server->module_config, + &mpm_event_module); + } + else { + cs->sc = ap_get_module_config(c->base_server->module_config, + &mpm_event_module); + } + return OK; +} + +/* Forward declare */ +static void process_lingering_close(event_conn_state_t *cs); + +/* + * process one connection in the worker + */ +static void process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * sock, + event_conn_state_t * cs, int my_child_num, + int my_thread_num) +{ + conn_rec *c; + long conn_id = ID_FROM_CHILD_THREAD(my_child_num, my_thread_num); + int clogging = 0; + apr_status_t rv; + int rc = OK; + + if (cs == NULL) { /* This is a new connection */ + listener_poll_type *pt = apr_pcalloc(p, sizeof(*pt)); + cs = apr_pcalloc(p, sizeof(event_conn_state_t)); + cs->bucket_alloc = apr_bucket_alloc_create(p); + ap_create_sb_handle(&cs->sbh, p, my_child_num, my_thread_num); + c = ap_run_create_connection(p, ap_server_conf, sock, + conn_id, cs->sbh, cs->bucket_alloc); + if (!c) { + ap_queue_info_push_pool(worker_queue_info, p); + return; + } + apr_atomic_inc32(&connection_count); + apr_pool_cleanup_register(c->pool, cs, decrement_connection_count, + apr_pool_cleanup_null); + ap_set_module_config(c->conn_config, &mpm_event_module, cs); + c->current_thread = thd; + cs->c = c; + c->cs = &(cs->pub); + cs->p = p; + cs->sc = ap_get_module_config(ap_server_conf->module_config, + &mpm_event_module); + cs->pfd.desc_type = APR_POLL_SOCKET; + cs->pfd.reqevents = APR_POLLIN; + cs->pfd.desc.s = sock; + pt->type = PT_CSD; + pt->baton = cs; + cs->pfd.client_data = pt; + apr_pool_pre_cleanup_register(p, cs, ptrans_pre_cleanup); + TO_QUEUE_ELEM_INIT(cs); + + ap_update_vhost_given_ip(c); + + rc = ap_run_pre_connection(c, sock); + if (rc != OK && rc != DONE) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(00469) + "process_socket: connection aborted"); + c->aborted = 1; + } + + /** + * XXX If the platform does not have a usable way of bundling + * accept() with a socket readability check, like Win32, + * and there are measurable delays before the + * socket is readable due to the first data packet arriving, + * it might be better to create the cs on the listener thread + * with the state set to CONN_STATE_CHECK_REQUEST_LINE_READABLE + * + * FreeBSD users will want to enable the HTTP accept filter + * module in their kernel for the highest performance + * When the accept filter is active, sockets are kept in the + * kernel until a HTTP request is received. + */ + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + + cs->pub.sense = CONN_SENSE_DEFAULT; + rc = OK; + } + else { + c = cs->c; + ap_update_sb_handle(cs->sbh, my_child_num, my_thread_num); + notify_resume(cs, 0); + c->current_thread = thd; + /* Subsequent request on a conn, and thread number is part of ID */ + c->id = conn_id; + } + + if (c->aborted) { + /* do lingering close below */ + cs->pub.state = CONN_STATE_LINGER; + } + else if (cs->pub.state >= CONN_STATE_LINGER) { + /* fall through */ + } + else { + if (cs->pub.state == CONN_STATE_READ_REQUEST_LINE + /* If we have an input filter which 'clogs' the input stream, + * like mod_ssl used to, lets just do the normal read from input + * filters, like the Worker MPM does. Filters that need to write + * where they would otherwise read, or read where they would + * otherwise write, should set the sense appropriately. + */ + || c->clogging_input_filters) { +read_request: + clogging = c->clogging_input_filters; + if (clogging) { + apr_atomic_inc32(&clogged_count); + } + rc = ap_run_process_connection(c); + if (clogging) { + apr_atomic_dec32(&clogged_count); + } + if (cs->pub.state > CONN_STATE_LINGER) { + cs->pub.state = CONN_STATE_LINGER; + } + if (rc == DONE) { + rc = OK; + } + } + } + /* + * The process_connection hooks above should set the connection state + * appropriately upon return, for event MPM to either: + * - do lingering close (CONN_STATE_LINGER), + * - wait for readability of the next request with respect to the keepalive + * timeout (state CONN_STATE_CHECK_REQUEST_LINE_READABLE), + * - wait for read/write-ability of the underlying socket with respect to + * its timeout by setting c->clogging_input_filters to 1 and the sense + * to CONN_SENSE_WANT_READ/WRITE (state CONN_STATE_WRITE_COMPLETION), + * - keep flushing the output filters stack in nonblocking mode, and then + * if required wait for read/write-ability of the underlying socket with + * respect to its own timeout (state CONN_STATE_WRITE_COMPLETION); since + * completion at some point may require reads (e.g. SSL_ERROR_WANT_READ), + * an output filter can also set the sense to CONN_SENSE_WANT_READ at any + * time for event MPM to do the right thing, + * - suspend the connection (SUSPENDED) such that it now interracts with + * the MPM through suspend/resume_connection() hooks, and/or registered + * poll callbacks (PT_USER), and/or registered timed callbacks triggered + * by timer events. + * If a process_connection hook returns an error or no hook sets the state + * to one of the above expected value, we forcibly close the connection w/ + * CONN_STATE_LINGER. This covers the cases where no process_connection + * hook executes (DECLINED), or one returns OK w/o touching the state (i.e. + * CONN_STATE_READ_REQUEST_LINE remains after the call) which can happen + * with third-party modules not updated to work specifically with event MPM + * while this was expected to do lingering close unconditionally with + * worker or prefork MPMs for instance. + */ + if (rc != OK || (cs->pub.state >= CONN_STATE_NUM) + || (cs->pub.state < CONN_STATE_LINGER + && cs->pub.state != CONN_STATE_WRITE_COMPLETION + && cs->pub.state != CONN_STATE_CHECK_REQUEST_LINE_READABLE + && cs->pub.state != CONN_STATE_SUSPENDED)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10111) + "process_socket: connection processing %s: closing", + rc ? apr_psprintf(c->pool, "returned error %i", rc) + : apr_psprintf(c->pool, "unexpected state %i", + (int)cs->pub.state)); + cs->pub.state = CONN_STATE_LINGER; + } + + if (cs->pub.state == CONN_STATE_WRITE_COMPLETION) { + ap_filter_t *output_filter = c->output_filters; + apr_status_t rv; + ap_update_child_status(cs->sbh, SERVER_BUSY_WRITE, NULL); + while (output_filter->next != NULL) { + output_filter = output_filter->next; + } + rv = output_filter->frec->filter_func.out_func(output_filter, NULL); + if (rv != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00470) + "network write failure in core output filter"); + cs->pub.state = CONN_STATE_LINGER; + } + else if (c->data_in_output_filters) { + /* Still in WRITE_COMPLETION_STATE: + * Set a write timeout for this connection, and let the + * event thread poll for writeability. + */ + cs->queue_timestamp = apr_time_now(); + notify_suspend(cs); + + if (cs->pub.sense == CONN_SENSE_WANT_READ) { + cs->pfd.reqevents = APR_POLLIN; + } + else { + cs->pfd.reqevents = APR_POLLOUT; + } + /* POLLHUP/ERR are usually returned event only (ignored here), but + * some pollset backends may require them in reqevents to do the + * right thing, so it shouldn't hurt. + */ + cs->pfd.reqevents |= APR_POLLHUP | APR_POLLERR; + cs->pub.sense = CONN_SENSE_DEFAULT; + + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_APPEND(cs->sc->wc_q, cs); + rv = apr_pollset_add(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) { + AP_DEBUG_ASSERT(0); + TO_QUEUE_REMOVE(cs->sc->wc_q, cs); + apr_thread_mutex_unlock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03465) + "process_socket: apr_pollset_add failure for " + "write completion"); + apr_socket_close(cs->pfd.desc.s); + ap_queue_info_push_pool(worker_queue_info, cs->p); + } + else { + apr_thread_mutex_unlock(timeout_mutex); + } + return; + } + else if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted || + listener_may_exit) { + cs->pub.state = CONN_STATE_LINGER; + } + else if (c->data_in_input_filters) { + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + goto read_request; + } + else { + cs->pub.state = CONN_STATE_CHECK_REQUEST_LINE_READABLE; + } + } + + if (cs->pub.state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) { + ap_update_child_status(cs->sbh, SERVER_BUSY_KEEPALIVE, NULL); + + /* It greatly simplifies the logic to use a single timeout value per q + * because the new element can just be added to the end of the list and + * it will stay sorted in expiration time sequence. If brand new + * sockets are sent to the event thread for a readability check, this + * will be a slight behavior change - they use the non-keepalive + * timeout today. With a normal client, the socket will be readable in + * a few milliseconds anyway. + */ + cs->queue_timestamp = apr_time_now(); + notify_suspend(cs); + + /* Add work to pollset. */ + cs->pfd.reqevents = APR_POLLIN; + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_APPEND(cs->sc->ka_q, cs); + rv = apr_pollset_add(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) { + AP_DEBUG_ASSERT(0); + TO_QUEUE_REMOVE(cs->sc->ka_q, cs); + apr_thread_mutex_unlock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03093) + "process_socket: apr_pollset_add failure for " + "keep alive"); + apr_socket_close(cs->pfd.desc.s); + ap_queue_info_push_pool(worker_queue_info, cs->p); + } + else { + apr_thread_mutex_unlock(timeout_mutex); + } + return; + } + + if (cs->pub.state == CONN_STATE_SUSPENDED) { + apr_atomic_inc32(&suspended_count); + notify_suspend(cs); + return; + } + + if (cs->pub.state == CONN_STATE_LINGER) { + rc = start_lingering_close_blocking(cs); + } + if (rc == OK && (cs->pub.state == CONN_STATE_LINGER_NORMAL || + cs->pub.state == CONN_STATE_LINGER_SHORT)) { + process_lingering_close(cs); + } +} + +/* conns_this_child has gone to zero or below. See if the admin coded + "MaxConnectionsPerChild 0", and keep going in that case. Doing it this way + simplifies the hot path in worker_thread */ +static void check_infinite_requests(void) +{ + if (ap_max_requests_per_child) { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "Stopping process due to MaxConnectionsPerChild"); + signal_threads(ST_GRACEFUL); + } + else { + /* keep going */ + conns_this_child = APR_INT32_MAX; + } +} + +static void close_listeners(int *closed) +{ + if (!*closed) { + int i; + ap_close_listeners_ex(my_bucket->listeners); + *closed = 1; + dying = 1; + ap_scoreboard_image->parent[ap_child_slot].quiescing = 1; + for (i = 0; i < threads_per_child; ++i) { + ap_update_child_status_from_indexes(ap_child_slot, i, + SERVER_GRACEFUL, NULL); + } + /* wake up the main thread */ + kill(ap_my_pid, SIGTERM); + + ap_queue_info_free_idle_pools(worker_queue_info); + ap_queue_interrupt_all(worker_queue); + } +} + +static void unblock_signal(int sig) +{ + sigset_t sig_mask; + + sigemptyset(&sig_mask); + sigaddset(&sig_mask, sig); +#if defined(SIGPROCMASK_SETS_THREAD_MASK) + sigprocmask(SIG_UNBLOCK, &sig_mask, NULL); +#else + pthread_sigmask(SIG_UNBLOCK, &sig_mask, NULL); +#endif +} + +static void dummy_signal_handler(int sig) +{ + /* XXX If specifying SIG_IGN is guaranteed to unblock a syscall, + * then we don't need this goofy function. + */ +} + + +static apr_status_t push_timer2worker(timer_event_t* te) +{ + return ap_queue_push_timer(worker_queue, te); +} + +/* + * Pre-condition: cs is neither in event_pollset nor a timeout queue + * this function may only be called by the listener + */ +static apr_status_t push2worker(event_conn_state_t *cs, apr_socket_t *csd, + apr_pool_t *ptrans) +{ + apr_status_t rc; + + if (cs) { + csd = cs->pfd.desc.s; + ptrans = cs->p; + } + rc = ap_queue_push_socket(worker_queue, csd, cs, ptrans); + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, ap_server_conf, APLOGNO(00471) + "push2worker: ap_queue_push_socket failed"); + /* trash the connection; we couldn't queue the connected + * socket to a worker + */ + if (csd) { + abort_socket_nonblocking(csd); + } + if (ptrans) { + ap_queue_info_push_pool(worker_queue_info, ptrans); + } + signal_threads(ST_GRACEFUL); + } + + return rc; +} + +/* get_worker: + * If *have_idle_worker_p == 0, reserve a worker thread, and set + * *have_idle_worker_p = 1. + * If *have_idle_worker_p is already 1, will do nothing. + * If blocking == 1, block if all workers are currently busy. + * If no worker was available immediately, will set *all_busy to 1. + * XXX: If there are no workers, we should not block immediately but + * XXX: close all keep-alive connections first. + */ +static void get_worker(int *have_idle_worker_p, int blocking, int *all_busy) +{ + apr_status_t rc; + + if (*have_idle_worker_p) { + /* already reserved a worker thread - must have hit a + * transient error on a previous pass + */ + return; + } + + if (blocking) + rc = ap_queue_info_wait_for_idler(worker_queue_info, all_busy); + else + rc = ap_queue_info_try_get_idler(worker_queue_info); + + if (rc == APR_SUCCESS || APR_STATUS_IS_EOF(rc)) { + *have_idle_worker_p = 1; + } + else if (!blocking && rc == APR_EAGAIN) { + *all_busy = 1; + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf, APLOGNO(00472) + "ap_queue_info_wait_for_idler failed. " + "Attempting to shutdown process gracefully"); + signal_threads(ST_GRACEFUL); + } +} + +/* Structures to reuse */ +static APR_RING_HEAD(timer_free_ring_t, timer_event_t) timer_free_ring; + +static apr_skiplist *timer_skiplist; +static volatile apr_time_t timers_next_expiry; + +/* Same goal as for TIMEOUT_FUDGE_FACTOR (avoid extra poll calls), but applied + * to timers. Since their timeouts are custom (user defined), we can't be too + * approximative here (hence using 0.01s). + */ +#define EVENT_FUDGE_FACTOR apr_time_from_msec(10) + +/* The following compare function is used by apr_skiplist_insert() to keep the + * elements (timers) sorted and provide O(log n) complexity (this is also true + * for apr_skiplist_{find,remove}(), but those are not used in MPM event where + * inserted timers are not searched nor removed, but with apr_skiplist_pop() + * which does use any compare function). It is meant to return 0 when a == b, + * <0 when a < b, and >0 when a > b. However apr_skiplist_insert() will not + * add duplicates (i.e. a == b), and apr_skiplist_add() is only available in + * APR 1.6, yet multiple timers could possibly be created in the same micro- + * second (duplicates with regard to apr_time_t); therefore we implement the + * compare function to return +1 instead of 0 when compared timers are equal, + * thus duplicates are still added after each other (in order of insertion). + */ +static int timer_comp(void *a, void *b) +{ + apr_time_t t1 = (apr_time_t) ((timer_event_t *)a)->when; + apr_time_t t2 = (apr_time_t) ((timer_event_t *)b)->when; + AP_DEBUG_ASSERT(t1); + AP_DEBUG_ASSERT(t2); + return ((t1 < t2) ? -1 : 1); +} + +static apr_thread_mutex_t *g_timer_skiplist_mtx; + +static apr_status_t event_register_timed_callback(apr_time_t t, + ap_mpm_callback_fn_t *cbfn, + void *baton) +{ + timer_event_t *te; + /* oh yeah, and make locking smarter/fine grained. */ + apr_thread_mutex_lock(g_timer_skiplist_mtx); + + if (!APR_RING_EMPTY(&timer_free_ring, timer_event_t, link)) { + te = APR_RING_FIRST(&timer_free_ring); + APR_RING_REMOVE(te, link); + } + else { + te = apr_skiplist_alloc(timer_skiplist, sizeof(timer_event_t)); + APR_RING_ELEM_INIT(te, link); + } + + te->cbfunc = cbfn; + te->baton = baton; + /* XXXXX: optimize */ + te->when = t + apr_time_now(); + + { + apr_time_t next_expiry; + + /* Okay, add sorted by when.. */ + apr_skiplist_insert(timer_skiplist, te); + + /* Cheaply update the overall timers' next expiry according to + * this event, if necessary. + */ + next_expiry = timers_next_expiry; + if (!next_expiry || next_expiry > te->when + EVENT_FUDGE_FACTOR) { + timers_next_expiry = te->when; + /* Unblock the poll()ing listener for it to update its timeout. */ + if (listener_is_wakeable) { + apr_pollset_wakeup(event_pollset); + } + } + } + + apr_thread_mutex_unlock(g_timer_skiplist_mtx); + + return APR_SUCCESS; +} + + +/* + * Close socket and clean up if remote closed its end while we were in + * lingering close. Only to be called in the worker thread, and since it's + * in immediate call stack, we can afford a comfortable buffer size to + * consume data quickly. + */ +#define LINGERING_BUF_SIZE (32 * 1024) +static void process_lingering_close(event_conn_state_t *cs) +{ + apr_socket_t *csd = ap_get_conn_socket(cs->c); + char dummybuf[LINGERING_BUF_SIZE]; + apr_size_t nbytes; + apr_status_t rv; + struct timeout_queue *q; + + /* socket is already in non-blocking state */ + do { + nbytes = sizeof(dummybuf); + rv = apr_socket_recv(csd, dummybuf, &nbytes); + } while (rv == APR_SUCCESS); + + if (!APR_STATUS_IS_EAGAIN(rv)) { + rv = apr_socket_close(csd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + ap_queue_info_push_pool(worker_queue_info, cs->p); + return; + } + + /* Re-queue the connection to come back when readable */ + cs->pfd.reqevents = APR_POLLIN; + cs->pub.sense = CONN_SENSE_DEFAULT; + q = (cs->pub.state == CONN_STATE_LINGER_SHORT) ? short_linger_q : linger_q; + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_APPEND(q, cs); + rv = apr_pollset_add(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) { + AP_DEBUG_ASSERT(0); + TO_QUEUE_REMOVE(q, cs); + apr_thread_mutex_unlock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03092) + "process_lingering_close: apr_pollset_add failure"); + rv = apr_socket_close(cs->pfd.desc.s); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + ap_queue_info_push_pool(worker_queue_info, cs->p); + return; + } + apr_thread_mutex_unlock(timeout_mutex); +} + +/* call 'func' for all elements of 'q' with timeout less than 'timeout_time'. + * Pre-condition: timeout_mutex must already be locked + * Post-condition: timeout_mutex will be locked again + */ +static void process_timeout_queue(struct timeout_queue *q, + apr_time_t timeout_time, + int (*func)(event_conn_state_t *)) +{ + apr_uint32_t total = 0, count; + event_conn_state_t *first, *cs, *last; + struct timeout_head_t trash; + struct timeout_queue *qp; + apr_status_t rv; + + if (!*q->total) { + return; + } + + APR_RING_INIT(&trash, event_conn_state_t, timeout_list); + for (qp = q; qp; qp = qp->next) { + count = 0; + cs = first = last = APR_RING_FIRST(&qp->head); + while (cs != APR_RING_SENTINEL(&qp->head, event_conn_state_t, + timeout_list)) { + /* Trash the entry if: + * - no timeout_time was given (asked for all), or + * - it expired (according to the queue timeout), or + * - the system clock skewed in the past: no entry should be + * registered above the given timeout_time (~now) + the queue + * timeout, we won't keep any here (eg. for centuries). + * + * Otherwise stop, no following entry will match thanks to the + * single timeout per queue (entries are added to the end!). + * This allows maintenance in O(1). + */ + if (timeout_time + && cs->queue_timestamp + qp->timeout > timeout_time + && cs->queue_timestamp < timeout_time + qp->timeout) { + /* Since this is the next expiring of this queue, update the + * overall queues' next expiry if it's later than this one. + */ + apr_time_t q_expiry = cs->queue_timestamp + qp->timeout; + apr_time_t next_expiry = queues_next_expiry; + if (!next_expiry || next_expiry > q_expiry) { + queues_next_expiry = q_expiry; + } + break; + } + + last = cs; + rv = apr_pollset_remove(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS && !APR_STATUS_IS_NOTFOUND(rv)) { + AP_DEBUG_ASSERT(0); + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, cs->c, APLOGNO(00473) + "apr_pollset_remove failed"); + } + cs = APR_RING_NEXT(cs, timeout_list); + count++; + } + if (!count) + continue; + + APR_RING_UNSPLICE(first, last, timeout_list); + APR_RING_SPLICE_TAIL(&trash, first, last, event_conn_state_t, + timeout_list); + AP_DEBUG_ASSERT(*q->total >= count && qp->count >= count); + *q->total -= count; + qp->count -= count; + total += count; + } + if (!total) + return; + + apr_thread_mutex_unlock(timeout_mutex); + first = APR_RING_FIRST(&trash); + do { + cs = APR_RING_NEXT(first, timeout_list); + TO_QUEUE_ELEM_INIT(first); + func(first); + first = cs; + } while (--total); + apr_thread_mutex_lock(timeout_mutex); +} + +static void process_keepalive_queue(apr_time_t timeout_time) +{ + /* If all workers are busy, we kill older keep-alive connections so + * that they may connect to another process. + */ + if (!timeout_time) { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "All workers are busy or dying, will close %u " + "keep-alive connections", *keepalive_q->total); + } + process_timeout_queue(keepalive_q, timeout_time, + start_lingering_close_nonblocking); +} + +static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) +{ + apr_status_t rc; + proc_info *ti = dummy; + int process_slot = ti->pslot; + struct process_score *ps = ap_get_scoreboard_process(process_slot); + int closed = 0; + int have_idle_worker = 0; + apr_time_t last_log; + + last_log = apr_time_now(); + free(ti); + + /* Unblock the signal used to wake this thread up, and set a handler for + * it. + */ + unblock_signal(LISTENER_SIGNAL); + apr_signal(LISTENER_SIGNAL, dummy_signal_handler); + + for (;;) { + timer_event_t *te; + const apr_pollfd_t *out_pfd; + apr_int32_t num = 0; + apr_interval_time_t timeout_interval; + apr_time_t now, timeout_time; + int workers_were_busy = 0; + + if (conns_this_child <= 0) + check_infinite_requests(); + + if (listener_may_exit) { + close_listeners(&closed); + if (terminate_mode == ST_UNGRACEFUL + || apr_atomic_read32(&connection_count) == 0) + break; + } + + now = apr_time_now(); + if (APLOGtrace6(ap_server_conf)) { + /* trace log status every second */ + if (now - last_log > apr_time_from_sec(1)) { + last_log = now; + apr_thread_mutex_lock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf, + "connections: %u (clogged: %u write-completion: %d " + "keep-alive: %d lingering: %d suspended: %u)", + apr_atomic_read32(&connection_count), + apr_atomic_read32(&clogged_count), + *(volatile apr_uint32_t*)write_completion_q->total, + *(volatile apr_uint32_t*)keepalive_q->total, + apr_atomic_read32(&lingering_count), + apr_atomic_read32(&suspended_count)); + if (dying) { + ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf, + "%u/%u workers shutdown", + apr_atomic_read32(&threads_shutdown), + threads_per_child); + } + apr_thread_mutex_unlock(timeout_mutex); + } + } + + /* Start with an infinite poll() timeout and update it according to + * the next expiring timer or queue entry. If there are none, either + * the listener is wakeable and it can poll() indefinitely until a wake + * up occurs, otherwise periodic checks (maintenance, shutdown, ...) + * must be performed. + */ + timeout_interval = -1; + + /* Push expired timers to a worker, the first remaining one determines + * the maximum time to poll() below, if any. + */ + timeout_time = timers_next_expiry; + if (timeout_time && timeout_time < now + EVENT_FUDGE_FACTOR) { + apr_thread_mutex_lock(g_timer_skiplist_mtx); + while ((te = apr_skiplist_peek(timer_skiplist))) { + if (te->when > now + EVENT_FUDGE_FACTOR) { + timers_next_expiry = te->when; + timeout_interval = te->when - now; + break; + } + apr_skiplist_pop(timer_skiplist, NULL); + push_timer2worker(te); + } + if (!te) { + timers_next_expiry = 0; + } + apr_thread_mutex_unlock(g_timer_skiplist_mtx); + } + + /* Same for queues, use their next expiry, if any. */ + timeout_time = queues_next_expiry; + if (timeout_time + && (timeout_interval < 0 + || timeout_time <= now + || timeout_interval > timeout_time - now)) { + timeout_interval = timeout_time > now ? timeout_time - now : 1; + } + + /* When non-wakeable, don't wait more than 100 ms, in any case. */ +#define NON_WAKEABLE_POLL_TIMEOUT apr_time_from_msec(100) + if (!listener_is_wakeable + && (timeout_interval < 0 + || timeout_interval > NON_WAKEABLE_POLL_TIMEOUT)) { + timeout_interval = NON_WAKEABLE_POLL_TIMEOUT; + } + + rc = apr_pollset_poll(event_pollset, timeout_interval, &num, &out_pfd); + if (rc != APR_SUCCESS) { + if (APR_STATUS_IS_EINTR(rc)) { + /* Woken up, if we are exiting or listeners are disabled we + * must fall through to kill kept-alive connections or test + * whether listeners should be re-enabled. Otherwise we only + * need to update timeouts (logic is above, so simply restart + * the loop). + */ + if (!listener_may_exit && !listeners_disabled()) { + continue; + } + timeout_time = 0; + } + else if (!APR_STATUS_IS_TIMEUP(rc)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, ap_server_conf, + "apr_pollset_poll failed. Attempting to " + "shutdown process gracefully"); + signal_threads(ST_GRACEFUL); + } + num = 0; + } + + if (listener_may_exit) { + close_listeners(&closed); + if (terminate_mode == ST_UNGRACEFUL + || apr_atomic_read32(&connection_count) == 0) + break; + } + + for (; num; --num, ++out_pfd) { + listener_poll_type *pt = (listener_poll_type *) out_pfd->client_data; + if (pt->type == PT_CSD) { + /* one of the sockets is readable */ + event_conn_state_t *cs = (event_conn_state_t *) pt->baton; + struct timeout_queue *remove_from_q = NULL; + /* don't wait for a worker for a keepalive request or + * lingering close processing. */ + int blocking = 0; + + switch (cs->pub.state) { + case CONN_STATE_WRITE_COMPLETION: + remove_from_q = cs->sc->wc_q; + blocking = 1; + break; + + case CONN_STATE_CHECK_REQUEST_LINE_READABLE: + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + remove_from_q = cs->sc->ka_q; + break; + + case CONN_STATE_LINGER_NORMAL: + remove_from_q = linger_q; + break; + + case CONN_STATE_LINGER_SHORT: + remove_from_q = short_linger_q; + break; + + default: + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, + ap_server_conf, APLOGNO(03096) + "event_loop: unexpected state %d", + cs->pub.state); + ap_assert(0); + } + + if (remove_from_q) { + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_REMOVE(remove_from_q, cs); + rc = apr_pollset_remove(event_pollset, &cs->pfd); + apr_thread_mutex_unlock(timeout_mutex); + /* + * Some of the pollset backends, like KQueue or Epoll + * automagically remove the FD if the socket is closed, + * therefore, we can accept _SUCCESS or _NOTFOUND, + * and we still want to keep going + */ + if (rc != APR_SUCCESS && !APR_STATUS_IS_NOTFOUND(rc)) { + AP_DEBUG_ASSERT(0); + ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf, + APLOGNO(03094) "pollset remove failed"); + start_lingering_close_nonblocking(cs); + break; + } + + /* If we don't get a worker immediately (nonblocking), we + * close the connection; the client can re-connect to a + * different process for keepalive, and for lingering close + * the connection will be reset so the choice is to favor + * incoming/alive connections. + */ + get_worker(&have_idle_worker, blocking, + &workers_were_busy); + if (!have_idle_worker) { + if (remove_from_q == cs->sc->ka_q) { + start_lingering_close_nonblocking(cs); + } + else { + stop_lingering_close(cs); + } + } + else if (push2worker(cs, NULL, NULL) == APR_SUCCESS) { + have_idle_worker = 0; + } + } + } + else if (pt->type == PT_ACCEPT && !listeners_disabled()) { + /* A Listener Socket is ready for an accept() */ + if (workers_were_busy) { + disable_listensocks(); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "All workers busy, not accepting new conns " + "in this process"); + } + else if (connections_above_limit()) { + disable_listensocks(); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Too many open connections (%u), " + "not accepting new conns in this process", + apr_atomic_read32(&connection_count)); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "Idle workers: %u", + ap_queue_info_num_idlers(worker_queue_info)); + workers_were_busy = 1; + } + else if (!listener_may_exit) { + void *csd = NULL; + ap_listen_rec *lr = (ap_listen_rec *) pt->baton; + apr_pool_t *ptrans; /* Pool for per-transaction stuff */ + ap_queue_info_pop_pool(worker_queue_info, &ptrans); + + if (ptrans == NULL) { + /* create a new transaction pool for each accepted socket */ + apr_allocator_t *allocator = NULL; + + rc = apr_allocator_create(&allocator); + if (rc == APR_SUCCESS) { + apr_allocator_max_free_set(allocator, + ap_max_mem_free); + rc = apr_pool_create_ex(&ptrans, pconf, NULL, + allocator); + if (rc == APR_SUCCESS) { + apr_pool_tag(ptrans, "transaction"); + apr_allocator_owner_set(allocator, ptrans); + } + } + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, + ap_server_conf, APLOGNO(03097) + "Failed to create transaction pool"); + if (allocator) { + apr_allocator_destroy(allocator); + } + resource_shortage = 1; + signal_threads(ST_GRACEFUL); + continue; + } + } + + get_worker(&have_idle_worker, 1, &workers_were_busy); + rc = lr->accept_func(&csd, lr, ptrans); + + /* later we trash rv and rely on csd to indicate + * success/failure + */ + AP_DEBUG_ASSERT(rc == APR_SUCCESS || !csd); + + if (rc == APR_EGENERAL) { + /* E[NM]FILE, ENOMEM, etc */ + resource_shortage = 1; + signal_threads(ST_GRACEFUL); + } + + if (csd != NULL) { + conns_this_child--; + if (push2worker(NULL, csd, ptrans) == APR_SUCCESS) { + have_idle_worker = 0; + } + } + else { + ap_queue_info_push_pool(worker_queue_info, ptrans); + } + } + } /* if:else on pt->type */ + } /* for processing poll */ + + /* XXX possible optimization: stash the current time for use as + * r->request_time for new requests + */ + /* We process the timeout queues here only when their overall next + * expiry (read once above) is over. This happens accurately since + * adding to the queues (in workers) can only decrease this expiry, + * while latest ones are only taken into account here (in listener) + * during queues' processing, with the lock held. This works both + * with and without wake-ability. + */ + if (timeout_time && timeout_time < (now = apr_time_now())) { + timeout_time = now + TIMEOUT_FUDGE_FACTOR; + + /* handle timed out sockets */ + apr_thread_mutex_lock(timeout_mutex); + + /* Processing all the queues below will recompute this. */ + queues_next_expiry = 0; + + /* Step 1: keepalive timeouts */ + if (workers_were_busy || dying) { + process_keepalive_queue(0); /* kill'em all \m/ */ + } + else { + process_keepalive_queue(timeout_time); + } + /* Step 2: write completion timeouts */ + process_timeout_queue(write_completion_q, timeout_time, + start_lingering_close_nonblocking); + /* Step 3: (normal) lingering close completion timeouts */ + process_timeout_queue(linger_q, timeout_time, + stop_lingering_close); + /* Step 4: (short) lingering close completion timeouts */ + process_timeout_queue(short_linger_q, timeout_time, + stop_lingering_close); + + apr_thread_mutex_unlock(timeout_mutex); + + ps->keep_alive = *(volatile apr_uint32_t*)keepalive_q->total; + ps->write_completion = *(volatile apr_uint32_t*)write_completion_q->total; + ps->connections = apr_atomic_read32(&connection_count); + ps->suspended = apr_atomic_read32(&suspended_count); + ps->lingering_close = apr_atomic_read32(&lingering_count); + } + else if ((workers_were_busy || dying) + && *(volatile apr_uint32_t*)keepalive_q->total) { + apr_thread_mutex_lock(timeout_mutex); + process_keepalive_queue(0); /* kill'em all \m/ */ + apr_thread_mutex_unlock(timeout_mutex); + ps->keep_alive = 0; + } + + /* If there are some lingering closes to defer (to a worker), schedule + * them now. We might wakeup a worker spuriously if another one empties + * defer_linger_chain in the meantime, but there also may be no active + * or all busy workers for an undefined time. In any case a deferred + * lingering close can't starve if we do that here since the chain is + * filled only above in the listener and it's emptied only in the + * worker(s); thus a NULL here means it will stay so while the listener + * waits (possibly indefinitely) in poll(). + */ + if (defer_linger_chain) { + get_worker(&have_idle_worker, 0, &workers_were_busy); + if (have_idle_worker + && defer_linger_chain /* re-test */ + && push2worker(NULL, NULL, NULL) == APR_SUCCESS) { + have_idle_worker = 0; + } + } + + if (listeners_disabled() + && !workers_were_busy + && !connections_above_limit()) { + enable_listensocks(); + } + } /* listener main loop */ + + close_listeners(&closed); + ap_queue_term(worker_queue); + + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +/* + * During graceful shutdown, if there are more running worker threads than + * open connections, exit one worker thread. + * + * return 1 if thread should exit, 0 if it should continue running. + */ +static int worker_thread_should_exit_early(void) +{ + for (;;) { + apr_uint32_t conns = apr_atomic_read32(&connection_count); + apr_uint32_t dead = apr_atomic_read32(&threads_shutdown); + apr_uint32_t newdead; + + AP_DEBUG_ASSERT(dead <= threads_per_child); + if (conns >= threads_per_child - dead) + return 0; + + newdead = dead + 1; + if (apr_atomic_cas32(&threads_shutdown, newdead, dead) == dead) { + /* + * No other thread has exited in the mean time, safe to exit + * this one. + */ + return 1; + } + } +} + +/* XXX For ungraceful termination/restart, we definitely don't want to + * wait for active connections to finish but we may want to wait + * for idle workers to get out of the queue code and release mutexes, + * since those mutexes are cleaned up pretty soon and some systems + * may not react favorably (i.e., segfault) if operations are attempted + * on cleaned-up mutexes. + */ +static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy) +{ + proc_info *ti = dummy; + int process_slot = ti->pslot; + int thread_slot = ti->tslot; + apr_status_t rv; + int is_idle = 0; + + free(ti); + + ap_scoreboard_image->servers[process_slot][thread_slot].pid = ap_my_pid; + ap_scoreboard_image->servers[process_slot][thread_slot].tid = apr_os_thread_current(); + ap_scoreboard_image->servers[process_slot][thread_slot].generation = retained->mpm->my_generation; + ap_update_child_status_from_indexes(process_slot, thread_slot, + SERVER_STARTING, NULL); + + while (!workers_may_exit) { + apr_socket_t *csd = NULL; + event_conn_state_t *cs; + timer_event_t *te = NULL; + apr_pool_t *ptrans; /* Pool for per-transaction stuff */ + + if (!is_idle) { + rv = ap_queue_info_set_idle(worker_queue_info, NULL); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + "ap_queue_info_set_idle failed. Attempting to " + "shutdown process gracefully."); + signal_threads(ST_GRACEFUL); + break; + } + is_idle = 1; + } + + ap_update_child_status_from_indexes(process_slot, thread_slot, + dying ? SERVER_GRACEFUL + : SERVER_READY, NULL); + worker_pop: + if (workers_may_exit) { + break; + } + if (dying && worker_thread_should_exit_early()) { + break; + } + + rv = ap_queue_pop_something(worker_queue, &csd, (void **)&cs, + &ptrans, &te); + + if (rv != APR_SUCCESS) { + /* We get APR_EOF during a graceful shutdown once all the + * connections accepted by this server process have been handled. + */ + if (APR_STATUS_IS_EOF(rv)) { + break; + } + /* We get APR_EINTR whenever ap_queue_pop_*() has been interrupted + * from an explicit call to ap_queue_interrupt_all(). This allows + * us to unblock threads stuck in ap_queue_pop_*() when a shutdown + * is pending. + * + * If workers_may_exit is set and this is ungraceful termination/ + * restart, we are bound to get an error on some systems (e.g., + * AIX, which sanity-checks mutex operations) since the queue + * may have already been cleaned up. Don't log the "error" if + * workers_may_exit is set. + */ + else if (APR_STATUS_IS_EINTR(rv)) { + goto worker_pop; + } + /* We got some other error. */ + else if (!workers_may_exit) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + APLOGNO(03099) "ap_queue_pop_socket failed"); + } + continue; + } + if (te != NULL) { + te->cbfunc(te->baton); + + { + apr_thread_mutex_lock(g_timer_skiplist_mtx); + APR_RING_INSERT_TAIL(&timer_free_ring, te, timer_event_t, link); + apr_thread_mutex_unlock(g_timer_skiplist_mtx); + } + } + else { + is_idle = 0; + if (csd != NULL) { + worker_sockets[thread_slot] = csd; + process_socket(thd, ptrans, csd, cs, process_slot, thread_slot); + worker_sockets[thread_slot] = NULL; + } + } + + /* If there are deferred lingering closes, handle them now. */ + while (!workers_may_exit) { + cs = defer_linger_chain; + if (!cs) { + break; + } + if (apr_atomic_casptr((void *)&defer_linger_chain, cs->chain, + cs) != cs) { + /* Race lost, try again */ + continue; + } + cs->chain = NULL; + + worker_sockets[thread_slot] = csd = cs->pfd.desc.s; +#ifdef AP_DEBUG + rv = apr_socket_timeout_set(csd, SECONDS_TO_LINGER); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); +#else + apr_socket_timeout_set(csd, SECONDS_TO_LINGER); +#endif + cs->pub.state = CONN_STATE_LINGER; + process_socket(thd, cs->p, csd, cs, process_slot, thread_slot); + worker_sockets[thread_slot] = NULL; + } + } + + ap_update_child_status_from_indexes(process_slot, thread_slot, + dying ? SERVER_DEAD + : SERVER_GRACEFUL, NULL); + + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +static int check_signal(int signum) +{ + switch (signum) { + case SIGTERM: + case SIGINT: + return 1; + } + return 0; +} + +static void create_listener_thread(thread_starter * ts) +{ + int my_child_num = ts->child_num_arg; + apr_threadattr_t *thread_attr = ts->threadattr; + proc_info *my_info; + apr_status_t rv; + + my_info = (proc_info *) ap_malloc(sizeof(proc_info)); + my_info->pslot = my_child_num; + my_info->tslot = -1; /* listener thread doesn't have a thread slot */ + rv = apr_thread_create(&ts->listener, thread_attr, listener_thread, + my_info, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00474) + "apr_thread_create: unable to create listener thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + apr_os_thread_get(&listener_os_thread, ts->listener); +} + +static void setup_threads_runtime(void) +{ + apr_status_t rv; + ap_listen_rec *lr; + apr_pool_t *pskip = NULL; + int max_recycled_pools = -1, i; + const int good_methods[] = { APR_POLLSET_KQUEUE, + APR_POLLSET_PORT, + APR_POLLSET_EPOLL }; + /* XXX: K-A or lingering close connection included in the async factor */ + const apr_uint32_t async_factor = worker_factor / WORKER_FACTOR_SCALE; + const apr_uint32_t pollset_size = (apr_uint32_t)num_listensocks + + (apr_uint32_t)threads_per_child * + (async_factor > 2 ? async_factor : 2); + int pollset_flags; + + /* Event's skiplist operations will happen concurrently with other modules' + * runtime so they need their own pool for allocations, and its lifetime + * should be at least the one of the connections (ptrans). Thus pskip is + * created as a subpool of pconf like/before ptrans (before so that it's + * destroyed after). In forked mode pconf is never destroyed so we are good + * anyway, but in ONE_PROCESS mode this ensures that the skiplist works + * from connection/ptrans cleanups (even after pchild is destroyed). + */ + apr_pool_create(&pskip, pconf); + apr_pool_tag(pskip, "mpm_skiplist"); + apr_thread_mutex_create(&g_timer_skiplist_mtx, APR_THREAD_MUTEX_DEFAULT, pskip); + APR_RING_INIT(&timer_free_ring, timer_event_t, link); + apr_skiplist_init(&timer_skiplist, pskip); + apr_skiplist_set_compare(timer_skiplist, timer_comp, timer_comp); + + /* All threads (listener, workers) and synchronization objects (queues, + * pollset, mutexes...) created here should have at least the lifetime of + * the connections they handle (i.e. ptrans). We can't use this thread's + * self pool because all these objects survive it, nor use pchild or pconf + * directly because this starter thread races with other modules' runtime, + * nor finally pchild (or subpool thereof) because it is killed explicitely + * before pconf (thus connections/ptrans can live longer, which matters in + * ONE_PROCESS mode). So this leaves us with a subpool of pconf, created + * before any ptrans hence destroyed after. + */ + apr_pool_create(&pruntime, pconf); + apr_pool_tag(pruntime, "mpm_runtime"); + + /* We must create the fd queues before we start up the listener + * and worker threads. */ + rv = ap_queue_create(&worker_queue, threads_per_child, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03100) + "ap_queue_create() failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + if (ap_max_mem_free != APR_ALLOCATOR_MAX_FREE_UNLIMITED) { + /* If we want to conserve memory, let's not keep an unlimited number of + * pools & allocators. + * XXX: This should probably be a separate config directive + */ + max_recycled_pools = threads_per_child * 3 / 4 ; + } + rv = ap_queue_info_create(&worker_queue_info, pruntime, + threads_per_child, max_recycled_pools); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03101) + "ap_queue_info_create() failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create the timeout mutex and main pollset before the listener + * thread starts. + */ + rv = apr_thread_mutex_create(&timeout_mutex, APR_THREAD_MUTEX_DEFAULT, + pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03102) + "creation of the timeout mutex failed."); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create the main pollset */ + pollset_flags = APR_POLLSET_THREADSAFE | APR_POLLSET_NOCOPY | + APR_POLLSET_NODEFAULT | APR_POLLSET_WAKEABLE; + for (i = 0; i < sizeof(good_methods) / sizeof(good_methods[0]); i++) { + rv = apr_pollset_create_ex(&event_pollset, pollset_size, pruntime, + pollset_flags, good_methods[i]); + if (rv == APR_SUCCESS) { + listener_is_wakeable = 1; + break; + } + } + if (rv != APR_SUCCESS) { + pollset_flags &= ~APR_POLLSET_WAKEABLE; + for (i = 0; i < sizeof(good_methods) / sizeof(good_methods[0]); i++) { + rv = apr_pollset_create_ex(&event_pollset, pollset_size, pruntime, + pollset_flags, good_methods[i]); + if (rv == APR_SUCCESS) { + break; + } + } + } + if (rv != APR_SUCCESS) { + pollset_flags &= ~APR_POLLSET_NODEFAULT; + rv = apr_pollset_create(&event_pollset, pollset_size, pruntime, + pollset_flags); + } + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03103) + "apr_pollset_create with Thread Safety failed."); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Add listeners to the main pollset */ + listener_pollfd = apr_pcalloc(pruntime, num_listensocks * + sizeof(apr_pollfd_t)); + for (i = 0, lr = my_bucket->listeners; lr; lr = lr->next, i++) { + apr_pollfd_t *pfd; + listener_poll_type *pt; + + AP_DEBUG_ASSERT(i < num_listensocks); + pfd = &listener_pollfd[i]; + + pfd->reqevents = APR_POLLIN; + pfd->desc_type = APR_POLL_SOCKET; + pfd->desc.s = lr->sd; + + pt = apr_pcalloc(pruntime, sizeof(*pt)); + pfd->client_data = pt; + pt->type = PT_ACCEPT; + pt->baton = lr; + + apr_socket_opt_set(pfd->desc.s, APR_SO_NONBLOCK, 1); + apr_pollset_add(event_pollset, pfd); + + lr->accept_func = ap_unixd_accept; + } + + worker_sockets = apr_pcalloc(pruntime, threads_per_child * + sizeof(apr_socket_t *)); +} + +/* XXX under some circumstances not understood, children can get stuck + * in start_threads forever trying to take over slots which will + * never be cleaned up; for now there is an APLOG_DEBUG message issued + * every so often when this condition occurs + */ +static void *APR_THREAD_FUNC start_threads(apr_thread_t * thd, void *dummy) +{ + thread_starter *ts = dummy; + apr_thread_t **threads = ts->threads; + apr_threadattr_t *thread_attr = ts->threadattr; + int my_child_num = ts->child_num_arg; + proc_info *my_info; + apr_status_t rv; + int threads_created = 0; + int listener_started = 0; + int prev_threads_created; + int loops, i; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02471) + "start_threads: Using %s (%swakeable)", + apr_pollset_method_name(event_pollset), + listener_is_wakeable ? "" : "not "); + + loops = prev_threads_created = 0; + while (1) { + /* threads_per_child does not include the listener thread */ + for (i = 0; i < threads_per_child; i++) { + int status = + ap_scoreboard_image->servers[my_child_num][i].status; + + if (status != SERVER_DEAD) { + continue; + } + + my_info = (proc_info *) ap_malloc(sizeof(proc_info)); + my_info->pslot = my_child_num; + my_info->tslot = i; + + /* We are creating threads right now */ + ap_update_child_status_from_indexes(my_child_num, i, + SERVER_STARTING, NULL); + /* We let each thread update its own scoreboard entry. This is + * done because it lets us deal with tid better. + */ + rv = apr_thread_create(&threads[i], thread_attr, + worker_thread, my_info, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, + APLOGNO(03104) + "apr_thread_create: unable to create worker thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + threads_created++; + } + + /* Start the listener only when there are workers available */ + if (!listener_started && threads_created) { + create_listener_thread(ts); + listener_started = 1; + } + + + if (start_thread_may_exit || threads_created == threads_per_child) { + break; + } + /* wait for previous generation to clean up an entry */ + apr_sleep(apr_time_from_sec(1)); + ++loops; + if (loops % 120 == 0) { /* every couple of minutes */ + if (prev_threads_created == threads_created) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "child %" APR_PID_T_FMT " isn't taking over " + "slots very quickly (%d of %d)", + ap_my_pid, threads_created, + threads_per_child); + } + prev_threads_created = threads_created; + } + } + + /* What state should this child_main process be listed as in the + * scoreboard...? + * ap_update_child_status_from_indexes(my_child_num, i, SERVER_STARTING, + * (request_rec *) NULL); + * + * This state should be listed separately in the scoreboard, in some kind + * of process_status, not mixed in with the worker threads' status. + * "life_status" is almost right, but it's in the worker's structure, and + * the name could be clearer. gla + */ + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +static void join_workers(apr_thread_t * listener, apr_thread_t ** threads) +{ + int i; + apr_status_t rv, thread_rv; + + if (listener) { + int iter; + + /* deal with a rare timing window which affects waking up the + * listener thread... if the signal sent to the listener thread + * is delivered between the time it verifies that the + * listener_may_exit flag is clear and the time it enters a + * blocking syscall, the signal didn't do any good... work around + * that by sleeping briefly and sending it again + */ + + iter = 0; + while (iter < 10 && !dying) { + /* listener has not stopped accepting yet */ + apr_sleep(apr_time_make(0, 500000)); + wakeup_listener(); + ++iter; + } + if (iter >= 10) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00475) + "the listener thread didn't stop accepting"); + } + else { + rv = apr_thread_join(&thread_rv, listener); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00476) + "apr_thread_join: unable to join listener thread"); + } + } + } + + for (i = 0; i < threads_per_child; i++) { + if (threads[i]) { /* if we ever created this thread */ + rv = apr_thread_join(&thread_rv, threads[i]); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00477) + "apr_thread_join: unable to join worker " + "thread %d", i); + } + } + } +} + +static void join_start_thread(apr_thread_t * start_thread_id) +{ + apr_status_t rv, thread_rv; + + start_thread_may_exit = 1; /* tell it to give up in case it is still + * trying to take over slots from a + * previous generation + */ + rv = apr_thread_join(&thread_rv, start_thread_id); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00478) + "apr_thread_join: unable to join the start " "thread"); + } +} + +static void child_main(int child_num_arg, int child_bucket) +{ + apr_thread_t **threads; + apr_status_t rv; + thread_starter *ts; + apr_threadattr_t *thread_attr; + apr_thread_t *start_thread_id; + int i; + + /* for benefit of any hooks that run as this child initializes */ + retained->mpm->mpm_state = AP_MPMQ_STARTING; + + ap_my_pid = getpid(); + ap_child_slot = child_num_arg; + ap_fatal_signal_child_setup(ap_server_conf); + + /* Get a sub context for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + /* close unused listeners and pods */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (i != child_bucket) { + ap_close_listeners_ex(all_buckets[i].listeners); + ap_mpm_podx_close(all_buckets[i].pod); + } + } + + /*stuff to do before we switch id's, so we have permissions. */ + ap_reopen_scoreboard(pchild, NULL, 0); + + /* done with init critical section */ + if (ap_run_drop_privileges(pchild, ap_server_conf)) { + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Just use the standard apr_setup_signal_thread to block all signals + * from being received. The child processes no longer use signals for + * any communication with the parent process. Let's also do this before + * child_init() hooks are called and possibly create threads that + * otherwise could "steal" (implicitely) MPM's signals. + */ + rv = apr_setup_signal_thread(); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00479) + "Couldn't initialize signal thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_run_child_init(pchild, ap_server_conf); + + if (ap_max_requests_per_child) { + conns_this_child = ap_max_requests_per_child; + } + else { + /* coding a value of zero means infinity */ + conns_this_child = APR_INT32_MAX; + } + + /* Setup threads */ + + /* Globals used by signal_threads() so to be initialized before */ + setup_threads_runtime(); + + /* clear the storage; we may not create all our threads immediately, + * and we want a 0 entry to indicate a thread which was not created + */ + threads = ap_calloc(threads_per_child, sizeof(apr_thread_t *)); + ts = apr_palloc(pchild, sizeof(*ts)); + + apr_threadattr_create(&thread_attr, pchild); + /* 0 means PTHREAD_CREATE_JOINABLE */ + apr_threadattr_detach_set(thread_attr, 0); + + if (ap_thread_stacksize != 0) { + rv = apr_threadattr_stacksize_set(thread_attr, ap_thread_stacksize); + if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(02436) + "WARNING: ThreadStackSize of %" APR_SIZE_T_FMT " is " + "inappropriate, using default", + ap_thread_stacksize); + } + } + + ts->threads = threads; + ts->listener = NULL; + ts->child_num_arg = child_num_arg; + ts->threadattr = thread_attr; + + rv = apr_thread_create(&start_thread_id, thread_attr, start_threads, + ts, pchild); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00480) + "apr_thread_create: unable to create worker thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + /* If we are only running in one_process mode, we will want to + * still handle signals. */ + if (one_process) { + /* Block until we get a terminating signal. */ + apr_signal_thread(check_signal); + /* make sure the start thread has finished; signal_threads() + * and join_workers() depend on that + */ + /* XXX join_start_thread() won't be awakened if one of our + * threads encounters a critical error and attempts to + * shutdown this child + */ + join_start_thread(start_thread_id); + + /* helps us terminate a little more quickly than the dispatch of the + * signal thread; beats the Pipe of Death and the browsers + */ + signal_threads(ST_UNGRACEFUL); + + /* A terminating signal was received. Now join each of the + * workers to clean them up. + * If the worker already exited, then the join frees + * their resources and returns. + * If the worker hasn't exited, then this blocks until + * they have (then cleans up). + */ + join_workers(ts->listener, threads); + } + else { /* !one_process */ + /* remove SIGTERM from the set of blocked signals... if one of + * the other threads in the process needs to take us down + * (e.g., for MaxConnectionsPerChild) it will send us SIGTERM + */ + unblock_signal(SIGTERM); + apr_signal(SIGTERM, dummy_signal_handler); + /* Watch for any messages from the parent over the POD */ + while (1) { + rv = ap_mpm_podx_check(my_bucket->pod); + if (rv == AP_MPM_PODX_NORESTART) { + /* see if termination was triggered while we slept */ + switch (terminate_mode) { + case ST_GRACEFUL: + rv = AP_MPM_PODX_GRACEFUL; + break; + case ST_UNGRACEFUL: + rv = AP_MPM_PODX_RESTART; + break; + } + } + if (rv == AP_MPM_PODX_GRACEFUL || rv == AP_MPM_PODX_RESTART) { + /* make sure the start thread has finished; + * signal_threads() and join_workers depend on that + */ + join_start_thread(start_thread_id); + signal_threads(rv == + AP_MPM_PODX_GRACEFUL ? ST_GRACEFUL : ST_UNGRACEFUL); + break; + } + } + + /* A terminating signal was received. Now join each of the + * workers to clean them up. + * If the worker already exited, then the join frees + * their resources and returns. + * If the worker hasn't exited, then this blocks until + * they have (then cleans up). + */ + join_workers(ts->listener, threads); + } + + free(threads); + + clean_child_exit(resource_shortage ? APEXIT_CHILDSICK : 0); +} + +static int make_child(server_rec * s, int slot, int bucket) +{ + int pid; + + if (slot + 1 > retained->max_daemons_limit) { + retained->max_daemons_limit = slot + 1; + } + + if (ap_scoreboard_image->parent[slot].pid != 0) { + /* XXX replace with assert or remove ? */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(03455) + "BUG: Scoreboard slot %d should be empty but is " + "in use by pid %" APR_PID_T_FMT, + slot, ap_scoreboard_image->parent[slot].pid); + return -1; + } + + if (one_process) { + my_bucket = &all_buckets[0]; + + event_note_child_started(slot, getpid()); + child_main(slot, 0); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + if ((pid = fork()) == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(00481) + "fork: Unable to fork new process"); + + /* fork didn't succeed. There's no need to touch the scoreboard; + * if we were trying to replace a failed child process, then + * server_main_loop() marked its workers SERVER_DEAD, and if + * we were trying to replace a child process that exited normally, + * its worker_thread()s left SERVER_DEAD or SERVER_GRACEFUL behind. + */ + + /* In case system resources are maxxed out, we don't want + Apache running away with the CPU trying to fork over and + over and over again. */ + apr_sleep(apr_time_from_sec(10)); + + return -1; + } + + if (!pid) { + my_bucket = &all_buckets[bucket]; + +#ifdef HAVE_BINDPROCESSOR + /* By default, AIX binds to a single processor. This bit unbinds + * children which will then bind to another CPU. + */ + int status = bindprocessor(BINDPROCESS, (int) getpid(), + PROCESSOR_CLASS_ANY); + if (status != OK) + ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, + ap_server_conf, APLOGNO(00482) + "processor unbind failed"); +#endif + RAISE_SIGSTOP(MAKE_CHILD); + + apr_signal(SIGTERM, just_die); + child_main(slot, bucket); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + ap_scoreboard_image->parent[slot].quiescing = 0; + ap_scoreboard_image->parent[slot].not_accepting = 0; + ap_scoreboard_image->parent[slot].bucket = bucket; + event_note_child_started(slot, pid); + active_daemons++; + retained->total_daemons++; + return 0; +} + +/* start up a bunch of children */ +static void startup_children(int number_to_start) +{ + int i; + + for (i = 0; number_to_start && i < server_limit; ++i) { + if (ap_scoreboard_image->parent[i].pid != 0) { + continue; + } + if (make_child(ap_server_conf, i, i % retained->mpm->num_buckets) < 0) { + break; + } + --number_to_start; + } +} + +static void perform_idle_server_maintenance(int child_bucket, int num_buckets) +{ + int i, j; + int idle_thread_count = 0; + worker_score *ws; + process_score *ps; + int free_length = 0; + int free_slots[MAX_SPAWN_RATE]; + int last_non_dead = -1; + int active_thread_count = 0; + + for (i = 0; i < server_limit; ++i) { + /* Initialization to satisfy the compiler. It doesn't know + * that threads_per_child is always > 0 */ + int status = SERVER_DEAD; + int child_threads_active = 0; + + if (i >= retained->max_daemons_limit && + free_length == retained->idle_spawn_rate[child_bucket]) { + /* short cut if all active processes have been examined and + * enough empty scoreboard slots have been found + */ + + break; + } + ps = &ap_scoreboard_image->parent[i]; + if (ps->pid != 0) { + for (j = 0; j < threads_per_child; j++) { + ws = &ap_scoreboard_image->servers[i][j]; + status = ws->status; + + /* We consider a starting server as idle because we started it + * at least a cycle ago, and if it still hasn't finished starting + * then we're just going to swamp things worse by forking more. + * So we hopefully won't need to fork more if we count it. + * This depends on the ordering of SERVER_READY and SERVER_STARTING. + */ + if (status <= SERVER_READY && !ps->quiescing && !ps->not_accepting + && ps->generation == retained->mpm->my_generation + && ps->bucket == child_bucket) + { + ++idle_thread_count; + } + if (status >= SERVER_READY && status < SERVER_GRACEFUL) { + ++child_threads_active; + } + } + last_non_dead = i; + } + active_thread_count += child_threads_active; + if (!ps->pid && free_length < retained->idle_spawn_rate[child_bucket]) + free_slots[free_length++] = i; + else if (child_threads_active == threads_per_child) + had_healthy_child = 1; + } + + if (retained->sick_child_detected) { + if (had_healthy_child) { + /* Assume this is a transient error, even though it may not be. Leave + * the server up in case it is able to serve some requests or the + * problem will be resolved. + */ + retained->sick_child_detected = 0; + } + else { + /* looks like a basket case, as no child ever fully initialized; give up. + */ + retained->mpm->shutdown_pending = 1; + child_fatal = 1; + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, + ap_server_conf, APLOGNO(02324) + "A resource shortage or other unrecoverable failure " + "was encountered before any child process initialized " + "successfully... httpd is exiting!"); + /* the child already logged the failure details */ + return; + } + } + + retained->max_daemons_limit = last_non_dead + 1; + + if (idle_thread_count > max_spare_threads / num_buckets) + { + /* + * Child processes that we ask to shut down won't die immediately + * but may stay around for a long time when they finish their + * requests. If the server load changes many times, many such + * gracefully finishing processes may accumulate, filling up the + * scoreboard. To avoid running out of scoreboard entries, we + * don't shut down more processes when the total number of processes + * is high. + * + * XXX It would be nice if we could + * XXX - kill processes without keepalive connections first + * XXX - tell children to stop accepting new connections, and + * XXX depending on server load, later be able to resurrect them + * or kill them + */ + if (retained->total_daemons <= active_daemons_limit && + retained->total_daemons < server_limit) { + /* Kill off one child */ + ap_mpm_podx_signal(all_buckets[child_bucket].pod, + AP_MPM_PODX_GRACEFUL); + retained->idle_spawn_rate[child_bucket] = 1; + active_daemons--; + } else { + ap_log_error(APLOG_MARK, APLOG_TRACE5, 0, ap_server_conf, + "Not shutting down child: total daemons %d / " + "active limit %d / ServerLimit %d", + retained->total_daemons, active_daemons_limit, + server_limit); + } + } + else if (idle_thread_count < min_spare_threads / num_buckets) { + if (active_thread_count >= max_workers) { + if (0 == idle_thread_count) { + if (!retained->maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00484) + "server reached MaxRequestWorkers setting, " + "consider raising the MaxRequestWorkers " + "setting"); + retained->maxclients_reported = 1; + } + } + else { + if (!retained->near_maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(10159) + "server is within MinSpareThreads of " + "MaxRequestWorkers, consider raising the " + "MaxRequestWorkers setting"); + retained->near_maxclients_reported = 1; + } + } + retained->idle_spawn_rate[child_bucket] = 1; + } + else if (free_length == 0) { /* scoreboard is full, can't fork */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(03490) + "scoreboard is full, not at MaxRequestWorkers." + "Increase ServerLimit."); + retained->idle_spawn_rate[child_bucket] = 1; + } + else { + if (free_length > retained->idle_spawn_rate[child_bucket]) { + free_length = retained->idle_spawn_rate[child_bucket]; + } + if (retained->idle_spawn_rate[child_bucket] >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00486) + "server seems busy, (you may need " + "to increase StartServers, ThreadsPerChild " + "or Min/MaxSpareThreads), " + "spawning %d children, there are around %d idle " + "threads, %d active children, and %d children " + "that are shutting down", free_length, + idle_thread_count, active_daemons, + retained->total_daemons); + } + for (i = 0; i < free_length; ++i) { + ap_log_error(APLOG_MARK, APLOG_TRACE5, 0, ap_server_conf, + "Spawning new child: slot %d active / " + "total daemons: %d/%d", + free_slots[i], active_daemons, + retained->total_daemons); + make_child(ap_server_conf, free_slots[i], child_bucket); + } + /* the next time around we want to spawn twice as many if this + * wasn't good enough, but not if we've just done a graceful + */ + if (retained->hold_off_on_exponential_spawning) { + --retained->hold_off_on_exponential_spawning; + } + else if (retained->idle_spawn_rate[child_bucket] + < MAX_SPAWN_RATE / num_buckets) { + retained->idle_spawn_rate[child_bucket] *= 2; + } + } + } + else { + retained->idle_spawn_rate[child_bucket] = 1; + } +} + +static void server_main_loop(int remaining_children_to_start, int num_buckets) +{ + int child_slot; + apr_exit_why_e exitwhy; + int status, processed_status; + apr_proc_t pid; + int i; + + while (!retained->mpm->restart_pending && !retained->mpm->shutdown_pending) { + ap_wait_or_timeout(&exitwhy, &status, &pid, pconf, ap_server_conf); + + if (pid.pid != -1) { + processed_status = ap_process_child_status(&pid, exitwhy, status); + child_slot = ap_find_child_by_pid(&pid); + if (processed_status == APEXIT_CHILDFATAL) { + /* fix race condition found in PR 39311 + * A child created at the same time as a graceful happens + * can find the lock missing and create a fatal error. + * It is not fatal for the last generation to be in this state. + */ + if (child_slot < 0 + || ap_get_scoreboard_process(child_slot)->generation + == retained->mpm->my_generation) { + retained->mpm->shutdown_pending = 1; + child_fatal = 1; + /* + * total_daemons counting will be off now, but as we + * are shutting down, that is not an issue anymore. + */ + return; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00487) + "Ignoring fatal error in child of previous " + "generation (pid %ld).", + (long)pid.pid); + retained->sick_child_detected = 1; + } + } + else if (processed_status == APEXIT_CHILDSICK) { + /* tell perform_idle_server_maintenance to check into this + * on the next timer pop + */ + retained->sick_child_detected = 1; + } + /* non-fatal death... note that it's gone in the scoreboard. */ + if (child_slot >= 0) { + process_score *ps; + + for (i = 0; i < threads_per_child; i++) + ap_update_child_status_from_indexes(child_slot, i, + SERVER_DEAD, NULL); + + event_note_child_killed(child_slot, 0, 0); + ps = &ap_scoreboard_image->parent[child_slot]; + if (!ps->quiescing) + active_daemons--; + ps->quiescing = 0; + /* NOTE: We don't dec in the (child_slot < 0) case! */ + retained->total_daemons--; + if (processed_status == APEXIT_CHILDSICK) { + /* resource shortage, minimize the fork rate */ + retained->idle_spawn_rate[ps->bucket] = 1; + } + else if (remaining_children_to_start) { + /* we're still doing a 1-for-1 replacement of dead + * children with new children + */ + make_child(ap_server_conf, child_slot, ps->bucket); + --remaining_children_to_start; + } + } +#if APR_HAS_OTHER_CHILD + else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, + status) == 0) { + /* handled */ + } +#endif + else if (retained->mpm->was_graceful) { + /* Great, we've probably just lost a slot in the + * scoreboard. Somehow we don't know about this child. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, + ap_server_conf, APLOGNO(00488) + "long lost child came home! (pid %ld)", + (long) pid.pid); + } + /* Don't perform idle maintenance when a child dies, + * only do it when there's a timeout. Remember only a + * finite number of children can die, and it's pretty + * pathological for a lot to die suddenly. + */ + continue; + } + else if (remaining_children_to_start) { + /* we hit a 1 second timeout in which none of the previous + * generation of children needed to be reaped... so assume + * they're all done, and pick up the slack if any is left. + */ + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + /* In any event we really shouldn't do the code below because + * few of the servers we just started are in the IDLE state + * yet, so we'd mistakenly create an extra server. + */ + continue; + } + + for (i = 0; i < num_buckets; i++) { + perform_idle_server_maintenance(i, num_buckets); + } + } +} + +static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) +{ + int num_buckets = retained->mpm->num_buckets; + int remaining_children_to_start; + int i; + + ap_log_pid(pconf, ap_pid_fname); + + if (!retained->mpm->was_graceful) { + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + return !OK; + } + /* fix the generation number in the global score; we just got a new, + * cleared scoreboard + */ + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + } + + ap_unixd_mpm_set_signals(pconf, one_process); + + /* Don't thrash since num_buckets depends on the + * system and the number of online CPU cores... + */ + if (active_daemons_limit < num_buckets) + active_daemons_limit = num_buckets; + if (ap_daemons_to_start < num_buckets) + ap_daemons_to_start = num_buckets; + /* We want to create as much children at a time as the number of buckets, + * so to optimally accept connections (evenly distributed across buckets). + * Thus min_spare_threads should at least maintain num_buckets children, + * and max_spare_threads allow num_buckets more children w/o triggering + * immediately (e.g. num_buckets idle threads margin, one per bucket). + */ + if (min_spare_threads < threads_per_child * (num_buckets - 1) + num_buckets) + min_spare_threads = threads_per_child * (num_buckets - 1) + num_buckets; + if (max_spare_threads < min_spare_threads + (threads_per_child + 1) * num_buckets) + max_spare_threads = min_spare_threads + (threads_per_child + 1) * num_buckets; + + /* If we're doing a graceful_restart then we're going to see a lot + * of children exiting immediately when we get into the main loop + * below (because we just sent them AP_SIG_GRACEFUL). This happens pretty + * rapidly... and for each one that exits we may start a new one, until + * there are at least min_spare_threads idle threads, counting across + * all children. But we may be permitted to start more children than + * that, so we'll just keep track of how many we're + * supposed to start up without the 1 second penalty between each fork. + */ + remaining_children_to_start = ap_daemons_to_start; + if (remaining_children_to_start > active_daemons_limit) { + remaining_children_to_start = active_daemons_limit; + } + if (!retained->mpm->was_graceful) { + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + } + else { + /* give the system some time to recover before kicking into + * exponential mode */ + retained->hold_off_on_exponential_spawning = 10; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00489) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00490) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + server_main_loop(remaining_children_to_start, num_buckets); + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + if (retained->mpm->shutdown_pending && retained->mpm->is_ungraceful) { + /* Time to shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_RESTART); + } + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + event_note_child_killed); + + if (!child_fatal) { + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, + ap_server_conf, APLOGNO(00491) "caught SIGTERM, shutting down"); + } + + return DONE; + } + + if (retained->mpm->shutdown_pending) { + /* Time to gracefully shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + int active_children; + int index; + apr_time_t cutoff = 0; + + /* Close our listeners, and then ask our children to do same */ + ap_close_listeners(); + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_GRACEFUL); + } + ap_relieve_child_processes(event_note_child_killed); + + if (!child_fatal) { + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00492) + "caught " AP_SIG_GRACEFUL_STOP_STRING + ", shutting down gracefully"); + } + + if (ap_graceful_shutdown_timeout) { + cutoff = apr_time_now() + + apr_time_from_sec(ap_graceful_shutdown_timeout); + } + + /* Don't really exit until each child has finished */ + retained->mpm->shutdown_pending = 0; + do { + /* Pause for a second */ + apr_sleep(apr_time_from_sec(1)); + + /* Relieve any children which have now exited */ + ap_relieve_child_processes(event_note_child_killed); + + active_children = 0; + for (index = 0; index < retained->max_daemons_limit; ++index) { + if (ap_mpm_safe_kill(MPM_CHILD_PID(index), 0) == APR_SUCCESS) { + active_children = 1; + /* Having just one child is enough to stay around */ + break; + } + } + } while (!retained->mpm->shutdown_pending && active_children && + (!ap_graceful_shutdown_timeout || apr_time_now() < cutoff)); + + /* We might be here because we received SIGTERM, either + * way, try and make sure that all of our processes are + * really dead. + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_RESTART); + } + ap_reclaim_child_processes(1, event_note_child_killed); + + return DONE; + } + + /* we've been told to restart */ + if (one_process) { + /* not worth thinking about */ + return DONE; + } + + /* advance to the next generation */ + /* XXX: we really need to make sure this new generation number isn't in + * use by any of the children. + */ + ++retained->mpm->my_generation; + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + + if (!retained->mpm->is_ungraceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00493) + AP_SIG_GRACEFUL_STRING + " received. Doing graceful restart"); + /* wake up the children...time to die. But we'll have more soon */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_GRACEFUL); + } + + /* This is mostly for debugging... so that we know what is still + * gracefully dealing with existing request. + */ + + } + else { + /* Kill 'em all. Since the child acts the same on the parents SIGTERM + * and a SIGHUP, we may as well use the same signal, because some user + * pthreads are stealing signals from us left and right. + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_RESTART); + } + + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + event_note_child_killed); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00494) + "SIGHUP received. Attempting to restart"); + } + + active_daemons = 0; + + return OK; +} + +static void setup_slave_conn(conn_rec *c, void *csd) +{ + event_conn_state_t *mcs; + event_conn_state_t *cs; + + mcs = ap_get_module_config(c->master->conn_config, &mpm_event_module); + + cs = apr_pcalloc(c->pool, sizeof(*cs)); + cs->c = c; + cs->r = NULL; + cs->sc = mcs->sc; + cs->suspended = 0; + cs->p = c->pool; + cs->bucket_alloc = c->bucket_alloc; + cs->pfd = mcs->pfd; + cs->pub = mcs->pub; + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + cs->pub.sense = CONN_SENSE_DEFAULT; + + c->cs = &(cs->pub); + ap_set_module_config(c->conn_config, &mpm_event_module, cs); +} + +static int event_pre_connection(conn_rec *c, void *csd) +{ + if (c->master && (!c->cs || c->cs == c->master->cs)) { + setup_slave_conn(c, csd); + } + return OK; +} + +static int event_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, + const char *protocol) +{ + if (!r && s) { + /* connection based switching of protocol, set the correct server + * configuration, so that timeouts, keepalives and such are used + * for the server that the connection was switched on. + * Normally, we set this on post_read_request, but on a protocol + * other than http/1.1, this might never happen. + */ + event_conn_state_t *cs; + + cs = ap_get_module_config(c->conn_config, &mpm_event_module); + cs->sc = ap_get_module_config(s->module_config, &mpm_event_module); + } + return DECLINED; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int event_open_logs(apr_pool_t * p, apr_pool_t * plog, + apr_pool_t * ptemp, server_rec * s) +{ + int startup = 0; + int level_flags = 0; + int num_buckets = 0; + ap_listen_rec **listen_buckets; + apr_status_t rv; + int i; + + pconf = p; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + level_flags |= APLOG_STARTUP; + } + + if ((num_listensocks = ap_setup_listeners(ap_server_conf)) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0, + (startup ? NULL : s), + "no listening sockets available, shutting down"); + return !OK; + } + + if (one_process) { + num_buckets = 1; + } + else if (retained->mpm->was_graceful) { + /* Preserve the number of buckets on graceful restarts. */ + num_buckets = retained->mpm->num_buckets; + } + if ((rv = ap_duplicate_listeners(pconf, ap_server_conf, + &listen_buckets, &num_buckets))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not duplicate listeners"); + return !OK; + } + + all_buckets = apr_pcalloc(pconf, num_buckets * sizeof(*all_buckets)); + for (i = 0; i < num_buckets; i++) { + if (!one_process && /* no POD in one_process mode */ + (rv = ap_mpm_podx_open(pconf, &all_buckets[i].pod))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not open pipe-of-death"); + return !OK; + } + all_buckets[i].listeners = listen_buckets[i]; + } + + if (retained->mpm->max_buckets < num_buckets) { + int new_max, *new_ptr; + new_max = retained->mpm->max_buckets * 2; + if (new_max < num_buckets) { + new_max = num_buckets; + } + new_ptr = (int *)apr_palloc(ap_pglobal, new_max * sizeof(int)); + memcpy(new_ptr, retained->idle_spawn_rate, + retained->mpm->num_buckets * sizeof(int)); + retained->idle_spawn_rate = new_ptr; + retained->mpm->max_buckets = new_max; + } + if (retained->mpm->num_buckets < num_buckets) { + int rate_max = 1; + /* If new buckets are added, set their idle spawn rate to + * the highest so far, so that they get filled as quickly + * as the existing ones. + */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (rate_max < retained->idle_spawn_rate[i]) { + rate_max = retained->idle_spawn_rate[i]; + } + } + for (/* up to date i */; i < num_buckets; i++) { + retained->idle_spawn_rate[i] = rate_max; + } + } + retained->mpm->num_buckets = num_buckets; + + /* for skiplist */ + srand((unsigned int)apr_time_now()); + return OK; +} + +static int event_pre_config(apr_pool_t * pconf, apr_pool_t * plog, + apr_pool_t * ptemp) +{ + int no_detach, debug, foreground; + apr_status_t rv; + const char *userdata_key = "mpm_event_module"; + int test_atomics = 0; + + debug = ap_exists_config_define("DEBUG"); + + if (debug) { + foreground = one_process = 1; + no_detach = 0; + } + else { + one_process = ap_exists_config_define("ONE_PROCESS"); + no_detach = ap_exists_config_define("NO_DETACH"); + foreground = ap_exists_config_define("FOREGROUND"); + } + + retained = ap_retained_data_get(userdata_key); + if (!retained) { + retained = ap_retained_data_create(userdata_key, sizeof(*retained)); + retained->mpm = ap_unixd_mpm_get_retained_data(); + retained->max_daemons_limit = -1; + if (retained->mpm->module_loads) { + test_atomics = 1; + } + } + retained->mpm->mpm_state = AP_MPMQ_STARTING; + if (retained->mpm->baton != retained) { + retained->mpm->was_graceful = 0; + retained->mpm->baton = retained; + } + ++retained->mpm->module_loads; + + /* test once for correct operation of fdqueue */ + if (test_atomics || retained->mpm->module_loads == 2) { + static apr_uint32_t foo1, foo2; + + apr_atomic_set32(&foo1, 100); + foo2 = apr_atomic_add32(&foo1, -10); + if (foo2 != 100 || foo1 != 90) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(02405) + "atomics not working as expected - add32 of negative number"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + /* sigh, want this only the second time around */ + if (retained->mpm->module_loads == 2) { + rv = apr_pollset_create(&event_pollset, 1, plog, + APR_POLLSET_THREADSAFE | APR_POLLSET_NOCOPY); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00495) + "Couldn't create a Thread Safe Pollset. " + "Is it supported on your platform?" + "Also check system or user limits!"); + return HTTP_INTERNAL_SERVER_ERROR; + } + apr_pollset_destroy(event_pollset); + + if (!one_process && !foreground) { + /* before we detach, setup crash handlers to log to errorlog */ + ap_fatal_signal_setup(ap_server_conf, pconf); + rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND + : APR_PROC_DETACH_DAEMONIZE); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00496) + "apr_proc_detach failed"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + } + + parent_pid = ap_my_pid = getpid(); + + ap_listen_pre_config(); + ap_daemons_to_start = DEFAULT_START_DAEMON; + min_spare_threads = DEFAULT_MIN_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD; + max_spare_threads = DEFAULT_MAX_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD; + server_limit = DEFAULT_SERVER_LIMIT; + thread_limit = DEFAULT_THREAD_LIMIT; + active_daemons_limit = server_limit; + threads_per_child = DEFAULT_THREADS_PER_CHILD; + max_workers = active_daemons_limit * threads_per_child; + defer_linger_chain = NULL; + had_healthy_child = 0; + ap_extended_status = 0; + + event_pollset = NULL; + worker_queue_info = NULL; + listener_os_thread = NULL; + listensocks_disabled = 0; + + return OK; +} + +static int event_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + struct { + struct timeout_queue *tail, *q; + apr_hash_t *hash; + } wc, ka; + + /* Not needed in pre_config stage */ + if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { + return OK; + } + + wc.tail = ka.tail = NULL; + wc.hash = apr_hash_make(ptemp); + ka.hash = apr_hash_make(ptemp); + + linger_q = TO_QUEUE_MAKE(pconf, apr_time_from_sec(MAX_SECS_TO_LINGER), + NULL); + short_linger_q = TO_QUEUE_MAKE(pconf, apr_time_from_sec(SECONDS_TO_LINGER), + NULL); + + for (; s; s = s->next) { + event_srv_cfg *sc = apr_pcalloc(pconf, sizeof *sc); + + ap_set_module_config(s->module_config, &mpm_event_module, sc); + if (!wc.tail) { + /* The main server uses the global queues */ + wc.q = TO_QUEUE_MAKE(pconf, s->timeout, NULL); + apr_hash_set(wc.hash, &s->timeout, sizeof s->timeout, wc.q); + wc.tail = write_completion_q = wc.q; + + ka.q = TO_QUEUE_MAKE(pconf, s->keep_alive_timeout, NULL); + apr_hash_set(ka.hash, &s->keep_alive_timeout, + sizeof s->keep_alive_timeout, ka.q); + ka.tail = keepalive_q = ka.q; + } + else { + /* The vhosts use any existing queue with the same timeout, + * or their own queue(s) if there isn't */ + wc.q = apr_hash_get(wc.hash, &s->timeout, sizeof s->timeout); + if (!wc.q) { + wc.q = TO_QUEUE_MAKE(pconf, s->timeout, wc.tail); + apr_hash_set(wc.hash, &s->timeout, sizeof s->timeout, wc.q); + wc.tail = wc.tail->next = wc.q; + } + + ka.q = apr_hash_get(ka.hash, &s->keep_alive_timeout, + sizeof s->keep_alive_timeout); + if (!ka.q) { + ka.q = TO_QUEUE_MAKE(pconf, s->keep_alive_timeout, ka.tail); + apr_hash_set(ka.hash, &s->keep_alive_timeout, + sizeof s->keep_alive_timeout, ka.q); + ka.tail = ka.tail->next = ka.q; + } + } + sc->wc_q = wc.q; + sc->ka_q = ka.q; + } + + return OK; +} + +static int event_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + } + + if (server_limit > MAX_SERVER_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00497) + "WARNING: ServerLimit of %d exceeds compile-time " + "limit of %d servers, decreasing to %d.", + server_limit, MAX_SERVER_LIMIT, MAX_SERVER_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00498) + "ServerLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + server_limit, MAX_SERVER_LIMIT); + } + server_limit = MAX_SERVER_LIMIT; + } + else if (server_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00499) + "WARNING: ServerLimit of %d not allowed, " + "increasing to 1.", server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00500) + "ServerLimit of %d not allowed, increasing to 1", + server_limit); + } + server_limit = 1; + } + + /* you cannot change ServerLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_server_limit) { + retained->first_server_limit = server_limit; + } + else if (server_limit != retained->first_server_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00501) + "changing ServerLimit to %d from original value of %d " + "not allowed during restart", + server_limit, retained->first_server_limit); + server_limit = retained->first_server_limit; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00502) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of %d threads, decreasing to %d.", + thread_limit, MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00503) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00504) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00505) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* you cannot change ThreadLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_thread_limit) { + retained->first_thread_limit = thread_limit; + } + else if (thread_limit != retained->first_thread_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00506) + "changing ThreadLimit to %d from original value of %d " + "not allowed during restart", + thread_limit, retained->first_thread_limit); + thread_limit = retained->first_thread_limit; + } + + if (threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00507) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of %d threads, decreasing to %d. " + "To increase, please see the ThreadLimit directive.", + threads_per_child, thread_limit, thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00508) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + threads_per_child, thread_limit); + } + threads_per_child = thread_limit; + } + else if (threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00509) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00510) + "ThreadsPerChild of %d not allowed, increasing to 1", + threads_per_child); + } + threads_per_child = 1; + } + + if (max_workers < threads_per_child) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00511) + "WARNING: MaxRequestWorkers of %d is less than " + "ThreadsPerChild of %d, increasing to %d. " + "MaxRequestWorkers must be at least as large " + "as the number of threads in a single server.", + max_workers, threads_per_child, threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00512) + "MaxRequestWorkers of %d is less than ThreadsPerChild " + "of %d, increasing to match", + max_workers, threads_per_child); + } + max_workers = threads_per_child; + } + + active_daemons_limit = max_workers / threads_per_child; + + if (max_workers % threads_per_child) { + int tmp_max_workers = active_daemons_limit * threads_per_child; + + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00513) + "WARNING: MaxRequestWorkers of %d is not an integer " + "multiple of ThreadsPerChild of %d, decreasing to nearest " + "multiple %d, for a maximum of %d servers.", + max_workers, threads_per_child, tmp_max_workers, + active_daemons_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00514) + "MaxRequestWorkers of %d is not an integer multiple " + "of ThreadsPerChild of %d, decreasing to nearest " + "multiple %d", max_workers, threads_per_child, + tmp_max_workers); + } + max_workers = tmp_max_workers; + } + + if (active_daemons_limit > server_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00515) + "WARNING: MaxRequestWorkers of %d would require %d servers " + "and would exceed ServerLimit of %d, decreasing to %d. " + "To increase, please see the ServerLimit directive.", + max_workers, active_daemons_limit, server_limit, + server_limit * threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00516) + "MaxRequestWorkers of %d would require %d servers and " + "exceed ServerLimit of %d, decreasing to %d", + max_workers, active_daemons_limit, server_limit, + server_limit * threads_per_child); + } + active_daemons_limit = server_limit; + } + + /* ap_daemons_to_start > active_daemons_limit checked in ap_mpm_run() */ + if (ap_daemons_to_start < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00517) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00518) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00519) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1 to avoid almost certain server " + "failure. Please read the documentation.", + min_spare_threads); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00520) + "MinSpareThreads of %d not allowed, increasing to 1", + min_spare_threads); + } + min_spare_threads = 1; + } + + /* max_spare_threads < min_spare_threads + threads_per_child + * checked in ap_mpm_run() + */ + + return OK; +} + +static void event_hooks(apr_pool_t * p) +{ + /* Our open_logs hook function must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = { "core.c", NULL }; + one_process = 0; + + ap_hook_open_logs(event_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + /* we need to set the MPM state before other pre-config hooks use MPM query + * to retrieve it, so register as REALLY_FIRST + */ + ap_hook_pre_config(event_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_post_config(event_post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(event_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(event_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(event_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_register_timed_callback(event_register_timed_callback, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_pre_read_request(event_pre_read_request, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_read_request(event_post_read_request, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(event_get_name, NULL, NULL, APR_HOOK_MIDDLE); + + ap_hook_pre_connection(event_pre_connection, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_protocol_switch(event_protocol_switch, NULL, NULL, APR_HOOK_REALLY_FIRST); +} + +static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_to_start = atoi(arg); + return NULL; +} + +static const char *set_min_spare_threads(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + min_spare_threads = atoi(arg); + return NULL; +} + +static const char *set_max_spare_threads(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + max_spare_threads = atoi(arg); + return NULL; +} + +static const char *set_max_workers(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + if (!strcasecmp(cmd->cmd->name, "MaxClients")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00521) + "MaxClients is deprecated, use MaxRequestWorkers " + "instead."); + } + max_workers = atoi(arg); + return NULL; +} + +static const char *set_threads_per_child(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + threads_per_child = atoi(arg); + return NULL; +} +static const char *set_server_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + server_limit = atoi(arg); + return NULL; +} + +static const char *set_thread_limit(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + thread_limit = atoi(arg); + return NULL; +} + +static const char *set_worker_factor(cmd_parms * cmd, void *dummy, + const char *arg) +{ + double val; + char *endptr; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + val = strtod(arg, &endptr); + if (*endptr) + return "error parsing value"; + + if (val <= 0) + return "AsyncRequestWorkerFactor argument must be a positive number"; + + worker_factor = val * WORKER_FACTOR_SCALE; + if (worker_factor < WORKER_FACTOR_SCALE) { + worker_factor = WORKER_FACTOR_SCALE; + } + return NULL; +} + + +static const command_rec event_cmds[] = { + LISTEN_COMMANDS, + AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF, + "Number of child processes launched at server startup"), + AP_INIT_TAKE1("ServerLimit", set_server_limit, NULL, RSRC_CONF, + "Maximum number of child processes for this run of Apache"), + AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF, + "Minimum number of idle threads, to handle request spikes"), + AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF, + "Maximum number of idle threads"), + AP_INIT_TAKE1("MaxClients", set_max_workers, NULL, RSRC_CONF, + "Deprecated name of MaxRequestWorkers"), + AP_INIT_TAKE1("MaxRequestWorkers", set_max_workers, NULL, RSRC_CONF, + "Maximum number of threads alive at the same time"), + AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF, + "Number of threads each child creates"), + AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF, + "Maximum number of worker threads per child process for this " + "run of Apache - Upper limit for ThreadsPerChild"), + AP_INIT_TAKE1("AsyncRequestWorkerFactor", set_worker_factor, NULL, RSRC_CONF, + "How many additional connects will be accepted per idle " + "worker thread"), + AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, + {NULL} +}; + +AP_DECLARE_MODULE(mpm_event) = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + event_cmds, /* command apr_table_t */ + event_hooks /* register_hooks */ +}; diff --git a/server/mpm/event/mpm_default.h b/server/mpm/event/mpm_default.h new file mode 100644 index 0000000..214caa0 --- /dev/null +++ b/server/mpm/event/mpm_default.h @@ -0,0 +1,56 @@ +/* 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. + */ + + +/** + * @file event/mpm_default.h + * @brief Event MPM defaults + * + * @defgroup APACHE_MPM_EVENT Event MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Number of servers to spawn off by default --- also, if fewer than + * this free when the caretaker checks, it will spawn more. + */ +#ifndef DEFAULT_START_DAEMON +#define DEFAULT_START_DAEMON 3 +#endif + +/* Maximum number of *free* server processes --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_FREE_DAEMON +#define DEFAULT_MAX_FREE_DAEMON 10 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_FREE_DAEMON +#define DEFAULT_MIN_FREE_DAEMON 3 +#endif + +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 25 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/mpmt_os2/Makefile.in b/server/mpm/mpmt_os2/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/mpmt_os2/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/mpmt_os2/config.m4 b/server/mpm/mpmt_os2/config.m4 new file mode 100644 index 0000000..9a29903 --- /dev/null +++ b/server/mpm/mpmt_os2/config.m4 @@ -0,0 +1,10 @@ +AC_MSG_CHECKING(if mpmt_os2 MPM supports this platform) +case $host in + *os2-emx*) + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(mpmt_os2, no, yes) + ;; + *) + AC_MSG_RESULT(no) + ;; +esac diff --git a/server/mpm/mpmt_os2/config5.m4 b/server/mpm/mpmt_os2/config5.m4 new file mode 100644 index 0000000..c74f145 --- /dev/null +++ b/server/mpm/mpmt_os2/config5.m4 @@ -0,0 +1,3 @@ +APACHE_MPM_MODULE(mpmt_os2, $enable_mpm_mpmt_os2, mpmt_os2.lo mpmt_os2_child.lo,[ + APR_ADDTO(CFLAGS,-Zmt) +]) diff --git a/server/mpm/mpmt_os2/mpm_default.h b/server/mpm/mpmt_os2/mpm_default.h new file mode 100644 index 0000000..36ba944 --- /dev/null +++ b/server/mpm/mpmt_os2/mpm_default.h @@ -0,0 +1,57 @@ +/* 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. + */ + +/** + * @file mpmt_os2/mpm_default.h + * @brief os2 MPM defaults + * + * @defgroup APACHE_MPM_OS2 OS/2 MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Number of servers processes to spawn off by default + */ +#ifndef DEFAULT_START_DAEMON +#define DEFAULT_START_DAEMON 2 +#endif + +/* Maximum number of *free* server threads --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_SPARE_THREAD +#define DEFAULT_MAX_SPARE_THREAD 10 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_SPARE_THREAD +#define DEFAULT_MIN_SPARE_THREAD 5 +#endif + +/* + * Interval, in microseconds, between scoreboard maintenance. + */ +#ifndef SCOREBOARD_MAINTENANCE_INTERVAL +#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/mpmt_os2/mpmt_os2.c b/server/mpm/mpmt_os2/mpmt_os2.c new file mode 100644 index 0000000..22bf5e7 --- /dev/null +++ b/server/mpm/mpmt_os2/mpmt_os2.c @@ -0,0 +1,614 @@ +/* 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. + */ + +/* Multi-process, multi-threaded MPM for OS/2 + * + * Server consists of + * - a main, parent process + * - a small, static number of child processes + * + * The parent process's job is to manage the child processes. This involves + * spawning children as required to ensure there are always ap_daemons_to_start + * processes accepting connections. + * + * Each child process consists of a pool of worker threads and a + * main thread that accepts connections & passes them to the workers via + * a work queue. The worker thread pool is dynamic, managed by a maintanence + * thread so that the number of idle threads is kept between + * min_spare_threads & max_spare_threads. + * + */ + +/* + Todo list + - Enforce MaxRequestWorkers somehow +*/ +#define INCL_NOPMAPI +#define INCL_DOS +#define INCL_DOSERRORS + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "apr_portable.h" +#include "mpm_common.h" +#include "scoreboard.h" +#include "apr_strings.h" +#include <os2.h> +#include <process.h> + +/* We don't need many processes, + * they're only for redundancy in the event of a crash + */ +#define HARD_SERVER_LIMIT 10 + +/* Limit on the total number of threads per process + */ +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 256 +#endif + +server_rec *ap_server_conf; +static apr_pool_t *pconf = NULL; /* Pool for config stuff */ + +/* Config globals */ +static int one_process = 0; +static int ap_daemons_to_start = 0; +static int ap_thread_limit = 0; +int ap_min_spare_threads = 0; +int ap_max_spare_threads = 0; + +/* Keep track of a few interesting statistics */ +int ap_max_daemons_limit = -1; + +/* volatile just in case */ +static int volatile shutdown_pending; +static int volatile restart_pending; +static int volatile is_graceful = 0; +ap_generation_t volatile ap_my_generation=0; /* Used by the scoreboard */ +static int is_parent_process=TRUE; +HMTX ap_mpm_accept_mutex = 0; + +/* An array of these is stored in a shared memory area for passing + * sockets from the parent to child processes + */ +typedef struct { + struct sockaddr_in name; + apr_os_sock_t listen_fd; +} listen_socket_t; + +typedef struct { + HMTX accept_mutex; + listen_socket_t listeners[1]; +} parent_info_t; + +static int master_main(); +static void spawn_child(int slot); +void ap_mpm_child_main(apr_pool_t *pconf); +static void set_signals(); + + +static int mpmt_os2_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) +{ + char *listener_shm_name; + parent_info_t *parent_info; + ULONG rc; + pconf = _pconf; + ap_server_conf = s; + restart_pending = 0; + + DosSetMaxFH(ap_thread_limit * 2); + listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getppid()); + rc = DosGetNamedSharedMem((PPVOID)&parent_info, listener_shm_name, PAG_READ); + is_parent_process = rc != 0; + ap_scoreboard_fname = apr_psprintf(pconf, "/sharemem/httpd/scoreboard.%d", is_parent_process ? getpid() : getppid()); + + if (rc == 0) { + /* Child process */ + ap_listen_rec *lr; + int num_listeners = 0; + + ap_mpm_accept_mutex = parent_info->accept_mutex; + + /* Set up a default listener if necessary */ + if (ap_listeners == NULL) { + ap_listen_rec *lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec)); + ap_listeners = lr; + apr_sockaddr_info_get(&lr->bind_addr, "0.0.0.0", APR_UNSPEC, + DEFAULT_HTTP_PORT, 0, s->process->pool); + apr_socket_create(&lr->sd, lr->bind_addr->family, + SOCK_STREAM, 0, s->process->pool); + } + + for (lr = ap_listeners; lr; lr = lr->next) { + apr_sockaddr_t *sa; + apr_os_sock_put(&lr->sd, &parent_info->listeners[num_listeners].listen_fd, pconf); + apr_socket_addr_get(&sa, APR_LOCAL, lr->sd); + num_listeners++; + } + + DosFreeMem(parent_info); + + /* Do the work */ + ap_mpm_child_main(pconf); + + /* Outta here */ + return DONE; + } + else { + /* Parent process */ + int rc; + is_parent_process = TRUE; + + if (ap_setup_listeners(ap_server_conf) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00200) + "no listening sockets available, shutting down"); + return !OK; + } + + ap_log_pid(pconf, ap_pid_fname); + + rc = master_main(); + ++ap_my_generation; + ap_scoreboard_image->global->running_generation = ap_my_generation; + + if (rc != OK) { + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00201) + "caught %s, shutting down", + (rc == DONE) ? "SIGTERM" : "error"); + return rc; + } + } /* Parent process */ + + return OK; /* Restart */ +} + + + +/* Main processing of the parent process + * returns TRUE if restarting + */ +static int master_main() +{ + server_rec *s = ap_server_conf; + ap_listen_rec *lr; + parent_info_t *parent_info; + char *listener_shm_name; + int listener_num, num_listeners, slot; + ULONG rc; + + printf("%s \n", ap_get_server_description()); + set_signals(); + + if (ap_setup_listeners(ap_server_conf) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00202) + "no listening sockets available, shutting down"); + return !OK; + } + + /* Allocate a shared memory block for the array of listeners */ + for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) { + num_listeners++; + } + + listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getpid()); + rc = DosAllocSharedMem((PPVOID)&parent_info, listener_shm_name, + sizeof(parent_info_t) + num_listeners * sizeof(listen_socket_t), + PAG_READ|PAG_WRITE|PAG_COMMIT); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00203) + "failure allocating shared memory, shutting down"); + return !OK; + } + + /* Store the listener sockets in the shared memory area for our children to see */ + for (listener_num = 0, lr = ap_listeners; lr; lr = lr->next, listener_num++) { + apr_os_sock_get(&parent_info->listeners[listener_num].listen_fd, lr->sd); + } + + /* Create mutex to prevent multiple child processes from detecting + * a connection with apr_poll() + */ + + rc = DosCreateMutexSem(NULL, &ap_mpm_accept_mutex, DC_SEM_SHARED, FALSE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00204) + "failure creating accept mutex, shutting down"); + return !OK; + } + + parent_info->accept_mutex = ap_mpm_accept_mutex; + + /* Allocate shared memory for scoreboard */ + if (ap_scoreboard_image == NULL) { + void *sb_mem; + rc = DosAllocSharedMem(&sb_mem, ap_scoreboard_fname, + ap_calc_scoreboard_size(), + PAG_COMMIT|PAG_READ|PAG_WRITE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00205) + "unable to allocate shared memory for scoreboard , exiting"); + return !OK; + } + + ap_init_scoreboard(sb_mem); + } + + ap_scoreboard_image->global->restart_time = apr_time_now(); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00206) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00207) + "Server built: %s", ap_get_server_built()); + if (one_process) { + ap_scoreboard_image->parent[0].pid = getpid(); + ap_mpm_child_main(pconf); + return DONE; + } + + while (!restart_pending && !shutdown_pending) { + RESULTCODES proc_rc; + PID child_pid; + int active_children = 0; + + /* Count number of active children */ + for (slot=0; slot < HARD_SERVER_LIMIT; slot++) { + active_children += ap_scoreboard_image->parent[slot].pid != 0 && + !ap_scoreboard_image->parent[slot].quiescing; + } + + /* Spawn children if needed */ + for (slot=0; slot < HARD_SERVER_LIMIT && active_children < ap_daemons_to_start; slot++) { + if (ap_scoreboard_image->parent[slot].pid == 0) { + spawn_child(slot); + active_children++; + } + } + + rc = DosWaitChild(DCWA_PROCESSTREE, DCWW_NOWAIT, &proc_rc, &child_pid, 0); + + if (rc == 0) { + /* A child has terminated, remove its scoreboard entry & terminate if necessary */ + for (slot=0; ap_scoreboard_image->parent[slot].pid != child_pid && slot < HARD_SERVER_LIMIT; slot++); + + if (slot < HARD_SERVER_LIMIT) { + ap_scoreboard_image->parent[slot].pid = 0; + ap_scoreboard_image->parent[slot].quiescing = 0; + + if (proc_rc.codeTerminate == TC_EXIT) { + /* Child terminated normally, check its exit code and + * terminate server if child indicates a fatal error + */ + if (proc_rc.codeResult == APEXIT_CHILDFATAL) + break; + } + } + } else if (rc == ERROR_CHILD_NOT_COMPLETE) { + /* No child exited, lets sleep for a while.... */ + apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL); + } + } + + /* Signal children to shut down, either gracefully or immediately */ + for (slot=0; slot<HARD_SERVER_LIMIT; slot++) { + kill(ap_scoreboard_image->parent[slot].pid, is_graceful ? SIGHUP : SIGTERM); + } + + DosFreeMem(parent_info); + return restart_pending ? OK : DONE; +} + + + +static void spawn_child(int slot) +{ + PPIB ppib; + PTIB ptib; + char fail_module[100]; + char progname[CCHMAXPATH]; + RESULTCODES proc_rc; + ULONG rc; + + ap_scoreboard_image->parent[slot].generation = ap_my_generation; + DosGetInfoBlocks(&ptib, &ppib); + DosQueryModuleName(ppib->pib_hmte, sizeof(progname), progname); + rc = DosExecPgm(fail_module, sizeof(fail_module), EXEC_ASYNCRESULT, + ppib->pib_pchcmd, NULL, &proc_rc, progname); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00208) + "error spawning child, slot %d", slot); + } + + if (ap_max_daemons_limit < slot) { + ap_max_daemons_limit = slot; + } + + ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate; +} + + + +/* Signal handling routines */ + +static void sig_term(int sig) +{ + shutdown_pending = 1; + signal(SIGTERM, SIG_DFL); +} + + + +static void sig_restart(int sig) +{ + if (sig == SIGUSR1) { + is_graceful = 1; + } + + restart_pending = 1; +} + + + +static void set_signals() +{ + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_term; + + if (sigaction(SIGTERM, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00209) "sigaction(SIGTERM)"); + + if (sigaction(SIGINT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00210) "sigaction(SIGINT)"); + + sa.sa_handler = sig_restart; + + if (sigaction(SIGHUP, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00211) "sigaction(SIGHUP)"); + if (sigaction(SIGUSR1, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00212) "sigaction(SIGUSR1)"); +} + + + +/* Enquiry functions used get MPM status info */ + +static apr_status_t mpmt_os2_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = ap_max_daemons_limit; + break; + + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_DYNAMIC; + break; + + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = HARD_SERVER_LIMIT; + break; + + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = HARD_THREAD_LIMIT; + break; + + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + + case AP_MPMQ_GENERATION: + *result = ap_my_generation; + break; + + default: + *rv = APR_ENOTIMPL; + break; + } + + return OK; +} + + + + +static const char *mpmt_os2_get_name(void) +{ + return "mpmt_os2"; +} + + + + +/* Configuration handling stuff */ + +static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ + one_process = ap_exists_config_define("ONE_PROCESS") || + ap_exists_config_define("DEBUG"); + is_graceful = 0; + ap_listen_pre_config(); + ap_daemons_to_start = DEFAULT_START_DAEMON; + ap_thread_limit = HARD_THREAD_LIMIT; + ap_extended_status = 0; + ap_min_spare_threads = DEFAULT_MIN_SPARE_THREAD; + ap_max_spare_threads = DEFAULT_MAX_SPARE_THREAD; + ap_sys_privileges_handlers(1); + + return OK; +} + + + +static int mpmt_os2_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + static int restart_num = 0; + int startup = 0; + + /* we want this only the first time around */ + if (restart_num++ == 0) { + startup = 1; + } + + if (ap_daemons_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00213) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00214) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (ap_min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00215) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1 to avoid almost certain server failure. " + "Please read the documentation.", ap_min_spare_threads); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00216) + "MinSpareThreads of %d not allowed, increasing to 1", + ap_min_spare_threads); + } + ap_min_spare_threads = 1; + } + + return OK; +} + + + +static void mpmt_os2_hooks(apr_pool_t *p) +{ + ap_hook_pre_config(mpmt_os2_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(mpmt_os2_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(mpmt_os2_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(mpmt_os2_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(mpmt_os2_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + + + +static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + ap_daemons_to_start = atoi(arg); + return NULL; +} + + + +static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + ap_min_spare_threads = atoi(arg); + return NULL; +} + + + +static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + ap_max_spare_threads = atoi(arg); + return NULL; +} + + + +static const char *ignore_cmd(cmd_parms *cmd, void *dummy, const char *arg) +{ + return NULL; +} + + + +static const command_rec mpmt_os2_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1( "StartServers", set_daemons_to_start, NULL, RSRC_CONF, + "Number of child processes launched at server startup" ), +AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF, + "Minimum number of idle children, to handle request spikes"), +AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF, + "Maximum number of idle children"), +AP_INIT_TAKE1("User", ignore_cmd, NULL, RSRC_CONF, + "Not applicable on this platform"), +AP_INIT_TAKE1("Group", ignore_cmd, NULL, RSRC_CONF, + "Not applicable on this platform"), +AP_INIT_TAKE1("ScoreBoardFile", ignore_cmd, NULL, RSRC_CONF, \ + "Not applicable on this platform"), +{ NULL } +}; + +AP_DECLARE_MODULE(mpm_mpmt_os2) = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + mpmt_os2_cmds, /* command apr_table_t */ + mpmt_os2_hooks, /* register_hooks */ +}; diff --git a/server/mpm/mpmt_os2/mpmt_os2_child.c b/server/mpm/mpmt_os2/mpmt_os2_child.c new file mode 100644 index 0000000..bb7e136 --- /dev/null +++ b/server/mpm/mpmt_os2/mpmt_os2_child.c @@ -0,0 +1,489 @@ +/* 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 INCL_NOPMAPI +#define INCL_DOS +#define INCL_DOSERRORS + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "scoreboard.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "apr_portable.h" +#include "apr_poll.h" +#include "mpm_common.h" +#include "apr_strings.h" +#include <os2.h> +#include <process.h> + +APLOG_USE_MODULE(mpm_mpmt_os2); + +/* XXXXXX move these to header file private to this MPM */ + +/* We don't need many processes, + * they're only for redundancy in the event of a crash + */ +#define HARD_SERVER_LIMIT 10 + +/* Limit on the total number of threads per process + */ +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 256 +#endif + +#define ID_FROM_CHILD_THREAD(c, t) ((c * HARD_THREAD_LIMIT) + t) + +typedef struct { + apr_pool_t *pconn; + apr_socket_t *conn_sd; +} worker_args_t; + +#define WORKTYPE_CONN 0 +#define WORKTYPE_EXIT 1 + +static apr_pool_t *pchild = NULL; +static int child_slot; +static int shutdown_pending = 0; +extern int ap_my_generation; +static int volatile is_graceful = 1; +HEV shutdown_event; /* signaled when this child is shutting down */ + +/* grab some MPM globals */ +extern int ap_min_spare_threads; +extern int ap_max_spare_threads; +extern HMTX ap_mpm_accept_mutex; + +static void worker_main(void *vpArg); +static void clean_child_exit(int code); +static void set_signals(); +static void server_maintenance(void *vpArg); + + +static void clean_child_exit(int code) +{ + if (pchild) { + apr_pool_destroy(pchild); + } + + exit(code); +} + + + +void ap_mpm_child_main(apr_pool_t *pconf) +{ + ap_listen_rec *lr = NULL; + int requests_this_child = 0; + int rv = 0; + unsigned long ulTimes; + int my_pid = getpid(); + ULONG rc, c; + HQUEUE workq; + apr_pollset_t *pollset; + int num_listeners; + TID server_maint_tid; + void *sb_mem; + + /* Stop Ctrl-C/Ctrl-Break signals going to child processes */ + DosSetSignalExceptionFocus(0, &ulTimes); + set_signals(); + + /* Create pool for child */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + ap_run_child_init(pchild, ap_server_conf); + + /* Create an event semaphore used to trigger other threads to shutdown */ + rc = DosCreateEventSem(NULL, &shutdown_event, 0, FALSE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00189) + "unable to create shutdown semaphore, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Gain access to the scoreboard. */ + rc = DosGetNamedSharedMem(&sb_mem, ap_scoreboard_fname, + PAG_READ|PAG_WRITE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00190) + "scoreboard not readable in child, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_calc_scoreboard_size(); + ap_init_scoreboard(sb_mem); + + /* Gain access to the accpet mutex */ + rc = DosOpenMutexSem(NULL, &ap_mpm_accept_mutex); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00191) + "accept mutex couldn't be accessed in child, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Find our pid in the scoreboard so we know what slot our parent allocated us */ + for (child_slot = 0; ap_scoreboard_image->parent[child_slot].pid != my_pid && child_slot < HARD_SERVER_LIMIT; child_slot++); + + if (child_slot == HARD_SERVER_LIMIT) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00192) + "child pid not found in scoreboard, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_my_generation = ap_scoreboard_image->parent[child_slot].generation; + memset(ap_scoreboard_image->servers[child_slot], 0, sizeof(worker_score) * HARD_THREAD_LIMIT); + + /* Set up an OS/2 queue for passing connections & termination requests + * to worker threads + */ + rc = DosCreateQueue(&workq, QUE_FIFO, apr_psprintf(pchild, "/queues/httpd/work.%d", my_pid)); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00193) + "unable to create work queue, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create initial pool of worker threads */ + for (c = 0; c < ap_min_spare_threads; c++) { +// ap_scoreboard_image->servers[child_slot][c].tid = _beginthread(worker_main, NULL, 128*1024, (void *)c); + } + + /* Start maintenance thread */ + server_maint_tid = _beginthread(server_maintenance, NULL, 32768, NULL); + + /* Set up poll */ + for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) { + num_listeners++; + } + + apr_pollset_create(&pollset, num_listeners, pchild, 0); + + for (lr = ap_listeners; lr != NULL; lr = lr->next) { + apr_pollfd_t pfd = { 0 }; + + pfd.desc_type = APR_POLL_SOCKET; + pfd.desc.s = lr->sd; + pfd.reqevents = APR_POLLIN; + pfd.client_data = lr; + apr_pollset_add(pollset, &pfd); + } + + /* Main connection accept loop */ + do { + apr_pool_t *pconn; + worker_args_t *worker_args; + int last_poll_idx = 0; + + apr_pool_create(&pconn, pchild); + worker_args = apr_palloc(pconn, sizeof(worker_args_t)); + worker_args->pconn = pconn; + + if (num_listeners == 1) { + rv = apr_socket_accept(&worker_args->conn_sd, ap_listeners->sd, pconn); + } else { + const apr_pollfd_t *poll_results; + apr_int32_t num_poll_results; + + rc = DosRequestMutexSem(ap_mpm_accept_mutex, SEM_INDEFINITE_WAIT); + + if (shutdown_pending) { + DosReleaseMutexSem(ap_mpm_accept_mutex); + break; + } + + rv = APR_FROM_OS_ERROR(rc); + + if (rv == APR_SUCCESS) { + rv = apr_pollset_poll(pollset, -1, &num_poll_results, &poll_results); + DosReleaseMutexSem(ap_mpm_accept_mutex); + } + + if (rv == APR_SUCCESS) { + if (last_poll_idx >= num_listeners) { + last_poll_idx = 0; + } + + lr = poll_results[last_poll_idx++].client_data; + rv = apr_socket_accept(&worker_args->conn_sd, lr->sd, pconn); + last_poll_idx++; + } + } + + if (rv != APR_SUCCESS) { + if (!APR_STATUS_IS_EINTR(rv)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00194) + "apr_socket_accept"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } else { + DosWriteQueue(workq, WORKTYPE_CONN, sizeof(worker_args_t), worker_args, 0); + requests_this_child++; + } + + if (ap_max_requests_per_child != 0 && requests_this_child >= ap_max_requests_per_child) + break; + } while (!shutdown_pending && ap_my_generation == ap_scoreboard_image->global->running_generation); + + ap_scoreboard_image->parent[child_slot].quiescing = 1; + DosPostEventSem(shutdown_event); + DosWaitThread(&server_maint_tid, DCWW_WAIT); + + if (is_graceful) { + char someleft; + + /* tell our worker threads to exit */ + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) { + DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0); + } + } + + do { + someleft = 0; + + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) { + someleft = 1; + DosSleep(1000); + break; + } + } + } while (someleft); + } else { + DosPurgeQueue(workq); + + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) { + DosKillThread(ap_scoreboard_image->servers[child_slot][c].tid); + } + } + } + + apr_pool_destroy(pchild); +} + + + +void add_worker() +{ + int thread_slot; + int stacksize = ap_thread_stacksize == 0 ? 128*1024 : ap_thread_stacksize; + + /* Find a free thread slot */ + for (thread_slot=0; thread_slot < HARD_THREAD_LIMIT; thread_slot++) { + if (ap_scoreboard_image->servers[child_slot][thread_slot].status == SERVER_DEAD) { + ap_scoreboard_image->servers[child_slot][thread_slot].status = SERVER_STARTING; + ap_scoreboard_image->servers[child_slot][thread_slot].tid = + _beginthread(worker_main, NULL, stacksize, (void *)thread_slot); + break; + } + } +} + + + +ULONG APIENTRY thread_exception_handler(EXCEPTIONREPORTRECORD *pReportRec, + EXCEPTIONREGISTRATIONRECORD *pRegRec, + CONTEXTRECORD *pContext, + PVOID p) +{ + int c; + + if (pReportRec->fHandlerFlags & EH_NESTED_CALL) { + return XCPT_CONTINUE_SEARCH; + } + + if (pReportRec->ExceptionNum == XCPT_ACCESS_VIOLATION || + pReportRec->ExceptionNum == XCPT_INTEGER_DIVIDE_BY_ZERO) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00195) + "caught exception in worker thread, initiating child shutdown pid=%d", getpid()); + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].tid == _gettid()) { + ap_scoreboard_image->servers[child_slot][c].status = SERVER_DEAD; + break; + } + } + + /* Shut down process ASAP, it could be quite unhealthy & leaking resources */ + shutdown_pending = 1; + ap_scoreboard_image->parent[child_slot].quiescing = 1; + kill(getpid(), SIGHUP); + DosUnwindException(UNWIND_ALL, 0, 0); + } + + return XCPT_CONTINUE_SEARCH; +} + + + +static void worker_main(void *vpArg) +{ + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; + long conn_id; + conn_rec *current_conn; + apr_pool_t *pconn; + apr_allocator_t *allocator; + apr_bucket_alloc_t *bucket_alloc; + worker_args_t *worker_args; + HQUEUE workq; + PID owner; + int rc; + REQUESTDATA rd; + ULONG len; + BYTE priority; + int thread_slot = (int)vpArg; + EXCEPTIONREGISTRATIONRECORD reg_rec = { NULL, thread_exception_handler }; + ap_sb_handle_t *sbh; + + /* Trap exceptions in this thread so we don't take down the whole process */ + DosSetExceptionHandler( ®_rec ); + + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pchild); + + rc = DosOpenQueue(&owner, &workq, + apr_psprintf(pchild, "/queues/httpd/work.%d", getpid())); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00196) + "unable to open work queue, exiting"); + ap_scoreboard_image->servers[child_slot][thread_slot].tid = 0; + } + + conn_id = ID_FROM_CHILD_THREAD(child_slot, thread_slot); + ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_READY, + NULL); + + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + bucket_alloc = apr_bucket_alloc_create_ex(allocator); + + while (rc = DosReadQueue(workq, &rd, &len, (PPVOID)&worker_args, 0, DCWW_WAIT, &priority, NULLHANDLE), + rc == 0 && rd.ulData != WORKTYPE_EXIT) { + pconn = worker_args->pconn; + ap_create_sb_handle(&sbh, pconn, child_slot, thread_slot); + current_conn = ap_run_create_connection(pconn, ap_server_conf, + worker_args->conn_sd, conn_id, + sbh, bucket_alloc); + + if (current_conn) { + current_conn->current_thread = thd; + ap_process_connection(current_conn, worker_args->conn_sd); + ap_lingering_close(current_conn); + } + + apr_pool_destroy(pconn); + ap_update_child_status_from_indexes(child_slot, thread_slot, + SERVER_READY, NULL); + } + + ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_DEAD, + NULL); + + apr_bucket_alloc_destroy(bucket_alloc); + apr_allocator_destroy(allocator); +} + + + +static void server_maintenance(void *vpArg) +{ + int num_idle, num_needed; + ULONG num_pending = 0; + int threadnum; + HQUEUE workq; + ULONG rc; + PID owner; + + rc = DosOpenQueue(&owner, &workq, + apr_psprintf(pchild, "/queues/httpd/work.%d", getpid())); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00197) + "unable to open work queue in maintenance thread"); + return; + } + + do { + for (num_idle=0, threadnum=0; threadnum < HARD_THREAD_LIMIT; threadnum++) { + num_idle += ap_scoreboard_image->servers[child_slot][threadnum].status == SERVER_READY; + } + + DosQueryQueue(workq, &num_pending); + num_needed = ap_min_spare_threads - num_idle + num_pending; + + if (num_needed > 0) { + for (threadnum=0; threadnum < num_needed; threadnum++) { + add_worker(); + } + } + + if (num_idle - num_pending > ap_max_spare_threads) { + DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0); + } + } while (DosWaitEventSem(shutdown_event, 500) == ERROR_TIMEOUT); +} + + + +/* Signal handling routines */ + +static void sig_term(int sig) +{ + shutdown_pending = 1; + is_graceful = 0; + signal(SIGTERM, SIG_DFL); +} + + + +static void sig_hup(int sig) +{ + shutdown_pending = 1; + is_graceful = 1; +} + + + +static void set_signals() +{ + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_term; + + if (sigaction(SIGTERM, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00198) "sigaction(SIGTERM)"); + + sa.sa_handler = sig_hup; + + if (sigaction(SIGHUP, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00199) "sigaction(SIGHUP)"); +} diff --git a/server/mpm/netware/mpm_default.h b/server/mpm/netware/mpm_default.h new file mode 100644 index 0000000..f7783ce --- /dev/null +++ b/server/mpm/netware/mpm_default.h @@ -0,0 +1,78 @@ +/* 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. + */ + +/** + * @file netware/mpm_default.h + * @brief Defaults for Netware MPM + * + * @defgroup APACHE_MPM_NETWARE Netware MPM + * @ingroup APACHE_INTERNAL + * @{ + */ +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Limit on the threads per process. Clients will be locked out if more than + * this * HARD_SERVER_LIMIT are needed. + * + * We keep this for one reason it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 2048 +#endif + +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 50 +#endif + +/* Number of threads to spawn off by default --- also, if fewer than + * this free when the caretaker checks, it will spawn more. + */ +#ifndef DEFAULT_START_THREADS +#define DEFAULT_START_THREADS DEFAULT_THREADS_PER_CHILD +#endif + +/* Maximum number of *free* threads --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_FREE_THREADS +#define DEFAULT_MAX_FREE_THREADS 100 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_FREE_THREADS +#define DEFAULT_MIN_FREE_THREADS 10 +#endif + +/* + * Interval, in microseconds, between scoreboard maintenance. + */ +#ifndef SCOREBOARD_MAINTENANCE_INTERVAL +#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000 +#endif + +/* Default stack size allocated for each worker thread. + */ +#ifndef DEFAULT_THREAD_STACKSIZE +#define DEFAULT_THREAD_STACKSIZE 65536 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/netware/mpm_netware.c b/server/mpm/netware/mpm_netware.c new file mode 100644 index 0000000..8248033 --- /dev/null +++ b/server/mpm/netware/mpm_netware.c @@ -0,0 +1,1364 @@ +/* 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. + */ + +/* + * httpd.c: simple http daemon for answering WWW file requests + * + * + * 03-21-93 Rob McCool wrote original code (up to NCSA HTTPd 1.3) + * + * 03-06-95 blong + * changed server number for child-alone processes to 0 and changed name + * of processes + * + * 03-10-95 blong + * Added numerous speed hacks proposed by Robert S. Thau (rst@ai.mit.edu) + * including set group before fork, and call gettime before to fork + * to set up libraries. + * + * 04-14-95 rst / rh + * Brandon's code snarfed from NCSA 1.4, but tinkered to work with the + * Apache server, and also to have child processes do accept() directly. + * + * April-July '95 rst + * Extensive rework for Apache. + */ + +#include "apr.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_tables.h" +#include "apr_getopt.h" +#include "apr_thread_mutex.h" + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifndef USE_WINSOCK +#include <sys/select.h> +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "scoreboard.h" +#include "ap_mpm.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "ap_mmn.h" + +#ifdef HAVE_TIME_H +#include <time.h> +#endif + +#include <signal.h> + +#include <netware.h> +#include <nks/netware.h> +#include <library.h> +#include <screen.h> + +int nlmUnloadSignaled(int wait); + +/* Limit on the total --- clients will be locked out if more servers than + * this are needed. It is intended solely to keep the server from crashing + * when things get out of hand. + * + * We keep a hard maximum number of servers, for two reasons --- first off, + * in case something goes seriously wrong, we want to stop the fork bomb + * short of actually crashing the machine we're running on by filling some + * kernel table. Secondly, it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef HARD_SERVER_LIMIT +#define HARD_SERVER_LIMIT 1 +#endif + +#define WORKER_DEAD SERVER_DEAD +#define WORKER_STARTING SERVER_STARTING +#define WORKER_READY SERVER_READY +#define WORKER_IDLE_KILL SERVER_IDLE_KILL + +#define MPM_HARD_LIMITS_FILE "/mpm_default.h" + +/* *Non*-shared http_main globals... */ + +static int ap_threads_per_child=0; /* Worker threads per child */ +static int ap_threads_to_start=0; +static int ap_threads_min_free=0; +static int ap_threads_max_free=0; +static int ap_threads_limit=0; +static int mpm_state = AP_MPMQ_STARTING; + +/* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers changes across SIGWINCH restarts. We use this + * value to optimize routines that have to scan the entire scoreboard. + */ +static int ap_max_workers_limit = -1; + +int hold_screen_on_exit = 0; /* Indicates whether the screen should be held open */ + +static fd_set listenfds; +static int listenmaxfd; + +static apr_pool_t *pconf; /* Pool for config stuff */ +static apr_pool_t *pmain; /* Pool for httpd child stuff */ + +static pid_t ap_my_pid; /* it seems silly to call getpid all the time */ +static char *ap_my_addrspace = NULL; + +static int die_now = 0; + +/* Keep track of the number of worker threads currently active */ +static unsigned long worker_thread_count; +static int request_count; + +/* Structure used to register/deregister a console handler with the OS */ +static int InstallConsoleHandler(void); +static void RemoveConsoleHandler(void); +static int CommandLineInterpreter(scr_t screenID, const char *commandLine); +static CommandParser_t ConsoleHandler = {0, NULL, 0}; +#define HANDLEDCOMMAND 0 +#define NOTMYCOMMAND 1 + +static int show_settings = 0; + +//#define DBINFO_ON +//#define DBPRINT_ON +#ifdef DBPRINT_ON +#define DBPRINT0(s) printf(s) +#define DBPRINT1(s,v1) printf(s,v1) +#define DBPRINT2(s,v1,v2) printf(s,v1,v2) +#else +#define DBPRINT0(s) +#define DBPRINT1(s,v1) +#define DBPRINT2(s,v1,v2) +#endif + +/* volatile just in case */ +static int volatile shutdown_pending; +static int volatile restart_pending; +static int volatile is_graceful; +static int volatile wait_to_finish=1; +static ap_generation_t volatile ap_my_generation=0; + +/* a clean exit from a child with proper cleanup */ +static void clean_child_exit(int code, int worker_num, apr_pool_t *ptrans, + apr_bucket_alloc_t *bucket_alloc) __attribute__ ((noreturn)); +static void clean_child_exit(int code, int worker_num, apr_pool_t *ptrans, + apr_bucket_alloc_t *bucket_alloc) +{ + apr_bucket_alloc_destroy(bucket_alloc); + if (!shutdown_pending) { + apr_pool_destroy(ptrans); + } + + atomic_dec (&worker_thread_count); + if (worker_num >=0) + ap_update_child_status_from_indexes(0, worker_num, WORKER_DEAD, + (request_rec *) NULL); + NXThreadExit((void*)&code); +} + +/* proper cleanup when returning from ap_mpm_run() */ +static void mpm_main_cleanup(void) +{ + if (pmain) { + apr_pool_destroy(pmain); + } +} + +static int netware_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch(query_code){ + case AP_MPMQ_MAX_DAEMON_USED: + *result = 1; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_DYNAMIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = HARD_SERVER_LIMIT; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = HARD_THREAD_LIMIT; + break; + case AP_MPMQ_MAX_THREADS: + *result = ap_threads_limit; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = ap_threads_min_free; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = ap_threads_max_free; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = 1; + break; + case AP_MPMQ_MPM_STATE: + *result = mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = ap_my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static const char *netware_get_name(void) +{ + return "NetWare"; +} + +/***************************************************************** + * Connection structures and accounting... + */ + +static void mpm_term(void) +{ + RemoveConsoleHandler(); + wait_to_finish = 0; + NXThreadYield(); +} + +static void sig_term(int sig) +{ + if (shutdown_pending == 1) { + /* Um, is this _probably_ not an error, if the user has + * tried to do a shutdown twice quickly, so we won't + * worry about reporting it. + */ + return; + } + shutdown_pending = 1; + + DBPRINT0 ("waiting for threads\n"); + while (wait_to_finish) { + apr_thread_yield(); + } + DBPRINT0 ("goodbye\n"); +} + +/* restart() is the signal handler for SIGHUP and SIGWINCH + * in the parent process, unless running in ONE_PROCESS mode + */ +static void restart(void) +{ + if (restart_pending == 1) { + /* Probably not an error - don't bother reporting it */ + return; + } + restart_pending = 1; + is_graceful = 1; +} + +static void set_signals(void) +{ + apr_signal(SIGTERM, sig_term); + apr_signal(SIGABRT, sig_term); +} + +int nlmUnloadSignaled(int wait) +{ + shutdown_pending = 1; + + if (wait) { + while (wait_to_finish) { + NXThreadYield(); + } + } + + return 0; +} + +/***************************************************************** + * Child process main loop. + * The following vars are static to avoid getting clobbered by longjmp(); + * they are really private to child_main. + */ + + +#define MAX_WB_RETRIES 3 +#ifdef DBINFO_ON +static int would_block = 0; +static int retry_success = 0; +static int retry_fail = 0; +static int avg_retries = 0; +#endif + +/*static */ +void worker_main(void *arg) +{ + ap_listen_rec *lr, *first_lr, *last_lr = NULL; + apr_pool_t *ptrans; + apr_allocator_t *allocator; + apr_bucket_alloc_t *bucket_alloc; + conn_rec *current_conn; + apr_status_t stat = APR_EINIT; + ap_sb_handle_t *sbh; + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; + + int my_worker_num = (int)arg; + apr_socket_t *csd = NULL; + int requests_this_child = 0; + apr_socket_t *sd = NULL; + fd_set main_fds; + + int sockdes; + int srv; + struct timeval tv; + int wouldblock_retry; + + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pmain); + + tv.tv_sec = 1; + tv.tv_usec = 0; + + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + + apr_pool_create_ex(&ptrans, pmain, NULL, allocator); + apr_allocator_owner_set(allocator, ptrans); + apr_pool_tag(ptrans, "transaction"); + + bucket_alloc = apr_bucket_alloc_create_ex(allocator); + + atomic_inc (&worker_thread_count); + + while (!die_now) { + /* + * (Re)initialize this child to a pre-connection state. + */ + current_conn = NULL; + apr_pool_clear(ptrans); + + if ((ap_max_requests_per_child > 0 + && requests_this_child++ >= ap_max_requests_per_child)) { + DBPRINT1 ("\n**Thread slot %d is shutting down", my_worker_num); + clean_child_exit(0, my_worker_num, ptrans, bucket_alloc); + } + + ap_update_child_status_from_indexes(0, my_worker_num, WORKER_READY, + (request_rec *) NULL); + + /* + * Wait for an acceptable connection to arrive. + */ + + for (;;) { + if (shutdown_pending || restart_pending || (ap_scoreboard_image->servers[0][my_worker_num].status == WORKER_IDLE_KILL)) { + DBPRINT1 ("\nThread slot %d is shutting down\n", my_worker_num); + clean_child_exit(0, my_worker_num, ptrans, bucket_alloc); + } + + /* Check the listen queue on all sockets for requests */ + memcpy(&main_fds, &listenfds, sizeof(fd_set)); + srv = select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv); + + if (srv <= 0) { + if (srv < 0) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00217) + "select() failed on listen socket"); + apr_thread_yield(); + } + continue; + } + + /* remember the last_lr we searched last time around so that + we don't end up starving any particular listening socket */ + if (last_lr == NULL) { + lr = ap_listeners; + } + else { + lr = last_lr->next; + if (!lr) + lr = ap_listeners; + } + first_lr = lr; + do { + apr_os_sock_get(&sockdes, lr->sd); + if (FD_ISSET(sockdes, &main_fds)) + goto got_listener; + lr = lr->next; + if (!lr) + lr = ap_listeners; + } while (lr != first_lr); + /* if we get here, something unexpected happened. Go back + into the select state and try again. + */ + continue; + got_listener: + last_lr = lr; + sd = lr->sd; + + wouldblock_retry = MAX_WB_RETRIES; + + while (wouldblock_retry) { + if ((stat = apr_socket_accept(&csd, sd, ptrans)) == APR_SUCCESS) { + break; + } + else { + /* if the error is a wouldblock then maybe we were too + quick try to pull the next request from the listen + queue. Try a few more times then return to our idle + listen state. */ + if (!APR_STATUS_IS_EAGAIN(stat)) { + break; + } + + if (wouldblock_retry--) { + apr_thread_yield(); + } + } + } + + /* If we got a new socket, set it to non-blocking mode and process + it. Otherwise handle the error. */ + if (stat == APR_SUCCESS) { + apr_socket_opt_set(csd, APR_SO_NONBLOCK, 0); +#ifdef DBINFO_ON + if (wouldblock_retry < MAX_WB_RETRIES) { + retry_success++; + avg_retries += (MAX_WB_RETRIES-wouldblock_retry); + } +#endif + break; /* We have a socket ready for reading */ + } + else { +#ifdef DBINFO_ON + if (APR_STATUS_IS_EAGAIN(stat)) { + would_block++; + retry_fail++; + } + else if ( +#else + if (APR_STATUS_IS_EAGAIN(stat) || +#endif + APR_STATUS_IS_ECONNRESET(stat) || + APR_STATUS_IS_ETIMEDOUT(stat) || + APR_STATUS_IS_EHOSTUNREACH(stat) || + APR_STATUS_IS_ENETUNREACH(stat)) { + ; + } +#ifdef USE_WINSOCK + else if (APR_STATUS_IS_ENETDOWN(stat)) { + /* + * When the network layer has been shut down, there + * is not much use in simply exiting: the parent + * would simply re-create us (and we'd fail again). + * Use the CHILDFATAL code to tear the server down. + * @@@ Martin's idea for possible improvement: + * A different approach would be to define + * a new APEXIT_NETDOWN exit code, the reception + * of which would make the parent shutdown all + * children, then idle-loop until it detected that + * the network is up again, and restart the children. + * Ben Hyde noted that temporary ENETDOWN situations + * occur in mobile IP. + */ + ap_log_error(APLOG_MARK, APLOG_EMERG, stat, ap_server_conf, APLOGNO(00218) + "apr_socket_accept: giving up."); + clean_child_exit(APEXIT_CHILDFATAL, my_worker_num, ptrans, + bucket_alloc); + } +#endif + else { + ap_log_error(APLOG_MARK, APLOG_ERR, stat, ap_server_conf, APLOGNO(00219) + "apr_socket_accept: (client socket)"); + clean_child_exit(1, my_worker_num, ptrans, bucket_alloc); + } + } + } + + ap_create_sb_handle(&sbh, ptrans, 0, my_worker_num); + /* + * We now have a connection, so set it up with the appropriate + * socket options, file descriptors, and read/write buffers. + */ + current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, + my_worker_num, sbh, + bucket_alloc); + if (current_conn) { + current_conn->current_thread = thd; + ap_process_connection(current_conn, csd); + ap_lingering_close(current_conn); + } + request_count++; + } + clean_child_exit(0, my_worker_num, ptrans, bucket_alloc); +} + + +static int make_child(server_rec *s, int slot) +{ + int tid; + int err=0; + NXContext_t ctx; + + if (slot + 1 > ap_max_workers_limit) { + ap_max_workers_limit = slot + 1; + } + + ap_update_child_status_from_indexes(0, slot, WORKER_STARTING, + (request_rec *) NULL); + + if (ctx = NXContextAlloc((void (*)(void *)) worker_main, (void*)slot, NX_PRIO_MED, ap_thread_stacksize, NX_CTX_NORMAL, &err)) { + char threadName[32]; + + sprintf (threadName, "Apache_Worker %d", slot); + NXContextSetName(ctx, threadName); + err = NXThreadCreate(ctx, NX_THR_BIND_CONTEXT, &tid); + if (err) { + NXContextFree (ctx); + } + } + + if (err) { + /* create thread didn't succeed. Fix the scoreboard or else + * it will say SERVER_STARTING forever and ever + */ + ap_update_child_status_from_indexes(0, slot, WORKER_DEAD, + (request_rec *) NULL); + + /* In case system resources are maxxed out, we don't want + Apache running away with the CPU trying to fork over and + over and over again. */ + apr_thread_yield(); + + return -1; + } + + ap_scoreboard_image->servers[0][slot].tid = tid; + + return 0; +} + + +/* start up a bunch of worker threads */ +static void startup_workers(int number_to_start) +{ + int i; + + for (i = 0; number_to_start && i < ap_threads_limit; ++i) { + if (ap_scoreboard_image->servers[0][i].status != WORKER_DEAD) { + continue; + } + if (make_child(ap_server_conf, i) < 0) { + break; + } + --number_to_start; + } +} + + +/* + * idle_spawn_rate is the number of children that will be spawned on the + * next maintenance cycle if there aren't enough idle servers. It is + * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by + * without the need to spawn. + */ +static int idle_spawn_rate = 1; +#ifndef MAX_SPAWN_RATE +#define MAX_SPAWN_RATE (64) +#endif +static int hold_off_on_exponential_spawning; + +static void perform_idle_server_maintenance(apr_pool_t *p) +{ + int i; + int idle_count; + worker_score *ws; + int free_length; + int free_slots[MAX_SPAWN_RATE]; + int last_non_dead; + int total_non_dead; + + /* initialize the free_list */ + free_length = 0; + + idle_count = 0; + last_non_dead = -1; + total_non_dead = 0; + + for (i = 0; i < ap_threads_limit; ++i) { + int status; + + if (i >= ap_max_workers_limit && free_length == idle_spawn_rate) + break; + ws = &ap_scoreboard_image->servers[0][i]; + status = ws->status; + if (status == WORKER_DEAD) { + /* try to keep children numbers as low as possible */ + if (free_length < idle_spawn_rate) { + free_slots[free_length] = i; + ++free_length; + } + } + else if (status == WORKER_IDLE_KILL) { + /* If it is already marked to die, skip it */ + continue; + } + else { + /* We consider a starting server as idle because we started it + * at least a cycle ago, and if it still hasn't finished starting + * then we're just going to swamp things worse by forking more. + * So we hopefully won't need to fork more if we count it. + * This depends on the ordering of SERVER_READY and SERVER_STARTING. + */ + if (status <= WORKER_READY) { + ++ idle_count; + } + + ++total_non_dead; + last_non_dead = i; + } + } + DBPRINT2("Total: %d Idle Count: %d \r", total_non_dead, idle_count); + ap_max_workers_limit = last_non_dead + 1; + if (idle_count > ap_threads_max_free) { + /* kill off one child... we use the pod because that'll cause it to + * shut down gracefully, in case it happened to pick up a request + * while we were counting + */ + idle_spawn_rate = 1; + ap_update_child_status_from_indexes(0, last_non_dead, WORKER_IDLE_KILL, + (request_rec *) NULL); + DBPRINT1("\nKilling idle thread: %d\n", last_non_dead); + } + else if (idle_count < ap_threads_min_free) { + /* terminate the free list */ + if (free_length == 0) { + /* only report this condition once */ + static int reported = 0; + + if (!reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00220) + "server reached MaxRequestWorkers setting, consider" + " raising the MaxRequestWorkers setting"); + reported = 1; + } + idle_spawn_rate = 1; + } + else { + if (idle_spawn_rate >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00221) + "server seems busy, (you may need " + "to increase StartServers, or Min/MaxSpareServers), " + "spawning %d children, there are %d idle, and " + "%d total children", idle_spawn_rate, + idle_count, total_non_dead); + } + DBPRINT0("\n"); + for (i = 0; i < free_length; ++i) { + DBPRINT1("Spawning additional thread slot: %d\n", free_slots[i]); + make_child(ap_server_conf, free_slots[i]); + } + /* the next time around we want to spawn twice as many if this + * wasn't good enough, but not if we've just done a graceful + */ + if (hold_off_on_exponential_spawning) { + --hold_off_on_exponential_spawning; + } + else if (idle_spawn_rate < MAX_SPAWN_RATE) { + idle_spawn_rate *= 2; + } + } + } + else { + idle_spawn_rate = 1; + } +} + +static void display_settings() +{ + int status_array[SERVER_NUM_STATUS]; + int i, status, total=0; + int reqs = request_count; +#ifdef DBINFO_ON + int wblock = would_block; + + would_block = 0; +#endif + + request_count = 0; + + ClearScreen (getscreenhandle()); + printf("%s \n", ap_get_server_description()); + + for (i=0;i<SERVER_NUM_STATUS;i++) { + status_array[i] = 0; + } + + for (i = 0; i < ap_threads_limit; ++i) { + status = (ap_scoreboard_image->servers[0][i]).status; + status_array[status]++; + } + + for (i=0;i<SERVER_NUM_STATUS;i++) { + switch(i) + { + case SERVER_DEAD: + printf ("Available:\t%d\n", status_array[i]); + break; + case SERVER_STARTING: + printf ("Starting:\t%d\n", status_array[i]); + break; + case SERVER_READY: + printf ("Ready:\t\t%d\n", status_array[i]); + break; + case SERVER_BUSY_READ: + printf ("Busy:\t\t%d\n", status_array[i]); + break; + case SERVER_BUSY_WRITE: + printf ("Busy Write:\t%d\n", status_array[i]); + break; + case SERVER_BUSY_KEEPALIVE: + printf ("Busy Keepalive:\t%d\n", status_array[i]); + break; + case SERVER_BUSY_LOG: + printf ("Busy Log:\t%d\n", status_array[i]); + break; + case SERVER_BUSY_DNS: + printf ("Busy DNS:\t%d\n", status_array[i]); + break; + case SERVER_CLOSING: + printf ("Closing:\t%d\n", status_array[i]); + break; + case SERVER_GRACEFUL: + printf ("Restart:\t%d\n", status_array[i]); + break; + case SERVER_IDLE_KILL: + printf ("Idle Kill:\t%d\n", status_array[i]); + break; + default: + printf ("Unknown Status:\t%d\n", status_array[i]); + break; + } + if (i != SERVER_DEAD) + total+=status_array[i]; + } + printf ("Total Running:\t%d\tout of: \t%d\n", total, ap_threads_limit); + printf ("Requests per interval:\t%d\n", reqs); + +#ifdef DBINFO_ON + printf ("Would blocks:\t%d\n", wblock); + printf ("Successful retries:\t%d\n", retry_success); + printf ("Failed retries:\t%d\n", retry_fail); + printf ("Avg retries:\t%d\n", retry_success == 0 ? 0 : avg_retries / retry_success); +#endif +} + +static void show_server_data() +{ + ap_listen_rec *lr; + module **m; + + printf("%s\n", ap_get_server_description()); + if (ap_my_addrspace && (ap_my_addrspace[0] != 'O') && (ap_my_addrspace[1] != 'S')) + printf(" Running in address space %s\n", ap_my_addrspace); + + + /* Display listening ports */ + printf(" Listening on port(s):"); + lr = ap_listeners; + do { + printf(" %d", lr->bind_addr->port); + lr = lr->next; + } while (lr && lr != ap_listeners); + + /* Display dynamic modules loaded */ + printf("\n"); + for (m = ap_loaded_modules; *m != NULL; m++) { + if (((module*)*m)->dynamic_load_handle) { + printf(" Loaded dynamic module %s\n", ((module*)*m)->name); + } + } +} + + +static int setup_listeners(server_rec *s) +{ + ap_listen_rec *lr; + int sockdes; + + if (ap_setup_listeners(s) < 1 ) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00222) + "no listening sockets available, shutting down"); + return -1; + } + + listenmaxfd = -1; + FD_ZERO(&listenfds); + for (lr = ap_listeners; lr; lr = lr->next) { + apr_os_sock_get(&sockdes, lr->sd); + FD_SET(sockdes, &listenfds); + if (sockdes > listenmaxfd) { + listenmaxfd = sockdes; + } + } + return 0; +} + +static int shutdown_listeners() +{ + ap_listen_rec *lr; + + for (lr = ap_listeners; lr; lr = lr->next) { + apr_socket_close(lr->sd); + } + ap_listeners = NULL; + return 0; +} + +/***************************************************************** + * Executive routines. + */ + +static int netware_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +{ + apr_status_t status=0; + + pconf = _pconf; + ap_server_conf = s; + + if (setup_listeners(s)) { + ap_log_error(APLOG_MARK, APLOG_ALERT, status, s, APLOGNO(00223) + "no listening sockets available, shutting down"); + return !OK; + } + + restart_pending = shutdown_pending = 0; + worker_thread_count = 0; + + if (!is_graceful) { + if (ap_run_pre_mpm(s->process->pool, SB_NOT_SHARED) != OK) { + return !OK; + } + } + + /* Only set slot 0 since that is all NetWare will ever have. */ + ap_scoreboard_image->parent[0].pid = getpid(); + ap_scoreboard_image->parent[0].generation = ap_my_generation; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[0].pid, + ap_my_generation, + 0, + MPM_CHILD_STARTED); + + set_signals(); + + apr_pool_create(&pmain, pconf); + ap_run_child_init(pmain, ap_server_conf); + + if (ap_threads_max_free < ap_threads_min_free + 1) /* Don't thrash... */ + ap_threads_max_free = ap_threads_min_free + 1; + request_count = 0; + + startup_workers(ap_threads_to_start); + + /* Allow the Apache screen to be closed normally on exit() only if it + has not been explicitly forced to close on exit(). (ie. the -E flag + was specified at startup) */ + if (hold_screen_on_exit > 0) { + hold_screen_on_exit = 0; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00224) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00225) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + show_server_data(); + + mpm_state = AP_MPMQ_RUNNING; + while (!restart_pending && !shutdown_pending) { + perform_idle_server_maintenance(pconf); + if (show_settings) + display_settings(); + apr_thread_yield(); + apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL); + } + mpm_state = AP_MPMQ_STOPPING; + + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[0].pid, + ap_my_generation, + 0, + MPM_CHILD_EXITED); + + /* Shutdown the listen sockets so that we don't get stuck in a blocking call. + shutdown_listeners();*/ + + if (shutdown_pending) { /* Got an unload from the console */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00226) + "caught SIGTERM, shutting down"); + + while (worker_thread_count > 0) { + printf ("\rShutdown pending. Waiting for %lu thread(s) to terminate...", + worker_thread_count); + apr_thread_yield(); + } + + mpm_main_cleanup(); + return DONE; + } + else { /* the only other way out is a restart */ + /* advance to the next generation */ + /* XXX: we really need to make sure this new generation number isn't in + * use by any of the children. + */ + ++ap_my_generation; + ap_scoreboard_image->global->running_generation = ap_my_generation; + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00227) + "Graceful restart requested, doing restart"); + + /* Wait for all of the threads to terminate before initiating the restart */ + while (worker_thread_count > 0) { + printf ("\rRestart pending. Waiting for %lu thread(s) to terminate...", + worker_thread_count); + apr_thread_yield(); + } + printf ("\nRestarting...\n"); + } + + mpm_main_cleanup(); + return OK; +} + +static int netware_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) +{ + char *addrname = NULL; + + mpm_state = AP_MPMQ_STARTING; + + is_graceful = 0; + ap_my_pid = getpid(); + addrname = getaddressspacename (NULL, NULL); + if (addrname) { + ap_my_addrspace = apr_pstrdup (p, addrname); + free (addrname); + } + +#ifndef USE_WINSOCK + /* The following call has been moved to the mod_nw_ssl pre-config handler */ + ap_listen_pre_config(); +#endif + + ap_threads_to_start = DEFAULT_START_THREADS; + ap_threads_min_free = DEFAULT_MIN_FREE_THREADS; + ap_threads_max_free = DEFAULT_MAX_FREE_THREADS; + ap_threads_limit = HARD_THREAD_LIMIT; + ap_extended_status = 0; + + /* override core's default thread stacksize */ + ap_thread_stacksize = DEFAULT_THREAD_STACKSIZE; + + return OK; +} + +static int netware_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + static int restart_num = 0; + int startup = 0; + + /* we want this only the first time around */ + if (restart_num++ == 0) { + startup = 1; + } + + if (ap_threads_limit > HARD_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00228) + "WARNING: MaxThreads of %d exceeds compile-time " + "limit of %d threads, decreasing to %d. " + "To increase, please see the HARD_THREAD_LIMIT " + "define in server/mpm/netware%s.", + ap_threads_limit, HARD_THREAD_LIMIT, HARD_THREAD_LIMIT, + MPM_HARD_LIMITS_FILE); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00229) + "MaxThreads of %d exceeds compile-time limit " + "of %d, decreasing to match", + ap_threads_limit, HARD_THREAD_LIMIT); + } + ap_threads_limit = HARD_THREAD_LIMIT; + } + else if (ap_threads_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00230) + "WARNING: MaxThreads of %d not allowed, " + "increasing to 1.", ap_threads_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02661) + "MaxThreads of %d not allowed, increasing to 1", + ap_threads_limit); + } + ap_threads_limit = 1; + } + + /* ap_threads_to_start > ap_threads_limit effectively checked in + * call to startup_workers(ap_threads_to_start) in ap_mpm_run() + */ + if (ap_threads_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00231) + "WARNING: StartThreads of %d not allowed, " + "increasing to 1.", ap_threads_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00232) + "StartThreads of %d not allowed, increasing to 1", + ap_threads_to_start); + } + ap_threads_to_start = 1; + } + + if (ap_threads_min_free < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00233) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1 to avoid almost certain server failure. " + "Please read the documentation.", ap_threads_min_free); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00234) + "MinSpareThreads of %d not allowed, increasing to 1", + ap_threads_min_free); + } + ap_threads_min_free = 1; + } + + /* ap_threads_max_free < ap_threads_min_free + 1 checked in ap_mpm_run() */ + + return OK; +} + +static void netware_mpm_hooks(apr_pool_t *p) +{ + /* Run the pre-config hook after core's so that it can override the + * default setting of ThreadStackSize for NetWare. + */ + static const char * const predecessors[] = {"core.c", NULL}; + + ap_hook_pre_config(netware_pre_config, predecessors, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(netware_check_config, NULL, NULL, APR_HOOK_MIDDLE); + //ap_hook_post_config(netware_post_config, NULL, NULL, 0); + //ap_hook_child_init(netware_child_init, NULL, NULL, APR_HOOK_MIDDLE); + //ap_hook_open_logs(netware_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + ap_hook_mpm(netware_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(netware_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(netware_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +static void netware_rewrite_args(process_rec *process) +{ + char *def_server_root; + char optbuf[3]; + const char *opt_arg; + apr_getopt_t *opt; + apr_array_header_t *mpm_new_argv; + + + atexit (mpm_term); + InstallConsoleHandler(); + + /* Make sure to hold the Apache screen open if exit() is called */ + hold_screen_on_exit = 1; + + /* Rewrite process->argv[]; + * + * add default -d serverroot from the path of this executable + * + * The end result will look like: + * The -d serverroot default from the running executable + */ + if (process->argc > 0) { + char *s = apr_pstrdup (process->pconf, process->argv[0]); + if (s) { + int i, len = strlen(s); + + for (i=len; i; i--) { + if (s[i] == '\\' || s[i] == '/') { + s[i] = '\0'; + apr_filepath_merge(&def_server_root, NULL, s, + APR_FILEPATH_TRUENAME, process->pool); + break; + } + } + /* Use process->pool so that the rewritten argv + * lasts for the lifetime of the server process, + * because pconf will be destroyed after the + * initial pre-flight of the config parser. + */ + mpm_new_argv = apr_array_make(process->pool, process->argc + 2, + sizeof(const char *)); + *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; + *(const char **)apr_array_push(mpm_new_argv) = "-d"; + *(const char **)apr_array_push(mpm_new_argv) = def_server_root; + + optbuf[0] = '-'; + optbuf[2] = '\0'; + apr_getopt_init(&opt, process->pool, process->argc, process->argv); + while (apr_getopt(opt, AP_SERVER_BASEARGS"n:", optbuf + 1, &opt_arg) == APR_SUCCESS) { + switch (optbuf[1]) { + case 'n': + if (opt_arg) { + renamescreen(opt_arg); + } + break; + case 'E': + /* Don't need to hold the screen open if the output is going to a file */ + hold_screen_on_exit = -1; + default: + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, optbuf); + + if (opt_arg) { + *(const char **)apr_array_push(mpm_new_argv) = opt_arg; + } + break; + } + } + process->argc = mpm_new_argv->nelts; + process->argv = (const char * const *) mpm_new_argv->elts; + } + } +} + +static int CommandLineInterpreter(scr_t screenID, const char *commandLine) +{ + char *szCommand = "APACHE2 "; + int iCommandLen = 8; + char szcommandLine[256]; + char *pID; + screenID = screenID; + + + if (commandLine == NULL) + return NOTMYCOMMAND; + if (strlen(commandLine) <= strlen(szCommand)) + return NOTMYCOMMAND; + + apr_cpystrn(szcommandLine, commandLine, sizeof(szcommandLine)); + + /* All added commands begin with "APACHE2 " */ + + if (!strnicmp(szCommand, szcommandLine, iCommandLen)) { + ActivateScreen (getscreenhandle()); + + /* If an instance id was not given but the nlm is loaded in + protected space, then the command belongs to the + OS address space instance to pass it on. */ + pID = strstr (szcommandLine, "-p"); + if ((pID == NULL) && nlmisloadedprotected()) + return NOTMYCOMMAND; + + /* If we got an instance id but it doesn't match this + instance of the nlm, pass it on. */ + if (pID) { + pID = &pID[2]; + while (*pID && (*pID == ' ')) + pID++; + } + if (pID && ap_my_addrspace && strnicmp(pID, ap_my_addrspace, strlen(ap_my_addrspace))) + return NOTMYCOMMAND; + + /* If we have determined that this command belongs to this + instance of the nlm, then handle it. */ + if (!strnicmp("RESTART",&szcommandLine[iCommandLen],3)) { + printf("Restart Requested...\n"); + restart(); + } + else if (!strnicmp("VERSION",&szcommandLine[iCommandLen],3)) { + printf("Server version: %s\n", ap_get_server_description()); + printf("Server built: %s\n", ap_get_server_built()); + } + else if (!strnicmp("MODULES",&szcommandLine[iCommandLen],3)) { + ap_show_modules(); + } + else if (!strnicmp("DIRECTIVES",&szcommandLine[iCommandLen],3)) { + ap_show_directives(); + } + else if (!strnicmp("SHUTDOWN",&szcommandLine[iCommandLen],3)) { + printf("Shutdown Requested...\n"); + shutdown_pending = 1; + } + else if (!strnicmp("SETTINGS",&szcommandLine[iCommandLen],3)) { + if (show_settings) { + show_settings = 0; + ClearScreen (getscreenhandle()); + show_server_data(); + } + else { + show_settings = 1; + display_settings(); + } + } + else { + show_settings = 0; + if (strnicmp("HELP",&szcommandLine[iCommandLen],3)) + printf("Unknown APACHE2 command %s\n", &szcommandLine[iCommandLen]); + printf("Usage: APACHE2 [command] [-p <instance ID>]\n"); + printf("Commands:\n"); + printf("\tDIRECTIVES - Show directives\n"); + printf("\tHELP - Display this help information\n"); + printf("\tMODULES - Show a list of the loaded modules\n"); + printf("\tRESTART - Reread the configuration file and restart Apache\n"); + printf("\tSETTINGS - Show current thread status\n"); + printf("\tSHUTDOWN - Shutdown Apache\n"); + printf("\tVERSION - Display the server version information\n"); + } + + /* Tell NetWare we handled the command */ + return HANDLEDCOMMAND; + } + + /* Tell NetWare that the command isn't mine */ + return NOTMYCOMMAND; +} + +static int InstallConsoleHandler(void) +{ + /* Our command line handler interfaces the system operator + with this NLM */ + + NX_WRAP_INTERFACE(CommandLineInterpreter, 2, (void*)&(ConsoleHandler.parser)); + + ConsoleHandler.rTag = AllocateResourceTag(getnlmhandle(), "Command Line Processor", + ConsoleCommandSignature); + if (!ConsoleHandler.rTag) + { + printf("Error on allocate resource tag\n"); + return 1; + } + + RegisterConsoleCommand(&ConsoleHandler); + + /* The Remove procedure unregisters the console handler */ + + return 0; +} + +static void RemoveConsoleHandler(void) +{ + UnRegisterConsoleCommand(&ConsoleHandler); + NX_UNWRAP_INTERFACE(ConsoleHandler.parser); +} + +static const char *set_threads_to_start(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_to_start = atoi(arg); + return NULL; +} + +static const char *set_min_free_threads(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_min_free = atoi(arg); + return NULL; +} + +static const char *set_max_free_threads(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_max_free = atoi(arg); + return NULL; +} + +static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_limit = atoi(arg); + return NULL; +} + +static const command_rec netware_mpm_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("StartThreads", set_threads_to_start, NULL, RSRC_CONF, + "Number of worker threads launched at server startup"), +AP_INIT_TAKE1("MinSpareThreads", set_min_free_threads, NULL, RSRC_CONF, + "Minimum number of idle threads, to handle request spikes"), +AP_INIT_TAKE1("MaxSpareThreads", set_max_free_threads, NULL, RSRC_CONF, + "Maximum number of idle threads"), +AP_INIT_TAKE1("MaxThreads", set_thread_limit, NULL, RSRC_CONF, + "Maximum number of worker threads alive at the same time"), +{ NULL } +}; + +AP_DECLARE_MODULE(mpm_netware) = { + MPM20_MODULE_STUFF, + netware_rewrite_args, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + netware_mpm_cmds, /* command apr_table_t */ + netware_mpm_hooks, /* register hooks */ +}; diff --git a/server/mpm/prefork/Makefile.in b/server/mpm/prefork/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/prefork/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/prefork/config.m4 b/server/mpm/prefork/config.m4 new file mode 100644 index 0000000..296f834 --- /dev/null +++ b/server/mpm/prefork/config.m4 @@ -0,0 +1,7 @@ +AC_MSG_CHECKING(if prefork MPM supports this platform) +if test $forking_mpms_supported != yes; then + AC_MSG_RESULT(no - This is not a forking platform) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(prefork, yes, no) +fi diff --git a/server/mpm/prefork/config3.m4 b/server/mpm/prefork/config3.m4 new file mode 100644 index 0000000..25fd8df --- /dev/null +++ b/server/mpm/prefork/config3.m4 @@ -0,0 +1 @@ +APACHE_MPM_MODULE(prefork, $enable_mpm_prefork) diff --git a/server/mpm/prefork/mpm_default.h b/server/mpm/prefork/mpm_default.h new file mode 100644 index 0000000..55b038b --- /dev/null +++ b/server/mpm/prefork/mpm_default.h @@ -0,0 +1,51 @@ +/* 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. + */ + +/** + * @file prefork/mpm_default.h + * @brief Prefork MPM defaults + * + * @defgroup APACHE_MPM_PREFORK Prefork MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Number of servers to spawn off by default --- also, if fewer than + * this free when the caretaker checks, it will spawn more. + */ +#ifndef DEFAULT_START_DAEMON +#define DEFAULT_START_DAEMON 5 +#endif + +/* Maximum number of *free* server processes --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_FREE_DAEMON +#define DEFAULT_MAX_FREE_DAEMON 10 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_FREE_DAEMON +#define DEFAULT_MIN_FREE_DAEMON 5 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c new file mode 100644 index 0000000..8efda72 --- /dev/null +++ b/server/mpm/prefork/prefork.c @@ -0,0 +1,1513 @@ +/* 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 "apr.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "scoreboard.h" +#include "ap_mpm.h" +#include "util_mutex.h" +#include "unixd.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "ap_mmn.h" +#include "apr_poll.h" + +#include <stdlib.h> + +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#ifdef HAVE_SYS_PROCESSOR_H +#include <sys/processor.h> /* for bindprocessor() */ +#endif + +#include <signal.h> +#include <sys/times.h> + +/* Limit on the total --- clients will be locked out if more servers than + * this are needed. It is intended solely to keep the server from crashing + * when things get out of hand. + * + * We keep a hard maximum number of servers, for two reasons --- first off, + * in case something goes seriously wrong, we want to stop the fork bomb + * short of actually crashing the machine we're running on by filling some + * kernel table. Secondly, it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_SERVER_LIMIT +#define DEFAULT_SERVER_LIMIT 256 +#endif + +/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_SERVER_LIMIT +#define MAX_SERVER_LIMIT 200000 +#endif + +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 1 +#endif + +/* config globals */ + +static int ap_daemons_to_start=0; +static int ap_daemons_min_free=0; +static int ap_daemons_max_free=0; +static int ap_daemons_limit=0; /* MaxRequestWorkers */ +static int server_limit = 0; + +/* data retained by prefork across load/unload of the module + * allocated on first call to pre-config hook; located on + * subsequent calls to pre-config hook + */ +typedef struct prefork_retained_data { + ap_unixd_mpm_retained_data *mpm; + + int first_server_limit; + int maxclients_reported; + /* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers changes across AP_SIG_GRACEFUL restarts. We + * use this value to optimize routines that have to scan the entire scoreboard. + */ + int max_daemons_limit; + /* + * idle_spawn_rate is the number of children that will be spawned on the + * next maintenance cycle if there aren't enough idle servers. It is + * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by + * without the need to spawn. + */ + int idle_spawn_rate; +#ifndef MAX_SPAWN_RATE +#define MAX_SPAWN_RATE (32) +#endif + int hold_off_on_exponential_spawning; +} prefork_retained_data; +static prefork_retained_data *retained; + +typedef struct prefork_child_bucket { + ap_pod_t *pod; + ap_listen_rec *listeners; + apr_proc_mutex_t *mutex; +} prefork_child_bucket; +static prefork_child_bucket *all_buckets, /* All listeners buckets */ + *my_bucket; /* Current child bucket */ + +#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid) + +/* one_process --- debugging mode variable; can be set from the command line + * with the -X flag. If set, this gets you the child_main loop running + * in the process which originally started up (no detach, no make_child), + * which is a pretty nice debugging environment. (You'll get a SIGHUP + * early in standalone_main; just continue through. This is the server + * trying to kill off any child processes which it might have lying + * around --- Apache doesn't keep track of their pids, it just sends + * SIGHUP to the process group, ignoring it in the root process. + * Continue through and you'll be fine.). + */ + +static int one_process = 0; + +static apr_pool_t *pconf; /* Pool for config stuff */ +static apr_pool_t *pchild; /* Pool for httpd child stuff */ + +static pid_t ap_my_pid; /* it seems silly to call getpid all the time */ +static pid_t parent_pid; +static int my_child_num; + +#ifdef GPROF +/* + * change directory for gprof to plop the gmon.out file + * configure in httpd.conf: + * GprofDir $RuntimeDir/ -> $ServerRoot/$RuntimeDir/gmon.out + * GprofDir $RuntimeDir/% -> $ServerRoot/$RuntimeDir/gprof.$pid/gmon.out + */ +static void chdir_for_gprof(void) +{ + core_server_config *sconf = + ap_get_core_module_config(ap_server_conf->module_config); + char *dir = sconf->gprof_dir; + const char *use_dir; + + if(dir) { + apr_status_t res; + char *buf = NULL ; + int len = strlen(sconf->gprof_dir) - 1; + if(*(dir + len) == '%') { + dir[len] = '\0'; + buf = ap_append_pid(pconf, dir, "gprof."); + } + use_dir = ap_server_root_relative(pconf, buf ? buf : dir); + res = apr_dir_make(use_dir, + APR_UREAD | APR_UWRITE | APR_UEXECUTE | + APR_GREAD | APR_GEXECUTE | + APR_WREAD | APR_WEXECUTE, pconf); + if(res != APR_SUCCESS && !APR_STATUS_IS_EEXIST(res)) { + ap_log_error(APLOG_MARK, APLOG_ERR, res, ap_server_conf, APLOGNO(00142) + "gprof: error creating directory %s", dir); + } + } + else { + use_dir = ap_runtime_dir_relative(pconf, ""); + } + + chdir(use_dir); +} +#else +#define chdir_for_gprof() +#endif + +static void prefork_note_child_killed(int childnum, pid_t pid, + ap_generation_t gen) +{ + AP_DEBUG_ASSERT(childnum != -1); /* no scoreboard squatting with this MPM */ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[childnum].pid, + ap_scoreboard_image->parent[childnum].generation, + childnum, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[childnum].pid = 0; +} + +static void prefork_note_child_started(int slot, pid_t pid) +{ + ap_generation_t gen = retained->mpm->my_generation; + ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].generation = gen; + ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_STARTED); +} + +/* a clean exit from a child with proper cleanup */ +static void clean_child_exit(int code) __attribute__ ((noreturn)); +static void clean_child_exit(int code) +{ + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + apr_signal(SIGHUP, SIG_IGN); + apr_signal(SIGTERM, SIG_IGN); + + if (pchild) { + apr_pool_destroy(pchild); + } + + if (one_process) { + prefork_note_child_killed(/* slot */ 0, 0, 0); + } + + ap_mpm_pod_close(my_bucket->pod); + chdir_for_gprof(); + exit(code); +} + +static apr_status_t accept_mutex_on(void) +{ + apr_status_t rv = apr_proc_mutex_lock(my_bucket->mutex); + if (rv != APR_SUCCESS) { + const char *msg = "couldn't grab the accept mutex"; + + if (retained->mpm->my_generation != + ap_scoreboard_image->global->running_generation) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00143) "%s", msg); + clean_child_exit(0); + } + else { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00144) "%s", msg); + exit(APEXIT_CHILDFATAL); + } + } + return APR_SUCCESS; +} + +static apr_status_t accept_mutex_off(void) +{ + apr_status_t rv = apr_proc_mutex_unlock(my_bucket->mutex); + if (rv != APR_SUCCESS) { + const char *msg = "couldn't release the accept mutex"; + + if (retained->mpm->my_generation != + ap_scoreboard_image->global->running_generation) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00145) "%s", msg); + /* don't exit here... we have a connection to + * process, after which point we'll see that the + * generation changed and we'll exit cleanly + */ + } + else { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00146) "%s", msg); + exit(APEXIT_CHILDFATAL); + } + } + return APR_SUCCESS; +} + +/* On some architectures it's safe to do unserialized accept()s in the single + * Listen case. But it's never safe to do it in the case where there's + * multiple Listen statements. Define SINGLE_LISTEN_UNSERIALIZED_ACCEPT + * when it's safe in the single Listen case. + */ +#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT +#define SAFE_ACCEPT(stmt) (ap_listeners->next ? (stmt) : APR_SUCCESS) +#else +#define SAFE_ACCEPT(stmt) (stmt) +#endif + +static int prefork_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch(query_code){ + case AP_MPMQ_MAX_DAEMON_USED: + *result = ap_daemons_limit; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_DYNAMIC; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = server_limit; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = HARD_THREAD_LIMIT; + break; + case AP_MPMQ_MAX_THREADS: + *result = 1; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = ap_daemons_min_free; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = ap_daemons_max_free; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = ap_daemons_limit; + break; + case AP_MPMQ_MPM_STATE: + *result = retained->mpm->mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = retained->mpm->my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static const char *prefork_get_name(void) +{ + return "prefork"; +} + +/***************************************************************** + * Connection structures and accounting... + */ + +static void just_die(int sig) +{ + clean_child_exit(0); +} + +/* volatile because it's updated from a signal handler */ +static int volatile die_now = 0; + +static void stop_listening(int sig) +{ + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + ap_close_listeners_ex(my_bucket->listeners); + + /* For a graceful stop, we want the child to exit when done */ + die_now = 1; +} + +/***************************************************************** + * Child process main loop. + * The following vars are static to avoid getting clobbered by longjmp(); + * they are really private to child_main. + */ + +static int requests_this_child; +static int num_listensocks = 0; + +static void child_main(int child_num_arg, int child_bucket) +{ +#if APR_HAS_THREADS + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; +#endif + apr_pool_t *ptrans; + apr_allocator_t *allocator; + apr_status_t status; + int i; + ap_listen_rec *lr; + apr_pollset_t *pollset; + ap_sb_handle_t *sbh; + apr_bucket_alloc_t *bucket_alloc; + int last_poll_idx = 0; + const char *lockfile; + + /* for benefit of any hooks that run as this child initializes */ + retained->mpm->mpm_state = AP_MPMQ_STARTING; + + my_child_num = child_num_arg; + ap_my_pid = getpid(); + requests_this_child = 0; + + ap_fatal_signal_child_setup(ap_server_conf); + + /* Get a sub context for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + apr_pool_create_ex(&pchild, pconf, NULL, allocator); + apr_allocator_owner_set(allocator, pchild); + apr_pool_tag(pchild, "pchild"); + +#if APR_HAS_THREADS + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pchild); +#endif + + apr_pool_create(&ptrans, pchild); + apr_pool_tag(ptrans, "transaction"); + + /* close unused listeners and pods */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (i != child_bucket) { + ap_close_listeners_ex(all_buckets[i].listeners); + ap_mpm_pod_close(all_buckets[i].pod); + } + } + + /* needs to be done before we switch UIDs so we have permissions */ + ap_reopen_scoreboard(pchild, NULL, 0); + status = SAFE_ACCEPT(apr_proc_mutex_child_init(&my_bucket->mutex, + apr_proc_mutex_lockfile(my_bucket->mutex), + pchild)); + if (status != APR_SUCCESS) { + lockfile = apr_proc_mutex_lockfile(my_bucket->mutex); + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(00155) + "Couldn't initialize cross-process lock in child " + "(%s) (%s)", + lockfile ? lockfile : "none", + apr_proc_mutex_name(my_bucket->mutex)); + clean_child_exit(APEXIT_CHILDFATAL); + } + + if (ap_run_drop_privileges(pchild, ap_server_conf)) { + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_run_child_init(pchild, ap_server_conf); + + ap_create_sb_handle(&sbh, pchild, my_child_num, 0); + + (void) ap_update_child_status(sbh, SERVER_READY, (request_rec *) NULL); + + /* Set up the pollfd array */ + status = apr_pollset_create(&pollset, num_listensocks, pchild, + APR_POLLSET_NOCOPY); + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(00156) + "Couldn't create pollset in child; check system or user limits"); + clean_child_exit(APEXIT_CHILDSICK); /* assume temporary resource issue */ + } + + for (lr = my_bucket->listeners, i = num_listensocks; i--; lr = lr->next) { + apr_pollfd_t *pfd = apr_pcalloc(pchild, sizeof *pfd); + + pfd->desc_type = APR_POLL_SOCKET; + pfd->desc.s = lr->sd; + pfd->reqevents = APR_POLLIN; + pfd->client_data = lr; + + status = apr_pollset_add(pollset, pfd); + if (status != APR_SUCCESS) { + /* If the child processed a SIGWINCH before setting up the + * pollset, this error path is expected and harmless, + * since the listener fd was already closed; so don't + * pollute the logs in that case. */ + if (!die_now) { + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(00157) + "Couldn't add listener to pollset; check system or user limits"); + clean_child_exit(APEXIT_CHILDSICK); + } + clean_child_exit(0); + } + + lr->accept_func = ap_unixd_accept; + } + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + bucket_alloc = apr_bucket_alloc_create(pchild); + + /* die_now is set when AP_SIG_GRACEFUL is received in the child; + * {shutdown,restart}_pending are set when a signal is received while + * running in single process mode. + */ + while (!die_now + && !retained->mpm->shutdown_pending + && !retained->mpm->restart_pending) { + conn_rec *current_conn; + void *csd; + + /* + * (Re)initialize this child to a pre-connection state. + */ + + apr_pool_clear(ptrans); + + if ((ap_max_requests_per_child > 0 + && requests_this_child++ >= ap_max_requests_per_child)) { + clean_child_exit(0); + } + + (void) ap_update_child_status(sbh, SERVER_READY, (request_rec *) NULL); + + /* + * Wait for an acceptable connection to arrive. + */ + + /* Lock around "accept", if necessary */ + SAFE_ACCEPT(accept_mutex_on()); + + if (num_listensocks == 1) { + /* There is only one listener record, so refer to that one. */ + lr = my_bucket->listeners; + } + else { + /* multiple listening sockets - need to poll */ + for (;;) { + apr_int32_t numdesc; + const apr_pollfd_t *pdesc; + + /* check for termination first so we don't sleep for a while in + * poll if already signalled + */ + if (die_now /* in graceful stop/restart */ + || retained->mpm->shutdown_pending + || retained->mpm->restart_pending) { + SAFE_ACCEPT(accept_mutex_off()); + clean_child_exit(0); + } + + /* timeout == 10 seconds to avoid a hang at graceful restart/stop + * caused by the closing of sockets by the signal handler + */ + status = apr_pollset_poll(pollset, apr_time_from_sec(10), + &numdesc, &pdesc); + if (status != APR_SUCCESS) { + if (APR_STATUS_IS_TIMEUP(status) || + APR_STATUS_IS_EINTR(status)) { + continue; + } + /* Single Unix documents select as returning errnos + * EBADF, EINTR, and EINVAL... and in none of those + * cases does it make sense to continue. In fact + * on Linux 2.0.x we seem to end up with EFAULT + * occasionally, and we'd loop forever due to it. + */ + ap_log_error(APLOG_MARK, APLOG_ERR, status, + ap_server_conf, APLOGNO(00158) "apr_pollset_poll: (listen)"); + SAFE_ACCEPT(accept_mutex_off()); + clean_child_exit(APEXIT_CHILDSICK); + } + + /* We can always use pdesc[0], but sockets at position N + * could end up completely starved of attention in a very + * busy server. Therefore, we round-robin across the + * returned set of descriptors. While it is possible that + * the returned set of descriptors might flip around and + * continue to starve some sockets, we happen to know the + * internal pollset implementation retains ordering + * stability of the sockets. Thus, the round-robin should + * ensure that a socket will eventually be serviced. + */ + if (last_poll_idx >= numdesc) + last_poll_idx = 0; + + /* Grab a listener record from the client_data of the poll + * descriptor, and advance our saved index to round-robin + * the next fetch. + * + * ### hmm... this descriptor might have POLLERR rather + * ### than POLLIN + */ + lr = pdesc[last_poll_idx++].client_data; + goto got_fd; + } + } + got_fd: + /* if we accept() something we don't want to die, so we have to + * defer the exit + */ + status = lr->accept_func(&csd, lr, ptrans); + + SAFE_ACCEPT(accept_mutex_off()); /* unlock after "accept" */ + + if (status == APR_EGENERAL) { + /* resource shortage or should-not-occur occurred */ + clean_child_exit(APEXIT_CHILDSICK); + } + else if (status != APR_SUCCESS) { + continue; + } + + /* + * We now have a connection, so set it up with the appropriate + * socket options, file descriptors, and read/write buffers. + */ + + current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, my_child_num, sbh, bucket_alloc); + if (current_conn) { +#if APR_HAS_THREADS + current_conn->current_thread = thd; +#endif + ap_process_connection(current_conn, csd); + ap_lingering_close(current_conn); + } + + /* Check the pod and the generation number after processing a + * connection so that we'll go away if a graceful restart occurred + * while we were processing the connection or we are the lucky + * idle server process that gets to die. + */ + if (ap_mpm_pod_check(my_bucket->pod) == APR_SUCCESS) { /* selected as idle? */ + die_now = 1; + } + else if (retained->mpm->my_generation != + ap_scoreboard_image->global->running_generation) { /* restart? */ + /* yeah, this could be non-graceful restart, in which case the + * parent will kill us soon enough, but why bother checking? + */ + die_now = 1; + } + } + apr_pool_clear(ptrans); /* kludge to avoid crash in APR reslist cleanup code */ + clean_child_exit(0); +} + + +static int make_child(server_rec *s, int slot, int bucket) +{ + int pid; + + if (slot + 1 > retained->max_daemons_limit) { + retained->max_daemons_limit = slot + 1; + } + + if (one_process) { + my_bucket = &all_buckets[0]; + + prefork_note_child_started(slot, getpid()); + child_main(slot, 0); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + (void) ap_update_child_status_from_indexes(slot, 0, SERVER_STARTING, + (request_rec *) NULL); + +#ifdef _OSD_POSIX + /* BS2000 requires a "special" version of fork() before a setuid() call */ + if ((pid = os_fork(ap_unixd_config.user_name)) == -1) { +#else + if ((pid = fork()) == -1) { +#endif + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(00159) "fork: Unable to fork new process"); + + /* fork didn't succeed. Fix the scoreboard or else + * it will say SERVER_STARTING forever and ever + */ + (void) ap_update_child_status_from_indexes(slot, 0, SERVER_DEAD, + (request_rec *) NULL); + + /* In case system resources are maxxed out, we don't want + * Apache running away with the CPU trying to fork over and + * over and over again. + */ + sleep(10); + + return -1; + } + + if (!pid) { + my_bucket = &all_buckets[bucket]; + +#ifdef HAVE_BINDPROCESSOR + /* by default AIX binds to a single processor + * this bit unbinds children which will then bind to another cpu + */ + int status = bindprocessor(BINDPROCESS, (int)getpid(), + PROCESSOR_CLASS_ANY); + if (status != OK) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, + ap_server_conf, APLOGNO(00160) "processor unbind failed"); + } +#endif + RAISE_SIGSTOP(MAKE_CHILD); + AP_MONCONTROL(1); + /* Disable the parent's signal handlers and set up proper handling in + * the child. + */ + apr_signal(SIGHUP, just_die); + apr_signal(SIGTERM, just_die); + /* Ignore SIGINT in child. This fixes race-condition in signals + * handling when httpd is runnning on foreground and user hits ctrl+c. + * In this case, SIGINT is sent to all children followed by SIGTERM + * from the main process, which interrupts the SIGINT handler and + * leads to inconsistency. + */ + apr_signal(SIGINT, SIG_IGN); + /* The child process just closes listeners on AP_SIG_GRACEFUL. + * The pod is used for signalling the graceful restart. + */ + apr_signal(AP_SIG_GRACEFUL, stop_listening); + child_main(slot, bucket); + } + + ap_scoreboard_image->parent[slot].bucket = bucket; + prefork_note_child_started(slot, pid); + + return 0; +} + + +/* start up a bunch of children */ +static void startup_children(int number_to_start) +{ + int i; + + for (i = 0; number_to_start && i < ap_daemons_limit; ++i) { + if (ap_scoreboard_image->servers[i][0].status != SERVER_DEAD) { + continue; + } + if (make_child(ap_server_conf, i, i % retained->mpm->num_buckets) < 0) { + break; + } + --number_to_start; + } +} + +static void perform_idle_server_maintenance(apr_pool_t *p) +{ + static int bucket_make_child_record = -1; + static int bucket_kill_child_record = -1; + int i; + int idle_count; + worker_score *ws; + int free_length; + int free_slots[MAX_SPAWN_RATE]; + int last_non_dead; + int total_non_dead; + + /* initialize the free_list */ + free_length = 0; + + idle_count = 0; + last_non_dead = -1; + total_non_dead = 0; + + for (i = 0; i < ap_daemons_limit; ++i) { + int status; + + if (i >= retained->max_daemons_limit && free_length == retained->idle_spawn_rate) + break; + ws = &ap_scoreboard_image->servers[i][0]; + status = ws->status; + if (status == SERVER_DEAD) { + /* try to keep children numbers as low as possible */ + if (free_length < retained->idle_spawn_rate) { + free_slots[free_length] = i; + ++free_length; + } + } + else { + /* We consider a starting server as idle because we started it + * at least a cycle ago, and if it still hasn't finished starting + * then we're just going to swamp things worse by forking more. + * So we hopefully won't need to fork more if we count it. + * This depends on the ordering of SERVER_READY and SERVER_STARTING. + */ + if (status <= SERVER_READY) { + ++ idle_count; + } + + ++total_non_dead; + last_non_dead = i; + } + } + retained->max_daemons_limit = last_non_dead + 1; + if (idle_count > ap_daemons_max_free) { + /* kill off one child... we use the pod because that'll cause it to + * shut down gracefully, in case it happened to pick up a request + * while we were counting + */ + bucket_kill_child_record = (bucket_kill_child_record + 1) % retained->mpm->num_buckets; + ap_mpm_pod_signal(all_buckets[bucket_kill_child_record].pod); + retained->idle_spawn_rate = 1; + } + else if (idle_count < ap_daemons_min_free) { + /* terminate the free list */ + if (free_length == 0) { + /* only report this condition once */ + if (!retained->maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00161) + "server reached MaxRequestWorkers setting, consider" + " raising the MaxRequestWorkers setting"); + retained->maxclients_reported = 1; + } + retained->idle_spawn_rate = 1; + } + else { + if (retained->idle_spawn_rate >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00162) + "server seems busy, (you may need " + "to increase StartServers, or Min/MaxSpareServers), " + "spawning %d children, there are %d idle, and " + "%d total children", retained->idle_spawn_rate, + idle_count, total_non_dead); + } + for (i = 0; i < free_length; ++i) { + bucket_make_child_record++; + bucket_make_child_record %= retained->mpm->num_buckets; + make_child(ap_server_conf, free_slots[i], + bucket_make_child_record); + } + /* the next time around we want to spawn twice as many if this + * wasn't good enough, but not if we've just done a graceful + */ + if (retained->hold_off_on_exponential_spawning) { + --retained->hold_off_on_exponential_spawning; + } + else if (retained->idle_spawn_rate < MAX_SPAWN_RATE) { + retained->idle_spawn_rate *= 2; + } + } + } + else { + retained->idle_spawn_rate = 1; + } +} + +/***************************************************************** + * Executive routines. + */ + +static int prefork_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +{ + int index; + int remaining_children_to_start; + int i; + + ap_log_pid(pconf, ap_pid_fname); + + if (!retained->mpm->was_graceful) { + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + return !OK; + } + /* fix the generation number in the global score; we just got a new, + * cleared scoreboard + */ + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + } + + ap_unixd_mpm_set_signals(pconf, one_process); + + if (one_process) { + AP_MONCONTROL(1); + make_child(ap_server_conf, 0, 0); + /* NOTREACHED */ + ap_assert(0); + return !OK; + } + + /* Don't thrash since num_buckets depends on the + * system and the number of online CPU cores... + */ + if (ap_daemons_limit < retained->mpm->num_buckets) + ap_daemons_limit = retained->mpm->num_buckets; + if (ap_daemons_to_start < retained->mpm->num_buckets) + ap_daemons_to_start = retained->mpm->num_buckets; + if (ap_daemons_min_free < retained->mpm->num_buckets) + ap_daemons_min_free = retained->mpm->num_buckets; + if (ap_daemons_max_free < ap_daemons_min_free + retained->mpm->num_buckets) + ap_daemons_max_free = ap_daemons_min_free + retained->mpm->num_buckets; + + /* If we're doing a graceful_restart then we're going to see a lot + * of children exiting immediately when we get into the main loop + * below (because we just sent them AP_SIG_GRACEFUL). This happens pretty + * rapidly... and for each one that exits we'll start a new one until + * we reach at least daemons_min_free. But we may be permitted to + * start more than that, so we'll just keep track of how many we're + * supposed to start up without the 1 second penalty between each fork. + */ + remaining_children_to_start = ap_daemons_to_start; + if (remaining_children_to_start > ap_daemons_limit) { + remaining_children_to_start = ap_daemons_limit; + } + if (!retained->mpm->was_graceful) { + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + } + else { + /* give the system some time to recover before kicking into + * exponential mode + */ + retained->hold_off_on_exponential_spawning = 10; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00163) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00164) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00165) + "Accept mutex: %s (default: %s)", + (all_buckets[0].mutex) + ? apr_proc_mutex_name(all_buckets[0].mutex) + : "none", + apr_proc_mutex_defname()); + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + while (!retained->mpm->restart_pending && !retained->mpm->shutdown_pending) { + int child_slot; + apr_exit_why_e exitwhy; + int status, processed_status; + /* this is a memory leak, but I'll fix it later. */ + apr_proc_t pid; + + ap_wait_or_timeout(&exitwhy, &status, &pid, pconf, ap_server_conf); + + /* XXX: if it takes longer than 1 second for all our children + * to start up and get into IDLE state then we may spawn an + * extra child + */ + if (pid.pid != -1) { + processed_status = ap_process_child_status(&pid, exitwhy, status); + child_slot = ap_find_child_by_pid(&pid); + if (processed_status == APEXIT_CHILDFATAL) { + /* fix race condition found in PR 39311 + * A child created at the same time as a graceful happens + * can find the lock missing and create a fatal error. + * It is not fatal for the last generation to be in this state. + */ + if (child_slot < 0 + || ap_get_scoreboard_process(child_slot)->generation + == retained->mpm->my_generation) { + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + return !OK; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00166) + "Ignoring fatal error in child of previous " + "generation (pid %ld).", + (long)pid.pid); + } + } + + /* non-fatal death... note that it's gone in the scoreboard. */ + if (child_slot >= 0) { + (void) ap_update_child_status_from_indexes(child_slot, 0, SERVER_DEAD, + (request_rec *) NULL); + prefork_note_child_killed(child_slot, 0, 0); + if (processed_status == APEXIT_CHILDSICK) { + /* child detected a resource shortage (E[NM]FILE, ENOBUFS, etc) + * cut the fork rate to the minimum + */ + retained->idle_spawn_rate = 1; + } + else if (remaining_children_to_start + && child_slot < ap_daemons_limit) { + /* we're still doing a 1-for-1 replacement of dead + * children with new children + */ + make_child(ap_server_conf, child_slot, + ap_get_scoreboard_process(child_slot)->bucket); + --remaining_children_to_start; + } +#if APR_HAS_OTHER_CHILD + } + else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, status) == APR_SUCCESS) { + /* handled */ +#endif + } + else if (retained->mpm->was_graceful) { + /* Great, we've probably just lost a slot in the + * scoreboard. Somehow we don't know about this + * child. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, + 0, ap_server_conf, APLOGNO(00167) + "long lost child came home! (pid %ld)", (long)pid.pid); + } + /* Don't perform idle maintenance when a child dies, + * only do it when there's a timeout. Remember only a + * finite number of children can die, and it's pretty + * pathological for a lot to die suddenly. + */ + continue; + } + else if (remaining_children_to_start) { + /* we hit a 1 second timeout in which none of the previous + * generation of children needed to be reaped... so assume + * they're all done, and pick up the slack if any is left. + */ + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + /* In any event we really shouldn't do the code below because + * few of the servers we just started are in the IDLE state + * yet, so we'd mistakenly create an extra server. + */ + continue; + } + + perform_idle_server_maintenance(pconf); + } + + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + if (retained->mpm->shutdown_pending && retained->mpm->is_ungraceful) { + /* Time to shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + if (ap_unixd_killpg(getpgrp(), SIGTERM) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00168) "killpg SIGTERM"); + } + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + prefork_note_child_killed); + + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00169) + "caught SIGTERM, shutting down"); + + return DONE; + } + + if (retained->mpm->shutdown_pending) { + /* Time to perform a graceful shut down: + * Reap the inactive children, and ask the active ones + * to close their listeners, then wait until they are + * all done to exit. + */ + int active_children; + apr_time_t cutoff = 0; + + /* Stop listening */ + ap_close_listeners(); + + /* kill off the idle ones */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + ap_mpm_pod_killpg(all_buckets[i].pod, retained->max_daemons_limit); + } + + /* Send SIGUSR1 to the active children */ + active_children = 0; + for (index = 0; index < ap_daemons_limit; ++index) { + if (ap_scoreboard_image->servers[index][0].status != SERVER_DEAD) { + /* Ask each child to close its listeners. */ + ap_mpm_safe_kill(MPM_CHILD_PID(index), AP_SIG_GRACEFUL); + active_children++; + } + } + + /* Allow each child which actually finished to exit */ + ap_relieve_child_processes(prefork_note_child_killed); + + /* cleanup pid file */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00170) + "caught " AP_SIG_GRACEFUL_STOP_STRING ", shutting down gracefully"); + + if (ap_graceful_shutdown_timeout) { + cutoff = apr_time_now() + + apr_time_from_sec(ap_graceful_shutdown_timeout); + } + + /* Don't really exit until each child has finished */ + retained->mpm->shutdown_pending = 0; + do { + /* Pause for a second */ + sleep(1); + + /* Relieve any children which have now exited */ + ap_relieve_child_processes(prefork_note_child_killed); + + active_children = 0; + for (index = 0; index < ap_daemons_limit; ++index) { + if (ap_mpm_safe_kill(MPM_CHILD_PID(index), 0) == APR_SUCCESS) { + active_children = 1; + /* Having just one child is enough to stay around */ + break; + } + } + } while (!retained->mpm->shutdown_pending && active_children && + (!ap_graceful_shutdown_timeout || apr_time_now() < cutoff)); + + /* We might be here because we received SIGTERM, either + * way, try and make sure that all of our processes are + * really dead. + */ + ap_unixd_killpg(getpgrp(), SIGTERM); + + return DONE; + } + + /* we've been told to restart */ + if (one_process) { + /* not worth thinking about */ + return DONE; + } + + /* advance to the next generation */ + /* XXX: we really need to make sure this new generation number isn't in + * use by any of the children. + */ + ++retained->mpm->my_generation; + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + + if (!retained->mpm->is_ungraceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00171) + "Graceful restart requested, doing restart"); + + /* kill off the idle ones */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + ap_mpm_pod_killpg(all_buckets[i].pod, retained->max_daemons_limit); + } + + /* This is mostly for debugging... so that we know what is still + * gracefully dealing with existing request. This will break + * in a very nasty way if we ever have the scoreboard totally + * file-based (no shared memory) + */ + for (index = 0; index < ap_daemons_limit; ++index) { + if (ap_scoreboard_image->servers[index][0].status != SERVER_DEAD) { + ap_scoreboard_image->servers[index][0].status = SERVER_GRACEFUL; + /* Ask each child to close its listeners. + * + * NOTE: we use the scoreboard, because if we send SIGUSR1 + * to every process in the group, this may include CGI's, + * piped loggers, etc. They almost certainly won't handle + * it gracefully. + */ + ap_mpm_safe_kill(ap_scoreboard_image->parent[index].pid, AP_SIG_GRACEFUL); + } + } + } + else { + /* Kill 'em off */ + if (ap_unixd_killpg(getpgrp(), SIGHUP) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00172) "killpg SIGHUP"); + } + ap_reclaim_child_processes(0, /* Not when just starting up */ + prefork_note_child_killed); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00173) + "SIGHUP received. Attempting to restart"); + } + + return OK; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int prefork_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + int level_flags = 0; + ap_listen_rec **listen_buckets; + apr_status_t rv; + char id[16]; + int i; + + pconf = p; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + level_flags |= APLOG_STARTUP; + } + + if ((num_listensocks = ap_setup_listeners(ap_server_conf)) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0, + (startup ? NULL : s), + "no listening sockets available, shutting down"); + return !OK; + } + + if (one_process) { + retained->mpm->num_buckets = 1; + } + else if (!retained->mpm->was_graceful) { + /* Preserve the number of buckets on graceful restarts. */ + retained->mpm->num_buckets = 0; + } + if ((rv = ap_duplicate_listeners(pconf, ap_server_conf, + &listen_buckets, &retained->mpm->num_buckets))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not duplicate listeners"); + return !OK; + } + all_buckets = apr_pcalloc(pconf, retained->mpm->num_buckets * + sizeof(prefork_child_bucket)); + for (i = 0; i < retained->mpm->num_buckets; i++) { + if ((rv = ap_mpm_pod_open(pconf, &all_buckets[i].pod))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not open pipe-of-death"); + return !OK; + } + /* Initialize cross-process accept lock (safe accept needed only) */ + if ((rv = SAFE_ACCEPT((apr_snprintf(id, sizeof id, "%i", i), + ap_proc_mutex_create(&all_buckets[i].mutex, + NULL, AP_ACCEPT_MUTEX_TYPE, + id, s, pconf, 0))))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not create accept mutex"); + return !OK; + } + all_buckets[i].listeners = listen_buckets[i]; + } + + return OK; +} + +static int prefork_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) +{ + int no_detach, debug, foreground; + apr_status_t rv; + const char *userdata_key = "mpm_prefork_module"; + + debug = ap_exists_config_define("DEBUG"); + + if (debug) { + foreground = one_process = 1; + no_detach = 0; + } + else + { + no_detach = ap_exists_config_define("NO_DETACH"); + one_process = ap_exists_config_define("ONE_PROCESS"); + foreground = ap_exists_config_define("FOREGROUND"); + } + + ap_mutex_register(p, AP_ACCEPT_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0); + + retained = ap_retained_data_get(userdata_key); + if (!retained) { + retained = ap_retained_data_create(userdata_key, sizeof(*retained)); + retained->mpm = ap_unixd_mpm_get_retained_data(); + retained->max_daemons_limit = -1; + retained->idle_spawn_rate = 1; + } + retained->mpm->mpm_state = AP_MPMQ_STARTING; + if (retained->mpm->baton != retained) { + retained->mpm->was_graceful = 0; + retained->mpm->baton = retained; + } + ++retained->mpm->module_loads; + + /* sigh, want this only the second time around */ + if (retained->mpm->module_loads == 2) { + if (!one_process && !foreground) { + /* before we detach, setup crash handlers to log to errorlog */ + ap_fatal_signal_setup(ap_server_conf, pconf); + rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND + : APR_PROC_DETACH_DAEMONIZE); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00174) + "apr_proc_detach failed"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + } + + parent_pid = ap_my_pid = getpid(); + + ap_listen_pre_config(); + ap_daemons_to_start = DEFAULT_START_DAEMON; + ap_daemons_min_free = DEFAULT_MIN_FREE_DAEMON; + ap_daemons_max_free = DEFAULT_MAX_FREE_DAEMON; + server_limit = DEFAULT_SERVER_LIMIT; + ap_daemons_limit = server_limit; + ap_extended_status = 0; + + return OK; +} + +static int prefork_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + } + + if (server_limit > MAX_SERVER_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00175) + "WARNING: ServerLimit of %d exceeds compile-time " + "limit of %d servers, decreasing to %d.", + server_limit, MAX_SERVER_LIMIT, MAX_SERVER_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00176) + "ServerLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + server_limit, MAX_SERVER_LIMIT); + } + server_limit = MAX_SERVER_LIMIT; + } + else if (server_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00177) + "WARNING: ServerLimit of %d not allowed, " + "increasing to 1.", server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00178) + "ServerLimit of %d not allowed, increasing to 1", + server_limit); + } + server_limit = 1; + } + + /* you cannot change ServerLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_server_limit) { + retained->first_server_limit = server_limit; + } + else if (server_limit != retained->first_server_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00179) + "changing ServerLimit to %d from original value of %d " + "not allowed during restart", + server_limit, retained->first_server_limit); + server_limit = retained->first_server_limit; + } + + if (ap_daemons_limit > server_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00180) + "WARNING: MaxRequestWorkers of %d exceeds ServerLimit " + "value of %d servers, decreasing MaxRequestWorkers to %d. " + "To increase, please see the ServerLimit directive.", + ap_daemons_limit, server_limit, server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00181) + "MaxRequestWorkers of %d exceeds ServerLimit value " + "of %d, decreasing to match", + ap_daemons_limit, server_limit); + } + ap_daemons_limit = server_limit; + } + else if (ap_daemons_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00182) + "WARNING: MaxRequestWorkers of %d not allowed, " + "increasing to 1.", ap_daemons_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00183) + "MaxRequestWorkers of %d not allowed, increasing to 1", + ap_daemons_limit); + } + ap_daemons_limit = 1; + } + + /* ap_daemons_to_start > ap_daemons_limit checked in prefork_run() */ + if (ap_daemons_to_start < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00184) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00185) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (ap_daemons_min_free < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00186) + "WARNING: MinSpareServers of %d not allowed, " + "increasing to 1 to avoid almost certain server failure. " + "Please read the documentation.", ap_daemons_min_free); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00187) + "MinSpareServers of %d not allowed, increasing to 1", + ap_daemons_min_free); + } + ap_daemons_min_free = 1; + } + + /* ap_daemons_max_free < ap_daemons_min_free + 1 checked in prefork_run() */ + + return OK; +} + +static void prefork_hooks(apr_pool_t *p) +{ + /* Our open_logs hook function must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = {"core.c", NULL}; + + ap_hook_open_logs(prefork_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + /* we need to set the MPM state before other pre-config hooks use MPM query + * to retrieve it, so register as REALLY_FIRST + */ + ap_hook_pre_config(prefork_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_check_config(prefork_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(prefork_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(prefork_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(prefork_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_to_start = atoi(arg); + return NULL; +} + +static const char *set_min_free_servers(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_min_free = atoi(arg); + return NULL; +} + +static const char *set_max_free_servers(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_max_free = atoi(arg); + return NULL; +} + +static const char *set_max_clients (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + if (!strcasecmp(cmd->cmd->name, "MaxClients")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00188) + "MaxClients is deprecated, use MaxRequestWorkers " + "instead."); + } + ap_daemons_limit = atoi(arg); + return NULL; +} + +static const char *set_server_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + server_limit = atoi(arg); + return NULL; +} + +static const command_rec prefork_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF, + "Number of child processes launched at server startup"), +AP_INIT_TAKE1("MinSpareServers", set_min_free_servers, NULL, RSRC_CONF, + "Minimum number of idle children, to handle request spikes"), +AP_INIT_TAKE1("MaxSpareServers", set_max_free_servers, NULL, RSRC_CONF, + "Maximum number of idle children"), +AP_INIT_TAKE1("MaxClients", set_max_clients, NULL, RSRC_CONF, + "Deprecated name of MaxRequestWorkers"), +AP_INIT_TAKE1("MaxRequestWorkers", set_max_clients, NULL, RSRC_CONF, + "Maximum number of children alive at the same time"), +AP_INIT_TAKE1("ServerLimit", set_server_limit, NULL, RSRC_CONF, + "Maximum value of MaxRequestWorkers for this run of Apache"), +AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, +{ NULL } +}; + +AP_DECLARE_MODULE(mpm_prefork) = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + prefork_cmds, /* command apr_table_t */ + prefork_hooks, /* register hooks */ +}; diff --git a/server/mpm/winnt/Makefile.in b/server/mpm/winnt/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/winnt/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/winnt/child.c b/server/mpm/winnt/child.c new file mode 100644 index 0000000..21755f3 --- /dev/null +++ b/server/mpm/winnt/child.c @@ -0,0 +1,1289 @@ +/* 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. + */ + +#ifdef WIN32 + +#include "apr.h" +#include <process.h> +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "http_vhost.h" /* for ap_update_vhost_given_ip */ +#include "apr_portable.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_shm.h" +#include "apr_thread_mutex.h" +#include "ap_mpm.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "mpm_default.h" +#include "mpm_winnt.h" +#include "mpm_common.h" +#include <malloc.h> +#include "apr_atomic.h" +#include "apr_buckets.h" +#include "scoreboard.h" + +#ifdef __MINGW32__ +#include <mswsock.h> + +#ifndef WSAID_ACCEPTEX +#define WSAID_ACCEPTEX \ + {0xb5367df1, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} +typedef BOOL (WINAPI *LPFN_ACCEPTEX)(SOCKET, SOCKET, PVOID, DWORD, DWORD, DWORD, LPDWORD, LPOVERLAPPED); +#endif /* WSAID_ACCEPTEX */ + +#ifndef WSAID_GETACCEPTEXSOCKADDRS +#define WSAID_GETACCEPTEXSOCKADDRS \ + {0xb5367df2, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} +typedef VOID (WINAPI *LPFN_GETACCEPTEXSOCKADDRS)(PVOID, DWORD, DWORD, DWORD, + struct sockaddr **, LPINT, + struct sockaddr **, LPINT); +#endif /* WSAID_GETACCEPTEXSOCKADDRS */ + +#endif /* __MINGW32__ */ + +/* + * The Windows MPM uses a queue of completion contexts that it passes + * between the accept threads and the worker threads. Declare the + * functions to access the queue and the structures passed on the + * queue in the header file to enable modules to access them + * if necessary. The queue resides in the MPM. + */ +#ifdef CONTAINING_RECORD +#undef CONTAINING_RECORD +#endif +#define CONTAINING_RECORD(address, type, field) ((type *)( \ + (char *)(address) - \ + (char *)(&((type *)0)->field))) +#if APR_HAVE_IPV6 +#define PADDED_ADDR_SIZE (sizeof(SOCKADDR_IN6)+16) +#else +#define PADDED_ADDR_SIZE (sizeof(SOCKADDR_IN)+16) +#endif + +APLOG_USE_MODULE(mpm_winnt); + +/* Queue for managing the passing of winnt_conn_ctx_t between + * the accept and worker threads. + */ +typedef struct winnt_conn_ctx_t_s { + struct winnt_conn_ctx_t_s *next; + OVERLAPPED overlapped; + apr_socket_t *sock; + SOCKET accept_socket; + char buff[2*PADDED_ADDR_SIZE]; + struct sockaddr *sa_server; + int sa_server_len; + struct sockaddr *sa_client; + int sa_client_len; + apr_pool_t *ptrans; + apr_bucket_alloc_t *ba; + apr_bucket *data; +#if APR_HAVE_IPV6 + short socket_family; +#endif +} winnt_conn_ctx_t; + +typedef enum { + IOCP_CONNECTION_ACCEPTED = 1, + IOCP_WAIT_FOR_RECEIVE = 2, + IOCP_WAIT_FOR_TRANSMITFILE = 3, + IOCP_SHUTDOWN = 4 +} io_state_e; + +static apr_pool_t *pchild; +static int shutdown_in_progress = 0; +static int workers_may_exit = 0; +static unsigned int g_blocked_threads = 0; +static HANDLE max_requests_per_child_event; + +static apr_thread_mutex_t *child_lock; +static apr_thread_mutex_t *qlock; +static winnt_conn_ctx_t *qhead = NULL; +static winnt_conn_ctx_t *qtail = NULL; +static apr_uint32_t num_completion_contexts = 0; +static apr_uint32_t max_num_completion_contexts = 0; +static HANDLE ThreadDispatchIOCP = NULL; +static HANDLE qwait_event = NULL; + +static void mpm_recycle_completion_context(winnt_conn_ctx_t *context) +{ + /* Recycle the completion context. + * - clear the ptrans pool + * - put the context on the queue to be consumed by the accept thread + * Note: + * context->accept_socket may be in a disconnected but reusable + * state so -don't- close it. + */ + if (context) { + HANDLE saved_event; + + apr_pool_clear(context->ptrans); + context->ba = apr_bucket_alloc_create(context->ptrans); + context->next = NULL; + + saved_event = context->overlapped.hEvent; + memset(&context->overlapped, 0, sizeof(context->overlapped)); + context->overlapped.hEvent = saved_event; + ResetEvent(context->overlapped.hEvent); + + apr_thread_mutex_lock(qlock); + if (qtail) { + qtail->next = context; + } else { + qhead = context; + SetEvent(qwait_event); + } + qtail = context; + apr_thread_mutex_unlock(qlock); + } +} + +static winnt_conn_ctx_t *mpm_get_completion_context(int *timeout) +{ + apr_status_t rv; + winnt_conn_ctx_t *context = NULL; + + *timeout = 0; + while (1) { + /* Grab a context off the queue */ + apr_thread_mutex_lock(qlock); + if (qhead) { + context = qhead; + qhead = qhead->next; + if (!qhead) + qtail = NULL; + } else { + ResetEvent(qwait_event); + } + apr_thread_mutex_unlock(qlock); + + if (!context) { + /* We failed to grab a context off the queue, consider allocating + * a new one out of the child pool. There may be up to + * (ap_threads_per_child + num_listeners) contexts in the system + * at once. + */ + if (num_completion_contexts >= max_num_completion_contexts) { + /* All workers are busy, need to wait for one */ + static int reported = 0; + if (!reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00326) + "Server ran out of threads to serve " + "requests. Consider raising the " + "ThreadsPerChild setting"); + reported = 1; + } + + /* Wait for a worker to free a context. Once per second, give + * the caller a chance to check for shutdown. If the wait + * succeeds, get the context off the queue. It must be + * available, since there's only one consumer. + */ + rv = WaitForSingleObject(qwait_event, 1000); + if (rv == WAIT_OBJECT_0) + continue; + else { + if (rv == WAIT_TIMEOUT) { + /* somewhat-normal condition where threads are busy */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00327) + "mpm_get_completion_context: Failed to get a " + "free context within 1 second"); + *timeout = 1; + } + else { + /* should be the unexpected, generic WAIT_FAILED */ + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + ap_server_conf, APLOGNO(00328) + "mpm_get_completion_context: " + "WaitForSingleObject failed to get free context"); + } + return NULL; + } + } else { + /* Allocate another context. + * Note: Multiple failures in the next two steps will cause + * the pchild pool to 'leak' storage. I don't think this + * is worth fixing... + */ + apr_allocator_t *allocator; + + apr_thread_mutex_lock(child_lock); + context = (winnt_conn_ctx_t *)apr_pcalloc(pchild, + sizeof(winnt_conn_ctx_t)); + + + context->overlapped.hEvent = CreateEvent(NULL, TRUE, + FALSE, NULL); + if (context->overlapped.hEvent == NULL) { + /* Hopefully this is a temporary condition ... */ + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + ap_server_conf, APLOGNO(00329) + "mpm_get_completion_context: " + "CreateEvent failed."); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + + /* Create the transaction pool */ + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + rv = apr_pool_create_ex(&context->ptrans, pchild, NULL, + allocator); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00330) + "mpm_get_completion_context: Failed " + "to create the transaction pool."); + CloseHandle(context->overlapped.hEvent); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + apr_allocator_owner_set(allocator, context->ptrans); + apr_pool_tag(context->ptrans, "transaction"); + + context->accept_socket = INVALID_SOCKET; + context->ba = apr_bucket_alloc_create(context->ptrans); + apr_atomic_inc32(&num_completion_contexts); + + apr_thread_mutex_unlock(child_lock); + break; + } + } else { + /* Got a context from the queue */ + break; + } + } + + return context; +} + +typedef enum { + ACCEPT_FILTER_NONE = 0, + ACCEPT_FILTER_CONNECT = 1 +} accept_filter_e; + +static const char * accept_filter_to_string(accept_filter_e accf) +{ + switch (accf) { + case ACCEPT_FILTER_NONE: + return "none"; + case ACCEPT_FILTER_CONNECT: + return "connect"; + default: + return ""; + } +} + +static accept_filter_e get_accept_filter(const char *protocol) +{ + core_server_config *core_sconf; + const char *name; + + core_sconf = ap_get_core_module_config(ap_server_conf->module_config); + name = apr_table_get(core_sconf->accf_map, protocol); + if (!name) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, + APLOGNO(02531) "winnt_accept: Listen protocol '%s' has " + "no known accept filter. Using 'none' instead", + protocol); + return ACCEPT_FILTER_NONE; + } + else if (strcmp(name, "data") == 0) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + APLOGNO(03458) "winnt_accept: 'data' accept filter is no " + "longer supported. Using 'connect' instead"); + return ACCEPT_FILTER_CONNECT; + } + else if (strcmp(name, "connect") == 0) { + return ACCEPT_FILTER_CONNECT; + } + else if (strcmp(name, "none") == 0) { + return ACCEPT_FILTER_NONE; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00331) + "winnt_accept: unrecognized AcceptFilter '%s', " + "only 'data', 'connect' or 'none' are valid. " + "Using 'none' instead", name); + return ACCEPT_FILTER_NONE; + } +} + +/* Windows NT/2000 specific code... + * Accept processing for on Windows NT uses a producer/consumer queue + * model. An accept thread accepts connections off the network then issues + * PostQueuedCompletionStatus() to awake a thread blocked on the ThreadDispatch + * IOCompletionPort. + * + * winnt_accept() + * One or more accept threads run in this function, each of which accepts + * connections off the network and calls PostQueuedCompletionStatus() to + * queue an io completion packet to the ThreadDispatch IOCompletionPort. + * winnt_get_connection() + * Worker threads block on the ThreadDispatch IOCompletionPort awaiting + * connections to service. + */ +#define MAX_ACCEPTEX_ERR_COUNT 10 + +static unsigned int __stdcall winnt_accept(void *lr_) +{ + ap_listen_rec *lr = (ap_listen_rec *)lr_; + apr_os_sock_info_t sockinfo; + winnt_conn_ctx_t *context = NULL; + DWORD BytesRead; + SOCKET nlsd; + LPFN_ACCEPTEX lpfnAcceptEx = NULL; + LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs = NULL; + GUID GuidAcceptEx = WSAID_ACCEPTEX; + GUID GuidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS; + int rv; + accept_filter_e accf; + int err_count = 0; + HANDLE events[3]; +#if APR_HAVE_IPV6 + SOCKADDR_STORAGE ss_listen; + int namelen = sizeof(ss_listen); +#endif + u_long zero = 0; + + apr_os_sock_get(&nlsd, lr->sd); + +#if APR_HAVE_IPV6 + if (getsockname(nlsd, (struct sockaddr *)&ss_listen, &namelen) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(00332) + "winnt_accept: getsockname error on listening socket, " + "is IPv6 available?"); + return 1; + } +#endif + + accf = get_accept_filter(lr->protocol); + if (accf == ACCEPT_FILTER_CONNECT) + { + if (WSAIoctl(nlsd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &GuidAcceptEx, sizeof GuidAcceptEx, + &lpfnAcceptEx, sizeof lpfnAcceptEx, + &BytesRead, NULL, NULL) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(02322) + "winnt_accept: failed to retrieve AcceptEx, try 'AcceptFilter none'"); + return 1; + } + if (WSAIoctl(nlsd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &GuidGetAcceptExSockaddrs, sizeof GuidGetAcceptExSockaddrs, + &lpfnGetAcceptExSockaddrs, sizeof lpfnGetAcceptExSockaddrs, + &BytesRead, NULL, NULL) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(02323) + "winnt_accept: failed to retrieve GetAcceptExSockaddrs, try 'AcceptFilter none'"); + return 1; + } + /* first, high priority event is an already accepted connection */ + events[1] = exit_event; + events[2] = max_requests_per_child_event; + } + else /* accf == ACCEPT_FILTER_NONE */ + { +reinit: /* target of connect upon too many AcceptEx failures */ + + /* last, low priority event is a not yet accepted connection */ + events[0] = exit_event; + events[1] = max_requests_per_child_event; + events[2] = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* The event needs to be removed from the accepted socket, + * if not removed from the listen socket prior to accept(), + */ + rv = WSAEventSelect(nlsd, events[2], FD_ACCEPT); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, + apr_get_netos_error(), ap_server_conf, APLOGNO(00333) + "WSAEventSelect() failed."); + CloseHandle(events[2]); + return 1; + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00334) + "Child: Accept thread listening on %pI using AcceptFilter %s", + lr->bind_addr, accept_filter_to_string(accf)); + + while (!shutdown_in_progress) { + if (!context) { + int timeout; + + context = mpm_get_completion_context(&timeout); + if (!context) { + if (!timeout) { + /* Hopefully a temporary condition in the provider? */ + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00335) + "winnt_accept: Too many failures grabbing a " + "connection ctx. Aborting."); + break; + } + } + Sleep(100); + continue; + } + } + + if (accf == ACCEPT_FILTER_CONNECT) + { + char *buf; + + /* Create and initialize the accept socket */ +#if APR_HAVE_IPV6 + if (context->accept_socket == INVALID_SOCKET) { + context->accept_socket = socket(ss_listen.ss_family, SOCK_STREAM, + IPPROTO_TCP); + context->socket_family = ss_listen.ss_family; + } + else if (context->socket_family != ss_listen.ss_family) { + closesocket(context->accept_socket); + context->accept_socket = socket(ss_listen.ss_family, SOCK_STREAM, + IPPROTO_TCP); + context->socket_family = ss_listen.ss_family; + } +#else + if (context->accept_socket == INVALID_SOCKET) + context->accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif + + if (context->accept_socket == INVALID_SOCKET) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), + ap_server_conf, APLOGNO(00336) + "winnt_accept: Failed to allocate an accept socket. " + "Temporary resource constraint? Try again."); + Sleep(100); + continue; + } + + buf = context->buff; + + /* AcceptEx on the completion context. The completion context will be + * signaled when a connection is accepted. + */ + if (!lpfnAcceptEx(nlsd, context->accept_socket, buf, 0, + PADDED_ADDR_SIZE, PADDED_ADDR_SIZE, &BytesRead, + &context->overlapped)) { + rv = apr_get_netos_error(); + if ((rv == APR_FROM_OS_ERROR(WSAECONNRESET)) || + (rv == APR_FROM_OS_ERROR(WSAEACCES))) { + /* We can get here when: + * 1) the client disconnects early + * 2) handshake was incomplete + */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + continue; + } + else if ((rv == APR_FROM_OS_ERROR(WSAEINVAL)) || + (rv == APR_FROM_OS_ERROR(WSAENOTSOCK))) { + /* We can get here when: + * 1) TransmitFile does not properly recycle the accept socket (typically + * because the client disconnected) + * 2) there is VPN or Firewall software installed with + * buggy WSAAccept or WSADuplicateSocket implementation + * 3) the dynamic address / adapter has changed + * Give five chances, then fall back on AcceptFilter 'none' + */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00337) + "Child: Encountered too many AcceptEx " + "faults accepting client connections. " + "Possible causes: dynamic address renewal, " + "or incompatible VPN or firewall software. "); + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00338) + "winnt_mpm: falling back to " + "'AcceptFilter none'."); + err_count = 0; + accf = ACCEPT_FILTER_NONE; + } + continue; + } + else if ((rv != APR_FROM_OS_ERROR(ERROR_IO_PENDING)) && + (rv != APR_FROM_OS_ERROR(WSA_IO_PENDING))) { + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00339) + "Child: Encountered too many AcceptEx " + "faults accepting client connections."); + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00340) + "winnt_mpm: falling back to " + "'AcceptFilter none'."); + err_count = 0; + accf = ACCEPT_FILTER_NONE; + goto reinit; + } + continue; + } + + err_count = 0; + events[0] = context->overlapped.hEvent; + + do { + rv = WaitForMultipleObjectsEx(3, events, FALSE, INFINITE, TRUE); + } while (rv == WAIT_IO_COMPLETION); + + if (rv == WAIT_OBJECT_0) { + if ((context->accept_socket != INVALID_SOCKET) && + !GetOverlappedResult((HANDLE)context->accept_socket, + &context->overlapped, + &BytesRead, FALSE)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, + apr_get_os_error(), ap_server_conf, APLOGNO(00341) + "winnt_accept: Asynchronous AcceptEx failed."); + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + } + } + else { + /* exit_event triggered or event handle was closed */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + break; + } + + if (context->accept_socket == INVALID_SOCKET) { + continue; + } + } + err_count = 0; + + /* Potential optimization; consider handing off to the worker */ + + /* Inherit the listen socket settings. Required for + * shutdown() to work + */ + if (setsockopt(context->accept_socket, SOL_SOCKET, + SO_UPDATE_ACCEPT_CONTEXT, (char *)&nlsd, + sizeof(nlsd))) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), + ap_server_conf, APLOGNO(00342) + "setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed."); + /* Not a failure condition. Keep running. */ + } + + /* Get the local & remote address + * TODO; error check + */ + lpfnGetAcceptExSockaddrs(buf, 0, PADDED_ADDR_SIZE, PADDED_ADDR_SIZE, + &context->sa_server, &context->sa_server_len, + &context->sa_client, &context->sa_client_len); + } + else /* accf == ACCEPT_FILTER_NONE */ + { + /* There is no socket reuse without AcceptEx() */ + if (context->accept_socket != INVALID_SOCKET) + closesocket(context->accept_socket); + + /* This could be a persistent event per-listener rather than + * per-accept. However, the event needs to be removed from + * the target socket if not removed from the listen socket + * prior to accept(), or the event select is inherited. + * and must be removed from the accepted socket. + */ + + do { + rv = WaitForMultipleObjectsEx(3, events, FALSE, INFINITE, TRUE); + } while (rv == WAIT_IO_COMPLETION); + + + if (rv != WAIT_OBJECT_0 + 2) { + /* not FD_ACCEPT; + * exit_event triggered or event handle was closed + */ + break; + } + + context->sa_server = (void *) context->buff; + context->sa_server_len = sizeof(context->buff) / 2; + context->sa_client_len = context->sa_server_len; + context->sa_client = (void *) (context->buff + + context->sa_server_len); + + context->accept_socket = accept(nlsd, context->sa_server, + &context->sa_server_len); + + if (context->accept_socket == INVALID_SOCKET) { + + rv = apr_get_netos_error(); + if ( rv == APR_FROM_OS_ERROR(WSAECONNRESET) + || rv == APR_FROM_OS_ERROR(WSAEINPROGRESS) + || rv == APR_FROM_OS_ERROR(WSAEWOULDBLOCK) ) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, + rv, ap_server_conf, APLOGNO(00343) + "accept() failed, retrying."); + continue; + } + + /* A more serious error than 'retry', log it */ + ap_log_error(APLOG_MARK, APLOG_WARNING, + rv, ap_server_conf, APLOGNO(00344) + "accept() failed."); + + if ( rv == APR_FROM_OS_ERROR(WSAEMFILE) + || rv == APR_FROM_OS_ERROR(WSAENOBUFS) ) { + /* Hopefully a temporary condition in the provider? */ + Sleep(100); + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00345) + "Child: Encountered too many accept() " + "resource faults, aborting."); + break; + } + continue; + } + break; + } + /* Per MSDN, cancel the inherited association of this socket + * to the WSAEventSelect API, and restore the state corresponding + * to apr_os_sock_make's default assumptions (really, a flaw within + * os_sock_make and os_sock_put that it does not query). + */ + WSAEventSelect(context->accept_socket, 0, 0); + err_count = 0; + + context->sa_server_len = sizeof(context->buff) / 2; + if (getsockname(context->accept_socket, context->sa_server, + &context->sa_server_len) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00346) + "getsockname failed"); + continue; + } + if ((getpeername(context->accept_socket, context->sa_client, + &context->sa_client_len)) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00347) + "getpeername failed"); + memset(&context->sa_client, '\0', sizeof(context->sa_client)); + } + } + + sockinfo.os_sock = &context->accept_socket; + sockinfo.local = context->sa_server; + sockinfo.remote = context->sa_client; + sockinfo.family = context->sa_server->sa_family; + sockinfo.type = SOCK_STREAM; + sockinfo.protocol = IPPROTO_TCP; + /* Restore the state corresponding to apr_os_sock_make's default + * assumption of timeout -1 (really, a flaw of os_sock_make and + * os_sock_put that it does not query to determine ->timeout). + * XXX: Upon a fix to APR, these three statements should disappear. + */ + ioctlsocket(context->accept_socket, FIONBIO, &zero); + setsockopt(context->accept_socket, SOL_SOCKET, SO_RCVTIMEO, + (char *) &zero, sizeof(zero)); + setsockopt(context->accept_socket, SOL_SOCKET, SO_SNDTIMEO, + (char *) &zero, sizeof(zero)); + apr_os_sock_make(&context->sock, &sockinfo, context->ptrans); + + /* When a connection is received, send an io completion notification + * to the ThreadDispatchIOCP. + */ + PostQueuedCompletionStatus(ThreadDispatchIOCP, BytesRead, + IOCP_CONNECTION_ACCEPTED, + &context->overlapped); + context = NULL; + } + if (accf == ACCEPT_FILTER_NONE) + CloseHandle(events[2]); + + if (!shutdown_in_progress) { + /* Yow, hit an irrecoverable error! Tell the child to die. */ + SetEvent(exit_event); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00348) + "Child: Accept thread exiting."); + return 0; +} + + +static winnt_conn_ctx_t *winnt_get_connection(winnt_conn_ctx_t *context) +{ + int rc; + DWORD BytesRead; + LPOVERLAPPED pol; +#ifdef _WIN64 + ULONG_PTR CompKey; +#else + DWORD CompKey; +#endif + + mpm_recycle_completion_context(context); + + apr_atomic_inc32(&g_blocked_threads); + while (1) { + if (workers_may_exit) { + apr_atomic_dec32(&g_blocked_threads); + return NULL; + } + rc = GetQueuedCompletionStatus(ThreadDispatchIOCP, &BytesRead, + &CompKey, &pol, INFINITE); + if (!rc) { + rc = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, ap_server_conf, APLOGNO(00349) + "Child: GetQueuedCompletionStatus returned %d", + rc); + continue; + } + + switch (CompKey) { + case IOCP_CONNECTION_ACCEPTED: + context = CONTAINING_RECORD(pol, winnt_conn_ctx_t, overlapped); + break; + case IOCP_SHUTDOWN: + apr_atomic_dec32(&g_blocked_threads); + return NULL; + default: + apr_atomic_dec32(&g_blocked_threads); + return NULL; + } + break; + } + apr_atomic_dec32(&g_blocked_threads); + + return context; +} + +/* + * worker_main() + * Main entry point for the worker threads. Worker threads block in + * win*_get_connection() awaiting a connection to service. + */ +static DWORD __stdcall worker_main(void *thread_num_val) +{ + apr_thread_t *thd; + apr_os_thread_t osthd; + static int requests_this_child = 0; + winnt_conn_ctx_t *context = NULL; + int thread_num = (int)thread_num_val; + ap_sb_handle_t *sbh; + conn_rec *c; + apr_int32_t disconnected; + + osthd = apr_os_thread_current(); + + while (1) { + + ap_update_child_status_from_indexes(0, thread_num, SERVER_READY, NULL); + + /* Grab a connection off the network */ + context = winnt_get_connection(context); + + if (!context) { + /* Time for the thread to exit */ + break; + } + + /* Have we hit MaxConnectionsPerChild connections? */ + if (ap_max_requests_per_child) { + requests_this_child++; + if (requests_this_child > ap_max_requests_per_child) { + SetEvent(max_requests_per_child_event); + } + } + + ap_create_sb_handle(&sbh, context->ptrans, 0, thread_num); + c = ap_run_create_connection(context->ptrans, ap_server_conf, + context->sock, thread_num, sbh, + context->ba); + + if (!c) { + /* ap_run_create_connection closes the socket on failure */ + context->accept_socket = INVALID_SOCKET; + continue; + } + + thd = NULL; + apr_os_thread_put(&thd, &osthd, context->ptrans); + c->current_thread = thd; + + ap_process_connection(c, context->sock); + + ap_lingering_close(c); + + apr_socket_opt_get(context->sock, APR_SO_DISCONNECTED, &disconnected); + if (!disconnected) { + context->accept_socket = INVALID_SOCKET; + } + } + + ap_update_child_status_from_indexes(0, thread_num, SERVER_DEAD, NULL); + + return 0; +} + + +static void cleanup_thread(HANDLE *handles, int *thread_cnt, + int thread_to_clean) +{ + int i; + + CloseHandle(handles[thread_to_clean]); + for (i = thread_to_clean; i < ((*thread_cnt) - 1); i++) + handles[i] = handles[i + 1]; + (*thread_cnt)--; +} + + +/* + * child_main() + * Entry point for the main control thread for the child process. + * This thread creates the accept thread, worker threads and + * monitors the child process for maintenance and shutdown + * events. + */ +static void create_listener_thread(void) +{ + unsigned tid; + int num_listeners = 0; + /* Start an accept thread per listener + * XXX: Why would we have a NULL sd in our listeners? + */ + ap_listen_rec *lr; + + /* Number of completion_contexts allowed in the system is + * (ap_threads_per_child + num_listeners). We need the additional + * completion contexts to prevent server hangs when ThreadsPerChild + * is configured to something less than or equal to the number + * of listeners. This is not a usual case, but people have + * encountered it. + */ + for (lr = ap_listeners; lr ; lr = lr->next) { + num_listeners++; + } + max_num_completion_contexts = ap_threads_per_child + num_listeners; + + /* Now start a thread per listener */ + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->sd != NULL) { + /* A smaller stack is sufficient. + * To convert to CreateThread, the returned handle cannot be + * ignored, it must be closed/joined. + */ + _beginthreadex(NULL, 65536, winnt_accept, + (void *) lr, stack_res_flag, &tid); + } + } +} + + +void child_main(apr_pool_t *pconf, DWORD parent_pid) +{ + apr_status_t status; + apr_hash_t *ht; + ap_listen_rec *lr; + HANDLE child_events[3]; + HANDLE *child_handles; + int listener_started = 0; + int threads_created = 0; + int watch_thread; + int time_remains; + int cld; + DWORD tid; + int rv; + int i; + int num_events; + + /* Get a sub context for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + ap_run_child_init(pchild, ap_server_conf); + ht = apr_hash_make(pchild); + + /* Initialize the child_events */ + max_requests_per_child_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!max_requests_per_child_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00350) + "Child: Failed to create a max_requests event."); + exit(APEXIT_CHILDINIT); + } + child_events[0] = exit_event; + child_events[1] = max_requests_per_child_event; + + if (parent_pid != my_pid) { + child_events[2] = OpenProcess(SYNCHRONIZE, FALSE, parent_pid); + if (child_events[2] == NULL) { + num_events = 2; + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(02643) + "Child: Failed to open handle to parent process %ld; " + "will not react to abrupt parent termination", parent_pid); + } + else { + num_events = 3; + } + } + else { + /* presumably -DONE_PROCESS */ + child_events[2] = NULL; + num_events = 2; + } + + /* + * Wait until we have permission to start accepting connections. + * start_mutex is used to ensure that only one child ever + * goes into the listen/accept loop at once. + */ + status = apr_proc_mutex_lock(start_mutex); + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf, APLOGNO(00351) + "Child: Failed to acquire the start_mutex. " + "Process will exit."); + exit(APEXIT_CHILDINIT); + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00352) + "Child: Acquired the start mutex."); + + /* + * Create the worker thread dispatch IOCompletionPort + */ + /* Create the worker thread dispatch IOCP */ + ThreadDispatchIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, 0, 0); + apr_thread_mutex_create(&qlock, APR_THREAD_MUTEX_DEFAULT, pchild); + qwait_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!qwait_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00353) + "Child: Failed to create a qwait event."); + exit(APEXIT_CHILDINIT); + } + + /* + * Create the pool of worker threads + */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00354) + "Child: Starting %d worker threads.", ap_threads_per_child); + child_handles = (HANDLE) apr_pcalloc(pchild, ap_threads_per_child + * sizeof(HANDLE)); + apr_thread_mutex_create(&child_lock, APR_THREAD_MUTEX_DEFAULT, pchild); + + while (1) { + for (i = 0; i < ap_threads_per_child; i++) { + int *score_idx; + int status = ap_scoreboard_image->servers[0][i].status; + if (status != SERVER_GRACEFUL && status != SERVER_DEAD) { + continue; + } + ap_update_child_status_from_indexes(0, i, SERVER_STARTING, NULL); + + child_handles[i] = CreateThread(NULL, ap_thread_stacksize, + worker_main, (void *) i, + stack_res_flag, &tid); + if (child_handles[i] == 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00355) + "Child: CreateThread failed. Unable to " + "create all worker threads. Created %d of the %d " + "threads requested with the ThreadsPerChild " + "configuration directive.", + threads_created, ap_threads_per_child); + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + goto shutdown; + } + threads_created++; + /* Save the score board index in ht keyed to the thread handle. + * We need this when cleaning up threads down below... + */ + apr_thread_mutex_lock(child_lock); + score_idx = apr_pcalloc(pchild, sizeof(int)); + *score_idx = i; + apr_hash_set(ht, &child_handles[i], sizeof(HANDLE), score_idx); + apr_thread_mutex_unlock(child_lock); + } + /* Start the listener only when workers are available */ + if (!listener_started && threads_created) { + create_listener_thread(); + listener_started = 1; + winnt_mpm_state = AP_MPMQ_RUNNING; + } + if (threads_created == ap_threads_per_child) { + break; + } + /* Check to see if the child has been told to exit */ + if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT) { + break; + } + /* wait for previous generation to clean up an entry in the scoreboard + */ + apr_sleep(1 * APR_USEC_PER_SEC); + } + + /* Wait for one of these events: + * exit_event: + * The exit_event is signaled by the parent process to notify + * the child that it is time to exit. + * + * max_requests_per_child_event: + * This event is signaled by the worker threads to indicate that + * the process has handled MaxConnectionsPerChild connections. + * + * parent process exiting + * + * TIMEOUT: + * To do periodic maintenance on the server (check for thread exits, + * number of completion contexts, etc.) + * + * XXX: thread exits *aren't* being checked. + * + * XXX: other_child - we need the process handles to the other children + * in order to map them to apr_proc_other_child_read (which is not + * named well, it's more like a_p_o_c_died.) + * + * XXX: however - if we get a_p_o_c handle inheritance working, and + * the parent process creates other children and passes the pipes + * to our worker processes, then we have no business doing such + * things in the child_main loop, but should happen in master_main. + */ + while (1) { +#if !APR_HAS_OTHER_CHILD + rv = WaitForMultipleObjects(num_events, (HANDLE *)child_events, FALSE, INFINITE); + cld = rv - WAIT_OBJECT_0; +#else + /* THIS IS THE EXPECTED BUILD VARIATION -- APR_HAS_OTHER_CHILD */ + rv = WaitForMultipleObjects(num_events, (HANDLE *)child_events, FALSE, 1000); + cld = rv - WAIT_OBJECT_0; + if (rv == WAIT_TIMEOUT) { + apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); + } + else +#endif + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00356) + "Child: WAIT_FAILED -- shutting down server"); + /* check handle validity to identify a possible culprit */ + for (i = 0; i < num_events; i++) { + DWORD out_flags; + + if (0 == GetHandleInformation(child_events[i], &out_flags)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(02644) + "Child: Event handle #%d (%pp) is invalid", + i, child_events[i]); + } + } + break; + } + else if (cld == 0) { + /* Exit event was signaled */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00357) + "Child: Exit event signaled. Child process is " + "ending."); + break; + } + else if (cld == 2) { + /* The parent is dead. Shutdown the child process. */ + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(02538) + "Child: Parent process exited abruptly. Child process " + "is ending"); + break; + } + else { + /* MaxConnectionsPerChild event set by the worker threads. + * Signal the parent to restart + */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00358) + "Child: Process exiting because it reached " + "MaxConnectionsPerChild. Signaling the parent to " + "restart a new child process."); + ap_signal_parent(SIGNAL_PARENT_RESTART); + break; + } + } + + /* + * Time to shutdown the child process + */ + + shutdown: + + winnt_mpm_state = AP_MPMQ_STOPPING; + + /* Close the listening sockets. Note, we must close the listeners + * before closing any accept sockets pending in AcceptEx to prevent + * memory leaks in the kernel. + */ + for (lr = ap_listeners; lr ; lr = lr->next) { + apr_socket_close(lr->sd); + } + + /* Shutdown listener threads and pending AcceptEx sockets + * but allow the worker threads to continue consuming from + * the queue of accepted connections. + */ + shutdown_in_progress = 1; + + Sleep(1000); + + /* Tell the worker threads to exit */ + workers_may_exit = 1; + + /* Release the start_mutex to let the new process (in the restart + * scenario) a chance to begin accepting and servicing requests + */ + rv = apr_proc_mutex_unlock(start_mutex); + if (rv == APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00359) + "Child: Released the start mutex"); + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00360) + "Child: Failure releasing the start mutex"); + } + + /* Shutdown the worker threads + * Post worker threads blocked on the ThreadDispatch IOCompletion port + */ + while (g_blocked_threads > 0) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00361) + "Child: %d threads blocked on the completion port", + g_blocked_threads); + for (i=g_blocked_threads; i > 0; i--) { + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, + IOCP_SHUTDOWN, NULL); + } + Sleep(1000); + } + /* Empty the accept queue of completion contexts */ + apr_thread_mutex_lock(qlock); + while (qhead) { + CloseHandle(qhead->overlapped.hEvent); + closesocket(qhead->accept_socket); + qhead = qhead->next; + } + apr_thread_mutex_unlock(qlock); + + /* Give busy threads a chance to service their connections + * (no more than the global server timeout period which + * we track in msec remaining). + */ + watch_thread = 0; + time_remains = (int)(ap_server_conf->timeout / APR_TIME_C(1000)); + + while (threads_created) + { + int nFailsafe = MAXIMUM_WAIT_OBJECTS; + DWORD dwRet; + + /* Every time we roll over to wait on the first group + * of MAXIMUM_WAIT_OBJECTS threads, take a breather, + * and infrequently update the error log. + */ + if (watch_thread >= threads_created) { + if ((time_remains -= 100) < 0) + break; + + /* Every 30 seconds give an update */ + if ((time_remains % 30000) == 0) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, + ap_server_conf, APLOGNO(00362) + "Child: Waiting %d more seconds " + "for %d worker threads to finish.", + time_remains / 1000, threads_created); + } + /* We'll poll from the top, 10 times per second */ + Sleep(100); + watch_thread = 0; + } + + /* Fairness, on each iteration we will pick up with the thread + * after the one we just removed, even if it's a single thread. + * We don't block here. + */ + dwRet = WaitForMultipleObjects(min(threads_created - watch_thread, + MAXIMUM_WAIT_OBJECTS), + child_handles + watch_thread, 0, 0); + + if (dwRet == WAIT_FAILED) { + break; + } + if (dwRet == WAIT_TIMEOUT) { + /* none ready */ + watch_thread += MAXIMUM_WAIT_OBJECTS; + continue; + } + else if (dwRet >= WAIT_ABANDONED_0) { + /* We just got the ownership of the object, which + * should happen at most MAXIMUM_WAIT_OBJECTS times. + * It does NOT mean that the object is signaled. + */ + if ((nFailsafe--) < 1) + break; + } + else { + watch_thread += (dwRet - WAIT_OBJECT_0); + if (watch_thread >= threads_created) + break; + cleanup_thread(child_handles, &threads_created, watch_thread); + } + } + + /* Kill remaining threads off the hard way */ + if (threads_created) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00363) + "Child: Terminating %d threads that failed to exit.", + threads_created); + } + for (i = 0; i < threads_created; i++) { + int *idx; + TerminateThread(child_handles[i], 1); + CloseHandle(child_handles[i]); + /* Reset the scoreboard entry for the thread we just whacked */ + idx = apr_hash_get(ht, &child_handles[i], sizeof(HANDLE)); + if (idx) { + ap_update_child_status_from_indexes(0, *idx, SERVER_DEAD, NULL); + } + } + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00364) + "Child: All worker threads have exited."); + + apr_thread_mutex_destroy(child_lock); + apr_thread_mutex_destroy(qlock); + CloseHandle(qwait_event); + CloseHandle(ThreadDispatchIOCP); + + apr_pool_destroy(pchild); + CloseHandle(exit_event); + if (child_events[2] != NULL) { + CloseHandle(child_events[2]); + } +} + +#endif /* def WIN32 */ diff --git a/server/mpm/winnt/config.m4 b/server/mpm/winnt/config.m4 new file mode 100644 index 0000000..5c1fd42 --- /dev/null +++ b/server/mpm/winnt/config.m4 @@ -0,0 +1,10 @@ +AC_MSG_CHECKING(if WinNT MPM supports this platform) +case $host in + *mingw32*) + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(winnt, no, yes) + ;; + *) + AC_MSG_RESULT(no) + ;; +esac diff --git a/server/mpm/winnt/config3.m4 b/server/mpm/winnt/config3.m4 new file mode 100644 index 0000000..f937e40 --- /dev/null +++ b/server/mpm/winnt/config3.m4 @@ -0,0 +1,2 @@ +winnt_objects="child.lo mpm_winnt.lo nt_eventlog.lo service.lo" +APACHE_MPM_MODULE(winnt, $enable_mpm_winnt, $winnt_objects) diff --git a/server/mpm/winnt/mpm_default.h b/server/mpm/winnt/mpm_default.h new file mode 100644 index 0000000..b5350d4 --- /dev/null +++ b/server/mpm/winnt/mpm_default.h @@ -0,0 +1,60 @@ +/* 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. + */ + +/** + * @file winnt/mpm_default.h + * @brief win32 MPM defaults + * + * @defgroup APACHE_MPM_WINNT WinNT MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Default limit on the maximum setting of the ThreadsPerChild configuration + * directive. This limit can be overridden with the ThreadLimit directive. + * This limit directly influences the amount of shared storage that is allocated + * for the scoreboard. DEFAULT_THREAD_LIMIT represents a good compromise + * between scoreboard size and the ability of the server to handle the most + * common installation requirements. + */ +#ifndef DEFAULT_THREAD_LIMIT +#define DEFAULT_THREAD_LIMIT 1920 +#endif + +/* The ThreadLimit directive can be used to override the DEFAULT_THREAD_LIMIT. + * ThreadLimit cannot be tuned larger than MAX_THREAD_LIMIT. + * This is a sort of compile-time limit to help catch typos. + */ +#ifndef MAX_THREAD_LIMIT +#define MAX_THREAD_LIMIT 15000 +#endif + +/* Number of threads started in the child process in the absence + * of a ThreadsPerChild configuration directive + */ +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 64 +#endif + +/* Max number of child processes allowed. + */ +#define HARD_SERVER_LIMIT 1 + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/winnt/mpm_winnt.c b/server/mpm/winnt/mpm_winnt.c new file mode 100644 index 0000000..3d71f80 --- /dev/null +++ b/server/mpm/winnt/mpm_winnt.c @@ -0,0 +1,1786 @@ +/* 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. + */ + +#ifdef WIN32 + +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "apr_portable.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_shm.h" +#include "apr_thread_mutex.h" +#include "ap_mpm.h" +#include "apr_general.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "mpm_default.h" +#include "mpm_winnt.h" +#include "mpm_common.h" +#include <malloc.h> +#include "apr_atomic.h" +#include "scoreboard.h" + +#ifdef __WATCOMC__ +#define _environ environ +#endif + +#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION /* missing on MinGW */ +#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 +#endif + +/* Because ap_setup_listeners() is skipped in the child, any merging + * of [::]:80 and 0.0.0.0:80 for AP_ENABLE_V4_MAPPED in the parent + * won't have taken place in the child, so the child will expect to + * read two sockets for "Listen 80" but the parent will send only + * one. + */ +#ifdef AP_ENABLE_V4_MAPPED +#error The WinNT MPM does not currently support AP_ENABLE_V4_MAPPED +#endif + +/* scoreboard.c does the heavy lifting; all we do is create the child + * score by moving a handle down the pipe into the child's stdin. + */ +extern apr_shm_t *ap_scoreboard_shm; + +/* my_generation is returned to the scoreboard code */ +static volatile ap_generation_t my_generation=0; + +/* Definitions of WINNT MPM specific config globals */ +static HANDLE shutdown_event; /* used to signal the parent to shutdown */ +static HANDLE restart_event; /* used to signal the parent to restart */ + +static int one_process = 0; +static char const* signal_arg = NULL; + +OSVERSIONINFO osver; /* VER_PLATFORM_WIN32_NT */ + +/* set by child_main to STACK_SIZE_PARAM_IS_A_RESERVATION for NT >= 5.1 (XP/2003) */ +DWORD stack_res_flag; + +static DWORD parent_pid; +DWORD my_pid; + +/* used by parent to signal the child to start and exit */ +apr_proc_mutex_t *start_mutex; +HANDLE exit_event; + +int ap_threads_per_child = 0; +static int thread_limit = 0; +static int first_thread_limit = 0; +int winnt_mpm_state = AP_MPMQ_STARTING; + +/* shared by service.c as global, although + * perhaps it should be private. + */ +apr_pool_t *pconf; + +/* Only one of these, the pipe from our parent, meant only for + * one child worker's consumption (not to be inherited!) + * XXX: decorate this name for the trunk branch, was left simplified + * only to make the 2.2 patch trivial to read. + */ +static HANDLE pipe; + +/* + * Command processors + */ + +static const char *set_threads_per_child (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_per_child = atoi(arg); + return NULL; +} +static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + thread_limit = atoi(arg); + return NULL; +} + +static const command_rec winnt_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF, + "Number of threads each child creates" ), +AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF, + "Maximum worker threads in a server for this run of Apache"), +{ NULL } +}; + +static void winnt_note_child_started(int slot, pid_t pid) +{ + ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].generation = my_generation; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + my_generation, slot, MPM_CHILD_STARTED); +} + +static void winnt_note_child_killed(int slot) +{ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation, + slot, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[slot].pid = 0; +} + +/* + * Signalling Apache on NT. + * + * Under Unix, Apache can be told to shutdown or restart by sending various + * signals (HUP, USR, TERM). On NT we don't have easy access to signals, so + * we use "events" instead. The parent apache process goes into a loop + * where it waits forever for a set of events. Two of those events are + * called + * + * apPID_shutdown + * apPID_restart + * + * (where PID is the PID of the apache parent process). When one of these + * is signalled, the Apache parent performs the appropriate action. The events + * can become signalled through internal Apache methods (e.g. if the child + * finds a fatal error and needs to kill its parent), via the service + * control manager (the control thread will signal the shutdown event when + * requested to stop the Apache service), from the -k Apache command line, + * or from any external program which finds the Apache PID from the + * httpd.pid file. + * + * The signal_parent() function, below, is used to signal one of these events. + * It can be called by any child or parent process, since it does not + * rely on global variables. + * + * On entry, type gives the event to signal. 0 means shutdown, 1 means + * graceful restart. + */ +/* + * Initialise the signal names, in the global variables signal_name_prefix, + * signal_restart_name and signal_shutdown_name. + */ +#define MAX_SIGNAL_NAME 30 /* Long enough for apPID_shutdown, where PID is an int */ +static char signal_name_prefix[MAX_SIGNAL_NAME]; +static char signal_restart_name[MAX_SIGNAL_NAME]; +static char signal_shutdown_name[MAX_SIGNAL_NAME]; +static void setup_signal_names(char *prefix) +{ + apr_snprintf(signal_name_prefix, sizeof(signal_name_prefix), prefix); + apr_snprintf(signal_shutdown_name, sizeof(signal_shutdown_name), + "%s_shutdown", signal_name_prefix); + apr_snprintf(signal_restart_name, sizeof(signal_restart_name), + "%s_restart", signal_name_prefix); +} + +AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type) +{ + HANDLE e; + char *signal_name; + + if (parent_pid == my_pid) { + switch(type) { + case SIGNAL_PARENT_SHUTDOWN: + { + SetEvent(shutdown_event); + break; + } + /* This MPM supports only graceful restarts right now */ + case SIGNAL_PARENT_RESTART: + case SIGNAL_PARENT_RESTART_GRACEFUL: + { + SetEvent(restart_event); + break; + } + } + return; + } + + switch(type) { + case SIGNAL_PARENT_SHUTDOWN: + { + signal_name = signal_shutdown_name; + break; + } + /* This MPM supports only graceful restarts right now */ + case SIGNAL_PARENT_RESTART: + case SIGNAL_PARENT_RESTART_GRACEFUL: + { + signal_name = signal_restart_name; + break; + } + default: + return; + } + + e = OpenEvent(EVENT_MODIFY_STATE, FALSE, signal_name); + if (!e) { + /* Um, problem, can't signal the parent, which means we can't + * signal ourselves to die. Ignore for now... + */ + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, APLOGNO(00382) + "OpenEvent on %s event", signal_name); + return; + } + if (SetEvent(e) == 0) { + /* Same problem as above */ + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, APLOGNO(00383) + "SetEvent on %s event", signal_name); + CloseHandle(e); + return; + } + CloseHandle(e); +} + + +/* + * Passed the following handles [in sync with send_handles_to_child()] + * + * ready event [signal the parent immediately, then close] + * exit event [save to poll later] + * start mutex [signal from the parent to begin accept()] + * scoreboard shm handle [to recreate the ap_scoreboard] + */ +static void get_handles_from_parent(server_rec *s, HANDLE *child_exit_event, + apr_proc_mutex_t **child_start_mutex, + apr_shm_t **scoreboard_shm) +{ + HANDLE hScore; + HANDLE ready_event; + HANDLE os_start; + DWORD BytesRead; + void *sb_shared; + apr_status_t rv; + + /* *** We now do this way back in winnt_rewrite_args + * pipe = GetStdHandle(STD_INPUT_HANDLE); + */ + if (!ReadFile(pipe, &ready_event, sizeof(HANDLE), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(HANDLE))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00384) + "Child: Unable to retrieve the ready event from the parent"); + exit(APEXIT_CHILDINIT); + } + + SetEvent(ready_event); + CloseHandle(ready_event); + + if (!ReadFile(pipe, child_exit_event, sizeof(HANDLE), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(HANDLE))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00385) + "Child: Unable to retrieve the exit event from the parent"); + exit(APEXIT_CHILDINIT); + } + + if (!ReadFile(pipe, &os_start, sizeof(os_start), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(os_start))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00386) + "Child: Unable to retrieve the start_mutex from the parent"); + exit(APEXIT_CHILDINIT); + } + *child_start_mutex = NULL; + if ((rv = apr_os_proc_mutex_put(child_start_mutex, &os_start, s->process->pool)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00387) + "Child: Unable to access the start_mutex from the parent"); + exit(APEXIT_CHILDINIT); + } + + if (!ReadFile(pipe, &hScore, sizeof(hScore), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(hScore))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00388) + "Child: Unable to retrieve the scoreboard from the parent"); + exit(APEXIT_CHILDINIT); + } + *scoreboard_shm = NULL; + if ((rv = apr_os_shm_put(scoreboard_shm, &hScore, s->process->pool)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00389) + "Child: Unable to access the scoreboard from the parent"); + exit(APEXIT_CHILDINIT); + } + + rv = ap_reopen_scoreboard(s->process->pool, scoreboard_shm, 1); + if (rv || !(sb_shared = apr_shm_baseaddr_get(*scoreboard_shm))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00390) + "Child: Unable to reopen the scoreboard from the parent"); + exit(APEXIT_CHILDINIT); + } + /* We must 'initialize' the scoreboard to relink all the + * process-local pointer arrays into the shared memory block. + */ + ap_init_scoreboard(sb_shared); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00391) + "Child: Retrieved our scoreboard from the parent."); +} + + +static int send_handles_to_child(apr_pool_t *p, + HANDLE child_ready_event, + HANDLE child_exit_event, + apr_proc_mutex_t *child_start_mutex, + apr_shm_t *scoreboard_shm, + HANDLE hProcess, + apr_file_t *child_in) +{ + apr_status_t rv; + HANDLE hCurrentProcess = GetCurrentProcess(); + HANDLE hDup; + HANDLE os_start; + HANDLE hScore; + apr_size_t BytesWritten; + + if ((rv = apr_file_write_full(child_in, &my_generation, + sizeof(my_generation), NULL)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(02964) + "Parent: Unable to send its generation to the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, child_ready_event, hProcess, &hDup, + EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00392) + "Parent: Unable to duplicate the ready event handle for the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00393) + "Parent: Unable to send the exit event handle to the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, child_exit_event, hProcess, &hDup, + EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00394) + "Parent: Unable to duplicate the exit event handle for the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00395) + "Parent: Unable to send the exit event handle to the child"); + return -1; + } + if ((rv = apr_os_proc_mutex_get(&os_start, child_start_mutex)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00396) + "Parent: Unable to retrieve the start mutex for the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, os_start, hProcess, &hDup, + SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00397) + "Parent: Unable to duplicate the start mutex to the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00398) + "Parent: Unable to send the start mutex to the child"); + return -1; + } + if ((rv = apr_os_shm_get(&hScore, scoreboard_shm)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00399) + "Parent: Unable to retrieve the scoreboard handle for the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, hScore, hProcess, &hDup, + FILE_MAP_READ | FILE_MAP_WRITE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00400) + "Parent: Unable to duplicate the scoreboard handle to the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00401) + "Parent: Unable to send the scoreboard handle to the child"); + return -1; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00402) + "Parent: Sent the scoreboard to the child"); + return 0; +} + + +/* + * get_listeners_from_parent() + * The listen sockets are opened in the parent. This function, which runs + * exclusively in the child process, receives them from the parent and + * makes them availeble in the child. + */ +static void get_listeners_from_parent(server_rec *s) +{ + WSAPROTOCOL_INFO WSAProtocolInfo; + ap_listen_rec *lr; + DWORD BytesRead; + int lcnt = 0; + SOCKET nsd; + + /* Set up a default listener if necessary */ + if (ap_listeners == NULL) { + ap_listen_rec *lr; + lr = apr_palloc(s->process->pool, sizeof(ap_listen_rec)); + lr->sd = NULL; + lr->next = ap_listeners; + ap_listeners = lr; + } + + /* Open the pipe to the parent process to receive the inherited socket + * data. The sockets have been set to listening in the parent process. + * + * *** We now do this way back in winnt_rewrite_args + * pipe = GetStdHandle(STD_INPUT_HANDLE); + */ + for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00403) + "Child: Waiting for data for listening socket %pI", + lr->bind_addr); + if (!ReadFile(pipe, &WSAProtocolInfo, sizeof(WSAPROTOCOL_INFO), + &BytesRead, (LPOVERLAPPED) NULL)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00404) + "Child: Unable to read socket data from parent"); + exit(APEXIT_CHILDINIT); + } + + nsd = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, + &WSAProtocolInfo, 0, 0); + if (nsd == INVALID_SOCKET) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, APLOGNO(00405) + "Child: WSASocket failed to open the inherited socket"); + exit(APEXIT_CHILDINIT); + } + + if (!SetHandleInformation((HANDLE)nsd, HANDLE_FLAG_INHERIT, 0)) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(00406) + "Child: SetHandleInformation failed"); + } + apr_os_sock_put(&lr->sd, &nsd, s->process->pool); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00407) + "Child: retrieved %d listeners from parent", lcnt); +} + + +static int send_listeners_to_child(apr_pool_t *p, DWORD dwProcessId, + apr_file_t *child_in) +{ + apr_status_t rv; + int lcnt = 0; + ap_listen_rec *lr; + LPWSAPROTOCOL_INFO lpWSAProtocolInfo; + apr_size_t BytesWritten; + + /* Run the chain of open sockets. For each socket, duplicate it + * for the target process then send the WSAPROTOCOL_INFO + * (returned by dup socket) to the child. + */ + for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) { + apr_os_sock_t nsd; + lpWSAProtocolInfo = apr_pcalloc(p, sizeof(WSAPROTOCOL_INFO)); + apr_os_sock_get(&nsd, lr->sd); + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00408) + "Parent: Duplicating socket %d (%pI) and sending it to child process %lu", + nsd, lr->bind_addr, dwProcessId); + if (WSADuplicateSocket(nsd, dwProcessId, + lpWSAProtocolInfo) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, APLOGNO(00409) + "Parent: WSADuplicateSocket failed for socket %d. Check the FAQ.", nsd); + return -1; + } + + if ((rv = apr_file_write_full(child_in, lpWSAProtocolInfo, + sizeof(WSAPROTOCOL_INFO), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00410) + "Parent: Unable to write duplicated socket %d to the child.", nsd); + return -1; + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00411) + "Parent: Sent %d listeners to child %lu", lcnt, dwProcessId); + return 0; +} + +enum waitlist_e { + waitlist_ready = 0, + waitlist_term = 1 +}; + +static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_event, + DWORD *child_pid) +{ + /* These NEVER change for the lifetime of this parent + */ + static char **args = NULL; + static char pidbuf[28]; + + apr_status_t rv; + apr_pool_t *ptemp; + apr_procattr_t *attr; + apr_proc_t new_child; + HANDLE hExitEvent; + HANDLE waitlist[2]; /* see waitlist_e */ + char *cmd; + char *cwd; + char **env; + int envc; + + apr_pool_create_ex(&ptemp, p, NULL, NULL); + + /* Build the command line. Should look something like this: + * C:/apache/bin/httpd.exe -f ap_server_confname + * First, get the path to the executable... + */ + apr_procattr_create(&attr, ptemp); + apr_procattr_cmdtype_set(attr, APR_PROGRAM); + apr_procattr_detach_set(attr, 1); + if (((rv = apr_filepath_get(&cwd, 0, ptemp)) != APR_SUCCESS) + || ((rv = apr_procattr_dir_set(attr, cwd)) != APR_SUCCESS)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00412) + "Parent: Failed to get the current path"); + } + + if (!args) { + /* Build the args array, only once since it won't change + * for the lifetime of this parent process. + */ + if ((rv = ap_os_proc_filepath(&cmd, ptemp)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, ERROR_BAD_PATHNAME, ap_server_conf, APLOGNO(00413) + "Parent: Failed to get full path of %s", + ap_server_conf->process->argv[0]); + apr_pool_destroy(ptemp); + return -1; + } + + args = malloc((ap_server_conf->process->argc + 1) * sizeof (char*)); + memcpy(args + 1, ap_server_conf->process->argv + 1, + (ap_server_conf->process->argc - 1) * sizeof (char*)); + args[0] = malloc(strlen(cmd) + 1); + strcpy(args[0], cmd); + args[ap_server_conf->process->argc] = NULL; + } + else { + cmd = args[0]; + } + + /* Create a pipe to send handles to the child */ + if ((rv = apr_procattr_io_set(attr, APR_FULL_BLOCK, + APR_NO_PIPE, APR_NO_PIPE)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00414) + "Parent: Unable to create child stdin pipe."); + apr_pool_destroy(ptemp); + return -1; + } + + /* Create the child_ready_event */ + waitlist[waitlist_ready] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!waitlist[waitlist_ready]) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00415) + "Parent: Could not create ready event for child process"); + apr_pool_destroy (ptemp); + return -1; + } + + /* Create the child_exit_event */ + hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hExitEvent) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00416) + "Parent: Could not create exit event for child process"); + apr_pool_destroy(ptemp); + CloseHandle(waitlist[waitlist_ready]); + return -1; + } + + /* Build the env array */ + for (envc = 0; _environ[envc]; ++envc) { + ; + } + env = apr_palloc(ptemp, (envc + 2) * sizeof (char*)); + memcpy(env, _environ, envc * sizeof (char*)); + apr_snprintf(pidbuf, sizeof(pidbuf), "AP_PARENT_PID=%lu", parent_pid); + env[envc] = pidbuf; + env[envc + 1] = NULL; + + rv = apr_proc_create(&new_child, cmd, (const char * const *)args, + (const char * const *)env, attr, ptemp); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00417) + "Parent: Failed to create the child process."); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(waitlist[waitlist_ready]); + CloseHandle(new_child.hproc); + return -1; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00418) + "Parent: Created child process %d", new_child.pid); + + if (send_handles_to_child(ptemp, waitlist[waitlist_ready], hExitEvent, + start_mutex, ap_scoreboard_shm, + new_child.hproc, new_child.in)) { + /* + * This error is fatal, mop up the child and move on + * We toggle the child's exit event to cause this child + * to quit even as it is attempting to start. + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(waitlist[waitlist_ready]); + CloseHandle(new_child.hproc); + return -1; + } + + /* Important: + * Give the child process a chance to run before dup'ing the sockets. + * We have already set the listening sockets noninheritable, but if + * WSADuplicateSocket runs before the child process initializes + * the listeners will be inherited anyway. + */ + waitlist[waitlist_term] = new_child.hproc; + rv = WaitForMultipleObjects(2, waitlist, FALSE, INFINITE); + CloseHandle(waitlist[waitlist_ready]); + if (rv != WAIT_OBJECT_0) { + /* + * Outch... that isn't a ready signal. It's dead, Jim! + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(new_child.hproc); + return -1; + } + + if (send_listeners_to_child(ptemp, new_child.pid, new_child.in)) { + /* + * This error is fatal, mop up the child and move on + * We toggle the child's exit event to cause this child + * to quit even as it is attempting to start. + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(new_child.hproc); + return -1; + } + + apr_file_close(new_child.in); + + *child_exit_event = hExitEvent; + *child_proc = new_child.hproc; + *child_pid = new_child.pid; + + return 0; +} + +/*********************************************************************** + * master_main() + * master_main() runs in the parent process. It creates the child + * process which handles HTTP requests then waits on one of three + * events: + * + * restart_event + * ------------- + * The restart event causes master_main to start a new child process and + * tells the old child process to exit (by setting the child_exit_event). + * The restart event is set as a result of one of the following: + * 1. An apache -k restart command on the command line + * 2. A command received from Windows service manager which gets + * translated into an ap_signal_parent(SIGNAL_PARENT_RESTART) + * call by code in service.c. + * 3. The child process calling ap_signal_parent(SIGNAL_PARENT_RESTART) + * as a result of hitting MaxConnectionsPerChild. + * + * shutdown_event + * -------------- + * The shutdown event causes master_main to tell the child process to + * exit and that the server is shutting down. The shutdown event is + * set as a result of one of the following: + * 1. An apache -k shutdown command on the command line + * 2. A command received from Windows service manager which gets + * translated into an ap_signal_parent(SIGNAL_PARENT_SHUTDOWN) + * call by code in service.c. + * + * child process handle + * -------------------- + * The child process handle will be signaled if the child process + * exits for any reason. In a normal running server, the signaling + * of this event means that the child process has exited prematurely + * due to a seg fault or other irrecoverable error. For server + * robustness, master_main will restart the child process under this + * condtion. + * + * master_main uses the child_exit_event to signal the child process + * to exit. + **********************************************************************/ +#define NUM_WAIT_HANDLES 3 +#define CHILD_HANDLE 0 +#define SHUTDOWN_HANDLE 1 +#define RESTART_HANDLE 2 +static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_event) +{ + int rv, cld; + int child_created; + int restart_pending; + int shutdown_pending; + HANDLE child_exit_event; + HANDLE event_handles[NUM_WAIT_HANDLES]; + DWORD child_pid; + + child_created = restart_pending = shutdown_pending = 0; + + event_handles[SHUTDOWN_HANDLE] = shutdown_event; + event_handles[RESTART_HANDLE] = restart_event; + + /* Create a single child process */ + rv = create_process(pconf, &event_handles[CHILD_HANDLE], + &child_exit_event, &child_pid); + if (rv < 0) + { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00419) + "master_main: create child process failed. Exiting."); + shutdown_pending = 1; + goto die_now; + } + + child_created = 1; + + if (!strcasecmp(signal_arg, "runservice")) { + mpm_service_started(); + } + + /* Update the scoreboard. Note that there is only a single active + * child at once. + */ + ap_scoreboard_image->parent[0].quiescing = 0; + winnt_note_child_started(/* slot */ 0, child_pid); + + /* Wait for shutdown or restart events or for child death */ + winnt_mpm_state = AP_MPMQ_RUNNING; + rv = WaitForMultipleObjects(NUM_WAIT_HANDLES, (HANDLE *) event_handles, FALSE, INFINITE); + cld = rv - WAIT_OBJECT_0; + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK,APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00420) + "master_main: WaitForMultipleObjects WAIT_FAILED -- doing server shutdown"); + shutdown_pending = 1; + } + else if (rv == WAIT_TIMEOUT) { + /* Hey, this cannot happen */ + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00421) + "master_main: WaitForMultipleObjects with INFINITE wait exited with WAIT_TIMEOUT"); + shutdown_pending = 1; + } + else if (cld == SHUTDOWN_HANDLE) { + /* shutdown_event signalled */ + shutdown_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, s, APLOGNO(00422) + "Parent: Received shutdown signal -- Shutting down the server."); + if (ResetEvent(shutdown_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00423) + "ResetEvent(shutdown_event)"); + } + } + else if (cld == RESTART_HANDLE) { + /* Received a restart event. Prepare the restart_event to be reused + * then signal the child process to exit. + */ + restart_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(00424) + "Parent: Received restart signal -- Restarting the server."); + if (ResetEvent(restart_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00425) + "Parent: ResetEvent(restart_event) failed."); + } + if (SetEvent(child_exit_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00426) + "Parent: SetEvent for child process event %pp failed.", + event_handles[CHILD_HANDLE]); + } + /* Don't wait to verify that the child process really exits, + * just move on with the restart. + */ + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + else { + /* The child process exited prematurely due to a fatal error. */ + DWORD exitcode; + if (!GetExitCodeProcess(event_handles[CHILD_HANDLE], &exitcode)) { + /* HUH? We did exit, didn't we? */ + exitcode = APEXIT_CHILDFATAL; + } + if ( exitcode == APEXIT_CHILDFATAL + || exitcode == APEXIT_CHILDINIT + || exitcode == APEXIT_INIT) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00427) + "Parent: child process %lu exited with status %lu -- Aborting.", + child_pid, exitcode); + shutdown_pending = 1; + } + else { + int i; + restart_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00428) + "Parent: child process %lu exited with status %lu -- Restarting.", + child_pid, exitcode); + for (i = 0; i < ap_threads_per_child; i++) { + ap_update_child_status_from_indexes(0, i, SERVER_DEAD, NULL); + } + } + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + + winnt_note_child_killed(/* slot */ 0); + + if (restart_pending) { + ++my_generation; + ap_scoreboard_image->global->running_generation = my_generation; + } +die_now: + if (shutdown_pending) + { + int timeout = 30000; /* Timeout is milliseconds */ + winnt_mpm_state = AP_MPMQ_STOPPING; + + if (!child_created) { + return 0; /* Tell the caller we do not want to restart */ + } + + /* This shutdown is only marginally graceful. We will give the + * child a bit of time to exit gracefully. If the time expires, + * the child will be wacked. + */ + if (!strcasecmp(signal_arg, "runservice")) { + mpm_service_stopping(); + } + /* Signal the child processes to exit */ + if (SetEvent(child_exit_event) == 0) { + ap_log_error(APLOG_MARK,APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(00429) + "Parent: SetEvent for child process event %pp failed", + event_handles[CHILD_HANDLE]); + } + if (event_handles[CHILD_HANDLE]) { + rv = WaitForSingleObject(event_handles[CHILD_HANDLE], timeout); + if (rv == WAIT_OBJECT_0) { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00430) + "Parent: Child process %lu exited successfully.", child_pid); + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + else { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00431) + "Parent: Forcing termination of child process %lu", + child_pid); + TerminateProcess(event_handles[CHILD_HANDLE], 1); + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + } + CloseHandle(child_exit_event); + return 0; /* Tell the caller we do not want to restart */ + } + winnt_mpm_state = AP_MPMQ_STARTING; + CloseHandle(child_exit_event); + return 1; /* Tell the caller we want a restart */ +} + +/* service_nt_main_fn needs to append the StartService() args + * outside of our call stack and thread as the service starts... + */ +apr_array_header_t *mpm_new_argv; + +/* Remember service_to_start failures to log and fail in pre_config. + * Remember inst_argc and inst_argv for installing or starting the + * service after we preflight the config. + */ + +static int winnt_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = MAXIMUM_WAIT_OBJECTS; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_STATIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = HARD_SERVER_LIMIT; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = thread_limit; + break; + case AP_MPMQ_MAX_THREADS: + *result = ap_threads_per_child; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = 1; + break; + case AP_MPMQ_MPM_STATE: + *result = winnt_mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static const char *winnt_get_name(void) +{ + return "WinNT"; +} + +#define SERVICE_UNSET (-1) +static apr_status_t service_set = SERVICE_UNSET; +static apr_status_t service_to_start_success; +static int inst_argc; +static const char * const *inst_argv; +static const char *service_name = NULL; + +static void winnt_rewrite_args(process_rec *process) +{ + /* Handle the following SCM aspects in this phase: + * + * -k runservice [transition in service context only] + * -k install + * -k config + * -k uninstall + * -k stop + * -k shutdown (same as -k stop). Maintained for backward compatibility. + * + * We can't leave this phase until we know our identity + * and modify the command arguments appropriately. + * + * We do not care if the .conf file exists or is parsable when + * attempting to stop or uninstall a service. + */ + apr_status_t rv; + char *def_server_root; + char *binpath; + char optbuf[3]; + const char *opt_arg; + int fixed_args; + char *pid; + apr_getopt_t *opt; + int running_as_service = 1; + int errout = 0; + apr_file_t *nullfile; + + pconf = process->pconf; + + osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osver); + + /* We wish this was *always* a reservation, but sadly it wasn't so and + * we couldn't break a hard limit prior to NT Kernel 5.1 + */ + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT + && ((osver.dwMajorVersion > 5) + || ((osver.dwMajorVersion == 5) && (osver.dwMinorVersion > 0)))) { + stack_res_flag = STACK_SIZE_PARAM_IS_A_RESERVATION; + } + + /* AP_PARENT_PID is only valid in the child */ + pid = getenv("AP_PARENT_PID"); + if (pid) + { + HANDLE filehand; + HANDLE hproc = GetCurrentProcess(); + DWORD BytesRead; + + /* This is the child */ + my_pid = GetCurrentProcessId(); + parent_pid = (DWORD) atol(pid); + + /* Prevent holding open the (nonexistent) console */ + ap_real_exit_code = 0; + + /* The parent gave us stdin, we need to remember this + * handle, and no longer inherit it at our children + * (we can't slurp it up now, we just aren't ready yet). + * The original handle is closed below, at apr_file_dup2() + */ + pipe = GetStdHandle(STD_INPUT_HANDLE); + if (DuplicateHandle(hproc, pipe, + hproc, &filehand, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + pipe = filehand; + } + + /* The parent gave us stdout of the NUL device, + * and expects us to suck up stdin of all of our + * shared handles and data from the parent. + * Don't infect child processes with our stdin + * handle, use another handle to NUL! + */ + { + apr_file_t *infile, *outfile; + if ((apr_file_open_stdout(&outfile, process->pool) == APR_SUCCESS) + && (apr_file_open_stdin(&infile, process->pool) == APR_SUCCESS)) + apr_file_dup2(infile, outfile, process->pool); + } + + /* This child needs the existing stderr opened for logging, + * already + */ + + /* Read this child's generation number as soon as now, + * so that further hooks can query it. + */ + if (!ReadFile(pipe, &my_generation, sizeof(my_generation), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(my_generation))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), NULL, APLOGNO(02965) + "Child: Unable to retrieve my generation from the parent"); + exit(APEXIT_CHILDINIT); + } + + /* The parent is responsible for providing the + * COMPLETE ARGUMENTS REQUIRED to the child. + * + * No further argument parsing is needed, but + * for good measure we will provide a simple + * signal string for later testing. + */ + signal_arg = "runchild"; + return; + } + + /* This is the parent, we have a long way to go :-) */ + parent_pid = my_pid = GetCurrentProcessId(); + + /* This behavior is voided by setting real_exit_code to 0 */ + atexit(hold_console_open_on_error); + + /* Rewrite process->argv[]; + * + * strip out -k signal into signal_arg + * strip out -n servicename and set the names + * add default -d serverroot from the path of this executable + * + * The end result will look like: + * + * The invocation command (%0) + * The -d serverroot default from the running executable + * The requested service's (-n) registry ConfigArgs + * The WinNT SCM's StartService() args + */ + if ((rv = ap_os_proc_filepath(&binpath, process->pconf)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_CRIT, rv, NULL, APLOGNO(00432) + "Failed to get the full path of %s", process->argv[0]); + exit(APEXIT_INIT); + } + /* WARNING: There is an implict assumption here that the + * executable resides in ServerRoot or ServerRoot\bin + */ + def_server_root = (char *) apr_filepath_name_get(binpath); + if (def_server_root > binpath) { + *(def_server_root - 1) = '\0'; + def_server_root = (char *) apr_filepath_name_get(binpath); + if (!strcasecmp(def_server_root, "bin")) + *(def_server_root - 1) = '\0'; + } + apr_filepath_merge(&def_server_root, NULL, binpath, + APR_FILEPATH_TRUENAME, process->pool); + + /* Use process->pool so that the rewritten argv + * lasts for the lifetime of the server process, + * because pconf will be destroyed after the + * initial pre-flight of the config parser. + */ + mpm_new_argv = apr_array_make(process->pool, process->argc + 2, + sizeof(const char *)); + *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; + *(const char **)apr_array_push(mpm_new_argv) = "-d"; + *(const char **)apr_array_push(mpm_new_argv) = def_server_root; + + fixed_args = mpm_new_argv->nelts; + + optbuf[0] = '-'; + optbuf[2] = '\0'; + apr_getopt_init(&opt, process->pool, process->argc, process->argv); + opt->errfn = NULL; + while ((rv = apr_getopt(opt, "wn:k:" AP_SERVER_BASEARGS, + optbuf + 1, &opt_arg)) == APR_SUCCESS) { + switch (optbuf[1]) { + + /* Shortcuts; include the -w option to hold the window open on error. + * This must not be toggled once we reset ap_real_exit_code to 0! + */ + case 'w': + if (ap_real_exit_code) + ap_real_exit_code = 2; + break; + + case 'n': + service_set = mpm_service_set_name(process->pool, &service_name, + opt_arg); + break; + + case 'k': + signal_arg = opt_arg; + break; + + case 'E': + errout = 1; + /* Fall through so the Apache main() handles the 'E' arg */ + default: + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, optbuf); + + if (opt_arg) { + *(const char **)apr_array_push(mpm_new_argv) = opt_arg; + } + break; + } + } + + /* back up to capture the bad argument */ + if (rv == APR_BADCH || rv == APR_BADARG) { + opt->ind--; + } + + while (opt->ind < opt->argc) { + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, opt->argv[opt->ind++]); + } + + /* Track the number of args actually entered by the user */ + inst_argc = mpm_new_argv->nelts - fixed_args; + + /* Provide a default 'run' -k arg to simplify signal_arg tests */ + if (!signal_arg) + { + signal_arg = "run"; + running_as_service = 0; + } + + if (!strcasecmp(signal_arg, "runservice")) + { + /* Start the NT Service _NOW_ because the WinNT SCM is + * expecting us to rapidly assume control of our own + * process, the SCM will tell us our service name, and + * may have extra StartService() command arguments to + * add for us. + * + * The SCM will generally invoke the executable with + * the c:\win\system32 default directory. This is very + * lethal if folks use ServerRoot /foopath on windows + * without a drive letter. Change to the default root + * (path to apache root, above /bin) for safety. + */ + apr_filepath_set(def_server_root, process->pool); + + /* Any other process has a console, so we don't to begin + * a Win9x service until the configuration is parsed and + * any command line errors are reported. + * + * We hold the return value so that we can die in pre_config + * after logging begins, and the failure can land in the log. + */ + if (!errout) { + mpm_nt_eventlog_stderr_open(service_name, process->pool); + } + service_to_start_success = mpm_service_to_start(&service_name, + process->pool); + if (service_to_start_success == APR_SUCCESS) { + service_set = APR_SUCCESS; + } + + /* Open a null handle to soak stdout in this process. + * Windows service processes are missing any file handle + * usable for stdin/out/err. This was the cause of later + * trouble with invocations of apr_file_open_stdout() + */ + if ((rv = apr_file_open(&nullfile, "NUL", + APR_READ | APR_WRITE, APR_OS_DEFAULT, + process->pool)) == APR_SUCCESS) { + apr_file_t *nullstdout; + if (apr_file_open_stdout(&nullstdout, process->pool) + == APR_SUCCESS) + apr_file_dup2(nullstdout, nullfile, process->pool); + apr_file_close(nullfile); + } + } + + /* Get the default for any -k option, except run */ + if (service_set == SERVICE_UNSET && strcasecmp(signal_arg, "run")) { + service_set = mpm_service_set_name(process->pool, &service_name, + AP_DEFAULT_SERVICE_NAME); + } + + if (!strcasecmp(signal_arg, "install")) /* -k install */ + { + if (service_set == APR_SUCCESS) + { + ap_log_error(APLOG_MARK,APLOG_ERR, 0, NULL, APLOGNO(00433) + "%s: Service is already installed.", service_name); + exit(APEXIT_INIT); + } + } + else if (running_as_service) + { + if (service_set == APR_SUCCESS) + { + /* Attempt to Uninstall, or stop, before + * we can read the arguments or .conf files + */ + if (!strcasecmp(signal_arg, "uninstall")) { + rv = mpm_service_uninstall(); + exit(rv); + } + + if ((!strcasecmp(signal_arg, "stop")) || + (!strcasecmp(signal_arg, "shutdown"))) { + mpm_signal_service(process->pool, 0); + exit(0); + } + + rv = mpm_merge_service_args(process->pool, mpm_new_argv, + fixed_args); + if (rv == APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_INFO, 0, NULL, APLOGNO(00434) + "Using ConfigArgs of the installed service " + "\"%s\".", service_name); + } + else { + ap_log_error(APLOG_MARK,APLOG_WARNING, rv, NULL, APLOGNO(00435) + "No installed ConfigArgs for the service " + "\"%s\", using Apache defaults.", service_name); + } + } + else + { + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, APLOGNO(00436) + "No installed service named \"%s\".", service_name); + exit(APEXIT_INIT); + } + } + if (strcasecmp(signal_arg, "install") && service_set && service_set != SERVICE_UNSET) + { + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, APLOGNO(00437) + "No installed service named \"%s\".", service_name); + exit(APEXIT_INIT); + } + + /* Track the args actually entered by the user. + * These will be used for the -k install parameters, as well as + * for the -k start service override arguments. + */ + inst_argv = (const char * const *)mpm_new_argv->elts + + mpm_new_argv->nelts - inst_argc; + + /* Now, do service install or reconfigure then proceed to + * post_config to test the installed configuration. + */ + if (!strcasecmp(signal_arg, "config")) { /* -k config */ + /* Reconfigure the service */ + rv = mpm_service_install(process->pool, inst_argc, inst_argv, 1); + if (rv != APR_SUCCESS) { + exit(rv); + } + + fprintf(stderr,"Testing httpd.conf....\n"); + fprintf(stderr,"Errors reported here must be corrected before the " + "service can be started.\n"); + } + else if (!strcasecmp(signal_arg, "install")) { /* -k install */ + /* Install the service */ + rv = mpm_service_install(process->pool, inst_argc, inst_argv, 0); + if (rv != APR_SUCCESS) { + exit(rv); + } + + fprintf(stderr,"Testing httpd.conf....\n"); + fprintf(stderr,"Errors reported here must be corrected before the " + "service can be started.\n"); + } + + process->argc = mpm_new_argv->nelts; + process->argv = (const char * const *) mpm_new_argv->elts; +} + + +static int winnt_pre_config(apr_pool_t *pconf_, apr_pool_t *plog, apr_pool_t *ptemp) +{ + /* Handle the following SCM aspects in this phase: + * + * -k runservice [WinNT errors logged from rewrite_args] + */ + + /* Initialize shared static objects. + * TODO: Put config related statics into an sconf structure. + */ + pconf = pconf_; + + if (ap_exists_config_define("ONE_PROCESS") || + ap_exists_config_define("DEBUG")) + one_process = -1; + + /* XXX: presume proper privilages; one nice thing would be + * a loud emit if running as "LocalSystem"/"SYSTEM" to indicate + * they should change to a user with write access to logs/ alone. + */ + ap_sys_privileges_handlers(1); + + if (!strcasecmp(signal_arg, "runservice") + && (service_to_start_success != APR_SUCCESS)) { + ap_log_error(APLOG_MARK,APLOG_CRIT, service_to_start_success, NULL, APLOGNO(00438) + "%s: Unable to start the service manager.", + service_name); + exit(APEXIT_INIT); + } + else if (ap_state_query(AP_SQ_RUN_MODE) == AP_SQ_RM_NORMAL + && !one_process && !my_generation) { + /* Open a null handle to soak stdout in this process. + * We need to emulate apr_proc_detach, unix performs this + * same check in the pre_config hook (although it is + * arguably premature). Services already fixed this. + */ + apr_file_t *nullfile; + apr_status_t rv; + apr_pool_t *pproc = apr_pool_parent_get(pconf); + + if ((rv = apr_file_open(&nullfile, "NUL", + APR_READ | APR_WRITE, APR_OS_DEFAULT, + pproc)) == APR_SUCCESS) { + apr_file_t *nullstdout; + if (apr_file_open_stdout(&nullstdout, pproc) + == APR_SUCCESS) + apr_file_dup2(nullstdout, nullfile, pproc); + apr_file_close(nullfile); + } + } + + ap_listen_pre_config(); + thread_limit = DEFAULT_THREAD_LIMIT; + ap_threads_per_child = DEFAULT_THREADS_PER_CHILD; + + return OK; +} + +static int winnt_check_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec* s) +{ + int is_parent; + int startup = 0; + + /* We want this only in the parent and only the first time around */ + is_parent = (parent_pid == my_pid); + if (is_parent && + ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { + startup = 1; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00439) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of %d threads, decreasing to %d.", + thread_limit, MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00440) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00441) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00442) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* You cannot change ThreadLimit across a restart; ignore + * any such attempts. + */ + if (!first_thread_limit) { + first_thread_limit = thread_limit; + } + else if (thread_limit != first_thread_limit) { + /* Don't need a startup console version here */ + if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00443) + "changing ThreadLimit to %d from original value " + "of %d not allowed during restart", + thread_limit, first_thread_limit); + } + thread_limit = first_thread_limit; + } + + if (ap_threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00444) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of %d threads, decreasing to %d. To increase, please " + "see the ThreadLimit directive.", + ap_threads_per_child, thread_limit, thread_limit); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00445) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + ap_threads_per_child, thread_limit); + } + ap_threads_per_child = thread_limit; + } + else if (ap_threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00446) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", ap_threads_per_child); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00447) + "ThreadsPerChild of %d not allowed, increasing to 1", + ap_threads_per_child); + } + ap_threads_per_child = 1; + } + + return OK; +} + +static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec* s) +{ + apr_status_t rv = 0; + + /* Handle the following SCM aspects in this phase: + * + * -k install (catch and exit as install was handled in rewrite_args) + * -k config (catch and exit as config was handled in rewrite_args) + * -k start + * -k restart + * -k runservice [Win95, only once - after we parsed the config] + * + * because all of these signals are useful _only_ if there + * is a valid conf\httpd.conf environment to start. + * + * We reached this phase by avoiding errors that would cause + * these options to fail unexpectedly in another process. + */ + + if (!strcasecmp(signal_arg, "install")) { + /* Service install happens in the rewrite_args hooks. If we + * made it this far, the server configuration is clean and the + * service will successfully start. + */ + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(0); + } + if (!strcasecmp(signal_arg, "config")) { + /* Service reconfiguration happens in the rewrite_args hooks. If we + * made it this far, the server configuration is clean and the + * service will successfully start. + */ + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(0); + } + + if (!strcasecmp(signal_arg, "start")) { + ap_listen_rec *lr; + + /* Close the listening sockets. */ + for (lr = ap_listeners; lr; lr = lr->next) { + apr_socket_close(lr->sd); + lr->active = 0; + } + rv = mpm_service_start(ptemp, inst_argc, inst_argv); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit (rv); + } + + if (!strcasecmp(signal_arg, "restart")) { + mpm_signal_service(ptemp, 1); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit (rv); + } + + if (parent_pid == my_pid) + { + if (ap_state_query(AP_SQ_MAIN_STATE) != AP_SQ_MS_CREATE_PRE_CONFIG + && ap_state_query(AP_SQ_CONFIG_GEN) == 1) + { + /* This code should be run once in the parent and not run + * across a restart + */ + PSECURITY_ATTRIBUTES sa = GetNullACL(); /* returns NULL if invalid (Win95?) */ + setup_signal_names(apr_psprintf(pconf, "ap%lu", parent_pid)); + + ap_log_pid(pconf, ap_pid_fname); + + /* Create shutdown event, apPID_shutdown, where PID is the parent + * Apache process ID. Shutdown is signaled by 'apache -k shutdown'. + */ + shutdown_event = CreateEvent(sa, FALSE, FALSE, signal_shutdown_name); + if (!shutdown_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00448) + "Parent: Cannot create shutdown event %s", signal_shutdown_name); + CleanNullACL((void *)sa); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Create restart event, apPID_restart, where PID is the parent + * Apache process ID. Restart is signaled by 'apache -k restart'. + */ + restart_event = CreateEvent(sa, FALSE, FALSE, signal_restart_name); + if (!restart_event) { + CloseHandle(shutdown_event); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00449) + "Parent: Cannot create restart event %s", signal_restart_name); + CleanNullACL((void *)sa); + return HTTP_INTERNAL_SERVER_ERROR; + } + CleanNullACL((void *)sa); + + /* Create the start mutex, as an unnamed object for security. + * Ths start mutex is used during a restart to prevent more than + * one child process from entering the accept loop at once. + */ + rv = apr_proc_mutex_create(&start_mutex, NULL, + APR_LOCK_DEFAULT, + ap_server_conf->process->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, APLOGNO(00450) + "%s: Unable to create the start_mutex.", + service_name); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + /* Always reset our console handler to be the first, even on a restart + * because some modules (e.g. mod_perl) might have set a console + * handler to terminate the process. + */ + if (strcasecmp(signal_arg, "runservice")) + mpm_start_console_handler(); + } + else /* parent_pid != my_pid */ + { + mpm_start_child_console_handler(); + } + return OK; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int winnt_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + /* Initialize shared static objects. + */ + if (parent_pid != my_pid) { + return OK; + } + + /* We cannot initialize our listeners if we are restarting + * (the parent process already has glomed on to them) + * nor should we do so for service reconfiguration + * (since the service may already be running.) + */ + if (!strcasecmp(signal_arg, "restart") + || !strcasecmp(signal_arg, "config")) { + return OK; + } + + if (ap_setup_listeners(s) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, + NULL, APLOGNO(00451) "no listening sockets available, shutting down"); + return !OK; + } + + return OK; +} + +static void winnt_child_init(apr_pool_t *pchild, struct server_rec *s) +{ + apr_status_t rv; + + setup_signal_names(apr_psprintf(pchild, "ap%lu", parent_pid)); + + /* This is a child process, not in single process mode */ + if (!one_process) { + /* Set up events and the scoreboard */ + get_handles_from_parent(s, &exit_event, &start_mutex, + &ap_scoreboard_shm); + + /* Set up the listeners */ + get_listeners_from_parent(s); + + /* Done reading from the parent, close that channel */ + CloseHandle(pipe); + } + else { + /* Single process mode - this lock doesn't even need to exist */ + rv = apr_proc_mutex_create(&start_mutex, signal_name_prefix, + APR_LOCK_DEFAULT, s->process->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, APLOGNO(00452) + "%s child: Unable to init the start_mutex.", + service_name); + exit(APEXIT_CHILDINIT); + } + + /* Borrow the shutdown_even as our _child_ loop exit event */ + exit_event = shutdown_event; + } +} + + +static int winnt_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) +{ + static int restart = 0; /* Default is "not a restart" */ + + /* ### If non-graceful restarts are ever introduced - we need to rerun + * the pre_mpm hook on subsequent non-graceful restarts. But Win32 + * has only graceful style restarts - and we need this hook to act + * the same on Win32 as on Unix. + */ + if (!restart && ((parent_pid == my_pid) || one_process)) { + /* Set up the scoreboard. */ + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + return !OK; + } + } + + if ((parent_pid != my_pid) || one_process) + { + /* The child process or in one_process (debug) mode + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00453) + "Child process is running"); + + child_main(pconf, parent_pid); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00454) + "Child process is exiting"); + return DONE; + } + else + { + /* A real-honest to goodness parent */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00455) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00456) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + + restart = master_main(ap_server_conf, shutdown_event, restart_event); + + if (!restart) + { + /* Shutting down. Clean up... */ + ap_remove_pid(pconf, ap_pid_fname); + apr_proc_mutex_destroy(start_mutex); + + CloseHandle(restart_event); + CloseHandle(shutdown_event); + + return DONE; + } + } + + return OK; /* Restart */ +} + +static void winnt_hooks(apr_pool_t *p) +{ + /* Our open_logs hook function must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = {"core.c", NULL}; + + ap_hook_pre_config(winnt_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(winnt_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(winnt_post_config, NULL, NULL, 0); + ap_hook_child_init(winnt_child_init, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_open_logs(winnt_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + ap_hook_mpm(winnt_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(winnt_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(winnt_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(mpm_winnt) = { + MPM20_MODULE_STUFF, + winnt_rewrite_args, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + winnt_cmds, /* command apr_table_t */ + winnt_hooks /* register_hooks */ +}; + +#endif /* def WIN32 */ diff --git a/server/mpm/winnt/mpm_winnt.h b/server/mpm/winnt/mpm_winnt.h new file mode 100644 index 0000000..22ba001 --- /dev/null +++ b/server/mpm/winnt/mpm_winnt.h @@ -0,0 +1,96 @@ +/* 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. + */ + +/** + * @file mpm_winnt.h + * @brief WinNT MPM specific + * + * @addtogroup APACHE_MPM_WINNT + * @{ + */ + +#ifndef APACHE_MPM_WINNT_H +#define APACHE_MPM_WINNT_H + +#include "apr_proc_mutex.h" +#include "ap_listen.h" + +/* From service.c: */ + +#define SERVICE_APACHE_RESTART 128 + +#ifndef AP_DEFAULT_SERVICE_NAME +#define AP_DEFAULT_SERVICE_NAME "Apache2.4" +#endif + +#define SERVICECONFIG "System\\CurrentControlSet\\Services\\%s" +#define SERVICEPARAMS "System\\CurrentControlSet\\Services\\%s\\Parameters" + +apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, + const char *set_name); +apr_status_t mpm_merge_service_args(apr_pool_t *p, apr_array_header_t *args, + int fixed_args); + +apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p); +apr_status_t mpm_service_started(void); +apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, + char const* const* argv, int reconfig); +apr_status_t mpm_service_uninstall(void); + +apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, + char const* const* argv); + +void mpm_signal_service(apr_pool_t *ptemp, int signal); + +void mpm_service_stopping(void); + +void mpm_start_console_handler(void); +void mpm_start_child_console_handler(void); + +/* From nt_eventlog.c: */ + +void mpm_nt_eventlog_stderr_open(const char *display_name, apr_pool_t *p); +void mpm_nt_eventlog_stderr_flush(void); + +/* From mpm_winnt.c: */ + +extern module AP_MODULE_DECLARE_DATA mpm_winnt_module; +extern int ap_threads_per_child; + +extern DWORD my_pid; +extern apr_proc_mutex_t *start_mutex; +extern HANDLE exit_event; + +extern int winnt_mpm_state; +extern OSVERSIONINFO osver; +extern DWORD stack_res_flag; + +extern void clean_child_exit(int); + +typedef enum { + SIGNAL_PARENT_SHUTDOWN, + SIGNAL_PARENT_RESTART, + SIGNAL_PARENT_RESTART_GRACEFUL +} ap_signal_parent_e; +AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type); + +void hold_console_open_on_error(void); + +/* From child.c: */ +void child_main(apr_pool_t *pconf, DWORD parent_pid); + +#endif /* APACHE_MPM_WINNT_H */ +/** @} */ diff --git a/server/mpm/winnt/nt_eventlog.c b/server/mpm/winnt/nt_eventlog.c new file mode 100644 index 0000000..e7e80d0 --- /dev/null +++ b/server/mpm/winnt/nt_eventlog.c @@ -0,0 +1,171 @@ +/* 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_log.h" +#include "mpm_winnt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_portable.h" +#include "ap_regkey.h" + +static const char *display_name = NULL; +static HANDLE stderr_thread = NULL; +static HANDLE stderr_ready; + +static DWORD WINAPI service_stderr_thread(LPVOID hPipe) +{ + HANDLE hPipeRead = (HANDLE) hPipe; + HANDLE hEventSource; + char errbuf[256]; + char *errmsg = errbuf; + const char *errarg[9]; + DWORD errres; + ap_regkey_t *regkey; + apr_status_t rv; + apr_pool_t *p; + + apr_pool_create_ex(&p, NULL, NULL, NULL); + + errarg[0] = "The Apache service named"; + errarg[1] = display_name; + errarg[2] = "reported the following error:\r\n>>>"; + errarg[3] = errbuf; + errarg[4] = NULL; + errarg[5] = NULL; + errarg[6] = NULL; + errarg[7] = NULL; + errarg[8] = NULL; + + /* What are we going to do in here, bail on the user? not. */ + if ((rv = ap_regkey_open(®key, AP_REGKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Services\\" + "EventLog\\Application\\Apache Service", + APR_READ | APR_WRITE | APR_CREATE, p)) + == APR_SUCCESS) + { + DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | + EVENTLOG_INFORMATION_TYPE; + + /* The stock message file */ + ap_regkey_value_set(regkey, "EventMessageFile", + "%SystemRoot%\\System32\\netmsg.dll", + AP_REGKEY_EXPAND, p); + + ap_regkey_value_raw_set(regkey, "TypesSupported", &dwData, + sizeof(dwData), REG_DWORD, p); + ap_regkey_close(regkey); + } + + hEventSource = RegisterEventSourceW(NULL, L"Apache Service"); + + SetEvent(stderr_ready); + + while (ReadFile(hPipeRead, errmsg, 1, &errres, NULL) && (errres == 1)) + { + if ((errmsg > errbuf) || !apr_isspace(*errmsg)) + { + ++errmsg; + if ((*(errmsg - 1) == '\n') + || (errmsg >= errbuf + sizeof(errbuf) - 1)) + { + while ((errmsg > errbuf) && apr_isspace(*(errmsg - 1))) { + --errmsg; + } + *errmsg = '\0'; + + /* Generic message: '%1 %2 %3 %4 %5 %6 %7 %8 %9' + * The event code in netmsg.dll is 3299 + */ + ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, + 3299, NULL, 9, 0, errarg, NULL); + errmsg = errbuf; + } + } + } + + if ((errres = GetLastError()) != ERROR_BROKEN_PIPE) { + apr_snprintf(errbuf, sizeof(errbuf), + "Win32 error %lu reading stderr pipe stream\r\n", + GetLastError()); + + ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, + 3299, NULL, 9, 0, errarg, NULL); + } + + CloseHandle(hPipeRead); + DeregisterEventSource(hEventSource); + CloseHandle(stderr_thread); + stderr_thread = NULL; + apr_pool_destroy(p); + return 0; +} + + +void mpm_nt_eventlog_stderr_flush(void) +{ + HANDLE cleanup_thread = stderr_thread; + + if (cleanup_thread) { + HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE); + fclose(stderr); + CloseHandle(hErr); + WaitForSingleObject(cleanup_thread, 30000); + CloseHandle(cleanup_thread); + } +} + + +void mpm_nt_eventlog_stderr_open(const char *argv0, apr_pool_t *p) +{ + SECURITY_ATTRIBUTES sa; + HANDLE hPipeRead = NULL; + HANDLE hPipeWrite = NULL; + DWORD threadid; + apr_file_t *eventlog_file; + apr_file_t *stderr_file; + + display_name = argv0; + + /* Create a pipe to send stderr messages to the system error log. + * + * _dup2() duplicates the write handle inheritable for us. + */ + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = FALSE; + CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0); + ap_assert(hPipeRead && hPipeWrite); + + stderr_ready = CreateEvent(NULL, FALSE, FALSE, NULL); + stderr_thread = CreateThread(NULL, 65536, service_stderr_thread, + (LPVOID)hPipeRead, stack_res_flag, &threadid); + ap_assert(stderr_ready && stderr_thread); + + WaitForSingleObject(stderr_ready, INFINITE); + + if ((apr_file_open_stderr(&stderr_file, p) + == APR_SUCCESS) + && (apr_os_file_put(&eventlog_file, &hPipeWrite, APR_WRITE, p) + == APR_SUCCESS)) + apr_file_dup2(stderr_file, eventlog_file, p); + + /* The code above _will_ corrupt the StdHandle... + * and we must do so anyways. We set this up only + * after we initialized the posix stderr API. + */ + ap_open_stderr_log(p); +} diff --git a/server/mpm/winnt/service.c b/server/mpm/winnt/service.c new file mode 100644 index 0000000..121aca1 --- /dev/null +++ b/server/mpm/winnt/service.c @@ -0,0 +1,1241 @@ +/* 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 ALONE requires the window message API from user.h + * and the default APR include of windows.h will omit it, so + * preload the API symbols now... + */ + +#define _WINUSER_ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_lib.h" +#if APR_HAS_UNICODE_FS +#include "arch/win32/apr_arch_utf8.h" +#include "arch/win32/apr_arch_misc.h" +#include <wchar.h> +#endif + +#include "httpd.h" +#include "http_log.h" +#include "mpm_winnt.h" +#include "ap_regkey.h" + +#ifdef NOUSER +#undef NOUSER +#endif +#undef _WINUSER_ +#include <winuser.h> +#include <time.h> + +APLOG_USE_MODULE(mpm_winnt); + +/* Todo; clear up statics */ +static char *mpm_service_name = NULL; +static char *mpm_display_name = NULL; + +#if APR_HAS_UNICODE_FS +static apr_wchar_t *mpm_service_name_w; +#endif + +typedef struct nt_service_ctx_t +{ + HANDLE mpm_thread; /* primary thread handle of the apache server */ + HANDLE service_thread; /* thread service/monitor handle */ + DWORD service_thread_id;/* thread service/monitor ID */ + HANDLE service_init; /* controller thread init mutex */ + HANDLE service_term; /* NT service thread kill signal */ + SERVICE_STATUS ssStatus; + SERVICE_STATUS_HANDLE hServiceStatus; +} nt_service_ctx_t; + +static nt_service_ctx_t globdat; + +static int ReportStatusToSCMgr(int currentState, int waitHint, + nt_service_ctx_t *ctx); + +/* Rather than repeat this logic throughout, create an either-or wide or narrow + * implementation because we don't actually pass strings to OpenSCManager. + * This election is based on build time defines and runtime os version test. + */ +#undef OpenSCManager +typedef SC_HANDLE (WINAPI *fpt_OpenSCManager)(const void *lpMachine, + const void *lpDatabase, + DWORD dwAccess); +static fpt_OpenSCManager pfn_OpenSCManager = NULL; +static APR_INLINE SC_HANDLE OpenSCManager(const void *lpMachine, + const void *lpDatabase, + DWORD dwAccess) +{ + if (!pfn_OpenSCManager) { +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + pfn_OpenSCManager = (fpt_OpenSCManager)OpenSCManagerW; +#endif +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + pfn_OpenSCManager = (fpt_OpenSCManager)OpenSCManagerA; +#endif + } + return (*(pfn_OpenSCManager))(lpMachine, lpDatabase, dwAccess); +} + +/* exit() for Win32 is macro mapped (horrible, we agree) that allows us + * to catch the non-zero conditions and inform the console process that + * the application died, and hang on to the console a bit longer. + * + * The macro only maps for http_main.c and other sources that include + * the service.h header, so we best assume it's an error to exit from + * _any_ other module. + * + * If ap_real_exit_code is reset to 0, it will not be set or trigger this + * behavior on exit. All service and child processes are expected to + * reset this flag to zero to avoid undesirable side effects. + */ +AP_DECLARE_DATA int ap_real_exit_code = 1; + +void hold_console_open_on_error(void) +{ + HANDLE hConIn; + HANDLE hConErr; + DWORD result; + time_t start; + time_t remains; + char *msg = "Note the errors or messages above, " + "and press the <ESC> key to exit. "; + CONSOLE_SCREEN_BUFFER_INFO coninfo; + INPUT_RECORD in; + char count[16]; + + if (!ap_real_exit_code) + return; + hConIn = GetStdHandle(STD_INPUT_HANDLE); + hConErr = GetStdHandle(STD_ERROR_HANDLE); + if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE)) + return; + if (!WriteConsole(hConErr, msg, (DWORD)strlen(msg), &result, NULL) + || !result) + return; + if (!GetConsoleScreenBufferInfo(hConErr, &coninfo)) + return; + if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80)) + return; + + start = time(NULL); + do + { + while (PeekConsoleInput(hConIn, &in, 1, &result) && result) + { + if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result) + return; + if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown + && (in.Event.KeyEvent.uChar.AsciiChar == 27)) + return; + if (in.EventType == MOUSE_EVENT + && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK)) + return; + } + remains = ((start + 30) - time(NULL)); + sprintf(count, "%d...", + (int)remains); /* 30 or less, so can't overflow int */ + if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition)) + return; + if (!WriteConsole(hConErr, count, (DWORD)strlen(count), &result, NULL) + || !result) + return; + } + while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED); +} + +static BOOL CALLBACK console_control_handler(DWORD ctrl_type) +{ + switch (ctrl_type) + { + case CTRL_BREAK_EVENT: + fprintf(stderr, "Apache server restarting...\n"); + ap_signal_parent(SIGNAL_PARENT_RESTART); + return TRUE; + case CTRL_C_EVENT: + fprintf(stderr, "Apache server interrupted...\n"); + /* for Interrupt signals, shut down the server. + * Tell the system we have dealt with the signal + * without waiting for Apache to terminate. + */ + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + return TRUE; + + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + /* for Terminate signals, shut down the server. + * Wait for Apache to terminate, but respond + * after a reasonable time to tell the system + * that we did attempt to shut ourself down. + */ + fprintf(stderr, "Apache server shutdown initiated...\n"); + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + Sleep(30000); + return TRUE; + } + + /* We should never get here, but this is (mostly) harmless */ + return FALSE; +} + + +static void stop_console_handler(void) +{ + SetConsoleCtrlHandler(console_control_handler, FALSE); +} + + +void mpm_start_console_handler(void) +{ + SetConsoleCtrlHandler(console_control_handler, TRUE); + atexit(stop_console_handler); +} + + +void mpm_start_child_console_handler(void) +{ + FreeConsole(); +} + + +/********************************** + WinNT service control management + **********************************/ + +static int ReportStatusToSCMgr(int currentState, int waitHint, + nt_service_ctx_t *ctx) +{ + int rv = APR_SUCCESS; + + if (ctx->hServiceStatus) + { + if (currentState == SERVICE_RUNNING) { + ctx->ssStatus.dwWaitHint = 0; + ctx->ssStatus.dwCheckPoint = 0; + ctx->ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP + | SERVICE_ACCEPT_SHUTDOWN; + } + else if (currentState == SERVICE_STOPPED) { + ctx->ssStatus.dwWaitHint = 0; + ctx->ssStatus.dwCheckPoint = 0; + /* An unexpected exit? Better to error! */ + if (ctx->ssStatus.dwCurrentState != SERVICE_STOP_PENDING + && !ctx->ssStatus.dwServiceSpecificExitCode) + ctx->ssStatus.dwServiceSpecificExitCode = 1; + if (ctx->ssStatus.dwServiceSpecificExitCode) + ctx->ssStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + } + else { + ++ctx->ssStatus.dwCheckPoint; + ctx->ssStatus.dwControlsAccepted = 0; + if(waitHint) + ctx->ssStatus.dwWaitHint = waitHint; + } + + ctx->ssStatus.dwCurrentState = currentState; + + rv = SetServiceStatus(ctx->hServiceStatus, &ctx->ssStatus); + } + return(rv); +} + +/* Note this works on Win2000 and later due to ChangeServiceConfig2 + * Continue to test its existence, but at least drop the feature + * of revising service description tags prior to Win2000. + */ + +/* borrowed from mpm_winnt.c */ +extern apr_pool_t *pconf; + +static void set_service_description(void) +{ + const char *full_description; + SC_HANDLE schSCManager; + + /* Nothing to do if we are a console + */ + if (!mpm_service_name) + return; + + /* Time to fix up the description, upon each successful restart + */ + full_description = ap_get_server_description(); + + if ((ChangeServiceConfig2) && + (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT))) + { + SC_HANDLE schService; + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, + (LPCWSTR)mpm_service_name_w, + SERVICE_CHANGE_CONFIG); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_CHANGE_CONFIG); + } +#endif + if (schService) { + /* Cast is necessary, ChangeServiceConfig2 handles multiple + * object types, some volatile, some not. + */ +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + apr_size_t slen = strlen(full_description) + 1; + apr_size_t wslen = slen; + apr_wchar_t *full_description_w = + (apr_wchar_t*)apr_palloc(pconf, + wslen * sizeof(apr_wchar_t)); + apr_status_t rv = apr_conv_utf8_to_ucs2(full_description, &slen, + full_description_w, + &wslen); + if ((rv != APR_SUCCESS) || slen + || ChangeServiceConfig2W(schService, 1 + /*SERVICE_CONFIG_DESCRIPTION*/, + (LPVOID) &full_description_w)) + full_description = NULL; + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + if (ChangeServiceConfig2(schService, + 1 /* SERVICE_CONFIG_DESCRIPTION */, + (LPVOID) &full_description)) + full_description = NULL; + } +#endif + CloseServiceHandle(schService); + } + CloseServiceHandle(schSCManager); + } +} + +/* handle the SCM's ControlService() callbacks to our service */ + +static DWORD WINAPI service_nt_ctrl(DWORD dwCtrlCode, DWORD dwEventType, + LPVOID lpEventData, LPVOID lpContext) +{ + nt_service_ctx_t *ctx = lpContext; + + /* SHUTDOWN is offered before STOP, accept the first opportunity */ + if ((dwCtrlCode == SERVICE_CONTROL_STOP) + || (dwCtrlCode == SERVICE_CONTROL_SHUTDOWN)) + { + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + ReportStatusToSCMgr(SERVICE_STOP_PENDING, 30000, ctx); + return (NO_ERROR); + } + if (dwCtrlCode == SERVICE_APACHE_RESTART) + { + ap_signal_parent(SIGNAL_PARENT_RESTART); + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + return (NO_ERROR); + } + if (dwCtrlCode == SERVICE_CONTROL_INTERROGATE) { + ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, 0, ctx); + return (NO_ERROR); + } + + return (ERROR_CALL_NOT_IMPLEMENTED); +} + + +/* service_nt_main_fn is outside of the call stack and outside of the + * primary server thread... so now we _really_ need a placeholder! + * The winnt_rewrite_args has created and shared mpm_new_argv with us. + */ +extern apr_array_header_t *mpm_new_argv; + +#if APR_HAS_UNICODE_FS +static void __stdcall service_nt_main_fn_w(DWORD argc, LPWSTR *argv) +{ + const char *ignored; + nt_service_ctx_t *ctx = &globdat; + char *service_name; + apr_size_t wslen = wcslen(argv[0]) + 1; + apr_size_t slen = wslen * 3 - 2; + + service_name = malloc(slen); + (void)apr_conv_ucs2_to_utf8(argv[0], &wslen, service_name, &slen); + + /* args and service names live in the same pool */ + mpm_service_set_name(mpm_new_argv->pool, &ignored, service_name); + + memset(&ctx->ssStatus, 0, sizeof(ctx->ssStatus)); + ctx->ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ctx->ssStatus.dwCurrentState = SERVICE_START_PENDING; + ctx->ssStatus.dwCheckPoint = 1; + if (!(ctx->hServiceStatus = + RegisterServiceCtrlHandlerExW(argv[0], service_nt_ctrl, ctx))) + { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(00365) "Failure registering service handler"); + return; + } + + /* Report status, no errors, and buy 3 more seconds */ + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + + /* We need to append all the command arguments passed via StartService() + * to our running service... which just got here via the SCM... + * but we have no interest in argv[0] for the mpm_new_argv list. + */ + if (argc > 1) + { + char **cmb_data, **cmb; + DWORD i; + + mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1; + cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *)); + + /* mpm_new_argv remains first (of lower significance) */ + memcpy (cmb_data, mpm_new_argv->elts, + mpm_new_argv->elt_size * mpm_new_argv->nelts); + + /* Service args follow from StartService() invocation */ + memcpy (cmb_data + mpm_new_argv->nelts, argv + 1, + mpm_new_argv->elt_size * (argc - 1)); + + cmb = cmb_data + mpm_new_argv->nelts; + + for (i = 1; i < argc; ++i) + { + wslen = wcslen(argv[i]) + 1; + slen = wslen * 3 - 2; + service_name = malloc(slen); + (void)apr_conv_ucs2_to_utf8(argv[i], &wslen, *(cmb++), &slen); + } + + /* The replacement arg list is complete */ + mpm_new_argv->elts = (char *)cmb_data; + mpm_new_argv->nelts = mpm_new_argv->nalloc; + } + + /* Let the main thread continue now... but hang on to the + * signal_monitor event so we can take further action + */ + SetEvent(ctx->service_init); + + WaitForSingleObject(ctx->service_term, INFINITE); +} +#endif /* APR_HAS_UNICODE_FS */ + + +#if APR_HAS_ANSI_FS +static void __stdcall service_nt_main_fn(DWORD argc, LPSTR *argv) +{ + const char *ignored; + nt_service_ctx_t *ctx = &globdat; + + /* args and service names live in the same pool */ + mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]); + + memset(&ctx->ssStatus, 0, sizeof(ctx->ssStatus)); + ctx->ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ctx->ssStatus.dwCurrentState = SERVICE_START_PENDING; + ctx->ssStatus.dwCheckPoint = 1; + + if (!(ctx->hServiceStatus = + RegisterServiceCtrlHandlerExA(argv[0], service_nt_ctrl, ctx))) + { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(10008) "Failure registering service handler"); + return; + } + + /* Report status, no errors, and buy 3 more seconds */ + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + + /* We need to append all the command arguments passed via StartService() + * to our running service... which just got here via the SCM... + * but we have no interest in argv[0] for the mpm_new_argv list. + */ + if (argc > 1) + { + char **cmb_data; + + mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1; + cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *)); + + /* mpm_new_argv remains first (of lower significance) */ + memcpy (cmb_data, mpm_new_argv->elts, + mpm_new_argv->elt_size * mpm_new_argv->nelts); + + /* Service args follow from StartService() invocation */ + memcpy (cmb_data + mpm_new_argv->nelts, argv + 1, + mpm_new_argv->elt_size * (argc - 1)); + + /* The replacement arg list is complete */ + mpm_new_argv->elts = (char *)cmb_data; + mpm_new_argv->nelts = mpm_new_argv->nalloc; + } + + /* Let the main thread continue now... but hang on to the + * signal_monitor event so we can take further action + */ + SetEvent(ctx->service_init); + + WaitForSingleObject(ctx->service_term, INFINITE); +} +#endif + + + static DWORD WINAPI service_nt_dispatch_thread(LPVOID nada) + { +#if APR_HAS_UNICODE_FS + SERVICE_TABLE_ENTRYW dispatchTable_w[] = + { + { L"", service_nt_main_fn_w }, + { NULL, NULL } + }; +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + SERVICE_TABLE_ENTRYA dispatchTable[] = + { + { "", service_nt_main_fn }, + { NULL, NULL } + }; +#endif + apr_status_t rv; + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + rv = StartServiceCtrlDispatcherW(dispatchTable_w); +#endif +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + rv = StartServiceCtrlDispatcherA(dispatchTable); +#endif + if (rv) { + rv = APR_SUCCESS; + } + else { + /* This is a genuine failure of the SCM. */ + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00366) "Error starting Windows service control " + "dispatcher"); + } + return (rv); +} + + +/* The service configuration's is stored under the following trees: + * + * HKLM\System\CurrentControlSet\Services\[service name] + * + * \DisplayName + * \ImagePath + * \Parameters\ConfigArgs + */ + + +apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, + const char *set_name) +{ + char key_name[MAX_PATH]; + ap_regkey_t *key; + apr_status_t rv; + + /* ### Needs improvement, on Win2K the user can _easily_ + * change the display name to a string that doesn't reflect + * the internal service name + whitespace! + */ + mpm_service_name = apr_palloc(p, strlen(set_name) + 1); + apr_collapse_spaces((char*) mpm_service_name, set_name); +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + apr_size_t slen = strlen(mpm_service_name) + 1; + apr_size_t wslen = slen; + mpm_service_name_w = apr_palloc(p, wslen * sizeof(apr_wchar_t)); + rv = apr_conv_utf8_to_ucs2(mpm_service_name, &slen, + mpm_service_name_w, &wslen); + if (rv != APR_SUCCESS) + return rv; + else if (slen) + return APR_ENAMETOOLONG; + } +#endif /* APR_HAS_UNICODE_FS */ + + apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, + APR_READ, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_get(&mpm_display_name, key, "DisplayName", pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + /* Take the given literal name if there is no service entry */ + mpm_display_name = apr_pstrdup(p, set_name); + } + *display_name = mpm_display_name; + + return rv; +} + + +apr_status_t mpm_merge_service_args(apr_pool_t *p, + apr_array_header_t *args, + int fixed_args) +{ + apr_array_header_t *svc_args = NULL; + char conf_key[MAX_PATH]; + char **cmb_data; + apr_status_t rv; + ap_regkey_t *key; + + apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, conf_key, APR_READ, p); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_array_get(&svc_args, key, "ConfigArgs", p); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + if (rv == ERROR_FILE_NOT_FOUND) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00367) + "No ConfigArgs registered for the '%s' service, " + "perhaps this service is not installed?", + mpm_service_name); + return APR_SUCCESS; + } + else + return (rv); + } + + if (!svc_args || svc_args->nelts == 0) { + return (APR_SUCCESS); + } + + /* Now we have the mpm_service_name arg, and the mpm_runservice_nt() + * call appended the arguments passed by StartService(), so it's + * time to _prepend_ the default arguments for the server from + * the service's default arguments (all others override them)... + */ + args->nalloc = args->nelts + svc_args->nelts; + cmb_data = malloc(args->nalloc * sizeof(const char *)); + + /* First three args (argv[0], -f, path) remain first */ + memcpy(cmb_data, args->elts, args->elt_size * fixed_args); + + /* Service args follow from service registry array */ + memcpy(cmb_data + fixed_args, svc_args->elts, + svc_args->elt_size * svc_args->nelts); + + /* Remaining new args follow */ + memcpy(cmb_data + fixed_args + svc_args->nelts, + (const char **)args->elts + fixed_args, + args->elt_size * (args->nelts - fixed_args)); + + args->elts = (char *)cmb_data; + args->nelts = args->nalloc; + + return APR_SUCCESS; +} + + +static void service_stopped(void) +{ + /* Still have a thread & window to clean up, so signal now */ + if (globdat.service_thread) + { + /* Stop logging to the event log */ + mpm_nt_eventlog_stderr_flush(); + + /* Cause the service_nt_main_fn to complete */ + ReleaseMutex(globdat.service_term); + + ReportStatusToSCMgr(SERVICE_STOPPED, 0, &globdat); + + WaitForSingleObject(globdat.service_thread, 5000); + CloseHandle(globdat.service_thread); + } +} + + +apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p) +{ + HANDLE hProc = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + HANDLE waitfor[2]; + + /* Prevent holding open the (hidden) console */ + ap_real_exit_code = 0; + + /* GetCurrentThread returns a psuedo-handle, we need + * a real handle for another thread to wait upon. + */ + if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread), + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + return APR_ENOTHREAD; + } + + globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL); + globdat.service_term = CreateMutex(NULL, TRUE, NULL); + if (!globdat.service_init || !globdat.service_term) { + return APR_EGENERAL; + } + + globdat.service_thread = CreateThread(NULL, 65536, + service_nt_dispatch_thread, + NULL, stack_res_flag, + &globdat.service_thread_id); + + if (!globdat.service_thread) { + return APR_ENOTHREAD; + } + + waitfor[0] = globdat.service_init; + waitfor[1] = globdat.service_thread; + + /* Wait for controlling thread init or termination */ + if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) { + return APR_ENOTHREAD; + } + + atexit(service_stopped); + *display_name = mpm_display_name; + return APR_SUCCESS; +} + + +apr_status_t mpm_service_started(void) +{ + set_service_description(); + ReportStatusToSCMgr(SERVICE_RUNNING, 0, &globdat); + return APR_SUCCESS; +} + + +void mpm_service_stopping(void) +{ + ReportStatusToSCMgr(SERVICE_STOP_PENDING, 30000, &globdat); +} + + +apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, + const char * const * argv, int reconfig) +{ + char key_name[MAX_PATH]; + char *launch_cmd; + ap_regkey_t *key; + apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + DWORD rc; +#if APR_HAS_UNICODE_FS + apr_wchar_t *display_name_w; + apr_wchar_t *launch_cmd_w; +#endif + + fprintf(stderr, reconfig ? "Reconfiguring the '%s' service\n" + : "Installing the '%s' service\n", + mpm_display_name); + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + apr_size_t slen = strlen(mpm_display_name) + 1; + apr_size_t wslen = slen; + display_name_w = apr_palloc(ptemp, wslen * sizeof(apr_wchar_t)); + rv = apr_conv_utf8_to_ucs2(mpm_display_name, &slen, + display_name_w, &wslen); + if (rv != APR_SUCCESS) + return rv; + else if (slen) + return APR_ENAMETOOLONG; + + launch_cmd_w = apr_palloc(ptemp, (MAX_PATH + 17) * sizeof(apr_wchar_t)); + launch_cmd_w[0] = L'"'; + rc = GetModuleFileNameW(NULL, launch_cmd_w + 1, MAX_PATH); + wcscpy(launch_cmd_w + rc + 1, L"\" -k runservice"); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + launch_cmd = apr_palloc(ptemp, MAX_PATH + 17); + launch_cmd[0] = '"'; + rc = GetModuleFileName(NULL, launch_cmd + 1, MAX_PATH); + strcpy(launch_cmd + rc + 1, "\" -k runservice"); + } +#endif + if (rc == 0) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00368) "GetModuleFileName failed"); + return rv; + } + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CREATE_SERVICE); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00369) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Adminstrator?"); + return (rv); + } + + if (reconfig) { +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, + SERVICE_CHANGE_CONFIG); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_CHANGE_CONFIG); + } +#endif + if (!schService) { + rv = apr_get_os_error(); + CloseServiceHandle(schSCManager); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00373) "Failed to open the '%s' service", + mpm_display_name); + return (rv); + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + rc = ChangeServiceConfigW(schService, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + launch_cmd_w, NULL, NULL, + L"Tcpip\0Afd\0", NULL, NULL, + display_name_w); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + rc = ChangeServiceConfig(schService, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + launch_cmd, NULL, NULL, + "Tcpip\0Afd\0", NULL, NULL, + mpm_display_name); + } +#endif + if (!rc) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(02652) "ChangeServiceConfig failed"); + + /* !schService aborts configuration below */ + CloseServiceHandle(schService); + schService = NULL; + } + } + else { + /* RPCSS is the Remote Procedure Call (RPC) Locator required + * for DCOM communication pipes. I am far from convinced we + * should add this to the default service dependencies, but + * be warned that future apache modules or ISAPI dll's may + * depend on it. + */ +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = CreateServiceW(schSCManager, // SCManager database + mpm_service_name_w, // name of service + display_name_w, // name to display + SERVICE_ALL_ACCESS, // access required + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + launch_cmd_w, // service's binary + NULL, // no load svc group + NULL, // no tag identifier + L"Tcpip\0Afd\0", // dependencies + NULL, // use SYSTEM account + NULL); // no password + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = CreateService(schSCManager, // SCManager database + mpm_service_name, // name of service + mpm_display_name, // name to display + SERVICE_ALL_ACCESS, // access required + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + launch_cmd, // service's binary + NULL, // no load svc group + NULL, // no tag identifier + "Tcpip\0Afd\0", // dependencies + NULL, // use SYSTEM account + NULL); // no password + } +#endif + if (!schService) + { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00370) "Failed to create the '%s' service", + mpm_display_name); + CloseServiceHandle(schSCManager); + return (rv); + } + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + set_service_description(); + + /* Store the service ConfigArgs in the registry... + */ + apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, + APR_READ | APR_WRITE | APR_CREATE, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_array_set(key, "ConfigArgs", argc, argv, pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00371) "Failed to store ConfigArgs for the " + "'%s' service in the registry.", mpm_display_name); + return (rv); + } + fprintf(stderr, "The '%s' service is successfully installed.\n", + mpm_display_name); + return APR_SUCCESS; +} + + +apr_status_t mpm_service_uninstall(void) +{ + apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + fprintf(stderr, "Removing the '%s' service\n", mpm_display_name); + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CONNECT); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10009) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Adminstrator?"); + return (rv); + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, DELETE); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, DELETE); + } +#endif + if (!schService) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10010) "Failed to open the '%s' service", + mpm_display_name); + return (rv); + } + + /* assure the service is stopped before continuing + * + * This may be out of order... we might not be able to be + * granted all access if the service is running anyway. + * + * And do we want to make it *this easy* for them + * to uninstall their service unintentionally? + */ + /* ap_stop_service(schService); + */ + + if (DeleteService(schService) == 0) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00374) "Failed to delete the '%s' service", + mpm_display_name); + return (rv); + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + fprintf(stderr, "The '%s' service has been removed successfully.\n", + mpm_display_name); + return APR_SUCCESS; +} + + +/* signal_service_transition is a simple thunk to signal the service + * and monitor its successful transition. If the signal passed is 0, + * then the caller is assumed to already have performed some service + * operation to be monitored (such as StartService), and no actual + * ControlService signal is sent. + */ + +static int signal_service_transition(SC_HANDLE schService, DWORD signal, + DWORD pending, DWORD complete) +{ + if (signal && !ControlService(schService, signal, &globdat.ssStatus)) + return FALSE; + + do { + Sleep(1000); + if (!QueryServiceStatus(schService, &globdat.ssStatus)) + return FALSE; + } while (globdat.ssStatus.dwCurrentState == pending); + + return (globdat.ssStatus.dwCurrentState == complete); +} + + +apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, + const char * const * argv) +{ + apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + fprintf(stderr, "Starting the '%s' service\n", mpm_display_name); + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CONNECT); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10011) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Adminstrator?"); + return (rv); + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, + SERVICE_START | SERVICE_QUERY_STATUS); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_START | SERVICE_QUERY_STATUS); + } +#endif + if (!schService) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10012) "Failed to open the '%s' service", + mpm_display_name); + CloseServiceHandle(schSCManager); + return (rv); + } + + if (QueryServiceStatus(schService, &globdat.ssStatus) + && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, + APLOGNO(00377) "The '%s' service is already started!", + mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return 0; + } + + rv = APR_EINIT; +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + LPWSTR *start_argv_w = malloc((argc + 1) * sizeof(LPCWSTR)); + int i; + + for (i = 0; i < argc; ++i) + { + apr_size_t slen = strlen(argv[i]) + 1; + apr_size_t wslen = slen; + start_argv_w[i] = malloc(wslen * sizeof(WCHAR)); + rv = apr_conv_utf8_to_ucs2(argv[i], &slen, start_argv_w[i], &wslen); + if (rv != APR_SUCCESS) + return rv; + else if (slen) + return APR_ENAMETOOLONG; + } + start_argv_w[argc] = NULL; + + if (StartServiceW(schService, argc, start_argv_w) + && signal_service_transition(schService, 0, /* test only */ + SERVICE_START_PENDING, + SERVICE_RUNNING)) + rv = APR_SUCCESS; + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + char **start_argv = malloc((argc + 1) * sizeof(const char *)); + memcpy(start_argv, argv, argc * sizeof(const char *)); + start_argv[argc] = NULL; + + if (StartService(schService, argc, start_argv) + && signal_service_transition(schService, 0, /* test only */ + SERVICE_START_PENDING, + SERVICE_RUNNING)) + rv = APR_SUCCESS; + } +#endif + if (rv != APR_SUCCESS) + rv = apr_get_os_error(); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + if (rv == APR_SUCCESS) + fprintf(stderr, "The '%s' service is running.\n", mpm_display_name); + else + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00378) + "Failed to start the '%s' service", + mpm_display_name); + + return rv; +} + + +/* signal is zero to stop, non-zero for restart */ + +void mpm_signal_service(apr_pool_t *ptemp, int signal) +{ + int success = FALSE; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + schSCManager = OpenSCManager(NULL, NULL, /* default machine & database */ + SC_MANAGER_CONNECT); + + if (!schSCManager) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(10013) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Adminstrator?"); + return; + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, + SERVICE_INTERROGATE | SERVICE_QUERY_STATUS | + SERVICE_USER_DEFINED_CONTROL | + SERVICE_START | SERVICE_STOP); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_INTERROGATE | SERVICE_QUERY_STATUS | + SERVICE_USER_DEFINED_CONTROL | + SERVICE_START | SERVICE_STOP); + } +#endif + if (schService == NULL) { + /* Could not open the service */ + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(10014) "Failed to open the '%s' service", + mpm_display_name); + CloseServiceHandle(schSCManager); + return; + } + + if (!QueryServiceStatus(schService, &globdat.ssStatus)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(00381) "Query of the '%s' service failed", + mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + + if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) { + fprintf(stderr, "The '%s' service is not started.\n", mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + + fprintf(stderr, signal ? "The '%s' service is restarting.\n" + : "The '%s' service is stopping.\n", + mpm_display_name); + + if (!signal) + success = signal_service_transition(schService, + SERVICE_CONTROL_STOP, + SERVICE_STOP_PENDING, + SERVICE_STOPPED); + else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) { + mpm_service_start(ptemp, 0, NULL); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + else + success = signal_service_transition(schService, + SERVICE_APACHE_RESTART, + SERVICE_START_PENDING, + SERVICE_RUNNING); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + if (success) + fprintf(stderr, signal ? "The '%s' service has restarted.\n" + : "The '%s' service has stopped.\n", + mpm_display_name); + else + fprintf(stderr, signal ? "Failed to restart the '%s' service.\n" + : "Failed to stop the '%s' service.\n", + mpm_display_name); +} diff --git a/server/mpm/worker/Makefile.in b/server/mpm/worker/Makefile.in new file mode 100644 index 0000000..e32210f --- /dev/null +++ b/server/mpm/worker/Makefile.in @@ -0,0 +1,2 @@ + +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/worker/config.m4 b/server/mpm/worker/config.m4 new file mode 100644 index 0000000..1a50026 --- /dev/null +++ b/server/mpm/worker/config.m4 @@ -0,0 +1,11 @@ +AC_MSG_CHECKING(if worker MPM supports this platform) +if test $forking_mpms_supported != yes; then + AC_MSG_RESULT(no - This is not a forking platform) +elif test $ac_cv_define_APR_HAS_THREADS != yes; then + AC_MSG_RESULT(no - APR does not support threads) +elif test $have_threaded_sig_graceful != yes; then + AC_MSG_RESULT(no - SIG_GRACEFUL cannot be used with a threaded MPM) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(worker, yes, yes) +fi diff --git a/server/mpm/worker/config3.m4 b/server/mpm/worker/config3.m4 new file mode 100644 index 0000000..6c1eb17 --- /dev/null +++ b/server/mpm/worker/config3.m4 @@ -0,0 +1,5 @@ +dnl ## XXX - Need a more thorough check of the proper flags to use + +APACHE_MPM_MODULE(worker, $enable_mpm_worker, worker.lo,[ + AC_CHECK_FUNCS(pthread_kill) +]) diff --git a/server/mpm/worker/mpm_default.h b/server/mpm/worker/mpm_default.h new file mode 100644 index 0000000..464250e --- /dev/null +++ b/server/mpm/worker/mpm_default.h @@ -0,0 +1,55 @@ +/* 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. + */ + +/** + * @file worker/mpm_default.h + * @brief Worker MPM defaults + * + * @defgroup APACHE_MPM_WORKER Worker MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Number of servers to spawn off by default --- also, if fewer than + * this free when the caretaker checks, it will spawn more. + */ +#ifndef DEFAULT_START_DAEMON +#define DEFAULT_START_DAEMON 3 +#endif + +/* Maximum number of *free* server processes --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_FREE_DAEMON +#define DEFAULT_MAX_FREE_DAEMON 10 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_FREE_DAEMON +#define DEFAULT_MIN_FREE_DAEMON 3 +#endif + +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 25 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c new file mode 100644 index 0000000..8012fe2 --- /dev/null +++ b/server/mpm/worker/worker.c @@ -0,0 +1,2406 @@ +/* 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. + */ + +/* The purpose of this MPM is to fix the design flaws in the threaded + * model. Because of the way that pthreads and mutex locks interact, + * it is basically impossible to cleanly gracefully shutdown a child + * process if multiple threads are all blocked in accept. This model + * fixes those problems. + */ + +#include "apr.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "apr_file_io.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_thread_mutex.h" +#include "apr_proc_mutex.h" +#include "apr_poll.h" + +#include <stdlib.h> + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if APR_HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifdef HAVE_SYS_PROCESSOR_H +#include <sys/processor.h> /* for bindprocessor() */ +#endif + +#if !APR_HAS_THREADS +#error The Worker MPM requires APR threads, but they are unavailable. +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "ap_mpm.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "scoreboard.h" +#include "mpm_fdqueue.h" +#include "mpm_default.h" +#include "util_mutex.h" +#include "unixd.h" + +#include <signal.h> +#include <limits.h> /* for INT_MAX */ + +/* Limit on the total --- clients will be locked out if more servers than + * this are needed. It is intended solely to keep the server from crashing + * when things get out of hand. + * + * We keep a hard maximum number of servers, for two reasons --- first off, + * in case something goes seriously wrong, we want to stop the fork bomb + * short of actually crashing the machine we're running on by filling some + * kernel table. Secondly, it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_SERVER_LIMIT +#define DEFAULT_SERVER_LIMIT 16 +#endif + +/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_SERVER_LIMIT +#define MAX_SERVER_LIMIT 20000 +#endif + +/* Limit on the threads per process. Clients will be locked out if more than + * this * server_limit are needed. + * + * We keep this for one reason it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_THREAD_LIMIT +#define DEFAULT_THREAD_LIMIT 64 +#endif + +/* Admin can't tune ThreadLimit beyond MAX_THREAD_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_THREAD_LIMIT +#define MAX_THREAD_LIMIT 20000 +#endif + +/* + * Actual definitions of config globals + */ + +static int threads_per_child = 0; /* Worker threads per child */ +static int ap_daemons_to_start = 0; +static int min_spare_threads = 0; +static int max_spare_threads = 0; +static int ap_daemons_limit = 0; +static int max_workers = 0; +static int server_limit = 0; +static int thread_limit = 0; +static int had_healthy_child = 0; +static int dying = 0; +static int workers_may_exit = 0; +static int start_thread_may_exit = 0; +static int listener_may_exit = 0; +static int requests_this_child; +static int num_listensocks = 0; +static int resource_shortage = 0; +static fd_queue_t *worker_queue; +static fd_queue_info_t *worker_queue_info; +static apr_pollset_t *worker_pollset; + + +/* data retained by worker across load/unload of the module + * allocated on first call to pre-config hook; located on + * subsequent calls to pre-config hook + */ +typedef struct worker_retained_data { + ap_unixd_mpm_retained_data *mpm; + + int first_server_limit; + int first_thread_limit; + int sick_child_detected; + int maxclients_reported; + int near_maxclients_reported; + /* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers changes across AP_SIG_GRACEFUL restarts. + * We use this value to optimize routines that have to scan the entire + * scoreboard. + */ + int max_daemons_limit; + /* + * idle_spawn_rate is the number of children that will be spawned on the + * next maintenance cycle if there aren't enough idle servers. It is + * maintained per listeners bucket, doubled up to MAX_SPAWN_RATE, and + * reset only when a cycle goes by without the need to spawn. + */ + int *idle_spawn_rate; +#ifndef MAX_SPAWN_RATE +#define MAX_SPAWN_RATE (32) +#endif + int hold_off_on_exponential_spawning; +} worker_retained_data; +static worker_retained_data *retained; + +typedef struct worker_child_bucket { + ap_pod_t *pod; + ap_listen_rec *listeners; + apr_proc_mutex_t *mutex; +} worker_child_bucket; +static worker_child_bucket *all_buckets, /* All listeners buckets */ + *my_bucket; /* Current child bucket */ + +#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid) + +/* The structure used to pass unique initialization info to each thread */ +typedef struct { + int pid; + int tid; + int sd; +} proc_info; + +/* Structure used to pass information to the thread responsible for + * creating the rest of the threads. + */ +typedef struct { + apr_thread_t **threads; + apr_thread_t *listener; + int child_num_arg; + apr_threadattr_t *threadattr; +} thread_starter; + +#define ID_FROM_CHILD_THREAD(c, t) ((c * thread_limit) + t) + +/* The worker MPM respects a couple of runtime flags that can aid + * in debugging. Setting the -DNO_DETACH flag will prevent the root process + * from detaching from its controlling terminal. Additionally, setting + * the -DONE_PROCESS flag (which implies -DNO_DETACH) will get you the + * child_main loop running in the process which originally started up. + * This gives you a pretty nice debugging environment. (You'll get a SIGHUP + * early in standalone_main; just continue through. This is the server + * trying to kill off any child processes which it might have lying + * around --- Apache doesn't keep track of their pids, it just sends + * SIGHUP to the process group, ignoring it in the root process. + * Continue through and you'll be fine.). + */ + +static int one_process = 0; + +#ifdef DEBUG_SIGSTOP +int raise_sigstop_flags; +#endif + +static apr_pool_t *pconf; /* Pool for config stuff */ +static apr_pool_t *pchild; /* Pool for httpd child stuff */ +static apr_pool_t *pruntime; /* Pool for MPM threads stuff */ + +static pid_t ap_my_pid; /* Linux getpid() doesn't work except in main + thread. Use this instead */ +static pid_t parent_pid; +static apr_os_thread_t *listener_os_thread; + +#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT +#define SAFE_ACCEPT(stmt) (ap_listeners->next ? (stmt) : APR_SUCCESS) +#else +#define SAFE_ACCEPT(stmt) (stmt) +#endif + +/* The LISTENER_SIGNAL signal will be sent from the main thread to the + * listener thread to wake it up for graceful termination (what a child + * process from an old generation does when the admin does "apachectl + * graceful"). This signal will be blocked in all threads of a child + * process except for the listener thread. + */ +#define LISTENER_SIGNAL SIGHUP + +/* The WORKER_SIGNAL signal will be sent from the main thread to the + * worker threads during an ungraceful restart or shutdown. + * This ensures that on systems (i.e., Linux) where closing the worker + * socket doesn't awake the worker thread when it is polling on the socket + * (especially in apr_wait_for_io_or_timeout() when handling + * Keep-Alive connections), close_worker_sockets() and join_workers() + * still function in timely manner and allow ungraceful shutdowns to + * proceed to completion. Otherwise join_workers() doesn't return + * before the main process decides the child process is non-responsive + * and sends a SIGKILL. + */ +#define WORKER_SIGNAL AP_SIG_GRACEFUL + +/* An array of socket descriptors in use by each thread used to + * perform a non-graceful (forced) shutdown of the server. */ +static apr_socket_t **worker_sockets; + +static void close_worker_sockets(void) +{ + int i; + for (i = 0; i < threads_per_child; i++) { + if (worker_sockets[i]) { + apr_socket_close(worker_sockets[i]); + worker_sockets[i] = NULL; + } + } +} + +static void wakeup_listener(void) +{ + listener_may_exit = 1; + if (!listener_os_thread) { + /* XXX there is an obscure path that this doesn't handle perfectly: + * right after listener thread is created but before + * listener_os_thread is set, the first worker thread hits an + * error and starts graceful termination + */ + return; + } + + /* unblock the listener if it's waiting for a worker */ + ap_queue_info_term(worker_queue_info); + + /* + * we should just be able to "kill(ap_my_pid, LISTENER_SIGNAL)" on all + * platforms and wake up the listener thread since it is the only thread + * with SIGHUP unblocked, but that doesn't work on Linux + */ +#ifdef HAVE_PTHREAD_KILL + pthread_kill(*listener_os_thread, LISTENER_SIGNAL); +#else + kill(ap_my_pid, LISTENER_SIGNAL); +#endif +} + +#define ST_INIT 0 +#define ST_GRACEFUL 1 +#define ST_UNGRACEFUL 2 + +static int terminate_mode = ST_INIT; + +static void signal_threads(int mode) +{ + if (terminate_mode == mode) { + return; + } + terminate_mode = mode; + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + /* in case we weren't called from the listener thread, wake up the + * listener thread + */ + wakeup_listener(); + + /* for ungraceful termination, let the workers exit now; + * for graceful termination, the listener thread will notify the + * workers to exit once it has stopped accepting new connections + */ + if (mode == ST_UNGRACEFUL) { + workers_may_exit = 1; + ap_queue_interrupt_all(worker_queue); + close_worker_sockets(); /* forcefully kill all current connections */ + } +} + +static int worker_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = retained->max_daemons_limit; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_STATIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_DYNAMIC; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = server_limit; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = thread_limit; + break; + case AP_MPMQ_MAX_THREADS: + *result = threads_per_child; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = min_spare_threads; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = max_spare_threads; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = ap_daemons_limit; + break; + case AP_MPMQ_MPM_STATE: + *result = retained->mpm->mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = retained->mpm->my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static void worker_note_child_killed(int childnum, pid_t pid, ap_generation_t gen) +{ + if (childnum != -1) { /* child had a scoreboard slot? */ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[childnum].pid, + ap_scoreboard_image->parent[childnum].generation, + childnum, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[childnum].pid = 0; + } + else { + ap_run_child_status(ap_server_conf, pid, gen, -1, MPM_CHILD_EXITED); + } +} + +static void worker_note_child_started(int slot, pid_t pid) +{ + ap_generation_t gen = retained->mpm->my_generation; + ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].generation = gen; + ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_STARTED); +} + +static void worker_note_child_lost_slot(int slot, pid_t newpid) +{ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00263) + "pid %" APR_PID_T_FMT " taking over scoreboard slot from " + "%" APR_PID_T_FMT "%s", + newpid, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].quiescing ? + " (quiescing)" : ""); + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation, + slot, MPM_CHILD_LOST_SLOT); + /* Don't forget about this exiting child process, or we + * won't be able to kill it if it doesn't exit by the + * time the server is shut down. + */ + ap_register_extra_mpm_process(ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation); +} + +static const char *worker_get_name(void) +{ + return "worker"; +} + +/* a clean exit from a child with proper cleanup */ +static void clean_child_exit(int code) __attribute__ ((noreturn)); +static void clean_child_exit(int code) +{ + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + if (pchild) { + apr_pool_destroy(pchild); + } + + if (one_process) { + worker_note_child_killed(/* slot */ 0, 0, 0); + } + + exit(code); +} + +static void just_die(int sig) +{ + clean_child_exit(0); +} + +/***************************************************************** + * Connection structures and accounting... + */ + +static int child_fatal; + +/***************************************************************** + * Here follows a long bunch of generic server bookkeeping stuff... + */ + +/***************************************************************** + * Child process main loop. + */ + +static void process_socket(apr_thread_t *thd, apr_pool_t *p, apr_socket_t *sock, + int my_child_num, + int my_thread_num, apr_bucket_alloc_t *bucket_alloc) +{ + conn_rec *current_conn; + long conn_id = ID_FROM_CHILD_THREAD(my_child_num, my_thread_num); + ap_sb_handle_t *sbh; + + ap_create_sb_handle(&sbh, p, my_child_num, my_thread_num); + + current_conn = ap_run_create_connection(p, ap_server_conf, sock, + conn_id, sbh, bucket_alloc); + if (current_conn) { + current_conn->current_thread = thd; + ap_process_connection(current_conn, sock); + ap_lingering_close(current_conn); + } +} + +/* requests_this_child has gone to zero or below. See if the admin coded + "MaxConnectionsPerChild 0", and keep going in that case. Doing it this way + simplifies the hot path in worker_thread */ +static void check_infinite_requests(void) +{ + if (ap_max_requests_per_child) { + signal_threads(ST_GRACEFUL); + } + else { + requests_this_child = INT_MAX; /* keep going */ + } +} + +static void unblock_signal(int sig) +{ + sigset_t sig_mask; + + sigemptyset(&sig_mask); + sigaddset(&sig_mask, sig); +#if defined(SIGPROCMASK_SETS_THREAD_MASK) + sigprocmask(SIG_UNBLOCK, &sig_mask, NULL); +#else + pthread_sigmask(SIG_UNBLOCK, &sig_mask, NULL); +#endif +} + +static void dummy_signal_handler(int sig) +{ + /* XXX If specifying SIG_IGN is guaranteed to unblock a syscall, + * then we don't need this goofy function. + */ +} + +static void accept_mutex_error(const char *func, apr_status_t rv, int process_slot) +{ + int level = APLOG_EMERG; + + if (ap_scoreboard_image->parent[process_slot].generation != + ap_scoreboard_image->global->running_generation) { + level = APLOG_DEBUG; /* common to get these at restart time */ + } + else if (requests_this_child == INT_MAX + || ((requests_this_child == ap_max_requests_per_child) + && ap_max_requests_per_child)) { + ap_log_error(APLOG_MARK, level, rv, ap_server_conf, APLOGNO(00272) + "apr_proc_mutex_%s failed " + "before this child process served any requests.", + func); + clean_child_exit(APEXIT_CHILDSICK); + } + ap_log_error(APLOG_MARK, level, rv, ap_server_conf, APLOGNO(00273) + "apr_proc_mutex_%s failed. Attempting to " + "shutdown process gracefully.", func); + signal_threads(ST_GRACEFUL); +} + +static void * APR_THREAD_FUNC listener_thread(apr_thread_t *thd, void * dummy) +{ + proc_info * ti = dummy; + int process_slot = ti->pid; + void *csd = NULL; + apr_pool_t *ptrans = NULL; /* Pool for per-transaction stuff */ + apr_status_t rv; + ap_listen_rec *lr = NULL; + int have_idle_worker = 0; + int last_poll_idx = 0; + + free(ti); + + /* Unblock the signal used to wake this thread up, and set a handler for + * it. + */ + unblock_signal(LISTENER_SIGNAL); + apr_signal(LISTENER_SIGNAL, dummy_signal_handler); + + /* TODO: Switch to a system where threads reuse the results from earlier + poll calls - manoj */ + while (1) { + /* TODO: requests_this_child should be synchronized - aaron */ + if (requests_this_child <= 0) { + check_infinite_requests(); + } + if (listener_may_exit) break; + + if (!have_idle_worker) { + rv = ap_queue_info_wait_for_idler(worker_queue_info, NULL); + if (APR_STATUS_IS_EOF(rv)) { + break; /* we've been signaled to die now */ + } + else if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + "apr_queue_info_wait failed. Attempting to " + " shutdown process gracefully."); + signal_threads(ST_GRACEFUL); + break; + } + have_idle_worker = 1; + } + + /* We've already decremented the idle worker count inside + * ap_queue_info_wait_for_idler. */ + + if ((rv = SAFE_ACCEPT(apr_proc_mutex_lock(my_bucket->mutex))) + != APR_SUCCESS) { + + if (!listener_may_exit) { + accept_mutex_error("lock", rv, process_slot); + } + break; /* skip the lock release */ + } + + if (!my_bucket->listeners->next) { + /* Only one listener, so skip the poll */ + lr = my_bucket->listeners; + } + else { + while (!listener_may_exit) { + apr_int32_t numdesc; + const apr_pollfd_t *pdesc; + + rv = apr_pollset_poll(worker_pollset, -1, &numdesc, &pdesc); + if (rv != APR_SUCCESS) { + if (APR_STATUS_IS_EINTR(rv)) { + continue; + } + + /* apr_pollset_poll() will only return errors in catastrophic + * circumstances. Let's try exiting gracefully, for now. */ + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03137) + "apr_pollset_poll: (listen)"); + signal_threads(ST_GRACEFUL); + } + + if (listener_may_exit) break; + + /* We can always use pdesc[0], but sockets at position N + * could end up completely starved of attention in a very + * busy server. Therefore, we round-robin across the + * returned set of descriptors. While it is possible that + * the returned set of descriptors might flip around and + * continue to starve some sockets, we happen to know the + * internal pollset implementation retains ordering + * stability of the sockets. Thus, the round-robin should + * ensure that a socket will eventually be serviced. + */ + if (last_poll_idx >= numdesc) + last_poll_idx = 0; + + /* Grab a listener record from the client_data of the poll + * descriptor, and advance our saved index to round-robin + * the next fetch. + * + * ### hmm... this descriptor might have POLLERR rather + * ### than POLLIN + */ + lr = pdesc[last_poll_idx++].client_data; + break; + + } /* while */ + + } /* if/else */ + + if (!listener_may_exit) { + /* the following pops a recycled ptrans pool off a stack */ + ap_queue_info_pop_pool(worker_queue_info, &ptrans); + if (ptrans == NULL) { + /* we can't use a recycled transaction pool this time. + * create a new transaction pool */ + apr_allocator_t *allocator; + + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + apr_pool_create_ex(&ptrans, pconf, NULL, allocator); + apr_allocator_owner_set(allocator, ptrans); + apr_pool_tag(ptrans, "transaction"); + } + rv = lr->accept_func(&csd, lr, ptrans); + /* later we trash rv and rely on csd to indicate success/failure */ + AP_DEBUG_ASSERT(rv == APR_SUCCESS || !csd); + + if (rv == APR_EGENERAL) { + /* E[NM]FILE, ENOMEM, etc */ + resource_shortage = 1; + signal_threads(ST_GRACEFUL); + } + if ((rv = SAFE_ACCEPT(apr_proc_mutex_unlock(my_bucket->mutex))) + != APR_SUCCESS) { + + if (listener_may_exit) { + break; + } + accept_mutex_error("unlock", rv, process_slot); + } + if (csd != NULL) { + rv = ap_queue_push_socket(worker_queue, csd, NULL, ptrans); + if (rv) { + /* trash the connection; we couldn't queue the connected + * socket to a worker + */ + apr_socket_close(csd); + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(03138) + "ap_queue_push_socket failed"); + } + else { + have_idle_worker = 0; + } + } + } + else { + if ((rv = SAFE_ACCEPT(apr_proc_mutex_unlock(my_bucket->mutex))) + != APR_SUCCESS) { + int level = APLOG_EMERG; + + if (ap_scoreboard_image->parent[process_slot].generation != + ap_scoreboard_image->global->running_generation) { + level = APLOG_DEBUG; /* common to get these at restart time */ + } + ap_log_error(APLOG_MARK, level, rv, ap_server_conf, APLOGNO(00274) + "apr_proc_mutex_unlock failed. Attempting to " + "shutdown process gracefully."); + signal_threads(ST_GRACEFUL); + } + break; + } + } + + ap_close_listeners_ex(my_bucket->listeners); + ap_queue_info_free_idle_pools(worker_queue_info); + ap_queue_term(worker_queue); + dying = 1; + ap_scoreboard_image->parent[process_slot].quiescing = 1; + + /* wake up the main thread */ + kill(ap_my_pid, SIGTERM); + + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +/* XXX For ungraceful termination/restart, we definitely don't want to + * wait for active connections to finish but we may want to wait + * for idle workers to get out of the queue code and release mutexes, + * since those mutexes are cleaned up pretty soon and some systems + * may not react favorably (i.e., segfault) if operations are attempted + * on cleaned-up mutexes. + */ +static void * APR_THREAD_FUNC worker_thread(apr_thread_t *thd, void * dummy) +{ + proc_info * ti = dummy; + int process_slot = ti->pid; + int thread_slot = ti->tid; + apr_socket_t *csd = NULL; + apr_bucket_alloc_t *bucket_alloc; + apr_pool_t *last_ptrans = NULL; + apr_pool_t *ptrans; /* Pool for per-transaction stuff */ + apr_status_t rv; + int is_idle = 0; + + free(ti); + + ap_scoreboard_image->servers[process_slot][thread_slot].pid = ap_my_pid; + ap_scoreboard_image->servers[process_slot][thread_slot].tid = apr_os_thread_current(); + ap_scoreboard_image->servers[process_slot][thread_slot].generation = retained->mpm->my_generation; + ap_update_child_status_from_indexes(process_slot, thread_slot, + SERVER_STARTING, NULL); + +#ifdef HAVE_PTHREAD_KILL + unblock_signal(WORKER_SIGNAL); + apr_signal(WORKER_SIGNAL, dummy_signal_handler); +#endif + + while (!workers_may_exit) { + if (!is_idle) { + rv = ap_queue_info_set_idle(worker_queue_info, last_ptrans); + last_ptrans = NULL; + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + "ap_queue_info_set_idle failed. Attempting to " + "shutdown process gracefully."); + signal_threads(ST_GRACEFUL); + break; + } + is_idle = 1; + } + + ap_update_child_status_from_indexes(process_slot, thread_slot, + SERVER_READY, NULL); +worker_pop: + if (workers_may_exit) { + break; + } + rv = ap_queue_pop_socket(worker_queue, &csd, &ptrans); + + if (rv != APR_SUCCESS) { + /* We get APR_EOF during a graceful shutdown once all the connections + * accepted by this server process have been handled. + */ + if (APR_STATUS_IS_EOF(rv)) { + break; + } + /* We get APR_EINTR whenever ap_queue_pop_*() has been interrupted + * from an explicit call to ap_queue_interrupt_all(). This allows + * us to unblock threads stuck in ap_queue_pop_*() when a shutdown + * is pending. + * + * If workers_may_exit is set and this is ungraceful termination/ + * restart, we are bound to get an error on some systems (e.g., + * AIX, which sanity-checks mutex operations) since the queue + * may have already been cleaned up. Don't log the "error" if + * workers_may_exit is set. + */ + else if (APR_STATUS_IS_EINTR(rv)) { + goto worker_pop; + } + /* We got some other error. */ + else if (!workers_may_exit) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(03139) + "ap_queue_pop_socket failed"); + } + continue; + } + is_idle = 0; + worker_sockets[thread_slot] = csd; + bucket_alloc = apr_bucket_alloc_create(ptrans); + process_socket(thd, ptrans, csd, process_slot, thread_slot, bucket_alloc); + worker_sockets[thread_slot] = NULL; + requests_this_child--; + apr_pool_clear(ptrans); + last_ptrans = ptrans; + } + + ap_update_child_status_from_indexes(process_slot, thread_slot, + dying ? SERVER_DEAD + : SERVER_GRACEFUL, NULL); + + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +static int check_signal(int signum) +{ + switch (signum) { + case SIGTERM: + case SIGINT: + return 1; + } + return 0; +} + +static void create_listener_thread(thread_starter *ts) +{ + int my_child_num = ts->child_num_arg; + apr_threadattr_t *thread_attr = ts->threadattr; + proc_info *my_info; + apr_status_t rv; + + my_info = (proc_info *)ap_malloc(sizeof(proc_info)); + my_info->pid = my_child_num; + my_info->tid = -1; /* listener thread doesn't have a thread slot */ + my_info->sd = 0; + rv = apr_thread_create(&ts->listener, thread_attr, listener_thread, + my_info, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00275) + "apr_thread_create: unable to create listener thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + apr_os_thread_get(&listener_os_thread, ts->listener); +} + +static void setup_threads_runtime(void) +{ + ap_listen_rec *lr; + apr_status_t rv; + + /* All threads (listener, workers) and synchronization objects (queues, + * pollset, mutexes...) created here should have at least the lifetime of + * the connections they handle (i.e. ptrans). We can't use this thread's + * self pool because all these objects survive it, nor use pchild or pconf + * directly because this starter thread races with other modules' runtime, + * nor finally pchild (or subpool thereof) because it is killed explicitely + * before pconf (thus connections/ptrans can live longer, which matters in + * ONE_PROCESS mode). So this leaves us with a subpool of pconf, created + * before any ptrans hence destroyed after. + */ + apr_pool_create(&pruntime, pconf); + apr_pool_tag(pruntime, "mpm_runtime"); + + /* We must create the fd queues before we start up the listener + * and worker threads. */ + rv = ap_queue_create(&worker_queue, threads_per_child, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03140) + "ap_queue_create() failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + rv = ap_queue_info_create(&worker_queue_info, pruntime, + threads_per_child, -1); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03141) + "ap_queue_info_create() failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create the main pollset */ + rv = apr_pollset_create(&worker_pollset, num_listensocks, pruntime, + APR_POLLSET_NOCOPY); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(03285) + "Couldn't create pollset in thread;" + " check system or user limits"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + + for (lr = my_bucket->listeners; lr != NULL; lr = lr->next) { + apr_pollfd_t *pfd = apr_pcalloc(pruntime, sizeof *pfd); + + pfd->desc_type = APR_POLL_SOCKET; + pfd->desc.s = lr->sd; + pfd->reqevents = APR_POLLIN; + pfd->client_data = lr; + + rv = apr_pollset_add(worker_pollset, pfd); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(03286) + "Couldn't create add listener to pollset;" + " check system or user limits"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + + lr->accept_func = ap_unixd_accept; + } + + worker_sockets = apr_pcalloc(pruntime, threads_per_child * + sizeof(apr_socket_t *)); +} + +/* XXX under some circumstances not understood, children can get stuck + * in start_threads forever trying to take over slots which will + * never be cleaned up; for now there is an APLOG_DEBUG message issued + * every so often when this condition occurs + */ +static void * APR_THREAD_FUNC start_threads(apr_thread_t *thd, void *dummy) +{ + thread_starter *ts = dummy; + apr_thread_t **threads = ts->threads; + apr_threadattr_t *thread_attr = ts->threadattr; + int my_child_num = ts->child_num_arg; + proc_info *my_info; + apr_status_t rv; + int threads_created = 0; + int listener_started = 0; + int prev_threads_created; + int loops, i; + + loops = prev_threads_created = 0; + while (1) { + /* threads_per_child does not include the listener thread */ + for (i = 0; i < threads_per_child; i++) { + int status = ap_scoreboard_image->servers[my_child_num][i].status; + + if (status != SERVER_GRACEFUL && status != SERVER_DEAD) { + continue; + } + + my_info = (proc_info *)ap_malloc(sizeof(proc_info)); + my_info->pid = my_child_num; + my_info->tid = i; + my_info->sd = 0; + + /* We are creating threads right now */ + ap_update_child_status_from_indexes(my_child_num, i, + SERVER_STARTING, NULL); + /* We let each thread update its own scoreboard entry. This is + * done because it lets us deal with tid better. + */ + rv = apr_thread_create(&threads[i], thread_attr, + worker_thread, my_info, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03142) + "apr_thread_create: unable to create worker thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + threads_created++; + } + /* Start the listener only when there are workers available */ + if (!listener_started && threads_created) { + create_listener_thread(ts); + listener_started = 1; + } + if (start_thread_may_exit || threads_created == threads_per_child) { + break; + } + /* wait for previous generation to clean up an entry */ + apr_sleep(apr_time_from_sec(1)); + ++loops; + if (loops % 120 == 0) { /* every couple of minutes */ + if (prev_threads_created == threads_created) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "child %" APR_PID_T_FMT " isn't taking over " + "slots very quickly (%d of %d)", + ap_my_pid, threads_created, threads_per_child); + } + prev_threads_created = threads_created; + } + } + + /* What state should this child_main process be listed as in the + * scoreboard...? + * ap_update_child_status_from_indexes(my_child_num, i, SERVER_STARTING, + * (request_rec *) NULL); + * + * This state should be listed separately in the scoreboard, in some kind + * of process_status, not mixed in with the worker threads' status. + * "life_status" is almost right, but it's in the worker's structure, and + * the name could be clearer. gla + */ + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +static void join_workers(apr_thread_t *listener, apr_thread_t **threads, + int mode) +{ + int i; + apr_status_t rv, thread_rv; + + if (listener) { + int iter; + + /* deal with a rare timing window which affects waking up the + * listener thread... if the signal sent to the listener thread + * is delivered between the time it verifies that the + * listener_may_exit flag is clear and the time it enters a + * blocking syscall, the signal didn't do any good... work around + * that by sleeping briefly and sending it again + */ + + iter = 0; + while (iter < 10 && +#ifdef HAVE_PTHREAD_KILL + pthread_kill(*listener_os_thread, 0) +#else + kill(ap_my_pid, 0) +#endif + == 0) { + /* listener not dead yet */ + apr_sleep(apr_time_make(0, 500000)); + wakeup_listener(); + ++iter; + } + if (iter >= 10) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00276) + "the listener thread didn't exit"); + } + else { + rv = apr_thread_join(&thread_rv, listener); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00277) + "apr_thread_join: unable to join listener thread"); + } + } + } + + for (i = 0; i < threads_per_child; i++) { + if (threads[i]) { /* if we ever created this thread */ + if (mode != ST_GRACEFUL) { +#ifdef HAVE_PTHREAD_KILL + apr_os_thread_t *worker_os_thread; + + apr_os_thread_get(&worker_os_thread, threads[i]); + pthread_kill(*worker_os_thread, WORKER_SIGNAL); +#endif + } + + rv = apr_thread_join(&thread_rv, threads[i]); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00278) + "apr_thread_join: unable to join worker " + "thread %d", + i); + } + } + } +} + +static void join_start_thread(apr_thread_t *start_thread_id) +{ + apr_status_t rv, thread_rv; + + start_thread_may_exit = 1; /* tell it to give up in case it is still + * trying to take over slots from a + * previous generation + */ + rv = apr_thread_join(&thread_rv, start_thread_id); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00279) + "apr_thread_join: unable to join the start " + "thread"); + } +} + +static void child_main(int child_num_arg, int child_bucket) +{ + apr_thread_t **threads; + apr_status_t rv; + thread_starter *ts; + apr_threadattr_t *thread_attr; + apr_thread_t *start_thread_id; + int i; + + /* for benefit of any hooks that run as this child initializes */ + retained->mpm->mpm_state = AP_MPMQ_STARTING; + + ap_my_pid = getpid(); + ap_fatal_signal_child_setup(ap_server_conf); + + /* Get a sub context for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + /* close unused listeners and pods */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (i != child_bucket) { + ap_close_listeners_ex(all_buckets[i].listeners); + ap_mpm_podx_close(all_buckets[i].pod); + } + } + + /*stuff to do before we switch id's, so we have permissions.*/ + ap_reopen_scoreboard(pchild, NULL, 0); + + rv = SAFE_ACCEPT(apr_proc_mutex_child_init(&my_bucket->mutex, + apr_proc_mutex_lockfile(my_bucket->mutex), + pchild)); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00280) + "Couldn't initialize cross-process lock in child"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* done with init critical section */ + if (ap_run_drop_privileges(pchild, ap_server_conf)) { + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Just use the standard apr_setup_signal_thread to block all signals + * from being received. The child processes no longer use signals for + * any communication with the parent process. Let's also do this before + * child_init() hooks are called and possibly create threads that + * otherwise could "steal" (implicitely) MPM's signals. + */ + rv = apr_setup_signal_thread(); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00281) + "Couldn't initialize signal thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_run_child_init(pchild, ap_server_conf); + + if (ap_max_requests_per_child) { + requests_this_child = ap_max_requests_per_child; + } + else { + /* coding a value of zero means infinity */ + requests_this_child = INT_MAX; + } + + /* Setup threads */ + + /* Globals used by signal_threads() so to be initialized before */ + setup_threads_runtime(); + + /* clear the storage; we may not create all our threads immediately, + * and we want a 0 entry to indicate a thread which was not created + */ + threads = (apr_thread_t **)ap_calloc(1, + sizeof(apr_thread_t *) * threads_per_child); + ts = (thread_starter *)apr_palloc(pchild, sizeof(*ts)); + + apr_threadattr_create(&thread_attr, pchild); + /* 0 means PTHREAD_CREATE_JOINABLE */ + apr_threadattr_detach_set(thread_attr, 0); + + if (ap_thread_stacksize != 0) { + rv = apr_threadattr_stacksize_set(thread_attr, ap_thread_stacksize); + if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(02435) + "WARNING: ThreadStackSize of %" APR_SIZE_T_FMT " is " + "inappropriate, using default", + ap_thread_stacksize); + } + } + + ts->threads = threads; + ts->listener = NULL; + ts->child_num_arg = child_num_arg; + ts->threadattr = thread_attr; + + rv = apr_thread_create(&start_thread_id, thread_attr, start_threads, + ts, pchild); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00282) + "apr_thread_create: unable to create worker thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + /* If we are only running in one_process mode, we will want to + * still handle signals. */ + if (one_process) { + /* Block until we get a terminating signal. */ + apr_signal_thread(check_signal); + /* make sure the start thread has finished; signal_threads() + * and join_workers() depend on that + */ + /* XXX join_start_thread() won't be awakened if one of our + * threads encounters a critical error and attempts to + * shutdown this child + */ + join_start_thread(start_thread_id); + signal_threads(ST_UNGRACEFUL); /* helps us terminate a little more + * quickly than the dispatch of the signal thread + * beats the Pipe of Death and the browsers + */ + /* A terminating signal was received. Now join each of the + * workers to clean them up. + * If the worker already exited, then the join frees + * their resources and returns. + * If the worker hasn't exited, then this blocks until + * they have (then cleans up). + */ + join_workers(ts->listener, threads, ST_UNGRACEFUL); + } + else { /* !one_process */ + /* remove SIGTERM from the set of blocked signals... if one of + * the other threads in the process needs to take us down + * (e.g., for MaxConnectionsPerChild) it will send us SIGTERM + */ + unblock_signal(SIGTERM); + apr_signal(SIGTERM, dummy_signal_handler); + /* Watch for any messages from the parent over the POD */ + while (1) { + rv = ap_mpm_podx_check(my_bucket->pod); + if (rv == AP_MPM_PODX_NORESTART) { + /* see if termination was triggered while we slept */ + switch(terminate_mode) { + case ST_GRACEFUL: + rv = AP_MPM_PODX_GRACEFUL; + break; + case ST_UNGRACEFUL: + rv = AP_MPM_PODX_RESTART; + break; + } + } + if (rv == AP_MPM_PODX_GRACEFUL || rv == AP_MPM_PODX_RESTART) { + /* make sure the start thread has finished; + * signal_threads() and join_workers depend on that + */ + join_start_thread(start_thread_id); + signal_threads(rv == AP_MPM_PODX_GRACEFUL ? ST_GRACEFUL : ST_UNGRACEFUL); + break; + } + } + + /* A terminating signal was received. Now join each of the + * workers to clean them up. + * If the worker already exited, then the join frees + * their resources and returns. + * If the worker hasn't exited, then this blocks until + * they have (then cleans up). + */ + join_workers(ts->listener, threads, + rv == AP_MPM_PODX_GRACEFUL ? ST_GRACEFUL : ST_UNGRACEFUL); + } + + free(threads); + + clean_child_exit(resource_shortage ? APEXIT_CHILDSICK : 0); +} + +static int make_child(server_rec *s, int slot, int bucket) +{ + int pid; + + if (slot + 1 > retained->max_daemons_limit) { + retained->max_daemons_limit = slot + 1; + } + + if (one_process) { + my_bucket = &all_buckets[0]; + + worker_note_child_started(slot, getpid()); + child_main(slot, 0); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + if ((pid = fork()) == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(00283) + "fork: Unable to fork new process"); + /* fork didn't succeed. There's no need to touch the scoreboard; + * if we were trying to replace a failed child process, then + * server_main_loop() marked its workers SERVER_DEAD, and if + * we were trying to replace a child process that exited normally, + * its worker_thread()s left SERVER_DEAD or SERVER_GRACEFUL behind. + */ + + /* In case system resources are maxxed out, we don't want + Apache running away with the CPU trying to fork over and + over and over again. */ + apr_sleep(apr_time_from_sec(10)); + + return -1; + } + + if (!pid) { + my_bucket = &all_buckets[bucket]; + +#ifdef HAVE_BINDPROCESSOR + /* By default, AIX binds to a single processor. This bit unbinds + * children which will then bind to another CPU. + */ + int status = bindprocessor(BINDPROCESS, (int)getpid(), + PROCESSOR_CLASS_ANY); + if (status != OK) + ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, + ap_server_conf, APLOGNO(00284) + "processor unbind failed"); +#endif + RAISE_SIGSTOP(MAKE_CHILD); + + apr_signal(SIGTERM, just_die); + child_main(slot, bucket); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + if (ap_scoreboard_image->parent[slot].pid != 0) { + /* This new child process is squatting on the scoreboard + * entry owned by an exiting child process, which cannot + * exit until all active requests complete. + */ + worker_note_child_lost_slot(slot, pid); + } + ap_scoreboard_image->parent[slot].quiescing = 0; + ap_scoreboard_image->parent[slot].bucket = bucket; + worker_note_child_started(slot, pid); + return 0; +} + +/* start up a bunch of children */ +static void startup_children(int number_to_start) +{ + int i; + + for (i = 0; number_to_start && i < ap_daemons_limit; ++i) { + if (ap_scoreboard_image->parent[i].pid != 0) { + continue; + } + if (make_child(ap_server_conf, i, i % retained->mpm->num_buckets) < 0) { + break; + } + --number_to_start; + } +} + +static void perform_idle_server_maintenance(int child_bucket, int num_buckets) +{ + int i, j; + int idle_thread_count; + worker_score *ws; + process_score *ps; + int free_length; + int totally_free_length = 0; + int free_slots[MAX_SPAWN_RATE]; + int last_non_dead; + int total_non_dead; + int active_thread_count = 0; + + /* initialize the free_list */ + free_length = 0; + + idle_thread_count = 0; + last_non_dead = -1; + total_non_dead = 0; + + for (i = 0; i < ap_daemons_limit; ++i) { + /* Initialization to satisfy the compiler. It doesn't know + * that threads_per_child is always > 0 */ + int status = SERVER_DEAD; + int any_dying_threads = 0; + int any_dead_threads = 0; + int all_dead_threads = 1; + int child_threads_active = 0; + + if (i >= retained->max_daemons_limit && + totally_free_length == retained->idle_spawn_rate[child_bucket]) { + /* short cut if all active processes have been examined and + * enough empty scoreboard slots have been found + */ + break; + } + ps = &ap_scoreboard_image->parent[i]; + for (j = 0; j < threads_per_child; j++) { + ws = &ap_scoreboard_image->servers[i][j]; + status = ws->status; + + /* XXX any_dying_threads is probably no longer needed GLA */ + any_dying_threads = any_dying_threads || + (status == SERVER_GRACEFUL); + any_dead_threads = any_dead_threads || (status == SERVER_DEAD); + all_dead_threads = all_dead_threads && + (status == SERVER_DEAD || + status == SERVER_GRACEFUL); + + /* We consider a starting server as idle because we started it + * at least a cycle ago, and if it still hasn't finished starting + * then we're just going to swamp things worse by forking more. + * So we hopefully won't need to fork more if we count it. + * This depends on the ordering of SERVER_READY and SERVER_STARTING. + */ + if (ps->pid != 0) { /* XXX just set all_dead_threads in outer for + loop if no pid? not much else matters */ + if (status <= SERVER_READY && + !ps->quiescing && + ps->generation == retained->mpm->my_generation && + ps->bucket == child_bucket) { + ++idle_thread_count; + } + if (status >= SERVER_READY && status < SERVER_GRACEFUL) { + ++child_threads_active; + } + } + } + active_thread_count += child_threads_active; + if (any_dead_threads + && totally_free_length < retained->idle_spawn_rate[child_bucket] + && free_length < MAX_SPAWN_RATE / num_buckets + && (!ps->pid /* no process in the slot */ + || ps->quiescing)) { /* or at least one is going away */ + if (all_dead_threads) { + /* great! we prefer these, because the new process can + * start more threads sooner. So prioritize this slot + * by putting it ahead of any slots with active threads. + * + * first, make room by moving a slot that's potentially still + * in use to the end of the array + */ + free_slots[free_length] = free_slots[totally_free_length]; + free_slots[totally_free_length++] = i; + } + else { + /* slot is still in use - back of the bus + */ + free_slots[free_length] = i; + } + ++free_length; + } + else if (child_threads_active == threads_per_child) { + had_healthy_child = 1; + } + /* XXX if (!ps->quiescing) is probably more reliable GLA */ + if (!any_dying_threads) { + last_non_dead = i; + ++total_non_dead; + } + } + + if (retained->sick_child_detected) { + if (had_healthy_child) { + /* Assume this is a transient error, even though it may not be. Leave + * the server up in case it is able to serve some requests or the + * problem will be resolved. + */ + retained->sick_child_detected = 0; + } + else { + /* looks like a basket case, as no child ever fully initialized; give up. + */ + retained->mpm->shutdown_pending = 1; + child_fatal = 1; + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, + ap_server_conf, APLOGNO(02325) + "A resource shortage or other unrecoverable failure " + "was encountered before any child process initialized " + "successfully... httpd is exiting!"); + /* the child already logged the failure details */ + return; + } + } + + retained->max_daemons_limit = last_non_dead + 1; + + if (idle_thread_count > max_spare_threads / num_buckets) { + /* Kill off one child */ + ap_mpm_podx_signal(all_buckets[child_bucket].pod, + AP_MPM_PODX_GRACEFUL); + retained->idle_spawn_rate[child_bucket] = 1; + } + else if (idle_thread_count < min_spare_threads / num_buckets) { + /* terminate the free list */ + if (free_length == 0) { /* scoreboard is full, can't fork */ + + if (active_thread_count >= ap_daemons_limit * threads_per_child) { + /* no threads are "inactive" - starting, stopping, etc. */ + /* have we reached MaxRequestWorkers, or just getting close? */ + if (0 == idle_thread_count) { + if (!retained->maxclients_reported) { + /* only report this condition once */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00286) + "server reached MaxRequestWorkers " + "setting, consider raising the " + "MaxRequestWorkers setting"); + retained->maxclients_reported = 1; + } + } else { + if (!retained->near_maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00287) + "server is within MinSpareThreads of " + "MaxRequestWorkers, consider raising the " + "MaxRequestWorkers setting"); + retained->near_maxclients_reported = 1; + } + } + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, + ap_server_conf, APLOGNO(00288) + "scoreboard is full, not at MaxRequestWorkers"); + } + retained->idle_spawn_rate[child_bucket] = 1; + } + else { + if (free_length > retained->idle_spawn_rate[child_bucket]) { + free_length = retained->idle_spawn_rate[child_bucket]; + } + if (retained->idle_spawn_rate[child_bucket] >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, + ap_server_conf, APLOGNO(00289) + "server seems busy, (you may need " + "to increase StartServers, ThreadsPerChild " + "or Min/MaxSpareThreads), " + "spawning %d children, there are around %d idle " + "threads, and %d total children", free_length, + idle_thread_count, total_non_dead); + } + for (i = 0; i < free_length; ++i) { + make_child(ap_server_conf, free_slots[i], child_bucket); + } + /* the next time around we want to spawn twice as many if this + * wasn't good enough, but not if we've just done a graceful + */ + if (retained->hold_off_on_exponential_spawning) { + --retained->hold_off_on_exponential_spawning; + } + else if (retained->idle_spawn_rate[child_bucket] + < MAX_SPAWN_RATE / num_buckets) { + retained->idle_spawn_rate[child_bucket] *= 2; + } + } + } + else { + retained->idle_spawn_rate[child_bucket] = 1; + } +} + +static void server_main_loop(int remaining_children_to_start, int num_buckets) +{ + ap_generation_t old_gen; + int child_slot; + apr_exit_why_e exitwhy; + int status, processed_status; + apr_proc_t pid; + int i; + + while (!retained->mpm->restart_pending && !retained->mpm->shutdown_pending) { + ap_wait_or_timeout(&exitwhy, &status, &pid, pconf, ap_server_conf); + + if (pid.pid != -1) { + processed_status = ap_process_child_status(&pid, exitwhy, status); + child_slot = ap_find_child_by_pid(&pid); + if (processed_status == APEXIT_CHILDFATAL) { + /* fix race condition found in PR 39311 + * A child created at the same time as a graceful happens + * can find the lock missing and create a fatal error. + * It is not fatal for the last generation to be in this state. + */ + if (child_slot < 0 + || ap_get_scoreboard_process(child_slot)->generation + == retained->mpm->my_generation) { + retained->mpm->shutdown_pending = 1; + child_fatal = 1; + return; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00290) + "Ignoring fatal error in child of previous " + "generation (pid %ld).", + (long)pid.pid); + retained->sick_child_detected = 1; + } + } + else if (processed_status == APEXIT_CHILDSICK) { + /* tell perform_idle_server_maintenance to check into this + * on the next timer pop + */ + retained->sick_child_detected = 1; + } + /* non-fatal death... note that it's gone in the scoreboard. */ + if (child_slot >= 0) { + process_score *ps; + + for (i = 0; i < threads_per_child; i++) + ap_update_child_status_from_indexes(child_slot, i, + SERVER_DEAD, NULL); + + worker_note_child_killed(child_slot, 0, 0); + ps = &ap_scoreboard_image->parent[child_slot]; + ps->quiescing = 0; + if (processed_status == APEXIT_CHILDSICK) { + /* resource shortage, minimize the fork rate */ + retained->idle_spawn_rate[ps->bucket] = 1; + } + else if (remaining_children_to_start + && child_slot < ap_daemons_limit) { + /* we're still doing a 1-for-1 replacement of dead + * children with new children + */ + make_child(ap_server_conf, child_slot, ps->bucket); + --remaining_children_to_start; + } + } + else if (ap_unregister_extra_mpm_process(pid.pid, &old_gen) == 1) { + worker_note_child_killed(-1, /* already out of the scoreboard */ + pid.pid, old_gen); + if (processed_status == APEXIT_CHILDSICK + && old_gen == retained->mpm->my_generation) { + /* resource shortage, minimize the fork rate */ + for (i = 0; i < num_buckets; i++) { + retained->idle_spawn_rate[i] = 1; + } + } +#if APR_HAS_OTHER_CHILD + } + else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, + status) == 0) { + /* handled */ +#endif + } + else if (retained->mpm->was_graceful) { + /* Great, we've probably just lost a slot in the + * scoreboard. Somehow we don't know about this child. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, + ap_server_conf, APLOGNO(00291) + "long lost child came home! (pid %ld)", + (long)pid.pid); + } + /* Don't perform idle maintenance when a child dies, + * only do it when there's a timeout. Remember only a + * finite number of children can die, and it's pretty + * pathological for a lot to die suddenly. + */ + continue; + } + else if (remaining_children_to_start) { + /* we hit a 1 second timeout in which none of the previous + * generation of children needed to be reaped... so assume + * they're all done, and pick up the slack if any is left. + */ + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + /* In any event we really shouldn't do the code below because + * few of the servers we just started are in the IDLE state + * yet, so we'd mistakenly create an extra server. + */ + continue; + } + + for (i = 0; i < num_buckets; i++) { + perform_idle_server_maintenance(i, num_buckets); + } + } +} + +static int worker_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +{ + int num_buckets = retained->mpm->num_buckets; + int remaining_children_to_start; + int i; + + ap_log_pid(pconf, ap_pid_fname); + + if (!retained->mpm->was_graceful) { + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + return !OK; + } + /* fix the generation number in the global score; we just got a new, + * cleared scoreboard + */ + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + } + + ap_unixd_mpm_set_signals(pconf, one_process); + + /* Don't thrash since num_buckets depends on the + * system and the number of online CPU cores... + */ + if (ap_daemons_limit < num_buckets) + ap_daemons_limit = num_buckets; + if (ap_daemons_to_start < num_buckets) + ap_daemons_to_start = num_buckets; + /* We want to create as much children at a time as the number of buckets, + * so to optimally accept connections (evenly distributed across buckets). + * Thus min_spare_threads should at least maintain num_buckets children, + * and max_spare_threads allow num_buckets more children w/o triggering + * immediately (e.g. num_buckets idle threads margin, one per bucket). + */ + if (min_spare_threads < threads_per_child * (num_buckets - 1) + num_buckets) + min_spare_threads = threads_per_child * (num_buckets - 1) + num_buckets; + if (max_spare_threads < min_spare_threads + (threads_per_child + 1) * num_buckets) + max_spare_threads = min_spare_threads + (threads_per_child + 1) * num_buckets; + + /* If we're doing a graceful_restart then we're going to see a lot + * of children exiting immediately when we get into the main loop + * below (because we just sent them AP_SIG_GRACEFUL). This happens pretty + * rapidly... and for each one that exits we may start a new one, until + * there are at least min_spare_threads idle threads, counting across + * all children. But we may be permitted to start more children than + * that, so we'll just keep track of how many we're + * supposed to start up without the 1 second penalty between each fork. + */ + remaining_children_to_start = ap_daemons_to_start; + if (remaining_children_to_start > ap_daemons_limit) { + remaining_children_to_start = ap_daemons_limit; + } + if (!retained->mpm->was_graceful) { + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + } + else { + /* give the system some time to recover before kicking into + * exponential mode */ + retained->hold_off_on_exponential_spawning = 10; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00292) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00293) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00294) + "Accept mutex: %s (default: %s)", + (all_buckets[0].mutex) + ? apr_proc_mutex_name(all_buckets[0].mutex) + : "none", + apr_proc_mutex_defname()); + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + server_main_loop(remaining_children_to_start, num_buckets); + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + if (retained->mpm->shutdown_pending && retained->mpm->is_ungraceful) { + /* Time to shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_RESTART); + } + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + worker_note_child_killed); + + if (!child_fatal) { + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, + ap_server_conf, APLOGNO(00295) "caught SIGTERM, shutting down"); + } + return DONE; + } + + if (retained->mpm->shutdown_pending) { + /* Time to gracefully shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + int active_children; + int index; + apr_time_t cutoff = 0; + + /* Close our listeners, and then ask our children to do same */ + ap_close_listeners(); + + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_GRACEFUL); + } + ap_relieve_child_processes(worker_note_child_killed); + + if (!child_fatal) { + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00296) + "caught " AP_SIG_GRACEFUL_STOP_STRING + ", shutting down gracefully"); + } + + if (ap_graceful_shutdown_timeout) { + cutoff = apr_time_now() + + apr_time_from_sec(ap_graceful_shutdown_timeout); + } + + /* Don't really exit until each child has finished */ + retained->mpm->shutdown_pending = 0; + do { + /* Pause for a second */ + apr_sleep(apr_time_from_sec(1)); + + /* Relieve any children which have now exited */ + ap_relieve_child_processes(worker_note_child_killed); + + active_children = 0; + for (index = 0; index < ap_daemons_limit; ++index) { + if (ap_mpm_safe_kill(MPM_CHILD_PID(index), 0) == APR_SUCCESS) { + active_children = 1; + /* Having just one child is enough to stay around */ + break; + } + } + } while (!retained->mpm->shutdown_pending && active_children && + (!ap_graceful_shutdown_timeout || apr_time_now() < cutoff)); + + /* We might be here because we received SIGTERM, either + * way, try and make sure that all of our processes are + * really dead. + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_RESTART); + } + ap_reclaim_child_processes(1, worker_note_child_killed); + + return DONE; + } + + /* we've been told to restart */ + if (one_process) { + /* not worth thinking about */ + return DONE; + } + + /* advance to the next generation */ + /* XXX: we really need to make sure this new generation number isn't in + * use by any of the children. + */ + ++retained->mpm->my_generation; + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + + if (!retained->mpm->is_ungraceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00297) + AP_SIG_GRACEFUL_STRING " received. Doing graceful restart"); + /* wake up the children...time to die. But we'll have more soon */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_GRACEFUL); + } + + /* This is mostly for debugging... so that we know what is still + * gracefully dealing with existing request. + */ + + } + else { + /* Kill 'em all. Since the child acts the same on the parents SIGTERM + * and a SIGHUP, we may as well use the same signal, because some user + * pthreads are stealing signals from us left and right. + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_RESTART); + } + + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + worker_note_child_killed); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00298) + "SIGHUP received. Attempting to restart"); + } + + return OK; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int worker_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + int level_flags = 0; + int num_buckets = 0; + ap_listen_rec **listen_buckets; + apr_status_t rv; + char id[16]; + int i; + + pconf = p; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + level_flags |= APLOG_STARTUP; + } + + if ((num_listensocks = ap_setup_listeners(ap_server_conf)) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0, + (startup ? NULL : s), + "no listening sockets available, shutting down"); + return !OK; + } + + if (one_process) { + num_buckets = 1; + } + else if (retained->mpm->was_graceful) { + /* Preserve the number of buckets on graceful restarts. */ + num_buckets = retained->mpm->num_buckets; + } + if ((rv = ap_duplicate_listeners(pconf, ap_server_conf, + &listen_buckets, &num_buckets))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not duplicate listeners"); + return !OK; + } + + all_buckets = apr_pcalloc(pconf, num_buckets * sizeof(*all_buckets)); + for (i = 0; i < num_buckets; i++) { + if (!one_process && /* no POD in one_process mode */ + (rv = ap_mpm_podx_open(pconf, &all_buckets[i].pod))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not open pipe-of-death"); + return !OK; + } + /* Initialize cross-process accept lock (safe accept needed only) */ + if ((rv = SAFE_ACCEPT((apr_snprintf(id, sizeof id, "%i", i), + ap_proc_mutex_create(&all_buckets[i].mutex, + NULL, AP_ACCEPT_MUTEX_TYPE, + id, s, pconf, 0))))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not create accept mutex"); + return !OK; + } + all_buckets[i].listeners = listen_buckets[i]; + } + + if (retained->mpm->max_buckets < num_buckets) { + int new_max, *new_ptr; + new_max = retained->mpm->max_buckets * 2; + if (new_max < num_buckets) { + new_max = num_buckets; + } + new_ptr = (int *)apr_palloc(ap_pglobal, new_max * sizeof(int)); + memcpy(new_ptr, retained->idle_spawn_rate, + retained->mpm->num_buckets * sizeof(int)); + retained->idle_spawn_rate = new_ptr; + retained->mpm->max_buckets = new_max; + } + if (retained->mpm->num_buckets < num_buckets) { + int rate_max = 1; + /* If new buckets are added, set their idle spawn rate to + * the highest so far, so that they get filled as quickly + * as the existing ones. + */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (rate_max < retained->idle_spawn_rate[i]) { + rate_max = retained->idle_spawn_rate[i]; + } + } + for (/* up to date i */; i < num_buckets; i++) { + retained->idle_spawn_rate[i] = rate_max; + } + } + retained->mpm->num_buckets = num_buckets; + + return OK; +} + +static int worker_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + int no_detach, debug, foreground; + apr_status_t rv; + const char *userdata_key = "mpm_worker_module"; + + debug = ap_exists_config_define("DEBUG"); + + if (debug) { + foreground = one_process = 1; + no_detach = 0; + } + else { + one_process = ap_exists_config_define("ONE_PROCESS"); + no_detach = ap_exists_config_define("NO_DETACH"); + foreground = ap_exists_config_define("FOREGROUND"); + } + + ap_mutex_register(pconf, AP_ACCEPT_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0); + + retained = ap_retained_data_get(userdata_key); + if (!retained) { + retained = ap_retained_data_create(userdata_key, sizeof(*retained)); + retained->mpm = ap_unixd_mpm_get_retained_data(); + retained->max_daemons_limit = -1; + } + retained->mpm->mpm_state = AP_MPMQ_STARTING; + if (retained->mpm->baton != retained) { + retained->mpm->was_graceful = 0; + retained->mpm->baton = retained; + } + ++retained->mpm->module_loads; + + /* sigh, want this only the second time around */ + if (retained->mpm->module_loads == 2) { + if (!one_process && !foreground) { + /* before we detach, setup crash handlers to log to errorlog */ + ap_fatal_signal_setup(ap_server_conf, pconf); + rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND + : APR_PROC_DETACH_DAEMONIZE); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00299) + "apr_proc_detach failed"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + } + + parent_pid = ap_my_pid = getpid(); + + ap_listen_pre_config(); + ap_daemons_to_start = DEFAULT_START_DAEMON; + min_spare_threads = DEFAULT_MIN_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD; + max_spare_threads = DEFAULT_MAX_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD; + server_limit = DEFAULT_SERVER_LIMIT; + thread_limit = DEFAULT_THREAD_LIMIT; + ap_daemons_limit = server_limit; + threads_per_child = DEFAULT_THREADS_PER_CHILD; + max_workers = ap_daemons_limit * threads_per_child; + had_healthy_child = 0; + ap_extended_status = 0; + + return OK; +} + +static int worker_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + } + + if (server_limit > MAX_SERVER_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00300) + "WARNING: ServerLimit of %d exceeds compile-time " + "limit of %d servers, decreasing to %d.", + server_limit, MAX_SERVER_LIMIT, MAX_SERVER_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00301) + "ServerLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + server_limit, MAX_SERVER_LIMIT); + } + server_limit = MAX_SERVER_LIMIT; + } + else if (server_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00302) + "WARNING: ServerLimit of %d not allowed, " + "increasing to 1.", server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00303) + "ServerLimit of %d not allowed, increasing to 1", + server_limit); + } + server_limit = 1; + } + + /* you cannot change ServerLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_server_limit) { + retained->first_server_limit = server_limit; + } + else if (server_limit != retained->first_server_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00304) + "changing ServerLimit to %d from original value of %d " + "not allowed during restart", + server_limit, retained->first_server_limit); + server_limit = retained->first_server_limit; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00305) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of %d threads, decreasing to %d.", + thread_limit, MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00306) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00307) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00308) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* you cannot change ThreadLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_thread_limit) { + retained->first_thread_limit = thread_limit; + } + else if (thread_limit != retained->first_thread_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00309) + "changing ThreadLimit to %d from original value of %d " + "not allowed during restart", + thread_limit, retained->first_thread_limit); + thread_limit = retained->first_thread_limit; + } + + if (threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00310) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of %d threads, decreasing to %d. " + "To increase, please see the ThreadLimit directive.", + threads_per_child, thread_limit, thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00311) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + threads_per_child, thread_limit); + } + threads_per_child = thread_limit; + } + else if (threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00312) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00313) + "ThreadsPerChild of %d not allowed, increasing to 1", + threads_per_child); + } + threads_per_child = 1; + } + + if (max_workers < threads_per_child) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00314) + "WARNING: MaxRequestWorkers of %d is less than " + "ThreadsPerChild of %d, increasing to %d. " + "MaxRequestWorkers must be at least as large " + "as the number of threads in a single server.", + max_workers, threads_per_child, threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00315) + "MaxRequestWorkers of %d is less than ThreadsPerChild " + "of %d, increasing to match", + max_workers, threads_per_child); + } + max_workers = threads_per_child; + } + + ap_daemons_limit = max_workers / threads_per_child; + + if (max_workers % threads_per_child) { + int tmp_max_workers = ap_daemons_limit * threads_per_child; + + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00316) + "WARNING: MaxRequestWorkers of %d is not an integer " + "multiple of ThreadsPerChild of %d, decreasing to nearest " + "multiple %d, for a maximum of %d servers.", + max_workers, threads_per_child, tmp_max_workers, + ap_daemons_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00317) + "MaxRequestWorkers of %d is not an integer multiple of " + "ThreadsPerChild of %d, decreasing to nearest " + "multiple %d", max_workers, threads_per_child, + tmp_max_workers); + } + max_workers = tmp_max_workers; + } + + if (ap_daemons_limit > server_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00318) + "WARNING: MaxRequestWorkers of %d would require %d " + "servers and would exceed ServerLimit of %d, decreasing to %d. " + "To increase, please see the ServerLimit directive.", + max_workers, ap_daemons_limit, server_limit, + server_limit * threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00319) + "MaxRequestWorkers of %d would require %d servers and " + "exceed ServerLimit of %d, decreasing to %d", + max_workers, ap_daemons_limit, server_limit, + server_limit * threads_per_child); + } + ap_daemons_limit = server_limit; + } + + /* ap_daemons_to_start > ap_daemons_limit checked in worker_run() */ + if (ap_daemons_to_start < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00320) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00321) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00322) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1 to avoid almost certain server failure. " + "Please read the documentation.", min_spare_threads); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00323) + "MinSpareThreads of %d not allowed, increasing to 1", + min_spare_threads); + } + min_spare_threads = 1; + } + + /* max_spare_threads < min_spare_threads + threads_per_child + * checked in worker_run() + */ + + return OK; +} + +static void worker_hooks(apr_pool_t *p) +{ + /* Our open_logs hook function must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = {"core.c", NULL}; + one_process = 0; + + ap_hook_open_logs(worker_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + /* we need to set the MPM state before other pre-config hooks use MPM query + * to retrieve it, so register as REALLY_FIRST + */ + ap_hook_pre_config(worker_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_check_config(worker_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(worker_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(worker_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(worker_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_to_start = atoi(arg); + return NULL; +} + +static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + min_spare_threads = atoi(arg); + return NULL; +} + +static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + max_spare_threads = atoi(arg); + return NULL; +} + +static const char *set_max_workers (cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + if (!strcasecmp(cmd->cmd->name, "MaxClients")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00324) + "MaxClients is deprecated, use MaxRequestWorkers " + "instead."); + } + max_workers = atoi(arg); + return NULL; +} + +static const char *set_threads_per_child (cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + threads_per_child = atoi(arg); + return NULL; +} + +static const char *set_server_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + server_limit = atoi(arg); + return NULL; +} + +static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + thread_limit = atoi(arg); + return NULL; +} + +static const command_rec worker_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF, + "Number of child processes launched at server startup"), +AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF, + "Minimum number of idle threads, to handle request spikes"), +AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF, + "Maximum number of idle threads"), +AP_INIT_TAKE1("MaxRequestWorkers", set_max_workers, NULL, RSRC_CONF, + "Maximum number of threads alive at the same time"), +AP_INIT_TAKE1("MaxClients", set_max_workers, NULL, RSRC_CONF, + "Deprecated name of MaxRequestWorkers"), +AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF, + "Number of threads each child creates"), +AP_INIT_TAKE1("ServerLimit", set_server_limit, NULL, RSRC_CONF, + "Maximum number of child processes for this run of Apache"), +AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF, + "Maximum number of worker threads per child process for this run of Apache - Upper limit for ThreadsPerChild"), +AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, +{ NULL } +}; + +AP_DECLARE_MODULE(mpm_worker) = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + worker_cmds, /* command apr_table_t */ + worker_hooks /* register_hooks */ +}; + diff --git a/server/mpm_common.c b/server/mpm_common.c new file mode 100644 index 0000000..04f36d9 --- /dev/null +++ b/server/mpm_common.c @@ -0,0 +1,574 @@ +/* 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. + */ + +/* The purpose of this file is to store the code that MOST mpm's will need + * this does not mean a function only goes into this file if every MPM needs + * it. It means that if a function is needed by more than one MPM, and + * future maintenance would be served by making the code common, then the + * function belongs here. + * + * This is going in src/main because it is not platform specific, it is + * specific to multi-process servers, but NOT to Unix. Which is why it + * does not belong in src/os/unix + */ + +#include "apr.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_strings.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_getopt.h" +#include "apr_optional.h" +#include "apr_allocator.h" + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_main.h" +#include "mpm_common.h" +#include "mod_core.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "util_mutex.h" + +#include "scoreboard.h" + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +#define DEFAULT_HOOK_LINKS \ + APR_HOOK_LINK(monitor) \ + APR_HOOK_LINK(drop_privileges) \ + APR_HOOK_LINK(mpm) \ + APR_HOOK_LINK(mpm_query) \ + APR_HOOK_LINK(mpm_register_timed_callback) \ + APR_HOOK_LINK(mpm_get_name) \ + APR_HOOK_LINK(end_generation) \ + APR_HOOK_LINK(child_status) \ + APR_HOOK_LINK(suspend_connection) \ + APR_HOOK_LINK(resume_connection) + +#if AP_ENABLE_EXCEPTION_HOOK +APR_HOOK_STRUCT( + APR_HOOK_LINK(fatal_exception) + DEFAULT_HOOK_LINKS +) +AP_IMPLEMENT_HOOK_RUN_ALL(int, fatal_exception, + (ap_exception_info_t *ei), (ei), OK, DECLINED) +#else +APR_HOOK_STRUCT( + DEFAULT_HOOK_LINKS +) +#endif +AP_IMPLEMENT_HOOK_RUN_ALL(int, monitor, + (apr_pool_t *p, server_rec *s), (p, s), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int, drop_privileges, + (apr_pool_t * pchild, server_rec * s), + (pchild, s), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, mpm, + (apr_pool_t *pconf, apr_pool_t *plog, server_rec *s), + (pconf, plog, s), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, mpm_query, + (int query_code, int *result, apr_status_t *_rv), + (query_code, result, _rv), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, mpm_register_timed_callback, + (apr_time_t t, ap_mpm_callback_fn_t *cbfn, void *baton), + (t, cbfn, baton), APR_ENOTIMPL) +AP_IMPLEMENT_HOOK_VOID(end_generation, + (server_rec *s, ap_generation_t gen), + (s, gen)) +AP_IMPLEMENT_HOOK_VOID(child_status, + (server_rec *s, pid_t pid, ap_generation_t gen, int slot, mpm_child_status status), + (s,pid,gen,slot,status)) +AP_IMPLEMENT_HOOK_VOID(suspend_connection, + (conn_rec *c, request_rec *r), + (c, r)) +AP_IMPLEMENT_HOOK_VOID(resume_connection, + (conn_rec *c, request_rec *r), + (c, r)) + +/* hooks with no args are implemented last, after disabling APR hook probes */ +#if defined(APR_HOOK_PROBES_ENABLED) +#undef APR_HOOK_PROBES_ENABLED +#undef APR_HOOK_PROBE_ENTRY +#define APR_HOOK_PROBE_ENTRY(ud,ns,name,args) +#undef APR_HOOK_PROBE_RETURN +#define APR_HOOK_PROBE_RETURN(ud,ns,name,rv,args) +#undef APR_HOOK_PROBE_INVOKE +#define APR_HOOK_PROBE_INVOKE(ud,ns,name,src,args) +#undef APR_HOOK_PROBE_COMPLETE +#define APR_HOOK_PROBE_COMPLETE(ud,ns,name,src,rv,args) +#undef APR_HOOK_INT_DCL_UD +#define APR_HOOK_INT_DCL_UD +#endif +AP_IMPLEMENT_HOOK_RUN_FIRST(const char *, mpm_get_name, + (void), + (), NULL) + +typedef struct mpm_gen_info_t { + APR_RING_ENTRY(mpm_gen_info_t) link; + int gen; /* which gen? */ + int active; /* number of active processes */ + int done; /* gen finished? (whether or not active processes) */ +} mpm_gen_info_t; + +APR_RING_HEAD(mpm_gen_info_head_t, mpm_gen_info_t); +static struct mpm_gen_info_head_t *geninfo, *unused_geninfo; +static int gen_head_init; /* yuck */ + +/* variables representing config directives implemented here */ +AP_DECLARE_DATA const char *ap_pid_fname; +AP_DECLARE_DATA int ap_max_requests_per_child; +AP_DECLARE_DATA char ap_coredump_dir[MAX_STRING_LEN]; +AP_DECLARE_DATA int ap_coredumpdir_configured; +AP_DECLARE_DATA int ap_graceful_shutdown_timeout; +AP_DECLARE_DATA apr_uint32_t ap_max_mem_free; +AP_DECLARE_DATA apr_size_t ap_thread_stacksize; + +#define ALLOCATOR_MAX_FREE_DEFAULT (2048*1024) + +/* Set defaults for config directives implemented here. This is + * called from core's pre-config hook, so MPMs which need to override + * one of these should run their pre-config hook after that of core. + */ +void mpm_common_pre_config(apr_pool_t *pconf) +{ + ap_pid_fname = DEFAULT_PIDLOG; + ap_max_requests_per_child = 0; /* unlimited */ + apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir)); + ap_coredumpdir_configured = 0; + ap_graceful_shutdown_timeout = 0; /* unlimited */ + ap_max_mem_free = ALLOCATOR_MAX_FREE_DEFAULT; + ap_thread_stacksize = 0; /* use system default */ +} + +/* number of calls to wait_or_timeout between writable probes */ +#ifndef INTERVAL_OF_WRITABLE_PROBES +#define INTERVAL_OF_WRITABLE_PROBES 10 +#endif +static int wait_or_timeout_counter; + +AP_DECLARE(void) ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode, + apr_proc_t *ret, apr_pool_t *p, + server_rec *s) +{ + apr_status_t rv; + + ++wait_or_timeout_counter; + if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES) { + wait_or_timeout_counter = 0; + ap_run_monitor(p, s); + } + + rv = apr_proc_wait_all_procs(ret, exitcode, status, APR_NOWAIT, p); + ap_update_global_status(); + + if (APR_STATUS_IS_EINTR(rv)) { + ret->pid = -1; + return; + } + + if (APR_STATUS_IS_CHILD_DONE(rv)) { + return; + } + + apr_sleep(apr_time_from_sec(1)); + ret->pid = -1; +} + +#if defined(TCP_NODELAY) +void ap_sock_disable_nagle(apr_socket_t *s) +{ + /* The Nagle algorithm says that we should delay sending partial + * packets in hopes of getting more data. We don't want to do + * this; we are not telnet. There are bad interactions between + * persistent connections and Nagle's algorithm that have very severe + * performance penalties. (Failing to disable Nagle is not much of a + * problem with simple HTTP.) + * + * In spite of these problems, failure here is not a shooting offense. + */ + apr_status_t status = apr_socket_opt_set(s, APR_TCP_NODELAY, 1); + + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, status, ap_server_conf, APLOGNO(00542) + "apr_socket_opt_set: (TCP_NODELAY)"); + } +} +#endif + +#ifdef HAVE_GETPWNAM +AP_DECLARE(uid_t) ap_uname2id(const char *name) +{ + struct passwd *ent; + + if (name[0] == '#') + return (atoi(&name[1])); + + if (!(ent = getpwnam(name))) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00543) + "%s: bad user name %s", ap_server_argv0, name); + exit(1); + } + + return (ent->pw_uid); +} +#endif + +#ifdef HAVE_GETGRNAM +AP_DECLARE(gid_t) ap_gname2id(const char *name) +{ + struct group *ent; + + if (name[0] == '#') + return (atoi(&name[1])); + + if (!(ent = getgrnam(name))) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00544) + "%s: bad group name %s", ap_server_argv0, name); + exit(1); + } + + return (ent->gr_gid); +} +#endif + +#ifndef HAVE_INITGROUPS +int initgroups(const char *name, gid_t basegid) +{ +#if defined(_OSD_POSIX) || defined(OS2) || defined(WIN32) || defined(NETWARE) + return 0; +#else + gid_t groups[NGROUPS_MAX]; + struct group *g; + int index = 0; + + setgrent(); + + groups[index++] = basegid; + + while (index < NGROUPS_MAX && ((g = getgrent()) != NULL)) { + if (g->gr_gid != basegid) { + char **names; + + for (names = g->gr_mem; *names != NULL; ++names) { + if (!strcmp(*names, name)) + groups[index++] = g->gr_gid; + } + } + } + + endgrent(); + + return setgroups(index, groups); +#endif +} +#endif /* def HAVE_INITGROUPS */ + +/* standard mpm configuration handling */ + +const char *ap_mpm_set_pidfile(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + if (cmd->server->is_virtual) { + return "PidFile directive not allowed in <VirtualHost>"; + } + + ap_pid_fname = arg; + return NULL; +} + +void ap_mpm_dump_pidfile(apr_pool_t *p, apr_file_t *out) +{ + apr_file_printf(out, "PidFile: \"%s\"\n", + ap_server_root_relative(p, ap_pid_fname)); +} + +const char *ap_mpm_set_max_requests(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + if (!strcasecmp(cmd->cmd->name, "MaxRequestsPerChild")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00545) + "MaxRequestsPerChild is deprecated, use " + "MaxConnectionsPerChild instead."); + } + + ap_max_requests_per_child = atoi(arg); + + return NULL; +} + +const char *ap_mpm_set_coredumpdir(cmd_parms *cmd, void *dummy, + const char *arg) +{ + apr_finfo_t finfo; + const char *fname; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + fname = ap_server_root_relative(cmd->temp_pool, arg); + if (!fname) { + return apr_pstrcat(cmd->pool, "Invalid CoreDumpDirectory path ", + arg, NULL); + } + if (apr_stat(&finfo, fname, APR_FINFO_TYPE, cmd->pool) != APR_SUCCESS) { + return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname, + " does not exist", NULL); + } + if (finfo.filetype != APR_DIR) { + return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname, + " is not a directory", NULL); + } + apr_cpystrn(ap_coredump_dir, fname, sizeof(ap_coredump_dir)); + ap_coredumpdir_configured = 1; + return NULL; +} + +AP_DECLARE(const char *)ap_mpm_set_graceful_shutdown(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + ap_graceful_shutdown_timeout = atoi(arg); + return NULL; +} + +const char *ap_mpm_set_max_mem_free(cmd_parms *cmd, void *dummy, + const char *arg) +{ + long value; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + errno = 0; + value = strtol(arg, NULL, 10); + if (value < 0 || errno == ERANGE) + return apr_pstrcat(cmd->pool, "Invalid MaxMemFree value: ", + arg, NULL); + + ap_max_mem_free = (apr_uint32_t)value * 1024; + + return NULL; +} + +const char *ap_mpm_set_thread_stacksize(cmd_parms *cmd, void *dummy, + const char *arg) +{ + long value; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + errno = 0; + value = strtol(arg, NULL, 10); + if (value < 0 || errno == ERANGE) + return apr_pstrcat(cmd->pool, "Invalid ThreadStackSize value: ", + arg, NULL); + + ap_thread_stacksize = (apr_size_t)value; + + return NULL; +} + +AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) +{ + apr_status_t rv; + + if (ap_run_mpm_query(query_code, result, &rv) == DECLINED) { + rv = APR_EGENERAL; + } + + return rv; +} + +static void end_gen(mpm_gen_info_t *gi) +{ + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, + "end of generation %d", gi->gen); + ap_run_end_generation(ap_server_conf, gi->gen); + APR_RING_REMOVE(gi, link); + APR_RING_INSERT_HEAD(unused_geninfo, gi, mpm_gen_info_t, link); +} + +apr_status_t ap_mpm_end_gen_helper(void *unused) /* cleanup on pconf */ +{ + int gen = ap_config_generation - 1; /* differs from MPM generation */ + mpm_gen_info_t *cur; + + if (geninfo == NULL) { + /* initial pconf teardown, MPM hasn't run */ + return APR_SUCCESS; + } + + cur = APR_RING_FIRST(geninfo); + while (cur != APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link) && + cur->gen != gen) { + cur = APR_RING_NEXT(cur, link); + } + + if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { + /* last child of generation already exited */ + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, + "no record of generation %d", gen); + } + else { + cur->done = 1; + if (cur->active == 0) { + end_gen(cur); + } + } + + return APR_SUCCESS; +} + +/* core's child-status hook + * tracks number of remaining children per generation and + * runs the end-generation hook when the last child of + * a generation exits + */ +void ap_core_child_status(server_rec *s, pid_t pid, + ap_generation_t gen, int slot, + mpm_child_status status) +{ + mpm_gen_info_t *cur; + const char *status_msg = "unknown status"; + + if (!gen_head_init) { /* where to run this? */ + gen_head_init = 1; + geninfo = apr_pcalloc(s->process->pool, sizeof *geninfo); + unused_geninfo = apr_pcalloc(s->process->pool, sizeof *unused_geninfo); + APR_RING_INIT(geninfo, mpm_gen_info_t, link); + APR_RING_INIT(unused_geninfo, mpm_gen_info_t, link); + } + + cur = APR_RING_FIRST(geninfo); + while (cur != APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link) && + cur->gen != gen) { + cur = APR_RING_NEXT(cur, link); + } + + switch(status) { + case MPM_CHILD_STARTED: + status_msg = "started"; + if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { + /* first child for this generation */ + if (!APR_RING_EMPTY(unused_geninfo, mpm_gen_info_t, link)) { + cur = APR_RING_FIRST(unused_geninfo); + APR_RING_REMOVE(cur, link); + cur->active = cur->done = 0; + } + else { + cur = apr_pcalloc(s->process->pool, sizeof *cur); + } + cur->gen = gen; + APR_RING_ELEM_INIT(cur, link); + APR_RING_INSERT_HEAD(geninfo, cur, mpm_gen_info_t, link); + } + ap_random_parent_after_fork(); + ++cur->active; + break; + case MPM_CHILD_EXITED: + ap_update_global_status(); + status_msg = "exited"; + if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00546) + "no record of generation %d of exiting child %" APR_PID_T_FMT, + gen, pid); + } + else { + --cur->active; + if (!cur->active && cur->done) { /* no children, server has stopped/restarted */ + end_gen(cur); + } + } + break; + case MPM_CHILD_LOST_SLOT: + status_msg = "lost slot"; + /* we don't track by slot, so it doesn't matter */ + break; + } + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, s, + "mpm child %" APR_PID_T_FMT " (gen %d/slot %d) %s", + pid, gen, slot, status_msg); +} + +AP_DECLARE(apr_status_t) ap_mpm_register_timed_callback(apr_time_t t, ap_mpm_callback_fn_t *cbfn, void *baton) +{ + return ap_run_mpm_register_timed_callback(t, cbfn, baton); +} + +AP_DECLARE(const char *)ap_show_mpm(void) +{ + const char *name = ap_run_mpm_get_name(); + + if (!name) { + name = ""; + } + + return name; +} + +AP_DECLARE(const char *)ap_check_mpm(void) +{ + static const char *last_mpm_name = NULL; + + if (!_hooks.link_mpm || _hooks.link_mpm->nelts == 0) + return "No MPM loaded."; + else if (_hooks.link_mpm->nelts > 1) + return "More than one MPM loaded."; + + if (last_mpm_name) { + if (strcmp(last_mpm_name, ap_show_mpm())) { + return "The MPM cannot be changed during restart."; + } + } + else { + last_mpm_name = apr_pstrdup(ap_pglobal, ap_show_mpm()); + } + + return NULL; +} diff --git a/server/mpm_fdqueue.c b/server/mpm_fdqueue.c new file mode 100644 index 0000000..c812450 --- /dev/null +++ b/server/mpm_fdqueue.c @@ -0,0 +1,530 @@ +/* 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 "mpm_fdqueue.h" + +#if APR_HAS_THREADS + +#include <apr_atomic.h> + +static const apr_uint32_t zero_pt = APR_UINT32_MAX/2; + +struct recycled_pool +{ + apr_pool_t *pool; + struct recycled_pool *next; +}; + +struct fd_queue_info_t +{ + apr_uint32_t volatile idlers; /** + * >= zero_pt: number of idle worker threads + * < zero_pt: number of threads blocked, + * waiting for an idle worker + */ + apr_thread_mutex_t *idlers_mutex; + apr_thread_cond_t *wait_for_idler; + int terminated; + int max_idlers; + int max_recycled_pools; + apr_uint32_t recycled_pools_count; + struct recycled_pool *volatile recycled_pools; +}; + +struct fd_queue_elem_t +{ + apr_socket_t *sd; + void *sd_baton; + apr_pool_t *p; +}; + +static apr_status_t queue_info_cleanup(void *data_) +{ + fd_queue_info_t *qi = data_; + apr_thread_cond_destroy(qi->wait_for_idler); + apr_thread_mutex_destroy(qi->idlers_mutex); + + /* Clean up any pools in the recycled list */ + for (;;) { + struct recycled_pool *first_pool = qi->recycled_pools; + if (first_pool == NULL) { + break; + } + if (apr_atomic_casptr((void *)&qi->recycled_pools, first_pool->next, + first_pool) == first_pool) { + apr_pool_destroy(first_pool->pool); + } + } + + return APR_SUCCESS; +} + +apr_status_t ap_queue_info_create(fd_queue_info_t **queue_info, + apr_pool_t *pool, int max_idlers, + int max_recycled_pools) +{ + apr_status_t rv; + fd_queue_info_t *qi; + + qi = apr_pcalloc(pool, sizeof(*qi)); + + rv = apr_thread_mutex_create(&qi->idlers_mutex, APR_THREAD_MUTEX_DEFAULT, + pool); + if (rv != APR_SUCCESS) { + return rv; + } + rv = apr_thread_cond_create(&qi->wait_for_idler, pool); + if (rv != APR_SUCCESS) { + return rv; + } + qi->recycled_pools = NULL; + qi->max_recycled_pools = max_recycled_pools; + qi->max_idlers = max_idlers; + qi->idlers = zero_pt; + apr_pool_cleanup_register(pool, qi, queue_info_cleanup, + apr_pool_cleanup_null); + + *queue_info = qi; + + return APR_SUCCESS; +} + +apr_status_t ap_queue_info_set_idle(fd_queue_info_t *queue_info, + apr_pool_t *pool_to_recycle) +{ + apr_status_t rv; + + ap_queue_info_push_pool(queue_info, pool_to_recycle); + + /* If other threads are waiting on a worker, wake one up */ + if (apr_atomic_inc32(&queue_info->idlers) < zero_pt) { + rv = apr_thread_mutex_lock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + AP_DEBUG_ASSERT(0); + return rv; + } + rv = apr_thread_cond_signal(queue_info->wait_for_idler); + if (rv != APR_SUCCESS) { + apr_thread_mutex_unlock(queue_info->idlers_mutex); + return rv; + } + rv = apr_thread_mutex_unlock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + return rv; + } + } + + return APR_SUCCESS; +} + +apr_status_t ap_queue_info_try_get_idler(fd_queue_info_t *queue_info) +{ + /* Don't block if there isn't any idle worker. */ + for (;;) { + apr_uint32_t idlers = queue_info->idlers; + if (idlers <= zero_pt) { + return APR_EAGAIN; + } + if (apr_atomic_cas32(&queue_info->idlers, idlers - 1, + idlers) == idlers) { + return APR_SUCCESS; + } + } +} + +apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t *queue_info, + int *had_to_block) +{ + apr_status_t rv; + + /* Block if there isn't any idle worker. + * apr_atomic_add32(x, -1) does the same as dec32(x), except + * that it returns the previous value (unlike dec32's bool). + */ + if (apr_atomic_add32(&queue_info->idlers, -1) <= zero_pt) { + rv = apr_thread_mutex_lock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + AP_DEBUG_ASSERT(0); + apr_atomic_inc32(&(queue_info->idlers)); /* back out dec */ + return rv; + } + /* Re-check the idle worker count to guard against a + * race condition. Now that we're in the mutex-protected + * region, one of two things may have happened: + * - If the idle worker count is still negative, the + * workers are all still busy, so it's safe to + * block on a condition variable. + * - If the idle worker count is non-negative, then a + * worker has become idle since the first check + * of queue_info->idlers above. It's possible + * that the worker has also signaled the condition + * variable--and if so, the listener missed it + * because it wasn't yet blocked on the condition + * variable. But if the idle worker count is + * now non-negative, it's safe for this function to + * return immediately. + * + * A "negative value" (relative to zero_pt) in + * queue_info->idlers tells how many + * threads are waiting on an idle worker. + */ + if (queue_info->idlers < zero_pt) { + if (had_to_block) { + *had_to_block = 1; + } + rv = apr_thread_cond_wait(queue_info->wait_for_idler, + queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + AP_DEBUG_ASSERT(0); + apr_thread_mutex_unlock(queue_info->idlers_mutex); + return rv; + } + } + rv = apr_thread_mutex_unlock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + return rv; + } + } + + if (queue_info->terminated) { + return APR_EOF; + } + else { + return APR_SUCCESS; + } +} + +apr_uint32_t ap_queue_info_num_idlers(fd_queue_info_t *queue_info) +{ + apr_uint32_t val; + val = apr_atomic_read32(&queue_info->idlers); + return (val > zero_pt) ? val - zero_pt : 0; +} + +void ap_queue_info_push_pool(fd_queue_info_t *queue_info, + apr_pool_t *pool_to_recycle) +{ + struct recycled_pool *new_recycle; + /* If we have been given a pool to recycle, atomically link + * it into the queue_info's list of recycled pools + */ + if (!pool_to_recycle) + return; + + if (queue_info->max_recycled_pools >= 0) { + apr_uint32_t n = apr_atomic_read32(&queue_info->recycled_pools_count); + if (n >= queue_info->max_recycled_pools) { + apr_pool_destroy(pool_to_recycle); + return; + } + apr_atomic_inc32(&queue_info->recycled_pools_count); + } + + apr_pool_clear(pool_to_recycle); + new_recycle = apr_palloc(pool_to_recycle, sizeof *new_recycle); + new_recycle->pool = pool_to_recycle; + for (;;) { + /* + * Save queue_info->recycled_pool in local variable next because + * new_recycle->next can be changed after apr_atomic_casptr + * function call. For gory details see PR 44402. + */ + struct recycled_pool *next = queue_info->recycled_pools; + new_recycle->next = next; + if (apr_atomic_casptr((void *)&queue_info->recycled_pools, + new_recycle, next) == next) + break; + } +} + +void ap_queue_info_pop_pool(fd_queue_info_t *queue_info, + apr_pool_t **recycled_pool) +{ + /* Atomically pop a pool from the recycled list */ + + /* This function is safe only as long as it is single threaded because + * it reaches into the queue and accesses "next" which can change. + * We are OK today because it is only called from the listener thread. + * cas-based pushes do not have the same limitation - any number can + * happen concurrently with a single cas-based pop. + */ + + *recycled_pool = NULL; + + + /* Atomically pop a pool from the recycled list */ + for (;;) { + struct recycled_pool *first_pool = queue_info->recycled_pools; + if (first_pool == NULL) { + break; + } + if (apr_atomic_casptr((void *)&queue_info->recycled_pools, + first_pool->next, first_pool) == first_pool) { + *recycled_pool = first_pool->pool; + if (queue_info->max_recycled_pools >= 0) + apr_atomic_dec32(&queue_info->recycled_pools_count); + break; + } + } +} + +void ap_queue_info_free_idle_pools(fd_queue_info_t *queue_info) +{ + apr_pool_t *p; + + queue_info->max_recycled_pools = 0; + for (;;) { + ap_queue_info_pop_pool(queue_info, &p); + if (p == NULL) + break; + apr_pool_destroy(p); + } + apr_atomic_set32(&queue_info->recycled_pools_count, 0); +} + + +apr_status_t ap_queue_info_term(fd_queue_info_t *queue_info) +{ + apr_status_t rv; + + rv = apr_thread_mutex_lock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + return rv; + } + + queue_info->terminated = 1; + apr_thread_cond_broadcast(queue_info->wait_for_idler); + + return apr_thread_mutex_unlock(queue_info->idlers_mutex); +} + +/** + * Detects when the fd_queue_t is full. This utility function is expected + * to be called from within critical sections, and is not threadsafe. + */ +#define ap_queue_full(queue) ((queue)->nelts == (queue)->bounds) + +/** + * Detects when the fd_queue_t is empty. This utility function is expected + * to be called from within critical sections, and is not threadsafe. + */ +#define ap_queue_empty(queue) ((queue)->nelts == 0 && \ + APR_RING_EMPTY(&queue->timers, \ + timer_event_t, link)) + +/** + * Callback routine that is called to destroy this + * fd_queue_t when its pool is destroyed. + */ +static apr_status_t ap_queue_destroy(void *data) +{ + fd_queue_t *queue = data; + + /* Ignore errors here, we can't do anything about them anyway. + * XXX: We should at least try to signal an error here, it is + * indicative of a programmer error. -aaron */ + apr_thread_cond_destroy(queue->not_empty); + apr_thread_mutex_destroy(queue->one_big_mutex); + + return APR_SUCCESS; +} + +/** + * Initialize the fd_queue_t. + */ +apr_status_t ap_queue_create(fd_queue_t **pqueue, int capacity, apr_pool_t *p) +{ + apr_status_t rv; + fd_queue_t *queue; + + queue = apr_pcalloc(p, sizeof *queue); + + if ((rv = apr_thread_mutex_create(&queue->one_big_mutex, + APR_THREAD_MUTEX_DEFAULT, + p)) != APR_SUCCESS) { + return rv; + } + if ((rv = apr_thread_cond_create(&queue->not_empty, p)) != APR_SUCCESS) { + return rv; + } + + APR_RING_INIT(&queue->timers, timer_event_t, link); + + queue->data = apr_pcalloc(p, capacity * sizeof(fd_queue_elem_t)); + queue->bounds = capacity; + + apr_pool_cleanup_register(p, queue, ap_queue_destroy, + apr_pool_cleanup_null); + *pqueue = queue; + + return APR_SUCCESS; +} + +/** + * Push a new socket onto the queue. + * + * precondition: ap_queue_info_wait_for_idler has already been called + * to reserve an idle worker thread + */ +apr_status_t ap_queue_push_socket(fd_queue_t *queue, + apr_socket_t *sd, void *sd_baton, + apr_pool_t *p) +{ + fd_queue_elem_t *elem; + apr_status_t rv; + + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { + return rv; + } + + AP_DEBUG_ASSERT(!queue->terminated); + AP_DEBUG_ASSERT(!ap_queue_full(queue)); + + elem = &queue->data[queue->in++]; + if (queue->in >= queue->bounds) + queue->in -= queue->bounds; + elem->sd = sd; + elem->sd_baton = sd_baton; + elem->p = p; + queue->nelts++; + + apr_thread_cond_signal(queue->not_empty); + + return apr_thread_mutex_unlock(queue->one_big_mutex); +} + +apr_status_t ap_queue_push_timer(fd_queue_t *queue, timer_event_t *te) +{ + apr_status_t rv; + + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { + return rv; + } + + AP_DEBUG_ASSERT(!queue->terminated); + + APR_RING_INSERT_TAIL(&queue->timers, te, timer_event_t, link); + + apr_thread_cond_signal(queue->not_empty); + + return apr_thread_mutex_unlock(queue->one_big_mutex); +} + +/** + * Retrieves the next available socket from the queue. If there are no + * sockets available, it will block until one becomes available. + * Once retrieved, the socket is placed into the address specified by + * 'sd'. + */ +apr_status_t ap_queue_pop_something(fd_queue_t *queue, + apr_socket_t **sd, void **sd_baton, + apr_pool_t **p, timer_event_t **te_out) +{ + fd_queue_elem_t *elem; + timer_event_t *te; + apr_status_t rv; + + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { + return rv; + } + + /* Keep waiting until we wake up and find that the queue is not empty. */ + if (ap_queue_empty(queue)) { + if (!queue->terminated) { + apr_thread_cond_wait(queue->not_empty, queue->one_big_mutex); + } + /* If we wake up and it's still empty, then we were interrupted */ + if (ap_queue_empty(queue)) { + rv = apr_thread_mutex_unlock(queue->one_big_mutex); + if (rv != APR_SUCCESS) { + return rv; + } + if (queue->terminated) { + return APR_EOF; /* no more elements ever again */ + } + else { + return APR_EINTR; + } + } + } + + te = NULL; + if (te_out) { + if (!APR_RING_EMPTY(&queue->timers, timer_event_t, link)) { + te = APR_RING_FIRST(&queue->timers); + APR_RING_REMOVE(te, link); + } + *te_out = te; + } + if (!te) { + elem = &queue->data[queue->out++]; + if (queue->out >= queue->bounds) + queue->out -= queue->bounds; + queue->nelts--; + + *sd = elem->sd; + if (sd_baton) { + *sd_baton = elem->sd_baton; + } + *p = elem->p; +#ifdef AP_DEBUG + elem->sd = NULL; + elem->p = NULL; +#endif /* AP_DEBUG */ + } + + return apr_thread_mutex_unlock(queue->one_big_mutex); +} + +static apr_status_t queue_interrupt(fd_queue_t *queue, int all, int term) +{ + apr_status_t rv; + + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { + return rv; + } + + /* we must hold one_big_mutex when setting this... otherwise, + * we could end up setting it and waking everybody up just after a + * would-be popper checks it but right before they block + */ + if (term) { + queue->terminated = 1; + } + if (all) + apr_thread_cond_broadcast(queue->not_empty); + else + apr_thread_cond_signal(queue->not_empty); + + return apr_thread_mutex_unlock(queue->one_big_mutex); +} + +apr_status_t ap_queue_interrupt_all(fd_queue_t *queue) +{ + return queue_interrupt(queue, 1, 0); +} + +apr_status_t ap_queue_interrupt_one(fd_queue_t *queue) +{ + return queue_interrupt(queue, 0, 0); +} + +apr_status_t ap_queue_term(fd_queue_t *queue) +{ + return queue_interrupt(queue, 1, 1); +} + +#endif /* APR_HAS_THREADS */ diff --git a/server/mpm_fdqueue.h b/server/mpm_fdqueue.h new file mode 100644 index 0000000..9aeedde --- /dev/null +++ b/server/mpm_fdqueue.h @@ -0,0 +1,110 @@ +/* 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. + */ + +/** + * @file server/mpm_fdqueue.h + * @brief fd queue declarations + * + * @addtogroup APACHE_MPM_EVENT + * @{ + */ + +#ifndef MPM_FDQUEUE_H +#define MPM_FDQUEUE_H + +#include <apr.h> + +/* This code is not AP_DECLARE()ed/exported, and used by MPMs event/worker + * only (for now), not worth thinking about w/o threads either... + */ +#if APR_HAS_THREADS + +#include "ap_mpm.h" + +#include <apr_ring.h> +#include <apr_pools.h> +#include <apr_thread_mutex.h> +#include <apr_thread_cond.h> +#include <apr_network_io.h> + +struct fd_queue_info_t; /* opaque */ +struct fd_queue_elem_t; /* opaque */ +typedef struct fd_queue_info_t fd_queue_info_t; +typedef struct fd_queue_elem_t fd_queue_elem_t; + +AP_DECLARE(apr_status_t) ap_queue_info_create(fd_queue_info_t **queue_info, + apr_pool_t *pool, int max_idlers, + int max_recycled_pools); +AP_DECLARE(apr_status_t) ap_queue_info_set_idle(fd_queue_info_t *queue_info, + apr_pool_t *pool_to_recycle); +AP_DECLARE(apr_status_t) ap_queue_info_try_get_idler(fd_queue_info_t *queue_info); +AP_DECLARE(apr_status_t) ap_queue_info_wait_for_idler(fd_queue_info_t *queue_info, + int *had_to_block); +AP_DECLARE(apr_uint32_t) ap_queue_info_num_idlers(fd_queue_info_t *queue_info); +AP_DECLARE(apr_status_t) ap_queue_info_term(fd_queue_info_t *queue_info); + +AP_DECLARE(void) ap_queue_info_pop_pool(fd_queue_info_t *queue_info, + apr_pool_t **recycled_pool); +AP_DECLARE(void) ap_queue_info_push_pool(fd_queue_info_t *queue_info, + apr_pool_t *pool_to_recycle); +AP_DECLARE(void) ap_queue_info_free_idle_pools(fd_queue_info_t *queue_info); + +struct timer_event_t +{ + APR_RING_ENTRY(timer_event_t) link; + apr_time_t when; + ap_mpm_callback_fn_t *cbfunc; + void *baton; + int canceled; + apr_array_header_t *remove; +}; +typedef struct timer_event_t timer_event_t; + +struct fd_queue_t +{ + APR_RING_HEAD(timers_t, timer_event_t) timers; + fd_queue_elem_t *data; + unsigned int nelts; + unsigned int bounds; + unsigned int in; + unsigned int out; + apr_thread_mutex_t *one_big_mutex; + apr_thread_cond_t *not_empty; + int terminated; +}; +typedef struct fd_queue_t fd_queue_t; + +AP_DECLARE(apr_status_t) ap_queue_create(fd_queue_t **pqueue, + int capacity, apr_pool_t *p); +AP_DECLARE(apr_status_t) ap_queue_push_socket(fd_queue_t *queue, + apr_socket_t *sd, void *sd_baton, + apr_pool_t *p); +AP_DECLARE(apr_status_t) ap_queue_push_timer(fd_queue_t *queue, + timer_event_t *te); +AP_DECLARE(apr_status_t) ap_queue_pop_something(fd_queue_t *queue, + apr_socket_t **sd, void **sd_baton, + apr_pool_t **p, timer_event_t **te); +#define ap_queue_pop_socket(q_, s_, p_) \ + ap_queue_pop_something((q_), (s_), NULL, (p_), NULL) + +AP_DECLARE(apr_status_t) ap_queue_interrupt_all(fd_queue_t *queue); +AP_DECLARE(apr_status_t) ap_queue_interrupt_one(fd_queue_t *queue); +AP_DECLARE(apr_status_t) ap_queue_term(fd_queue_t *queue); + +#endif /* APR_HAS_THREADS */ + +#endif /* MPM_FDQUEUE_H */ +/** @} */ diff --git a/server/mpm_unix.c b/server/mpm_unix.c new file mode 100644 index 0000000..1800f5d --- /dev/null +++ b/server/mpm_unix.c @@ -0,0 +1,1107 @@ +/* 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. + */ + +/* The purpose of this file is to store the code that MOST mpm's will need + * this does not mean a function only goes into this file if every MPM needs + * it. It means that if a function is needed by more than one MPM, and + * future maintenance would be served by making the code common, then the + * function belongs here. + * + * This is going in src/main because it is not platform specific, it is + * specific to multi-process servers, but NOT to Unix. Which is why it + * does not belong in src/os/unix + */ + +#ifndef WIN32 + +#include "apr.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_strings.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_getopt.h" +#include "apr_optional.h" +#include "apr_allocator.h" + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_main.h" +#include "mpm_common.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "scoreboard.h" +#include "util_mutex.h" + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +typedef enum { + DO_NOTHING, + SEND_SIGTERM, + SEND_SIGTERM_NOLOG, + SEND_SIGKILL, + GIVEUP +} action_t; + +typedef struct extra_process_t { + struct extra_process_t *next; + pid_t pid; + ap_generation_t gen; +} extra_process_t; + +static extra_process_t *extras; + +AP_DECLARE(void) ap_register_extra_mpm_process(pid_t pid, ap_generation_t gen) +{ + extra_process_t *p = (extra_process_t *)ap_malloc(sizeof(extra_process_t)); + + p->next = extras; + p->pid = pid; + p->gen = gen; + extras = p; +} + +AP_DECLARE(int) ap_unregister_extra_mpm_process(pid_t pid, ap_generation_t *old_gen) +{ + extra_process_t *cur = extras; + extra_process_t *prev = NULL; + + while (cur && cur->pid != pid) { + prev = cur; + cur = cur->next; + } + + if (cur) { + if (prev) { + prev->next = cur->next; + } + else { + extras = cur->next; + } + *old_gen = cur->gen; + free(cur); + return 1; /* found */ + } + else { + /* we don't know about any such process */ + return 0; + } +} + +static int reclaim_one_pid(pid_t pid, action_t action) +{ + apr_proc_t proc; + apr_status_t waitret; + apr_exit_why_e why; + int status; + + /* Ensure pid sanity. */ + if (pid < 1) { + return 1; + } + + proc.pid = pid; + waitret = apr_proc_wait(&proc, &status, &why, APR_NOWAIT); + if (waitret != APR_CHILD_NOTDONE) { + if (waitret == APR_CHILD_DONE) + ap_process_child_status(&proc, why, status); + return 1; + } + + switch(action) { + case DO_NOTHING: + break; + + case SEND_SIGTERM: + /* ok, now it's being annoying */ + ap_log_error(APLOG_MARK, APLOG_WARNING, + 0, ap_server_conf, APLOGNO(00045) + "child process %" APR_PID_T_FMT + " still did not exit, " + "sending a SIGTERM", + pid); + /* FALLTHROUGH */ + case SEND_SIGTERM_NOLOG: + kill(pid, SIGTERM); + break; + + case SEND_SIGKILL: + ap_log_error(APLOG_MARK, APLOG_ERR, + 0, ap_server_conf, APLOGNO(00046) + "child process %" APR_PID_T_FMT + " still did not exit, " + "sending a SIGKILL", + pid); + kill(pid, SIGKILL); + break; + + case GIVEUP: + /* gave it our best shot, but alas... If this really + * is a child we are trying to kill and it really hasn't + * exited, we will likely fail to bind to the port + * after the restart. + */ + ap_log_error(APLOG_MARK, APLOG_ERR, + 0, ap_server_conf, APLOGNO(00047) + "could not make child process %" APR_PID_T_FMT + " exit, " + "attempting to continue anyway", + pid); + break; + } + + return 0; +} + +AP_DECLARE(void) ap_reclaim_child_processes(int terminate, + ap_reclaim_callback_fn_t *mpm_callback) +{ + apr_time_t waittime = 1024 * 16; + int i; + extra_process_t *cur_extra; + int not_dead_yet; + int max_daemons; + apr_time_t starttime = apr_time_now(); + /* this table of actions and elapsed times tells what action is taken + * at which elapsed time from starting the reclaim + */ + struct { + action_t action; + apr_time_t action_time; + } action_table[] = { + {DO_NOTHING, 0}, /* dummy entry for iterations where we reap + * children but take no action against + * stragglers + */ + {SEND_SIGTERM_NOLOG, 0}, /* skipped if terminate == 0 */ + {SEND_SIGTERM, apr_time_from_sec(3)}, + {SEND_SIGTERM, apr_time_from_sec(5)}, + {SEND_SIGTERM, apr_time_from_sec(7)}, + {SEND_SIGKILL, apr_time_from_sec(9)}, + {GIVEUP, apr_time_from_sec(10)} + }; + int cur_action; /* index of action we decided to take this + * iteration + */ + int next_action = terminate ? 1 : 2; /* index of first real action */ + + ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons); + + do { + if (action_table[next_action].action_time > 0) { + apr_sleep(waittime); + /* don't let waittime get longer than 1 second; otherwise, we don't + * react quickly to the last child exiting, and taking action can + * be delayed + */ + waittime = waittime * 4; + if (waittime > apr_time_from_sec(1)) { + waittime = apr_time_from_sec(1); + } + } + + /* see what action to take, if any */ + if (action_table[next_action].action_time <= apr_time_now() - starttime) { + cur_action = next_action; + ++next_action; + } + else { + cur_action = 0; /* nothing to do */ + } + + /* now see who is done */ + not_dead_yet = 0; + for (i = 0; i < max_daemons; ++i) { + process_score *ps = ap_get_scoreboard_process(i); + pid_t pid = ps->pid; + + if (pid == 0) { + continue; /* not every scoreboard entry is in use */ + } + + if (reclaim_one_pid(pid, action_table[cur_action].action)) { + mpm_callback(i, 0, 0); + } + else { + ++not_dead_yet; + } + } + + cur_extra = extras; + while (cur_extra) { + ap_generation_t old_gen; + extra_process_t *next = cur_extra->next; + + if (reclaim_one_pid(cur_extra->pid, action_table[cur_action].action)) { + if (ap_unregister_extra_mpm_process(cur_extra->pid, &old_gen) == 1) { + mpm_callback(-1, cur_extra->pid, old_gen); + } + else { + AP_DEBUG_ASSERT(1 == 0); + } + } + else { + ++not_dead_yet; + } + cur_extra = next; + } +#if APR_HAS_OTHER_CHILD + apr_proc_other_child_refresh_all(APR_OC_REASON_RESTART); +#endif + + } while (not_dead_yet > 0 && + action_table[cur_action].action != GIVEUP); +} + +AP_DECLARE(void) ap_relieve_child_processes(ap_reclaim_callback_fn_t *mpm_callback) +{ + int i; + extra_process_t *cur_extra; + int max_daemons; + + ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons); + + /* now see who is done */ + for (i = 0; i < max_daemons; ++i) { + process_score *ps = ap_get_scoreboard_process(i); + pid_t pid = ps->pid; + + if (pid == 0) { + continue; /* not every scoreboard entry is in use */ + } + + if (reclaim_one_pid(pid, DO_NOTHING)) { + mpm_callback(i, 0, 0); + } + } + + cur_extra = extras; + while (cur_extra) { + ap_generation_t old_gen; + extra_process_t *next = cur_extra->next; + + if (reclaim_one_pid(cur_extra->pid, DO_NOTHING)) { + if (ap_unregister_extra_mpm_process(cur_extra->pid, &old_gen) == 1) { + mpm_callback(-1, cur_extra->pid, old_gen); + } + else { + AP_DEBUG_ASSERT(1 == 0); + } + } + cur_extra = next; + } +} + +/* Before sending the signal to the pid this function verifies that + * the pid is a member of the current process group; either using + * apr_proc_wait(), where waitpid() guarantees to fail for non-child + * processes; or by using getpgid() directly, if available. */ +AP_DECLARE(apr_status_t) ap_mpm_safe_kill(pid_t pid, int sig) +{ +#ifndef HAVE_GETPGID + apr_proc_t proc; + apr_status_t rv; + apr_exit_why_e why; + int status; + + /* Ensure pid sanity */ + if (pid < 1) { + return APR_EINVAL; + } + + proc.pid = pid; + rv = apr_proc_wait(&proc, &status, &why, APR_NOWAIT); + if (rv == APR_CHILD_DONE) { + /* The child already died - log the termination status if + * necessary: */ + ap_process_child_status(&proc, why, status); + return APR_EINVAL; + } + else if (rv != APR_CHILD_NOTDONE) { + /* The child is already dead and reaped, or was a bogus pid - + * log this either way. */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00048) + "cannot send signal %d to pid %ld (non-child or " + "already dead)", sig, (long)pid); + return APR_EINVAL; + } +#else + pid_t pg; + + /* Ensure pid sanity. */ + if (pid < 1) { + return APR_EINVAL; + } + + pg = getpgid(pid); + if (pg == -1) { + /* Process already dead... */ + return errno; + } + + if (pg != getpgrp()) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, ap_server_conf, APLOGNO(00049) + "refusing to send signal %d to pid %ld outside " + "process group", sig, (long)pid); + return APR_EINVAL; + } +#endif + + return kill(pid, sig) ? errno : APR_SUCCESS; +} + + +AP_DECLARE(int) ap_process_child_status(apr_proc_t *pid, apr_exit_why_e why, + int status) +{ + int signum = status; + const char *sigdesc; + + /* Child died... if it died due to a fatal error, + * we should simply bail out. The caller needs to + * check for bad rc from us and exit, running any + * appropriate cleanups. + * + * If the child died due to a resource shortage, + * the parent should limit the rate of forking + */ + if (APR_PROC_CHECK_EXIT(why)) { + if (status == APEXIT_CHILDSICK) { + return status; + } + + if (status == APEXIT_CHILDFATAL) { + ap_log_error(APLOG_MARK, APLOG_ALERT, + 0, ap_server_conf, APLOGNO(00050) + "Child %" APR_PID_T_FMT + " returned a Fatal error... Apache is exiting!", + pid->pid); + return APEXIT_CHILDFATAL; + } + + return 0; + } + + if (APR_PROC_CHECK_SIGNALED(why)) { + sigdesc = apr_signal_description_get(signum); + + switch (signum) { + case SIGTERM: + case SIGHUP: + case AP_SIG_GRACEFUL: + case SIGKILL: + break; + + default: + if (APR_PROC_CHECK_CORE_DUMP(why)) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, + 0, ap_server_conf, APLOGNO(00051) + "child pid %ld exit signal %s (%d), " + "possible coredump in %s", + (long)pid->pid, sigdesc, signum, + ap_coredump_dir); + } + else { + ap_log_error(APLOG_MARK, APLOG_NOTICE, + 0, ap_server_conf, APLOGNO(00052) + "child pid %ld exit signal %s (%d)", + (long)pid->pid, sigdesc, signum); + } + } + } + return 0; +} + +AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t *p, ap_pod_t **pod) +{ + apr_status_t rv; + + *pod = apr_palloc(p, sizeof(**pod)); + rv = apr_file_pipe_create_ex(&((*pod)->pod_in), &((*pod)->pod_out), + APR_WRITE_BLOCK, p); + if (rv != APR_SUCCESS) { + return rv; + } + + apr_file_pipe_timeout_set((*pod)->pod_in, 0); + (*pod)->p = p; + + /* close these before exec. */ + apr_file_inherit_unset((*pod)->pod_in); + apr_file_inherit_unset((*pod)->pod_out); + + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_mpm_pod_check(ap_pod_t *pod) +{ + char c; + apr_size_t len = 1; + apr_status_t rv; + + rv = apr_file_read(pod->pod_in, &c, &len); + + if ((rv == APR_SUCCESS) && (len == 1)) { + return APR_SUCCESS; + } + + if (rv != APR_SUCCESS) { + return rv; + } + + return AP_NORESTART; +} + +AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t *pod) +{ + apr_status_t rv; + + rv = apr_file_close(pod->pod_out); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = apr_file_close(pod->pod_in); + if (rv != APR_SUCCESS) { + return rv; + } + + return APR_SUCCESS; +} + +static apr_status_t pod_signal_internal(ap_pod_t *pod) +{ + apr_status_t rv; + char char_of_death = '!'; + apr_size_t one = 1; + + rv = apr_file_write(pod->pod_out, &char_of_death, &one); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00053) + "write pipe_of_death"); + } + + return rv; +} + +AP_DECLARE(apr_status_t) ap_mpm_podx_open(apr_pool_t *p, ap_pod_t **pod) +{ + apr_status_t rv; + + *pod = apr_palloc(p, sizeof(**pod)); + rv = apr_file_pipe_create(&((*pod)->pod_in), &((*pod)->pod_out), p); + if (rv != APR_SUCCESS) { + return rv; + } + /* + apr_file_pipe_timeout_set((*pod)->pod_in, 0); + */ + (*pod)->p = p; + + /* close these before exec. */ + apr_file_inherit_unset((*pod)->pod_in); + apr_file_inherit_unset((*pod)->pod_out); + + return APR_SUCCESS; +} + +AP_DECLARE(int) ap_mpm_podx_check(ap_pod_t *pod) +{ + char c; + apr_os_file_t fd; + int rc; + + /* we need to surface EINTR so we'll have to grab the + * native file descriptor and do the OS read() ourselves + */ + apr_os_file_get(&fd, pod->pod_in); + rc = read(fd, &c, 1); + if (rc == 1) { + switch (c) { + case AP_MPM_PODX_RESTART_CHAR: + return AP_MPM_PODX_RESTART; + case AP_MPM_PODX_GRACEFUL_CHAR: + return AP_MPM_PODX_GRACEFUL; + } + } + return AP_MPM_PODX_NORESTART; +} + +AP_DECLARE(apr_status_t) ap_mpm_podx_close(ap_pod_t *pod) +{ + apr_status_t rv; + + rv = apr_file_close(pod->pod_out); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = apr_file_close(pod->pod_in); + if (rv != APR_SUCCESS) { + return rv; + } + return rv; +} + +static apr_status_t podx_signal_internal(ap_pod_t *pod, + ap_podx_restart_t graceful) +{ + apr_status_t rv; + apr_size_t one = 1; + char char_of_death = ' '; + switch (graceful) { + case AP_MPM_PODX_RESTART: + char_of_death = AP_MPM_PODX_RESTART_CHAR; + break; + case AP_MPM_PODX_GRACEFUL: + char_of_death = AP_MPM_PODX_GRACEFUL_CHAR; + break; + case AP_MPM_PODX_NORESTART: + break; + } + + rv = apr_file_write(pod->pod_out, &char_of_death, &one); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(02404) + "write pipe_of_death"); + } + return rv; +} + +AP_DECLARE(apr_status_t) ap_mpm_podx_signal(ap_pod_t * pod, + ap_podx_restart_t graceful) +{ + return podx_signal_internal(pod, graceful); +} + +AP_DECLARE(void) ap_mpm_podx_killpg(ap_pod_t * pod, int num, + ap_podx_restart_t graceful) +{ + int i; + apr_status_t rv = APR_SUCCESS; + + for (i = 0; i < num && rv == APR_SUCCESS; i++) { + rv = podx_signal_internal(pod, graceful); + } +} + +/* This function connects to the server and sends enough data to + * ensure the child wakes up and processes a new connection. This + * permits the MPM to skip the poll when there is only one listening + * socket, because it provides a alternate way to unblock an accept() + * when the pod is used. */ +static apr_status_t dummy_connection(ap_pod_t *pod) +{ + const char *data; + apr_status_t rv; + apr_socket_t *sock; + apr_pool_t *p; + apr_size_t len; + ap_listen_rec *lp; + + /* create a temporary pool for the socket. pconf stays around too long */ + rv = apr_pool_create(&p, pod->p); + if (rv != APR_SUCCESS) { + return rv; + } + + /* If possible, find a listener which is configured for + * plain-HTTP, not SSL; using an SSL port would either be + * expensive to do correctly (performing a complete SSL handshake) + * or cause log spam by doing incorrectly (simply sending EOF). */ + lp = ap_listeners; + while (lp && lp->protocol && strcasecmp(lp->protocol, "http") != 0) { + lp = lp->next; + } + if (!lp) { + lp = ap_listeners; + } + + rv = apr_socket_create(&sock, lp->bind_addr->family, SOCK_STREAM, 0, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00054) + "get socket to connect to listener"); + apr_pool_destroy(p); + return rv; + } + + /* on some platforms (e.g., FreeBSD), the kernel won't accept many + * queued connections before it starts blocking local connects... + * we need to keep from blocking too long and instead return an error, + * because the MPM won't want to hold up a graceful restart for a + * long time + */ + rv = apr_socket_timeout_set(sock, apr_time_from_sec(3)); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00055) + "set timeout on socket to connect to listener"); + apr_socket_close(sock); + apr_pool_destroy(p); + return rv; + } + + rv = apr_socket_connect(sock, lp->bind_addr); + if (rv != APR_SUCCESS) { + int log_level = APLOG_WARNING; + + if (APR_STATUS_IS_TIMEUP(rv)) { + /* probably some server processes bailed out already and there + * is nobody around to call accept and clear out the kernel + * connection queue; usually this is not worth logging + */ + log_level = APLOG_DEBUG; + } + + ap_log_error(APLOG_MARK, log_level, rv, ap_server_conf, APLOGNO(00056) + "connect to listener on %pI", lp->bind_addr); + apr_pool_destroy(p); + return rv; + } + + if (lp->protocol && strcasecmp(lp->protocol, "https") == 0) { + /* Send a TLS 1.0 close_notify alert. This is perhaps the + * "least wrong" way to open and cleanly terminate an SSL + * connection. It should "work" without noisy error logs if + * the server actually expects SSLv3/TLSv1. With + * SSLv23_server_method() OpenSSL's SSL_accept() fails + * ungracefully on receipt of this message, since it requires + * an 11-byte ClientHello message and this is too short. */ + static const unsigned char tls10_close_notify[7] = { + '\x15', /* TLSPlainText.type = Alert (21) */ + '\x03', '\x01', /* TLSPlainText.version = {3, 1} */ + '\x00', '\x02', /* TLSPlainText.length = 2 */ + '\x01', /* Alert.level = warning (1) */ + '\x00' /* Alert.description = close_notify (0) */ + }; + data = (const char *)tls10_close_notify; + len = sizeof(tls10_close_notify); + } + else /* ... XXX other request types here? */ { + /* Create an HTTP request string. We include a User-Agent so + * that adminstrators can track down the cause of the + * odd-looking requests in their logs. A complete request is + * used since kernel-level filtering may require that much + * data before returning from accept(). */ + data = apr_pstrcat(p, "OPTIONS * HTTP/1.0\r\nUser-Agent: ", + ap_get_server_description(), + " (internal dummy connection)\r\n\r\n", NULL); + len = strlen(data); + } + + apr_socket_send(sock, data, &len); + apr_socket_close(sock); + apr_pool_destroy(p); + + return rv; +} + +AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t *pod) +{ + apr_status_t rv; + + rv = pod_signal_internal(pod); + if (rv != APR_SUCCESS) { + return rv; + } + + return dummy_connection(pod); +} + +void ap_mpm_pod_killpg(ap_pod_t *pod, int num) +{ + int i; + apr_status_t rv = APR_SUCCESS; + + /* we don't write anything to the pod here... we assume + * that the would-be reader of the pod has another way to + * see that it is time to die once we wake it up + * + * writing lots of things to the pod at once is very + * problematic... we can fill the kernel pipe buffer and + * be blocked until somebody consumes some bytes or + * we hit a timeout... if we hit a timeout we can't just + * keep trying because maybe we'll never successfully + * write again... but then maybe we'll leave would-be + * readers stranded (a number of them could be tied up for + * a while serving time-consuming requests) + */ + /* Recall: we only worry about IDLE child processes here */ + for (i = 0; i < num && rv == APR_SUCCESS; i++) { + if (ap_scoreboard_image->servers[i][0].status != SERVER_READY || + ap_scoreboard_image->servers[i][0].pid == 0) { + continue; + } + rv = dummy_connection(pod); + } +} + +static const char *dash_k_arg = NULL; +static const char *dash_k_arg_noarg = "noarg"; + +static int send_signal(pid_t pid, int sig) +{ + if (kill(pid, sig) < 0) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, errno, NULL, APLOGNO(00057) + "sending signal to server"); + return 1; + } + return 0; +} + +int ap_signal_server(int *exit_status, apr_pool_t *pconf) +{ + apr_status_t rv; + pid_t otherpid; + int running = 0; + const char *status; + + *exit_status = 0; + + rv = ap_read_pid(pconf, ap_pid_fname, &otherpid); + if (rv != APR_SUCCESS) { + if (!APR_STATUS_IS_ENOENT(rv)) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, rv, NULL, APLOGNO(00058) + "Error retrieving pid file %s", ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00059) + "Remove it before continuing if it is corrupted."); + *exit_status = 1; + return 1; + } + status = "httpd (no pid file) not running"; + } + else { + /* With containerization, httpd may get the same PID at each startup, + * handle it as if it were not running (it obviously can't). + */ + if (otherpid != getpid() && kill(otherpid, 0) == 0) { + running = 1; + status = apr_psprintf(pconf, + "httpd (pid %" APR_PID_T_FMT ") already " + "running", otherpid); + } + else { + status = apr_psprintf(pconf, + "httpd (pid %" APR_PID_T_FMT "?) not running", + otherpid); + } + } + + if (!strcmp(dash_k_arg, "start") || dash_k_arg == dash_k_arg_noarg) { + if (running) { + printf("%s\n", status); + return 1; + } + } + + if (!strcmp(dash_k_arg, "stop")) { + if (!running) { + printf("%s\n", status); + } + else { + send_signal(otherpid, SIGTERM); + } + return 1; + } + + if (!strcmp(dash_k_arg, "restart")) { + if (!running) { + printf("httpd not running, trying to start\n"); + } + else { + *exit_status = send_signal(otherpid, SIGHUP); + return 1; + } + } + + if (!strcmp(dash_k_arg, "graceful")) { + if (!running) { + printf("httpd not running, trying to start\n"); + } + else { + *exit_status = send_signal(otherpid, AP_SIG_GRACEFUL); + return 1; + } + } + + if (!strcmp(dash_k_arg, "graceful-stop")) { + if (!running) { + printf("%s\n", status); + } + else { + *exit_status = send_signal(otherpid, AP_SIG_GRACEFUL_STOP); + } + return 1; + } + + return 0; +} + +void ap_mpm_rewrite_args(process_rec *process) +{ + apr_array_header_t *mpm_new_argv; + apr_status_t rv; + apr_getopt_t *opt; + char optbuf[3]; + const char *optarg; + + mpm_new_argv = apr_array_make(process->pool, process->argc, + sizeof(const char **)); + *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; + apr_getopt_init(&opt, process->pool, process->argc, process->argv); + opt->errfn = NULL; + optbuf[0] = '-'; + /* option char returned by apr_getopt() will be stored in optbuf[1] */ + optbuf[2] = '\0'; + while ((rv = apr_getopt(opt, "k:" AP_SERVER_BASEARGS, + optbuf + 1, &optarg)) == APR_SUCCESS) { + switch(optbuf[1]) { + case 'k': + if (!dash_k_arg) { + if (!strcmp(optarg, "start") || !strcmp(optarg, "stop") || + !strcmp(optarg, "restart") || !strcmp(optarg, "graceful") || + !strcmp(optarg, "graceful-stop")) { + dash_k_arg = optarg; + break; + } + } + default: + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, optbuf); + if (optarg) { + *(const char **)apr_array_push(mpm_new_argv) = optarg; + } + } + } + + /* back up to capture the bad argument */ + if (rv == APR_BADCH || rv == APR_BADARG) { + opt->ind--; + } + + while (opt->ind < opt->argc) { + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, opt->argv[opt->ind++]); + } + + process->argc = mpm_new_argv->nelts; + process->argv = (const char * const *)mpm_new_argv->elts; + + if (NULL == dash_k_arg) { + dash_k_arg = dash_k_arg_noarg; + } + + APR_REGISTER_OPTIONAL_FN(ap_signal_server); +} + +static pid_t parent_pid, my_pid; +static apr_pool_t *pconf; + +#if AP_ENABLE_EXCEPTION_HOOK + +static int exception_hook_enabled; + +const char *ap_mpm_set_exception_hook(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + if (cmd->server->is_virtual) { + return "EnableExceptionHook directive not allowed in <VirtualHost>"; + } + + if (strcasecmp(arg, "on") == 0) { + exception_hook_enabled = 1; + } + else if (strcasecmp(arg, "off") == 0) { + exception_hook_enabled = 0; + } + else { + return "parameter must be 'on' or 'off'"; + } + + return NULL; +} + +static void run_fatal_exception_hook(int sig) +{ + ap_exception_info_t ei = {0}; + + if (exception_hook_enabled && + geteuid() != 0 && + my_pid != parent_pid) { + ei.sig = sig; + ei.pid = my_pid; + ap_run_fatal_exception(&ei); + } +} +#endif /* AP_ENABLE_EXCEPTION_HOOK */ + +/* handle all varieties of core dumping signals */ +static void sig_coredump(int sig) +{ + apr_filepath_set(ap_coredump_dir, pconf); + apr_signal(sig, SIG_DFL); +#if AP_ENABLE_EXCEPTION_HOOK + run_fatal_exception_hook(sig); +#endif + /* linuxthreads issue calling getpid() here: + * This comparison won't match if the crashing thread is + * some module's thread that runs in the parent process. + * The fallout, which is limited to linuxthreads: + * The special log message won't be written when such a + * thread in the parent causes the parent to crash. + */ + if (getpid() == parent_pid) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, + 0, ap_server_conf, APLOGNO(00060) + "seg fault or similar nasty error detected " + "in the parent process"); + /* XXX we can probably add some rudimentary cleanup code here, + * like getting rid of the pid file. If any additional bad stuff + * happens, we are protected from recursive errors taking down the + * system since this function is no longer the signal handler GLA + */ + } + kill(getpid(), sig); + /* At this point we've got sig blocked, because we're still inside + * the signal handler. When we leave the signal handler it will + * be unblocked, and we'll take the signal... and coredump or whatever + * is appropriate for this particular Unix. In addition the parent + * will see the real signal we received -- whereas if we called + * abort() here, the parent would only see SIGABRT. + */ +} + +AP_DECLARE(apr_status_t) ap_fatal_signal_child_setup(server_rec *s) +{ + my_pid = getpid(); + return APR_SUCCESS; +} + +/* We can't call sig_coredump (ap_log_error) once pconf is destroyed, so + * avoid double faults by restoring each default signal handler on cleanup. + */ +static apr_status_t fatal_signal_cleanup(void *unused) +{ + (void)unused; + + apr_signal(SIGSEGV, SIG_DFL); +#ifdef SIGBUS + apr_signal(SIGBUS, SIG_DFL); +#endif /* SIGBUS */ +#ifdef SIGABORT + apr_signal(SIGABORT, SIG_DFL); +#endif /* SIGABORT */ +#ifdef SIGABRT + apr_signal(SIGABRT, SIG_DFL); +#endif /* SIGABRT */ +#ifdef SIGILL + apr_signal(SIGILL, SIG_DFL); +#endif /* SIGILL */ +#ifdef SIGFPE + apr_signal(SIGFPE, SIG_DFL); +#endif /* SIGFPE */ + + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_fatal_signal_setup(server_rec *s, + apr_pool_t *in_pconf) +{ +#ifndef NO_USE_SIGACTION + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sigemptyset(&sa.sa_mask); + +#if defined(SA_ONESHOT) + sa.sa_flags = SA_ONESHOT; +#elif defined(SA_RESETHAND) + sa.sa_flags = SA_RESETHAND; +#endif + + sa.sa_handler = sig_coredump; + if (sigaction(SIGSEGV, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00061) "sigaction(SIGSEGV)"); +#ifdef SIGBUS + if (sigaction(SIGBUS, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00062) "sigaction(SIGBUS)"); +#endif +#ifdef SIGABORT + if (sigaction(SIGABORT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00063) "sigaction(SIGABORT)"); +#endif +#ifdef SIGABRT + if (sigaction(SIGABRT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00064) "sigaction(SIGABRT)"); +#endif +#ifdef SIGILL + if (sigaction(SIGILL, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00065) "sigaction(SIGILL)"); +#endif +#ifdef SIGFPE + if (sigaction(SIGFPE, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00066) "sigaction(SIGFPE)"); +#endif + +#else /* NO_USE_SIGACTION */ + + apr_signal(SIGSEGV, sig_coredump); +#ifdef SIGBUS + apr_signal(SIGBUS, sig_coredump); +#endif /* SIGBUS */ +#ifdef SIGABORT + apr_signal(SIGABORT, sig_coredump); +#endif /* SIGABORT */ +#ifdef SIGABRT + apr_signal(SIGABRT, sig_coredump); +#endif /* SIGABRT */ +#ifdef SIGILL + apr_signal(SIGILL, sig_coredump); +#endif /* SIGILL */ +#ifdef SIGFPE + apr_signal(SIGFPE, sig_coredump); +#endif /* SIGFPE */ + +#endif /* NO_USE_SIGACTION */ + + pconf = in_pconf; + parent_pid = my_pid = getpid(); + apr_pool_cleanup_register(pconf, NULL, fatal_signal_cleanup, + fatal_signal_cleanup); + + return APR_SUCCESS; +} + +#endif /* WIN32 */ diff --git a/server/protocol.c b/server/protocol.c new file mode 100644 index 0000000..8d90055 --- /dev/null +++ b/server/protocol.c @@ -0,0 +1,2456 @@ +/* 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. + */ + +/* + * protocol.c --- routines which directly communicate with the client. + * + * Code originally by Rob McCool; much redone by Robert S. Thau + * and the Apache Software Foundation. + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_buckets.h" +#include "apr_lib.h" +#include "apr_signal.h" +#include "apr_strmatch.h" + +#define APR_WANT_STDIO /* for sscanf */ +#define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC +#include "apr_want.h" + +#include "util_filter.h" +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_main.h" +#include "http_request.h" +#include "http_vhost.h" +#include "http_log.h" /* For errors detected in basic auth common + * support code... */ +#include "mod_core.h" +#include "util_charset.h" +#include "util_ebcdic.h" +#include "scoreboard.h" + +#if APR_HAVE_STDARG_H +#include <stdarg.h> +#endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +APR_HOOK_STRUCT( + APR_HOOK_LINK(pre_read_request) + APR_HOOK_LINK(post_read_request) + APR_HOOK_LINK(log_transaction) + APR_HOOK_LINK(http_scheme) + APR_HOOK_LINK(default_port) + APR_HOOK_LINK(note_auth_failure) + APR_HOOK_LINK(protocol_propose) + APR_HOOK_LINK(protocol_switch) + APR_HOOK_LINK(protocol_get) +) + +AP_DECLARE_DATA ap_filter_rec_t *ap_old_write_func = NULL; + + +/* Patterns to match in ap_make_content_type() */ +static const char *needcset[] = { + "text/plain", + "text/html", + NULL +}; +static const apr_strmatch_pattern **needcset_patterns; +static const apr_strmatch_pattern *charset_pattern; + +AP_DECLARE(void) ap_setup_make_content_type(apr_pool_t *pool) +{ + int i; + for (i = 0; needcset[i]; i++) { + continue; + } + needcset_patterns = (const apr_strmatch_pattern **) + apr_palloc(pool, (i + 1) * sizeof(apr_strmatch_pattern *)); + for (i = 0; needcset[i]; i++) { + needcset_patterns[i] = apr_strmatch_precompile(pool, needcset[i], 0); + } + needcset_patterns[i] = NULL; + charset_pattern = apr_strmatch_precompile(pool, "charset=", 0); +} + +/* + * Builds the content-type that should be sent to the client from the + * content-type specified. The following rules are followed: + * - if type is NULL or "", return NULL (do not set content-type). + * - if charset adding is disabled, stop processing and return type. + * - then, if there are no parameters on type, add the default charset + * - return type + */ +AP_DECLARE(const char *)ap_make_content_type(request_rec *r, const char *type) +{ + const apr_strmatch_pattern **pcset; + core_dir_config *conf = + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + core_request_config *request_conf; + apr_size_t type_len; + + if (!type || *type == '\0') { + return NULL; + } + + if (conf->add_default_charset != ADD_DEFAULT_CHARSET_ON) { + return type; + } + + request_conf = ap_get_core_module_config(r->request_config); + if (request_conf->suppress_charset) { + return type; + } + + type_len = strlen(type); + + if (apr_strmatch(charset_pattern, type, type_len) != NULL) { + /* already has parameter, do nothing */ + /* XXX we don't check the validity */ + ; + } + else { + /* see if it makes sense to add the charset. At present, + * we only add it if the Content-type is one of needcset[] + */ + for (pcset = needcset_patterns; *pcset ; pcset++) { + if (apr_strmatch(*pcset, type, type_len) != NULL) { + struct iovec concat[3]; + concat[0].iov_base = (void *)type; + concat[0].iov_len = type_len; + concat[1].iov_base = (void *)"; charset="; + concat[1].iov_len = sizeof("; charset=") - 1; + concat[2].iov_base = (void *)(conf->add_default_charset_name); + concat[2].iov_len = strlen(conf->add_default_charset_name); + type = apr_pstrcatv(r->pool, concat, 3, NULL); + break; + } + } + } + + return type; +} + +AP_DECLARE(void) ap_set_content_length(request_rec *r, apr_off_t clength) +{ + r->clength = clength; + apr_table_setn(r->headers_out, "Content-Length", + apr_off_t_toa(r->pool, clength)); +} + +/* + * Return the latest rational time from a request/mtime (modification time) + * pair. We return the mtime unless it's in the future, in which case we + * return the current time. We use the request time as a reference in order + * to limit the number of calls to time(). We don't check for futurosity + * unless the mtime is at least as new as the reference. + */ +AP_DECLARE(apr_time_t) ap_rationalize_mtime(request_rec *r, apr_time_t mtime) +{ + apr_time_t now; + + /* For all static responses, it's almost certain that the file was + * last modified before the beginning of the request. So there's + * no reason to call time(NULL) again. But if the response has been + * created on demand, then it might be newer than the time the request + * started. In this event we really have to call time(NULL) again + * so that we can give the clients the most accurate Last-Modified. If we + * were given a time in the future, we return the current time - the + * Last-Modified can't be in the future. + */ + now = (mtime < r->request_time) ? r->request_time : apr_time_now(); + return (mtime > now) ? now : mtime; +} + +/* Get a line of protocol input, including any continuation lines + * caused by MIME folding (or broken clients) if fold != 0, and place it + * in the buffer s, of size n bytes, without the ending newline. + * + * Pulls from r->proto_input_filters instead of r->input_filters for + * stricter protocol adherence and better input filter behavior during + * chunked trailer processing (for http). + * + * If s is NULL, ap_rgetline_core will allocate necessary memory from r->pool. + * + * Returns APR_SUCCESS if there are no problems and sets *read to be + * the full length of s. + * + * APR_ENOSPC is returned if there is not enough buffer space. + * Other errors may be returned on other errors. + * + * The [CR]LF are *not* returned in the buffer. Therefore, a *read of 0 + * indicates that an empty line was read. + * + * Notes: Because the buffer uses 1 char for NUL, the most we can return is + * (n - 1) actual characters. + * + * If no LF is detected on the last line due to a dropped connection + * or a full buffer, that's considered an error. + */ +AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + apr_size_t *read, request_rec *r, + int flags, apr_bucket_brigade *bb) +{ + apr_status_t rv; + apr_bucket *e; + apr_size_t bytes_handled = 0, current_alloc = 0; + char *pos, *last_char = *s; + int do_alloc = (*s == NULL), saw_eos = 0; + int fold = flags & AP_GETLINE_FOLD; + int crlf = flags & AP_GETLINE_CRLF; + int nospc_eol = flags & AP_GETLINE_NOSPC_EOL; + int saw_eol = 0, saw_nospc = 0; + + if (!n) { + /* Needs room for NUL byte at least */ + *read = 0; + return APR_BADARG; + } + + /* + * Initialize last_char as otherwise a random value will be compared + * against APR_ASCII_LF at the end of the loop if bb only contains + * zero-length buckets. + */ + if (last_char) + *last_char = '\0'; + + do { + apr_brigade_cleanup(bb); + rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_GETLINE, + APR_BLOCK_READ, 0); + if (rv != APR_SUCCESS) { + goto cleanup; + } + + /* Something horribly wrong happened. Someone didn't block! + * (this also happens at the end of each keepalive connection) + */ + if (APR_BRIGADE_EMPTY(bb)) { + rv = APR_EGENERAL; + goto cleanup; + } + + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = APR_BUCKET_NEXT(e)) + { + const char *str; + apr_size_t len; + + /* If we see an EOS, don't bother doing anything more. */ + if (APR_BUCKET_IS_EOS(e)) { + saw_eos = 1; + break; + } + + rv = apr_bucket_read(e, &str, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + goto cleanup; + } + + if (len == 0) { + /* no use attempting a zero-byte alloc (hurts when + * using --with-efence --enable-pool-debug) or + * doing any of the other logic either + */ + continue; + } + + /* Would this overrun our buffer? If so, we'll die. */ + if (n < bytes_handled + len) { + /* Before we die, let's fill the buffer up to its limit (i.e. + * fall through with the remaining length, if any), setting + * saw_eol on LF to stop the outer loop appropriately; we may + * come back here once the buffer is filled (no LF seen), and + * either be done at that time or continue to wait for LF here + * if nospc_eol is set. + * + * But there is also a corner cases which we want to address, + * namely if the buffer is overrun by the final LF only (i.e. + * the CR fits in); this is not really an overrun since we'll + * strip the CR finally (and use it for NUL byte), but anyway + * we have to handle the case so that it's not returned to the + * caller as part of the truncated line (it's not!). This is + * easier to consider that LF is out of counting and thus fall + * through with no error (saw_eol is set to 2 so that we later + * ignore LF handling already done here), while folding and + * nospc_eol logics continue to work (or fail) appropriately. + */ + saw_eol = (str[len - 1] == APR_ASCII_LF); + if (/* First time around */ + saw_eol && !saw_nospc + /* Single LF completing the buffered CR, */ + && ((len == 1 && ((*s)[bytes_handled - 1] == APR_ASCII_CR)) + /* or trailing CRLF overuns by LF only */ + || (len > 1 && str[len - 2] == APR_ASCII_CR + && n - bytes_handled + 1 == len))) { + /* In both cases *last_char is (to be) the CR stripped by + * later 'bytes_handled = last_char - *s'. + */ + saw_eol = 2; + } + else { + /* In any other case we'd lose data. */ + rv = APR_ENOSPC; + saw_nospc = 1; + } + len = n - bytes_handled; + if (!len) { + if (saw_eol) { + break; + } + if (nospc_eol) { + continue; + } + goto cleanup; + } + } + + /* Do we have to handle the allocation ourselves? */ + if (do_alloc) { + /* We'll assume the common case where one bucket is enough. */ + if (!*s) { + current_alloc = len; + *s = apr_palloc(r->pool, current_alloc + 1); + } + else if (bytes_handled + len > current_alloc) { + /* Increase the buffer size */ + apr_size_t new_size = current_alloc * 2; + char *new_buffer; + + if (bytes_handled + len > new_size) { + new_size = (bytes_handled + len) * 2; + } + + new_buffer = apr_palloc(r->pool, new_size + 1); + + /* Copy what we already had. */ + memcpy(new_buffer, *s, bytes_handled); + current_alloc = new_size; + *s = new_buffer; + } + } + + /* Just copy the rest of the data to the end of the old buffer. */ + pos = *s + bytes_handled; + memcpy(pos, str, len); + last_char = pos + len - 1; + + /* We've now processed that new data - update accordingly. */ + bytes_handled += len; + } + + /* If we got a full line of input, stop reading */ + if (last_char && (*last_char == APR_ASCII_LF)) { + saw_eol = 1; + } + } while (!saw_eol); + + if (rv != APR_SUCCESS) { + /* End of line after APR_ENOSPC above */ + goto cleanup; + } + + /* Now terminate the string at the end of the line; + * if the last-but-one character is a CR, terminate there. + * LF is handled above (not accounted) when saw_eol == 2, + * the last char is CR to terminate at still. + */ + if (saw_eol < 2) { + if (last_char > *s && last_char[-1] == APR_ASCII_CR) { + last_char--; + } + else if (crlf) { + rv = APR_EINVAL; + goto cleanup; + } + } + bytes_handled = last_char - *s; + + /* If we're folding, we have more work to do. + * + * Note that if an EOS was seen, we know we can't have another line. + */ + if (fold && bytes_handled && !saw_eos) { + for (;;) { + const char *str; + apr_size_t len; + char c; + + /* Clear the temp brigade for this filter read. */ + apr_brigade_cleanup(bb); + + /* We only care about the first byte. */ + rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_SPECULATIVE, + APR_BLOCK_READ, 1); + if (rv != APR_SUCCESS) { + goto cleanup; + } + + if (APR_BRIGADE_EMPTY(bb)) { + break; + } + + e = APR_BRIGADE_FIRST(bb); + + /* If we see an EOS, don't bother doing anything more. */ + if (APR_BUCKET_IS_EOS(e)) { + break; + } + + rv = apr_bucket_read(e, &str, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + apr_brigade_cleanup(bb); + goto cleanup; + } + + /* Found one, so call ourselves again to get the next line. + * + * FIXME: If the folding line is completely blank, should we + * stop folding? Does that require also looking at the next + * char? + */ + /* When we call destroy, the buckets are deleted, so save that + * one character we need. This simplifies our execution paths + * at the cost of one character read. + */ + c = *str; + if (c == APR_ASCII_BLANK || c == APR_ASCII_TAB) { + /* Do we have enough space? We may be full now. */ + if (bytes_handled >= n) { + rv = APR_ENOSPC; + goto cleanup; + } + else { + apr_size_t next_size, next_len; + char *tmp; + + /* If we're doing the allocations for them, we have to + * give ourselves a NULL and copy it on return. + */ + if (do_alloc) { + tmp = NULL; + } + else { + tmp = last_char; + } + + next_size = n - bytes_handled; + + rv = ap_rgetline_core(&tmp, next_size, &next_len, r, + flags & ~AP_GETLINE_FOLD, bb); + if (rv != APR_SUCCESS) { + goto cleanup; + } + + if (do_alloc && next_len > 0) { + char *new_buffer; + apr_size_t new_size = bytes_handled + next_len + 1; + + /* we need to alloc an extra byte for a null */ + new_buffer = apr_palloc(r->pool, new_size); + + /* Copy what we already had. */ + memcpy(new_buffer, *s, bytes_handled); + + /* copy the new line, including the trailing null */ + memcpy(new_buffer + bytes_handled, tmp, next_len); + *s = new_buffer; + } + + last_char += next_len; + bytes_handled += next_len; + } + } + else { /* next character is not tab or space */ + break; + } + } + } + +cleanup: + if (bytes_handled >= n) { + bytes_handled = n - 1; + } + + *read = bytes_handled; + if (*s) { + /* ensure the string is NUL terminated */ + (*s)[*read] = '\0'; + + /* PR#43039: We shouldn't accept NULL bytes within the line */ + bytes_handled = strlen(*s); + if (bytes_handled < *read) { + *read = bytes_handled; + if (rv == APR_SUCCESS) { + rv = APR_EINVAL; + } + } + } + return rv; +} + +#if APR_CHARSET_EBCDIC +AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n, + apr_size_t *read, request_rec *r, + int fold, apr_bucket_brigade *bb) +{ + /* on ASCII boxes, ap_rgetline is a macro which simply invokes + * ap_rgetline_core with the same parms + * + * on EBCDIC boxes, each complete http protocol input line needs to be + * translated into the code page used by the compiler. Since + * ap_rgetline_core uses recursion, we do the translation in a wrapper + * function to ensure that each input character gets translated only once. + */ + apr_status_t rv; + + rv = ap_rgetline_core(s, n, read, r, fold, bb); + if (rv == APR_SUCCESS || APR_STATUS_IS_ENOSPC(rv)) { + ap_xlate_proto_from_ascii(*s, *read); + } + return rv; +} +#endif + +AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags) +{ + char *tmp_s = s; + apr_status_t rv; + apr_size_t len; + apr_bucket_brigade *tmp_bb; + + if (n < 1) { + /* Can't work since we always NUL terminate */ + return -1; + } + + tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + rv = ap_rgetline(&tmp_s, n, &len, r, flags, tmp_bb); + apr_brigade_destroy(tmp_bb); + + /* Map the out-of-space condition to the old API. */ + if (rv == APR_ENOSPC) { + return n; + } + + /* Anything else is just bad. */ + if (rv != APR_SUCCESS) { + return -1; + } + + return (int)len; +} + +/* parse_uri: break apart the uri + * Side Effects: + * - sets r->args to rest after '?' (or NULL if no '?') + * - sets r->uri to request uri (without r->args part) + * - sets r->hostname (if not set already) from request (scheme://host:port) + */ +AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri) +{ + int status = HTTP_OK; + + r->unparsed_uri = apr_pstrdup(r->pool, uri); + + /* http://issues.apache.org/bugzilla/show_bug.cgi?id=31875 + * http://issues.apache.org/bugzilla/show_bug.cgi?id=28450 + * + * This is not in fact a URI, it's a path. That matters in the + * case of a leading double-slash. We need to resolve the issue + * by normalizing that out before treating it as a URI. + */ + while ((uri[0] == '/') && (uri[1] == '/')) { + ++uri ; + } + if (r->method_number == M_CONNECT) { + status = apr_uri_parse_hostinfo(r->pool, uri, &r->parsed_uri); + } + else { + status = apr_uri_parse(r->pool, uri, &r->parsed_uri); + } + + if (status == APR_SUCCESS) { + /* if it has a scheme we may need to do absoluteURI vhost stuff */ + if (r->parsed_uri.scheme + && !strcasecmp(r->parsed_uri.scheme, ap_http_scheme(r))) { + r->hostname = r->parsed_uri.hostname; + } + else if (r->method_number == M_CONNECT) { + r->hostname = r->parsed_uri.hostname; + } + + r->args = r->parsed_uri.query; + r->uri = r->parsed_uri.path ? r->parsed_uri.path + : apr_pstrdup(r->pool, "/"); + +#if defined(OS2) || defined(WIN32) + /* Handle path translations for OS/2 and plug security hole. + * This will prevent "http://www.wherever.com/..\..\/" from + * returning a directory for the root drive. + */ + { + char *x; + + for (x = r->uri; (x = strchr(x, '\\')) != NULL; ) + *x = '/'; + } +#endif /* OS2 || WIN32 */ + } + else { + r->args = NULL; + r->hostname = NULL; + r->status = HTTP_BAD_REQUEST; /* set error status */ + r->uri = apr_pstrdup(r->pool, uri); + } +} + +/* get the length of the field name for logging, but no more than 80 bytes */ +#define LOG_NAME_MAX_LEN 80 +static int field_name_len(const char *field) +{ + const char *end = ap_strchr_c(field, ':'); + if (end == NULL || end - field > LOG_NAME_MAX_LEN) + return LOG_NAME_MAX_LEN; + return end - field; +} + +static int read_request_line(request_rec *r, apr_bucket_brigade *bb) +{ + enum { + rrl_none, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace, + rrl_missinguri, rrl_baduri, rrl_badprotocol, rrl_trailingtext, + rrl_badmethod09, rrl_reject09 + } deferred_error = rrl_none; + char *ll; + char *uri; + apr_size_t len; + int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES; + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + + /* Read past empty lines until we get a real request line, + * a read error, the connection closes (EOF), or we timeout. + * + * We skip empty lines because browsers have to tack a CRLF on to the end + * of POSTs to support old CERN webservers. But note that we may not + * have flushed any previous response completely to the client yet. + * We delay the flush as long as possible so that we can improve + * performance for clients that are pipelining requests. If a request + * is pipelined then we won't block during the (implicit) read() below. + * If the requests aren't pipelined, then the client is still waiting + * for the final buffer flush from us, and we will block in the implicit + * read(). B_SAFEREAD ensures that the BUFF layer flushes if it will + * have to block during a read. + */ + + do { + apr_status_t rv; + + /* ensure ap_rgetline allocates memory each time thru the loop + * if there are empty lines + */ + r->the_request = NULL; + rv = ap_rgetline(&(r->the_request), (apr_size_t)(r->server->limit_req_line + 2), + &len, r, strict ? AP_GETLINE_CRLF : 0, bb); + + if (rv != APR_SUCCESS) { + r->request_time = apr_time_now(); + + /* ap_rgetline returns APR_ENOSPC if it fills up the + * buffer before finding the end-of-line. This is only going to + * happen if it exceeds the configured limit for a request-line. + */ + if (APR_STATUS_IS_ENOSPC(rv)) { + r->status = HTTP_REQUEST_URI_TOO_LARGE; + } + else if (APR_STATUS_IS_TIMEUP(rv)) { + r->status = HTTP_REQUEST_TIME_OUT; + } + else if (APR_STATUS_IS_EINVAL(rv)) { + r->status = HTTP_BAD_REQUEST; + } + r->proto_num = HTTP_VERSION(1,0); + r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); + return 0; + } + } while ((len <= 0) && (--num_blank_lines >= 0)); + + if (APLOGrtrace5(r)) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, + "Request received from client: %s", + ap_escape_logitem(r->pool, r->the_request)); + } + + r->request_time = apr_time_now(); + + r->method = r->the_request; + + /* If there is whitespace before a method, skip it and mark in error */ + if (apr_isspace(*r->method)) { + deferred_error = rrl_badwhitespace; + for ( ; apr_isspace(*r->method); ++r->method) + ; + } + + /* Scan the method up to the next whitespace, ensure it contains only + * valid http-token characters, otherwise mark in error + */ + if (strict) { + ll = (char*) ap_scan_http_token(r->method); + } + else { + ll = (char*) ap_scan_vchar_obstext(r->method); + } + + if (((ll == r->method) || (*ll && !apr_isspace(*ll))) + && deferred_error == rrl_none) { + deferred_error = rrl_badmethod; + ll = strpbrk(ll, "\t\n\v\f\r "); + } + + /* Verify method terminated with a single SP, or mark as specific error */ + if (!ll) { + if (deferred_error == rrl_none) + deferred_error = rrl_missinguri; + r->protocol = uri = ""; + len = 0; + goto rrl_done; + } + else if (strict && ll[0] && apr_isspace(ll[1]) + && deferred_error == rrl_none) { + deferred_error = rrl_excesswhitespace; + } + + /* Advance uri pointer over leading whitespace, NUL terminate the method + * If non-SP whitespace is encountered, mark as specific error + */ + for (uri = ll; apr_isspace(*uri); ++uri) + if (*uri != ' ' && deferred_error == rrl_none) + deferred_error = rrl_badwhitespace; + *ll = '\0'; + + if (!*uri && deferred_error == rrl_none) + deferred_error = rrl_missinguri; + + /* Scan the URI up to the next whitespace, ensure it contains no raw + * control characters, otherwise mark in error + */ + ll = (char*) ap_scan_vchar_obstext(uri); + if (ll == uri || (*ll && !apr_isspace(*ll))) { + deferred_error = rrl_baduri; + ll = strpbrk(ll, "\t\n\v\f\r "); + } + + /* Verify URI terminated with a single SP, or mark as specific error */ + if (!ll) { + r->protocol = ""; + len = 0; + goto rrl_done; + } + else if (strict && ll[0] && apr_isspace(ll[1]) + && deferred_error == rrl_none) { + deferred_error = rrl_excesswhitespace; + } + + /* Advance protocol pointer over leading whitespace, NUL terminate the uri + * If non-SP whitespace is encountered, mark as specific error + */ + for (r->protocol = ll; apr_isspace(*r->protocol); ++r->protocol) + if (*r->protocol != ' ' && deferred_error == rrl_none) + deferred_error = rrl_badwhitespace; + *ll = '\0'; + + /* Scan the protocol up to the next whitespace, validation comes later */ + if (!(ll = (char*) ap_scan_vchar_obstext(r->protocol))) { + len = strlen(r->protocol); + goto rrl_done; + } + len = ll - r->protocol; + + /* Advance over trailing whitespace, if found mark in error, + * determine if trailing text is found, unconditionally mark in error, + * finally NUL terminate the protocol string + */ + if (*ll && !apr_isspace(*ll)) { + deferred_error = rrl_badprotocol; + } + else if (strict && *ll) { + deferred_error = rrl_excesswhitespace; + } + else { + for ( ; apr_isspace(*ll); ++ll) + if (*ll != ' ' && deferred_error == rrl_none) + deferred_error = rrl_badwhitespace; + if (*ll && deferred_error == rrl_none) + deferred_error = rrl_trailingtext; + } + *((char *)r->protocol + len) = '\0'; + +rrl_done: + /* For internal integrity and palloc efficiency, reconstruct the_request + * in one palloc, using only single SP characters, per spec. + */ + r->the_request = apr_pstrcat(r->pool, r->method, *uri ? " " : NULL, uri, + *r->protocol ? " " : NULL, r->protocol, NULL); + + if (len == 8 + && r->protocol[0] == 'H' && r->protocol[1] == 'T' + && r->protocol[2] == 'T' && r->protocol[3] == 'P' + && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) + && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) + && r->protocol[5] != '0') { + r->assbackwards = 0; + r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); + } + else if (len == 8 + && (r->protocol[0] == 'H' || r->protocol[0] == 'h') + && (r->protocol[1] == 'T' || r->protocol[1] == 't') + && (r->protocol[2] == 'T' || r->protocol[2] == 't') + && (r->protocol[3] == 'P' || r->protocol[3] == 'p') + && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) + && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) + && r->protocol[5] != '0') { + r->assbackwards = 0; + r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); + if (strict && deferred_error == rrl_none) + deferred_error = rrl_badprotocol; + else + memcpy((char*)r->protocol, "HTTP", 4); + } + else if (r->protocol[0]) { + r->proto_num = HTTP_VERSION(0, 9); + /* Defer setting the r->protocol string till error msg is composed */ + if (deferred_error == rrl_none) + deferred_error = rrl_badprotocol; + } + else { + r->assbackwards = 1; + r->protocol = apr_pstrdup(r->pool, "HTTP/0.9"); + r->proto_num = HTTP_VERSION(0, 9); + } + + /* Determine the method_number and parse the uri prior to invoking error + * handling, such that these fields are available for substitution + */ + r->method_number = ap_method_number_of(r->method); + if (r->method_number == M_GET && r->method[0] == 'H') + r->header_only = 1; + + ap_parse_uri(r, uri); + + /* With the request understood, we can consider HTTP/0.9 specific errors */ + if (r->proto_num == HTTP_VERSION(0, 9) && deferred_error == rrl_none) { + if (conf->http09_enable == AP_HTTP09_DISABLE) + deferred_error = rrl_reject09; + else if (strict && (r->method_number != M_GET || r->header_only)) + deferred_error = rrl_badmethod09; + } + + /* Now that the method, uri and protocol are all processed, + * we can safely resume any deferred error reporting + */ + if (deferred_error != rrl_none) { + if (deferred_error == rrl_badmethod) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03445) + "HTTP Request Line; Invalid method token: '%.*s'", + field_name_len(r->method), r->method); + else if (deferred_error == rrl_badmethod09) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03444) + "HTTP Request Line; Invalid method token: '%.*s'" + " (only GET is allowed for HTTP/0.9 requests)", + field_name_len(r->method), r->method); + else if (deferred_error == rrl_missinguri) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03446) + "HTTP Request Line; Missing URI"); + else if (deferred_error == rrl_baduri) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03454) + "HTTP Request Line; URI incorrectly encoded: '%.*s'", + field_name_len(r->unparsed_uri), r->unparsed_uri); + else if (deferred_error == rrl_badwhitespace) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03447) + "HTTP Request Line; Invalid whitespace"); + else if (deferred_error == rrl_excesswhitespace) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03448) + "HTTP Request Line; Excess whitespace " + "(disallowed by HttpProtocolOptions Strict"); + else if (deferred_error == rrl_trailingtext) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03449) + "HTTP Request Line; Extraneous text found '%.*s' " + "(perhaps whitespace was injected?)", + field_name_len(ll), ll); + else if (deferred_error == rrl_reject09) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02401) + "HTTP Request Line; Rejected HTTP/0.9 request"); + else if (deferred_error == rrl_badprotocol) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418) + "HTTP Request Line; Unrecognized protocol '%.*s' " + "(perhaps whitespace was injected?)", + field_name_len(r->protocol), r->protocol); + r->status = HTTP_BAD_REQUEST; + goto rrl_failed; + } + + if (conf->http_methods == AP_HTTP_METHODS_REGISTERED + && r->method_number == M_INVALID) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02423) + "HTTP Request Line; Unrecognized HTTP method: '%.*s' " + "(disallowed by RegisteredMethods)", + field_name_len(r->method), r->method); + r->status = HTTP_NOT_IMPLEMENTED; + /* This can't happen in an HTTP/0.9 request, we verified GET above */ + return 0; + } + + if (r->status != HTTP_OK) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03450) + "HTTP Request Line; Unable to parse URI: '%.*s'", + field_name_len(r->uri), r->uri); + goto rrl_failed; + } + + if (strict) { + if (r->parsed_uri.fragment) { + /* RFC3986 3.5: no fragment */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02421) + "HTTP Request Line; URI must not contain a fragment"); + r->status = HTTP_BAD_REQUEST; + goto rrl_failed; + } + if (r->parsed_uri.user || r->parsed_uri.password) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02422) + "HTTP Request Line; URI must not contain a " + "username/password"); + r->status = HTTP_BAD_REQUEST; + goto rrl_failed; + } + } + + return 1; + +rrl_failed: + if (r->proto_num == HTTP_VERSION(0, 9)) { + /* Send all parsing and protocol error response with 1.x behavior, + * and reserve 505 errors for actual HTTP protocols presented. + * As called out in RFC7230 3.5, any errors parsing the protocol + * from the request line are nearly always misencoded HTTP/1.x + * requests. Only a valid 0.9 request with no parsing errors + * at all may be treated as a simple request, if allowed. + */ + r->assbackwards = 0; + r->connection->keepalive = AP_CONN_CLOSE; + r->proto_num = HTTP_VERSION(1, 0); + r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); + } + return 0; +} + +static int table_do_fn_check_lengths(void *r_, const char *key, + const char *value) +{ + request_rec *r = r_; + if (value == NULL || r->server->limit_req_fieldsize >= strlen(value) ) + return 1; + + r->status = HTTP_BAD_REQUEST; + apr_table_setn(r->notes, "error-notes", + "Size of a request header field exceeds server limit."); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request " + "header exceeds LimitRequestFieldSize after merging: %.*s", + field_name_len(key), key); + return 0; +} + +AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb) +{ + char *last_field = NULL; + apr_size_t last_len = 0; + apr_size_t alloc_len = 0; + char *field; + char *value; + apr_size_t len; + int fields_read = 0; + char *tmp_field; + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + + /* + * Read header lines until we get the empty separator line, a read error, + * the connection closes (EOF), reach the server limit, or we timeout. + */ + while(1) { + apr_status_t rv; + + field = NULL; + rv = ap_rgetline(&field, r->server->limit_req_fieldsize + 2, + &len, r, strict ? AP_GETLINE_CRLF : 0, bb); + + if (rv != APR_SUCCESS) { + if (APR_STATUS_IS_TIMEUP(rv)) { + r->status = HTTP_REQUEST_TIME_OUT; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, + "Failed to read request header line %s", field); + r->status = HTTP_BAD_REQUEST; + } + + /* ap_rgetline returns APR_ENOSPC if it fills up the buffer before + * finding the end-of-line. This is only going to happen if it + * exceeds the configured limit for a field size. + */ + if (rv == APR_ENOSPC) { + apr_table_setn(r->notes, "error-notes", + "Size of a request header field " + "exceeds server limit."); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00561) + "Request header exceeds LimitRequestFieldSize%s" + "%.*s", + (field && *field) ? ": " : "", + (field) ? field_name_len(field) : 0, + (field) ? field : ""); + } + return; + } + + /* For all header values, and all obs-fold lines, the presence of + * additional whitespace is a no-op, so collapse trailing whitespace + * to save buffer allocation and optimize copy operations. + * Do not remove the last single whitespace under any condition. + */ + while (len > 1 && (field[len-1] == '\t' || field[len-1] == ' ')) { + field[--len] = '\0'; + } + + if (*field == '\t' || *field == ' ') { + + /* Append any newly-read obs-fold line onto the preceding + * last_field line we are processing + */ + apr_size_t fold_len; + + if (last_field == NULL) { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03442) + "Line folding encountered before first" + " header line"); + return; + } + + if (field[1] == '\0') { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03443) + "Empty folded line encountered"); + return; + } + + /* Leading whitespace on an obs-fold line can be + * similarly discarded */ + while (field[1] == '\t' || field[1] == ' ') { + ++field; --len; + } + + /* This line is a continuation of the preceding line(s), + * so append it to the line that we've set aside. + * Note: this uses a power-of-two allocator to avoid + * doing O(n) allocs and using O(n^2) space for + * continuations that span many many lines. + */ + fold_len = last_len + len + 1; /* trailing null */ + + if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) { + r->status = HTTP_BAD_REQUEST; + /* report what we have accumulated so far before the + * overflow (last_field) as the field with the problem + */ + apr_table_setn(r->notes, "error-notes", + "Size of a request header field " + "exceeds server limit."); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00562) + "Request header exceeds LimitRequestFieldSize " + "after folding: %.*s", + field_name_len(last_field), last_field); + return; + } + + if (fold_len > alloc_len) { + char *fold_buf; + alloc_len += alloc_len; + if (fold_len > alloc_len) { + alloc_len = fold_len; + } + fold_buf = (char *)apr_palloc(r->pool, alloc_len); + memcpy(fold_buf, last_field, last_len); + last_field = fold_buf; + } + memcpy(last_field + last_len, field, len +1); /* +1 for nul */ + /* Replace obs-fold w/ SP per RFC 7230 3.2.4 */ + last_field[last_len] = ' '; + last_len += len; + + /* We've appended this obs-fold line to last_len, proceed to + * read the next input line + */ + continue; + } + else if (last_field != NULL) { + + /* Process the previous last_field header line with all obs-folded + * segments already concatenated (this is not operating on the + * most recently read input line). + */ + + if (r->server->limit_req_fields + && (++fields_read > r->server->limit_req_fields)) { + r->status = HTTP_BAD_REQUEST; + apr_table_setn(r->notes, "error-notes", + "The number of request header fields " + "exceeds this server's limit."); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563) + "Number of request headers exceeds " + "LimitRequestFields"); + return; + } + + if (!strict) + { + /* Not Strict ('Unsafe' mode), using the legacy parser */ + + if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ + r->status = HTTP_BAD_REQUEST; /* abort bad request */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00564) + "Request header field is missing ':' " + "separator: %.*s", (int)LOG_NAME_MAX_LEN, + last_field); + return; + } + + if (value == last_field) { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03453) + "Request header field name was empty"); + return; + } + + *value++ = '\0'; /* NUL-terminate at colon */ + + if (strpbrk(last_field, "\t\n\v\f\r ")) { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03452) + "Request header field name presented" + " invalid whitespace"); + return; + } + + while (*value == ' ' || *value == '\t') { + ++value; /* Skip to start of value */ + } + + if (strpbrk(value, "\n\v\f\r")) { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03451) + "Request header field value presented" + " bad whitespace"); + return; + } + } + else /* Using strict RFC7230 parsing */ + { + /* Ensure valid token chars before ':' per RFC 7230 3.2.4 */ + value = (char *)ap_scan_http_token(last_field); + if ((value == last_field) || *value != ':') { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02426) + "Request header field name is malformed: " + "%.*s", (int)LOG_NAME_MAX_LEN, last_field); + return; + } + + *value++ = '\0'; /* NUL-terminate last_field name at ':' */ + + while (*value == ' ' || *value == '\t') { + ++value; /* Skip LWS of value */ + } + + /* Find invalid, non-HT ctrl char, or the trailing NULL */ + tmp_field = (char *)ap_scan_http_field_content(value); + + /* Reject value for all garbage input (CTRLs excluding HT) + * e.g. only VCHAR / SP / HT / obs-text are allowed per + * RFC7230 3.2.6 - leave all more explicit rule enforcement + * for specific header handler logic later in the cycle + */ + if (*tmp_field != '\0') { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02427) + "Request header value is malformed: " + "%.*s", (int)LOG_NAME_MAX_LEN, value); + return; + } + } + + apr_table_addn(r->headers_in, last_field, value); + + /* This last_field header is now stored in headers_in, + * resume processing of the current input line. + */ + } + + /* Found the terminating empty end-of-headers line, stop. */ + if (len == 0) { + break; + } + + /* Keep track of this new header line so that we can extend it across + * any obs-fold or parse it on the next loop iteration. We referenced + * our previously allocated buffer in r->headers_in, + * so allocate a fresh buffer if required. + */ + alloc_len = 0; + last_field = field; + last_len = len; + } + + /* Combine multiple message-header fields with the same + * field-name, following RFC 2616, 4.2. + */ + apr_table_compress(r->headers_in, APR_OVERLAP_TABLES_MERGE); + + /* enforce LimitRequestFieldSize for merged headers */ + apr_table_do(table_do_fn_check_lengths, r, r->headers_in, NULL); +} + +AP_DECLARE(void) ap_get_mime_headers(request_rec *r) +{ + apr_bucket_brigade *tmp_bb; + tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + ap_get_mime_headers_core(r, tmp_bb); + apr_brigade_destroy(tmp_bb); +} + +request_rec *ap_read_request(conn_rec *conn) +{ + request_rec *r; + apr_pool_t *p; + const char *expect; + int access_status; + apr_bucket_brigade *tmp_bb; + apr_socket_t *csd; + apr_interval_time_t cur_timeout; + + + apr_pool_create(&p, conn->pool); + apr_pool_tag(p, "request"); + r = apr_pcalloc(p, sizeof(request_rec)); + AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn); + r->pool = p; + r->connection = conn; + r->server = conn->base_server; + + r->user = NULL; + r->ap_auth_type = NULL; + + r->allowed_methods = ap_make_method_list(p, 2); + + r->headers_in = apr_table_make(r->pool, 25); + r->trailers_in = apr_table_make(r->pool, 5); + r->subprocess_env = apr_table_make(r->pool, 25); + r->headers_out = apr_table_make(r->pool, 12); + r->err_headers_out = apr_table_make(r->pool, 5); + r->trailers_out = apr_table_make(r->pool, 5); + r->notes = apr_table_make(r->pool, 5); + + r->request_config = ap_create_request_config(r->pool); + /* Must be set before we run create request hook */ + + r->proto_output_filters = conn->output_filters; + r->output_filters = r->proto_output_filters; + r->proto_input_filters = conn->input_filters; + r->input_filters = r->proto_input_filters; + ap_run_create_request(r); + r->per_dir_config = r->server->lookup_defaults; + + r->sent_bodyct = 0; /* bytect isn't for body */ + + r->read_length = 0; + r->read_body = REQUEST_NO_BODY; + + r->status = HTTP_OK; /* Until further notice */ + r->the_request = NULL; + + /* Begin by presuming any module can make its own path_info assumptions, + * until some module interjects and changes the value. + */ + r->used_path_info = AP_REQ_DEFAULT_PATH_INFO; + + r->useragent_addr = conn->client_addr; + r->useragent_ip = conn->client_ip; + + tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + ap_run_pre_read_request(r, conn); + + /* Get the request... */ + if (!read_request_line(r, tmp_bb)) { + switch (r->status) { + case HTTP_REQUEST_URI_TOO_LARGE: + case HTTP_BAD_REQUEST: + case HTTP_VERSION_NOT_SUPPORTED: + case HTTP_NOT_IMPLEMENTED: + if (r->status == HTTP_REQUEST_URI_TOO_LARGE) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00565) + "request failed: client's request-line exceeds LimitRequestLine (longer than %d)", + r->server->limit_req_line); + } + else if (r->method == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00566) + "request failed: malformed request line"); + } + access_status = r->status; + r->status = HTTP_OK; + ap_die(access_status, r); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + r = NULL; + apr_brigade_destroy(tmp_bb); + goto traceout; + case HTTP_REQUEST_TIME_OUT: + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, NULL); + if (!r->connection->keepalives) + ap_run_log_transaction(r); + apr_brigade_destroy(tmp_bb); + goto traceout; + default: + apr_brigade_destroy(tmp_bb); + r = NULL; + goto traceout; + } + } + + /* We may have been in keep_alive_timeout mode, so toggle back + * to the normal timeout mode as we fetch the header lines, + * as necessary. + */ + csd = ap_get_conn_socket(conn); + apr_socket_timeout_get(csd, &cur_timeout); + if (cur_timeout != conn->base_server->timeout) { + apr_socket_timeout_set(csd, conn->base_server->timeout); + cur_timeout = conn->base_server->timeout; + } + + if (!r->assbackwards) { + const char *tenc; + + ap_get_mime_headers_core(r, tmp_bb); + if (r->status != HTTP_OK) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00567) + "request failed: error reading the headers"); + ap_send_error_response(r, 0); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + apr_brigade_destroy(tmp_bb); + goto traceout; + } + + tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); + if (tenc) { + /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23 + * Section 3.3.3.3: "If a Transfer-Encoding header field is + * present in a request and the chunked transfer coding is not + * the final encoding ...; the server MUST respond with the 400 + * (Bad Request) status code and then close the connection". + */ + if (!(strcasecmp(tenc, "chunked") == 0 /* fast path */ + || ap_find_last_token(r->pool, tenc, "chunked"))) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02539) + "client sent unknown Transfer-Encoding " + "(%s): %s", tenc, r->uri); + r->status = HTTP_BAD_REQUEST; + conn->keepalive = AP_CONN_CLOSE; + ap_send_error_response(r, 0); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + apr_brigade_destroy(tmp_bb); + goto traceout; + } + + /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23 + * Section 3.3.3.3: "If a message is received with both a + * Transfer-Encoding and a Content-Length header field, the + * Transfer-Encoding overrides the Content-Length. ... A sender + * MUST remove the received Content-Length field". + */ + apr_table_unset(r->headers_in, "Content-Length"); + } + } + + apr_brigade_destroy(tmp_bb); + + /* update what we think the virtual host is based on the headers we've + * now read. may update status. + */ + ap_update_vhost_from_headers(r); + access_status = r->status; + + /* Toggle to the Host:-based vhost's timeout mode to fetch the + * request body and send the response body, if needed. + */ + if (cur_timeout != r->server->timeout) { + apr_socket_timeout_set(csd, r->server->timeout); + cur_timeout = r->server->timeout; + } + + /* we may have switched to another server */ + r->per_dir_config = r->server->lookup_defaults; + + if ((!r->hostname && (r->proto_num >= HTTP_VERSION(1, 1))) + || ((r->proto_num == HTTP_VERSION(1, 1)) + && !apr_table_get(r->headers_in, "Host"))) { + /* + * Client sent us an HTTP/1.1 or later request without telling us the + * hostname, either with a full URL or a Host: header. We therefore + * need to (as per the 1.1 spec) send an error. As a special case, + * HTTP/1.1 mentions twice (S9, S14.23) that a request MUST contain + * a Host: header, and the server MUST respond with 400 if it doesn't. + */ + access_status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00569) + "client sent HTTP/1.1 request without hostname " + "(see RFC2616 section 14.23): %s", r->uri); + } + + /* + * Add the HTTP_IN filter here to ensure that ap_discard_request_body + * called by ap_die and by ap_send_error_response works correctly on + * status codes that do not cause the connection to be dropped and + * in situations where the connection should be kept alive. + */ + + ap_add_input_filter_handle(ap_http_input_filter_handle, + NULL, r, r->connection); + + if (access_status != HTTP_OK + || (access_status = ap_run_post_read_request(r))) { + ap_die(access_status, r); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + r = NULL; + goto traceout; + } + + if (((expect = apr_table_get(r->headers_in, "Expect")) != NULL) + && (expect[0] != '\0')) { + /* + * The Expect header field was added to HTTP/1.1 after RFC 2068 + * as a means to signal when a 100 response is desired and, + * unfortunately, to signal a poor man's mandatory extension that + * the server must understand or return 417 Expectation Failed. + */ + if (strcasecmp(expect, "100-continue") == 0) { + r->expecting_100 = 1; + } + else { + r->status = HTTP_EXPECTATION_FAILED; + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00570) + "client sent an unrecognized expectation value of " + "Expect: %s", expect); + ap_send_error_response(r, 0); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + goto traceout; + } + } + + AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, (char *)r->uri, (char *)r->server->defn_name, r->status); + return r; + traceout: + AP_READ_REQUEST_FAILURE((uintptr_t)r); + return r; +} + +/* if a request with a body creates a subrequest, remove original request's + * input headers which pertain to the body which has already been read. + * out-of-line helper function for ap_set_sub_req_protocol. + */ + +static void strip_headers_request_body(request_rec *rnew) +{ + apr_table_unset(rnew->headers_in, "Content-Encoding"); + apr_table_unset(rnew->headers_in, "Content-Language"); + apr_table_unset(rnew->headers_in, "Content-Length"); + apr_table_unset(rnew->headers_in, "Content-Location"); + apr_table_unset(rnew->headers_in, "Content-MD5"); + apr_table_unset(rnew->headers_in, "Content-Range"); + apr_table_unset(rnew->headers_in, "Content-Type"); + apr_table_unset(rnew->headers_in, "Expires"); + apr_table_unset(rnew->headers_in, "Last-Modified"); + apr_table_unset(rnew->headers_in, "Transfer-Encoding"); +} + +/* + * A couple of other functions which initialize some of the fields of + * a request structure, as appropriate for adjuncts of one kind or another + * to a request in progress. Best here, rather than elsewhere, since + * *someone* has to set the protocol-specific fields... + */ + +AP_DECLARE(void) ap_set_sub_req_protocol(request_rec *rnew, + const request_rec *r) +{ + rnew->the_request = r->the_request; /* Keep original request-line */ + + rnew->assbackwards = 1; /* Don't send headers from this. */ + rnew->no_local_copy = 1; /* Don't try to send HTTP_NOT_MODIFIED for a + * fragment. */ + rnew->method = "GET"; + rnew->method_number = M_GET; + rnew->protocol = "INCLUDED"; + + rnew->status = HTTP_OK; + + rnew->headers_in = apr_table_copy(rnew->pool, r->headers_in); + rnew->trailers_in = apr_table_copy(rnew->pool, r->trailers_in); + + /* did the original request have a body? (e.g. POST w/SSI tags) + * if so, make sure the subrequest doesn't inherit body headers + */ + if (!r->kept_body && (apr_table_get(r->headers_in, "Content-Length") + || apr_table_get(r->headers_in, "Transfer-Encoding"))) { + strip_headers_request_body(rnew); + } + rnew->subprocess_env = apr_table_copy(rnew->pool, r->subprocess_env); + rnew->headers_out = apr_table_make(rnew->pool, 5); + rnew->err_headers_out = apr_table_make(rnew->pool, 5); + rnew->trailers_out = apr_table_make(rnew->pool, 5); + rnew->notes = apr_table_make(rnew->pool, 5); + + rnew->expecting_100 = r->expecting_100; + rnew->read_length = r->read_length; + rnew->read_body = REQUEST_NO_BODY; + + rnew->main = (request_rec *) r; +} + +static void end_output_stream(request_rec *r) +{ + conn_rec *c = r->connection; + apr_bucket_brigade *bb; + apr_bucket *b; + + bb = apr_brigade_create(r->pool, c->bucket_alloc); + b = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + ap_pass_brigade(r->output_filters, bb); +} + +AP_DECLARE(void) ap_finalize_sub_req_protocol(request_rec *sub) +{ + /* tell the filter chain there is no more content coming */ + if (!sub->eos_sent) { + end_output_stream(sub); + } +} + +/* finalize_request_protocol is called at completion of sending the + * response. Its sole purpose is to send the terminating protocol + * information for any wrappers around the response message body + * (i.e., transfer encodings). It should have been named finalize_response. + */ +AP_DECLARE(void) ap_finalize_request_protocol(request_rec *r) +{ + (void) ap_discard_request_body(r); + + /* tell the filter chain there is no more content coming */ + if (!r->eos_sent) { + end_output_stream(r); + } +} + +/* + * Support for the Basic authentication protocol, and a bit for Digest. + */ +AP_DECLARE(void) ap_note_auth_failure(request_rec *r) +{ + const char *type = ap_auth_type(r); + if (type) { + ap_run_note_auth_failure(r, type); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00571) + "need AuthType to note auth failure: %s", r->uri); + } +} + +AP_DECLARE(void) ap_note_basic_auth_failure(request_rec *r) +{ + ap_note_auth_failure(r); +} + +AP_DECLARE(void) ap_note_digest_auth_failure(request_rec *r) +{ + ap_note_auth_failure(r); +} + +AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw) +{ + const char *auth_line = apr_table_get(r->headers_in, + (PROXYREQ_PROXY == r->proxyreq) + ? "Proxy-Authorization" + : "Authorization"); + const char *t; + + if (!(t = ap_auth_type(r)) || strcasecmp(t, "Basic")) + return DECLINED; + + if (!ap_auth_name(r)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00572) + "need AuthName: %s", r->uri); + return HTTP_INTERNAL_SERVER_ERROR; + } + + if (!auth_line) { + ap_note_auth_failure(r); + return HTTP_UNAUTHORIZED; + } + + if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) { + /* Client tried to authenticate using wrong auth scheme */ + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00573) + "client used wrong authentication scheme: %s", r->uri); + ap_note_auth_failure(r); + return HTTP_UNAUTHORIZED; + } + + while (*auth_line == ' ' || *auth_line == '\t') { + auth_line++; + } + + t = ap_pbase64decode(r->pool, auth_line); + r->user = ap_getword_nulls (r->pool, &t, ':'); + apr_table_setn(r->notes, AP_GET_BASIC_AUTH_PW_NOTE, "1"); + r->ap_auth_type = "Basic"; + + *pw = t; + + return OK; +} + +AP_DECLARE(apr_status_t) ap_get_basic_auth_components(const request_rec *r, + const char **username, + const char **password) +{ + const char *auth_header; + const char *credentials; + const char *decoded; + const char *user; + + auth_header = (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authorization" + : "Authorization"; + credentials = apr_table_get(r->headers_in, auth_header); + + if (!credentials) { + /* No auth header. */ + return APR_EINVAL; + } + + if (ap_cstr_casecmp(ap_getword(r->pool, &credentials, ' '), "Basic")) { + /* These aren't Basic credentials. */ + return APR_EINVAL; + } + + while (*credentials == ' ' || *credentials == '\t') { + credentials++; + } + + /* XXX Our base64 decoding functions don't actually error out if the string + * we give it isn't base64; they'll just silently stop and hand us whatever + * they've parsed up to that point. + * + * Since this function is supposed to be a drop-in replacement for the + * deprecated ap_get_basic_auth_pw(), don't fix this for 2.4.x. + */ + decoded = ap_pbase64decode(r->pool, credentials); + user = ap_getword_nulls(r->pool, &decoded, ':'); + + if (username) { + *username = user; + } + if (password) { + *password = decoded; + } + + return APR_SUCCESS; +} + +struct content_length_ctx { + int data_sent; /* true if the C-L filter has already sent at + * least one bucket on to the next output filter + * for this request + */ + apr_bucket_brigade *tmpbb; +}; + +/* This filter computes the content length, but it also computes the number + * of bytes sent to the client. This means that this filter will always run + * through all of the buckets in all brigades + */ +AP_CORE_DECLARE_NONSTD(apr_status_t) ap_content_length_filter( + ap_filter_t *f, + apr_bucket_brigade *b) +{ + request_rec *r = f->r; + struct content_length_ctx *ctx; + apr_bucket *e; + int eos = 0; + apr_read_type_e eblock = APR_NONBLOCK_READ; + + ctx = f->ctx; + if (!ctx) { + f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx)); + ctx->data_sent = 0; + ctx->tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + } + + /* Loop through the brigade to count the length. To avoid + * arbitrary memory consumption with morphing bucket types, this + * loop will stop and pass on the brigade when necessary. */ + e = APR_BRIGADE_FIRST(b); + while (e != APR_BRIGADE_SENTINEL(b)) { + apr_status_t rv; + + if (APR_BUCKET_IS_EOS(e)) { + eos = 1; + break; + } + /* For a flush bucket, fall through to pass the brigade and + * flush now. */ + else if (APR_BUCKET_IS_FLUSH(e)) { + e = APR_BUCKET_NEXT(e); + } + /* For metadata bucket types other than FLUSH, loop. */ + else if (APR_BUCKET_IS_METADATA(e)) { + e = APR_BUCKET_NEXT(e); + continue; + } + /* For determinate length data buckets, count the length and + * continue. */ + else if (e->length != (apr_size_t)-1) { + r->bytes_sent += e->length; + e = APR_BUCKET_NEXT(e); + continue; + } + /* For indeterminate length data buckets, perform one read. */ + else /* e->length == (apr_size_t)-1 */ { + apr_size_t len; + const char *ignored; + + rv = apr_bucket_read(e, &ignored, &len, eblock); + if ((rv != APR_SUCCESS) && !APR_STATUS_IS_EAGAIN(rv)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00574) + "ap_content_length_filter: " + "apr_bucket_read() failed"); + return rv; + } + if (rv == APR_SUCCESS) { + eblock = APR_NONBLOCK_READ; + e = APR_BUCKET_NEXT(e); + r->bytes_sent += len; + } + else if (APR_STATUS_IS_EAGAIN(rv)) { + apr_bucket *flush; + + /* Next read must block. */ + eblock = APR_BLOCK_READ; + + /* Ensure the last bucket to pass down is a flush if + * the next read will block. */ + flush = apr_bucket_flush_create(f->c->bucket_alloc); + APR_BUCKET_INSERT_BEFORE(e, flush); + } + } + + /* Optimization: if the next bucket is EOS (directly after a + * bucket morphed to the heap, or a flush), short-cut to + * handle EOS straight away - allowing C-L to be determined + * for content which is already entirely in memory. */ + if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) { + continue; + } + + /* On reaching here, pass on everything in the brigade up to + * this point. */ + apr_brigade_split_ex(b, e, ctx->tmpbb); + + rv = ap_pass_brigade(f->next, b); + if (rv != APR_SUCCESS) { + return rv; + } + else if (f->c->aborted) { + return APR_ECONNABORTED; + } + apr_brigade_cleanup(b); + APR_BRIGADE_CONCAT(b, ctx->tmpbb); + e = APR_BRIGADE_FIRST(b); + + ctx->data_sent = 1; + } + + /* If we've now seen the entire response and it's otherwise + * okay to set the C-L in the response header, then do so now. + * + * We can only set a C-L in the response header if we haven't already + * sent any buckets on to the next output filter for this request. + */ + if (ctx->data_sent == 0 && eos && + /* don't whack the C-L if it has already been set for a HEAD + * by something like proxy. the brigade only has an EOS bucket + * in this case, making r->bytes_sent zero. + * + * if r->bytes_sent > 0 we have a (temporary) body whose length may + * have been changed by a filter. the C-L header might not have been + * updated so we do it here. long term it would be cleaner to have + * such filters update or remove the C-L header, and just use it + * if present. + */ + !((r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) && r->bytes_sent == 0 && + apr_table_get(r->headers_out, "Content-Length"))) { + ap_set_content_length(r, r->bytes_sent); + } + + ctx->data_sent = 1; + return ap_pass_brigade(f->next, b); +} + +/* + * Send the body of a response to the client. + */ +AP_DECLARE(apr_status_t) ap_send_fd(apr_file_t *fd, request_rec *r, + apr_off_t offset, apr_size_t len, + apr_size_t *nbytes) +{ + conn_rec *c = r->connection; + apr_bucket_brigade *bb = NULL; + apr_status_t rv; + + bb = apr_brigade_create(r->pool, c->bucket_alloc); + + apr_brigade_insert_file(bb, fd, offset, len, r->pool); + + rv = ap_pass_brigade(r->output_filters, bb); + if (rv != APR_SUCCESS) { + *nbytes = 0; /* no way to tell how many were actually sent */ + } + else { + *nbytes = len; + } + + return rv; +} + +#if APR_HAS_MMAP +/* send data from an in-memory buffer */ +AP_DECLARE(apr_size_t) ap_send_mmap(apr_mmap_t *mm, + request_rec *r, + apr_size_t offset, + apr_size_t length) +{ + conn_rec *c = r->connection; + apr_bucket_brigade *bb = NULL; + apr_bucket *b; + + bb = apr_brigade_create(r->pool, c->bucket_alloc); + b = apr_bucket_mmap_create(mm, offset, length, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + ap_pass_brigade(r->output_filters, bb); + + return mm->size; /* XXX - change API to report apr_status_t? */ +} +#endif /* APR_HAS_MMAP */ + +typedef struct { + apr_bucket_brigade *bb; + apr_bucket_brigade *tmpbb; +} old_write_filter_ctx; + +AP_CORE_DECLARE_NONSTD(apr_status_t) ap_old_write_filter( + ap_filter_t *f, apr_bucket_brigade *bb) +{ + old_write_filter_ctx *ctx = f->ctx; + + AP_DEBUG_ASSERT(ctx); + + if (ctx->bb != NULL) { + /* whatever is coming down the pipe (we don't care), we + * can simply insert our buffered data at the front and + * pass the whole bundle down the chain. + */ + APR_BRIGADE_PREPEND(bb, ctx->bb); + } + + return ap_pass_brigade(f->next, bb); +} + +static ap_filter_t *insert_old_write_filter(request_rec *r) +{ + ap_filter_t *f; + old_write_filter_ctx *ctx; + + /* future optimization: record some flags in the request_rec to + * say whether we've added our filter, and whether it is first. + */ + + /* this will typically exit on the first test */ + for (f = r->output_filters; f != NULL; f = f->next) { + if (ap_old_write_func == f->frec) + break; + } + + if (f == NULL) { + /* our filter hasn't been added yet */ + ctx = apr_pcalloc(r->pool, sizeof(*ctx)); + ctx->tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + ap_add_output_filter("OLD_WRITE", ctx, r, r->connection); + f = r->output_filters; + } + + return f; +} + +static apr_status_t buffer_output(request_rec *r, + const char *str, apr_size_t len) +{ + conn_rec *c = r->connection; + ap_filter_t *f; + old_write_filter_ctx *ctx; + + if (len == 0) + return APR_SUCCESS; + + f = insert_old_write_filter(r); + ctx = f->ctx; + + /* if the first filter is not our buffering filter, then we have to + * deliver the content through the normal filter chain + */ + if (f != r->output_filters) { + apr_status_t rv; + apr_bucket *b = apr_bucket_transient_create(str, len, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, b); + + rv = ap_pass_brigade(r->output_filters, ctx->tmpbb); + apr_brigade_cleanup(ctx->tmpbb); + return rv; + } + + if (ctx->bb == NULL) { + ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc); + } + + return ap_fwrite(f->next, ctx->bb, str, len); +} + +AP_DECLARE(int) ap_rputc(int c, request_rec *r) +{ + char c2 = (char)c; + + if (r->connection->aborted) { + return -1; + } + + if (buffer_output(r, &c2, 1) != APR_SUCCESS) + return -1; + + return c; +} + +AP_DECLARE(int) ap_rwrite(const void *buf, int nbyte, request_rec *r) +{ + if (r->connection->aborted) + return -1; + + if (buffer_output(r, buf, nbyte) != APR_SUCCESS) + return -1; + + return nbyte; +} + +struct ap_vrprintf_data { + apr_vformatter_buff_t vbuff; + request_rec *r; + char *buff; +}; + +/* Flush callback for apr_vformatter; returns -1 on error. */ +static int r_flush(apr_vformatter_buff_t *buff) +{ + /* callback function passed to ap_vformatter to be called when + * vformatter needs to write into buff and buff.curpos > buff.endpos */ + + /* ap_vrprintf_data passed as a apr_vformatter_buff_t, which is then + * "downcast" to an ap_vrprintf_data */ + struct ap_vrprintf_data *vd = (struct ap_vrprintf_data*)buff; + + if (vd->r->connection->aborted) + return -1; + + /* r_flush is called when vbuff is completely full */ + if (buffer_output(vd->r, vd->buff, AP_IOBUFSIZE)) { + return -1; + } + + /* reset the buffer position */ + vd->vbuff.curpos = vd->buff; + vd->vbuff.endpos = vd->buff + AP_IOBUFSIZE; + + return 0; +} + +AP_DECLARE(int) ap_vrprintf(request_rec *r, const char *fmt, va_list va) +{ + apr_size_t written; + struct ap_vrprintf_data vd; + char vrprintf_buf[AP_IOBUFSIZE]; + + vd.vbuff.curpos = vrprintf_buf; + vd.vbuff.endpos = vrprintf_buf + AP_IOBUFSIZE; + vd.r = r; + vd.buff = vrprintf_buf; + + if (r->connection->aborted) + return -1; + + written = apr_vformatter(r_flush, &vd.vbuff, fmt, va); + + if (written != -1) { + int n = vd.vbuff.curpos - vrprintf_buf; + + /* last call to buffer_output, to finish clearing the buffer */ + if (buffer_output(r, vrprintf_buf,n) != APR_SUCCESS) + return -1; + + written += n; + } + + return written; +} + +AP_DECLARE_NONSTD(int) ap_rprintf(request_rec *r, const char *fmt, ...) +{ + va_list va; + int n; + + if (r->connection->aborted) + return -1; + + va_start(va, fmt); + n = ap_vrprintf(r, fmt, va); + va_end(va); + + return n; +} + +AP_DECLARE_NONSTD(int) ap_rvputs(request_rec *r, ...) +{ + va_list va; + const char *s; + apr_size_t len; + apr_size_t written = 0; + + if (r->connection->aborted) + return -1; + + /* ### TODO: if the total output is large, put all the strings + * ### into a single brigade, rather than flushing each time we + * ### fill the buffer + */ + va_start(va, r); + while (1) { + s = va_arg(va, const char *); + if (s == NULL) + break; + + len = strlen(s); + if (buffer_output(r, s, len) != APR_SUCCESS) { + return -1; + } + + written += len; + } + va_end(va); + + return written; +} + +AP_DECLARE(int) ap_rflush(request_rec *r) +{ + conn_rec *c = r->connection; + apr_bucket *b; + ap_filter_t *f; + old_write_filter_ctx *ctx; + apr_status_t rv; + + f = insert_old_write_filter(r); + ctx = f->ctx; + + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, b); + + rv = ap_pass_brigade(r->output_filters, ctx->tmpbb); + apr_brigade_cleanup(ctx->tmpbb); + if (rv != APR_SUCCESS) + return -1; + + return 0; +} + +/* + * This function sets the Last-Modified output header field to the value + * of the mtime field in the request structure - rationalized to keep it from + * being in the future. + */ +AP_DECLARE(void) ap_set_last_modified(request_rec *r) +{ + if (!r->assbackwards) { + apr_time_t mod_time = ap_rationalize_mtime(r, r->mtime); + char *datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN); + + apr_rfc822_date(datestr, mod_time); + apr_table_setn(r->headers_out, "Last-Modified", datestr); + } +} + +typedef struct hdr_ptr { + ap_filter_t *f; + apr_bucket_brigade *bb; +} hdr_ptr; + +#if APR_CHARSET_EBCDIC +static int send_header(void *data, const char *key, const char *val) +{ + char *header_line = NULL; + hdr_ptr *hdr = (hdr_ptr*)data; + + header_line = apr_pstrcat(hdr->bb->p, key, ": ", val, CRLF, NULL); + ap_xlate_proto_to_ascii(header_line, strlen(header_line)); + ap_fputs(hdr->f, hdr->bb, header_line); + return 1; +} +#else +static int send_header(void *data, const char *key, const char *val) +{ + ap_fputstrs(((hdr_ptr*)data)->f, ((hdr_ptr*)data)->bb, + key, ": ", val, CRLF, NULL); + return 1; + } +#endif + +AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers) +{ + hdr_ptr x; + char *status_line = NULL; + request_rec *rr; + + if (r->proto_num < HTTP_VERSION(1,1)) { + /* don't send interim response to HTTP/1.0 Client */ + return; + } + if (!ap_is_HTTP_INFO(r->status)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00575) + "Status is %d - not sending interim response", r->status); + return; + } + if ((r->status == HTTP_CONTINUE) && !r->expecting_100) { + /* + * Don't send 100-Continue when there was no Expect: 100-continue + * in the request headers. For origin servers this is a SHOULD NOT + * for proxies it is a MUST NOT according to RFC 2616 8.2.3 + */ + return; + } + + /* if we send an interim response, we're no longer in a state of + * expecting one. Also, this could feasibly be in a subrequest, + * so we need to propagate the fact that we responded. + */ + for (rr = r; rr != NULL; rr = rr->main) { + rr->expecting_100 = 0; + } + + status_line = apr_pstrcat(r->pool, AP_SERVER_PROTOCOL, " ", r->status_line, CRLF, NULL); + ap_xlate_proto_to_ascii(status_line, strlen(status_line)); + + x.f = r->connection->output_filters; + x.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + ap_fputs(x.f, x.bb, status_line); + if (send_headers) { + apr_table_do(send_header, &x, r->headers_out, NULL); + apr_table_clear(r->headers_out); + } + ap_fputs(x.f, x.bb, CRLF_ASCII); + ap_fflush(x.f, x.bb); + apr_brigade_destroy(x.bb); +} + +/* + * Compare two protocol identifier. Result is similar to strcmp(): + * 0 gives same precedence, >0 means proto1 is preferred. + */ +static int protocol_cmp(const apr_array_header_t *preferences, + const char *proto1, + const char *proto2) +{ + if (preferences && preferences->nelts > 0) { + int index1 = ap_array_str_index(preferences, proto1, 0); + int index2 = ap_array_str_index(preferences, proto2, 0); + if (index2 > index1) { + return (index1 >= 0) ? 1 : -1; + } + else if (index1 > index2) { + return (index2 >= 0) ? -1 : 1; + } + } + /* both have the same index (mabye -1 or no pref configured) and we compare + * the names so that spdy3 gets precedence over spdy2. That makes + * the outcome at least deterministic. */ + return strcmp(proto1, proto2); +} + +AP_DECLARE(const char *) ap_get_protocol(conn_rec *c) +{ + const char *protocol = ap_run_protocol_get(c); + return protocol? protocol : AP_PROTOCOL_HTTP1; +} + +AP_DECLARE(apr_status_t) ap_get_protocol_upgrades(conn_rec *c, request_rec *r, + server_rec *s, int report_all, + const apr_array_header_t **pupgrades) +{ + apr_pool_t *pool = r? r->pool : c->pool; + core_server_config *conf; + const char *existing; + apr_array_header_t *upgrades = NULL; + + if (!s) { + s = (r? r->server : c->base_server); + } + conf = ap_get_core_module_config(s->module_config); + + if (conf->protocols->nelts > 0) { + existing = ap_get_protocol(c); + if (conf->protocols->nelts > 1 + || !ap_array_str_contains(conf->protocols, existing)) { + int i; + + /* possibly more than one choice or one, but not the + * existing. (TODO: maybe 426 and Upgrade then?) */ + upgrades = apr_array_make(pool, conf->protocols->nelts + 1, + sizeof(char *)); + for (i = 0; i < conf->protocols->nelts; i++) { + const char *p = APR_ARRAY_IDX(conf->protocols, i, char *); + if (strcmp(existing, p)) { + /* not the one we have and possible, add in this order */ + APR_ARRAY_PUSH(upgrades, const char*) = p; + } + else if (!report_all) { + break; + } + } + } + } + + *pupgrades = upgrades; + return APR_SUCCESS; +} + +AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r, + server_rec *s, + const apr_array_header_t *choices) +{ + apr_pool_t *pool = r? r->pool : c->pool; + core_server_config *conf; + const char *protocol = NULL, *existing; + apr_array_header_t *proposals; + + if (!s) { + s = (r? r->server : c->base_server); + } + conf = ap_get_core_module_config(s->module_config); + + if (APLOGcdebug(c)) { + const char *p = apr_array_pstrcat(pool, conf->protocols, ','); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03155) + "select protocol from %s, choices=%s for server %s", + p, apr_array_pstrcat(pool, choices, ','), + s->server_hostname); + } + + if (conf->protocols->nelts <= 0) { + /* nothing configured, by default, we only allow http/1.1 here. + * For now... + */ + if (ap_array_str_contains(choices, AP_PROTOCOL_HTTP1)) { + return AP_PROTOCOL_HTTP1; + } + else { + return NULL; + } + } + + proposals = apr_array_make(pool, choices->nelts + 1, sizeof(char *)); + ap_run_protocol_propose(c, r, s, choices, proposals); + + /* If the existing protocol has not been proposed, but is a choice, + * add it to the proposals implicitly. + */ + existing = ap_get_protocol(c); + if (!ap_array_str_contains(proposals, existing) + && ap_array_str_contains(choices, existing)) { + APR_ARRAY_PUSH(proposals, const char*) = existing; + } + + if (proposals->nelts > 0) { + int i; + const apr_array_header_t *prefs = NULL; + + /* Default for protocols_honor_order is 'on' or != 0 */ + if (conf->protocols_honor_order == 0 && choices->nelts > 0) { + prefs = choices; + } + else { + prefs = conf->protocols; + } + + /* Select the most preferred protocol */ + if (APLOGcdebug(c)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03156) + "select protocol, proposals=%s preferences=%s configured=%s", + apr_array_pstrcat(pool, proposals, ','), + apr_array_pstrcat(pool, prefs, ','), + apr_array_pstrcat(pool, conf->protocols, ',')); + } + for (i = 0; i < proposals->nelts; ++i) { + const char *p = APR_ARRAY_IDX(proposals, i, const char *); + if (!ap_array_str_contains(conf->protocols, p)) { + /* not a configured protocol here */ + continue; + } + else if (!protocol + || (protocol_cmp(prefs, protocol, p) < 0)) { + /* none selected yet or this one has preference */ + protocol = p; + } + } + } + if (APLOGcdebug(c)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03157) + "selected protocol=%s", + protocol? protocol : "(none)"); + } + + return protocol; +} + +AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r, + server_rec *s, + const char *protocol) +{ + const char *current = ap_get_protocol(c); + int rc; + + if (!strcmp(current, protocol)) { + ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(02906) + "already at it, protocol_switch to %s", + protocol); + return APR_SUCCESS; + } + + rc = ap_run_protocol_switch(c, r, s, protocol); + switch (rc) { + case DECLINED: + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02907) + "no implementation for protocol_switch to %s", + protocol); + return APR_ENOTIMPL; + case OK: + case DONE: + return APR_SUCCESS; + default: + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02905) + "unexpected return code %d from protocol_switch to %s" + , rc, protocol); + return APR_EOF; + } +} + +AP_DECLARE(int) ap_is_allowed_protocol(conn_rec *c, request_rec *r, + server_rec *s, const char *protocol) +{ + core_server_config *conf; + + if (!s) { + s = (r? r->server : c->base_server); + } + conf = ap_get_core_module_config(s->module_config); + + if (conf->protocols->nelts > 0) { + return ap_array_str_contains(conf->protocols, protocol); + } + return !strcmp(AP_PROTOCOL_HTTP1, protocol); +} + + +AP_IMPLEMENT_HOOK_VOID(pre_read_request, + (request_rec *r, conn_rec *c), + (r, c)) +AP_IMPLEMENT_HOOK_RUN_ALL(int,post_read_request, + (request_rec *r), (r), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int,log_transaction, + (request_rec *r), (r), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,http_scheme, + (const request_rec *r), (r), NULL) +AP_IMPLEMENT_HOOK_RUN_FIRST(unsigned short,default_port, + (const request_rec *r), (r), 0) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, note_auth_failure, + (request_rec *r, const char *auth_type), + (r, auth_type), DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int,protocol_propose, + (conn_rec *c, request_rec *r, server_rec *s, + const apr_array_header_t *offers, + apr_array_header_t *proposals), + (c, r, s, offers, proposals), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,protocol_switch, + (conn_rec *c, request_rec *r, server_rec *s, + const char *protocol), + (c, r, s, protocol), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,protocol_get, + (const conn_rec *c), (c), NULL) diff --git a/server/provider.c b/server/provider.c new file mode 100644 index 0000000..cf307e7 --- /dev/null +++ b/server/provider.c @@ -0,0 +1,197 @@ +/* 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 "apr_pools.h" +#include "apr_hash.h" +#include "apr_tables.h" +#include "apr_strings.h" + +#include "ap_provider.h" + +static apr_hash_t *global_providers = NULL; +static apr_hash_t *global_providers_names = NULL; + + +static apr_status_t cleanup_global_providers(void *ctx) +{ + global_providers = NULL; + global_providers_names = NULL; + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_register_provider(apr_pool_t *pool, + const char *provider_group, + const char *provider_name, + const char *provider_version, + const void *provider) +{ + apr_hash_t *provider_group_hash, *provider_version_hash; + + if (global_providers == NULL) { + global_providers = apr_hash_make(pool); + global_providers_names = apr_hash_make(pool); + apr_pool_cleanup_register(pool, NULL, cleanup_global_providers, + apr_pool_cleanup_null); + } + + /* First, deal with storing the provider away */ + provider_group_hash = apr_hash_get(global_providers, provider_group, + APR_HASH_KEY_STRING); + + if (!provider_group_hash) { + provider_group_hash = apr_hash_make(pool); + apr_hash_set(global_providers, provider_group, APR_HASH_KEY_STRING, + provider_group_hash); + + } + + provider_version_hash = apr_hash_get(provider_group_hash, provider_name, + APR_HASH_KEY_STRING); + + if (!provider_version_hash) { + provider_version_hash = apr_hash_make(pool); + apr_hash_set(provider_group_hash, provider_name, APR_HASH_KEY_STRING, + provider_version_hash); + + } + + /* just set it. no biggy if it was there before. */ + apr_hash_set(provider_version_hash, provider_version, APR_HASH_KEY_STRING, + provider); + + /* Now, tuck away the provider names in an easy-to-get format */ + provider_group_hash = apr_hash_get(global_providers_names, provider_group, + APR_HASH_KEY_STRING); + + if (!provider_group_hash) { + provider_group_hash = apr_hash_make(pool); + apr_hash_set(global_providers_names, provider_group, APR_HASH_KEY_STRING, + provider_group_hash); + + } + + provider_version_hash = apr_hash_get(provider_group_hash, provider_version, + APR_HASH_KEY_STRING); + + if (!provider_version_hash) { + provider_version_hash = apr_hash_make(pool); + apr_hash_set(provider_group_hash, provider_version, APR_HASH_KEY_STRING, + provider_version_hash); + + } + + /* just set it. no biggy if it was there before. */ + apr_hash_set(provider_version_hash, provider_name, APR_HASH_KEY_STRING, + provider_name); + + return APR_SUCCESS; +} + +AP_DECLARE(void *) ap_lookup_provider(const char *provider_group, + const char *provider_name, + const char *provider_version) +{ + apr_hash_t *provider_group_hash, *provider_name_hash; + + if (global_providers == NULL) { + return NULL; + } + + provider_group_hash = apr_hash_get(global_providers, provider_group, + APR_HASH_KEY_STRING); + + if (provider_group_hash == NULL) { + return NULL; + } + + provider_name_hash = apr_hash_get(provider_group_hash, provider_name, + APR_HASH_KEY_STRING); + + if (provider_name_hash == NULL) { + return NULL; + } + + return apr_hash_get(provider_name_hash, provider_version, + APR_HASH_KEY_STRING); +} + +AP_DECLARE(apr_array_header_t *) ap_list_provider_names(apr_pool_t *pool, + const char *provider_group, + const char *provider_version) +{ + apr_array_header_t *ret = apr_array_make(pool, 10, sizeof(ap_list_provider_names_t)); + ap_list_provider_names_t *entry; + apr_hash_t *provider_group_hash, *h; + apr_hash_index_t *hi; + char *val; + + if (global_providers_names == NULL) { + return ret; + } + + provider_group_hash = apr_hash_get(global_providers_names, provider_group, + APR_HASH_KEY_STRING); + + if (provider_group_hash == NULL) { + return ret; + } + + h = apr_hash_get(provider_group_hash, provider_version, + APR_HASH_KEY_STRING); + + if (h == NULL) { + return ret; + } + + for (hi = apr_hash_first(pool, h); hi; hi = apr_hash_next(hi)) { + apr_hash_this(hi, NULL, NULL, (void *)&val); + entry = apr_array_push(ret); + entry->provider_name = apr_pstrdup(pool, val); + } + return ret; +} + +AP_DECLARE(apr_array_header_t *) ap_list_provider_groups(apr_pool_t *pool) +{ + apr_array_header_t *ret = apr_array_make(pool, 10, sizeof(ap_list_provider_groups_t)); + ap_list_provider_groups_t *entry; + apr_hash_t *provider_group_hash; + apr_hash_index_t *groups_hi, *vers_hi; + char *group, *version; + + if (global_providers_names == NULL) + return ret; + + for (groups_hi = apr_hash_first(pool, global_providers_names); + groups_hi; + groups_hi = apr_hash_next(groups_hi)) + { + apr_hash_this(groups_hi, (void *)&group, NULL, (void *)&provider_group_hash); + if (provider_group_hash == NULL) + continue; + for (vers_hi = apr_hash_first(pool, provider_group_hash); + vers_hi; + vers_hi = apr_hash_next(vers_hi)) + { + apr_hash_this(vers_hi, (void *)&version, NULL, NULL); + + entry = apr_array_push(ret); + entry->provider_group = group; + entry->provider_version = version; + } + } + return ret; +} diff --git a/server/request.c b/server/request.c new file mode 100644 index 0000000..dbe3e07 --- /dev/null +++ b/server/request.c @@ -0,0 +1,2511 @@ +/* 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. + */ + +/* + * @file request.c + * @brief functions to get and process requests + * + * @author Rob McCool 3/21/93 + * + * Thoroughly revamped by rst for Apache. NB this file reads + * best from the bottom up. + * + */ + +#include "apr_strings.h" +#include "apr_file_io.h" +#include "apr_fnmatch.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "ap_provider.h" +#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_filter.h" +#include "util_charset.h" +#include "util_script.h" +#include "ap_expr.h" +#include "mod_request.h" + +#include "mod_core.h" +#include "mod_auth.h" + +#if APR_HAVE_STDARG_H +#include <stdarg.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +APR_HOOK_STRUCT( + APR_HOOK_LINK(translate_name) + APR_HOOK_LINK(map_to_storage) + APR_HOOK_LINK(check_user_id) + APR_HOOK_LINK(fixups) + APR_HOOK_LINK(type_checker) + APR_HOOK_LINK(access_checker) + APR_HOOK_LINK(access_checker_ex) + APR_HOOK_LINK(auth_checker) + APR_HOOK_LINK(insert_filter) + APR_HOOK_LINK(create_request) + APR_HOOK_LINK(post_perdir_config) + APR_HOOK_LINK(dirwalk_stat) + APR_HOOK_LINK(force_authn) +) + +AP_IMPLEMENT_HOOK_RUN_FIRST(int,translate_name, + (request_rec *r), (r), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,map_to_storage, + (request_rec *r), (r), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,check_user_id, + (request_rec *r), (r), DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int,fixups, + (request_rec *r), (r), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,type_checker, + (request_rec *r), (r), DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int,access_checker, + (request_rec *r), (r), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,access_checker_ex, + (request_rec *r), (r), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,auth_checker, + (request_rec *r), (r), DECLINED) +AP_IMPLEMENT_HOOK_VOID(insert_filter, (request_rec *r), (r)) +AP_IMPLEMENT_HOOK_RUN_ALL(int, create_request, + (request_rec *r), (r), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int, post_perdir_config, + (request_rec *r), (r), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t,dirwalk_stat, + (apr_finfo_t *finfo, request_rec *r, apr_int32_t wanted), + (finfo, r, wanted), AP_DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,force_authn, + (request_rec *r), (r), DECLINED) + +static int auth_internal_per_conf = 0; +static int auth_internal_per_conf_hooks = 0; +static int auth_internal_per_conf_providers = 0; + + +static int decl_die(int status, const char *phase, request_rec *r) +{ + if (status == DECLINED) { + ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(00025) + "configuration error: couldn't %s: %s", phase, r->uri); + return HTTP_INTERNAL_SERVER_ERROR; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "auth phase '%s' gave status %d: %s", phase, + status, r->uri); + return status; + } +} + +AP_DECLARE(int) ap_some_authn_required(request_rec *r) +{ + int access_status; + char *olduser = r->user; + int rv = FALSE; + + switch (ap_satisfies(r)) { + case SATISFY_ALL: + case SATISFY_NOSPEC: + if ((access_status = ap_run_access_checker(r)) != OK) { + break; + } + + access_status = ap_run_access_checker_ex(r); + if (access_status == DECLINED) { + rv = TRUE; + } + + break; + case SATISFY_ANY: + if ((access_status = ap_run_access_checker(r)) == OK) { + break; + } + + access_status = ap_run_access_checker_ex(r); + if (access_status == DECLINED) { + rv = TRUE; + } + + break; + } + + r->user = olduser; + return rv; +} + +/* This is the master logic for processing requests. Do NOT duplicate + * this logic elsewhere, or the security model will be broken by future + * API changes. Each phase must be individually optimized to pick up + * redundant/duplicate calls by subrequests, and redirects. + */ +AP_DECLARE(int) ap_process_request_internal(request_rec *r) +{ + int file_req = (r->main && r->filename); + int access_status; + core_dir_config *d; + + /* Ignore embedded %2F's in path for proxy requests */ + if (!r->proxyreq && r->parsed_uri.path) { + d = ap_get_core_module_config(r->per_dir_config); + if (d->allow_encoded_slashes) { + access_status = ap_unescape_url_keep2f(r->parsed_uri.path, d->decode_encoded_slashes); + } + else { + access_status = ap_unescape_url(r->parsed_uri.path); + } + if (access_status) { + if (access_status == HTTP_NOT_FOUND) { + if (! d->allow_encoded_slashes) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00026) + "found %%2f (encoded '/') in URI " + "(decoded='%s'), returning 404", + r->parsed_uri.path); + } + } + return access_status; + } + } + + ap_getparents(r->uri); /* OK --- shrinking transformations... */ + + /* All file subrequests are a huge pain... they cannot bubble through the + * next several steps. Only file subrequests are allowed an empty uri, + * otherwise let translate_name kill the request. + */ + if (!file_req) { + if ((access_status = ap_location_walk(r))) { + return access_status; + } + if ((access_status = ap_if_walk(r))) { + return access_status; + } + + d = ap_get_core_module_config(r->per_dir_config); + if (d->log) { + r->log = d->log; + } + + if ((access_status = ap_run_translate_name(r))) { + return decl_die(access_status, "translate", r); + } + } + + /* Reset to the server default config prior to running map_to_storage + */ + r->per_dir_config = r->server->lookup_defaults; + + if ((access_status = ap_run_map_to_storage(r))) { + /* This request wasn't in storage (e.g. TRACE) */ + return access_status; + } + + /* Rerun the location walk, which overrides any map_to_storage config. + */ + if ((access_status = ap_location_walk(r))) { + return access_status; + } + if ((access_status = ap_if_walk(r))) { + return access_status; + } + + d = ap_get_core_module_config(r->per_dir_config); + if (d->log) { + r->log = d->log; + } + + if ((access_status = ap_run_post_perdir_config(r))) { + return access_status; + } + + /* Only on the main request! */ + if (r->main == NULL) { + if ((access_status = ap_run_header_parser(r))) { + return access_status; + } + } + + /* Skip authn/authz if the parent or prior request passed the authn/authz, + * and that configuration didn't change (this requires optimized _walk() + * functions in map_to_storage that use the same merge results given + * identical input.) If the config changes, we must re-auth. + */ + if (r->prev && (r->prev->per_dir_config == r->per_dir_config)) { + r->user = r->prev->user; + r->ap_auth_type = r->prev->ap_auth_type; + } + else if (r->main && (r->main->per_dir_config == r->per_dir_config)) { + r->user = r->main->user; + r->ap_auth_type = r->main->ap_auth_type; + } + else { + /* A module using a confusing API (ap_get_basic_auth_pw) caused + ** r->user to be filled out prior to check_authn hook. We treat + ** it is inadvertent. + */ + if (r->user && apr_table_get(r->notes, AP_GET_BASIC_AUTH_PW_NOTE)) { + r->user = NULL; + } + + switch (ap_satisfies(r)) { + case SATISFY_ALL: + case SATISFY_NOSPEC: + if ((access_status = ap_run_access_checker(r)) != OK) { + return decl_die(access_status, + "check access (with Satisfy All)", r); + } + + access_status = ap_run_access_checker_ex(r); + if (access_status == DECLINED + || (access_status == OK && ap_run_force_authn(r) == OK)) { + if ((access_status = ap_run_check_user_id(r)) != OK) { + return decl_die(access_status, "check user", r); + } + if (r->user == NULL) { + /* don't let buggy authn module crash us in authz */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00027) + "No authentication done but request not " + "allowed without authentication for %s. " + "Authentication not configured?", + r->uri); + access_status = HTTP_INTERNAL_SERVER_ERROR; + return decl_die(access_status, "check user", r); + } + if ((access_status = ap_run_auth_checker(r)) != OK) { + return decl_die(access_status, "check authorization", r); + } + } + else if (access_status == OK) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "request authorized without authentication by " + "access_checker_ex hook: %s", r->uri); + } + else { + return decl_die(access_status, "check access", r); + } + break; + case SATISFY_ANY: + if ((access_status = ap_run_access_checker(r)) == OK) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "request authorized without authentication by " + "access_checker hook and 'Satisfy any': %s", + r->uri); + break; + } + + access_status = ap_run_access_checker_ex(r); + if (access_status == DECLINED + || (access_status == OK && ap_run_force_authn(r) == OK)) { + if ((access_status = ap_run_check_user_id(r)) != OK) { + return decl_die(access_status, "check user", r); + } + if (r->user == NULL) { + /* don't let buggy authn module crash us in authz */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00028) + "No authentication done but request not " + "allowed without authentication for %s. " + "Authentication not configured?", + r->uri); + access_status = HTTP_INTERNAL_SERVER_ERROR; + return decl_die(access_status, "check user", r); + } + if ((access_status = ap_run_auth_checker(r)) != OK) { + return decl_die(access_status, "check authorization", r); + } + } + else if (access_status == OK) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "request authorized without authentication by " + "access_checker_ex hook: %s", r->uri); + } + else { + return decl_die(access_status, "check access", r); + } + break; + } + } + /* XXX Must make certain the ap_run_type_checker short circuits mime + * in mod-proxy for r->proxyreq && r->parsed_uri.scheme + * && !strcmp(r->parsed_uri.scheme, "http") + */ + if ((access_status = ap_run_type_checker(r)) != OK) { + return decl_die(access_status, "find types", r); + } + + if ((access_status = ap_run_fixups(r)) != OK) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "fixups hook gave %d: %s", + access_status, r->uri); + return access_status; + } + + return OK; +} + + +/* Useful caching structures to repeat _walk/merge sequences as required + * when a subrequest or redirect reuses substantially the same config. + * + * Directive order in the httpd.conf file and its Includes significantly + * impact this optimization. Grouping common blocks at the front of the + * config that are less likely to change between a request and + * its subrequests, or between a request and its redirects reduced + * the work of these functions significantly. + */ + +typedef struct walk_walked_t { + ap_conf_vector_t *matched; /* A dir_conf sections we matched */ + ap_conf_vector_t *merged; /* The dir_conf merged result */ +} walk_walked_t; + +typedef struct walk_cache_t { + const char *cached; /* The identifier we matched */ + ap_conf_vector_t **dir_conf_tested; /* The sections we matched against */ + ap_conf_vector_t *dir_conf_merged; /* Base per_dir_config */ + ap_conf_vector_t *per_dir_result; /* per_dir_config += walked result */ + apr_array_header_t *walked; /* The list of walk_walked_t results */ + struct walk_cache_t *prev; /* Prev cache of same call in this (sub)req */ + int count; /* Number of prev invocations of same call in this (sub)req */ +} walk_cache_t; + +static walk_cache_t *prep_walk_cache(apr_size_t t, request_rec *r) +{ + void **note, **inherit_note; + walk_cache_t *cache, *prev_cache, *copy_cache; + int count; + + /* Find the most relevant, recent walk cache to work from and provide + * a copy the caller is allowed to munge. In the case of a sub-request + * or internal redirect, this is the cache corresponding to the equivalent + * invocation of the same function call in the "parent" request, if such + * a cache exists. Otherwise it is the walk cache of the previous + * invocation of the same function call in the current request, if + * that exists; if not, then create a new walk cache. + */ + note = ap_get_request_note(r, t); + AP_DEBUG_ASSERT(note != NULL); + + copy_cache = prev_cache = *note; + count = prev_cache ? (prev_cache->count + 1) : 0; + + if ((r->prev + && (inherit_note = ap_get_request_note(r->prev, t)) + && *inherit_note) + || (r->main + && (inherit_note = ap_get_request_note(r->main, t)) + && *inherit_note)) { + walk_cache_t *inherit_cache = *inherit_note; + + while (inherit_cache->count > count) { + inherit_cache = inherit_cache->prev; + } + if (inherit_cache->count == count) { + copy_cache = inherit_cache; + } + } + + if (copy_cache) { + cache = apr_pmemdup(r->pool, copy_cache, sizeof(*cache)); + cache->walked = apr_array_copy(r->pool, cache->walked); + cache->prev = prev_cache; + cache->count = count; + } + else { + cache = apr_pcalloc(r->pool, sizeof(*cache)); + cache->walked = apr_array_make(r->pool, 4, sizeof(walk_walked_t)); + } + + *note = cache; + + return cache; +} + +/***************************************************************** + * + * Getting and checking directory configuration. Also checks the + * FollowSymlinks and FollowSymOwner stuff, since this is really the + * only place that can happen (barring a new mid_dir_walk callout). + * + * We can't do it as an access_checker module function which gets + * called with the final per_dir_config, since we could have a directory + * with FollowSymLinks disabled, which contains a symlink to another + * with a .htaccess file which turns FollowSymLinks back on --- and + * access in such a case must be denied. So, whatever it is that + * checks FollowSymLinks needs to know the state of the options as + * they change, all the way down. + */ + + +/* + * resolve_symlink must _always_ be called on an APR_LNK file type! + * It will resolve the actual target file type, modification date, etc, + * and provide any processing required for symlink evaluation. + * Path must already be cleaned, no trailing slash, no multi-slashes, + * and don't call this on the root! + * + * Simply, the number of times we deref a symlink are minimal compared + * to the number of times we had an extra lstat() since we 'weren't sure'. + * + * To optimize, we stat() anything when given (opts & OPT_SYM_LINKS), otherwise + * we start off with an lstat(). Every lstat() must be dereferenced in case + * it points at a 'nasty' - we must always rerun check_safe_file (or similar.) + */ +static int resolve_symlink(char *d, apr_finfo_t *lfi, int opts, apr_pool_t *p) +{ + apr_finfo_t fi; + const char *savename; + + if (!(opts & (OPT_SYM_OWNER | OPT_SYM_LINKS))) { + return HTTP_FORBIDDEN; + } + + /* Save the name from the valid bits. */ + savename = (lfi->valid & APR_FINFO_NAME) ? lfi->name : NULL; + + /* if OPT_SYM_OWNER is unset, we only need to check target accessible */ + if (!(opts & OPT_SYM_OWNER)) { + if (apr_stat(&fi, d, lfi->valid & ~(APR_FINFO_NAME | APR_FINFO_LINK), p) + != APR_SUCCESS) + { + return HTTP_FORBIDDEN; + } + + /* Give back the target */ + memcpy(lfi, &fi, sizeof(fi)); + if (savename) { + lfi->name = savename; + lfi->valid |= APR_FINFO_NAME; + } + + return OK; + } + + /* OPT_SYM_OWNER only works if we can get the owner of + * both the file and symlink. First fill in a missing + * owner of the symlink, then get the info of the target. + */ + if (!(lfi->valid & APR_FINFO_OWNER)) { + if (apr_stat(lfi, d, lfi->valid | APR_FINFO_LINK | APR_FINFO_OWNER, p) + != APR_SUCCESS) + { + return HTTP_FORBIDDEN; + } + } + + if (apr_stat(&fi, d, lfi->valid & ~(APR_FINFO_NAME), p) != APR_SUCCESS) { + return HTTP_FORBIDDEN; + } + + if (apr_uid_compare(fi.user, lfi->user) != APR_SUCCESS) { + return HTTP_FORBIDDEN; + } + + /* Give back the target */ + memcpy(lfi, &fi, sizeof(fi)); + if (savename) { + lfi->name = savename; + lfi->valid |= APR_FINFO_NAME; + } + + return OK; +} + + +/* + * As we walk the directory configuration, the merged config won't + * be 'rooted' to a specific vhost until the very end of the merge. + * + * We need a very fast mini-merge to a real, vhost-rooted merge + * of core.opts and core.override, the only options tested within + * directory_walk itself. + * + * See core.c::merge_core_dir_configs() for explanation. + */ + +typedef struct core_opts_t { + allow_options_t opts; + allow_options_t add; + allow_options_t remove; + overrides_t override; + overrides_t override_opts; + apr_table_t *override_list; +} core_opts_t; + +static void core_opts_merge(const ap_conf_vector_t *sec, core_opts_t *opts) +{ + core_dir_config *this_dir = ap_get_core_module_config(sec); + + if (!this_dir) { + return; + } + + if (this_dir->opts & OPT_UNSET) { + opts->add = (opts->add & ~this_dir->opts_remove) + | this_dir->opts_add; + opts->remove = (opts->remove & ~this_dir->opts_add) + | this_dir->opts_remove; + opts->opts = (opts->opts & ~opts->remove) | opts->add; + } + else { + opts->opts = this_dir->opts; + opts->add = this_dir->opts_add; + opts->remove = this_dir->opts_remove; + } + + if (!(this_dir->override & OR_UNSET)) { + opts->override = this_dir->override; + opts->override_opts = this_dir->override_opts; + } + + if (this_dir->override_list != NULL) { + opts->override_list = this_dir->override_list; + } +} + + +/***************************************************************** + * + * Getting and checking directory configuration. Also checks the + * FollowSymlinks and FollowSymOwner stuff, since this is really the + * only place that can happen (barring a new mid_dir_walk callout). + * + * We can't do it as an access_checker module function which gets + * called with the final per_dir_config, since we could have a directory + * with FollowSymLinks disabled, which contains a symlink to another + * with a .htaccess file which turns FollowSymLinks back on --- and + * access in such a case must be denied. So, whatever it is that + * checks FollowSymLinks needs to know the state of the options as + * they change, all the way down. + */ + +AP_DECLARE(int) ap_directory_walk(request_rec *r) +{ + ap_conf_vector_t *now_merged = NULL; + core_server_config *sconf = + ap_get_core_module_config(r->server->module_config); + ap_conf_vector_t **sec_ent = (ap_conf_vector_t **) sconf->sec_dir->elts; + int num_sec = sconf->sec_dir->nelts; + walk_cache_t *cache; + char *entry_dir; + apr_status_t rv; + int cached; + + /* XXX: Better (faster) tests needed!!! + * + * "OK" as a response to a real problem is not _OK_, but to allow broken + * modules to proceed, we will permit the not-a-path filename to pass the + * following two tests. This behavior may be revoked in future versions + * of Apache. We still must catch it later if it's heading for the core + * handler. Leave INFO notes here for module debugging. + */ + if (r->filename == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00029) + "Module bug? Request filename is missing for URI %s", + r->uri); + return OK; + } + + /* Canonicalize the file path without resolving filename case or aliases + * so we can begin by checking the cache for a recent directory walk. + * This call will ensure we have an absolute path in the same pass. + */ + if ((rv = apr_filepath_merge(&entry_dir, NULL, r->filename, + APR_FILEPATH_NOTRELATIVE, r->pool)) + != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00030) + "Module bug? Request filename path %s is invalid or " + "or not absolute for uri %s", + r->filename, r->uri); + return OK; + } + + /* XXX Notice that this forces path_info to be canonical. That might + * not be desired by all apps. However, some of those same apps likely + * have significant security holes. + */ + r->filename = entry_dir; + + cache = prep_walk_cache(AP_NOTE_DIRECTORY_WALK, r); + cached = (cache->cached != NULL); + + /* If this is not a dirent subrequest with a preconstructed + * r->finfo value, then we can simply stat the filename to + * save burning mega-cycles with unneeded stats - if this is + * an exact file match. We don't care about failure... we + * will stat by component failing this meager attempt. + * + * It would be nice to distinguish APR_ENOENT from other + * types of failure, such as APR_ENOTDIR. We can do something + * with APR_ENOENT, knowing that the path is good. + */ + if (r->finfo.filetype == APR_NOFILE || r->finfo.filetype == APR_LNK) { + rv = ap_run_dirwalk_stat(&r->finfo, r, APR_FINFO_MIN); + + /* some OSs will return APR_SUCCESS/APR_REG if we stat + * a regular file but we have '/' at the end of the name; + * + * other OSs will return APR_ENOTDIR for that situation; + * + * handle it the same everywhere by simulating a failure + * if it looks like a directory but really isn't + * + * Also reset if the stat failed, just for safety. + */ + if ((rv != APR_SUCCESS) || + (r->finfo.filetype != APR_NOFILE && + (r->finfo.filetype != APR_DIR) && + (r->filename[strlen(r->filename) - 1] == '/'))) { + r->finfo.filetype = APR_NOFILE; /* forget what we learned */ + } + } + + if (r->finfo.filetype == APR_REG) { + entry_dir = ap_make_dirstr_parent(r->pool, entry_dir); + } + else if (r->filename[strlen(r->filename) - 1] != '/') { + entry_dir = apr_pstrcat(r->pool, r->filename, "/", NULL); + } + + /* If we have a file already matches the path of r->filename, + * and the vhost's list of directory sections hasn't changed, + * we can skip rewalking the directory_walk entries. + */ + if (cached + && ((r->finfo.filetype == APR_REG) + || ((r->finfo.filetype == APR_DIR) + && (!r->path_info || !*r->path_info))) + && (cache->dir_conf_tested == sec_ent) + && (strcmp(entry_dir, cache->cached) == 0)) { + int familiar = 0; + + /* Well this looks really familiar! If our end-result (per_dir_result) + * didn't change, we have absolutely nothing to do :) + * Otherwise (as is the case with most dir_merged/file_merged requests) + * we must merge our dir_conf_merged onto this new r->per_dir_config. + */ + if (r->per_dir_config == cache->per_dir_result) { + familiar = 1; + } + + if (r->per_dir_config == cache->dir_conf_merged) { + r->per_dir_config = cache->per_dir_result; + familiar = 1; + } + + if (familiar) { + apr_finfo_t thisinfo; + int res; + allow_options_t opts; + core_dir_config *this_dir; + + this_dir = ap_get_core_module_config(r->per_dir_config); + opts = this_dir->opts; + /* + * If Symlinks are allowed in general we do not need the following + * check. + */ + if (!(opts & OPT_SYM_LINKS)) { + rv = ap_run_dirwalk_stat(&thisinfo, r, + APR_FINFO_MIN | APR_FINFO_NAME | APR_FINFO_LINK); + /* + * APR_INCOMPLETE is as fine as result as APR_SUCCESS as we + * have added APR_FINFO_NAME to the wanted parameter of + * apr_stat above. On Unix platforms this means that apr_stat + * is always going to return APR_INCOMPLETE in the case that + * the call to the native stat / lstat did not fail. + */ + if ((rv != APR_INCOMPLETE) && (rv != APR_SUCCESS)) { + /* + * This should never happen, because we did a stat on the + * same file, resolving a possible symlink several lines + * above. Therefore do not make a detailed analysis of rv + * in this case for the reason of the failure, just bail out + * with a HTTP_FORBIDDEN in case we hit a race condition + * here. + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00031) + "access to %s failed; stat of '%s' failed.", + r->uri, r->filename); + return r->status = HTTP_FORBIDDEN; + } + if (thisinfo.filetype == APR_LNK) { + /* Is this a possibly acceptable symlink? */ + if ((res = resolve_symlink(r->filename, &thisinfo, + opts, r->pool)) != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00032) + "Symbolic link not allowed " + "or link target not accessible: %s", + r->filename); + return r->status = res; + } + } + } + return OK; + } + + if (cache->walked->nelts) { + now_merged = ((walk_walked_t*)cache->walked->elts) + [cache->walked->nelts - 1].merged; + } + } + else { + /* We start now_merged from NULL since we want to build + * a locations list that can be merged to any vhost. + */ + int sec_idx; + int matches = cache->walked->nelts; + int cached_matches = matches; + walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts; + core_dir_config *this_dir; + core_opts_t opts; + apr_finfo_t thisinfo; + char *save_path_info; + apr_size_t buflen; + char *buf; + unsigned int seg, startseg; + apr_pool_t *rxpool = NULL; + + /* Invariant: from the first time filename_len is set until + * it goes out of scope, filename_len==strlen(r->filename) + */ + apr_size_t filename_len; +#ifdef CASE_BLIND_FILESYSTEM + apr_size_t canonical_len; +#endif + + cached &= auth_internal_per_conf; + + /* + * We must play our own mini-merge game here, for the few + * running dir_config values we care about within dir_walk. + * We didn't start the merge from r->per_dir_config, so we + * accumulate opts and override as we merge, from the globals. + */ + this_dir = ap_get_core_module_config(r->per_dir_config); + opts.opts = this_dir->opts; + opts.add = this_dir->opts_add; + opts.remove = this_dir->opts_remove; + opts.override = this_dir->override; + opts.override_opts = this_dir->override_opts; + opts.override_list = this_dir->override_list; + + /* Set aside path_info to merge back onto path_info later. + * If r->filename is a directory, we must remerge the path_info, + * before we continue! [Directories cannot, by definition, have + * path info. Either the next segment is not-found, or a file.] + * + * r->path_info tracks the unconsumed source path. + * r->filename tracks the path as we process it + */ + if ((r->finfo.filetype == APR_DIR) && r->path_info && *r->path_info) + { + if ((rv = apr_filepath_merge(&r->path_info, r->filename, + r->path_info, + APR_FILEPATH_NOTABOVEROOT, r->pool)) + != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00033) + "dir_walk error, path_info %s is not relative " + "to the filename path %s for uri %s", + r->path_info, r->filename, r->uri); + return HTTP_INTERNAL_SERVER_ERROR; + } + + save_path_info = NULL; + } + else { + save_path_info = r->path_info; + r->path_info = r->filename; + } + +#ifdef CASE_BLIND_FILESYSTEM + + canonical_len = 0; + while (r->canonical_filename && r->canonical_filename[canonical_len] + && (r->canonical_filename[canonical_len] + == r->path_info[canonical_len])) { + ++canonical_len; + } + + while (canonical_len + && ((r->canonical_filename[canonical_len - 1] != '/' + && r->canonical_filename[canonical_len - 1]) + || (r->path_info[canonical_len - 1] != '/' + && r->path_info[canonical_len - 1]))) { + --canonical_len; + } + + /* + * Now build r->filename component by component, starting + * with the root (on Unix, simply "/"). We will make a huge + * assumption here for efficiency, that any canonical path + * already given included a canonical root. + */ + rv = apr_filepath_root((const char **)&r->filename, + (const char **)&r->path_info, + canonical_len ? 0 : APR_FILEPATH_TRUENAME, + r->pool); + filename_len = strlen(r->filename); + + /* + * Bad assumption above? If the root's length is longer + * than the canonical length, then it cannot be trusted as + * a truename. So try again, this time more seriously. + */ + if ((rv == APR_SUCCESS) && canonical_len + && (filename_len > canonical_len)) { + rv = apr_filepath_root((const char **)&r->filename, + (const char **)&r->path_info, + APR_FILEPATH_TRUENAME, r->pool); + filename_len = strlen(r->filename); + canonical_len = 0; + } + +#else /* ndef CASE_BLIND_FILESYSTEM, really this simple for Unix today; */ + + rv = apr_filepath_root((const char **)&r->filename, + (const char **)&r->path_info, + 0, r->pool); + filename_len = strlen(r->filename); + +#endif + + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00034) + "dir_walk error, could not determine the root " + "path of filename %s%s for uri %s", + r->filename, r->path_info, r->uri); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Working space for terminating null and an extra / is required. + */ + buflen = filename_len + strlen(r->path_info) + 2; + buf = apr_palloc(r->pool, buflen); + memcpy(buf, r->filename, filename_len + 1); + r->filename = buf; + thisinfo.valid = APR_FINFO_TYPE; + thisinfo.filetype = APR_DIR; /* It's the root, of course it's a dir */ + + /* + * seg keeps track of which segment we've copied. + * sec_idx keeps track of which section we're on, since sections are + * ordered by number of segments. See core_reorder_directories + * startseg tells us how many segments describe the root path + * e.g. the complete path "//host/foo/" to a UNC share (4) + */ + startseg = seg = ap_count_dirs(r->filename); + sec_idx = 0; + + /* + * Go down the directory hierarchy. Where we have to check for + * symlinks, do so. Where a .htaccess file has permission to + * override anything, try to find one. + */ + do { + int res; + char *seg_name; + char *delim; + int temp_slash=0; + + /* We have no trailing slash, but we sure would appreciate one. + * However, we don't want to append a / our first time through. + */ + if ((seg > startseg) && r->filename[filename_len-1] != '/') { + r->filename[filename_len++] = '/'; + r->filename[filename_len] = 0; + temp_slash=1; + } + + /* Begin *this* level by looking for matching <Directory> sections + * from the server config. + */ + for (; sec_idx < num_sec; ++sec_idx) { + + ap_conf_vector_t *entry_config = sec_ent[sec_idx]; + core_dir_config *entry_core; + entry_core = ap_get_core_module_config(entry_config); + + /* No more possible matches for this many segments? + * We are done when we find relative/regex/longer components. + */ + if (entry_core->r || entry_core->d_components > seg) { + break; + } + + /* We will never skip '0' element components, e.g. plain old + * <Directory >, and <Directory "/"> are classified as zero + * so that Win32/Netware/OS2 etc all pick them up. + * Otherwise, skip over the mismatches. + */ + if (entry_core->d_components + && ((entry_core->d_components < seg) + || (entry_core->d_is_fnmatch + ? (apr_fnmatch(entry_core->d, r->filename, + APR_FNM_PATHNAME) != APR_SUCCESS) + : (strcmp(r->filename, entry_core->d) != 0)))) { + continue; + } + + /* If we haven't continue'd above, we have a match. + * + * Calculate our full-context core opts & override. + */ + core_opts_merge(sec_ent[sec_idx], &opts); + + /* If we merged this same section last time, reuse it + */ + if (matches) { + if (last_walk->matched == sec_ent[sec_idx]) { + now_merged = last_walk->merged; + ++last_walk; + --matches; + continue; + } + + /* We fell out of sync. This is our own copy of walked, + * so truncate the remaining matches and reset remaining. + */ + cache->walked->nelts -= matches; + matches = 0; + cached = 0; + } + + if (now_merged) { + now_merged = ap_merge_per_dir_configs(r->pool, + now_merged, + sec_ent[sec_idx]); + } + else { + now_merged = sec_ent[sec_idx]; + } + + last_walk = (walk_walked_t*)apr_array_push(cache->walked); + last_walk->matched = sec_ent[sec_idx]; + last_walk->merged = now_merged; + } + + /* If .htaccess files are enabled, check for one, provided we + * have reached a real path. + */ + do { /* Not really a loop, just a break'able code block */ + + ap_conf_vector_t *htaccess_conf = NULL; + + /* No htaccess in an incomplete root path, + * nor if it's disabled + */ + if (seg < startseg || (!opts.override + && apr_is_empty_table(opts.override_list) + )) { + break; + } + + + res = ap_parse_htaccess(&htaccess_conf, r, opts.override, + opts.override_opts, opts.override_list, + r->filename, sconf->access_name); + if (res) { + return res; + } + + if (!htaccess_conf) { + break; + } + + /* If we are still here, we found our htaccess. + * + * Calculate our full-context core opts & override. + */ + core_opts_merge(htaccess_conf, &opts); + + /* If we merged this same htaccess last time, reuse it... + * this wouldn't work except that we cache the htaccess + * sections for the lifetime of the request, so we match + * the same conf. Good planning (no, pure luck ;) + */ + if (matches) { + if (last_walk->matched == htaccess_conf) { + now_merged = last_walk->merged; + ++last_walk; + --matches; + break; + } + + /* We fell out of sync. This is our own copy of walked, + * so truncate the remaining matches and reset + * remaining. + */ + cache->walked->nelts -= matches; + matches = 0; + cached = 0; + } + + if (now_merged) { + now_merged = ap_merge_per_dir_configs(r->pool, + now_merged, + htaccess_conf); + } + else { + now_merged = htaccess_conf; + } + + last_walk = (walk_walked_t*)apr_array_push(cache->walked); + last_walk->matched = htaccess_conf; + last_walk->merged = now_merged; + + } while (0); /* Only one htaccess, not a real loop */ + + /* That temporary trailing slash was useful, now drop it. + */ + if (temp_slash) { + r->filename[--filename_len] = '\0'; + } + + /* Time for all good things to come to an end? + */ + if (!r->path_info || !*r->path_info) { + break; + } + + /* Now it's time for the next segment... + * We will assume the next element is an end node, and fix it up + * below as necessary... + */ + + seg_name = r->filename + filename_len; + delim = strchr(r->path_info + (*r->path_info == '/' ? 1 : 0), '/'); + if (delim) { + apr_size_t path_info_len = delim - r->path_info; + *delim = '\0'; + memcpy(seg_name, r->path_info, path_info_len + 1); + filename_len += path_info_len; + r->path_info = delim; + *delim = '/'; + } + else { + apr_size_t path_info_len = strlen(r->path_info); + memcpy(seg_name, r->path_info, path_info_len + 1); + filename_len += path_info_len; + r->path_info += path_info_len; + } + if (*seg_name == '/') + ++seg_name; + + /* If nothing remained but a '/' string, we are finished + * XXX: NO WE ARE NOT!!! Now process this puppy!!! */ + if (!*seg_name) { + break; + } + + /* First optimization; + * If...we knew r->filename was a file, and + * if...we have strict (case-sensitive) filenames, or + * we know the canonical_filename matches to _this_ name, and + * if...we have allowed symlinks + * skip the lstat and dummy up an APR_DIR value for thisinfo. + */ + if (r->finfo.filetype != APR_NOFILE +#ifdef CASE_BLIND_FILESYSTEM + && (filename_len <= canonical_len) +#endif + && ((opts.opts & (OPT_SYM_OWNER | OPT_SYM_LINKS)) == OPT_SYM_LINKS)) + { + + thisinfo.filetype = APR_DIR; + ++seg; + continue; + } + + /* We choose apr_stat with flag APR_FINFO_LINK here, rather that + * plain apr_stat, so that we capture this path object rather than + * its target. We will replace the info with our target's info + * below. We especially want the name of this 'link' object, not + * the name of its target, if we are fixing the filename + * case/resolving aliases. + */ + rv = ap_run_dirwalk_stat(&thisinfo, r, + APR_FINFO_MIN | APR_FINFO_NAME | APR_FINFO_LINK); + + if (APR_STATUS_IS_ENOENT(rv)) { + /* Nothing? That could be nice. But our directory + * walk is done. + */ + thisinfo.filetype = APR_NOFILE; + break; + } + else if (APR_STATUS_IS_EACCES(rv)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00035) + "access to %s denied (filesystem path '%s') " + "because search permissions are missing on a " + "component of the path", r->uri, r->filename); + return r->status = HTTP_FORBIDDEN; + } + else if ((rv != APR_SUCCESS && rv != APR_INCOMPLETE) + || !(thisinfo.valid & APR_FINFO_TYPE)) { + /* If we hit ENOTDIR, we must have over-optimized, deny + * rather than assume not found. + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00036) + "access to %s failed (filesystem path '%s')", + r->uri, r->filename); + return r->status = HTTP_FORBIDDEN; + } + + /* Fix up the path now if we have a name, and they don't agree + */ + if ((thisinfo.valid & APR_FINFO_NAME) + && strcmp(seg_name, thisinfo.name)) { + /* TODO: provide users an option that an internal/external + * redirect is required here? We need to walk the URI and + * filename in tandem to properly correlate these. + */ + strcpy(seg_name, thisinfo.name); + filename_len = strlen(r->filename); + } + + if (thisinfo.filetype == APR_LNK) { + /* Is this a possibly acceptable symlink? + */ + if ((res = resolve_symlink(r->filename, &thisinfo, + opts.opts, r->pool)) != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00037) + "Symbolic link not allowed " + "or link target not accessible: %s", + r->filename); + return r->status = res; + } + } + + /* Ok, we are done with the link's info, test the real target + */ + if (thisinfo.filetype == APR_REG || + thisinfo.filetype == APR_NOFILE) { + /* That was fun, nothing left for us here + */ + break; + } + else if (thisinfo.filetype != APR_DIR) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00038) + "Forbidden: %s doesn't point to " + "a file or directory", + r->filename); + return r->status = HTTP_FORBIDDEN; + } + + ++seg; + } while (thisinfo.filetype == APR_DIR); + + /* If we have _not_ optimized, this is the time to recover + * the final stat result. + */ + if (r->finfo.filetype == APR_NOFILE || r->finfo.filetype == APR_LNK) { + r->finfo = thisinfo; + } + + /* Now splice the saved path_info back onto any new path_info + */ + if (save_path_info) { + if (r->path_info && *r->path_info) { + r->path_info = ap_make_full_path(r->pool, r->path_info, + save_path_info); + } + else { + r->path_info = save_path_info; + } + } + + /* + * Now we'll deal with the regexes, note we pick up sec_idx + * where we left off (we gave up after we hit entry_core->r) + */ + for (; sec_idx < num_sec; ++sec_idx) { + + int nmatch = 0; + int i; + ap_regmatch_t *pmatch = NULL; + + core_dir_config *entry_core; + entry_core = ap_get_core_module_config(sec_ent[sec_idx]); + + if (!entry_core->r) { + continue; + } + + if (entry_core->refs && entry_core->refs->nelts) { + if (!rxpool) { + apr_pool_create(&rxpool, r->pool); + } + nmatch = entry_core->refs->nelts; + pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); + } + + if (ap_regexec(entry_core->r, r->filename, nmatch, pmatch, 0)) { + continue; + } + + for (i = 0; i < nmatch; i++) { + if (pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0 && + ((const char **)entry_core->refs->elts)[i]) { + apr_table_setn(r->subprocess_env, + ((const char **)entry_core->refs->elts)[i], + apr_pstrndup(r->pool, + r->filename + pmatch[i].rm_so, + pmatch[i].rm_eo - pmatch[i].rm_so)); + } + } + + /* If we haven't already continue'd above, we have a match. + * + * Calculate our full-context core opts & override. + */ + core_opts_merge(sec_ent[sec_idx], &opts); + + /* If we merged this same section last time, reuse it + */ + if (matches) { + if (last_walk->matched == sec_ent[sec_idx]) { + now_merged = last_walk->merged; + ++last_walk; + --matches; + continue; + } + + /* We fell out of sync. This is our own copy of walked, + * so truncate the remaining matches and reset remaining. + */ + cache->walked->nelts -= matches; + matches = 0; + cached = 0; + } + + if (now_merged) { + now_merged = ap_merge_per_dir_configs(r->pool, + now_merged, + sec_ent[sec_idx]); + } + else { + now_merged = sec_ent[sec_idx]; + } + + last_walk = (walk_walked_t*)apr_array_push(cache->walked); + last_walk->matched = sec_ent[sec_idx]; + last_walk->merged = now_merged; + } + + if (rxpool) { + apr_pool_destroy(rxpool); + } + + /* Whoops - everything matched in sequence, but either the original + * walk found some additional matches (which we need to truncate), or + * this walk found some additional matches. + */ + if (matches) { + cache->walked->nelts -= matches; + cached = 0; + } + else if (cache->walked->nelts > cached_matches) { + cached = 0; + } + } + +/* It seems this shouldn't be needed anymore. We translated the + x symlink above into a real resource, and should have died up there. + x Even if we keep this, it needs more thought (maybe an r->file_is_symlink) + x perhaps it should actually happen in file_walk, so we catch more + x obscure cases in autoindex subrequests, etc. + x + x * Symlink permissions are determined by the parent. If the request is + x * for a directory then applying the symlink test here would use the + x * permissions of the directory as opposed to its parent. Consider a + x * symlink pointing to a dir with a .htaccess disallowing symlinks. If + x * you access /symlink (or /symlink/) you would get a 403 without this + x * APR_DIR test. But if you accessed /symlink/index.html, for example, + x * you would *not* get the 403. + x + x if (r->finfo.filetype != APR_DIR + x && (res = resolve_symlink(r->filename, r->info, ap_allow_options(r), + x r->pool))) { + x ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + x "Symbolic link not allowed: %s", r->filename); + x return res; + x } + */ + + /* Save future sub-requestors much angst in processing + * this subrequest. If dir_walk couldn't canonicalize + * the file path, nothing can. + */ + r->canonical_filename = r->filename; + + if (r->finfo.filetype == APR_DIR) { + cache->cached = r->filename; + } + else { + cache->cached = ap_make_dirstr_parent(r->pool, r->filename); + } + + if (cached + && r->per_dir_config == cache->dir_conf_merged) { + r->per_dir_config = cache->per_dir_result; + return OK; + } + + cache->dir_conf_tested = sec_ent; + cache->dir_conf_merged = r->per_dir_config; + + /* Merge our cache->dir_conf_merged construct with the r->per_dir_configs, + * and note the end result to (potentially) skip this step next time. + */ + if (now_merged) { + r->per_dir_config = ap_merge_per_dir_configs(r->pool, + r->per_dir_config, + now_merged); + } + cache->per_dir_result = r->per_dir_config; + + return OK; +} + + +AP_DECLARE(int) ap_location_walk(request_rec *r) +{ + ap_conf_vector_t *now_merged = NULL; + core_server_config *sconf = + ap_get_core_module_config(r->server->module_config); + ap_conf_vector_t **sec_ent = (ap_conf_vector_t **)sconf->sec_url->elts; + int num_sec = sconf->sec_url->nelts; + walk_cache_t *cache; + const char *entry_uri; + int cached; + + /* No tricks here, there are no <Locations > to parse in this vhost. + * We won't destroy the cache, just in case _this_ redirect is later + * redirected again to a vhost with <Location > blocks to optimize. + */ + if (!num_sec) { + return OK; + } + + cache = prep_walk_cache(AP_NOTE_LOCATION_WALK, r); + cached = (cache->cached != NULL); + + /* Location and LocationMatch differ on their behaviour w.r.t. multiple + * slashes. Location matches multiple slashes with a single slash, + * LocationMatch doesn't. An exception, for backwards brokenness is + * absoluteURIs... in which case neither match multiple slashes. + */ + if (r->uri[0] != '/') { + entry_uri = r->uri; + } + else { + char *uri = apr_pstrdup(r->pool, r->uri); + ap_no2slash(uri); + entry_uri = uri; + } + + /* If we have an cache->cached location that matches r->uri, + * and the vhost's list of locations hasn't changed, we can skip + * rewalking the location_walk entries. + */ + if (cached + && (cache->dir_conf_tested == sec_ent) + && (strcmp(entry_uri, cache->cached) == 0)) { + /* Well this looks really familiar! If our end-result (per_dir_result) + * didn't change, we have absolutely nothing to do :) + * Otherwise (as is the case with most dir_merged/file_merged requests) + * we must merge our dir_conf_merged onto this new r->per_dir_config. + */ + if (r->per_dir_config == cache->per_dir_result) { + return OK; + } + + if (cache->walked->nelts) { + now_merged = ((walk_walked_t*)cache->walked->elts) + [cache->walked->nelts - 1].merged; + } + } + else { + /* We start now_merged from NULL since we want to build + * a locations list that can be merged to any vhost. + */ + int len, sec_idx; + int matches = cache->walked->nelts; + int cached_matches = matches; + walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts; + apr_pool_t *rxpool = NULL; + + cached &= auth_internal_per_conf; + cache->cached = entry_uri; + + /* Go through the location entries, and check for matches. + * We apply the directive sections in given order, we should + * really try them with the most general first. + */ + for (sec_idx = 0; sec_idx < num_sec; ++sec_idx) { + + core_dir_config *entry_core; + entry_core = ap_get_core_module_config(sec_ent[sec_idx]); + + /* ### const strlen can be optimized in location config parsing */ + len = strlen(entry_core->d); + + /* Test the regex, fnmatch or string as appropriate. + * If it's a strcmp, and the <Location > pattern was + * not slash terminated, then this uri must be slash + * terminated (or at the end of the string) to match. + */ + if (entry_core->r) { + + int nmatch = 0; + int i; + ap_regmatch_t *pmatch = NULL; + + if (entry_core->refs && entry_core->refs->nelts) { + if (!rxpool) { + apr_pool_create(&rxpool, r->pool); + } + nmatch = entry_core->refs->nelts; + pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); + } + + if (ap_regexec(entry_core->r, r->uri, nmatch, pmatch, 0)) { + continue; + } + + for (i = 0; i < nmatch; i++) { + if (pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0 && + ((const char **)entry_core->refs->elts)[i]) { + apr_table_setn(r->subprocess_env, + ((const char **)entry_core->refs->elts)[i], + apr_pstrndup(r->pool, + r->uri + pmatch[i].rm_so, + pmatch[i].rm_eo - pmatch[i].rm_so)); + } + } + + } + else { + + if ((entry_core->d_is_fnmatch + ? apr_fnmatch(entry_core->d, cache->cached, APR_FNM_PATHNAME) + : (strncmp(entry_core->d, cache->cached, len) + || (len > 0 + && entry_core->d[len - 1] != '/' + && cache->cached[len] != '/' + && cache->cached[len] != '\0')))) { + continue; + } + + } + + /* If we merged this same section last time, reuse it + */ + if (matches) { + if (last_walk->matched == sec_ent[sec_idx]) { + now_merged = last_walk->merged; + ++last_walk; + --matches; + continue; + } + + /* We fell out of sync. This is our own copy of walked, + * so truncate the remaining matches and reset remaining. + */ + cache->walked->nelts -= matches; + matches = 0; + cached = 0; + } + + if (now_merged) { + now_merged = ap_merge_per_dir_configs(r->pool, + now_merged, + sec_ent[sec_idx]); + } + else { + now_merged = sec_ent[sec_idx]; + } + + last_walk = (walk_walked_t*)apr_array_push(cache->walked); + last_walk->matched = sec_ent[sec_idx]; + last_walk->merged = now_merged; + } + + if (rxpool) { + apr_pool_destroy(rxpool); + } + + /* Whoops - everything matched in sequence, but either the original + * walk found some additional matches (which we need to truncate), or + * this walk found some additional matches. + */ + if (matches) { + cache->walked->nelts -= matches; + cached = 0; + } + else if (cache->walked->nelts > cached_matches) { + cached = 0; + } + } + + if (cached + && r->per_dir_config == cache->dir_conf_merged) { + r->per_dir_config = cache->per_dir_result; + return OK; + } + + cache->dir_conf_tested = sec_ent; + cache->dir_conf_merged = r->per_dir_config; + + /* Merge our cache->dir_conf_merged construct with the r->per_dir_configs, + * and note the end result to (potentially) skip this step next time. + */ + if (now_merged) { + r->per_dir_config = ap_merge_per_dir_configs(r->pool, + r->per_dir_config, + now_merged); + } + cache->per_dir_result = r->per_dir_config; + + return OK; +} + +AP_DECLARE(int) ap_file_walk(request_rec *r) +{ + ap_conf_vector_t *now_merged = NULL; + core_dir_config *dconf = ap_get_core_module_config(r->per_dir_config); + ap_conf_vector_t **sec_ent = NULL; + int num_sec = 0; + walk_cache_t *cache; + const char *test_file; + int cached; + + if (dconf->sec_file) { + sec_ent = (ap_conf_vector_t **)dconf->sec_file->elts; + num_sec = dconf->sec_file->nelts; + } + + /* To allow broken modules to proceed, we allow missing filenames to pass. + * We will catch it later if it's heading for the core handler. + * directory_walk already posted an INFO note for module debugging. + */ + if (r->filename == NULL) { + return OK; + } + + /* No tricks here, there are just no <Files > to parse in this context. + * We won't destroy the cache, just in case _this_ redirect is later + * redirected again to a context containing the same or similar <Files >. + */ + if (!num_sec) { + return OK; + } + + cache = prep_walk_cache(AP_NOTE_FILE_WALK, r); + cached = (cache->cached != NULL); + + /* Get the basename .. and copy for the cache just + * in case r->filename is munged by another module + */ + test_file = strrchr(r->filename, '/'); + if (test_file == NULL) { + test_file = apr_pstrdup(r->pool, r->filename); + } + else { + test_file = apr_pstrdup(r->pool, ++test_file); + } + + /* If we have an cache->cached file name that matches test_file, + * and the directory's list of file sections hasn't changed, we + * can skip rewalking the file_walk entries. + */ + if (cached + && (cache->dir_conf_tested == sec_ent) + && (strcmp(test_file, cache->cached) == 0)) { + /* Well this looks really familiar! If our end-result (per_dir_result) + * didn't change, we have absolutely nothing to do :) + * Otherwise (as is the case with most dir_merged requests) + * we must merge our dir_conf_merged onto this new r->per_dir_config. + */ + if (r->per_dir_config == cache->per_dir_result) { + return OK; + } + + if (cache->walked->nelts) { + now_merged = ((walk_walked_t*)cache->walked->elts) + [cache->walked->nelts - 1].merged; + } + } + else { + /* We start now_merged from NULL since we want to build + * a file section list that can be merged to any dir_walk. + */ + int sec_idx; + int matches = cache->walked->nelts; + int cached_matches = matches; + walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts; + apr_pool_t *rxpool = NULL; + + cached &= auth_internal_per_conf; + cache->cached = test_file; + + /* Go through the location entries, and check for matches. + * We apply the directive sections in given order, we should + * really try them with the most general first. + */ + for (sec_idx = 0; sec_idx < num_sec; ++sec_idx) { + core_dir_config *entry_core; + entry_core = ap_get_core_module_config(sec_ent[sec_idx]); + + if (entry_core->r) { + + int nmatch = 0; + int i; + ap_regmatch_t *pmatch = NULL; + + if (entry_core->refs && entry_core->refs->nelts) { + if (!rxpool) { + apr_pool_create(&rxpool, r->pool); + } + nmatch = entry_core->refs->nelts; + pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); + } + + if (ap_regexec(entry_core->r, cache->cached, nmatch, pmatch, 0)) { + continue; + } + + for (i = 0; i < nmatch; i++) { + if (pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0 && + ((const char **)entry_core->refs->elts)[i]) { + apr_table_setn(r->subprocess_env, + ((const char **)entry_core->refs->elts)[i], + apr_pstrndup(r->pool, + cache->cached + pmatch[i].rm_so, + pmatch[i].rm_eo - pmatch[i].rm_so)); + } + } + + } + else { + if ((entry_core->d_is_fnmatch + ? apr_fnmatch(entry_core->d, cache->cached, APR_FNM_PATHNAME) + : strcmp(entry_core->d, cache->cached))) { + continue; + } + } + + /* If we merged this same section last time, reuse it + */ + if (matches) { + if (last_walk->matched == sec_ent[sec_idx]) { + now_merged = last_walk->merged; + ++last_walk; + --matches; + continue; + } + + /* We fell out of sync. This is our own copy of walked, + * so truncate the remaining matches and reset remaining. + */ + cache->walked->nelts -= matches; + matches = 0; + cached = 0; + } + + if (now_merged) { + now_merged = ap_merge_per_dir_configs(r->pool, + now_merged, + sec_ent[sec_idx]); + } + else { + now_merged = sec_ent[sec_idx]; + } + + last_walk = (walk_walked_t*)apr_array_push(cache->walked); + last_walk->matched = sec_ent[sec_idx]; + last_walk->merged = now_merged; + } + + if (rxpool) { + apr_pool_destroy(rxpool); + } + + /* Whoops - everything matched in sequence, but either the original + * walk found some additional matches (which we need to truncate), or + * this walk found some additional matches. + */ + if (matches) { + cache->walked->nelts -= matches; + cached = 0; + } + else if (cache->walked->nelts > cached_matches) { + cached = 0; + } + } + + if (cached + && r->per_dir_config == cache->dir_conf_merged) { + r->per_dir_config = cache->per_dir_result; + return OK; + } + + cache->dir_conf_tested = sec_ent; + cache->dir_conf_merged = r->per_dir_config; + + /* Merge our cache->dir_conf_merged construct with the r->per_dir_configs, + * and note the end result to (potentially) skip this step next time. + */ + if (now_merged) { + r->per_dir_config = ap_merge_per_dir_configs(r->pool, + r->per_dir_config, + now_merged); + } + cache->per_dir_result = r->per_dir_config; + + return OK; +} + +static int ap_if_walk_sub(request_rec *r, core_dir_config* dconf) +{ + ap_conf_vector_t *now_merged = NULL; + ap_conf_vector_t **sec_ent = NULL; + int num_sec = 0; + walk_cache_t *cache; + int cached; + int sec_idx; + int matches; + int cached_matches; + int prev_result = -1; + walk_walked_t *last_walk; + + if (dconf && dconf->sec_if) { + sec_ent = (ap_conf_vector_t **)dconf->sec_if->elts; + num_sec = dconf->sec_if->nelts; + } + + /* No tricks here, there are just no <If > to parse in this context. + * We won't destroy the cache, just in case _this_ redirect is later + * redirected again to a context containing the same or similar <If >. + */ + if (!num_sec) { + return OK; + } + + cache = prep_walk_cache(AP_NOTE_IF_WALK, r); + cached = (cache->cached != NULL); + cache->cached = (void *)1; + matches = cache->walked->nelts; + cached_matches = matches; + last_walk = (walk_walked_t*)cache->walked->elts; + + cached &= auth_internal_per_conf; + + /* Go through the if entries, and check for matches */ + for (sec_idx = 0; sec_idx < num_sec; ++sec_idx) { + const char *err = NULL; + core_dir_config *entry_core; + int rc; + entry_core = ap_get_core_module_config(sec_ent[sec_idx]); + + AP_DEBUG_ASSERT(entry_core->condition_ifelse != 0); + if (entry_core->condition_ifelse & AP_CONDITION_ELSE) { + AP_DEBUG_ASSERT(prev_result != -1); + if (prev_result == 1) + continue; + } + + if (entry_core->condition_ifelse & AP_CONDITION_IF) { + rc = ap_expr_exec(r, entry_core->condition, &err); + if (rc <= 0) { + if (rc < 0) + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00039) + "Failed to evaluate <If > condition: %s", + err); + prev_result = 0; + continue; + } + prev_result = 1; + } + else { + prev_result = -1; + } + + /* If we merged this same section last time, reuse it + */ + if (matches) { + if (last_walk->matched == sec_ent[sec_idx]) { + now_merged = last_walk->merged; + ++last_walk; + --matches; + continue; + } + + /* We fell out of sync. This is our own copy of walked, + * so truncate the remaining matches and reset remaining. + */ + cache->walked->nelts -= matches; + matches = 0; + cached = 0; + } + + if (now_merged) { + now_merged = ap_merge_per_dir_configs(r->pool, + now_merged, + sec_ent[sec_idx]); + } + else { + now_merged = sec_ent[sec_idx]; + } + + last_walk = (walk_walked_t*)apr_array_push(cache->walked); + last_walk->matched = sec_ent[sec_idx]; + last_walk->merged = now_merged; + } + + /* Everything matched in sequence, but it may be that the original + * walk found some additional matches (which we need to truncate), or + * this walk found some additional matches. + */ + if (matches) { + cache->walked->nelts -= matches; + cached = 0; + } + else if (cache->walked->nelts > cached_matches) { + cached = 0; + } + + if (cached + && r->per_dir_config == cache->dir_conf_merged) { + r->per_dir_config = cache->per_dir_result; + return OK; + } + + cache->dir_conf_tested = sec_ent; + cache->dir_conf_merged = r->per_dir_config; + + /* Merge our cache->dir_conf_merged construct with the r->per_dir_configs, + * and note the end result to (potentially) skip this step next time. + */ + if (now_merged) { + r->per_dir_config = ap_merge_per_dir_configs(r->pool, + r->per_dir_config, + now_merged); + } + cache->per_dir_result = r->per_dir_config; + + if (now_merged) { + core_dir_config *dconf_merged = ap_get_core_module_config(now_merged); + + /* Allow nested <If>s and their configs to get merged + * with the current one. + */ + return ap_if_walk_sub(r, dconf_merged); + } + + return OK; +} + +AP_DECLARE(int) ap_if_walk(request_rec *r) +{ + core_dir_config *dconf = ap_get_core_module_config(r->per_dir_config); + int status = ap_if_walk_sub(r, dconf); + return status; +} + +/***************************************************************** + * + * The sub_request mechanism. + * + * Fns to look up a relative URI from, e.g., a map file or SSI document. + * These do all access checks, etc., but don't actually run the transaction + * ... use run_sub_req below for that. Also, be sure to use destroy_sub_req + * as appropriate if you're likely to be creating more than a few of these. + * (An early Apache version didn't destroy the sub_reqs used in directory + * indexing. The result, when indexing a directory with 800-odd files in + * it, was massively excessive storage allocation). + * + * Note more manipulation of protocol-specific vars in the request + * structure... + */ + +static request_rec *make_sub_request(const request_rec *r, + ap_filter_t *next_filter) +{ + apr_pool_t *rrp; + request_rec *rnew; + + apr_pool_create(&rrp, r->pool); + apr_pool_tag(rrp, "subrequest"); + rnew = apr_pcalloc(rrp, sizeof(request_rec)); + rnew->pool = rrp; + + rnew->hostname = r->hostname; + rnew->request_time = r->request_time; + rnew->connection = r->connection; + rnew->server = r->server; + rnew->log = r->log; + + rnew->request_config = ap_create_request_config(rnew->pool); + + /* Start a clean config from this subrequest's vhost. Optimization in + * Location/File/Dir walks from the parent request assure that if the + * config blocks of the subrequest match the parent request, no merges + * will actually occur (and generally a minimal number of merges are + * required, even if the parent and subrequest aren't quite identical.) + */ + rnew->per_dir_config = r->server->lookup_defaults; + + rnew->htaccess = r->htaccess; + rnew->allowed_methods = ap_make_method_list(rnew->pool, 2); + + /* make a copy of the allowed-methods list */ + ap_copy_method_list(rnew->allowed_methods, r->allowed_methods); + + /* start with the same set of output filters */ + if (next_filter) { + ap_filter_t *scan = next_filter; + + /* while there are no input filters for a subrequest, we will + * try to insert some, so if we don't have valid data, the code + * will seg fault. + */ + rnew->input_filters = r->input_filters; + rnew->proto_input_filters = r->proto_input_filters; + rnew->output_filters = next_filter; + rnew->proto_output_filters = r->proto_output_filters; + while (scan && (scan != r->proto_output_filters)) { + if (scan->frec == ap_subreq_core_filter_handle) { + break; + } + scan = scan->next; + } + if (!scan || scan == r->proto_output_filters) { + ap_add_output_filter_handle(ap_subreq_core_filter_handle, + NULL, rnew, rnew->connection); + } + } + else { + /* If NULL - we are expecting to be internal_fast_redirect'ed + * to this subrequest - or this request will never be invoked. + * Ignore the original request filter stack entirely, and + * drill the input and output stacks back to the connection. + */ + rnew->proto_input_filters = r->proto_input_filters; + rnew->proto_output_filters = r->proto_output_filters; + + rnew->input_filters = r->proto_input_filters; + rnew->output_filters = r->proto_output_filters; + } + + rnew->useragent_addr = r->useragent_addr; + rnew->useragent_ip = r->useragent_ip; + + /* no input filters for a subrequest */ + + ap_set_sub_req_protocol(rnew, r); + + /* We have to run this after we fill in sub req vars, + * or the r->main pointer won't be setup + */ + ap_run_create_request(rnew); + + /* Begin by presuming any module can make its own path_info assumptions, + * until some module interjects and changes the value. + */ + rnew->used_path_info = AP_REQ_DEFAULT_PATH_INFO; + + /* Pass on the kept body (if any) into the new request. */ + rnew->kept_body = r->kept_body; + + return rnew; +} + +AP_CORE_DECLARE_NONSTD(apr_status_t) ap_sub_req_output_filter(ap_filter_t *f, + apr_bucket_brigade *bb) +{ + apr_bucket *e = APR_BRIGADE_LAST(bb); + + if (APR_BUCKET_IS_EOS(e)) { + apr_bucket_delete(e); + } + + if (!APR_BRIGADE_EMPTY(bb)) { + return ap_pass_brigade(f->next, bb); + } + + return APR_SUCCESS; +} + +extern APR_OPTIONAL_FN_TYPE(authz_some_auth_required) *ap__authz_ap_some_auth_required; + +AP_DECLARE(int) ap_some_auth_required(request_rec *r) +{ + /* Is there a require line configured for the type of *this* req? */ + if (ap__authz_ap_some_auth_required) { + return ap__authz_ap_some_auth_required(r); + } + else + return 0; +} + +AP_DECLARE(void) ap_clear_auth_internal(void) +{ + auth_internal_per_conf_hooks = 0; + auth_internal_per_conf_providers = 0; +} + +AP_DECLARE(void) ap_setup_auth_internal(apr_pool_t *ptemp) +{ + int total_auth_hooks = 0; + int total_auth_providers = 0; + + auth_internal_per_conf = 0; + + if (_hooks.link_access_checker) { + total_auth_hooks += _hooks.link_access_checker->nelts; + } + if (_hooks.link_access_checker_ex) { + total_auth_hooks += _hooks.link_access_checker_ex->nelts; + } + if (_hooks.link_check_user_id) { + total_auth_hooks += _hooks.link_check_user_id->nelts; + } + if (_hooks.link_auth_checker) { + total_auth_hooks += _hooks.link_auth_checker->nelts; + } + + if (total_auth_hooks > auth_internal_per_conf_hooks) { + return; + } + + total_auth_providers += + ap_list_provider_names(ptemp, AUTHN_PROVIDER_GROUP, + AUTHN_PROVIDER_VERSION)->nelts; + total_auth_providers += + ap_list_provider_names(ptemp, AUTHZ_PROVIDER_GROUP, + AUTHZ_PROVIDER_VERSION)->nelts; + + if (total_auth_providers > auth_internal_per_conf_providers) { + return; + } + + auth_internal_per_conf = 1; +} + +AP_DECLARE(apr_status_t) ap_register_auth_provider(apr_pool_t *pool, + const char *provider_group, + const char *provider_name, + const char *provider_version, + const void *provider, + int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_providers; + } + + return ap_register_provider(pool, provider_group, provider_name, + provider_version, provider); +} + +AP_DECLARE(void) ap_hook_check_access(ap_HOOK_access_checker_t *pf, + const char * const *aszPre, + const char * const *aszSucc, + int nOrder, int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_hooks; + } + + ap_hook_access_checker(pf, aszPre, aszSucc, nOrder); +} + +AP_DECLARE(void) ap_hook_check_access_ex(ap_HOOK_access_checker_ex_t *pf, + const char * const *aszPre, + const char * const *aszSucc, + int nOrder, int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_hooks; + } + + ap_hook_access_checker_ex(pf, aszPre, aszSucc, nOrder); +} + +AP_DECLARE(void) ap_hook_check_authn(ap_HOOK_check_user_id_t *pf, + const char * const *aszPre, + const char * const *aszSucc, + int nOrder, int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_hooks; + } + + ap_hook_check_user_id(pf, aszPre, aszSucc, nOrder); +} + +AP_DECLARE(void) ap_hook_check_authz(ap_HOOK_auth_checker_t *pf, + const char * const *aszPre, + const char * const *aszSucc, + int nOrder, int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_hooks; + } + + ap_hook_auth_checker(pf, aszPre, aszSucc, nOrder); +} + +AP_DECLARE(request_rec *) ap_sub_req_method_uri(const char *method, + const char *new_uri, + const request_rec *r, + ap_filter_t *next_filter) +{ + request_rec *rnew; + /* Initialise res, to avoid a gcc warning */ + int res = HTTP_INTERNAL_SERVER_ERROR; + char *udir; + + rnew = make_sub_request(r, next_filter); + + /* would be nicer to pass "method" to ap_set_sub_req_protocol */ + rnew->method = method; + rnew->method_number = ap_method_number_of(method); + + if (new_uri[0] == '/') { + ap_parse_uri(rnew, new_uri); + } + else { + udir = ap_make_dirstr_parent(rnew->pool, r->uri); + udir = ap_escape_uri(rnew->pool, udir); /* re-escape it */ + ap_parse_uri(rnew, ap_make_full_path(rnew->pool, udir, new_uri)); + } + + /* We cannot return NULL without violating the API. So just turn this + * subrequest into a 500 to indicate the failure. */ + if (ap_is_recursion_limit_exceeded(r)) { + rnew->status = HTTP_INTERNAL_SERVER_ERROR; + return rnew; + } + + /* lookup_uri + * If the content can be served by the quick_handler, we can + * safely bypass request_internal processing. + * + * If next_filter is NULL we are expecting to be + * internal_fast_redirect'ed to the subrequest, or the subrequest will + * never be invoked. We need to make sure that the quickhandler is not + * invoked by any lookups. Since an internal_fast_redirect will always + * occur too late for the quickhandler to handle the request. + */ + if (next_filter) { + res = ap_run_quick_handler(rnew, 1); + } + + if (next_filter == NULL || res != OK) { + if ((res = ap_process_request_internal(rnew))) { + rnew->status = res; + } + } + + return rnew; +} + +AP_DECLARE(request_rec *) ap_sub_req_lookup_uri(const char *new_uri, + const request_rec *r, + ap_filter_t *next_filter) +{ + return ap_sub_req_method_uri("GET", new_uri, r, next_filter); +} + +AP_DECLARE(request_rec *) ap_sub_req_lookup_dirent(const apr_finfo_t *dirent, + const request_rec *r, + int subtype, + ap_filter_t *next_filter) +{ + request_rec *rnew; + int res; + char *fdir; + char *udir; + + rnew = make_sub_request(r, next_filter); + + /* Special case: we are looking at a relative lookup in the same directory. + * This is 100% safe, since dirent->name just came from the filesystem. + */ + if (r->path_info && *r->path_info) { + /* strip path_info off the end of the uri to keep it in sync + * with r->filename, which has already been stripped by directory_walk, + * merge the dirent->name, and then, if the caller wants us to remerge + * the original path info, do so. Note we never fix the path_info back + * to r->filename, since dir_walk would do so (but we don't expect it + * to happen in the usual cases) + */ + udir = apr_pstrdup(rnew->pool, r->uri); + udir[ap_find_path_info(udir, r->path_info)] = '\0'; + udir = ap_make_dirstr_parent(rnew->pool, udir); + + rnew->uri = ap_make_full_path(rnew->pool, udir, dirent->name); + if (subtype == AP_SUBREQ_MERGE_ARGS) { + rnew->uri = ap_make_full_path(rnew->pool, rnew->uri, r->path_info + 1); + rnew->path_info = apr_pstrdup(rnew->pool, r->path_info); + } + rnew->uri = ap_escape_uri(rnew->pool, rnew->uri); + } + else { + udir = ap_make_dirstr_parent(rnew->pool, r->uri); + rnew->uri = ap_escape_uri(rnew->pool, ap_make_full_path(rnew->pool, + udir, + dirent->name)); + } + + fdir = ap_make_dirstr_parent(rnew->pool, r->filename); + rnew->filename = ap_make_full_path(rnew->pool, fdir, dirent->name); + if (r->canonical_filename == r->filename) { + rnew->canonical_filename = rnew->filename; + } + + /* XXX This is now less relevant; we will do a full location walk + * these days for this case. Preserve the apr_stat results, and + * perhaps we also tag that symlinks were tested and/or found for + * r->filename. + */ + rnew->per_dir_config = r->server->lookup_defaults; + + if ((dirent->valid & APR_FINFO_MIN) != APR_FINFO_MIN) { + /* + * apr_dir_read isn't very complete on this platform, so + * we need another apr_stat (with or without APR_FINFO_LINK + * depending on whether we allow all symlinks here.) If this + * is an APR_LNK that resolves to an APR_DIR, then we will rerun + * everything anyways... this should be safe. + */ + apr_status_t rv; + if (ap_allow_options(rnew) & OPT_SYM_LINKS) { + if (((rv = apr_stat(&rnew->finfo, rnew->filename, + APR_FINFO_MIN, rnew->pool)) != APR_SUCCESS) + && (rv != APR_INCOMPLETE)) { + rnew->finfo.filetype = APR_NOFILE; + } + } + else { + if (((rv = apr_stat(&rnew->finfo, rnew->filename, + APR_FINFO_LINK | APR_FINFO_MIN, + rnew->pool)) != APR_SUCCESS) + && (rv != APR_INCOMPLETE)) { + rnew->finfo.filetype = APR_NOFILE; + } + } + } + else { + memcpy(&rnew->finfo, dirent, sizeof(apr_finfo_t)); + } + + if (rnew->finfo.filetype == APR_LNK) { + /* + * Resolve this symlink. We should tie this back to dir_walk's cache + */ + if ((res = resolve_symlink(rnew->filename, &rnew->finfo, + ap_allow_options(rnew), rnew->pool)) + != OK) { + rnew->status = res; + return rnew; + } + } + + if (rnew->finfo.filetype == APR_DIR) { + /* ap_make_full_path overallocated the buffers + * by one character to help us out here. + */ + strcat(rnew->filename, "/"); + if (!rnew->path_info || !*rnew->path_info) { + strcat(rnew->uri, "/"); + } + } + + /* fill in parsed_uri values + */ + if (r->args && *r->args && (subtype == AP_SUBREQ_MERGE_ARGS)) { + ap_parse_uri(rnew, apr_pstrcat(r->pool, rnew->uri, "?", + r->args, NULL)); + } + else { + ap_parse_uri(rnew, rnew->uri); + } + + /* We cannot return NULL without violating the API. So just turn this + * subrequest into a 500. */ + if (ap_is_recursion_limit_exceeded(r)) { + rnew->status = HTTP_INTERNAL_SERVER_ERROR; + return rnew; + } + + if ((res = ap_process_request_internal(rnew))) { + rnew->status = res; + } + + return rnew; +} + +AP_DECLARE(request_rec *) ap_sub_req_lookup_file(const char *new_file, + const request_rec *r, + ap_filter_t *next_filter) +{ + request_rec *rnew; + int res; + char *fdir; + apr_size_t fdirlen; + + rnew = make_sub_request(r, next_filter); + + fdir = ap_make_dirstr_parent(rnew->pool, r->filename); + fdirlen = strlen(fdir); + + /* Translate r->filename, if it was canonical, it stays canonical + */ + if (r->canonical_filename == r->filename) { + rnew->canonical_filename = (char*)(1); + } + + if (apr_filepath_merge(&rnew->filename, fdir, new_file, + APR_FILEPATH_TRUENAME, rnew->pool) != APR_SUCCESS) { + rnew->status = HTTP_FORBIDDEN; + return rnew; + } + + if (rnew->canonical_filename) { + rnew->canonical_filename = rnew->filename; + } + + /* + * Check for a special case... if there are no '/' characters in new_file + * at all, and the path was the same, then we are looking at a relative + * lookup in the same directory. Fixup the URI to match. + */ + + if (strncmp(rnew->filename, fdir, fdirlen) == 0 + && rnew->filename[fdirlen] + && ap_strchr_c(rnew->filename + fdirlen, '/') == NULL) { + apr_status_t rv; + if (ap_allow_options(rnew) & OPT_SYM_LINKS) { + if (((rv = apr_stat(&rnew->finfo, rnew->filename, + APR_FINFO_MIN, rnew->pool)) != APR_SUCCESS) + && (rv != APR_INCOMPLETE)) { + rnew->finfo.filetype = APR_NOFILE; + } + } + else { + if (((rv = apr_stat(&rnew->finfo, rnew->filename, + APR_FINFO_LINK | APR_FINFO_MIN, + rnew->pool)) != APR_SUCCESS) + && (rv != APR_INCOMPLETE)) { + rnew->finfo.filetype = APR_NOFILE; + } + } + + if (r->uri && *r->uri) { + char *udir = ap_make_dirstr_parent(rnew->pool, r->uri); + rnew->uri = ap_make_full_path(rnew->pool, udir, + rnew->filename + fdirlen); + ap_parse_uri(rnew, rnew->uri); /* fill in parsed_uri values */ + } + else { + ap_parse_uri(rnew, new_file); /* fill in parsed_uri values */ + rnew->uri = apr_pstrdup(rnew->pool, ""); + } + } + else { + /* XXX: @@@: What should be done with the parsed_uri values? + * We would be better off stripping down to the 'common' elements + * of the path, then reassembling the URI as best as we can. + */ + ap_parse_uri(rnew, new_file); /* fill in parsed_uri values */ + /* + * XXX: this should be set properly like it is in the same-dir case + * but it's actually sometimes to impossible to do it... because the + * file may not have a uri associated with it -djg + */ + rnew->uri = apr_pstrdup(rnew->pool, ""); + } + + /* We cannot return NULL without violating the API. So just turn this + * subrequest into a 500. */ + if (ap_is_recursion_limit_exceeded(r)) { + rnew->status = HTTP_INTERNAL_SERVER_ERROR; + return rnew; + } + + if ((res = ap_process_request_internal(rnew))) { + rnew->status = res; + } + + return rnew; +} + +AP_DECLARE(int) ap_run_sub_req(request_rec *r) +{ + int retval = DECLINED; + /* Run the quick handler if the subrequest is not a dirent or file + * subrequest + */ + if (!(r->filename && r->finfo.filetype != APR_NOFILE)) { + retval = ap_run_quick_handler(r, 0); + } + if (retval != OK) { + retval = ap_invoke_handler(r); + if (retval == DONE) { + retval = OK; + } + } + ap_finalize_sub_req_protocol(r); + return retval; +} + +AP_DECLARE(void) ap_destroy_sub_req(request_rec *r) +{ + /* Reclaim the space */ + apr_pool_destroy(r->pool); +} + +/* + * Function to set the r->mtime field to the specified value if it's later + * than what's already there. + */ +AP_DECLARE(void) ap_update_mtime(request_rec *r, apr_time_t dependency_mtime) +{ + if (r->mtime < dependency_mtime) { + r->mtime = dependency_mtime; + } +} + +/* + * Is it the initial main request, which we only get *once* per HTTP request? + */ +AP_DECLARE(int) ap_is_initial_req(request_rec *r) +{ + return (r->main == NULL) /* otherwise, this is a sub-request */ + && (r->prev == NULL); /* otherwise, this is an internal redirect */ +} + diff --git a/server/scoreboard.c b/server/scoreboard.c new file mode 100644 index 0000000..23e3d70 --- /dev/null +++ b/server/scoreboard.c @@ -0,0 +1,701 @@ +/* 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 "apr.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_lib.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "http_log.h" +#include "http_main.h" +#include "http_core.h" +#include "http_config.h" +#include "http_protocol.h" +#include "ap_mpm.h" + +#include "scoreboard.h" + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +AP_DECLARE_DATA scoreboard *ap_scoreboard_image = NULL; +AP_DECLARE_DATA const char *ap_scoreboard_fname = NULL; +static ap_scoreboard_e scoreboard_type; + +const char * ap_set_scoreboard(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_scoreboard_fname = arg; + return NULL; +} + +/* Default to false when mod_status is not loaded */ +AP_DECLARE_DATA int ap_extended_status = 0; + +const char *ap_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; +} + +AP_DECLARE_DATA int ap_mod_status_reqtail = 0; + +const char *ap_set_reqtail(cmd_parms *cmd, void *dummy, int arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + ap_mod_status_reqtail = arg; + return NULL; +} + +#if APR_HAS_SHARED_MEMORY + +#include "apr_shm.h" + +#ifndef WIN32 +static /* but must be exported to mpm_winnt */ +#endif + apr_shm_t *ap_scoreboard_shm = NULL; + +#endif + +APR_HOOK_STRUCT( + APR_HOOK_LINK(pre_mpm) +) + +AP_IMPLEMENT_HOOK_RUN_ALL(int,pre_mpm, + (apr_pool_t *p, ap_scoreboard_e sb_type), + (p, sb_type),OK,DECLINED) + +static APR_OPTIONAL_FN_TYPE(ap_logio_get_last_bytes) + *pfn_ap_logio_get_last_bytes; + +struct ap_sb_handle_t { + int child_num; + int thread_num; +}; + +static int server_limit, thread_limit; +static apr_size_t scoreboard_size; + +/* + * ToDo: + * This function should be renamed to cleanup_shared + * and it should handle cleaning up a scoreboard shared + * between processes using any form of IPC (file, shared memory + * segment, etc.). Leave it as is now because it is being used + * by various MPMs. + */ +static apr_status_t ap_cleanup_shared_mem(void *d) +{ +#if APR_HAS_SHARED_MEMORY + free(ap_scoreboard_image); + ap_scoreboard_image = NULL; + apr_shm_destroy(ap_scoreboard_shm); +#endif + return APR_SUCCESS; +} + +#define SIZE_OF_scoreboard APR_ALIGN_DEFAULT(sizeof(scoreboard)) +#define SIZE_OF_global_score APR_ALIGN_DEFAULT(sizeof(global_score)) +#define SIZE_OF_process_score APR_ALIGN_DEFAULT(sizeof(process_score)) +#define SIZE_OF_worker_score APR_ALIGN_DEFAULT(sizeof(worker_score)) + +AP_DECLARE(int) ap_calc_scoreboard_size(void) +{ + ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); + ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit); + + scoreboard_size = SIZE_OF_global_score; + scoreboard_size += SIZE_OF_process_score * server_limit; + scoreboard_size += SIZE_OF_worker_score * server_limit * thread_limit; + + return scoreboard_size; +} + +AP_DECLARE(void) ap_init_scoreboard(void *shared_score) +{ + char *more_storage; + int i; + + pfn_ap_logio_get_last_bytes = APR_RETRIEVE_OPTIONAL_FN(ap_logio_get_last_bytes); + if (!shared_score) { + return; + } + + ap_calc_scoreboard_size(); + ap_scoreboard_image = + ap_calloc(1, SIZE_OF_scoreboard + server_limit * sizeof(worker_score *)); + more_storage = shared_score; + ap_scoreboard_image->global = (global_score *)more_storage; + more_storage += SIZE_OF_global_score; + ap_scoreboard_image->parent = (process_score *)more_storage; + more_storage += SIZE_OF_process_score * server_limit; + ap_scoreboard_image->servers = + (worker_score **)((char*)ap_scoreboard_image + SIZE_OF_scoreboard); + for (i = 0; i < server_limit; i++) { + ap_scoreboard_image->servers[i] = (worker_score *)more_storage; + more_storage += thread_limit * SIZE_OF_worker_score; + } + ap_assert(more_storage == (char*)shared_score + scoreboard_size); + ap_scoreboard_image->global->server_limit = server_limit; + ap_scoreboard_image->global->thread_limit = thread_limit; +} + +/** + * Create a name-based scoreboard in the given pool using the + * given filename. + */ +static apr_status_t create_namebased_scoreboard(apr_pool_t *pool, + const char *fname) +{ +#if APR_HAS_SHARED_MEMORY + apr_status_t rv; + + /* The shared memory file must not exist before we create the + * segment. */ + apr_shm_remove(fname, pool); /* ignore errors */ + + rv = apr_shm_create(&ap_scoreboard_shm, scoreboard_size, fname, pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00001) + "unable to create or access scoreboard \"%s\" " + "(name-based shared memory failure)", fname); + return rv; + } +#endif /* APR_HAS_SHARED_MEMORY */ + return APR_SUCCESS; +} + +/* ToDo: This function should be made to handle setting up + * a scoreboard shared between processes using any IPC technique, + * not just a shared memory segment + */ +static apr_status_t open_scoreboard(apr_pool_t *pconf) +{ +#if APR_HAS_SHARED_MEMORY + apr_status_t rv; + char *fname = NULL; + apr_pool_t *global_pool; + + /* We don't want to have to recreate the scoreboard after + * restarts, so we'll create a global pool and never clean it. + */ + rv = apr_pool_create(&global_pool, NULL); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00002) + "Fatal error: unable to create global pool " + "for use by the scoreboard"); + return rv; + } + + /* The config says to create a name-based shmem */ + if (ap_scoreboard_fname) { + /* make sure it's an absolute pathname */ + fname = ap_server_root_relative(pconf, ap_scoreboard_fname); + if (!fname) { + ap_log_error(APLOG_MARK, APLOG_CRIT, APR_EBADPATH, ap_server_conf, APLOGNO(00003) + "Fatal error: Invalid Scoreboard path %s", + ap_scoreboard_fname); + return APR_EBADPATH; + } + return create_namebased_scoreboard(global_pool, fname); + } + else { /* config didn't specify, we get to choose shmem type */ + rv = apr_shm_create(&ap_scoreboard_shm, scoreboard_size, NULL, + global_pool); /* anonymous shared memory */ + if ((rv != APR_SUCCESS) && (rv != APR_ENOTIMPL)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00004) + "Unable to create or access scoreboard " + "(anonymous shared memory failure)"); + return rv; + } + /* Make up a filename and do name-based shmem */ + else if (rv == APR_ENOTIMPL) { + /* Make sure it's an absolute pathname */ + ap_scoreboard_fname = DEFAULT_SCOREBOARD; + fname = ap_server_root_relative(pconf, ap_scoreboard_fname); + + return create_namebased_scoreboard(global_pool, fname); + } + } +#endif /* APR_HAS_SHARED_MEMORY */ + return APR_SUCCESS; +} + +/* If detach is non-zero, this is a separate child process, + * if zero, it is a forked child. + */ +AP_DECLARE(apr_status_t) ap_reopen_scoreboard(apr_pool_t *p, apr_shm_t **shm, + int detached) +{ +#if APR_HAS_SHARED_MEMORY + if (!detached) { + return APR_SUCCESS; + } + if (apr_shm_size_get(ap_scoreboard_shm) < scoreboard_size) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00005) + "Fatal error: shared scoreboard too small for child!"); + apr_shm_detach(ap_scoreboard_shm); + ap_scoreboard_shm = NULL; + return APR_EINVAL; + } + /* everything will be cleared shortly */ + if (*shm) { + *shm = ap_scoreboard_shm; + } +#endif + return APR_SUCCESS; +} + +apr_status_t ap_cleanup_scoreboard(void *d) +{ + if (ap_scoreboard_image == NULL) { + return APR_SUCCESS; + } + if (scoreboard_type == SB_SHARED) { + ap_cleanup_shared_mem(NULL); + } + else { + free(ap_scoreboard_image->global); + free(ap_scoreboard_image); + ap_scoreboard_image = NULL; + } + return APR_SUCCESS; +} + +/* Create or reinit an existing scoreboard. The MPM can control whether + * the scoreboard is shared across multiple processes or not + */ +int ap_create_scoreboard(apr_pool_t *p, ap_scoreboard_e sb_type) +{ + int i; +#if APR_HAS_SHARED_MEMORY + apr_status_t rv; +#endif + + if (ap_scoreboard_image) { + ap_scoreboard_image->global->restart_time = apr_time_now(); + memset(ap_scoreboard_image->parent, 0, + SIZE_OF_process_score * server_limit); + for (i = 0; i < server_limit; i++) { + memset(ap_scoreboard_image->servers[i], 0, + SIZE_OF_worker_score * thread_limit); + } + ap_init_scoreboard(NULL); + return OK; + } + + ap_calc_scoreboard_size(); +#if APR_HAS_SHARED_MEMORY + if (sb_type == SB_SHARED) { + void *sb_shared; + rv = open_scoreboard(p); + if (rv || !(sb_shared = apr_shm_baseaddr_get(ap_scoreboard_shm))) { + return HTTP_INTERNAL_SERVER_ERROR; + } + memset(sb_shared, 0, scoreboard_size); + ap_init_scoreboard(sb_shared); + } + else +#endif + { + /* A simple malloc will suffice */ + void *sb_mem = ap_calloc(1, scoreboard_size); + ap_init_scoreboard(sb_mem); + } + + scoreboard_type = sb_type; + ap_scoreboard_image->global->running_generation = 0; + ap_scoreboard_image->global->restart_time = apr_time_now(); + + apr_pool_cleanup_register(p, NULL, ap_cleanup_scoreboard, apr_pool_cleanup_null); + + return OK; +} + +/* Routines called to deal with the scoreboard image + * --- note that we do *not* need write locks, since update_child_status + * only updates a *single* record in place, and only one process writes to + * a given scoreboard slot at a time (either the child process owning that + * slot, or the parent, noting that the child has died). + * + * As a final note --- setting the score entry to getpid() is always safe, + * since when the parent is writing an entry, it's only noting SERVER_DEAD + * anyway. + */ + +AP_DECLARE(int) ap_exists_scoreboard_image(void) +{ + return (ap_scoreboard_image ? 1 : 0); +} + +AP_DECLARE(void) ap_increment_counts(ap_sb_handle_t *sb, request_rec *r) +{ + worker_score *ws; + apr_off_t bytes; + + if (!sb) + return; + + ws = &ap_scoreboard_image->servers[sb->child_num][sb->thread_num]; + if (pfn_ap_logio_get_last_bytes != NULL) { + bytes = pfn_ap_logio_get_last_bytes(r->connection); + } + else if (r->method_number == M_GET && r->method[0] == 'H') { + bytes = 0; + } + else { + bytes = r->bytes_sent; + } + +#ifdef HAVE_TIMES + times(&ws->times); +#endif + ws->access_count++; + ws->my_access_count++; + ws->conn_count++; + ws->bytes_served += bytes; + ws->my_bytes_served += bytes; + ws->conn_bytes += bytes; +} + +AP_DECLARE(int) ap_find_child_by_pid(apr_proc_t *pid) +{ + int i; + int max_daemons_limit = 0; + + ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons_limit); + + for (i = 0; i < max_daemons_limit; ++i) { + if (ap_scoreboard_image->parent[i].pid == pid->pid) { + return i; + } + } + + return -1; +} + +AP_DECLARE(void) ap_update_sb_handle(ap_sb_handle_t *sbh, + int child_num, int thread_num) +{ + sbh->child_num = child_num; + sbh->thread_num = thread_num; +} + +AP_DECLARE(void) ap_create_sb_handle(ap_sb_handle_t **new_sbh, apr_pool_t *p, + int child_num, int thread_num) +{ + *new_sbh = (ap_sb_handle_t *)apr_palloc(p, sizeof(ap_sb_handle_t)); + ap_update_sb_handle(*new_sbh, child_num, thread_num); +} + +static void copy_request(char *rbuf, apr_size_t rbuflen, request_rec *r) +{ + char *p; + + if (r->the_request == NULL) { + apr_cpystrn(rbuf, "NULL", rbuflen); + return; /* short circuit below */ + } + + if (r->parsed_uri.password == NULL) { + p = r->the_request; + } + else { + /* Don't reveal the password in the server-status view */ + p = apr_pstrcat(r->pool, r->method, " ", + apr_uri_unparse(r->pool, &r->parsed_uri, + APR_URI_UNP_OMITPASSWORD), + r->assbackwards ? NULL : " ", r->protocol, NULL); + } + + /* now figure out if we copy over the 1st rbuflen chars or the last */ + if (!ap_mod_status_reqtail) { + apr_cpystrn(rbuf, p, rbuflen); + } + else { + apr_size_t slen = strlen(p); + if (slen < rbuflen) { + /* it all fits anyway */ + apr_cpystrn(rbuf, p, rbuflen); + } + else { + apr_cpystrn(rbuf, p+(slen-rbuflen+1), rbuflen); + } + } +} + +static int update_child_status_internal(int child_num, + int thread_num, + int status, + conn_rec *c, + server_rec *s, + request_rec *r, + const char *descr) +{ + int old_status; + worker_score *ws; + int mpm_generation; + + ws = &ap_scoreboard_image->servers[child_num][thread_num]; + old_status = ws->status; + ws->status = status; + + if (status == SERVER_READY + && old_status == SERVER_STARTING) { + process_score *ps = &ap_scoreboard_image->parent[child_num]; + ws->thread_num = child_num * thread_limit + thread_num; + ap_mpm_query(AP_MPMQ_GENERATION, &mpm_generation); + ps->generation = mpm_generation; + } + + if (ap_extended_status) { + const char *val; + + if (status == SERVER_READY || status == SERVER_DEAD) { + /* + * Reset individual counters + */ + if (status == SERVER_DEAD) { + ws->my_access_count = 0L; + ws->my_bytes_served = 0L; +#ifdef HAVE_TIMES + ws->times.tms_utime = 0; + ws->times.tms_stime = 0; + ws->times.tms_cutime = 0; + ws->times.tms_cstime = 0; +#endif + } + ws->conn_count = 0; + ws->conn_bytes = 0; + ws->last_used = apr_time_now(); + } + + if (descr) { + apr_cpystrn(ws->request, descr, sizeof(ws->request)); + } + else if (r) { + copy_request(ws->request, sizeof(ws->request), r); + } + else if (c) { + ws->request[0]='\0'; + } + + if (r && r->useragent_ip) { + if (!(val = ap_get_useragent_host(r, REMOTE_NOLOOKUP, NULL))) { + apr_cpystrn(ws->client, r->useragent_ip, sizeof(ws->client)); /* DEPRECATE */ + apr_cpystrn(ws->client64, r->useragent_ip, sizeof(ws->client64)); + } + else { + apr_cpystrn(ws->client, val, sizeof(ws->client)); /* DEPRECATE */ + apr_cpystrn(ws->client64, val, sizeof(ws->client64)); + } + } + else if (c) { + if (!(val = ap_get_remote_host(c, c->base_server->lookup_defaults, + REMOTE_NOLOOKUP, NULL))) { + apr_cpystrn(ws->client, c->client_ip, sizeof(ws->client)); /* DEPRECATE */ + apr_cpystrn(ws->client64, c->client_ip, sizeof(ws->client64)); + } + else { + apr_cpystrn(ws->client, val, sizeof(ws->client)); /* DEPRECATE */ + apr_cpystrn(ws->client64, val, sizeof(ws->client64)); + } + } + + if (s) { + if (c) { + apr_snprintf(ws->vhost, sizeof(ws->vhost), "%s:%d", + s->server_hostname, c->local_addr->port); + } + else { + apr_cpystrn(ws->vhost, s->server_hostname, sizeof(ws->vhost)); + } + } + else if (c) { + ws->vhost[0]='\0'; + } + + if (c) { + val = ap_get_protocol(c); + apr_cpystrn(ws->protocol, val, sizeof(ws->protocol)); + } + } + + return old_status; +} + +AP_DECLARE(int) ap_update_child_status_from_indexes(int child_num, + int thread_num, + int status, + request_rec *r) +{ + if (child_num < 0) { + return -1; + } + + return update_child_status_internal(child_num, thread_num, status, + r ? r->connection : NULL, + r ? r->server : NULL, + r, NULL); +} + +AP_DECLARE(int) ap_update_child_status(ap_sb_handle_t *sbh, int status, + request_rec *r) +{ + if (!sbh || (sbh->child_num < 0)) + return -1; + + return update_child_status_internal(sbh->child_num, sbh->thread_num, + status, + r ? r->connection : NULL, + r ? r->server : NULL, + r, NULL); +} + +AP_DECLARE(int) ap_update_child_status_from_conn(ap_sb_handle_t *sbh, int status, + conn_rec *c) +{ + if (!sbh || (sbh->child_num < 0)) + return -1; + + return update_child_status_internal(sbh->child_num, sbh->thread_num, + status, c, NULL, NULL, NULL); +} + +AP_DECLARE(int) ap_update_child_status_from_server(ap_sb_handle_t *sbh, int status, + conn_rec *c, server_rec *s) +{ + if (!sbh || (sbh->child_num < 0)) + return -1; + + return update_child_status_internal(sbh->child_num, sbh->thread_num, + status, c, s, NULL, NULL); +} + +AP_DECLARE(int) ap_update_child_status_descr(ap_sb_handle_t *sbh, int status, const char *descr) +{ + if (!sbh || (sbh->child_num < 0)) + return -1; + + return update_child_status_internal(sbh->child_num, sbh->thread_num, + status, NULL, NULL, NULL, descr); +} + +AP_DECLARE(void) ap_time_process_request(ap_sb_handle_t *sbh, int status) +{ + worker_score *ws; + + if (!sbh) + return; + + if (sbh->child_num < 0) { + return; + } + + ws = &ap_scoreboard_image->servers[sbh->child_num][sbh->thread_num]; + + if (status == START_PREQUEST) { + ws->start_time = ws->last_used = apr_time_now(); + } + else if (status == STOP_PREQUEST) { + ws->stop_time = ws->last_used = apr_time_now(); + if (ap_extended_status) { + ws->duration += ws->stop_time - ws->start_time; + } + } +} + +AP_DECLARE(int) ap_update_global_status() +{ +#ifdef HAVE_TIMES + if (ap_scoreboard_image == NULL) { + return APR_SUCCESS; + } + times(&ap_scoreboard_image->global->times); +#endif + return APR_SUCCESS; +} + +AP_DECLARE(worker_score *) ap_get_scoreboard_worker_from_indexes(int x, int y) +{ + if (((x < 0) || (x >= server_limit)) || + ((y < 0) || (y >= thread_limit))) { + return(NULL); /* Out of range */ + } + return &ap_scoreboard_image->servers[x][y]; +} + +AP_DECLARE(worker_score *) ap_get_scoreboard_worker(ap_sb_handle_t *sbh) +{ + if (!sbh) + return NULL; + + return ap_get_scoreboard_worker_from_indexes(sbh->child_num, + sbh->thread_num); +} + +AP_DECLARE(void) ap_copy_scoreboard_worker(worker_score *dest, + int child_num, + int thread_num) +{ + worker_score *ws = ap_get_scoreboard_worker_from_indexes(child_num, thread_num); + + memcpy(dest, ws, sizeof *ws); + + /* For extra safety, NUL-terminate the strings returned, though it + * should be true those last bytes are always zero anyway. */ + dest->client[sizeof(dest->client) - 1] = '\0'; + dest->client64[sizeof(dest->client64) - 1] = '\0'; + dest->request[sizeof(dest->request) - 1] = '\0'; + dest->vhost[sizeof(dest->vhost) - 1] = '\0'; + dest->protocol[sizeof(dest->protocol) - 1] = '\0'; +} + +AP_DECLARE(process_score *) ap_get_scoreboard_process(int x) +{ + if ((x < 0) || (x >= server_limit)) { + return(NULL); /* Out of range */ + } + return &ap_scoreboard_image->parent[x]; +} + +AP_DECLARE(global_score *) ap_get_scoreboard_global() +{ + return ap_scoreboard_image->global; +} diff --git a/server/util.c b/server/util.c new file mode 100644 index 0000000..fd7a0a1 --- /dev/null +++ b/server/util.c @@ -0,0 +1,3309 @@ +/* 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. + */ + +/* + * util.c: string utility things + * + * 3/21/93 Rob McCool + * 1995-96 Many changes by the Apache Software Foundation + * + */ + +/* Debugging aid: + * #define DEBUG to trace all cfg_open*()/cfg_closefile() calls + * #define DEBUG_CFG_LINES to trace every line read from the config files + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_lib.h" + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_PROCESS_H +#include <process.h> /* for getpid() on Win32 */ +#endif +#if APR_HAVE_NETDB_H +#include <netdb.h> /* for gethostbyname() */ +#endif + +#include "ap_config.h" +#include "apr_base64.h" +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_config.h" +#include "http_core.h" +#include "util_ebcdic.h" +#include "util_varbuf.h" + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#ifdef HAVE_SYS_LOADAVG_H +#include <sys/loadavg.h> +#endif + +#include "ap_mpm.h" + +/* A bunch of functions in util.c scan strings looking for certain characters. + * To make that more efficient we encode a lookup table. The test_char_table + * is generated automatically by gen_test_char.c. + */ +#include "test_char.h" + +/* we assume the folks using this ensure 0 <= c < 256... which means + * you need a cast to (unsigned char) first, you can't just plug a + * char in here and get it to work, because if char is signed then it + * will first be sign extended. + */ +#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f)) + +/* Win32/NetWare/OS2 need to check for both forward and back slashes + * in ap_getparents() and ap_escape_url. + */ +#ifdef CASE_BLIND_FILESYSTEM +#define IS_SLASH(s) ((s == '/') || (s == '\\')) +#define SLASHES "/\\" +#else +#define IS_SLASH(s) (s == '/') +#define SLASHES "/" +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +/* + * Examine a field value (such as a media-/content-type) string and return + * it sans any parameters; e.g., strip off any ';charset=foo' and the like. + */ +AP_DECLARE(char *) ap_field_noparam(apr_pool_t *p, const char *intype) +{ + const char *semi; + + if (intype == NULL) return NULL; + + semi = ap_strchr_c(intype, ';'); + if (semi == NULL) { + return apr_pstrdup(p, intype); + } + else { + while ((semi > intype) && apr_isspace(semi[-1])) { + semi--; + } + return apr_pstrmemdup(p, intype, semi - intype); + } +} + +AP_DECLARE(char *) ap_ht_time(apr_pool_t *p, apr_time_t t, const char *fmt, + int gmt) +{ + apr_size_t retcode; + char ts[MAX_STRING_LEN]; + char tf[MAX_STRING_LEN]; + apr_time_exp_t xt; + + if (gmt) { + const char *f; + char *strp; + + apr_time_exp_gmt(&xt, t); + /* Convert %Z to "GMT" and %z to "+0000"; + * on hosts that do not have a time zone string in struct tm, + * strftime must assume its argument is local time. + */ + for(strp = tf, f = fmt; strp < tf + sizeof(tf) - 6 && (*strp = *f) + ; f++, strp++) { + if (*f != '%') continue; + switch (f[1]) { + case '%': + *++strp = *++f; + break; + case 'Z': + *strp++ = 'G'; + *strp++ = 'M'; + *strp = 'T'; + f++; + break; + case 'z': /* common extension */ + *strp++ = '+'; + *strp++ = '0'; + *strp++ = '0'; + *strp++ = '0'; + *strp = '0'; + f++; + break; + } + } + *strp = '\0'; + fmt = tf; + } + else { + apr_time_exp_lt(&xt, t); + } + + /* check return code? */ + apr_strftime(ts, &retcode, MAX_STRING_LEN, fmt, &xt); + ts[MAX_STRING_LEN - 1] = '\0'; + return apr_pstrdup(p, ts); +} + +/* Roy owes Rob beer. */ +/* Rob owes Roy dinner. */ + +/* These legacy comments would make a lot more sense if Roy hadn't + * replaced the old later_than() routine with util_date.c. + * + * Well, okay, they still wouldn't make any sense. + */ + +/* Match = 0, NoMatch = 1, Abort = -1 + * Based loosely on sections of wildmat.c by Rich Salz + * Hmmm... shouldn't this really go component by component? + */ +AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected) +{ + int x, y; + + for (x = 0, y = 0; expected[y]; ++y, ++x) { + if ((!str[x]) && (expected[y] != '*')) + return -1; + if (expected[y] == '*') { + while (expected[++y] == '*'); + if (!expected[y]) + return 0; + while (str[x]) { + int ret; + if ((ret = ap_strcmp_match(&str[x++], &expected[y])) != 1) + return ret; + } + return -1; + } + else if ((expected[y] != '?') && (str[x] != expected[y])) + return 1; + } + return (str[x] != '\0'); +} + +AP_DECLARE(int) ap_strcasecmp_match(const char *str, const char *expected) +{ + int x, y; + + for (x = 0, y = 0; expected[y]; ++y, ++x) { + if (!str[x] && expected[y] != '*') + return -1; + if (expected[y] == '*') { + while (expected[++y] == '*'); + if (!expected[y]) + return 0; + while (str[x]) { + int ret; + if ((ret = ap_strcasecmp_match(&str[x++], &expected[y])) != 1) + return ret; + } + return -1; + } + else if (expected[y] != '?' + && apr_tolower(str[x]) != apr_tolower(expected[y])) + return 1; + } + return (str[x] != '\0'); +} + +/* We actually compare the canonical root to this root, (but we don't + * waste time checking the case), since every use of this function in + * httpd-2.1 tests if the path is 'proper', meaning we've already passed + * it through apr_filepath_merge, or we haven't. + */ +AP_DECLARE(int) ap_os_is_path_absolute(apr_pool_t *p, const char *dir) +{ + const char *newpath; + const char *ourdir = dir; + if (apr_filepath_root(&newpath, &dir, 0, p) != APR_SUCCESS + || strncmp(newpath, ourdir, strlen(newpath)) != 0) { + return 0; + } + return 1; +} + +AP_DECLARE(int) ap_is_matchexp(const char *str) +{ + int x; + + for (x = 0; str[x]; x++) + if ((str[x] == '*') || (str[x] == '?')) + return 1; + return 0; +} + +/* + * Here's a pool-based interface to the POSIX-esque ap_regcomp(). + * Note that we return ap_regex_t instead of being passed one. + * The reason is that if you use an already-used ap_regex_t structure, + * the memory that you've already allocated gets forgotten, and + * regfree() doesn't clear it. So we don't allow it. + */ + +static apr_status_t regex_cleanup(void *preg) +{ + ap_regfree((ap_regex_t *) preg); + return APR_SUCCESS; +} + +AP_DECLARE(ap_regex_t *) ap_pregcomp(apr_pool_t *p, const char *pattern, + int cflags) +{ + ap_regex_t *preg = apr_palloc(p, sizeof *preg); + int err = ap_regcomp(preg, pattern, cflags); + if (err) { + if (err == AP_REG_ESPACE) + ap_abort_on_oom(); + return NULL; + } + + apr_pool_cleanup_register(p, (void *) preg, regex_cleanup, + apr_pool_cleanup_null); + + return preg; +} + +AP_DECLARE(void) ap_pregfree(apr_pool_t *p, ap_regex_t *reg) +{ + ap_regfree(reg); + apr_pool_cleanup_kill(p, (void *) reg, regex_cleanup); +} + +/* + * Similar to standard strstr() but we ignore case in this version. + * Based on the strstr() implementation further below. + */ +AP_DECLARE(char *) ap_strcasestr(const char *s1, const char *s2) +{ + char *p1, *p2; + if (*s2 == '\0') { + /* an empty s2 */ + return((char *)s1); + } + while(1) { + for ( ; (*s1 != '\0') && (apr_tolower(*s1) != apr_tolower(*s2)); s1++); + if (*s1 == '\0') { + return(NULL); + } + /* found first character of s2, see if the rest matches */ + p1 = (char *)s1; + p2 = (char *)s2; + for (++p1, ++p2; apr_tolower(*p1) == apr_tolower(*p2); ++p1, ++p2) { + if (*p1 == '\0') { + /* both strings ended together */ + return((char *)s1); + } + } + if (*p2 == '\0') { + /* second string ended, a match */ + break; + } + /* didn't find a match here, try starting at next character in s1 */ + s1++; + } + return((char *)s1); +} + +/* + * Returns an offsetted pointer in bigstring immediately after + * prefix. Returns bigstring if bigstring doesn't start with + * prefix or if prefix is longer than bigstring while still matching. + * NOTE: pointer returned is relative to bigstring, so we + * can use standard pointer comparisons in the calling function + * (eg: test if ap_stripprefix(a,b) == a) + */ +AP_DECLARE(const char *) ap_stripprefix(const char *bigstring, + const char *prefix) +{ + const char *p1; + + if (*prefix == '\0') + return bigstring; + + p1 = bigstring; + while (*p1 && *prefix) { + if (*p1++ != *prefix++) + return bigstring; + } + if (*prefix == '\0') + return p1; + + /* hit the end of bigstring! */ + return bigstring; +} + +/* This function substitutes for $0-$9, filling in regular expression + * submatches. Pass it the same nmatch and pmatch arguments that you + * passed ap_regexec(). pmatch should not be greater than the maximum number + * of subexpressions - i.e. one more than the re_nsub member of ap_regex_t. + * + * nmatch must be <=AP_MAX_REG_MATCH (10). + * + * input should be the string with the $-expressions, source should be the + * string that was matched against. + * + * It returns the substituted string, or NULL if a vbuf is used. + * On errors, returns the orig string. + * + * Parts of this code are based on Henry Spencer's regsub(), from his + * AT&T V8 regexp package. + */ + +static apr_status_t regsub_core(apr_pool_t *p, char **result, + struct ap_varbuf *vb, const char *input, + const char *source, apr_size_t nmatch, + ap_regmatch_t pmatch[], apr_size_t maxlen) +{ + const char *src = input; + char *dst; + char c; + apr_size_t no; + apr_size_t len = 0; + + AP_DEBUG_ASSERT((result && p && !vb) || (vb && !p && !result)); + if (!source || nmatch>AP_MAX_REG_MATCH) + return APR_EINVAL; + if (!nmatch) { + len = strlen(src); + if (maxlen > 0 && len >= maxlen) + return APR_ENOMEM; + if (!vb) { + *result = apr_pstrmemdup(p, src, len); + return APR_SUCCESS; + } + else { + ap_varbuf_strmemcat(vb, src, len); + return APR_SUCCESS; + } + } + + /* First pass, find the size */ + while ((c = *src++) != '\0') { + if (c == '$' && apr_isdigit(*src)) + no = *src++ - '0'; + else + no = AP_MAX_REG_MATCH; + + if (no >= AP_MAX_REG_MATCH) { /* Ordinary character. */ + if (c == '\\' && *src) + src++; + len++; + } + else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) { + if (APR_SIZE_MAX - len <= pmatch[no].rm_eo - pmatch[no].rm_so) + return APR_ENOMEM; + len += pmatch[no].rm_eo - pmatch[no].rm_so; + } + + } + + if (len >= maxlen && maxlen > 0) + return APR_ENOMEM; + + if (!vb) { + *result = dst = apr_palloc(p, len + 1); + } + else { + if (vb->strlen == AP_VARBUF_UNKNOWN) + vb->strlen = strlen(vb->buf); + ap_varbuf_grow(vb, vb->strlen + len); + dst = vb->buf + vb->strlen; + vb->strlen += len; + } + + /* Now actually fill in the string */ + + src = input; + + while ((c = *src++) != '\0') { + if (c == '$' && apr_isdigit(*src)) + no = *src++ - '0'; + else + no = AP_MAX_REG_MATCH; + + if (no >= AP_MAX_REG_MATCH) { /* Ordinary character. */ + if (c == '\\' && *src) + c = *src++; + *dst++ = c; + } + else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) { + len = pmatch[no].rm_eo - pmatch[no].rm_so; + memcpy(dst, source + pmatch[no].rm_so, len); + dst += len; + } + + } + *dst = '\0'; + + return APR_SUCCESS; +} + +#ifndef AP_PREGSUB_MAXLEN +#define AP_PREGSUB_MAXLEN (HUGE_STRING_LEN * 8) +#endif +AP_DECLARE(char *) ap_pregsub(apr_pool_t *p, const char *input, + const char *source, apr_size_t nmatch, + ap_regmatch_t pmatch[]) +{ + char *result; + apr_status_t rc = regsub_core(p, &result, NULL, input, source, nmatch, + pmatch, AP_PREGSUB_MAXLEN); + if (rc != APR_SUCCESS) + result = NULL; + return result; +} + +AP_DECLARE(apr_status_t) ap_pregsub_ex(apr_pool_t *p, char **result, + const char *input, const char *source, + apr_size_t nmatch, ap_regmatch_t pmatch[], + apr_size_t maxlen) +{ + apr_status_t rc = regsub_core(p, result, NULL, input, source, nmatch, + pmatch, maxlen); + if (rc != APR_SUCCESS) + *result = NULL; + return rc; +} + +/* + * Parse .. so we don't compromise security + */ +AP_DECLARE(void) ap_getparents(char *name) +{ + char *next; + int l, w, first_dot; + + /* Four paseses, as per RFC 1808 */ + /* a) remove ./ path segments */ + for (next = name; *next && (*next != '.'); next++) { + } + + l = w = first_dot = next - name; + while (name[l] != '\0') { + if (name[l] == '.' && IS_SLASH(name[l + 1]) + && (l == 0 || IS_SLASH(name[l - 1]))) + l += 2; + else + name[w++] = name[l++]; + } + + /* b) remove trailing . path, segment */ + if (w == 1 && name[0] == '.') + w--; + else if (w > 1 && name[w - 1] == '.' && IS_SLASH(name[w - 2])) + w--; + name[w] = '\0'; + + /* c) remove all xx/../ segments. (including leading ../ and /../) */ + l = first_dot; + + while (name[l] != '\0') { + if (name[l] == '.' && name[l + 1] == '.' && IS_SLASH(name[l + 2]) + && (l == 0 || IS_SLASH(name[l - 1]))) { + int m = l + 3, n; + + l = l - 2; + if (l >= 0) { + while (l >= 0 && !IS_SLASH(name[l])) + l--; + l++; + } + else + l = 0; + n = l; + while ((name[n] = name[m])) + (++n, ++m); + } + else + ++l; + } + + /* d) remove trailing xx/.. segment. */ + if (l == 2 && name[0] == '.' && name[1] == '.') + name[0] = '\0'; + else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.' + && IS_SLASH(name[l - 3])) { + l = l - 4; + if (l >= 0) { + while (l >= 0 && !IS_SLASH(name[l])) + l--; + l++; + } + else + l = 0; + name[l] = '\0'; + } +} + +AP_DECLARE(void) ap_no2slash(char *name) +{ + char *d, *s; + + s = d = name; + +#ifdef HAVE_UNC_PATHS + /* Check for UNC names. Leave leading two slashes. */ + if (s[0] == '/' && s[1] == '/') + *d++ = *s++; +#endif + + while (*s) { + if ((*d++ = *s) == '/') { + do { + ++s; + } while (*s == '/'); + } + else { + ++s; + } + } + *d = '\0'; +} + + +/* + * copy at most n leading directories of s into d + * d should be at least as large as s plus 1 extra byte + * assumes n > 0 + * the return value is the ever useful pointer to the trailing \0 of d + * + * MODIFIED FOR HAVE_DRIVE_LETTERS and NETWARE environments, + * so that if n == 0, "/" is returned in d with n == 1 + * and s == "e:/test.html", "e:/" is returned in d + * *** See also ap_directory_walk in server/request.c + * + * examples: + * /a/b, 0 ==> / (true for all platforms) + * /a/b, 1 ==> / + * /a/b, 2 ==> /a/ + * /a/b, 3 ==> /a/b/ + * /a/b, 4 ==> /a/b/ + * + * c:/a/b 0 ==> / + * c:/a/b 1 ==> c:/ + * c:/a/b 2 ==> c:/a/ + * c:/a/b 3 ==> c:/a/b + * c:/a/b 4 ==> c:/a/b + */ +AP_DECLARE(char *) ap_make_dirstr_prefix(char *d, const char *s, int n) +{ + if (n < 1) { + *d = '/'; + *++d = '\0'; + return (d); + } + + for (;;) { + if (*s == '\0' || (*s == '/' && (--n) == 0)) { + *d = '/'; + break; + } + *d++ = *s++; + } + *++d = 0; + return (d); +} + + +/* + * return the parent directory name including trailing / of the file s + */ +AP_DECLARE(char *) ap_make_dirstr_parent(apr_pool_t *p, const char *s) +{ + const char *last_slash = ap_strrchr_c(s, '/'); + char *d; + int l; + + if (last_slash == NULL) { + return apr_pstrdup(p, ""); + } + l = (last_slash - s) + 1; + d = apr_pstrmemdup(p, s, l); + + return (d); +} + + +AP_DECLARE(int) ap_count_dirs(const char *path) +{ + int x, n; + + for (x = 0, n = 0; path[x]; x++) + if (path[x] == '/') + n++; + return n; +} + +AP_DECLARE(char *) ap_getword_nc(apr_pool_t *atrans, char **line, char stop) +{ + return ap_getword(atrans, (const char **) line, stop); +} + +AP_DECLARE(char *) ap_getword(apr_pool_t *atrans, const char **line, char stop) +{ + const char *pos = *line; + int len; + char *res; + + while ((*pos != stop) && *pos) { + ++pos; + } + + len = pos - *line; + res = apr_pstrmemdup(atrans, *line, len); + + if (stop) { + while (*pos == stop) { + ++pos; + } + } + *line = pos; + + return res; +} + +AP_DECLARE(char *) ap_getword_white_nc(apr_pool_t *atrans, char **line) +{ + return ap_getword_white(atrans, (const char **) line); +} + +AP_DECLARE(char *) ap_getword_white(apr_pool_t *atrans, const char **line) +{ + const char *pos = *line; + int len; + char *res; + + while (!apr_isspace(*pos) && *pos) { + ++pos; + } + + len = pos - *line; + res = apr_pstrmemdup(atrans, *line, len); + + while (apr_isspace(*pos)) { + ++pos; + } + + *line = pos; + + return res; +} + +AP_DECLARE(char *) ap_getword_nulls_nc(apr_pool_t *atrans, char **line, + char stop) +{ + return ap_getword_nulls(atrans, (const char **) line, stop); +} + +AP_DECLARE(char *) ap_getword_nulls(apr_pool_t *atrans, const char **line, + char stop) +{ + const char *pos = ap_strchr_c(*line, stop); + char *res; + + if (!pos) { + apr_size_t len = strlen(*line); + res = apr_pstrmemdup(atrans, *line, len); + *line += len; + return res; + } + + res = apr_pstrmemdup(atrans, *line, pos - *line); + + ++pos; + + *line = pos; + + return res; +} + +/* Get a word, (new) config-file style --- quoted strings and backslashes + * all honored + */ + +static char *substring_conf(apr_pool_t *p, const char *start, int len, + char quote) +{ + char *result = apr_palloc(p, len + 1); + char *resp = result; + int i; + + for (i = 0; i < len; ++i) { + if (start[i] == '\\' && (start[i + 1] == '\\' + || (quote && start[i + 1] == quote))) + *resp++ = start[++i]; + else + *resp++ = start[i]; + } + + *resp++ = '\0'; +#if RESOLVE_ENV_PER_TOKEN + return (char *)ap_resolve_env(p,result); +#else + return result; +#endif +} + +AP_DECLARE(char *) ap_getword_conf_nc(apr_pool_t *p, char **line) +{ + return ap_getword_conf(p, (const char **) line); +} + +AP_DECLARE(char *) ap_getword_conf(apr_pool_t *p, const char **line) +{ + const char *str = *line, *strend; + char *res; + char quote; + + while (apr_isspace(*str)) + ++str; + + if (!*str) { + *line = str; + return ""; + } + + if ((quote = *str) == '"' || quote == '\'') { + strend = str + 1; + while (*strend && *strend != quote) { + if (*strend == '\\' && strend[1] && + (strend[1] == quote || strend[1] == '\\')) { + strend += 2; + } + else { + ++strend; + } + } + res = substring_conf(p, str + 1, strend - str - 1, quote); + + if (*strend == quote) + ++strend; + } + else { + strend = str; + while (*strend && !apr_isspace(*strend)) + ++strend; + + res = substring_conf(p, str, strend - str, 0); + } + + while (apr_isspace(*strend)) + ++strend; + *line = strend; + return res; +} + +AP_DECLARE(char *) ap_getword_conf2_nc(apr_pool_t *p, char **line) +{ + return ap_getword_conf2(p, (const char **) line); +} + +AP_DECLARE(char *) ap_getword_conf2(apr_pool_t *p, const char **line) +{ + const char *str = *line, *strend; + char *res; + char quote; + int count = 1; + + while (apr_isspace(*str)) + ++str; + + if (!*str) { + *line = str; + return ""; + } + + if ((quote = *str) == '"' || quote == '\'') + return ap_getword_conf(p, line); + + if (quote == '{') { + strend = str + 1; + while (*strend) { + if (*strend == '}' && !--count) + break; + if (*strend == '{') + ++count; + if (*strend == '\\' && strend[1] && strend[1] == '\\') { + ++strend; + } + ++strend; + } + res = substring_conf(p, str + 1, strend - str - 1, 0); + + if (*strend == '}') + ++strend; + } + else { + strend = str; + while (*strend && !apr_isspace(*strend)) + ++strend; + + res = substring_conf(p, str, strend - str, 0); + } + + while (apr_isspace(*strend)) + ++strend; + *line = strend; + return res; +} + +AP_DECLARE(int) ap_cfg_closefile(ap_configfile_t *cfp) +{ +#ifdef DEBUG + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(00551) + "Done with config file %s", cfp->name); +#endif + return (cfp->close == NULL) ? 0 : cfp->close(cfp->param); +} + +/* we can't use apr_file_* directly because of linking issues on Windows */ +static apr_status_t cfg_close(void *param) +{ + return apr_file_close(param); +} + +static apr_status_t cfg_getch(char *ch, void *param) +{ + return apr_file_getc(ch, param); +} + +static apr_status_t cfg_getstr(void *buf, apr_size_t bufsiz, void *param) +{ + return apr_file_gets(buf, bufsiz, param); +} + +/* Open a ap_configfile_t as FILE, return open ap_configfile_t struct pointer */ +AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg, + apr_pool_t *p, const char *name) +{ + ap_configfile_t *new_cfg; + apr_file_t *file = NULL; + apr_finfo_t finfo; + apr_status_t status; +#ifdef DEBUG + char buf[120]; +#endif + + if (name == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00552) + "Internal error: pcfg_openfile() called with NULL filename"); + return APR_EBADF; + } + + status = apr_file_open(&file, name, APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, p); +#ifdef DEBUG + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(00553) + "Opening config file %s (%s)", + name, (status != APR_SUCCESS) ? + apr_strerror(status, buf, sizeof(buf)) : "successful"); +#endif + if (status != APR_SUCCESS) + return status; + + status = apr_file_info_get(&finfo, APR_FINFO_TYPE, file); + if (status != APR_SUCCESS) + return status; + + if (finfo.filetype != APR_REG && +#if defined(WIN32) || defined(OS2) || defined(NETWARE) + strcasecmp(apr_filepath_name_get(name), "nul") != 0) { +#else + strcmp(name, "/dev/null") != 0) { +#endif /* WIN32 || OS2 */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00554) + "Access to file %s denied by server: not a regular file", + name); + apr_file_close(file); + return APR_EBADF; + } + +#ifdef WIN32 + /* Some twisted character [no pun intended] at MS decided that a + * zero width joiner as the lead wide character would be ideal for + * describing Unicode text files. This was further convoluted to + * another MSism that the same character mapped into utf-8, EF BB BF + * would signify utf-8 text files. + * + * Since MS configuration files are all protecting utf-8 encoded + * Unicode path, file and resource names, we already have the correct + * WinNT encoding. But at least eat the stupid three bytes up front. + */ + { + unsigned char buf[4]; + apr_size_t len = 3; + status = apr_file_read(file, buf, &len); + if ((status != APR_SUCCESS) || (len < 3) + || memcmp(buf, "\xEF\xBB\xBF", 3) != 0) { + apr_off_t zero = 0; + apr_file_seek(file, APR_SET, &zero); + } + } +#endif + + new_cfg = apr_palloc(p, sizeof(*new_cfg)); + new_cfg->param = file; + new_cfg->name = apr_pstrdup(p, name); + new_cfg->getch = cfg_getch; + new_cfg->getstr = cfg_getstr; + new_cfg->close = cfg_close; + new_cfg->line_number = 0; + *ret_cfg = new_cfg; + return APR_SUCCESS; +} + + +/* Allocate a ap_configfile_t handle with user defined functions and params */ +AP_DECLARE(ap_configfile_t *) ap_pcfg_open_custom( + apr_pool_t *p, const char *descr, void *param, + apr_status_t (*getc_func) (char *ch, void *param), + apr_status_t (*gets_func) (void *buf, apr_size_t bufsize, void *param), + apr_status_t (*close_func) (void *param)) +{ + ap_configfile_t *new_cfg = apr_palloc(p, sizeof(*new_cfg)); + new_cfg->param = param; + new_cfg->name = descr; + new_cfg->getch = getc_func; + new_cfg->getstr = gets_func; + new_cfg->close = close_func; + new_cfg->line_number = 0; + return new_cfg; +} + +/* Read one character from a configfile_t */ +AP_DECLARE(apr_status_t) ap_cfg_getc(char *ch, ap_configfile_t *cfp) +{ + apr_status_t rc = cfp->getch(ch, cfp->param); + if (rc == APR_SUCCESS && *ch == LF) + ++cfp->line_number; + return rc; +} + +AP_DECLARE(const char *) ap_pcfg_strerror(apr_pool_t *p, ap_configfile_t *cfp, + apr_status_t rc) +{ + if (rc == APR_SUCCESS) + return NULL; + + if (rc == APR_ENOSPC) + return apr_psprintf(p, "Error reading %s at line %d: Line too long", + cfp->name, cfp->line_number); + + return apr_psprintf(p, "Error reading %s at line %d: %pm", + cfp->name, cfp->line_number, &rc); +} + +/* Read one line from open ap_configfile_t, strip LF, increase line number */ +/* If custom handler does not define a getstr() function, read char by char */ +static apr_status_t ap_cfg_getline_core(char *buf, apr_size_t bufsize, + apr_size_t offset, ap_configfile_t *cfp) +{ + apr_status_t rc; + /* If a "get string" function is defined, use it */ + if (cfp->getstr != NULL) { + char *cp; + char *cbuf = buf + offset; + apr_size_t cbufsize = bufsize - offset; + + while (1) { + ++cfp->line_number; + rc = cfp->getstr(cbuf, cbufsize, cfp->param); + if (rc == APR_EOF) { + if (cbuf != buf + offset) { + *cbuf = '\0'; + break; + } + else { + return APR_EOF; + } + } + if (rc != APR_SUCCESS) { + return rc; + } + + /* + * check for line continuation, + * i.e. match [^\\]\\[\r]\n only + */ + cp = cbuf; + cp += strlen(cp); + if (cp > buf && cp[-1] == LF) { + cp--; + if (cp > buf && cp[-1] == CR) + cp--; + if (cp > buf && cp[-1] == '\\') { + cp--; + /* + * line continuation requested - + * then remove backslash and continue + */ + cbufsize -= (cp-cbuf); + cbuf = cp; + continue; + } + } + else if (cp - buf >= bufsize - 1) { + return APR_ENOSPC; + } + break; + } + } else { + /* No "get string" function defined; read character by character */ + apr_size_t i = offset; + + if (bufsize < 2) { + /* too small, assume caller is crazy */ + return APR_EINVAL; + } + buf[offset] = '\0'; + + while (1) { + char c; + rc = cfp->getch(&c, cfp->param); + if (rc == APR_EOF) { + if (i > offset) + break; + else + return APR_EOF; + } + if (rc != APR_SUCCESS) + return rc; + if (c == LF) { + ++cfp->line_number; + /* check for line continuation */ + if (i > 0 && buf[i-1] == '\\') { + i--; + continue; + } + else { + break; + } + } + buf[i] = c; + ++i; + if (i >= bufsize - 1) { + return APR_ENOSPC; + } + } + buf[i] = '\0'; + } + return APR_SUCCESS; +} + +static int cfg_trim_line(char *buf) +{ + char *start, *end; + /* + * Leading and trailing white space is eliminated completely + */ + start = buf; + while (apr_isspace(*start)) + ++start; + /* blast trailing whitespace */ + end = &start[strlen(start)]; + while (--end >= start && apr_isspace(*end)) + *end = '\0'; + /* Zap leading whitespace by shifting */ + if (start != buf) + memmove(buf, start, end - start + 2); +#ifdef DEBUG_CFG_LINES + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, APLOGNO(00555) "Read config: '%s'", buf); +#endif + return end - start + 1; +} + +/* Read one line from open ap_configfile_t, strip LF, increase line number */ +/* If custom handler does not define a getstr() function, read char by char */ +AP_DECLARE(apr_status_t) ap_cfg_getline(char *buf, apr_size_t bufsize, + ap_configfile_t *cfp) +{ + apr_status_t rc = ap_cfg_getline_core(buf, bufsize, 0, cfp); + if (rc == APR_SUCCESS) + cfg_trim_line(buf); + return rc; +} + +AP_DECLARE(apr_status_t) ap_varbuf_cfg_getline(struct ap_varbuf *vb, + ap_configfile_t *cfp, + apr_size_t max_len) +{ + apr_status_t rc; + apr_size_t new_len; + vb->strlen = 0; + *vb->buf = '\0'; + + if (vb->strlen == AP_VARBUF_UNKNOWN) + vb->strlen = strlen(vb->buf); + if (vb->avail - vb->strlen < 3) { + new_len = vb->avail * 2; + if (new_len > max_len) + new_len = max_len; + else if (new_len < 3) + new_len = 3; + ap_varbuf_grow(vb, new_len); + } + + for (;;) { + rc = ap_cfg_getline_core(vb->buf, vb->avail, vb->strlen, cfp); + if (rc == APR_ENOSPC || rc == APR_SUCCESS) + vb->strlen += strlen(vb->buf + vb->strlen); + if (rc != APR_ENOSPC) + break; + if (vb->avail >= max_len) + return APR_ENOSPC; + new_len = vb->avail * 2; + if (new_len > max_len) + new_len = max_len; + ap_varbuf_grow(vb, new_len); + --cfp->line_number; + } + if (vb->strlen > max_len) + return APR_ENOSPC; + if (rc == APR_SUCCESS) + vb->strlen = cfg_trim_line(vb->buf); + return rc; +} + +/* Size an HTTP header field list item, as separated by a comma. + * The return value is a pointer to the beginning of the non-empty list item + * within the original string (or NULL if there is none) and the address + * of field is shifted to the next non-comma, non-whitespace character. + * len is the length of the item excluding any beginning whitespace. + */ +AP_DECLARE(const char *) ap_size_list_item(const char **field, int *len) +{ + const unsigned char *ptr = (const unsigned char *)*field; + const unsigned char *token; + int in_qpair, in_qstr, in_com; + + /* Find first non-comma, non-whitespace byte */ + + while (*ptr == ',' || apr_isspace(*ptr)) + ++ptr; + + token = ptr; + + /* Find the end of this item, skipping over dead bits */ + + for (in_qpair = in_qstr = in_com = 0; + *ptr && (in_qpair || in_qstr || in_com || *ptr != ','); + ++ptr) { + + if (in_qpair) { + in_qpair = 0; + } + else { + switch (*ptr) { + case '\\': in_qpair = 1; /* quoted-pair */ + break; + case '"' : if (!in_com) /* quoted string delim */ + in_qstr = !in_qstr; + break; + case '(' : if (!in_qstr) /* comment (may nest) */ + ++in_com; + break; + case ')' : if (in_com) /* end comment */ + --in_com; + break; + default : break; + } + } + } + + if ((*len = (ptr - token)) == 0) { + *field = (const char *)ptr; + return NULL; + } + + /* Advance field pointer to the next non-comma, non-white byte */ + + while (*ptr == ',' || apr_isspace(*ptr)) + ++ptr; + + *field = (const char *)ptr; + return (const char *)token; +} + +/* Retrieve an HTTP header field list item, as separated by a comma, + * while stripping insignificant whitespace and lowercasing anything not in + * a quoted string or comment. The return value is a new string containing + * the converted list item (or NULL if none) and the address pointed to by + * field is shifted to the next non-comma, non-whitespace. + */ +AP_DECLARE(char *) ap_get_list_item(apr_pool_t *p, const char **field) +{ + const char *tok_start; + const unsigned char *ptr; + unsigned char *pos; + char *token; + int addspace = 0, in_qpair = 0, in_qstr = 0, in_com = 0, tok_len = 0; + + /* Find the beginning and maximum length of the list item so that + * we can allocate a buffer for the new string and reset the field. + */ + if ((tok_start = ap_size_list_item(field, &tok_len)) == NULL) { + return NULL; + } + token = apr_palloc(p, tok_len + 1); + + /* Scan the token again, but this time copy only the good bytes. + * We skip extra whitespace and any whitespace around a '=', '/', + * or ';' and lowercase normal characters not within a comment, + * quoted-string or quoted-pair. + */ + for (ptr = (const unsigned char *)tok_start, pos = (unsigned char *)token; + *ptr && (in_qpair || in_qstr || in_com || *ptr != ','); + ++ptr) { + + if (in_qpair) { + in_qpair = 0; + *pos++ = *ptr; + } + else { + switch (*ptr) { + case '\\': in_qpair = 1; + if (addspace == 1) + *pos++ = ' '; + *pos++ = *ptr; + addspace = 0; + break; + case '"' : if (!in_com) + in_qstr = !in_qstr; + if (addspace == 1) + *pos++ = ' '; + *pos++ = *ptr; + addspace = 0; + break; + case '(' : if (!in_qstr) + ++in_com; + if (addspace == 1) + *pos++ = ' '; + *pos++ = *ptr; + addspace = 0; + break; + case ')' : if (in_com) + --in_com; + *pos++ = *ptr; + addspace = 0; + break; + case ' ' : + case '\t': if (addspace) + break; + if (in_com || in_qstr) + *pos++ = *ptr; + else + addspace = 1; + break; + case '=' : + case '/' : + case ';' : if (!(in_com || in_qstr)) + addspace = -1; + *pos++ = *ptr; + break; + default : if (addspace == 1) + *pos++ = ' '; + *pos++ = (in_com || in_qstr) ? *ptr + : apr_tolower(*ptr); + addspace = 0; + break; + } + } + } + *pos = '\0'; + + return token; +} + +typedef enum ap_etag_e { + AP_ETAG_NONE, + AP_ETAG_WEAK, + AP_ETAG_STRONG +} ap_etag_e; + +/* Find an item in canonical form (lowercase, no extra spaces) within + * an HTTP field value list. Returns 1 if found, 0 if not found. + * This would be much more efficient if we stored header fields as + * an array of list items as they are received instead of a plain string. + */ +static int find_list_item(apr_pool_t *p, const char *line, + const char *tok, ap_etag_e type) +{ + const unsigned char *pos; + const unsigned char *ptr = (const unsigned char *)line; + int good = 0, addspace = 0, in_qpair = 0, in_qstr = 0, in_com = 0; + + if (!line || !tok) { + return 0; + } + if (type == AP_ETAG_STRONG && *tok != '\"') { + return 0; + } + if (type == AP_ETAG_WEAK) { + if (*tok == 'W' && (*(tok+1)) == '/' && (*(tok+2)) == '\"') { + tok += 2; + } + else if (*tok != '\"') { + return 0; + } + } + + do { /* loop for each item in line's list */ + + /* Find first non-comma, non-whitespace byte */ + while (*ptr == ',' || apr_isspace(*ptr)) { + ++ptr; + } + + /* Account for strong or weak Etags, depending on our search */ + if (type == AP_ETAG_STRONG && *ptr != '\"') { + break; + } + if (type == AP_ETAG_WEAK) { + if (*ptr == 'W' && (*(ptr+1)) == '/' && (*(ptr+2)) == '\"') { + ptr += 2; + } + else if (*ptr != '\"') { + break; + } + } + + if (*ptr) + good = 1; /* until proven otherwise for this item */ + else + break; /* no items left and nothing good found */ + + /* We skip extra whitespace and any whitespace around a '=', '/', + * or ';' and lowercase normal characters not within a comment, + * quoted-string or quoted-pair. + */ + for (pos = (const unsigned char *)tok; + *ptr && (in_qpair || in_qstr || in_com || *ptr != ','); + ++ptr) { + + if (in_qpair) { + in_qpair = 0; + if (good) + good = (*pos++ == *ptr); + } + else { + switch (*ptr) { + case '\\': in_qpair = 1; + if (addspace == 1) + good = good && (*pos++ == ' '); + good = good && (*pos++ == *ptr); + addspace = 0; + break; + case '"' : if (!in_com) + in_qstr = !in_qstr; + if (addspace == 1) + good = good && (*pos++ == ' '); + good = good && (*pos++ == *ptr); + addspace = 0; + break; + case '(' : if (!in_qstr) + ++in_com; + if (addspace == 1) + good = good && (*pos++ == ' '); + good = good && (*pos++ == *ptr); + addspace = 0; + break; + case ')' : if (in_com) + --in_com; + good = good && (*pos++ == *ptr); + addspace = 0; + break; + case ' ' : + case '\t': if (addspace || !good) + break; + if (in_com || in_qstr) + good = (*pos++ == *ptr); + else + addspace = 1; + break; + case '=' : + case '/' : + case ';' : if (!(in_com || in_qstr)) + addspace = -1; + good = good && (*pos++ == *ptr); + break; + default : if (!good) + break; + if (addspace == 1) + good = (*pos++ == ' '); + if (in_com || in_qstr) + good = good && (*pos++ == *ptr); + else + good = good + && (apr_tolower(*pos++) == apr_tolower(*ptr)); + addspace = 0; + break; + } + } + } + if (good && *pos) + good = 0; /* not good if only a prefix was matched */ + + } while (*ptr && !good); + + return good; +} + +/* Find an item in canonical form (lowercase, no extra spaces) within + * an HTTP field value list. Returns 1 if found, 0 if not found. + * This would be much more efficient if we stored header fields as + * an array of list items as they are received instead of a plain string. + */ +AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line, + const char *tok) +{ + return find_list_item(p, line, tok, AP_ETAG_NONE); +} + +/* Find a strong Etag in canonical form (lowercase, no extra spaces) within + * an HTTP field value list. Returns 1 if found, 0 if not found. + */ +AP_DECLARE(int) ap_find_etag_strong(apr_pool_t *p, const char *line, + const char *tok) +{ + return find_list_item(p, line, tok, AP_ETAG_STRONG); +} + +/* Find a weak ETag in canonical form (lowercase, no extra spaces) within + * an HTTP field value list. Returns 1 if found, 0 if not found. + */ +AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line, + const char *tok) +{ + return find_list_item(p, line, tok, AP_ETAG_WEAK); +} + +/* Grab a list of tokens of the format 1#token (from RFC7230) */ +AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p, + const char *str_in, + apr_array_header_t **tokens, + int skip_invalid) +{ + int in_leading_space = 1; + int in_trailing_space = 0; + int string_end = 0; + const char *tok_begin; + const char *cur; + + if (!str_in) { + return NULL; + } + + tok_begin = cur = str_in; + + while (!string_end) { + const unsigned char c = (unsigned char)*cur; + + if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP)) { + /* Non-separator character; we are finished with leading + * whitespace. We must never have encountered any trailing + * whitespace before the delimiter (comma) */ + in_leading_space = 0; + if (in_trailing_space) { + return "Encountered illegal whitespace in token"; + } + } + else if (c == ' ' || c == '\t') { + /* "Linear whitespace" only includes ASCII CRLF, space, and tab; + * we can't get a CRLF since headers are split on them already, + * so only look for a space or a tab */ + if (in_leading_space) { + /* We're still in leading whitespace */ + ++tok_begin; + } + else { + /* We must be in trailing whitespace */ + ++in_trailing_space; + } + } + else if (c == ',' || c == '\0') { + if (!in_leading_space) { + /* If we're out of the leading space, we know we've read some + * characters of a token */ + if (*tokens == NULL) { + *tokens = apr_array_make(p, 4, sizeof(char *)); + } + APR_ARRAY_PUSH(*tokens, char *) = + apr_pstrmemdup((*tokens)->pool, tok_begin, + (cur - tok_begin) - in_trailing_space); + } + /* We're allowed to have null elements, just don't add them to the + * array */ + + tok_begin = cur + 1; + in_leading_space = 1; + in_trailing_space = 0; + string_end = (c == '\0'); + } + else { + /* Encountered illegal separator char */ + if (skip_invalid) { + /* Skip to the next separator */ + const char *temp; + temp = ap_strchr_c(cur, ','); + if(!temp) { + temp = ap_strchr_c(cur, '\0'); + } + + /* Act like we haven't seen a token so we reset */ + cur = temp - 1; + in_leading_space = 1; + in_trailing_space = 0; + } + else { + return apr_psprintf(p, "Encountered illegal separator " + "'\\x%.2x'", (unsigned int)c); + } + } + + ++cur; + } + + return NULL; +} + +/* Scan a string for HTTP VCHAR/obs-text characters including HT and SP + * (as used in header values, for example, in RFC 7230 section 3.2) + * returning the pointer to the first non-HT ASCII ctrl character. + */ +AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr) +{ + for ( ; !TEST_CHAR(*ptr, T_HTTP_CTRLS); ++ptr) ; + + return ptr; +} + +/* Scan a string for HTTP token characters, returning the pointer to + * the first non-token character. + */ +AP_DECLARE(const char *) ap_scan_http_token(const char *ptr) +{ + for ( ; !TEST_CHAR(*ptr, T_HTTP_TOKEN_STOP); ++ptr) ; + + return ptr; +} + +/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+) + * and return a pointer to the first ctrl/space character encountered. + */ +AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr) +{ + for ( ; TEST_CHAR(*ptr, T_VCHAR_OBSTEXT); ++ptr) ; + + return ptr; +} + +/* Retrieve a token, spacing over it and returning a pointer to + * the first non-white byte afterwards. Note that these tokens + * are delimited by semis and commas; and can also be delimited + * by whitespace at the caller's option. + */ + +AP_DECLARE(char *) ap_get_token(apr_pool_t *p, const char **accept_line, + int accept_white) +{ + const char *ptr = *accept_line; + const char *tok_start; + char *token; + + /* Find first non-white byte */ + + while (apr_isspace(*ptr)) + ++ptr; + + tok_start = ptr; + + /* find token end, skipping over quoted strings. + * (comments are already gone). + */ + + while (*ptr && (accept_white || !apr_isspace(*ptr)) + && *ptr != ';' && *ptr != ',') { + if (*ptr++ == '"') + while (*ptr) + if (*ptr++ == '"') + break; + } + + token = apr_pstrmemdup(p, tok_start, ptr - tok_start); + + /* Advance accept_line pointer to the next non-white byte */ + + while (apr_isspace(*ptr)) + ++ptr; + + *accept_line = ptr; + return token; +} + + +/* find http tokens, see the definition of token from RFC2068 */ +AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok) +{ + const unsigned char *start_token; + const unsigned char *s; + + if (!line) + return 0; + + s = (const unsigned char *)line; + for (;;) { + /* find start of token, skip all stop characters */ + while (*s && TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) { + ++s; + } + if (!*s) { + return 0; + } + start_token = s; + /* find end of the token */ + while (*s && !TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) { + ++s; + } + if (!strncasecmp((const char *)start_token, (const char *)tok, + s - start_token)) { + return 1; + } + if (!*s) { + return 0; + } + } +} + + +AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line, + const char *tok) +{ + int llen, tlen, lidx; + + if (!line) + return 0; + + llen = strlen(line); + tlen = strlen(tok); + lidx = llen - tlen; + + if (lidx < 0 || + (lidx > 0 && !(apr_isspace(line[lidx - 1]) || line[lidx - 1] == ','))) + return 0; + + return (strncasecmp(&line[lidx], tok, tlen) == 0); +} + +AP_DECLARE(char *) ap_escape_shell_cmd(apr_pool_t *p, const char *str) +{ + char *cmd; + unsigned char *d; + const unsigned char *s; + + cmd = apr_palloc(p, 2 * strlen(str) + 1); /* Be safe */ + d = (unsigned char *)cmd; + s = (const unsigned char *)str; + for (; *s; ++s) { + +#if defined(OS2) || defined(WIN32) + /* + * Newlines to Win32/OS2 CreateProcess() are ill advised. + * Convert them to spaces since they are effectively white + * space to most applications + */ + if (*s == '\r' || *s == '\n') { + *d++ = ' '; + continue; + } +#endif + + if (TEST_CHAR(*s, T_ESCAPE_SHELL_CMD)) { + *d++ = '\\'; + } + *d++ = *s; + } + *d = '\0'; + + return cmd; +} + +static char x2c(const char *what) +{ + char digit; + +#if !APR_CHARSET_EBCDIC + digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10 + : (what[0] - '0')); + digit *= 16; + digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 + : (what[1] - '0')); +#else /*APR_CHARSET_EBCDIC*/ + char xstr[5]; + xstr[0]='0'; + xstr[1]='x'; + xstr[2]=what[0]; + xstr[3]=what[1]; + xstr[4]='\0'; + digit = apr_xlate_conv_byte(ap_hdrs_from_ascii, + 0xFF & strtol(xstr, NULL, 16)); +#endif /*APR_CHARSET_EBCDIC*/ + return (digit); +} + +/* + * Unescapes a URL, leaving reserved characters intact. + * Returns 0 on success, non-zero on error + * Failure is due to + * bad % escape returns HTTP_BAD_REQUEST + * + * decoding %00 or a forbidden character returns HTTP_NOT_FOUND + */ + +static int unescape_url(char *url, const char *forbid, const char *reserved) +{ + int badesc, badpath; + char *x, *y; + + badesc = 0; + badpath = 0; + /* Initial scan for first '%'. Don't bother writing values before + * seeing a '%' */ + y = strchr(url, '%'); + if (y == NULL) { + return OK; + } + for (x = y; *y; ++x, ++y) { + if (*y != '%') { + *x = *y; + } + else { + if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) { + badesc = 1; + *x = '%'; + } + else { + char decoded; + decoded = x2c(y + 1); + if ((decoded == '\0') + || (forbid && ap_strchr_c(forbid, decoded))) { + badpath = 1; + *x = decoded; + y += 2; + } + else if (reserved && ap_strchr_c(reserved, decoded)) { + *x++ = *y++; + *x++ = *y++; + *x = *y; + } + else { + *x = decoded; + y += 2; + } + } + } + } + *x = '\0'; + if (badesc) { + return HTTP_BAD_REQUEST; + } + else if (badpath) { + return HTTP_NOT_FOUND; + } + else { + return OK; + } +} +AP_DECLARE(int) ap_unescape_url(char *url) +{ + /* Traditional */ + return unescape_url(url, SLASHES, NULL); +} +AP_DECLARE(int) ap_unescape_url_keep2f(char *url, int decode_slashes) +{ + /* AllowEncodedSlashes (corrected) */ + if (decode_slashes) { + /* no chars reserved */ + return unescape_url(url, NULL, NULL); + } else { + /* reserve (do not decode) encoded slashes */ + return unescape_url(url, NULL, SLASHES); + } +} +#ifdef NEW_APIS +/* IFDEF these out until they've been thought through. + * Just a germ of an API extension for now + */ +AP_DECLARE(int) ap_unescape_url_proxy(char *url) +{ + /* leave RFC1738 reserved characters intact, * so proxied URLs + * don't get mangled. Where does that leave encoded '&' ? + */ + return unescape_url(url, NULL, "/;?"); +} +AP_DECLARE(int) ap_unescape_url_reserved(char *url, const char *reserved) +{ + return unescape_url(url, NULL, reserved); +} +#endif + +AP_DECLARE(int) ap_unescape_urlencoded(char *query) +{ + char *slider; + + /* replace plus with a space */ + if (query) { + for (slider = query; *slider; slider++) { + if (*slider == '+') { + *slider = ' '; + } + } + } + + /* unescape everything else */ + return unescape_url(query, NULL, NULL); +} + +AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname, + apr_port_t port, const request_rec *r) +{ + if (ap_is_default_port(port, r)) { + return apr_pstrdup(p, hostname); + } + else { + return apr_psprintf(p, "%s:%u", hostname, port); + } +} + +AP_DECLARE(int) ap_unescape_all(char *url) +{ + return unescape_url(url, NULL, NULL); +} + +/* c2x takes an unsigned, and expects the caller has guaranteed that + * 0 <= what < 256... which usually means that you have to cast to + * unsigned char first, because (unsigned)(char)(x) first goes through + * signed extension to an int before the unsigned cast. + * + * The reason for this assumption is to assist gcc code generation -- + * the unsigned char -> unsigned extension is already done earlier in + * both uses of this code, so there's no need to waste time doing it + * again. + */ +static const char c2x_table[] = "0123456789abcdef"; + +static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, + unsigned char *where) +{ +#if APR_CHARSET_EBCDIC + what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what); +#endif /*APR_CHARSET_EBCDIC*/ + *where++ = prefix; + *where++ = c2x_table[what >> 4]; + *where++ = c2x_table[what & 0xf]; + return where; +} + +/* + * escape_path_segment() escapes a path segment, as defined in RFC 1808. This + * routine is (should be) OS independent. + * + * os_escape_path() converts an OS path to a URL, in an OS dependent way. In all + * cases if a ':' occurs before the first '/' in the URL, the URL should be + * prefixed with "./" (or the ':' escaped). In the case of Unix, this means + * leaving '/' alone, but otherwise doing what escape_path_segment() does. For + * efficiency reasons, we don't use escape_path_segment(), which is provided for + * reference. Again, RFC 1808 is where this stuff is defined. + * + * If partial is set, os_escape_path() assumes that the path will be appended to + * something with a '/' in it (and thus does not prefix "./"). + */ + +AP_DECLARE(char *) ap_escape_path_segment_buffer(char *copy, const char *segment) +{ + const unsigned char *s = (const unsigned char *)segment; + unsigned char *d = (unsigned char *)copy; + unsigned c; + + while ((c = *s)) { + if (TEST_CHAR(c, T_ESCAPE_PATH_SEGMENT)) { + d = c2x(c, '%', d); + } + else { + *d++ = c; + } + ++s; + } + *d = '\0'; + return copy; +} + +AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *segment) +{ + return ap_escape_path_segment_buffer(apr_palloc(p, 3 * strlen(segment) + 1), segment); +} + +AP_DECLARE(char *) ap_os_escape_path(apr_pool_t *p, const char *path, int partial) +{ + char *copy = apr_palloc(p, 3 * strlen(path) + 3); + const unsigned char *s = (const unsigned char *)path; + unsigned char *d = (unsigned char *)copy; + unsigned c; + + if (!partial) { + const char *colon = ap_strchr_c(path, ':'); + const char *slash = ap_strchr_c(path, '/'); + + if (colon && (!slash || colon < slash)) { + *d++ = '.'; + *d++ = '/'; + } + } + while ((c = *s)) { + if (TEST_CHAR(c, T_OS_ESCAPE_PATH)) { + d = c2x(c, '%', d); + } + else { + *d++ = c; + } + ++s; + } + *d = '\0'; + return copy; +} + +AP_DECLARE(char *) ap_escape_urlencoded_buffer(char *copy, const char *buffer) +{ + const unsigned char *s = (const unsigned char *)buffer; + unsigned char *d = (unsigned char *)copy; + unsigned c; + + while ((c = *s)) { + if (TEST_CHAR(c, T_ESCAPE_URLENCODED)) { + d = c2x(c, '%', d); + } + else if (c == ' ') { + *d++ = '+'; + } + else { + *d++ = c; + } + ++s; + } + *d = '\0'; + return copy; +} + +AP_DECLARE(char *) ap_escape_urlencoded(apr_pool_t *p, const char *buffer) +{ + return ap_escape_urlencoded_buffer(apr_palloc(p, 3 * strlen(buffer) + 1), buffer); +} + +/* ap_escape_uri is now a macro for os_escape_path */ + +AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) +{ + int i, j; + char *x; + + /* first, count the number of extra characters */ + for (i = 0, j = 0; s[i] != '\0'; i++) + if (s[i] == '<' || s[i] == '>') + j += 3; + else if (s[i] == '&') + j += 4; + else if (s[i] == '"') + j += 5; + else if (toasc && !apr_isascii(s[i])) + j += 5; + + if (j == 0) + return apr_pstrmemdup(p, s, i); + + x = apr_palloc(p, i + j + 1); + for (i = 0, j = 0; s[i] != '\0'; i++, j++) + if (s[i] == '<') { + memcpy(&x[j], "<", 4); + j += 3; + } + else if (s[i] == '>') { + memcpy(&x[j], ">", 4); + j += 3; + } + else if (s[i] == '&') { + memcpy(&x[j], "&", 5); + j += 4; + } + else if (s[i] == '"') { + memcpy(&x[j], """, 6); + j += 5; + } + else if (toasc && !apr_isascii(s[i])) { + char *esc = apr_psprintf(p, "&#%3.3d;", (unsigned char)s[i]); + memcpy(&x[j], esc, 6); + j += 5; + } + else + x[j] = s[i]; + + x[j] = '\0'; + return x; +} +AP_DECLARE(char *) ap_escape_logitem(apr_pool_t *p, const char *str) +{ + char *ret; + unsigned char *d; + const unsigned char *s; + apr_size_t length, escapes = 0; + + if (!str) { + return NULL; + } + + /* Compute how many characters need to be escaped */ + s = (const unsigned char *)str; + for (; *s; ++s) { + if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) { + escapes++; + } + } + + /* Compute the length of the input string, including NULL */ + length = s - (const unsigned char *)str + 1; + + /* Fast path: nothing to escape */ + if (escapes == 0) { + return apr_pmemdup(p, str, length); + } + + /* Each escaped character needs up to 3 extra bytes (0 --> \x00) */ + ret = apr_palloc(p, length + 3 * escapes); + d = (unsigned char *)ret; + s = (const unsigned char *)str; + for (; *s; ++s) { + if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) { + *d++ = '\\'; + switch(*s) { + case '\b': + *d++ = 'b'; + break; + case '\n': + *d++ = 'n'; + break; + case '\r': + *d++ = 'r'; + break; + case '\t': + *d++ = 't'; + break; + case '\v': + *d++ = 'v'; + break; + case '\\': + case '"': + *d++ = *s; + break; + default: + c2x(*s, 'x', d); + d += 3; + } + } + else { + *d++ = *s; + } + } + *d = '\0'; + + return ret; +} + +AP_DECLARE(apr_size_t) ap_escape_errorlog_item(char *dest, const char *source, + apr_size_t buflen) +{ + unsigned char *d, *ep; + const unsigned char *s; + + if (!source || !buflen) { /* be safe */ + return 0; + } + + d = (unsigned char *)dest; + s = (const unsigned char *)source; + ep = d + buflen - 1; + + for (; d < ep && *s; ++s) { + + if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) { + *d++ = '\\'; + if (d >= ep) { + --d; + break; + } + + switch(*s) { + case '\b': + *d++ = 'b'; + break; + case '\n': + *d++ = 'n'; + break; + case '\r': + *d++ = 'r'; + break; + case '\t': + *d++ = 't'; + break; + case '\v': + *d++ = 'v'; + break; + case '\\': + *d++ = *s; + break; + case '"': /* no need for this in error log */ + d[-1] = *s; + break; + default: + if (d >= ep - 2) { + ep = --d; /* break the for loop as well */ + break; + } + c2x(*s, 'x', d); + d += 3; + } + } + else { + *d++ = *s; + } + } + *d = '\0'; + + return (d - (unsigned char *)dest); +} + +AP_DECLARE(void) ap_bin2hex(const void *src, apr_size_t srclen, char *dest) +{ + const unsigned char *in = src; + apr_size_t i; + + for (i = 0; i < srclen; i++) { + *dest++ = c2x_table[in[i] >> 4]; + *dest++ = c2x_table[in[i] & 0xf]; + } + *dest = '\0'; +} + +AP_DECLARE(int) ap_is_directory(apr_pool_t *p, const char *path) +{ + apr_finfo_t finfo; + + if (apr_stat(&finfo, path, APR_FINFO_TYPE, p) != APR_SUCCESS) + return 0; /* in error condition, just return no */ + + return (finfo.filetype == APR_DIR); +} + +AP_DECLARE(int) ap_is_rdirectory(apr_pool_t *p, const char *path) +{ + apr_finfo_t finfo; + + if (apr_stat(&finfo, path, APR_FINFO_LINK | APR_FINFO_TYPE, p) != APR_SUCCESS) + return 0; /* in error condition, just return no */ + + return (finfo.filetype == APR_DIR); +} + +AP_DECLARE(char *) ap_make_full_path(apr_pool_t *a, const char *src1, + const char *src2) +{ + apr_size_t len1, len2; + char *path; + + len1 = strlen(src1); + len2 = strlen(src2); + /* allocate +3 for '/' delimiter, trailing NULL and overallocate + * one extra byte to allow the caller to add a trailing '/' + */ + path = (char *)apr_palloc(a, len1 + len2 + 3); + if (len1 == 0) { + *path = '/'; + memcpy(path + 1, src2, len2 + 1); + } + else { + char *next; + memcpy(path, src1, len1); + next = path + len1; + if (next[-1] != '/') { + *next++ = '/'; + } + memcpy(next, src2, len2 + 1); + } + return path; +} + +/* + * Check for an absoluteURI syntax (see section 3.2 in RFC2068). + */ +AP_DECLARE(int) ap_is_url(const char *u) +{ + int x; + + for (x = 0; u[x] != ':'; x++) { + if ((!u[x]) || + ((!apr_isalnum(u[x])) && + (u[x] != '+') && (u[x] != '-') && (u[x] != '.'))) { + return 0; + } + } + + return (x ? 1 : 0); /* If the first character is ':', it's broken, too */ +} + +AP_DECLARE(int) ap_ind(const char *s, char c) +{ + const char *p = ap_strchr_c(s, c); + + if (p == NULL) + return -1; + return p - s; +} + +AP_DECLARE(int) ap_rind(const char *s, char c) +{ + const char *p = ap_strrchr_c(s, c); + + if (p == NULL) + return -1; + return p - s; +} + +AP_DECLARE(void) ap_str_tolower(char *str) +{ + while (*str) { + *str = apr_tolower(*str); + ++str; + } +} + +AP_DECLARE(void) ap_str_toupper(char *str) +{ + while (*str) { + *str = apr_toupper(*str); + ++str; + } +} + +/* + * We must return a FQDN + */ +char *ap_get_local_host(apr_pool_t *a) +{ +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + char str[MAXHOSTNAMELEN + 1]; + char *server_hostname = NULL; + apr_sockaddr_t *sockaddr; + char *hostname; + + if (apr_gethostname(str, sizeof(str) - 1, a) != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a, APLOGNO(00556) + "%s: apr_gethostname() failed to determine ServerName", + ap_server_argv0); + } else { + str[sizeof(str) - 1] = '\0'; + if (apr_sockaddr_info_get(&sockaddr, str, APR_UNSPEC, 0, 0, a) == APR_SUCCESS) { + if ( (apr_getnameinfo(&hostname, sockaddr, 0) == APR_SUCCESS) && + (ap_strchr_c(hostname, '.')) ) { + server_hostname = apr_pstrdup(a, hostname); + return server_hostname; + } else if (ap_strchr_c(str, '.')) { + server_hostname = apr_pstrdup(a, str); + } else { + apr_sockaddr_ip_get(&hostname, sockaddr); + server_hostname = apr_pstrdup(a, hostname); + } + } else { + ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a, APLOGNO(00557) + "%s: apr_sockaddr_info_get() failed for %s", + ap_server_argv0, str); + } + } + + if (!server_hostname) + server_hostname = apr_pstrdup(a, "127.0.0.1"); + + ap_log_perror(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, a, APLOGNO(00558) + "%s: Could not reliably determine the server's fully qualified " + "domain name, using %s. Set the 'ServerName' directive globally " + "to suppress this message", + ap_server_argv0, server_hostname); + + return server_hostname; +} + +/* simple 'pool' alloc()ing glue to apr_base64.c + */ +AP_DECLARE(char *) ap_pbase64decode(apr_pool_t *p, const char *bufcoded) +{ + char *decoded; + int l; + + decoded = (char *) apr_palloc(p, 1 + apr_base64_decode_len(bufcoded)); + l = apr_base64_decode(decoded, bufcoded); + decoded[l] = '\0'; /* make binary sequence into string */ + + return decoded; +} + +AP_DECLARE(char *) ap_pbase64encode(apr_pool_t *p, char *string) +{ + char *encoded; + int l = strlen(string); + + encoded = (char *) apr_palloc(p, 1 + apr_base64_encode_len(l)); + l = apr_base64_encode(encoded, string, l); + encoded[l] = '\0'; /* make binary sequence into string */ + + return encoded; +} + +/* we want to downcase the type/subtype for comparison purposes + * but nothing else because ;parameter=foo values are case sensitive. + * XXX: in truth we want to downcase parameter names... but really, + * apache has never handled parameters and such correctly. You + * also need to compress spaces and such to be able to compare + * properly. -djg + */ +AP_DECLARE(void) ap_content_type_tolower(char *str) +{ + char *semi; + + semi = strchr(str, ';'); + if (semi) { + *semi = '\0'; + } + + ap_str_tolower(str); + + if (semi) { + *semi = ';'; + } +} + +/* + * Given a string, replace any bare " with \" . + */ +AP_DECLARE(char *) ap_escape_quotes(apr_pool_t *p, const char *instring) +{ + int newlen = 0; + const char *inchr = instring; + char *outchr, *outstring; + + /* + * Look through the input string, jogging the length of the output + * string up by an extra byte each time we find an unescaped ". + */ + while (*inchr != '\0') { + newlen++; + if (*inchr == '"') { + newlen++; + } + /* + * If we find a slosh, and it's not the last byte in the string, + * it's escaping something - advance past both bytes. + */ + if ((*inchr == '\\') && (inchr[1] != '\0')) { + inchr++; + newlen++; + } + inchr++; + } + outstring = apr_palloc(p, newlen + 1); + inchr = instring; + outchr = outstring; + /* + * Now copy the input string to the output string, inserting a slosh + * in front of every " that doesn't already have one. + */ + while (*inchr != '\0') { + if ((*inchr == '\\') && (inchr[1] != '\0')) { + *outchr++ = *inchr++; + *outchr++ = *inchr++; + } + if (*inchr == '"') { + *outchr++ = '\\'; + } + if (*inchr != '\0') { + *outchr++ = *inchr++; + } + } + *outchr = '\0'; + return outstring; +} + +/* + * Given a string, append the PID deliminated by delim. + * Usually used to create a pid-appended filepath name + * (eg: /a/b/foo -> /a/b/foo.6726). A function, and not + * a macro, to avoid unistd.h dependency + */ +AP_DECLARE(char *) ap_append_pid(apr_pool_t *p, const char *string, + const char *delim) +{ + return apr_psprintf(p, "%s%s%" APR_PID_T_FMT, string, + delim, getpid()); + +} + +/** + * Parse a given timeout parameter string into an apr_interval_time_t value. + * The unit of the time interval is given as postfix string to the numeric + * string. Currently the following units are understood: + * + * ms : milliseconds + * s : seconds + * mi[n] : minutes + * h : hours + * + * If no unit is contained in the given timeout parameter the default_time_unit + * will be used instead. + * @param timeout_parameter The string containing the timeout parameter. + * @param timeout The timeout value to be returned. + * @param default_time_unit The default time unit to use if none is specified + * in timeout_parameter. + * @return Status value indicating whether the parsing was successful or not. + */ +AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( + const char *timeout_parameter, + apr_interval_time_t *timeout, + const char *default_time_unit) +{ + char *endp; + const char *time_str; + apr_int64_t tout; + + tout = apr_strtoi64(timeout_parameter, &endp, 10); + if (errno) { + return errno; + } + if (!endp || !*endp) { + time_str = default_time_unit; + } + else { + time_str = endp; + } + + switch (*time_str) { + /* Time is in seconds */ + case 's': + *timeout = (apr_interval_time_t) apr_time_from_sec(tout); + break; + case 'h': + /* Time is in hours */ + *timeout = (apr_interval_time_t) apr_time_from_sec(tout * 3600); + break; + case 'm': + switch (*(++time_str)) { + /* Time is in milliseconds */ + case 's': + *timeout = (apr_interval_time_t) tout * 1000; + break; + /* Time is in minutes */ + case 'i': + *timeout = (apr_interval_time_t) apr_time_from_sec(tout * 60); + break; + default: + return APR_EGENERAL; + } + break; + default: + return APR_EGENERAL; + } + return APR_SUCCESS; +} + +/** + * Determine if a request has a request body or not. + * + * @param r the request_rec of the request + * @return truth value + */ +AP_DECLARE(int) ap_request_has_body(request_rec *r) +{ + apr_off_t cl; + char *estr; + const char *cls; + int has_body; + + has_body = (!r->header_only + && (r->kept_body + || apr_table_get(r->headers_in, "Transfer-Encoding") + || ( (cls = apr_table_get(r->headers_in, "Content-Length")) + && (apr_strtoff(&cl, cls, &estr, 10) == APR_SUCCESS) + && (!*estr) + && (cl > 0) ) + ) + ); + return has_body; +} + +AP_DECLARE_NONSTD(apr_status_t) ap_pool_cleanup_set_null(void *data_) +{ + void **ptr = (void **)data_; + *ptr = NULL; + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_str2_alnum(const char *src, char *dest) { + + for ( ; *src; src++, dest++) + { + if (!apr_isprint(*src)) + *dest = 'x'; + else if (!apr_isalnum(*src)) + *dest = '_'; + else + *dest = (char)*src; + } + *dest = '\0'; + return APR_SUCCESS; + +} + +AP_DECLARE(apr_status_t) ap_pstr2_alnum(apr_pool_t *p, const char *src, + const char **dest) +{ + char *new = apr_palloc(p, strlen(src)+1); + if (!new) + return APR_ENOMEM; + *dest = new; + return ap_str2_alnum(src, new); +} + +/** + * Read the body and parse any form found, which must be of the + * type application/x-www-form-urlencoded. + * + * Name/value pairs are returned in an array, with the names as + * strings with a maximum length of HUGE_STRING_LEN, and the + * values as bucket brigades. This allows values to be arbitrarily + * large. + * + * All url-encoding is removed from both the names and the values + * on the fly. The names are interpreted as strings, while the + * values are interpreted as blocks of binary data, that may + * contain the 0 character. + * + * In order to ensure that resource limits are not exceeded, a + * maximum size must be provided. If the sum of the lengths of + * the names and the values exceed this size, this function + * will return HTTP_REQUEST_ENTITY_TOO_LARGE. + * + * An optional number of parameters can be provided, if the number + * of parameters provided exceeds this amount, this function will + * return HTTP_REQUEST_ENTITY_TOO_LARGE. If this value is negative, + * no limit is imposed, and the number of parameters is in turn + * constrained by the size parameter above. + * + * This function honours any kept_body configuration, and the + * original raw request body will be saved to the kept_body brigade + * if so configured, just as ap_discard_request_body does. + * + * NOTE: File upload is not yet supported, but can be without change + * to the function call. + */ + +/* form parsing stuff */ +typedef enum { + FORM_NORMAL, + FORM_AMP, + FORM_NAME, + FORM_VALUE, + FORM_PERCENTA, + FORM_PERCENTB, + FORM_ABORT +} ap_form_type_t; + +AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f, + apr_array_header_t **ptr, + apr_size_t num, apr_size_t usize) +{ + apr_bucket_brigade *bb = NULL; + int seen_eos = 0; + char buffer[HUGE_STRING_LEN + 1]; + const char *ct; + apr_size_t offset = 0; + apr_ssize_t size; + ap_form_type_t state = FORM_NAME, percent = FORM_NORMAL; + ap_form_pair_t *pair = NULL; + apr_array_header_t *pairs = apr_array_make(r->pool, 4, sizeof(ap_form_pair_t)); + char escaped_char[2] = { 0 }; + + *ptr = pairs; + + /* sanity check - we only support forms for now */ + ct = apr_table_get(r->headers_in, "Content-Type"); + if (!ct || strncasecmp("application/x-www-form-urlencoded", ct, 33)) { + return ap_discard_request_body(r); + } + + if (usize > APR_SIZE_MAX >> 1) + size = APR_SIZE_MAX >> 1; + else + size = usize; + + if (!f) { + f = r->input_filters; + } + + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + do { + apr_bucket *bucket = NULL, *last = NULL; + + int rv = ap_get_brigade(f, bb, AP_MODE_READBYTES, + APR_BLOCK_READ, HUGE_STRING_LEN); + if (rv != APR_SUCCESS) { + apr_brigade_destroy(bb); + return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); + } + + for (bucket = APR_BRIGADE_FIRST(bb); + bucket != APR_BRIGADE_SENTINEL(bb); + last = bucket, bucket = APR_BUCKET_NEXT(bucket)) { + const char *data; + apr_size_t len, slide; + + if (last) { + apr_bucket_delete(last); + } + if (APR_BUCKET_IS_EOS(bucket)) { + seen_eos = 1; + break; + } + if (bucket->length == 0) { + continue; + } + + rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + apr_brigade_destroy(bb); + return HTTP_BAD_REQUEST; + } + + slide = len; + while (state != FORM_ABORT && slide-- > 0 && size >= 0 && num != 0) { + char c = *data++; + if ('+' == c) { + c = ' '; + } + else if ('&' == c) { + state = FORM_AMP; + } + if ('%' == c) { + percent = FORM_PERCENTA; + continue; + } + if (FORM_PERCENTA == percent) { + escaped_char[0] = c; + percent = FORM_PERCENTB; + continue; + } + if (FORM_PERCENTB == percent) { + escaped_char[1] = c; + c = x2c(escaped_char); + percent = FORM_NORMAL; + } + switch (state) { + case FORM_AMP: + if (pair) { + const char *tmp = apr_pmemdup(r->pool, buffer, offset); + apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(pair->value, b); + } + state = FORM_NAME; + pair = NULL; + offset = 0; + num--; + break; + case FORM_NAME: + if (offset < HUGE_STRING_LEN) { + if ('=' == c) { + buffer[offset] = 0; + offset = 0; + pair = (ap_form_pair_t *) apr_array_push(pairs); + pair->name = apr_pstrdup(r->pool, buffer); + pair->value = apr_brigade_create(r->pool, r->connection->bucket_alloc); + state = FORM_VALUE; + } + else { + buffer[offset++] = c; + size--; + } + } + else { + state = FORM_ABORT; + } + break; + case FORM_VALUE: + if (offset >= HUGE_STRING_LEN) { + const char *tmp = apr_pmemdup(r->pool, buffer, offset); + apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(pair->value, b); + offset = 0; + } + buffer[offset++] = c; + size--; + break; + default: + break; + } + } + + } + + apr_brigade_cleanup(bb); + } while (!seen_eos); + + if (FORM_ABORT == state || size < 0 || num == 0) { + return HTTP_REQUEST_ENTITY_TOO_LARGE; + } + else if (FORM_VALUE == state && pair && offset > 0) { + const char *tmp = apr_pmemdup(r->pool, buffer, offset); + apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(pair->value, b); + } + + return OK; + +} + +#define VARBUF_SMALL_SIZE 2048 +#define VARBUF_MAX_SIZE (APR_SIZE_MAX - 1 - \ + APR_ALIGN_DEFAULT(sizeof(struct ap_varbuf_info))) + +struct ap_varbuf_info { + struct apr_memnode_t *node; + apr_allocator_t *allocator; +}; + +static apr_status_t varbuf_cleanup(void *info_) +{ + struct ap_varbuf_info *info = info_; + info->node->next = NULL; + apr_allocator_free(info->allocator, info->node); + return APR_SUCCESS; +} + +const char nul = '\0'; +static char * const varbuf_empty = (char *)&nul; + +AP_DECLARE(void) ap_varbuf_init(apr_pool_t *p, struct ap_varbuf *vb, + apr_size_t init_size) +{ + vb->buf = varbuf_empty; + vb->avail = 0; + vb->strlen = AP_VARBUF_UNKNOWN; + vb->pool = p; + vb->info = NULL; + + ap_varbuf_grow(vb, init_size); +} + +AP_DECLARE(void) ap_varbuf_grow(struct ap_varbuf *vb, apr_size_t new_len) +{ + apr_memnode_t *new_node = NULL; + apr_allocator_t *allocator; + struct ap_varbuf_info *new_info; + char *new; + + AP_DEBUG_ASSERT(vb->strlen == AP_VARBUF_UNKNOWN || vb->avail >= vb->strlen); + + if (new_len <= vb->avail) + return; + + if (new_len < 2 * vb->avail && vb->avail < VARBUF_MAX_SIZE/2) { + /* at least double the size, to avoid repeated reallocations */ + new_len = 2 * vb->avail; + } + else if (new_len > VARBUF_MAX_SIZE) { + apr_abortfunc_t abort_fn = apr_pool_abort_get(vb->pool); + ap_assert(abort_fn != NULL); + abort_fn(APR_ENOMEM); + return; + } + + new_len++; /* add space for trailing \0 */ + if (new_len <= VARBUF_SMALL_SIZE) { + new_len = APR_ALIGN_DEFAULT(new_len); + new = apr_palloc(vb->pool, new_len); + if (vb->avail && vb->strlen != 0) { + AP_DEBUG_ASSERT(vb->buf != NULL); + AP_DEBUG_ASSERT(vb->buf != varbuf_empty); + if (new == vb->buf + vb->avail + 1) { + /* We are lucky: the new memory lies directly after our old + * buffer, we can now use both. + */ + vb->avail += new_len; + return; + } + else { + /* copy up to vb->strlen + 1 bytes */ + memcpy(new, vb->buf, vb->strlen == AP_VARBUF_UNKNOWN ? + vb->avail + 1 : vb->strlen + 1); + } + } + else { + *new = '\0'; + } + vb->avail = new_len - 1; + vb->buf = new; + return; + } + + /* The required block is rather larger. Use allocator directly so that + * the memory can be freed independently from the pool. */ + allocator = apr_pool_allocator_get(vb->pool); + if (new_len <= VARBUF_MAX_SIZE) + new_node = apr_allocator_alloc(allocator, + new_len + APR_ALIGN_DEFAULT(sizeof(*new_info))); + if (!new_node) { + apr_abortfunc_t abort_fn = apr_pool_abort_get(vb->pool); + ap_assert(abort_fn != NULL); + abort_fn(APR_ENOMEM); + return; + } + new_info = (struct ap_varbuf_info *)new_node->first_avail; + new_node->first_avail += APR_ALIGN_DEFAULT(sizeof(*new_info)); + new_info->node = new_node; + new_info->allocator = allocator; + new = new_node->first_avail; + AP_DEBUG_ASSERT(new_node->endp - new_node->first_avail >= new_len); + new_len = new_node->endp - new_node->first_avail; + + if (vb->avail && vb->strlen != 0) + memcpy(new, vb->buf, vb->strlen == AP_VARBUF_UNKNOWN ? + vb->avail + 1 : vb->strlen + 1); + else + *new = '\0'; + if (vb->info) + apr_pool_cleanup_run(vb->pool, vb->info, varbuf_cleanup); + apr_pool_cleanup_register(vb->pool, new_info, varbuf_cleanup, + apr_pool_cleanup_null); + vb->info = new_info; + vb->buf = new; + vb->avail = new_len - 1; +} + +AP_DECLARE(void) ap_varbuf_strmemcat(struct ap_varbuf *vb, const char *str, + int len) +{ + if (len == 0) + return; + if (!vb->avail) { + ap_varbuf_grow(vb, len); + memcpy(vb->buf, str, len); + vb->buf[len] = '\0'; + vb->strlen = len; + return; + } + if (vb->strlen == AP_VARBUF_UNKNOWN) + vb->strlen = strlen(vb->buf); + ap_varbuf_grow(vb, vb->strlen + len); + memcpy(vb->buf + vb->strlen, str, len); + vb->strlen += len; + vb->buf[vb->strlen] = '\0'; +} + +AP_DECLARE(void) ap_varbuf_free(struct ap_varbuf *vb) +{ + if (vb->info) { + apr_pool_cleanup_run(vb->pool, vb->info, varbuf_cleanup); + vb->info = NULL; + } + vb->buf = NULL; +} + +AP_DECLARE(char *) ap_varbuf_pdup(apr_pool_t *p, struct ap_varbuf *buf, + const char *prepend, apr_size_t prepend_len, + const char *append, apr_size_t append_len, + apr_size_t *new_len) +{ + apr_size_t i = 0; + struct iovec vec[3]; + + if (prepend) { + vec[i].iov_base = (void *)prepend; + vec[i].iov_len = prepend_len; + i++; + } + if (buf->avail && buf->strlen) { + if (buf->strlen == AP_VARBUF_UNKNOWN) + buf->strlen = strlen(buf->buf); + vec[i].iov_base = (void *)buf->buf; + vec[i].iov_len = buf->strlen; + i++; + } + if (append) { + vec[i].iov_base = (void *)append; + vec[i].iov_len = append_len; + i++; + } + if (i) + return apr_pstrcatv(p, vec, i, new_len); + + if (new_len) + *new_len = 0; + return ""; +} + +AP_DECLARE(apr_status_t) ap_varbuf_regsub(struct ap_varbuf *vb, + const char *input, + const char *source, + apr_size_t nmatch, + ap_regmatch_t pmatch[], + apr_size_t maxlen) +{ + return regsub_core(NULL, NULL, vb, input, source, nmatch, pmatch, maxlen); +} + +static const char * const oom_message = "[crit] Memory allocation failed, " + "aborting process." APR_EOL_STR; + +AP_DECLARE(void) ap_abort_on_oom() +{ + int written, count = strlen(oom_message); + const char *buf = oom_message; + do { + written = write(STDERR_FILENO, buf, count); + if (written == count) + break; + if (written > 0) { + buf += written; + count -= written; + } + } while (written >= 0 || errno == EINTR); + abort(); +} + +AP_DECLARE(void *) ap_malloc(size_t size) +{ + void *p = malloc(size); + if (p == NULL && size != 0) + ap_abort_on_oom(); + return p; +} + +AP_DECLARE(void *) ap_calloc(size_t nelem, size_t size) +{ + void *p = calloc(nelem, size); + if (p == NULL && nelem != 0 && size != 0) + ap_abort_on_oom(); + return p; +} + +AP_DECLARE(void *) ap_realloc(void *ptr, size_t size) +{ + void *p = realloc(ptr, size); + if (p == NULL && size != 0) + ap_abort_on_oom(); + return p; +} + +AP_DECLARE(void) ap_get_sload(ap_sload_t *ld) +{ + int i, j, server_limit, thread_limit; + int ready = 0; + int busy = 0; + int total; + ap_generation_t mpm_generation; + + /* preload errored fields, we overwrite */ + ld->idle = -1; + ld->busy = -1; + ld->bytes_served = 0; + ld->access_count = 0; + + ap_mpm_query(AP_MPMQ_GENERATION, &mpm_generation); + ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); + ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit); + + for (i = 0; i < server_limit; i++) { + process_score *ps; + ps = ap_get_scoreboard_process(i); + + for (j = 0; j < thread_limit; j++) { + int res; + worker_score *ws = NULL; + ws = &ap_scoreboard_image->servers[i][j]; + res = ws->status; + + if (!ps->quiescing && ps->pid) { + if (res == SERVER_READY && ps->generation == mpm_generation) { + ready++; + } + else if (res != SERVER_DEAD && + res != SERVER_STARTING && res != SERVER_IDLE_KILL && + ps->generation == mpm_generation) { + busy++; + } + } + + if (ap_extended_status && !ps->quiescing && ps->pid) { + if (ws->access_count != 0 + || (res != SERVER_READY && res != SERVER_DEAD)) { + ld->access_count += ws->access_count; + ld->bytes_served += ws->bytes_served; + } + } + } + } + total = busy + ready; + if (total) { + ld->idle = ready * 100 / total; + ld->busy = busy * 100 / total; + } +} + +AP_DECLARE(void) ap_get_loadavg(ap_loadavg_t *ld) +{ + /* preload errored fields, we overwrite */ + ld->loadavg = -1.0; + ld->loadavg5 = -1.0; + ld->loadavg15 = -1.0; + +#if HAVE_GETLOADAVG + { + double la[3]; + int num; + + num = getloadavg(la, 3); + if (num > 0) { + ld->loadavg = (float)la[0]; + } + if (num > 1) { + ld->loadavg5 = (float)la[1]; + } + if (num > 2) { + ld->loadavg15 = (float)la[2]; + } + } +#endif +} + +AP_DECLARE(char *) ap_get_exec_line(apr_pool_t *p, + const char *cmd, + const char * const * argv) +{ + char buf[MAX_STRING_LEN]; + apr_procattr_t *procattr; + apr_proc_t *proc; + apr_file_t *fp; + apr_size_t nbytes = 1; + char c; + int k; + + if (apr_procattr_create(&procattr, p) != APR_SUCCESS) + return NULL; + if (apr_procattr_io_set(procattr, APR_FULL_BLOCK, APR_FULL_BLOCK, + APR_FULL_BLOCK) != APR_SUCCESS) + return NULL; + if (apr_procattr_dir_set(procattr, + ap_make_dirstr_parent(p, cmd)) != APR_SUCCESS) + return NULL; + if (apr_procattr_cmdtype_set(procattr, APR_PROGRAM) != APR_SUCCESS) + return NULL; + proc = apr_pcalloc(p, sizeof(apr_proc_t)); + if (apr_proc_create(proc, cmd, argv, NULL, procattr, p) != APR_SUCCESS) + return NULL; + fp = proc->out; + + if (fp == NULL) + return NULL; + /* XXX: we are reading 1 byte at a time here */ + for (k = 0; apr_file_read(fp, &c, &nbytes) == APR_SUCCESS + && nbytes == 1 && (k < MAX_STRING_LEN-1) ; ) { + if (c == '\n' || c == '\r') + break; + buf[k++] = c; + } + buf[k] = '\0'; + apr_file_close(fp); + + return apr_pstrndup(p, buf, k); +} + +AP_DECLARE(int) ap_array_str_index(const apr_array_header_t *array, + const char *s, + int start) +{ + if (start >= 0) { + int i; + + for (i = start; i < array->nelts; i++) { + const char *p = APR_ARRAY_IDX(array, i, const char *); + if (!strcmp(p, s)) { + return i; + } + } + } + + return -1; +} + +AP_DECLARE(int) ap_array_str_contains(const apr_array_header_t *array, + const char *s) +{ + return (ap_array_str_index(array, s, 0) >= 0); +} + +#if !APR_CHARSET_EBCDIC +/* + * Our own known-fast translation table for casecmp by character. + * Only ASCII alpha characters 41-5A are folded to 61-7A, other + * octets (such as extended latin alphabetics) are never case-folded. + * NOTE: Other than Alpha A-Z/a-z, each code point is unique! + */ +static const short ucharmap[] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, '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', 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, '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', 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; +#else /* APR_CHARSET_EBCDIC */ +/* + * Derived from apr-iconv/ccs/cp037.c for EBCDIC case comparison, + * provides unique identity of every char value (strict ISO-646 + * conformance, arbitrary election of an ISO-8859-1 ordering, and + * very arbitrary control code assignments into C1 to achieve + * identity and a reversible mapping of code points), + * then folding the equivalences of ASCII 41-5A into 61-7A, + * presenting comparison results in a somewhat ISO/IEC 10646 + * (ASCII-like) order, depending on the EBCDIC code page in use. + * + * NOTE: Other than Alpha A-Z/a-z, each code point is unique! + */ +static const short ucharmap[] = { + 0x00, 0x01, 0x02, 0x03, 0x9C, 0x09, 0x86, 0x7F, + 0x97, 0x8D, 0x8E, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x9D, 0x85, 0x08, 0x87, + 0x18, 0x19, 0x92, 0x8F, 0x1C, 0x1D, 0x1E, 0x1F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x0A, 0x17, 0x1B, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x05, 0x06, 0x07, + 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, + 0x98, 0x99, 0x9A, 0x9B, 0x14, 0x15, 0x9E, 0x1A, + 0x20, 0xA0, 0xE2, 0xE4, 0xE0, 0xE1, 0xE3, 0xE5, + 0xE7, 0xF1, 0xA2, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, + 0x26, 0xE9, 0xEA, 0xEB, 0xE8, 0xED, 0xEE, 0xEF, + 0xEC, 0xDF, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAC, + 0x2D, 0x2F, 0xC2, 0xC4, 0xC0, 0xC1, 0xC3, 0xC5, + 0xC7, 0xD1, 0xA6, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, + 0xF8, 0xC9, 0xCA, 0xCB, 0xC8, 0xCD, 0xCE, 0xCF, + 0xCC, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, + 0xD8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0xAB, 0xBB, 0xF0, 0xFD, 0xFE, 0xB1, + 0xB0, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, + 0x71, 0x72, 0xAA, 0xBA, 0xE6, 0xB8, 0xC6, 0xA4, + 0xB5, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0xA1, 0xBF, 0xD0, 0xDD, 0xDE, 0xAE, + 0x5E, 0xA3, 0xA5, 0xB7, 0xA9, 0xA7, 0xB6, 0xBC, + 0xBD, 0xBE, 0x5B, 0x5D, 0xAF, 0xA8, 0xB4, 0xD7, + 0x7B, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0xAD, 0xF4, 0xF6, 0xF2, 0xF3, 0xF5, + 0x7D, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, + 0x71, 0x72, 0xB9, 0xFB, 0xFC, 0xF9, 0xFA, 0xFF, + 0x5C, 0xF7, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0xB2, 0xD4, 0xD6, 0xD2, 0xD3, 0xD5, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0xB3, 0xDB, 0xDC, 0xD9, 0xDA, 0x9F +}; +#endif + +AP_DECLARE(int) ap_cstr_casecmp(const char *s1, const char *s2) +{ + const unsigned char *str1 = (const unsigned char *)s1; + const unsigned char *str2 = (const unsigned char *)s2; + for (;;) + { + const int c1 = (int)(*str1); + const int c2 = (int)(*str2); + const int cmp = ucharmap[c1] - ucharmap[c2]; + /* Not necessary to test for !c2, this is caught by cmp */ + if (cmp || !c1) + return cmp; + str1++; + str2++; + } +} + +AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2, apr_size_t n) +{ + const unsigned char *str1 = (const unsigned char *)s1; + const unsigned char *str2 = (const unsigned char *)s2; + while (n--) + { + const int c1 = (int)(*str1); + const int c2 = (int)(*str2); + const int cmp = ucharmap[c1] - ucharmap[c2]; + /* Not necessary to test for !c2, this is caught by cmp */ + if (cmp || !c1) + return cmp; + str1++; + str2++; + } + return 0; +} + diff --git a/server/util_cfgtree.c b/server/util_cfgtree.c new file mode 100644 index 0000000..a7142e3 --- /dev/null +++ b/server/util_cfgtree.c @@ -0,0 +1,46 @@ +/* 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 "util_cfgtree.h" +#include <stdlib.h> + +ap_directive_t *ap_add_node(ap_directive_t **parent, ap_directive_t *current, + ap_directive_t *toadd, int child) +{ + if (current == NULL) { + /* we just started a new parent */ + if (*parent != NULL) { + (*parent)->first_child = toadd; + toadd->parent = *parent; + } + if (child) { + /* First item in config file or container is a container */ + *parent = toadd; + return NULL; + } + return toadd; + } + current->next = toadd; + toadd->parent = *parent; + if (child) { + /* switch parents, navigate into child */ + *parent = toadd; + return NULL; + } + return toadd; +} + + diff --git a/server/util_charset.c b/server/util_charset.c new file mode 100644 index 0000000..f896729 --- /dev/null +++ b/server/util_charset.c @@ -0,0 +1,28 @@ +/* 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 "ap_config.h" + +#if APR_CHARSET_EBCDIC + +#include "httpd.h" +#include "http_log.h" +#include "http_core.h" +#include "util_charset.h" + +apr_xlate_t *ap_hdrs_to_ascii, *ap_hdrs_from_ascii; + +#endif /*APR_CHARSET_EBCDIC */ diff --git a/server/util_cookies.c b/server/util_cookies.c new file mode 100644 index 0000000..82a514f --- /dev/null +++ b/server/util_cookies.c @@ -0,0 +1,290 @@ +/* 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 "util_cookies.h" +#include "apr_lib.h" +#include "apr_strings.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" + +#define LOG_PREFIX "ap_cookie: " + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +/** + * Write an RFC2109 compliant cookie. + * + * @param r The request + * @param name The name of the cookie. + * @param val The value to place in the cookie. + * @param attrs The string containing additional cookie attributes. If NULL, the + * DEFAULT_ATTRS will be used. + * @param maxage If non zero, a Max-Age header will be added to the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_write(request_rec * r, const char *name, const char *val, + const char *attrs, long maxage, ...) +{ + + const char *buffer; + const char *rfc2109; + apr_table_t *t; + va_list vp; + + /* handle expiry */ + buffer = ""; + if (maxage) { + buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";", NULL); + } + + /* create RFC2109 compliant cookie */ + rfc2109 = apr_pstrcat(r->pool, name, "=", val, ";", buffer, + attrs && *attrs ? attrs : DEFAULT_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00007) LOG_PREFIX + "user '%s' set cookie: '%s'", r->user, rfc2109); + + /* write the cookie to the header table(s) provided */ + va_start(vp, maxage); + while ((t = va_arg(vp, apr_table_t *))) { + apr_table_addn(t, SET_COOKIE, rfc2109); + } + va_end(vp); + + return APR_SUCCESS; + +} + +/** + * Write an RFC2965 compliant cookie. + * + * @param r The request + * @param name2 The name of the cookie. + * @param val The value to place in the cookie. + * @param attrs2 The string containing additional cookie attributes. If NULL, the + * DEFAULT_ATTRS will be used. + * @param maxage If non zero, a Max-Age header will be added to the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_write2(request_rec * r, const char *name2, const char *val, + const char *attrs2, long maxage, ...) +{ + + const char *buffer; + const char *rfc2965; + apr_table_t *t; + va_list vp; + + /* handle expiry */ + buffer = ""; + if (maxage) { + buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";", NULL); + } + + /* create RFC2965 compliant cookie */ + rfc2965 = apr_pstrcat(r->pool, name2, "=", val, ";", buffer, + attrs2 && *attrs2 ? attrs2 : DEFAULT_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00008) LOG_PREFIX + "user '%s' set cookie2: '%s'", r->user, rfc2965); + + /* write the cookie to the header table(s) provided */ + va_start(vp, maxage); + while ((t = va_arg(vp, apr_table_t *))) { + apr_table_addn(t, SET_COOKIE2, rfc2965); + } + va_end(vp); + + return APR_SUCCESS; + +} + +/** + * Remove an RFC2109 compliant cookie. + * + * @param r The request + * @param name The name of the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_remove(request_rec * r, const char *name, const char *attrs, ...) +{ + apr_table_t *t; + va_list vp; + + /* create RFC2109 compliant cookie */ + const char *rfc2109 = apr_pstrcat(r->pool, name, "=;Max-Age=0;", + attrs ? attrs : CLEAR_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00009) LOG_PREFIX + "user '%s' removed cookie: '%s'", r->user, rfc2109); + + /* write the cookie to the header table(s) provided */ + va_start(vp, attrs); + while ((t = va_arg(vp, apr_table_t *))) { + apr_table_addn(t, SET_COOKIE, rfc2109); + } + va_end(vp); + + return APR_SUCCESS; + +} + +/** + * Remove an RFC2965 compliant cookie. + * + * @param r The request + * @param name2 The name of the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_remove2(request_rec * r, const char *name2, const char *attrs2, ...) +{ + apr_table_t *t; + va_list vp; + + /* create RFC2965 compliant cookie */ + const char *rfc2965 = apr_pstrcat(r->pool, name2, "=;Max-Age=0;", + attrs2 ? attrs2 : CLEAR_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00010) LOG_PREFIX + "user '%s' removed cookie2: '%s'", r->user, rfc2965); + + /* write the cookie to the header table(s) provided */ + va_start(vp, attrs2); + while ((t = va_arg(vp, apr_table_t *))) { + apr_table_addn(t, SET_COOKIE2, rfc2965); + } + va_end(vp); + + return APR_SUCCESS; + +} + +/* Iterate through the cookies, isolate our cookie and then remove it. + * + * If our cookie appears two or more times, but with different values, + * remove it twice and set the duplicated flag to true. Remove any + * $path or other attributes following our cookie if present. If we end + * up with an empty cookie, remove the whole header. + */ +static int extract_cookie_line(void *varg, const char *key, const char *val) +{ + ap_cookie_do *v = varg; + char *last1, *last2; + char *cookie = apr_pstrdup(v->r->pool, val); + const char *name = apr_pstrcat(v->r->pool, v->name ? v->name : "", "=", NULL); + apr_size_t len = strlen(name); + const char *new_cookie = ""; + const char *comma = ","; + char *next1; + const char *semi = ";"; + char *next2; + const char *sep = ""; + int cookies = 0; + + /* find the cookie called name */ + int eat = 0; + next1 = apr_strtok(cookie, comma, &last1); + while (next1) { + next2 = apr_strtok(next1, semi, &last2); + while (next2) { + char *trim = next2; + while (apr_isspace(*trim)) { + trim++; + } + if (!strncmp(trim, name, len)) { + if (v->encoded) { + if (strcmp(v->encoded, trim + len)) { + v->duplicated = 1; + } + } + v->encoded = apr_pstrdup(v->r->pool, trim + len); + eat = 1; + } + else { + if (*trim != '$') { + cookies++; + eat = 0; + } + if (!eat) { + new_cookie = apr_pstrcat(v->r->pool, new_cookie, sep, next2, NULL); + } + } + next2 = apr_strtok(NULL, semi, &last2); + sep = semi; + } + + next1 = apr_strtok(NULL, comma, &last1); + sep = comma; + } + + /* any cookies left over? */ + if (cookies) { + apr_table_addn(v->new_cookies, key, new_cookie); + } + + return 1; +} + +/** + * Read a cookie called name, placing its value in val. + * + * Both the Cookie and Cookie2 headers are scanned for the cookie. + * + * If the cookie is duplicated, this function returns APR_EGENERAL. If found, + * and if remove is non zero, the cookie will be removed from the headers, and + * thus kept private from the backend. + */ +AP_DECLARE(apr_status_t) ap_cookie_read(request_rec * r, const char *name, const char **val, + int remove) +{ + + ap_cookie_do v; + v.r = r; + v.encoded = NULL; + v.new_cookies = apr_table_make(r->pool, 10); + v.duplicated = 0; + v.name = name; + + apr_table_do(extract_cookie_line, &v, r->headers_in, + "Cookie", "Cookie2", NULL); + if (v.duplicated) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00011) LOG_PREFIX + "client submitted cookie '%s' more than once: %s", v.name, r->uri); + return APR_EGENERAL; + } + + /* remove our cookie(s), and replace them */ + if (remove) { + apr_table_unset(r->headers_in, "Cookie"); + apr_table_unset(r->headers_in, "Cookie2"); + r->headers_in = apr_table_overlay(r->pool, r->headers_in, v.new_cookies); + } + + *val = v.encoded; + + return APR_SUCCESS; + +} + +/** + * Sanity check a given string that it exists, is not empty, + * and does not contain the special characters '=', ';' and '&'. + * + * It is used to sanity check the cookie names. + */ +AP_DECLARE(apr_status_t) ap_cookie_check_string(const char *string) +{ + if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&') || + ap_strchr_c(string, ';')) { + return APR_EGENERAL; + } + return APR_SUCCESS; +} diff --git a/server/util_debug.c b/server/util_debug.c new file mode 100644 index 0000000..a75fdda --- /dev/null +++ b/server/util_debug.c @@ -0,0 +1,236 @@ +/* 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 APR_WANT_STRFUNC +#include "apr_want.h" + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" + +/* Possibly get rid of the macros we defined in httpd.h */ +#if defined(strchr) +#undef strchr +#endif + +#if defined (strrchr) +#undef strrchr +#endif + +#if defined (strstr) +#undef strstr +#endif + + +#if defined(ap_strchr) +#undef ap_strchr +AP_DECLARE(char *) ap_strchr(char *s, int c); +#endif + +AP_DECLARE(char *) ap_strchr(char *s, int c) +{ + return strchr(s,c); +} + +#if defined(ap_strchr_c) +#undef ap_strchr_c +AP_DECLARE(const char *) ap_strchr_c(const char *s, int c); +#endif + +AP_DECLARE(const char *) ap_strchr_c(const char *s, int c) +{ + return strchr(s,c); +} + +#if defined(ap_strrchr) +#undef ap_strrchr +AP_DECLARE(char *) ap_strrchr(char *s, int c); +#endif + +AP_DECLARE(char *) ap_strrchr(char *s, int c) +{ + return strrchr(s,c); +} + +#if defined(ap_strrchr_c) +#undef ap_strrchr_c +AP_DECLARE(const char *) ap_strrchr_c(const char *s, int c); +#endif + +AP_DECLARE(const char *) ap_strrchr_c(const char *s, int c) +{ + return strrchr(s,c); +} + +#if defined(ap_strstr) +#undef ap_strstr +AP_DECLARE(char *) ap_strstr(char *s, const char *c); +#endif + +AP_DECLARE(char *) ap_strstr(char *s, const char *c) +{ + return strstr(s,c); +} + +#if defined(ap_strstr_c) +#undef ap_strstr_c +AP_DECLARE(const char *) ap_strstr_c(const char *s, const char *c); +#endif + +AP_DECLARE(const char *) ap_strstr_c(const char *s, const char *c) +{ + return strstr(s,c); +} + +#if defined(ap_get_module_config) +#undef ap_get_module_config +AP_DECLARE(void *) ap_get_module_config(const ap_conf_vector_t *cv, + const module *m); +#endif + +AP_DECLARE(void *) ap_get_module_config(const ap_conf_vector_t *cv, + const module *m) +{ + return ((void **)cv)[m->module_index]; +} + +AP_DECLARE(int) ap_get_module_flags(const module *m) +{ + if (m->version < AP_MODULE_FLAGS_MMN_MAJOR + || (m->version == AP_MODULE_FLAGS_MMN_MAJOR + && (m->minor_version < AP_MODULE_FLAGS_MMN_MINOR))) { + return 0; + } + + return m->flags; +} + +#if defined(ap_get_core_module_config) +#undef ap_get_core_module_config +AP_DECLARE(void *) ap_get_core_module_config(const ap_conf_vector_t *cv); +#endif + +AP_DECLARE(void *) ap_get_core_module_config(const ap_conf_vector_t *cv) +{ + return ((void **)cv)[AP_CORE_MODULE_INDEX]; +} + + +#if defined(ap_get_server_module_loglevel) +#undef ap_get_server_module_loglevel +AP_DECLARE(int) ap_get_server_module_loglevel(const server_rec *s, int module_index); +#endif + +AP_DECLARE(int) ap_get_server_module_loglevel(const server_rec *s, int module_index) +{ + if (module_index < 0 || s->log.module_levels == NULL || + s->log.module_levels[module_index] < 0) + { + return s->log.level; + } + + return s->log.module_levels[module_index]; +} + +#if defined(ap_get_conn_module_loglevel) +#undef ap_get_conn_module_loglevel +AP_DECLARE(int) ap_get_conn_module_loglevel(const conn_rec *c, int module_index); +#endif + +AP_DECLARE(int) ap_get_conn_module_loglevel(const conn_rec *c, int module_index) +{ + const struct ap_logconf *l = (c)->log ? (c)->log : &(c)->base_server->log; + if (module_index < 0 || l->module_levels == NULL || + l->module_levels[module_index] < 0) + { + return l->level; + } + + return l->module_levels[module_index]; +} + +#if defined(ap_get_conn_server_module_loglevel) +#undef ap_get_conn_server_module_loglevel +AP_DECLARE(int) ap_get_conn_server_module_loglevel(const conn_rec *c, + const server_rec *s, + int module_index); +#endif + +AP_DECLARE(int) ap_get_conn_server_module_loglevel(const conn_rec *c, + const server_rec *s, + int module_index) +{ + const struct ap_logconf *l = (c->log && c->log != &c->base_server->log) ? + c->log : &s->log; + if (module_index < 0 || l->module_levels == NULL || + l->module_levels[module_index] < 0) + { + return l->level; + } + + return l->module_levels[module_index]; +} + +#if defined(ap_get_request_module_loglevel) +#undef ap_get_request_module_loglevel +AP_DECLARE(int) ap_get_request_module_loglevel(const request_rec *c, int module_index); +#endif + +AP_DECLARE(int) ap_get_request_module_loglevel(const request_rec *r, int module_index) +{ + const struct ap_logconf *l = r->log ? r->log : + r->connection->log ? r->connection->log : + &r->server->log; + if (module_index < 0 || l->module_levels == NULL || + l->module_levels[module_index] < 0) + { + return l->level; + } + + return l->module_levels[module_index]; +} + +/** + * Generic accessors for other modules to set at their own module-specific + * data + * @param conf_vector The vector in which the modules configuration is stored. + * usually r->per_dir_config or s->module_config + * @param m The module to set the data for. + * @param val The module-specific data to set + * @fn void ap_set_module_config(ap_conf_vector_t *cv, const module *m, void *val) + */ +#if defined(ap_set_module_config) +#undef ap_set_module_config +AP_DECLARE(void) ap_set_module_config(ap_conf_vector_t *cv, const module *m, + void *val); +#endif + +AP_DECLARE(void) ap_set_module_config(ap_conf_vector_t *cv, const module *m, + void *val) +{ + ((void **)cv)[m->module_index] = val; +} + + +#if defined(ap_set_core_module_config) +#undef ap_set_core_module_config +AP_DECLARE(void) ap_set_core_module_config(ap_conf_vector_t *cv, void *val); +#endif + +AP_DECLARE(void) ap_set_core_module_config(ap_conf_vector_t *cv, void *val) +{ + ((void **)cv)[AP_CORE_MODULE_INDEX] = val; +} diff --git a/server/util_ebcdic.c b/server/util_ebcdic.c new file mode 100644 index 0000000..dc13706 --- /dev/null +++ b/server/util_ebcdic.c @@ -0,0 +1,117 @@ +/* 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 "ap_config.h" + +#if APR_CHARSET_EBCDIC + +#include "apr_strings.h" +#include "httpd.h" +#include "http_log.h" +#include "http_core.h" +#include "util_ebcdic.h" + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +apr_status_t ap_init_ebcdic(apr_pool_t *pool) +{ + apr_status_t rv; + + rv = apr_xlate_open(&ap_hdrs_to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00040) + "apr_xlate_open() failed"); + return rv; + } + + rv = apr_xlate_open(&ap_hdrs_from_ascii, APR_DEFAULT_CHARSET, "ISO-8859-1", pool); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00041) + "apr_xlate_open() failed"); + return rv; + } + + rv = apr_MD5InitEBCDIC(ap_hdrs_to_ascii); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00042) + "apr_MD5InitEBCDIC() failed"); + return rv; + } + + rv = apr_base64init_ebcdic(ap_hdrs_to_ascii, ap_hdrs_from_ascii); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00043) + "apr_base64init_ebcdic() failed"); + return rv; + } + + rv = apr_SHA1InitEBCDIC(ap_hdrs_to_ascii); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00044) + "apr_SHA1InitEBCDIC() failed"); + return rv; + } + + return APR_SUCCESS; +} + +void ap_xlate_proto_to_ascii(char *buffer, apr_size_t len) +{ + apr_size_t inbytes_left, outbytes_left; + + inbytes_left = outbytes_left = len; + apr_xlate_conv_buffer(ap_hdrs_to_ascii, buffer, &inbytes_left, + buffer, &outbytes_left); +} + +void ap_xlate_proto_from_ascii(char *buffer, apr_size_t len) +{ + apr_size_t inbytes_left, outbytes_left; + + inbytes_left = outbytes_left = len; + apr_xlate_conv_buffer(ap_hdrs_from_ascii, buffer, &inbytes_left, + buffer, &outbytes_left); +} + +int ap_rvputs_proto_in_ascii(request_rec *r, ...) +{ + va_list va; + const char *s; + char *ascii_s; + apr_size_t len; + apr_size_t written = 0; + + va_start(va, r); + while (1) { + s = va_arg(va, const char *); + if (s == NULL) + break; + len = strlen(s); + ascii_s = apr_pstrmemdup(r->pool, s, len); + ap_xlate_proto_to_ascii(ascii_s, len); + if (ap_rputs(ascii_s, r) < 0) { + va_end(va); + return -1; + } + written += len; + } + va_end(va); + + return written; +} +#endif /* APR_CHARSET_EBCDIC */ diff --git a/server/util_expr_eval.c b/server/util_expr_eval.c new file mode 100644 index 0000000..0f92f41 --- /dev/null +++ b/server/util_expr_eval.c @@ -0,0 +1,1822 @@ +/* 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. + */ + +/* _ _ + * ap_expr_eval.c, based on ssl_expr_eval.c from mod_ssl + */ + +#include "httpd.h" +#include "http_log.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_request.h" +#include "ap_provider.h" +#include "util_expr_private.h" +#include "util_md5.h" + +#include "apr_lib.h" +#include "apr_fnmatch.h" +#include "apr_base64.h" +#include "apr_sha1.h" + +#include <limits.h> /* for INT_MAX */ + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +APR_HOOK_STRUCT( + APR_HOOK_LINK(expr_lookup) +) + +AP_IMPLEMENT_HOOK_RUN_FIRST(int, expr_lookup, (ap_expr_lookup_parms *parms), + (parms), DECLINED) + +#define LOG_MARK(info) __FILE__, __LINE__, (info)->module_index + +static const char *ap_expr_eval_string_func(ap_expr_eval_ctx_t *ctx, + const ap_expr_t *info, + const ap_expr_t *args); +static const char *ap_expr_eval_re_backref(ap_expr_eval_ctx_t *ctx, + unsigned int n); +static const char *ap_expr_eval_var(ap_expr_eval_ctx_t *ctx, + ap_expr_var_func_t *func, + const void *data); + +/* define AP_EXPR_DEBUG to log the parse tree when parsing an expression */ +#ifdef AP_EXPR_DEBUG +static void expr_dump_tree(const ap_expr_t *e, const server_rec *s, + int loglevel, int indent); +#endif + +/* + * To reduce counting overhead, we only count calls to + * ap_expr_eval_word() and ap_expr_eval(). The max number of + * stack frames is larger by some factor. + */ +#define AP_EXPR_MAX_RECURSION 20 +static int inc_rec(ap_expr_eval_ctx_t *ctx) +{ + if (ctx->reclvl < AP_EXPR_MAX_RECURSION) { + ctx->reclvl++; + return 0; + } + *ctx->err = "Recursion limit reached"; + /* short circuit further evaluation */ + ctx->reclvl = INT_MAX; + return 1; +} + +static const char *ap_expr_eval_word(ap_expr_eval_ctx_t *ctx, + const ap_expr_t *node) +{ + const char *result = ""; + if (inc_rec(ctx)) + return result; + switch (node->node_op) { + case op_Digit: + case op_String: + result = node->node_arg1; + break; + case op_Var: + result = ap_expr_eval_var(ctx, (ap_expr_var_func_t *)node->node_arg1, + node->node_arg2); + break; + case op_Concat: + if (((ap_expr_t *)node->node_arg2)->node_op != op_Concat && + ((ap_expr_t *)node->node_arg1)->node_op != op_Concat) { + const char *s1 = ap_expr_eval_word(ctx, node->node_arg1); + const char *s2 = ap_expr_eval_word(ctx, node->node_arg2); + if (!*s1) + result = s2; + else if (!*s2) + result = s1; + else + result = apr_pstrcat(ctx->p, s1, s2, NULL); + } + else if (((ap_expr_t *)node->node_arg1)->node_op == op_Concat) { + const ap_expr_t *nodep = node; + int n; + int i = 1; + struct iovec *vec; + do { + nodep = nodep->node_arg1; + i++; + } while (nodep->node_op == op_Concat); + vec = apr_palloc(ctx->p, i * sizeof(struct iovec)); + n = i; + nodep = node; + i--; + do { + vec[i].iov_base = (void *)ap_expr_eval_word(ctx, + nodep->node_arg2); + vec[i].iov_len = strlen(vec[i].iov_base); + i--; + nodep = nodep->node_arg1; + } while (nodep->node_op == op_Concat); + vec[i].iov_base = (void *)ap_expr_eval_word(ctx, nodep); + vec[i].iov_len = strlen(vec[i].iov_base); + result = apr_pstrcatv(ctx->p, vec, n, NULL); + } + else { + const ap_expr_t *nodep = node; + int i = 1; + struct iovec *vec; + do { + nodep = nodep->node_arg2; + i++; + } while (nodep->node_op == op_Concat); + vec = apr_palloc(ctx->p, i * sizeof(struct iovec)); + nodep = node; + i = 0; + do { + vec[i].iov_base = (void *)ap_expr_eval_word(ctx, + nodep->node_arg1); + vec[i].iov_len = strlen(vec[i].iov_base); + i++; + nodep = nodep->node_arg2; + } while (nodep->node_op == op_Concat); + vec[i].iov_base = (void *)ap_expr_eval_word(ctx, nodep); + vec[i].iov_len = strlen(vec[i].iov_base); + i++; + result = apr_pstrcatv(ctx->p, vec, i, NULL); + } + break; + case op_StringFuncCall: { + const ap_expr_t *info = node->node_arg1; + const ap_expr_t *args = node->node_arg2; + result = ap_expr_eval_string_func(ctx, info, args); + break; + } + case op_RegexBackref: { + const unsigned int *np = node->node_arg1; + result = ap_expr_eval_re_backref(ctx, *np); + break; + } + default: + *ctx->err = "Internal evaluation error: Unknown word expression node"; + break; + } + if (!result) + result = ""; + ctx->reclvl--; + return result; +} + +static const char *ap_expr_eval_var(ap_expr_eval_ctx_t *ctx, + ap_expr_var_func_t *func, + const void *data) +{ + AP_DEBUG_ASSERT(func != NULL); + AP_DEBUG_ASSERT(data != NULL); + return (*func)(ctx, data); +} + +static const char *ap_expr_eval_re_backref(ap_expr_eval_ctx_t *ctx, unsigned int n) +{ + int len; + + if (!ctx->re_pmatch || !ctx->re_source || !*ctx->re_source + || **ctx->re_source == '\0' || ctx->re_nmatch < n + 1) + return ""; + + len = ctx->re_pmatch[n].rm_eo - ctx->re_pmatch[n].rm_so; + if (len == 0) + return ""; + + return apr_pstrndup(ctx->p, *ctx->re_source + ctx->re_pmatch[n].rm_so, len); +} + +static const char *ap_expr_eval_string_func(ap_expr_eval_ctx_t *ctx, + const ap_expr_t *info, + const ap_expr_t *arg) +{ + ap_expr_string_func_t *func = (ap_expr_string_func_t *)info->node_arg1; + const void *data = info->node_arg2; + + AP_DEBUG_ASSERT(info->node_op == op_StringFuncInfo); + AP_DEBUG_ASSERT(func != NULL); + AP_DEBUG_ASSERT(data != NULL); + return (*func)(ctx, data, ap_expr_eval_word(ctx, arg)); +} + +static int intstrcmp(const char *s1, const char *s2) +{ + apr_int64_t i1 = apr_atoi64(s1); + apr_int64_t i2 = apr_atoi64(s2); + + if (i1 < i2) + return -1; + else if (i1 == i2) + return 0; + else + return 1; +} + +static int ap_expr_eval_comp(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node) +{ + const ap_expr_t *e1 = node->node_arg1; + const ap_expr_t *e2 = node->node_arg2; + switch (node->node_op) { + case op_EQ: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) == 0); + case op_NE: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) != 0); + case op_LT: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) < 0); + case op_LE: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) <= 0); + case op_GT: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) > 0); + case op_GE: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0); + case op_STR_EQ: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) == 0); + case op_STR_NE: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) != 0); + case op_STR_LT: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) < 0); + case op_STR_LE: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) <= 0); + case op_STR_GT: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) > 0); + case op_STR_GE: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0); + case op_IN: { + const char *needle = ap_expr_eval_word(ctx, e1); + if (e2->node_op == op_ListElement) { + do { + const ap_expr_t *val = e2->node_arg1; + AP_DEBUG_ASSERT(e2->node_op == op_ListElement); + if (strcmp(needle, ap_expr_eval_word(ctx, val)) == 0) + return 1; + e2 = e2->node_arg2; + } while (e2 != NULL); + } + else if (e2->node_op == op_ListFuncCall) { + const ap_expr_t *info = e2->node_arg1; + const ap_expr_t *arg = e2->node_arg2; + ap_expr_list_func_t *func = (ap_expr_list_func_t *)info->node_arg1; + apr_array_header_t *haystack; + + AP_DEBUG_ASSERT(func != NULL); + AP_DEBUG_ASSERT(info->node_op == op_ListFuncInfo); + haystack = (*func)(ctx, info->node_arg2, ap_expr_eval_word(ctx, arg)); + if (haystack == NULL) { + return 0; + } + if (ap_array_str_contains(haystack, needle)) { + return 1; + } + } + return 0; + } + case op_REG: + case op_NRE: { + const char *word = ap_expr_eval_word(ctx, e1); + const ap_regex_t *regex = e2->node_arg1; + int result; + + /* + * $0 ... $9 may contain stuff the user wants to keep. Therefore + * we only set them if there are capturing parens in the regex. + */ + if (regex->re_nsub > 0) { + result = (0 == ap_regexec(regex, word, ctx->re_nmatch, + ctx->re_pmatch, 0)); + *ctx->re_source = result ? word : NULL; + } + else { + result = (0 == ap_regexec(regex, word, 0, NULL, 0)); + } + + if (node->node_op == op_REG) + return result; + else + return !result; + } + default: + *ctx->err = "Internal evaluation error: Unknown comp expression node"; + return -1; + } +} + +/* combined string/int comparison for compatibility with ssl_expr */ +static int strcmplex(const char *str1, const char *str2) +{ + int i, n1, n2; + + if (str1 == NULL) + return -1; + if (str2 == NULL) + return +1; + n1 = strlen(str1); + n2 = strlen(str2); + if (n1 > n2) + return 1; + if (n1 < n2) + return -1; + for (i = 0; i < n1; i++) { + if (str1[i] > str2[i]) + return 1; + if (str1[i] < str2[i]) + return -1; + } + return 0; +} + +static int ssl_expr_eval_comp(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node) +{ + const ap_expr_t *e1 = node->node_arg1; + const ap_expr_t *e2 = node->node_arg2; + switch (node->node_op) { + case op_EQ: + case op_STR_EQ: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) == 0); + case op_NE: + case op_STR_NE: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) != 0); + case op_LT: + case op_STR_LT: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) < 0); + case op_LE: + case op_STR_LE: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) <= 0); + case op_GT: + case op_STR_GT: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) > 0); + case op_GE: + case op_STR_GE: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0); + default: + return ap_expr_eval_comp(ctx, node); + } +} + +AP_DECLARE_NONSTD(int) ap_expr_lookup_default(ap_expr_lookup_parms *parms) +{ + return ap_run_expr_lookup(parms); +} + +AP_DECLARE(const char *) ap_expr_parse(apr_pool_t *pool, apr_pool_t *ptemp, + ap_expr_info_t *info, const char *expr, + ap_expr_lookup_fn_t *lookup_fn) +{ + ap_expr_parse_ctx_t ctx; + int rc; + + ctx.pool = pool; + ctx.ptemp = ptemp; + ctx.inputbuf = expr; + ctx.inputlen = strlen(expr); + ctx.inputptr = ctx.inputbuf; + ctx.expr = NULL; + ctx.error = NULL; /* generic bison error message (XXX: usually not very useful, should be axed) */ + ctx.error2 = NULL; /* additional error message */ + ctx.flags = info->flags; + ctx.scan_del = '\0'; + ctx.scan_buf[0] = '\0'; + ctx.scan_ptr = ctx.scan_buf; + ctx.lookup_fn = lookup_fn ? lookup_fn : ap_expr_lookup_default; + ctx.at_start = 1; + + ap_expr_yylex_init(&ctx.scanner); + ap_expr_yyset_extra(&ctx, ctx.scanner); + rc = ap_expr_yyparse(&ctx); + ap_expr_yylex_destroy(ctx.scanner); + if (ctx.error) { + if (ctx.error2) + return apr_psprintf(pool, "%s: %s", ctx.error, ctx.error2); + else + return ctx.error; + } + else if (ctx.error2) { + return ctx.error2; + } + + if (rc) /* XXX can this happen? */ + return "syntax error"; + +#ifdef AP_EXPR_DEBUG + if (ctx.expr) + expr_dump_tree(ctx.expr, NULL, APLOG_NOTICE, 2); +#endif + + info->root_node = ctx.expr; + + return NULL; +} + +AP_DECLARE(ap_expr_info_t*) ap_expr_parse_cmd_mi(const cmd_parms *cmd, + const char *expr, + unsigned int flags, + const char **err, + ap_expr_lookup_fn_t *lookup_fn, + int module_index) +{ + ap_expr_info_t *info = apr_pcalloc(cmd->pool, sizeof(ap_expr_info_t)); + info->filename = cmd->directive->filename; + info->line_number = cmd->directive->line_num; + info->flags = flags; + info->module_index = module_index; + *err = ap_expr_parse(cmd->pool, cmd->temp_pool, info, expr, lookup_fn); + + if (*err) + return NULL; + + return info; +} + +ap_expr_t *ap_expr_make(ap_expr_node_op_e op, const void *a1, const void *a2, + ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *node = apr_palloc(ctx->pool, sizeof(ap_expr_t)); + node->node_op = op; + node->node_arg1 = a1; + node->node_arg2 = a2; + return node; +} + +static ap_expr_t *ap_expr_info_make(int type, const char *name, + ap_expr_parse_ctx_t *ctx, + const ap_expr_t *arg) +{ + ap_expr_t *info = apr_palloc(ctx->pool, sizeof(ap_expr_t)); + ap_expr_lookup_parms parms; + parms.type = type; + parms.flags = ctx->flags; + parms.pool = ctx->pool; + parms.ptemp = ctx->ptemp; + parms.name = name; + parms.func = &info->node_arg1; + parms.data = &info->node_arg2; + parms.err = &ctx->error2; + parms.arg = (arg && arg->node_op == op_String) ? arg->node_arg1 : NULL; + if (ctx->lookup_fn(&parms) != OK) + return NULL; + return info; +} + +ap_expr_t *ap_expr_str_func_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_STRING, name, ctx, arg); + if (!info) + return NULL; + + info->node_op = op_StringFuncInfo; + return ap_expr_make(op_StringFuncCall, info, arg, ctx); +} + +ap_expr_t *ap_expr_list_func_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_LIST, name, ctx, arg); + if (!info) + return NULL; + + info->node_op = op_ListFuncInfo; + return ap_expr_make(op_ListFuncCall, info, arg, ctx); +} + +ap_expr_t *ap_expr_unary_op_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_OP_UNARY, name, ctx, arg); + if (!info) + return NULL; + + info->node_op = op_UnaryOpInfo; + return ap_expr_make(op_UnaryOpCall, info, arg, ctx); +} + +ap_expr_t *ap_expr_binary_op_make(const char *name, const ap_expr_t *arg1, + const ap_expr_t *arg2, ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *args; + ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_OP_BINARY, name, ctx, + arg2); + if (!info) + return NULL; + + info->node_op = op_BinaryOpInfo; + args = ap_expr_make(op_BinaryOpArgs, arg1, arg2, ctx); + return ap_expr_make(op_BinaryOpCall, info, args, ctx); +} + + +ap_expr_t *ap_expr_var_make(const char *name, ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *node = ap_expr_info_make(AP_EXPR_FUNC_VAR, name, ctx, NULL); + if (!node) + return NULL; + + node->node_op = op_Var; + return node; +} + +#ifdef AP_EXPR_DEBUG + +#define MARK APLOG_MARK,loglevel,0,s +#define DUMP_E_E(op, e1, e2) \ + do { ap_log_error(MARK,"%*s%s: %pp %pp", indent, " ", op, e1, e2); \ + if (e1) expr_dump_tree(e1, s, loglevel, indent + 2); \ + if (e2) expr_dump_tree(e2, s, loglevel, indent + 2); \ + } while (0) +#define DUMP_S_E(op, s1, e1) \ + do { ap_log_error(MARK,"%*s%s: '%s' %pp", indent, " ", op, (char *)s1, e1); \ + if (e1) expr_dump_tree(e1, s, loglevel, indent + 2); \ + } while (0) +#define DUMP_S_P(op, s1, p1) \ + ap_log_error(MARK,"%*s%s: '%s' %pp", indent, " ", op, (char *)s1, p1); +#define DUMP_P_P(op, p1, p2) \ + ap_log_error(MARK,"%*s%s: %pp %pp", indent, " ", op, p1, p2); +#define DUMP_S_S(op, s1, s2) \ + ap_log_error(MARK,"%*s%s: '%s' '%s'", indent, " ", op, (char *)s1, (char *)s2) +#define DUMP_P(op, p1) \ + ap_log_error(MARK,"%*s%s: %pp", indent, " ", op, p1); +#define DUMP_IP(op, p1) \ + ap_log_error(MARK,"%*s%s: %d", indent, " ", op, *(int *)p1); +#define DUMP_S(op, s1) \ + ap_log_error(MARK,"%*s%s: '%s'", indent, " ", op, (char *)s1) + +#define CASE_OP(op) case op: name = #op ; break; + +static void expr_dump_tree(const ap_expr_t *e, const server_rec *s, + int loglevel, int indent) +{ + switch (e->node_op) { + /* no arg */ + case op_NOP: + case op_True: + case op_False: + { + char *name; + switch (e->node_op) { + CASE_OP(op_NOP); + CASE_OP(op_True); + CASE_OP(op_False); + default: + ap_assert(0); + } + ap_log_error(MARK, "%*s%s", indent, " ", name); + } + break; + + /* arg1: string, arg2: expr */ + case op_UnaryOpCall: + case op_BinaryOpCall: + case op_BinaryOpArgs: + { + char *name; + switch (e->node_op) { + CASE_OP(op_BinaryOpCall); + CASE_OP(op_UnaryOpCall); + CASE_OP(op_BinaryOpArgs); + default: + ap_assert(0); + } + DUMP_S_E(name, e->node_arg1, e->node_arg2); + } + break; + + /* arg1: expr, arg2: expr */ + case op_Comp: + case op_Not: + case op_Or: + case op_And: + case op_EQ: + case op_NE: + case op_LT: + case op_LE: + case op_GT: + case op_GE: + case op_STR_EQ: + case op_STR_NE: + case op_STR_LT: + case op_STR_LE: + case op_STR_GT: + case op_STR_GE: + case op_IN: + case op_REG: + case op_NRE: + case op_Concat: + case op_StringFuncCall: + case op_ListFuncCall: + case op_ListElement: + { + char *name; + switch (e->node_op) { + CASE_OP(op_Comp); + CASE_OP(op_Not); + CASE_OP(op_Or); + CASE_OP(op_And); + CASE_OP(op_EQ); + CASE_OP(op_NE); + CASE_OP(op_LT); + CASE_OP(op_LE); + CASE_OP(op_GT); + CASE_OP(op_GE); + CASE_OP(op_STR_EQ); + CASE_OP(op_STR_NE); + CASE_OP(op_STR_LT); + CASE_OP(op_STR_LE); + CASE_OP(op_STR_GT); + CASE_OP(op_STR_GE); + CASE_OP(op_IN); + CASE_OP(op_REG); + CASE_OP(op_NRE); + CASE_OP(op_Concat); + CASE_OP(op_StringFuncCall); + CASE_OP(op_ListFuncCall); + CASE_OP(op_ListElement); + default: + ap_assert(0); + } + DUMP_E_E(name, e->node_arg1, e->node_arg2); + } + break; + /* arg1: string */ + case op_Digit: + case op_String: + { + char *name; + switch (e->node_op) { + CASE_OP(op_Digit); + CASE_OP(op_String); + default: + ap_assert(0); + } + DUMP_S(name, e->node_arg1); + } + break; + /* arg1: pointer, arg2: pointer */ + case op_Var: + case op_StringFuncInfo: + case op_UnaryOpInfo: + case op_BinaryOpInfo: + case op_ListFuncInfo: + { + char *name; + switch (e->node_op) { + CASE_OP(op_Var); + CASE_OP(op_StringFuncInfo); + CASE_OP(op_UnaryOpInfo); + CASE_OP(op_BinaryOpInfo); + CASE_OP(op_ListFuncInfo); + default: + ap_assert(0); + } + DUMP_P_P(name, e->node_arg1, e->node_arg2); + } + break; + /* arg1: pointer */ + case op_Regex: + DUMP_P("op_Regex", e->node_arg1); + break; + /* arg1: pointer to int */ + case op_RegexBackref: + DUMP_IP("op_RegexBackref", e->node_arg1); + break; + default: + ap_log_error(MARK, "%*sERROR: INVALID OP %d", indent, " ", e->node_op); + break; + } +} +#endif /* AP_EXPR_DEBUG */ + +static int ap_expr_eval_unary_op(ap_expr_eval_ctx_t *ctx, const ap_expr_t *info, + const ap_expr_t *arg) +{ + ap_expr_op_unary_t *op_func = (ap_expr_op_unary_t *)info->node_arg1; + const void *data = info->node_arg2; + + AP_DEBUG_ASSERT(info->node_op == op_UnaryOpInfo); + AP_DEBUG_ASSERT(op_func != NULL); + AP_DEBUG_ASSERT(data != NULL); + return (*op_func)(ctx, data, ap_expr_eval_word(ctx, arg)); +} + +static int ap_expr_eval_binary_op(ap_expr_eval_ctx_t *ctx, + const ap_expr_t *info, + const ap_expr_t *args) +{ + ap_expr_op_binary_t *op_func = (ap_expr_op_binary_t *)info->node_arg1; + const void *data = info->node_arg2; + const ap_expr_t *a1 = args->node_arg1; + const ap_expr_t *a2 = args->node_arg2; + + AP_DEBUG_ASSERT(info->node_op == op_BinaryOpInfo); + AP_DEBUG_ASSERT(args->node_op == op_BinaryOpArgs); + AP_DEBUG_ASSERT(op_func != NULL); + AP_DEBUG_ASSERT(data != NULL); + return (*op_func)(ctx, data, ap_expr_eval_word(ctx, a1), + ap_expr_eval_word(ctx, a2)); +} + + +static int ap_expr_eval(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node) +{ + const ap_expr_t *e1 = node->node_arg1; + const ap_expr_t *e2 = node->node_arg2; + int result = FALSE; + if (inc_rec(ctx)) + return result; + while (1) { + switch (node->node_op) { + case op_True: + result ^= TRUE; + goto out; + case op_False: + result ^= FALSE; + goto out; + case op_Not: + result = !result; + node = e1; + break; + case op_Or: + do { + if (e1->node_op == op_Not) { + if (!ap_expr_eval(ctx, e1->node_arg1)) { + result ^= TRUE; + goto out; + } + } + else { + if (ap_expr_eval(ctx, e1)) { + result ^= TRUE; + goto out; + } + } + node = node->node_arg2; + e1 = node->node_arg1; + } while (node->node_op == op_Or); + break; + case op_And: + do { + if (e1->node_op == op_Not) { + if (ap_expr_eval(ctx, e1->node_arg1)) { + result ^= FALSE; + goto out; + } + } + else { + if (!ap_expr_eval(ctx, e1)) { + result ^= FALSE; + goto out; + } + } + node = node->node_arg2; + e1 = node->node_arg1; + } while (node->node_op == op_And); + break; + case op_UnaryOpCall: + result ^= ap_expr_eval_unary_op(ctx, e1, e2); + goto out; + case op_BinaryOpCall: + result ^= ap_expr_eval_binary_op(ctx, e1, e2); + goto out; + case op_Comp: + if (ctx->info->flags & AP_EXPR_FLAG_SSL_EXPR_COMPAT) + result ^= ssl_expr_eval_comp(ctx, e1); + else + result ^= ap_expr_eval_comp(ctx, e1); + goto out; + default: + *ctx->err = "Internal evaluation error: Unknown expression node"; + goto out; + } + e1 = node->node_arg1; + e2 = node->node_arg2; + } +out: + ctx->reclvl--; + return result; +} + +AP_DECLARE(int) ap_expr_exec(request_rec *r, const ap_expr_info_t *info, + const char **err) +{ + return ap_expr_exec_re(r, info, 0, NULL, NULL, err); +} + +AP_DECLARE(int) ap_expr_exec_ctx(ap_expr_eval_ctx_t *ctx) +{ + int rc; + + AP_DEBUG_ASSERT(ctx->p != NULL); + /* XXX: allow r, c == NULL */ + AP_DEBUG_ASSERT(ctx->r != NULL); + AP_DEBUG_ASSERT(ctx->c != NULL); + AP_DEBUG_ASSERT(ctx->s != NULL); + AP_DEBUG_ASSERT(ctx->err != NULL); + AP_DEBUG_ASSERT(ctx->info != NULL); + if (ctx->re_pmatch) { + AP_DEBUG_ASSERT(ctx->re_source != NULL); + AP_DEBUG_ASSERT(ctx->re_nmatch > 0); + } + ctx->reclvl = 0; + + *ctx->err = NULL; + if (ctx->info->flags & AP_EXPR_FLAG_STRING_RESULT) { + *ctx->result_string = ap_expr_eval_word(ctx, ctx->info->root_node); + if (*ctx->err != NULL) { + ap_log_rerror(LOG_MARK(ctx->info), APLOG_ERR, 0, ctx->r, + "Evaluation of expression from %s:%d failed: %s", + ctx->info->filename, ctx->info->line_number, *ctx->err); + return -1; + } else { + ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE4, 0, ctx->r, + "Evaluation of string expression from %s:%d gave: %s", + ctx->info->filename, ctx->info->line_number, + *ctx->result_string); + return 1; + } + } + else { + rc = ap_expr_eval(ctx, ctx->info->root_node); + if (*ctx->err != NULL) { + ap_log_rerror(LOG_MARK(ctx->info), APLOG_ERR, 0, ctx->r, + "Evaluation of expression from %s:%d failed: %s", + ctx->info->filename, ctx->info->line_number, *ctx->err); + return -1; + } else { + rc = rc ? 1 : 0; + ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE4, 0, ctx->r, + "Evaluation of expression from %s:%d gave: %d", + ctx->info->filename, ctx->info->line_number, rc); + + if (ctx->vary_this && *ctx->vary_this) + apr_table_merge(ctx->r->headers_out, "Vary", *ctx->vary_this); + + return rc; + } + } +} + +AP_DECLARE(int) ap_expr_exec_re(request_rec *r, const ap_expr_info_t *info, + apr_size_t nmatch, ap_regmatch_t *pmatch, + const char **source, const char **err) +{ + ap_expr_eval_ctx_t ctx; + int dont_vary = (info->flags & AP_EXPR_FLAG_DONT_VARY); + const char *tmp_source = NULL, *vary_this = NULL; + ap_regmatch_t tmp_pmatch[AP_MAX_REG_MATCH]; + + AP_DEBUG_ASSERT((info->flags & AP_EXPR_FLAG_STRING_RESULT) == 0); + + ctx.r = r; + ctx.c = r->connection; + ctx.s = r->server; + ctx.p = r->pool; + ctx.err = err; + ctx.info = info; + ctx.re_nmatch = nmatch; + ctx.re_pmatch = pmatch; + ctx.re_source = source; + ctx.vary_this = dont_vary ? NULL : &vary_this; + ctx.data = NULL; + + if (!pmatch) { + ctx.re_nmatch = AP_MAX_REG_MATCH; + ctx.re_pmatch = tmp_pmatch; + ctx.re_source = &tmp_source; + } + + return ap_expr_exec_ctx(&ctx); +} + +AP_DECLARE(const char *) ap_expr_str_exec_re(request_rec *r, + const ap_expr_info_t *info, + apr_size_t nmatch, + ap_regmatch_t *pmatch, + const char **source, + const char **err) +{ + ap_expr_eval_ctx_t ctx; + int dont_vary, rc; + const char *tmp_source, *vary_this; + ap_regmatch_t tmp_pmatch[AP_MAX_REG_MATCH]; + const char *result; + + AP_DEBUG_ASSERT(info->flags & AP_EXPR_FLAG_STRING_RESULT); + + if (info->root_node->node_op == op_String) { + /* short-cut for constant strings */ + *err = NULL; + return (const char *)info->root_node->node_arg1; + } + + tmp_source = NULL; + vary_this = NULL; + + dont_vary = (info->flags & AP_EXPR_FLAG_DONT_VARY); + + ctx.r = r; + ctx.c = r->connection; + ctx.s = r->server; + ctx.p = r->pool; + ctx.err = err; + ctx.info = info; + ctx.re_nmatch = nmatch; + ctx.re_pmatch = pmatch; + ctx.re_source = source; + ctx.vary_this = dont_vary ? NULL : &vary_this; + ctx.data = NULL; + ctx.result_string = &result; + + if (!pmatch) { + ctx.re_nmatch = AP_MAX_REG_MATCH; + ctx.re_pmatch = tmp_pmatch; + ctx.re_source = &tmp_source; + } + + rc = ap_expr_exec_ctx(&ctx); + if (rc > 0) + return result; + else if (rc < 0) + return NULL; + else + ap_assert(0); + /* Not reached */ + return NULL; +} + +AP_DECLARE(const char *) ap_expr_str_exec(request_rec *r, + const ap_expr_info_t *info, + const char **err) +{ + return ap_expr_str_exec_re(r, info, 0, NULL, NULL, err); +} + + +static void add_vary(ap_expr_eval_ctx_t *ctx, const char *name) +{ + if (!ctx->vary_this) + return; + + if (*ctx->vary_this) { + *ctx->vary_this = apr_pstrcat(ctx->p, *ctx->vary_this, ", ", name, + NULL); + } + else { + *ctx->vary_this = name; + } +} + +static const char *req_table_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + const char *name = (const char *)data; + apr_table_t *t; + if (!ctx->r) + return ""; + + if (name[2] == 's') { /* resp */ + /* Try r->headers_out first, fall back on err_headers_out. */ + const char *v = apr_table_get(ctx->r->headers_out, arg); + if (v) { + return v; + } + t = ctx->r->err_headers_out; + } + else if (name[0] == 'n') /* notes */ + t = ctx->r->notes; + else if (name[3] == 'e') /* reqenv */ + t = ctx->r->subprocess_env; + else if (name[3] == '_') /* req_novary */ + t = ctx->r->headers_in; + else { /* req, http */ + t = ctx->r->headers_in; + /* Skip the 'Vary: Host' header combination + * as indicated in rfc7231 section-7.1.4 + */ + if (strcasecmp(arg, "Host")){ + add_vary(ctx, arg); + } + } + return apr_table_get(t, arg); +} + +static const char *env_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + const char *res; + /* this order is for ssl_expr compatibility */ + if (ctx->r) { + if ((res = apr_table_get(ctx->r->notes, arg)) != NULL) + return res; + else if ((res = apr_table_get(ctx->r->subprocess_env, arg)) != NULL) + return res; + } + return getenv(arg); +} + +static const char *osenv_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + return getenv(arg); +} + +static const char *tolower_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + char *result = apr_pstrdup(ctx->p, arg); + ap_str_tolower(result); + return result; +} + +static const char *toupper_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + char *result = apr_pstrdup(ctx->p, arg); + ap_str_toupper(result); + return result; +} + +static const char *escape_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + return ap_escape_uri(ctx->p, arg); +} + +static const char *base64_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + return ap_pbase64encode(ctx->p, (char *)arg); +} + +static const char *unbase64_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + return ap_pbase64decode(ctx->p, arg); +} + +static const char *sha1_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + apr_sha1_ctx_t context; + apr_byte_t sha1[APR_SHA1_DIGESTSIZE]; + char *out; + + out = apr_palloc(ctx->p, APR_SHA1_DIGESTSIZE*2+1); + + apr_sha1_init(&context); + apr_sha1_update(&context, arg, strlen(arg)); + apr_sha1_final(sha1, &context); + + ap_bin2hex(sha1, APR_SHA1_DIGESTSIZE, out); + + return out; +} + +static const char *md5_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + return ap_md5(ctx->p, (const unsigned char *)arg); +} + + +#define MAX_FILE_SIZE 10*1024*1024 +static const char *file_func(ap_expr_eval_ctx_t *ctx, const void *data, + char *arg) +{ + apr_file_t *fp; + char *buf; + apr_off_t offset; + apr_size_t len; + apr_finfo_t finfo; + + if (apr_file_open(&fp, arg, APR_READ|APR_BUFFERED, + APR_OS_DEFAULT, ctx->p) != APR_SUCCESS) { + *ctx->err = apr_psprintf(ctx->p, "Cannot open file %s", arg); + return ""; + } + apr_file_info_get(&finfo, APR_FINFO_SIZE, fp); + if (finfo.size > MAX_FILE_SIZE) { + *ctx->err = apr_psprintf(ctx->p, "File %s too large", arg); + apr_file_close(fp); + return ""; + } + len = (apr_size_t)finfo.size; + if (len == 0) { + apr_file_close(fp); + return ""; + } + else { + if ((buf = (char *)apr_palloc(ctx->p, sizeof(char)*(len+1))) == NULL) { + *ctx->err = "Cannot allocate memory"; + apr_file_close(fp); + return ""; + } + offset = 0; + apr_file_seek(fp, APR_SET, &offset); + if (apr_file_read(fp, buf, &len) != APR_SUCCESS) { + *ctx->err = apr_psprintf(ctx->p, "Cannot read from file %s", arg); + apr_file_close(fp); + return ""; + } + buf[len] = '\0'; + } + apr_file_close(fp); + return buf; +} + +static const char *filesize_func(ap_expr_eval_ctx_t *ctx, const void *data, + char *arg) +{ + apr_finfo_t sb; + if (apr_stat(&sb, arg, APR_FINFO_MIN, ctx->p) == APR_SUCCESS + && sb.filetype == APR_REG && sb.size > 0) + return apr_psprintf(ctx->p, "%" APR_OFF_T_FMT, sb.size); + else + return "0"; +} + +static const char *unescape_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + char *result = apr_pstrdup(ctx->p, arg); + int ret = ap_unescape_url_keep2f(result, 0); + if (ret == OK) + return result; + ap_log_rerror(LOG_MARK(ctx->info), APLOG_DEBUG, 0, ctx->r, APLOGNO(00538) + "%s %% escape in unescape('%s') at %s:%d", + ret == HTTP_BAD_REQUEST ? "Bad" : "Forbidden", arg, + ctx->info->filename, ctx->info->line_number); + return ""; +} + +static int op_nz(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + const char *name = (const char *)data; + if (name[0] == 'z') + return (arg[0] == '\0'); + else + return (arg[0] != '\0'); +} + +static int op_file_min(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + apr_finfo_t sb; + const char *name = (const char *)data; + if (apr_stat(&sb, arg, APR_FINFO_MIN, ctx->p) != APR_SUCCESS) + return FALSE; + switch (name[0]) { + case 'd': + return (sb.filetype == APR_DIR); + case 'e': + return TRUE; + case 'f': + return (sb.filetype == APR_REG); + case 's': + return (sb.filetype == APR_REG && sb.size > 0); + default: + ap_assert(0); + } + return FALSE; +} + +static int op_file_link(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ +#if !defined(OS2) + apr_finfo_t sb; + if (apr_stat(&sb, arg, APR_FINFO_MIN | APR_FINFO_LINK, ctx->p) == APR_SUCCESS + && sb.filetype == APR_LNK) { + return TRUE; + } +#endif + return FALSE; +} + +static int op_file_xbit(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + apr_finfo_t sb; + if (apr_stat(&sb, arg, APR_FINFO_PROT| APR_FINFO_LINK, ctx->p) == APR_SUCCESS + && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) { + return TRUE; + } + return FALSE; +} + +static int op_url_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + int rc = FALSE; + request_rec *rsub, *r = ctx->r; + if (!r) + return FALSE; + /* avoid some infinite recursions */ + if (r->main && r->main->uri && r->uri && strcmp(r->main->uri, r->uri) == 0) + return FALSE; + + rsub = ap_sub_req_lookup_uri(arg, r, NULL); + if (rsub->status < 400) { + rc = TRUE; + } + ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE5, 0, r, + "Subrequest for -U %s at %s:%d gave status: %d", + arg, ctx->info->filename, ctx->info->line_number, + rsub->status); + ap_destroy_sub_req(rsub); + return rc; +} + +static int op_file_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + int rc = FALSE; + apr_finfo_t sb; + request_rec *rsub, *r = ctx->r; + if (!r) + return FALSE; + rsub = ap_sub_req_lookup_file(arg, r, NULL); + if (rsub->status < 300 && + /* double-check that file exists since default result is 200 */ + apr_stat(&sb, rsub->filename, APR_FINFO_MIN, ctx->p) == APR_SUCCESS) { + rc = TRUE; + } + ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE5, 0, r, + "Subrequest for -F %s at %s:%d gave status: %d", + arg, ctx->info->filename, ctx->info->line_number, + rsub->status); + ap_destroy_sub_req(rsub); + return rc; +} + + +APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); +static APR_OPTIONAL_FN_TYPE(ssl_is_https) *is_https = NULL; + +APR_DECLARE_OPTIONAL_FN(int, http2_is_h2, (conn_rec *)); +static APR_OPTIONAL_FN_TYPE(http2_is_h2) *is_http2 = NULL; + +static const char *conn_var_names[] = { + "HTTPS", /* 0 */ + "IPV6", /* 1 */ + "CONN_LOG_ID", /* 2 */ + "CONN_REMOTE_ADDR", /* 3 */ + "HTTP2", /* 4 */ + NULL +}; + +static const char *conn_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) +{ + int index = ((const char **)data - conn_var_names); + conn_rec *c = ctx->c; + if (!c) + return ""; + + switch (index) { + case 0: + if (is_https && is_https(c)) + return "on"; + else + return "off"; + case 1: +#if APR_HAVE_IPV6 + { + apr_sockaddr_t *addr = c->client_addr; + if (addr->family == AF_INET6 + && !IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr)) + return "on"; + else + return "off"; + } +#else + return "off"; +#endif + case 2: + return c->log_id; + case 3: + return c->client_ip; + case 4: + if (is_http2 && is_http2(c)) + return "on"; + else + return "off"; + default: + ap_assert(0); + return NULL; + } +} + +static const char *request_var_names[] = { + "REQUEST_METHOD", /* 0 */ + "REQUEST_SCHEME", /* 1 */ + "REQUEST_URI", /* 2 */ + "REQUEST_FILENAME", /* 3 */ + "REMOTE_HOST", /* 4 */ + "REMOTE_IDENT", /* 5 */ + "REMOTE_USER", /* 6 */ + "SERVER_ADMIN", /* 7 */ + "SERVER_NAME", /* 8 */ + "SERVER_PORT", /* 9 */ + "SERVER_PROTOCOL", /* 10 */ + "SCRIPT_FILENAME", /* 11 */ + "PATH_INFO", /* 12 */ + "QUERY_STRING", /* 13 */ + "IS_SUBREQ", /* 14 */ + "DOCUMENT_ROOT", /* 15 */ + "AUTH_TYPE", /* 16 */ + "THE_REQUEST", /* 17 */ + "CONTENT_TYPE", /* 18 */ + "HANDLER", /* 19 */ + "REQUEST_LOG_ID", /* 20 */ + "SCRIPT_USER", /* 21 */ + "SCRIPT_GROUP", /* 22 */ + "DOCUMENT_URI", /* 23 */ + "LAST_MODIFIED", /* 24 */ + "CONTEXT_PREFIX", /* 25 */ + "CONTEXT_DOCUMENT_ROOT", /* 26 */ + "REQUEST_STATUS", /* 27 */ + "REMOTE_ADDR", /* 28 */ + "REMOTE_PORT", /* 29 */ + NULL +}; + +static const char *request_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) +{ + int index = ((const char **)data - request_var_names); + request_rec *r = ctx->r; + if (!r) + return ""; + + switch (index) { + case 0: + return r->method; + case 1: + return ap_http_scheme(r); + case 2: + return r->uri; + case 3: + return r->filename; + case 4: + return ap_get_useragent_host(r, REMOTE_NAME, NULL); + case 5: + return ap_get_remote_logname(r); + case 6: + return r->user; + case 7: + return r->server->server_admin; + case 8: + return ap_get_server_name_for_url(r); + case 9: + return apr_psprintf(ctx->p, "%u", ap_get_server_port(r)); + case 10: + return r->protocol; + case 11: + return r->filename; + case 12: + return r->path_info; + case 13: + return r->args; + case 14: + return (r->main != NULL ? "true" : "false"); + case 15: + return ap_document_root(r); + case 16: + return r->ap_auth_type; + case 17: + return r->the_request; + case 18: + return r->content_type; + case 19: + return r->handler; + case 20: + return r->log_id; + case 21: + { + char *result = ""; + if (r->finfo.valid & APR_FINFO_USER) + apr_uid_name_get(&result, r->finfo.user, ctx->p); + return result; + } + case 22: + { + char *result = ""; + if (r->finfo.valid & APR_FINFO_USER) + apr_gid_name_get(&result, r->finfo.group, ctx->p); + return result; + } + case 23: + { + const char *uri = apr_table_get(r->subprocess_env, "DOCUMENT_URI"); + return uri ? uri : r->uri; + } + case 24: + { + apr_time_exp_t tm; + apr_time_exp_lt(&tm, r->mtime); + return apr_psprintf(ctx->p, "%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); + } + case 25: + return ap_context_prefix(r); + case 26: + return ap_context_document_root(r); + case 27: + return r->status ? apr_psprintf(ctx->p, "%d", r->status) : ""; + case 28: + return r->useragent_ip; + case 29: + return apr_psprintf(ctx->p, "%u", ctx->c->client_addr->port); + default: + ap_assert(0); + return NULL; + } +} + +static const char *req_header_var_names[] = { + "HTTP_USER_AGENT", /* 0 */ + "HTTP_PROXY_CONNECTION", /* 1 */ + "HTTP_REFERER", /* 2 */ + "HTTP_COOKIE", /* 3 */ + "HTTP_FORWARDED", /* 4 */ + "HTTP_HOST", /* 5 */ + "HTTP_ACCEPT", /* 6 */ + NULL +}; + +static const char *req_header_header_names[] = { + "User-Agent", + "Proxy-Connection", + "Referer", + "Cookie", + "Forwarded", + "Host", + "Accept" +}; + +static const char *req_header_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) +{ + const char **varname = (const char **)data; + int index = (varname - req_header_var_names); + const char *name; + + AP_DEBUG_ASSERT(index < 7); + if (!ctx->r) + return ""; + + name = req_header_header_names[index]; + /* Skip the 'Vary: Host' header combination + * as indicated in rfc7231 section-7.1.4 + */ + if (strcasecmp(name, "Host")){ + add_vary(ctx, name); + } + return apr_table_get(ctx->r->headers_in, name); +} + +static const char *misc_var_names[] = { + "TIME_YEAR", /* 0 */ + "TIME_MON", /* 1 */ + "TIME_DAY", /* 2 */ + "TIME_HOUR", /* 3 */ + "TIME_MIN", /* 4 */ + "TIME_SEC", /* 5 */ + "TIME_WDAY", /* 6 */ + "TIME", /* 7 */ + "SERVER_SOFTWARE", /* 8 */ + "API_VERSION", /* 9 */ + NULL +}; + +static const char *misc_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) +{ + apr_time_exp_t tm; + int index = ((const char **)data - misc_var_names); + apr_time_exp_lt(&tm, apr_time_now()); + + switch (index) { + case 0: + return apr_psprintf(ctx->p, "%02d%02d", (tm.tm_year / 100) + 19, + tm.tm_year % 100); + case 1: + return apr_psprintf(ctx->p, "%02d", tm.tm_mon+1); + case 2: + return apr_psprintf(ctx->p, "%02d", tm.tm_mday); + case 3: + return apr_psprintf(ctx->p, "%02d", tm.tm_hour); + case 4: + return apr_psprintf(ctx->p, "%02d", tm.tm_min); + case 5: + return apr_psprintf(ctx->p, "%02d", tm.tm_sec); + case 6: + return apr_psprintf(ctx->p, "%d", tm.tm_wday); + case 7: + return apr_psprintf(ctx->p, "%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); + case 8: + return ap_get_server_banner(); + case 9: + return apr_itoa(ctx->p, MODULE_MAGIC_NUMBER_MAJOR); + default: + ap_assert(0); + } + + return NULL; +} + +static int subnet_parse_arg(ap_expr_lookup_parms *parms) +{ + apr_ipsubnet_t *subnet; + const char *addr = parms->arg; + const char *mask; + apr_status_t ret; + + if (!parms->arg) { + *parms->err = apr_psprintf(parms->ptemp, + "-%s requires subnet/netmask as constant argument", + parms->name); + return !OK; + } + + mask = ap_strchr_c(addr, '/'); + if (mask) { + addr = apr_pstrmemdup(parms->ptemp, addr, mask - addr); + mask++; + } + + ret = apr_ipsubnet_create(&subnet, addr, mask, parms->pool); + if (ret != APR_SUCCESS) { + *parms->err = "parsing of subnet/netmask failed"; + return !OK; + } + + *parms->data = subnet; + return OK; +} + +static int op_ipmatch(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg1, + const char *arg2) +{ + apr_ipsubnet_t *subnet = (apr_ipsubnet_t *)data; + apr_sockaddr_t *saddr; + + AP_DEBUG_ASSERT(subnet != NULL); + + /* maybe log an error if this goes wrong? */ + if (apr_sockaddr_info_get(&saddr, arg1, APR_UNSPEC, 0, 0, ctx->p) != APR_SUCCESS) + return FALSE; + + return apr_ipsubnet_test(subnet, saddr); +} + +static int op_R(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg1) +{ + apr_ipsubnet_t *subnet = (apr_ipsubnet_t *)data; + + AP_DEBUG_ASSERT(subnet != NULL); + + if (!ctx->r) + return FALSE; + + return apr_ipsubnet_test(subnet, ctx->r->useragent_addr); +} + +static int op_T(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + switch (arg[0]) { + case '\0': + return FALSE; + case 'o': + case 'O': + return strcasecmp(arg, "off") == 0 ? FALSE : TRUE; + case 'n': + case 'N': + return strcasecmp(arg, "no") == 0 ? FALSE : TRUE; + case 'f': + case 'F': + return strcasecmp(arg, "false") == 0 ? FALSE : TRUE; + case '0': + return arg[1] == '\0' ? FALSE : TRUE; + default: + return TRUE; + } +} + +static int op_fnmatch(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg1, const char *arg2) +{ + return (APR_SUCCESS == apr_fnmatch(arg2, arg1, APR_FNM_PATHNAME)); +} + +static int op_strmatch(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg1, const char *arg2) +{ + return (APR_SUCCESS == apr_fnmatch(arg2, arg1, 0)); +} + +static int op_strcmatch(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg1, const char *arg2) +{ + return (APR_SUCCESS == apr_fnmatch(arg2, arg1, APR_FNM_CASE_BLIND)); +} + +struct expr_provider_single { + const void *func; + const char *name; + ap_expr_lookup_fn_t *arg_parsing_func; + int restricted; +}; + +struct expr_provider_multi { + const void *func; + const char **names; +}; + +static const struct expr_provider_multi var_providers[] = { + { misc_var_fn, misc_var_names }, + { req_header_var_fn, req_header_var_names }, + { request_var_fn, request_var_names }, + { conn_var_fn, conn_var_names }, + { NULL, NULL } +}; + +static const struct expr_provider_single string_func_providers[] = { + { osenv_func, "osenv", NULL, 0 }, + { env_func, "env", NULL, 0 }, + { req_table_func, "resp", NULL, 0 }, + { req_table_func, "req", NULL, 0 }, + /* 'http' as alias for 'req' for compatibility with ssl_expr */ + { req_table_func, "http", NULL, 0 }, + { req_table_func, "note", NULL, 0 }, + { req_table_func, "reqenv", NULL, 0 }, + { req_table_func, "req_novary", NULL, 0 }, + { tolower_func, "tolower", NULL, 0 }, + { toupper_func, "toupper", NULL, 0 }, + { escape_func, "escape", NULL, 0 }, + { unescape_func, "unescape", NULL, 0 }, + { file_func, "file", NULL, 1 }, + { filesize_func, "filesize", NULL, 1 }, + { base64_func, "base64", NULL, 0 }, + { unbase64_func, "unbase64", NULL, 0 }, + { sha1_func, "sha1", NULL, 0 }, + { md5_func, "md5", NULL, 0 }, + { NULL, NULL, NULL} +}; + +static const struct expr_provider_single unary_op_providers[] = { + { op_nz, "n", NULL, 0 }, + { op_nz, "z", NULL, 0 }, + { op_R, "R", subnet_parse_arg, 0 }, + { op_T, "T", NULL, 0 }, + { op_file_min, "d", NULL, 1 }, + { op_file_min, "e", NULL, 1 }, + { op_file_min, "f", NULL, 1 }, + { op_file_min, "s", NULL, 1 }, + { op_file_link, "L", NULL, 1 }, + { op_file_link, "h", NULL, 1 }, + { op_file_xbit, "x", NULL, 1 }, + { op_file_subr, "F", NULL, 0 }, + { op_url_subr, "U", NULL, 0 }, + { op_url_subr, "A", NULL, 0 }, + { NULL, NULL, NULL } +}; + +static const struct expr_provider_single binary_op_providers[] = { + { op_ipmatch, "ipmatch", subnet_parse_arg, 0 }, + { op_fnmatch, "fnmatch", NULL, 0 }, + { op_strmatch, "strmatch", NULL, 0 }, + { op_strcmatch, "strcmatch", NULL, 0 }, + { NULL, NULL, NULL } +}; + +static int core_expr_lookup(ap_expr_lookup_parms *parms) +{ + switch (parms->type) { + case AP_EXPR_FUNC_VAR: { + const struct expr_provider_multi *prov = var_providers; + while (prov->func) { + const char **name = prov->names; + while (*name) { + if (strcasecmp(*name, parms->name) == 0) { + *parms->func = prov->func; + *parms->data = name; + return OK; + } + name++; + } + prov++; + } + } + break; + case AP_EXPR_FUNC_STRING: + case AP_EXPR_FUNC_OP_UNARY: + case AP_EXPR_FUNC_OP_BINARY: { + const struct expr_provider_single *prov = NULL; + switch (parms->type) { + case AP_EXPR_FUNC_STRING: + prov = string_func_providers; + break; + case AP_EXPR_FUNC_OP_UNARY: + prov = unary_op_providers; + break; + case AP_EXPR_FUNC_OP_BINARY: + prov = binary_op_providers; + break; + default: + ap_assert(0); + } + while (prov && prov->func) { + int match; + if (parms->type == AP_EXPR_FUNC_OP_UNARY) + match = !strcmp(prov->name, parms->name); + else + match = !strcasecmp(prov->name, parms->name); + if (match) { + if ((parms->flags & AP_EXPR_FLAG_RESTRICTED) + && prov->restricted) { + *parms->err = + apr_psprintf(parms->ptemp, + "%s%s not available in restricted context", + (parms->type == AP_EXPR_FUNC_STRING) ? "" : "-", + prov->name); + return !OK; + } + *parms->func = prov->func; + if (prov->arg_parsing_func) { + return prov->arg_parsing_func(parms); + } + else { + *parms->data = prov->name; + return OK; + } + } + prov++; + } + } + break; + default: + break; + } + return DECLINED; +} + +static int expr_lookup_not_found(ap_expr_lookup_parms *parms) +{ + const char *type; + const char *prefix = ""; + + switch (parms->type) { + case AP_EXPR_FUNC_VAR: + type = "Variable"; + break; + case AP_EXPR_FUNC_STRING: + type = "Function"; + break; + case AP_EXPR_FUNC_LIST: + type = "List-returning function"; + break; + case AP_EXPR_FUNC_OP_UNARY: + type = "Unary operator"; + break; + case AP_EXPR_FUNC_OP_BINARY: + type = "Binary operator"; + break; + default: + *parms->err = "Inavalid expression type in expr_lookup"; + return !OK; + } + if ( parms->type == AP_EXPR_FUNC_OP_UNARY + || parms->type == AP_EXPR_FUNC_OP_BINARY) { + prefix = "-"; + } + *parms->err = apr_psprintf(parms->ptemp, "%s '%s%s' does not exist", type, + prefix, parms->name); + return !OK; +} + +static int ap_expr_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); + is_http2 = APR_RETRIEVE_OPTIONAL_FN(http2_is_h2); + apr_pool_cleanup_register(pconf, &is_https, ap_pool_cleanup_set_null, + apr_pool_cleanup_null); + return OK; +} + +void ap_expr_init(apr_pool_t *p) +{ + ap_hook_expr_lookup(core_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_expr_lookup(expr_lookup_not_found, NULL, NULL, APR_HOOK_REALLY_LAST); + ap_hook_post_config(ap_expr_post_config, NULL, NULL, APR_HOOK_MIDDLE); +} + diff --git a/server/util_expr_parse.c b/server/util_expr_parse.c new file mode 100644 index 0000000..bcf0173 --- /dev/null +++ b/server/util_expr_parse.c @@ -0,0 +1,2130 @@ +/* A Bison parser, made by GNU Bison 2.5. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.5" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + +/* Using locations. */ +#define YYLSP_NEEDED 0 + +/* Substitute the variable and function names. */ +#define yyparse ap_expr_yyparse +#define yylex ap_expr_yylex +#define yyerror ap_expr_yyerror +#define yylval ap_expr_yylval +#define yychar ap_expr_yychar +#define yydebug ap_expr_yydebug +#define yynerrs ap_expr_yynerrs + + +/* Copy the first part of user declarations. */ + +/* Line 268 of yacc.c */ +#line 31 "util_expr_parse.y" + +#include "util_expr_private.h" + + +/* Line 268 of yacc.c */ +#line 84 "util_expr_parse.c" + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 1 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + T_TRUE = 258, + T_FALSE = 259, + T_EXPR_BOOL = 260, + T_EXPR_STRING = 261, + T_ERROR = 262, + T_DIGIT = 263, + T_ID = 264, + T_STRING = 265, + T_REGEX = 266, + T_REGEX_I = 267, + T_REGEX_BACKREF = 268, + T_OP_UNARY = 269, + T_OP_BINARY = 270, + T_STR_BEGIN = 271, + T_STR_END = 272, + T_VAR_BEGIN = 273, + T_VAR_END = 274, + T_OP_EQ = 275, + T_OP_NE = 276, + T_OP_LT = 277, + T_OP_LE = 278, + T_OP_GT = 279, + T_OP_GE = 280, + T_OP_REG = 281, + T_OP_NRE = 282, + T_OP_IN = 283, + T_OP_STR_EQ = 284, + T_OP_STR_NE = 285, + T_OP_STR_LT = 286, + T_OP_STR_LE = 287, + T_OP_STR_GT = 288, + T_OP_STR_GE = 289, + T_OP_CONCAT = 290, + T_OP_OR = 291, + T_OP_AND = 292, + T_OP_NOT = 293 + }; +#endif + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +{ + +/* Line 293 of yacc.c */ +#line 35 "util_expr_parse.y" + + char *cpVal; + ap_expr_t *exVal; + int num; + + + +/* Line 293 of yacc.c */ +#line 166 "util_expr_parse.c" +} YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + + +/* Copy the second part of user declarations. */ + +/* Line 343 of yacc.c */ +#line 102 "util_expr_parse.y" + +#include "util_expr_private.h" +#define yyscanner ctx->scanner + +int ap_expr_yylex(YYSTYPE *lvalp, void *scanner); + + +/* Line 343 of yacc.c */ +#line 186 "util_expr_parse.c" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include <libintl.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int yyi) +#else +static int +YYID (yyi) + int yyi; +#endif +{ + return yyi; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include <alloca.h> /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include <malloc.h> /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 28 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 128 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 45 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 14 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 53 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 96 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 293 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 39, 40, 2, 2, 43, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 44, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 41, 2, 42, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 6, 9, 11, 13, 15, 18, 22, + 26, 28, 31, 35, 39, 41, 45, 49, 53, 57, + 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, + 101, 103, 107, 109, 113, 116, 118, 120, 122, 124, + 126, 130, 136, 138, 142, 144, 146, 148, 152, 155, + 157, 159, 161, 166 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 46, 0, -1, 5, 47, -1, 6, 51, -1, 7, + -1, 3, -1, 4, -1, 38, 47, -1, 47, 36, + 47, -1, 47, 37, 47, -1, 48, -1, 14, 54, + -1, 54, 15, 54, -1, 39, 47, 40, -1, 7, + -1, 54, 20, 54, -1, 54, 21, 54, -1, 54, + 22, 54, -1, 54, 23, 54, -1, 54, 24, 54, + -1, 54, 25, 54, -1, 54, 29, 54, -1, 54, + 30, 54, -1, 54, 31, 54, -1, 54, 32, 54, + -1, 54, 33, 54, -1, 54, 34, 54, -1, 54, + 28, 49, -1, 54, 26, 55, -1, 54, 27, 55, + -1, 57, -1, 41, 50, 42, -1, 54, -1, 50, + 43, 54, -1, 51, 52, -1, 52, -1, 7, -1, + 10, -1, 53, -1, 56, -1, 18, 9, 19, -1, + 18, 9, 44, 51, 19, -1, 8, -1, 54, 35, + 54, -1, 53, -1, 56, -1, 58, -1, 16, 51, + 17, -1, 16, 17, -1, 11, -1, 12, -1, 13, + -1, 9, 39, 54, 40, -1, 9, 39, 54, 40, + -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 112, 112, 113, 114, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 129, 130, 131, 132, 133, + 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 146, 147, 150, 151, 154, 155, 156, 159, 160, 161, + 164, 165, 168, 169, 170, 171, 172, 173, 174, 177, + 186, 197, 204, 207 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "T_TRUE", "T_FALSE", "T_EXPR_BOOL", + "T_EXPR_STRING", "T_ERROR", "T_DIGIT", "T_ID", "T_STRING", "T_REGEX", + "T_REGEX_I", "T_REGEX_BACKREF", "T_OP_UNARY", "T_OP_BINARY", + "T_STR_BEGIN", "T_STR_END", "T_VAR_BEGIN", "T_VAR_END", "T_OP_EQ", + "T_OP_NE", "T_OP_LT", "T_OP_LE", "T_OP_GT", "T_OP_GE", "T_OP_REG", + "T_OP_NRE", "T_OP_IN", "T_OP_STR_EQ", "T_OP_STR_NE", "T_OP_STR_LT", + "T_OP_STR_LE", "T_OP_STR_GT", "T_OP_STR_GE", "T_OP_CONCAT", "T_OP_OR", + "T_OP_AND", "T_OP_NOT", "'('", "')'", "'{'", "'}'", "','", "':'", + "$accept", "root", "expr", "comparison", "wordlist", "words", "string", + "strpart", "var", "word", "regex", "backref", "lstfunccall", + "strfunccall", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 40, + 41, 123, 125, 44, 58 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 45, 46, 46, 46, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 49, 49, 50, 50, 51, 51, 51, 52, 52, 52, + 53, 53, 54, 54, 54, 54, 54, 54, 54, 55, + 55, 56, 57, 58 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 2, 2, 1, 1, 1, 2, 3, 3, + 1, 2, 3, 3, 1, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 1, 3, 1, 3, 2, 1, 1, 1, 1, 1, + 3, 5, 1, 3, 1, 1, 1, 3, 2, 1, + 1, 1, 4, 4 +}; + +/* YYDEFACT[STATE-NAME] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 0, 0, 4, 0, 5, 6, 14, 42, 0, + 51, 0, 0, 0, 0, 0, 2, 10, 44, 0, + 45, 46, 36, 37, 3, 35, 38, 39, 1, 0, + 11, 48, 0, 0, 7, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 34, 0, 47, 40, 0, + 13, 8, 9, 12, 15, 16, 17, 18, 19, 20, + 49, 50, 28, 29, 0, 0, 27, 30, 21, 22, + 23, 24, 25, 26, 43, 53, 0, 0, 0, 32, + 41, 0, 31, 0, 52, 33 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 4, 16, 17, 76, 88, 24, 25, 18, 19, + 72, 20, 77, 21 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -35 +static const yytype_int8 yypact[] = +{ + 48, 60, 73, -35, 7, -35, -35, -35, -35, -34, + -35, 43, 8, 11, 60, 60, 86, -35, -35, 80, + -35, -35, -35, -35, 108, -35, -35, -35, -35, 43, + 25, -35, 79, -17, -35, -8, 60, 60, 43, 43, + 43, 43, 43, 43, 43, 5, 5, 0, 43, 43, + 43, 43, 43, 43, 43, -35, -27, -35, -35, 73, + -35, 86, 3, 25, 25, 25, 25, 25, 25, 25, + -35, -35, -35, -35, 23, 43, -35, -35, 25, 25, + 25, 25, 25, 25, 25, -35, 106, 43, 85, 25, + -35, -21, -35, 43, -35, 25 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -35, -35, 57, -35, -35, -35, -9, -20, -2, -5, + -4, -1, -35, -35 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 26, 27, 58, 32, 55, 29, 30, 28, 54, 74, + 26, 27, 55, 85, 54, 22, 70, 71, 23, 94, + 33, 10, 26, 27, 56, 31, 13, 59, 36, 37, + 26, 27, 60, 63, 64, 65, 66, 67, 68, 69, + 37, 75, 73, 78, 79, 80, 81, 82, 83, 84, + 86, 8, 9, 1, 2, 3, 10, 26, 27, 12, + 54, 13, 87, 5, 6, 0, 55, 7, 8, 9, + 89, 34, 35, 10, 11, 0, 12, 0, 13, 0, + 22, 0, 91, 23, 26, 27, 10, 0, 95, 23, + 0, 13, 10, 61, 62, 38, 57, 13, 14, 15, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 23, 0, 23, 10, + 0, 10, 36, 37, 13, 90, 13, 92, 93 +}; + +#define yypact_value_is_default(yystate) \ + ((yystate) == (-35)) + +#define yytable_value_is_error(yytable_value) \ + YYID (0) + +static const yytype_int8 yycheck[] = +{ + 2, 2, 19, 12, 24, 39, 11, 0, 35, 9, + 12, 12, 32, 40, 35, 7, 11, 12, 10, 40, + 9, 13, 24, 24, 29, 17, 18, 44, 36, 37, + 32, 32, 40, 38, 39, 40, 41, 42, 43, 44, + 37, 41, 46, 48, 49, 50, 51, 52, 53, 54, + 59, 8, 9, 5, 6, 7, 13, 59, 59, 16, + 35, 18, 39, 3, 4, -1, 86, 7, 8, 9, + 75, 14, 15, 13, 14, -1, 16, -1, 18, -1, + 7, -1, 87, 10, 86, 86, 13, -1, 93, 10, + -1, 18, 13, 36, 37, 15, 17, 18, 38, 39, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 10, -1, 10, 13, + -1, 13, 36, 37, 18, 19, 18, 42, 43 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 5, 6, 7, 46, 3, 4, 7, 8, 9, + 13, 14, 16, 18, 38, 39, 47, 48, 53, 54, + 56, 58, 7, 10, 51, 52, 53, 56, 0, 39, + 54, 17, 51, 9, 47, 47, 36, 37, 15, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 52, 54, 17, 19, 44, + 40, 47, 47, 54, 54, 54, 54, 54, 54, 54, + 11, 12, 55, 55, 9, 41, 49, 57, 54, 54, + 54, 54, 54, 54, 54, 40, 51, 39, 50, 54, + 19, 54, 42, 43, 40, 54 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. However, + YYFAIL appears to be in use. Nevertheless, it is formally deprecated + in Bison 2.4.2's NEWS entry, where a plan to phase it out is + discussed. */ + +#define YYFAIL goto yyerrlab +#if defined YYFAIL + /* This is here to suppress warnings from the GCC cpp's + -Wunused-macros. Normally we don't worry about that warning, but + some users do, and we want to make it easy for users to remove + YYFAIL uses, which will produce warnings from Bison 2.5. */ +#endif + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (ctx, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* This macro is provided for backward compatibility. */ + +#ifndef YY_LOCATION_PRINT +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, yyscanner) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, ctx); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, ap_expr_parse_ctx_t *ctx) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, ctx) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + ap_expr_parse_ctx_t *ctx; +#endif +{ + if (!yyvaluep) + return; + YYUSE (ctx); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, ap_expr_parse_ctx_t *ctx) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, ctx) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + ap_expr_parse_ctx_t *ctx; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + yy_symbol_value_print (yyoutput, yytype, yyvaluep, ctx); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +#else +static void +yy_stack_print (yybottom, yytop) + yytype_int16 *yybottom; + yytype_int16 *yytop; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, int yyrule, ap_expr_parse_ctx_t *ctx) +#else +static void +yy_reduce_print (yyvsp, yyrule, ctx) + YYSTYPE *yyvsp; + int yyrule; + ap_expr_parse_ctx_t *ctx; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , ctx); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, Rule, ctx); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message + about the unexpected token YYTOKEN for the state stack whose top is + YYSSP. + + Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is + not large enough to hold the message. In that case, also set + *YYMSG_ALLOC to the required number of bytes. Return 2 if the + required number of bytes is too large to store. */ +static int +yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, + yytype_int16 *yyssp, int yytoken) +{ + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytoken]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + /* Internationalized format string. */ + const char *yyformat = 0; + /* Arguments of yyformat. */ + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + /* Number of reported tokens (one for the "unexpected", one per + "expected"). */ + int yycount = 0; + + /* There are many possibilities here to consider: + - Assume YYFAIL is not used. It's too flawed to consider. See + <http://lists.gnu.org/archive/html/bison-patches/2009-12/msg00024.html> + for details. YYERROR is fine as it does not invoke this + function. + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yychar) is if + this state is a consistent state with a default action. Thus, + detecting the absence of a lookahead is sufficient to determine + that there is no unexpected or expected token to report. In that + case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is a + consistent state with a default action. There might have been a + previous inconsistent state, consistent state with a non-default + action, or user semantic action that manipulated yychar. + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state merging + (from LALR or IELR) and default reductions corrupt the expected + token list. However, the list is correct for canonical LR with + one exception: it will still contain any token that will not be + accepted due to an error action in a later state. + */ + if (yytoken != YYEMPTY) + { + int yyn = yypact[*yyssp]; + yyarg[yycount++] = yytname[yytoken]; + if (!yypact_value_is_default (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yyx; + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR + && !yytable_value_is_error (yytable[yyx + yyn])) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + if (! (yysize <= yysize1 + && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + } + } + + switch (yycount) + { +# define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +# undef YYCASE_ + } + + yysize1 = yysize + yystrlen (yyformat); + if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + + if (*yymsg_alloc < yysize) + { + *yymsg_alloc = 2 * yysize; + if (! (yysize <= *yymsg_alloc + && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) + *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; + return 1; + } + + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + { + char *yyp = *yymsg; + int yyi = 0; + while ((*yyp = *yyformat) != '\0') + if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyformat += 2; + } + else + { + yyp++; + yyformat++; + } + } + return 0; +} +#endif /* YYERROR_VERBOSE */ + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, ap_expr_parse_ctx_t *ctx) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, ctx) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + ap_expr_parse_ctx_t *ctx; +#endif +{ + YYUSE (yyvaluep); + YYUSE (ctx); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (ap_expr_parse_ctx_t *ctx); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (ap_expr_parse_ctx_t *ctx) +#else +int +yyparse (ctx) + ap_expr_parse_ctx_t *ctx; +#endif +#endif +{ +/* The lookahead symbol. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; + + /* Number of syntax errors so far. */ + int yynerrs; + + int yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + `yyss': related to states. + `yyvs': related to semantic values. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + YYSIZE_T yystacksize; + + int yyn; + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + int yytoken; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yytoken = 0; + yyss = yyssa; + yyvs = yyvsa; + yystacksize = YYINITDEPTH; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + yyssp = yyss; + yyvsp = yyvs; + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token. */ + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: + +/* Line 1806 of yacc.c */ +#line 112 "util_expr_parse.y" + { ctx->expr = (yyvsp[(2) - (2)].exVal); } + break; + + case 3: + +/* Line 1806 of yacc.c */ +#line 113 "util_expr_parse.y" + { ctx->expr = (yyvsp[(2) - (2)].exVal); } + break; + + case 4: + +/* Line 1806 of yacc.c */ +#line 114 "util_expr_parse.y" + { YYABORT; } + break; + + case 5: + +/* Line 1806 of yacc.c */ +#line 117 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_True, NULL, NULL, ctx); } + break; + + case 6: + +/* Line 1806 of yacc.c */ +#line 118 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_False, NULL, NULL, ctx); } + break; + + case 7: + +/* Line 1806 of yacc.c */ +#line 119 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Not, (yyvsp[(2) - (2)].exVal), NULL, ctx); } + break; + + case 8: + +/* Line 1806 of yacc.c */ +#line 120 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Or, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 9: + +/* Line 1806 of yacc.c */ +#line 121 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_And, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 10: + +/* Line 1806 of yacc.c */ +#line 122 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Comp, (yyvsp[(1) - (1)].exVal), NULL, ctx); } + break; + + case 11: + +/* Line 1806 of yacc.c */ +#line 123 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_unary_op_make( (yyvsp[(1) - (2)].cpVal), (yyvsp[(2) - (2)].exVal), ctx); } + break; + + case 12: + +/* Line 1806 of yacc.c */ +#line 124 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_binary_op_make((yyvsp[(2) - (3)].cpVal), (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 13: + +/* Line 1806 of yacc.c */ +#line 125 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(2) - (3)].exVal); } + break; + + case 14: + +/* Line 1806 of yacc.c */ +#line 126 "util_expr_parse.y" + { YYABORT; } + break; + + case 15: + +/* Line 1806 of yacc.c */ +#line 129 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_EQ, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 16: + +/* Line 1806 of yacc.c */ +#line 130 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_NE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 17: + +/* Line 1806 of yacc.c */ +#line 131 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_LT, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 18: + +/* Line 1806 of yacc.c */ +#line 132 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_LE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 19: + +/* Line 1806 of yacc.c */ +#line 133 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_GT, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 20: + +/* Line 1806 of yacc.c */ +#line 134 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_GE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 21: + +/* Line 1806 of yacc.c */ +#line 135 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_EQ, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 22: + +/* Line 1806 of yacc.c */ +#line 136 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_NE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 23: + +/* Line 1806 of yacc.c */ +#line 137 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_LT, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 24: + +/* Line 1806 of yacc.c */ +#line 138 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_LE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 25: + +/* Line 1806 of yacc.c */ +#line 139 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_GT, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 26: + +/* Line 1806 of yacc.c */ +#line 140 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_GE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 27: + +/* Line 1806 of yacc.c */ +#line 141 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_IN, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 28: + +/* Line 1806 of yacc.c */ +#line 142 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_REG, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 29: + +/* Line 1806 of yacc.c */ +#line 143 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_NRE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 30: + +/* Line 1806 of yacc.c */ +#line 146 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 31: + +/* Line 1806 of yacc.c */ +#line 147 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(2) - (3)].exVal); } + break; + + case 32: + +/* Line 1806 of yacc.c */ +#line 150 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_ListElement, (yyvsp[(1) - (1)].exVal), NULL, ctx); } + break; + + case 33: + +/* Line 1806 of yacc.c */ +#line 151 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_ListElement, (yyvsp[(3) - (3)].exVal), (yyvsp[(1) - (3)].exVal), ctx); } + break; + + case 34: + +/* Line 1806 of yacc.c */ +#line 154 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Concat, (yyvsp[(1) - (2)].exVal), (yyvsp[(2) - (2)].exVal), ctx); } + break; + + case 35: + +/* Line 1806 of yacc.c */ +#line 155 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 36: + +/* Line 1806 of yacc.c */ +#line 156 "util_expr_parse.y" + { YYABORT; } + break; + + case 37: + +/* Line 1806 of yacc.c */ +#line 159 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_String, (yyvsp[(1) - (1)].cpVal), NULL, ctx); } + break; + + case 38: + +/* Line 1806 of yacc.c */ +#line 160 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 39: + +/* Line 1806 of yacc.c */ +#line 161 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 40: + +/* Line 1806 of yacc.c */ +#line 164 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_var_make((yyvsp[(2) - (3)].cpVal), ctx); } + break; + + case 41: + +/* Line 1806 of yacc.c */ +#line 165 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_str_func_make((yyvsp[(2) - (5)].cpVal), (yyvsp[(4) - (5)].exVal), ctx); } + break; + + case 42: + +/* Line 1806 of yacc.c */ +#line 168 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Digit, (yyvsp[(1) - (1)].cpVal), NULL, ctx); } + break; + + case 43: + +/* Line 1806 of yacc.c */ +#line 169 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Concat, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 44: + +/* Line 1806 of yacc.c */ +#line 170 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 45: + +/* Line 1806 of yacc.c */ +#line 171 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 46: + +/* Line 1806 of yacc.c */ +#line 172 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 47: + +/* Line 1806 of yacc.c */ +#line 173 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(2) - (3)].exVal); } + break; + + case 48: + +/* Line 1806 of yacc.c */ +#line 174 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_String, "", NULL, ctx); } + break; + + case 49: + +/* Line 1806 of yacc.c */ +#line 177 "util_expr_parse.y" + { + ap_regex_t *regex; + if ((regex = ap_pregcomp(ctx->pool, (yyvsp[(1) - (1)].cpVal), + AP_REG_EXTENDED|AP_REG_NOSUB)) == NULL) { + ctx->error = "Failed to compile regular expression"; + YYERROR; + } + (yyval.exVal) = ap_expr_make(op_Regex, regex, NULL, ctx); + } + break; + + case 50: + +/* Line 1806 of yacc.c */ +#line 186 "util_expr_parse.y" + { + ap_regex_t *regex; + if ((regex = ap_pregcomp(ctx->pool, (yyvsp[(1) - (1)].cpVal), + AP_REG_EXTENDED|AP_REG_NOSUB|AP_REG_ICASE)) == NULL) { + ctx->error = "Failed to compile regular expression"; + YYERROR; + } + (yyval.exVal) = ap_expr_make(op_Regex, regex, NULL, ctx); + } + break; + + case 51: + +/* Line 1806 of yacc.c */ +#line 197 "util_expr_parse.y" + { + int *n = apr_palloc(ctx->pool, sizeof(int)); + *n = (yyvsp[(1) - (1)].num); + (yyval.exVal) = ap_expr_make(op_RegexBackref, n, NULL, ctx); + } + break; + + case 52: + +/* Line 1806 of yacc.c */ +#line 204 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_list_func_make((yyvsp[(1) - (4)].cpVal), (yyvsp[(3) - (4)].exVal), ctx); } + break; + + case 53: + +/* Line 1806 of yacc.c */ +#line 207 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_str_func_make((yyvsp[(1) - (4)].cpVal), (yyvsp[(3) - (4)].exVal), ctx); } + break; + + + +/* Line 1806 of yacc.c */ +#line 1891 "util_expr_parse.c" + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); + + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (ctx, YY_("syntax error")); +#else +# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ + yyssp, yytoken) + { + char const *yymsgp = YY_("syntax error"); + int yysyntax_error_status; + yysyntax_error_status = YYSYNTAX_ERROR; + if (yysyntax_error_status == 0) + yymsgp = yymsg; + else if (yysyntax_error_status == 1) + { + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); + if (!yymsg) + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + yysyntax_error_status = 2; + } + else + { + yysyntax_error_status = YYSYNTAX_ERROR; + yymsgp = yymsg; + } + } + yyerror (ctx, yymsgp); + if (yysyntax_error_status == 2) + goto yyexhaustedlab; + } +# undef YYSYNTAX_ERROR +#endif + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, ctx); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + yystos[yystate], yyvsp, ctx); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + *++yyvsp = yylval; + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#if !defined(yyoverflow) || YYERROR_VERBOSE +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (ctx, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, ctx); + } + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, ctx); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + +/* Line 2067 of yacc.c */ +#line 210 "util_expr_parse.y" + + +void yyerror(ap_expr_parse_ctx_t *ctx, const char *s) +{ + /* s is allocated on the stack */ + ctx->error = apr_pstrdup(ctx->ptemp, s); +} + + diff --git a/server/util_expr_parse.h b/server/util_expr_parse.h new file mode 100644 index 0000000..8540ec6 --- /dev/null +++ b/server/util_expr_parse.h @@ -0,0 +1,104 @@ +/* A Bison parser, made by GNU Bison 2.5. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + T_TRUE = 258, + T_FALSE = 259, + T_EXPR_BOOL = 260, + T_EXPR_STRING = 261, + T_ERROR = 262, + T_DIGIT = 263, + T_ID = 264, + T_STRING = 265, + T_REGEX = 266, + T_REGEX_I = 267, + T_REGEX_BACKREF = 268, + T_OP_UNARY = 269, + T_OP_BINARY = 270, + T_STR_BEGIN = 271, + T_STR_END = 272, + T_VAR_BEGIN = 273, + T_VAR_END = 274, + T_OP_EQ = 275, + T_OP_NE = 276, + T_OP_LT = 277, + T_OP_LE = 278, + T_OP_GT = 279, + T_OP_GE = 280, + T_OP_REG = 281, + T_OP_NRE = 282, + T_OP_IN = 283, + T_OP_STR_EQ = 284, + T_OP_STR_NE = 285, + T_OP_STR_LT = 286, + T_OP_STR_LE = 287, + T_OP_STR_GT = 288, + T_OP_STR_GE = 289, + T_OP_CONCAT = 290, + T_OP_OR = 291, + T_OP_AND = 292, + T_OP_NOT = 293 + }; +#endif + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +{ + +/* Line 2068 of yacc.c */ +#line 35 "util_expr_parse.y" + + char *cpVal; + ap_expr_t *exVal; + int num; + + + +/* Line 2068 of yacc.c */ +#line 96 "util_expr_parse.h" +} YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + + + + diff --git a/server/util_expr_parse.y b/server/util_expr_parse.y new file mode 100644 index 0000000..85ed123 --- /dev/null +++ b/server/util_expr_parse.y @@ -0,0 +1,217 @@ +/* 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. + */ + +/* based on ap_expr_parse.y from mod_ssl */ + +/* _________________________________________________________________ +** +** Expression Parser +** _________________________________________________________________ +*/ + +%pure-parser +%error-verbose +%defines +%lex-param { void *yyscanner } +%parse-param { ap_expr_parse_ctx_t *ctx } + +%{ +#include "util_expr_private.h" +%} + +%union { + char *cpVal; + ap_expr_t *exVal; + int num; +} + +%token T_TRUE +%token T_FALSE + +%token T_EXPR_BOOL +%token T_EXPR_STRING + +%token <cpVal> T_ERROR + +%token <cpVal> T_DIGIT +%token <cpVal> T_ID +%token <cpVal> T_STRING +%token <cpVal> T_REGEX +%token <cpVal> T_REGEX_I +%token <num> T_REGEX_BACKREF +%token <cpVal> T_OP_UNARY +%token <cpVal> T_OP_BINARY + +%token T_STR_BEGIN +%token T_STR_END +%token T_VAR_BEGIN +%token T_VAR_END + +%token T_OP_EQ +%token T_OP_NE +%token T_OP_LT +%token T_OP_LE +%token T_OP_GT +%token T_OP_GE +%token T_OP_REG +%token T_OP_NRE +%token T_OP_IN +%token T_OP_STR_EQ +%token T_OP_STR_NE +%token T_OP_STR_LT +%token T_OP_STR_LE +%token T_OP_STR_GT +%token T_OP_STR_GE +%token T_OP_CONCAT + +%token T_OP_OR +%token T_OP_AND +%token T_OP_NOT + +%right T_OP_OR +%right T_OP_AND +%right T_OP_NOT +%right T_OP_CONCAT + +%type <exVal> expr +%type <exVal> comparison +%type <exVal> strfunccall +%type <exVal> lstfunccall +%type <exVal> regex +%type <exVal> words +%type <exVal> wordlist +%type <exVal> word +%type <exVal> string +%type <exVal> strpart +%type <exVal> var +%type <exVal> backref + +%{ +#include "util_expr_private.h" +#define yyscanner ctx->scanner + +int ap_expr_yylex(YYSTYPE *lvalp, void *scanner); +%} + + +%% + +root : T_EXPR_BOOL expr { ctx->expr = $2; } + | T_EXPR_STRING string { ctx->expr = $2; } + | T_ERROR { YYABORT; } + ; + +expr : T_TRUE { $$ = ap_expr_make(op_True, NULL, NULL, ctx); } + | T_FALSE { $$ = ap_expr_make(op_False, NULL, NULL, ctx); } + | T_OP_NOT expr { $$ = ap_expr_make(op_Not, $2, NULL, ctx); } + | expr T_OP_OR expr { $$ = ap_expr_make(op_Or, $1, $3, ctx); } + | expr T_OP_AND expr { $$ = ap_expr_make(op_And, $1, $3, ctx); } + | comparison { $$ = ap_expr_make(op_Comp, $1, NULL, ctx); } + | T_OP_UNARY word { $$ = ap_expr_unary_op_make( $1, $2, ctx); } + | word T_OP_BINARY word { $$ = ap_expr_binary_op_make($2, $1, $3, ctx); } + | '(' expr ')' { $$ = $2; } + | T_ERROR { YYABORT; } + ; + +comparison: word T_OP_EQ word { $$ = ap_expr_make(op_EQ, $1, $3, ctx); } + | word T_OP_NE word { $$ = ap_expr_make(op_NE, $1, $3, ctx); } + | word T_OP_LT word { $$ = ap_expr_make(op_LT, $1, $3, ctx); } + | word T_OP_LE word { $$ = ap_expr_make(op_LE, $1, $3, ctx); } + | word T_OP_GT word { $$ = ap_expr_make(op_GT, $1, $3, ctx); } + | word T_OP_GE word { $$ = ap_expr_make(op_GE, $1, $3, ctx); } + | word T_OP_STR_EQ word { $$ = ap_expr_make(op_STR_EQ, $1, $3, ctx); } + | word T_OP_STR_NE word { $$ = ap_expr_make(op_STR_NE, $1, $3, ctx); } + | word T_OP_STR_LT word { $$ = ap_expr_make(op_STR_LT, $1, $3, ctx); } + | word T_OP_STR_LE word { $$ = ap_expr_make(op_STR_LE, $1, $3, ctx); } + | word T_OP_STR_GT word { $$ = ap_expr_make(op_STR_GT, $1, $3, ctx); } + | word T_OP_STR_GE word { $$ = ap_expr_make(op_STR_GE, $1, $3, ctx); } + | word T_OP_IN wordlist { $$ = ap_expr_make(op_IN, $1, $3, ctx); } + | word T_OP_REG regex { $$ = ap_expr_make(op_REG, $1, $3, ctx); } + | word T_OP_NRE regex { $$ = ap_expr_make(op_NRE, $1, $3, ctx); } + ; + +wordlist : lstfunccall { $$ = $1; } + | '{' words '}' { $$ = $2; } + ; + +words : word { $$ = ap_expr_make(op_ListElement, $1, NULL, ctx); } + | words ',' word { $$ = ap_expr_make(op_ListElement, $3, $1, ctx); } + ; + +string : string strpart { $$ = ap_expr_make(op_Concat, $1, $2, ctx); } + | strpart { $$ = $1; } + | T_ERROR { YYABORT; } + ; + +strpart : T_STRING { $$ = ap_expr_make(op_String, $1, NULL, ctx); } + | var { $$ = $1; } + | backref { $$ = $1; } + ; + +var : T_VAR_BEGIN T_ID T_VAR_END { $$ = ap_expr_var_make($2, ctx); } + | T_VAR_BEGIN T_ID ':' string T_VAR_END { $$ = ap_expr_str_func_make($2, $4, ctx); } + ; + +word : T_DIGIT { $$ = ap_expr_make(op_Digit, $1, NULL, ctx); } + | word T_OP_CONCAT word { $$ = ap_expr_make(op_Concat, $1, $3, ctx); } + | var { $$ = $1; } + | backref { $$ = $1; } + | strfunccall { $$ = $1; } + | T_STR_BEGIN string T_STR_END { $$ = $2; } + | T_STR_BEGIN T_STR_END { $$ = ap_expr_make(op_String, "", NULL, ctx); } + ; + +regex : T_REGEX { + ap_regex_t *regex; + if ((regex = ap_pregcomp(ctx->pool, $1, + AP_REG_EXTENDED|AP_REG_NOSUB)) == NULL) { + ctx->error = "Failed to compile regular expression"; + YYERROR; + } + $$ = ap_expr_make(op_Regex, regex, NULL, ctx); + } + | T_REGEX_I { + ap_regex_t *regex; + if ((regex = ap_pregcomp(ctx->pool, $1, + AP_REG_EXTENDED|AP_REG_NOSUB|AP_REG_ICASE)) == NULL) { + ctx->error = "Failed to compile regular expression"; + YYERROR; + } + $$ = ap_expr_make(op_Regex, regex, NULL, ctx); + } + ; + +backref : T_REGEX_BACKREF { + int *n = apr_palloc(ctx->pool, sizeof(int)); + *n = $1; + $$ = ap_expr_make(op_RegexBackref, n, NULL, ctx); + } + ; + +lstfunccall : T_ID '(' word ')' { $$ = ap_expr_list_func_make($1, $3, ctx); } + ; + +strfunccall : T_ID '(' word ')' { $$ = ap_expr_str_func_make($1, $3, ctx); } + ; + +%% + +void yyerror(ap_expr_parse_ctx_t *ctx, const char *s) +{ + /* s is allocated on the stack */ + ctx->error = apr_pstrdup(ctx->ptemp, s); +} + diff --git a/server/util_expr_private.h b/server/util_expr_private.h new file mode 100644 index 0000000..14cc4e3 --- /dev/null +++ b/server/util_expr_private.h @@ -0,0 +1,141 @@ +/* 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 __AP_EXPR_PRIVATE_H__ +#define __AP_EXPR_PRIVATE_H__ + +#include "httpd.h" +#include "apr_strings.h" +#include "apr_tables.h" +#include "ap_expr.h" + +#ifndef YY_NULL +#define YY_NULL 0 +#endif + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +#if !APR_HAVE_UNISTD_H +#define YY_NO_UNISTD_H +#endif + +#ifdef _MSC_VER +/* Avoid some warnings with Visual Studio (likely due to a bug in bison) */ +#define YYMALLOC malloc +#define YYFREE free +#endif + +#ifndef YYDEBUG +#define YYDEBUG 0 +#endif + +/** The operations in a parse tree node */ +typedef enum { + op_NOP, + op_True, op_False, + op_Not, op_Or, op_And, + op_Comp, + op_EQ, op_NE, op_LT, op_LE, op_GT, op_GE, op_IN, + op_REG, op_NRE, + op_STR_EQ, op_STR_NE, op_STR_LT, op_STR_LE, op_STR_GT, op_STR_GE, + op_Concat, + op_Digit, op_String, op_Regex, op_RegexBackref, + op_Var, + op_ListElement, + /* + * call external functions/operators. + * The info node contains the function pointer and some function specific + * info. + * For Binary operators, the Call node links to the Info node and the + * Args node, which in turn links to the left and right operand. + * For all other variants, the Call node links to the Info node and the + * argument. + */ + op_UnaryOpCall, op_UnaryOpInfo, + op_BinaryOpCall, op_BinaryOpInfo, op_BinaryOpArgs, + op_StringFuncCall, op_StringFuncInfo, + op_ListFuncCall, op_ListFuncInfo +} ap_expr_node_op_e; + +/** The basic parse tree node */ +struct ap_expr_node { + ap_expr_node_op_e node_op; + const void *node_arg1; + const void *node_arg2; +}; + +/** The context used by scanner and parser */ +typedef struct { + /* internal state of the scanner */ + const char *inputbuf; + int inputlen; + const char *inputptr; + void *scanner; + char *scan_ptr; + char scan_buf[MAX_STRING_LEN]; + char scan_del; + int at_start; + + /* pools for result and temporary usage */ + apr_pool_t *pool; + apr_pool_t *ptemp; + + /* The created parse tree */ + ap_expr_t *expr; + + const char *error; + const char *error2; + unsigned flags; + + /* + * The function to use to lookup provider functions for variables + * and funtctions + */ + ap_expr_lookup_fn_t *lookup_fn; +} ap_expr_parse_ctx_t; + +/* flex/bison functions */ +int ap_expr_yyparse(ap_expr_parse_ctx_t *context); +void ap_expr_yyerror(ap_expr_parse_ctx_t *context, const char *err); +int ap_expr_yylex_init(void **scanner); +int ap_expr_yylex_destroy(void *scanner); +void ap_expr_yyset_extra(ap_expr_parse_ctx_t *context, void *scanner); + +/* create a parse tree node */ +ap_expr_t *ap_expr_make(ap_expr_node_op_e op, const void *arg1, + const void *arg2, ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the string-returning function 'name' */ +ap_expr_t *ap_expr_str_func_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the list-returning function 'name' */ +ap_expr_t *ap_expr_list_func_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the variable 'name' */ +ap_expr_t *ap_expr_var_make(const char *name, ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the unary operator 'name' */ +ap_expr_t *ap_expr_unary_op_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the binary operator 'name' */ +ap_expr_t *ap_expr_binary_op_make(const char *name, const ap_expr_t *arg1, + const ap_expr_t *arg2, + ap_expr_parse_ctx_t *ctx); + + +#endif /* __AP_EXPR_PRIVATE_H__ */ +/** @} */ + diff --git a/server/util_expr_scan.c b/server/util_expr_scan.c new file mode 100644 index 0000000..ba759f2 --- /dev/null +++ b/server/util_expr_scan.c @@ -0,0 +1,2669 @@ +#line 2 "util_expr_scan.c" + +#line 4 "util_expr_scan.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 35 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +/* C99 requires __STDC__ to be defined as 1. */ +#if defined (__STDC__) + +#define YY_USE_CONST + +#endif /* defined (__STDC__) */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE ap_expr_yyrestart(yyin ,yyscanner ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via ap_expr_yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void ap_expr_yyrestart (FILE *input_file ,yyscan_t yyscanner ); +void ap_expr_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +YY_BUFFER_STATE ap_expr_yy_create_buffer (FILE *file,int size ,yyscan_t yyscanner ); +void ap_expr_yy_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void ap_expr_yy_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void ap_expr_yypush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +void ap_expr_yypop_buffer_state (yyscan_t yyscanner ); + +static void ap_expr_yyensure_buffer_stack (yyscan_t yyscanner ); +static void ap_expr_yy_load_buffer_state (yyscan_t yyscanner ); +static void ap_expr_yy_init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner ); + +#define YY_FLUSH_BUFFER ap_expr_yy_flush_buffer(YY_CURRENT_BUFFER ,yyscanner) + +YY_BUFFER_STATE ap_expr_yy_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner ); +YY_BUFFER_STATE ap_expr_yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner ); +YY_BUFFER_STATE ap_expr_yy_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner ); + +void *ap_expr_yyalloc (yy_size_t ,yyscan_t yyscanner ); +void *ap_expr_yyrealloc (void *,yy_size_t ,yyscan_t yyscanner ); +void ap_expr_yyfree (void * ,yyscan_t yyscanner ); + +#define yy_new_buffer ap_expr_yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + ap_expr_yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + ap_expr_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + ap_expr_yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + ap_expr_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define ap_expr_yywrap(n) 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state (yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ,yyscan_t yyscanner); +static int yy_get_next_buffer (yyscan_t yyscanner ); +static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (size_t) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; + +#define YY_NUM_RULES 67 +#define YY_END_OF_BUFFER 68 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[124] = + { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 68, 66, 1, 43, 2, 66, 66, 66, + 65, 66, 44, 26, 63, 32, 30, 34, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 66, + 14, 4, 3, 17, 17, 67, 17, 23, 4, 22, + 20, 21, 67, 16, 16, 24, 27, 29, 28, 1, + 31, 37, 19, 18, 39, 63, 59, 59, 59, 59, + 59, 59, 33, 30, 36, 35, 64, 64, 57, 64, + 55, 54, 58, 53, 52, 25, 25, 56, 64, 40, + 64, 41, 14, 13, 15, 12, 5, 6, 10, 11, + + 7, 8, 9, 20, 60, 46, 48, 50, 45, 49, + 51, 47, 38, 64, 42, 64, 5, 6, 64, 61, + 5, 62, 0 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 4, 5, 6, 7, 8, 9, 5, 10, + 10, 1, 1, 11, 12, 13, 14, 15, 15, 15, + 15, 15, 15, 15, 15, 16, 16, 17, 6, 18, + 19, 20, 6, 1, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 1, 22, 1, 6, 23, 1, 24, 25, 21, 26, + + 27, 28, 29, 21, 30, 21, 21, 31, 32, 33, + 34, 21, 35, 36, 37, 38, 39, 21, 21, 21, + 21, 21, 40, 41, 42, 43, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst flex_int32_t yy_meta[44] = + { 0, + 1, 1, 2, 1, 2, 1, 2, 2, 1, 1, + 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, + 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, + 1, 2, 1 + } ; + +static yyconst flex_int16_t yy_base[133] = + { 0, + 0, 0, 41, 47, 89, 0, 130, 136, 0, 0, + 147, 146, 175, 275, 54, 28, 275, 43, 134, 164, + 275, 164, 275, 275, 45, 152, 32, 151, 0, 136, + 133, 143, 26, 133, 35, 194, 38, 129, 128, 122, + 0, 275, 275, 51, 122, 221, 275, 275, 275, 275, + 0, 275, 275, 61, 121, 275, 275, 275, 275, 76, + 275, 275, 275, 275, 275, 65, 0, 125, 47, 126, + 107, 130, 275, 275, 275, 275, 0, 130, 0, 124, + 0, 0, 0, 0, 0, 275, 0, 0, 104, 0, + 101, 275, 0, 275, 275, 275, 71, 131, 275, 275, + + 275, 275, 275, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 99, 0, 61, 133, 135, 57, 0, + 138, 0, 275, 259, 262, 265, 79, 67, 268, 271, + 65, 42 + } ; + +static yyconst flex_int16_t yy_def[133] = + { 0, + 123, 1, 124, 124, 123, 5, 124, 124, 125, 125, + 126, 126, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 127, 123, 123, 123, 123, 123, 123, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 123, + 129, 123, 123, 123, 123, 130, 123, 123, 123, 123, + 131, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 132, 132, 132, 132, + 132, 132, 123, 123, 123, 123, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 123, 128, 128, 128, 128, + 128, 123, 129, 123, 123, 123, 123, 123, 123, 123, + + 123, 123, 123, 131, 132, 132, 132, 132, 132, 132, + 132, 132, 128, 128, 128, 128, 123, 123, 128, 128, + 123, 128, 0, 123, 123, 123, 123, 123, 123, 123, + 123, 123 + } ; + +static yyconst flex_int16_t yy_nxt[319] = + { 0, + 14, 15, 15, 16, 17, 14, 18, 19, 20, 21, + 21, 22, 23, 24, 25, 25, 21, 26, 27, 28, + 29, 14, 14, 30, 29, 29, 31, 32, 33, 34, + 35, 36, 37, 38, 29, 29, 29, 39, 29, 21, + 40, 21, 14, 42, 105, 43, 61, 44, 45, 42, + 74, 43, 81, 44, 45, 60, 60, 63, 63, 66, + 66, 84, 46, 82, 88, 94, 94, 104, 46, 77, + 62, 89, 85, 107, 75, 94, 94, 60, 60, 66, + 66, 67, 47, 122, 108, 117, 118, 120, 47, 48, + 48, 49, 48, 48, 48, 48, 48, 48, 48, 48, + + 48, 48, 48, 48, 48, 50, 48, 48, 48, 51, + 48, 48, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 48, 48, + 52, 48, 42, 110, 53, 119, 54, 55, 42, 116, + 53, 115, 54, 55, 111, 118, 118, 121, 118, 118, + 118, 46, 118, 118, 114, 113, 112, 46, 109, 106, + 95, 95, 92, 91, 90, 83, 80, 79, 78, 76, + 73, 56, 65, 64, 123, 59, 59, 56, 66, 66, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 68, 123, 69, 70, 71, 123, 72, 86, 86, 86, + + 86, 86, 123, 123, 86, 86, 86, 86, 123, 123, + 86, 123, 123, 123, 123, 123, 87, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 86, 97, 98, 123, 123, 123, + 123, 123, 123, 123, 123, 99, 123, 123, 100, 123, + 123, 123, 123, 101, 123, 123, 102, 123, 103, 41, + 41, 41, 57, 57, 57, 58, 58, 58, 93, 123, + 93, 96, 96, 96, 13, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123 + } ; + +static yyconst flex_int16_t yy_chk[319] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 3, 132, 3, 16, 3, 3, 4, + 27, 4, 33, 4, 4, 15, 15, 18, 18, 25, + 25, 35, 3, 33, 37, 44, 44, 131, 4, 128, + 16, 37, 35, 69, 27, 54, 54, 60, 60, 66, + 66, 127, 3, 119, 69, 97, 97, 116, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 7, 71, 7, 114, 7, 7, 8, 91, + 8, 89, 8, 8, 71, 98, 98, 117, 117, 118, + 118, 7, 121, 121, 80, 78, 72, 8, 70, 68, + 55, 45, 40, 39, 38, 34, 32, 31, 30, 28, + 26, 7, 20, 19, 13, 12, 11, 8, 22, 22, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 22, 0, 22, 22, 22, 0, 22, 36, 36, 36, + + 36, 36, 0, 0, 36, 36, 36, 36, 0, 0, + 36, 0, 0, 0, 0, 0, 36, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 36, 46, 46, 0, 0, 0, + 0, 0, 0, 0, 0, 46, 0, 0, 46, 0, + 0, 0, 0, 46, 0, 0, 46, 0, 46, 124, + 124, 124, 125, 125, 125, 126, 126, 126, 129, 0, + 129, 130, 130, 130, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123 + } ; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +#line 1 "util_expr_scan.l" +/* 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. + */ +/* + * ap_expr_scan.l, based on ssl_expr_scan.l from mod_ssl + */ +/* _________________________________________________________________ +** +** Expression Scanner +** _________________________________________________________________ +*/ +#define YY_NO_INPUT 1 + + + + +#line 43 "util_expr_scan.l" +#include "util_expr_private.h" +#include "util_expr_parse.h" + +#undef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ +{ \ + if ((result = MIN(max_size, yyextra->inputbuf \ + + yyextra->inputlen \ + - yyextra->inputptr)) <= 0) \ + { \ + result = YY_NULL; \ + } \ + else { \ + memcpy(buf, yyextra->inputptr, result); \ + yyextra->inputptr += result; \ + } \ +} + +#define YY_EXTRA_TYPE ap_expr_parse_ctx_t* + +#define PERROR(msg) do { yyextra->error2 = msg ; return T_ERROR; } while (0) + +#define str_ptr (yyextra->scan_ptr) +#define str_buf (yyextra->scan_buf) +#define str_del (yyextra->scan_del) + +#define STR_APPEND(c) do { \ + *str_ptr++ = (c); \ + if (str_ptr >= str_buf + sizeof(str_buf)) \ + PERROR("String too long"); \ + } while (0) + +#line 615 "util_expr_scan.c" + +#define INITIAL 0 +#define str 1 +#define var 2 +#define vararg 3 +#define regex 4 +#define regex_flags 5 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include <unistd.h> +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + int yy_n_chars; + int yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals (yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + +int ap_expr_yylex_init (yyscan_t* scanner); + +int ap_expr_yylex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int ap_expr_yylex_destroy (yyscan_t yyscanner ); + +int ap_expr_yyget_debug (yyscan_t yyscanner ); + +void ap_expr_yyset_debug (int debug_flag ,yyscan_t yyscanner ); + +YY_EXTRA_TYPE ap_expr_yyget_extra (yyscan_t yyscanner ); + +void ap_expr_yyset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner ); + +FILE *ap_expr_yyget_in (yyscan_t yyscanner ); + +void ap_expr_yyset_in (FILE * in_str ,yyscan_t yyscanner ); + +FILE *ap_expr_yyget_out (yyscan_t yyscanner ); + +void ap_expr_yyset_out (FILE * out_str ,yyscan_t yyscanner ); + +int ap_expr_yyget_leng (yyscan_t yyscanner ); + +char *ap_expr_yyget_text (yyscan_t yyscanner ); + +int ap_expr_yyget_lineno (yyscan_t yyscanner ); + +void ap_expr_yyset_lineno (int line_number ,yyscan_t yyscanner ); + +YYSTYPE * ap_expr_yyget_lval (yyscan_t yyscanner ); + +void ap_expr_yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int ap_expr_yywrap (yyscan_t yyscanner ); +#else +extern int ap_expr_yywrap (yyscan_t yyscanner ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (yyscan_t yyscanner ); +#else +static int input (yyscan_t yyscanner ); +#endif + +#endif + + static void yy_push_state (int new_state ,yyscan_t yyscanner); + + static void yy_pop_state (yyscan_t yyscanner ); + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int ap_expr_yylex \ + (YYSTYPE * yylval_param ,yyscan_t yyscanner); + +#define YY_DECL int ap_expr_yylex \ + (YYSTYPE * yylval_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + +#line 78 "util_expr_scan.l" + + + char regex_buf[MAX_STRING_LEN]; + char *regex_ptr = NULL; + char regex_del = '\0'; + + + /* + * Set initial state for string expressions + */ + if (yyextra->at_start) { + yyextra->at_start = 0; + if (yyextra->flags & AP_EXPR_FLAG_STRING_RESULT) { + BEGIN(str); + return T_EXPR_STRING; + } + else { + return T_EXPR_BOOL; + } + } + + + /* + * Whitespaces + */ +#line 886 "util_expr_scan.c" + + yylval = yylval_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + ap_expr_yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + ap_expr_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + ap_expr_yy_load_buffer_state(yyscanner ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 124 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_current_state != 123 ); + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +/* rule 1 can match eol */ +YY_RULE_SETUP +#line 103 "util_expr_scan.l" +{ + /* NOP */ +} + YY_BREAK +/* + * strings ("..." and '...') + */ +case 2: +YY_RULE_SETUP +#line 110 "util_expr_scan.l" +{ + str_ptr = str_buf; + str_del = yytext[0]; + BEGIN(str); + return T_STR_BEGIN; +} + YY_BREAK +case 3: +YY_RULE_SETUP +#line 116 "util_expr_scan.l" +{ + if (yytext[0] == str_del) { + if (YY_START == var) { + PERROR("Unterminated variable in string"); + } + else if (str_ptr == str_buf) { + BEGIN(INITIAL); + return T_STR_END; + } + else { + /* return what we have so far and scan delimiter again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + yyless(0); + str_ptr = str_buf; + return T_STRING; + } + } + else { + STR_APPEND(yytext[0]); + } +} + YY_BREAK +case 4: +/* rule 4 can match eol */ +YY_RULE_SETUP +#line 138 "util_expr_scan.l" +{ + PERROR("Unterminated string or variable"); +} + YY_BREAK +case YY_STATE_EOF(var): +case YY_STATE_EOF(vararg): +#line 141 "util_expr_scan.l" +{ + PERROR("Unterminated string or variable"); +} + YY_BREAK +case YY_STATE_EOF(str): +#line 144 "util_expr_scan.l" +{ + if (!(yyextra->flags & AP_EXPR_FLAG_STRING_RESULT)) { + PERROR("Unterminated string or variable"); + } + else { + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + BEGIN(INITIAL); + return T_STRING; + } +} + YY_BREAK +case 5: +YY_RULE_SETUP +#line 157 "util_expr_scan.l" +{ + int result; + + (void)sscanf(yytext+1, "%o", &result); + if (result > 0xff) { + PERROR("Escape sequence out of bound"); + } + else { + STR_APPEND(result); + } +} + YY_BREAK +case 6: +YY_RULE_SETUP +#line 168 "util_expr_scan.l" +{ + PERROR("Bad escape sequence"); +} + YY_BREAK +case 7: +YY_RULE_SETUP +#line 171 "util_expr_scan.l" +{ STR_APPEND('\n'); } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 172 "util_expr_scan.l" +{ STR_APPEND('\r'); } + YY_BREAK +case 9: +YY_RULE_SETUP +#line 173 "util_expr_scan.l" +{ STR_APPEND('\t'); } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 174 "util_expr_scan.l" +{ STR_APPEND('\b'); } + YY_BREAK +case 11: +YY_RULE_SETUP +#line 175 "util_expr_scan.l" +{ STR_APPEND('\f'); } + YY_BREAK +case 12: +/* rule 12 can match eol */ +YY_RULE_SETUP +#line 176 "util_expr_scan.l" +{ STR_APPEND(yytext[1]); } + YY_BREAK +/* regexp backref inside string/arg */ +case 13: +YY_RULE_SETUP +#line 179 "util_expr_scan.l" +{ + if (str_ptr != str_buf) { + /* return what we have so far and scan '$x' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + yyless(0); + return T_STRING; + } + else { + yylval->num = yytext[1] - '0'; + return T_REGEX_BACKREF; + } +} + YY_BREAK +case 14: +YY_RULE_SETUP +#line 194 "util_expr_scan.l" +{ + char *cp = yytext; + while (*cp != '\0') { + STR_APPEND(*cp); + cp++; + } +} + YY_BREAK +/* variable inside string/arg */ +case 15: +YY_RULE_SETUP +#line 203 "util_expr_scan.l" +{ + if (str_ptr != str_buf) { + /* return what we have so far and scan '%{' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + yyless(0); + str_ptr = str_buf; + return T_STRING; + } + else { + yy_push_state(var, yyscanner); + return T_VAR_BEGIN; + } +} + YY_BREAK +case 16: +YY_RULE_SETUP +#line 218 "util_expr_scan.l" +{ + STR_APPEND(yytext[0]); +} + YY_BREAK +case 17: +YY_RULE_SETUP +#line 222 "util_expr_scan.l" +{ + STR_APPEND(yytext[0]); +} + YY_BREAK +case 18: +YY_RULE_SETUP +#line 226 "util_expr_scan.l" +{ + yy_push_state(var, yyscanner); + return T_VAR_BEGIN; +} + YY_BREAK +case 19: +YY_RULE_SETUP +#line 231 "util_expr_scan.l" +{ + yylval->num = yytext[1] - '0'; + return T_REGEX_BACKREF; +} + YY_BREAK +/* + * fixed name variable expansion %{XXX} and function call in %{func:arg} syntax + */ +case 20: +YY_RULE_SETUP +#line 239 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_ID; +} + YY_BREAK +case 21: +YY_RULE_SETUP +#line 244 "util_expr_scan.l" +{ + yy_pop_state(yyscanner); + return T_VAR_END; +} + YY_BREAK +case 22: +YY_RULE_SETUP +#line 249 "util_expr_scan.l" +{ + BEGIN(vararg); + return yytext[0]; +} + YY_BREAK +case 23: +/* rule 23 can match eol */ +YY_RULE_SETUP +#line 254 "util_expr_scan.l" +{ + char *msg = apr_psprintf(yyextra->pool, + "Invalid character in variable name '%c'", yytext[0]); + PERROR(msg); +} + YY_BREAK +case 24: +YY_RULE_SETUP +#line 260 "util_expr_scan.l" +{ + if (str_ptr != str_buf) { + /* return what we have so far and scan '}' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + yyless(0); + return T_STRING; + } + else { + yy_pop_state(yyscanner); + return T_VAR_END; + } +} + YY_BREAK +/* + * Regular Expression + */ +case 25: +YY_RULE_SETUP +#line 278 "util_expr_scan.l" +{ + regex_del = yytext[1]; + regex_ptr = regex_buf; + BEGIN(regex); +} + YY_BREAK +case 26: +YY_RULE_SETUP +#line 283 "util_expr_scan.l" +{ + regex_del = yytext[0]; + regex_ptr = regex_buf; + BEGIN(regex); +} + YY_BREAK +case 27: +/* rule 27 can match eol */ +YY_RULE_SETUP +#line 288 "util_expr_scan.l" +{ + if (yytext[0] == regex_del) { + *regex_ptr = '\0'; + BEGIN(regex_flags); + } + else { + *regex_ptr++ = yytext[0]; + if (regex_ptr >= regex_buf + sizeof(regex_buf)) + PERROR("Regexp too long"); + } +} + YY_BREAK +case 28: +YY_RULE_SETUP +#line 299 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + BEGIN(INITIAL); + return T_REGEX_I; +} + YY_BREAK +case 29: +/* rule 29 can match eol */ +YY_RULE_SETUP +#line 304 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + yyless(0); + BEGIN(INITIAL); + return T_REGEX; +} + YY_BREAK +case YY_STATE_EOF(regex_flags): +#line 310 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + BEGIN(INITIAL); + return T_REGEX; +} + YY_BREAK +/* + * Operators + */ +case 30: +YY_RULE_SETUP +#line 319 "util_expr_scan.l" +{ return T_OP_STR_EQ; } + YY_BREAK +case 31: +YY_RULE_SETUP +#line 320 "util_expr_scan.l" +{ return T_OP_STR_NE; } + YY_BREAK +case 32: +YY_RULE_SETUP +#line 321 "util_expr_scan.l" +{ return T_OP_STR_LT; } + YY_BREAK +case 33: +YY_RULE_SETUP +#line 322 "util_expr_scan.l" +{ return T_OP_STR_LE; } + YY_BREAK +case 34: +YY_RULE_SETUP +#line 323 "util_expr_scan.l" +{ return T_OP_STR_GT; } + YY_BREAK +case 35: +YY_RULE_SETUP +#line 324 "util_expr_scan.l" +{ return T_OP_STR_GE; } + YY_BREAK +case 36: +YY_RULE_SETUP +#line 325 "util_expr_scan.l" +{ return T_OP_REG; } + YY_BREAK +case 37: +YY_RULE_SETUP +#line 326 "util_expr_scan.l" +{ return T_OP_NRE; } + YY_BREAK +case 38: +YY_RULE_SETUP +#line 327 "util_expr_scan.l" +{ return T_OP_AND; } + YY_BREAK +case 39: +YY_RULE_SETUP +#line 328 "util_expr_scan.l" +{ return T_OP_AND; } + YY_BREAK +case 40: +YY_RULE_SETUP +#line 329 "util_expr_scan.l" +{ return T_OP_OR; } + YY_BREAK +case 41: +YY_RULE_SETUP +#line 330 "util_expr_scan.l" +{ return T_OP_OR; } + YY_BREAK +case 42: +YY_RULE_SETUP +#line 331 "util_expr_scan.l" +{ return T_OP_NOT; } + YY_BREAK +case 43: +YY_RULE_SETUP +#line 332 "util_expr_scan.l" +{ return T_OP_NOT; } + YY_BREAK +case 44: +YY_RULE_SETUP +#line 333 "util_expr_scan.l" +{ return T_OP_CONCAT; } + YY_BREAK +case 45: +YY_RULE_SETUP +#line 334 "util_expr_scan.l" +{ return T_OP_IN; } + YY_BREAK +case 46: +YY_RULE_SETUP +#line 335 "util_expr_scan.l" +{ return T_OP_EQ; } + YY_BREAK +case 47: +YY_RULE_SETUP +#line 336 "util_expr_scan.l" +{ return T_OP_NE; } + YY_BREAK +case 48: +YY_RULE_SETUP +#line 337 "util_expr_scan.l" +{ return T_OP_GE; } + YY_BREAK +case 49: +YY_RULE_SETUP +#line 338 "util_expr_scan.l" +{ return T_OP_LE; } + YY_BREAK +case 50: +YY_RULE_SETUP +#line 339 "util_expr_scan.l" +{ return T_OP_GT; } + YY_BREAK +case 51: +YY_RULE_SETUP +#line 340 "util_expr_scan.l" +{ return T_OP_LT; } + YY_BREAK +/* for compatibility with ssl_expr */ +case 52: +YY_RULE_SETUP +#line 343 "util_expr_scan.l" +{ return T_OP_LT; } + YY_BREAK +case 53: +YY_RULE_SETUP +#line 344 "util_expr_scan.l" +{ return T_OP_LE; } + YY_BREAK +case 54: +YY_RULE_SETUP +#line 345 "util_expr_scan.l" +{ return T_OP_GT; } + YY_BREAK +case 55: +YY_RULE_SETUP +#line 346 "util_expr_scan.l" +{ return T_OP_GE; } + YY_BREAK +case 56: +YY_RULE_SETUP +#line 347 "util_expr_scan.l" +{ return T_OP_NE; } + YY_BREAK +case 57: +YY_RULE_SETUP +#line 348 "util_expr_scan.l" +{ return T_OP_EQ; } + YY_BREAK +case 58: +YY_RULE_SETUP +#line 349 "util_expr_scan.l" +{ return T_OP_IN; } + YY_BREAK +case 59: +YY_RULE_SETUP +#line 351 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1); + return T_OP_UNARY; +} + YY_BREAK +case 60: +YY_RULE_SETUP +#line 356 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1); + return T_OP_BINARY; +} + YY_BREAK +/* + * Specials + */ +case 61: +YY_RULE_SETUP +#line 364 "util_expr_scan.l" +{ return T_TRUE; } + YY_BREAK +case 62: +YY_RULE_SETUP +#line 365 "util_expr_scan.l" +{ return T_FALSE; } + YY_BREAK +/* + * Digits + */ +case 63: +YY_RULE_SETUP +#line 370 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_DIGIT; +} + YY_BREAK +/* + * Identifiers + */ +case 64: +YY_RULE_SETUP +#line 378 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_ID; +} + YY_BREAK +/* + * These are parts of the grammar and are returned as is + */ +case 65: +YY_RULE_SETUP +#line 386 "util_expr_scan.l" +{ + return yytext[0]; +} + YY_BREAK +/* + * Anything else is an error + */ +case 66: +/* rule 66 can match eol */ +YY_RULE_SETUP +#line 393 "util_expr_scan.l" +{ + char *msg = apr_psprintf(yyextra->pool, "Parse error near '%c'", yytext[0]); + PERROR(msg); +} + YY_BREAK +case 67: +YY_RULE_SETUP +#line 398 "util_expr_scan.l" +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +#line 1523 "util_expr_scan.c" +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(regex): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * ap_expr_yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( ap_expr_yywrap(yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of ap_expr_yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = yyg->yytext_ptr; + register int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + ap_expr_yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, (size_t) num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + ap_expr_yyrestart(yyin ,yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yy_size_t) (yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) ap_expr_yyrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ,yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 124 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + register int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + register char *yy_cp = yyg->yy_c_buf_p; + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 124 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 123); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + ap_expr_yyrestart(yyin ,yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( ap_expr_yywrap(yyscanner ) ) + return EOF; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void ap_expr_yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + ap_expr_yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + ap_expr_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + ap_expr_yy_init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner); + ap_expr_yy_load_buffer_state(yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void ap_expr_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * ap_expr_yypop_buffer_state(); + * ap_expr_yypush_buffer_state(new_buffer); + */ + ap_expr_yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + ap_expr_yy_load_buffer_state(yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (ap_expr_yywrap()) processing, but the only time this flag + * is looked at is after ap_expr_yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void ap_expr_yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE ap_expr_yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) ap_expr_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) ap_expr_yyalloc(b->yy_buf_size + 2 ,yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + ap_expr_yy_init_buffer(b,file ,yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with ap_expr_yy_create_buffer() + * @param yyscanner The scanner object. + */ + void ap_expr_yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + ap_expr_yyfree((void *) b->yy_ch_buf ,yyscanner ); + + ap_expr_yyfree((void *) b ,yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a ap_expr_yyrestart() or at EOF. + */ + static void ap_expr_yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + ap_expr_yy_flush_buffer(b ,yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then ap_expr_yy_init_buffer was _probably_ + * called from ap_expr_yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void ap_expr_yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + ap_expr_yy_load_buffer_state(yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void ap_expr_yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + ap_expr_yyensure_buffer_stack(yyscanner); + + /* This block is copied from ap_expr_yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from ap_expr_yy_switch_to_buffer. */ + ap_expr_yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void ap_expr_yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + ap_expr_yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + ap_expr_yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void ap_expr_yyensure_buffer_stack (yyscan_t yyscanner) +{ + int num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + yyg->yy_buffer_stack = (struct yy_buffer_state**)ap_expr_yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)ap_expr_yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE ap_expr_yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) ap_expr_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + ap_expr_yy_switch_to_buffer(b ,yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to ap_expr_yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * ap_expr_yy_scan_bytes() instead. + */ +YY_BUFFER_STATE ap_expr_yy_scan_string (yyconst char * yystr , yyscan_t yyscanner) +{ + + return ap_expr_yy_scan_bytes(yystr,strlen(yystr) ,yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to ap_expr_yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE ap_expr_yy_scan_bytes (yyconst char * yybytes, int _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) ap_expr_yyalloc(n ,yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = ap_expr_yy_scan_buffer(buf,n ,yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in ap_expr_yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + + static void yy_push_state (int new_state , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( yyg->yy_start_stack_ptr >= yyg->yy_start_stack_depth ) + { + yy_size_t new_size; + + yyg->yy_start_stack_depth += YY_START_STACK_INCR; + new_size = yyg->yy_start_stack_depth * sizeof( int ); + + if ( ! yyg->yy_start_stack ) + yyg->yy_start_stack = (int *) ap_expr_yyalloc(new_size ,yyscanner ); + + else + yyg->yy_start_stack = (int *) ap_expr_yyrealloc((void *) yyg->yy_start_stack,new_size ,yyscanner ); + + if ( ! yyg->yy_start_stack ) + YY_FATAL_ERROR( "out of memory expanding start-condition stack" ); + } + + yyg->yy_start_stack[yyg->yy_start_stack_ptr++] = YY_START; + + BEGIN(new_state); +} + + static void yy_pop_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( --yyg->yy_start_stack_ptr < 0 ) + YY_FATAL_ERROR( "start-condition stack underflow" ); + + BEGIN(yyg->yy_start_stack[yyg->yy_start_stack_ptr]); +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE ap_expr_yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int ap_expr_yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *ap_expr_yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *ap_expr_yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +int ap_expr_yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *ap_expr_yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void ap_expr_yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param line_number + * @param yyscanner The scanner object. + */ +void ap_expr_yyset_lineno (int line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + yy_fatal_error( "ap_expr_yyset_lineno called with no buffer" , yyscanner); + + yylineno = line_number; +} + +/** Set the current column. + * @param line_number + * @param yyscanner The scanner object. + */ + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * @param yyscanner The scanner object. + * @see ap_expr_yy_switch_to_buffer + */ +void ap_expr_yyset_in (FILE * in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = in_str ; +} + +void ap_expr_yyset_out (FILE * out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = out_str ; +} + +int ap_expr_yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void ap_expr_yyset_debug (int bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * ap_expr_yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void ap_expr_yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +/* User-visible API */ + +/* ap_expr_yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ + +int ap_expr_yylex_init(yyscan_t* ptr_yy_globals) + +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) ap_expr_yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* ap_expr_yylex_init_extra has the same functionality as ap_expr_yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to ap_expr_yyalloc in + * the yyextra field. + */ + +int ap_expr_yylex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals ) + +{ + struct yyguts_t dummy_yyguts; + + ap_expr_yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) ap_expr_yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + ap_expr_yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from ap_expr_yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = 0; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = (char *) 0; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = (FILE *) 0; + yyout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * ap_expr_yylex_init() + */ + return 0; +} + +/* ap_expr_yylex_destroy is for both reentrant and non-reentrant scanners. */ +int ap_expr_yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + ap_expr_yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + ap_expr_yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + ap_expr_yyfree(yyg->yy_buffer_stack ,yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + ap_expr_yyfree(yyg->yy_start_stack ,yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * ap_expr_yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + ap_expr_yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *ap_expr_yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + return (void *) malloc( size ); +} + +void *ap_expr_yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void ap_expr_yyfree (void * ptr , yyscan_t yyscanner) +{ + free( (char *) ptr ); /* see ap_expr_yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 398 "util_expr_scan.l" + + + + + diff --git a/server/util_expr_scan.l b/server/util_expr_scan.l new file mode 100644 index 0000000..513236a --- /dev/null +++ b/server/util_expr_scan.l @@ -0,0 +1,400 @@ +/* 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. + */ + +/* + * ap_expr_scan.l, based on ssl_expr_scan.l from mod_ssl + */ + +/* _________________________________________________________________ +** +** Expression Scanner +** _________________________________________________________________ +*/ + +%pointer +%option batch +%option never-interactive +%option nodefault +%option noyywrap +%option reentrant +%option bison-bridge +%option warn +%option noinput nounput noyy_top_state +%option stack +%x str +%x var +%x vararg +%x regex regex_flags + +%{ +#include "util_expr_private.h" +#include "util_expr_parse.h" + +#undef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ +{ \ + if ((result = MIN(max_size, yyextra->inputbuf \ + + yyextra->inputlen \ + - yyextra->inputptr)) <= 0) \ + { \ + result = YY_NULL; \ + } \ + else { \ + memcpy(buf, yyextra->inputptr, result); \ + yyextra->inputptr += result; \ + } \ +} + +#define YY_EXTRA_TYPE ap_expr_parse_ctx_t* + +#define PERROR(msg) do { yyextra->error2 = msg ; return T_ERROR; } while (0) + +#define str_ptr (yyextra->scan_ptr) +#define str_buf (yyextra->scan_buf) +#define str_del (yyextra->scan_del) + +#define STR_APPEND(c) do { \ + *str_ptr++ = (c); \ + if (str_ptr >= str_buf + sizeof(str_buf)) \ + PERROR("String too long"); \ + } while (0) + +%} + + +%% + + char regex_buf[MAX_STRING_LEN]; + char *regex_ptr = NULL; + char regex_del = '\0'; + +%{ + /* + * Set initial state for string expressions + */ + if (yyextra->at_start) { + yyextra->at_start = 0; + if (yyextra->flags & AP_EXPR_FLAG_STRING_RESULT) { + BEGIN(str); + return T_EXPR_STRING; + } + else { + return T_EXPR_BOOL; + } + } +%} + + /* + * Whitespaces + */ +[ \t\n]+ { + /* NOP */ +} + + /* + * strings ("..." and '...') + */ +["'] { + str_ptr = str_buf; + str_del = yytext[0]; + BEGIN(str); + return T_STR_BEGIN; +} +<str>["'] { + if (yytext[0] == str_del) { + if (YY_START == var) { + PERROR("Unterminated variable in string"); + } + else if (str_ptr == str_buf) { + BEGIN(INITIAL); + return T_STR_END; + } + else { + /* return what we have so far and scan delimiter again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + yyless(0); + str_ptr = str_buf; + return T_STRING; + } + } + else { + STR_APPEND(yytext[0]); + } +} +<str,var,vararg>\n { + PERROR("Unterminated string or variable"); +} +<var,vararg><<EOF>> { + PERROR("Unterminated string or variable"); +} +<str><<EOF>> { + if (!(yyextra->flags & AP_EXPR_FLAG_STRING_RESULT)) { + PERROR("Unterminated string or variable"); + } + else { + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + BEGIN(INITIAL); + return T_STRING; + } +} + +<str,vararg>\\[0-7]{1,3} { + int result; + + (void)sscanf(yytext+1, "%o", &result); + if (result > 0xff) { + PERROR("Escape sequence out of bound"); + } + else { + STR_APPEND(result); + } +} +<str,vararg>\\[0-9]+ { + PERROR("Bad escape sequence"); +} +<str,vararg>\\n { STR_APPEND('\n'); } +<str,vararg>\\r { STR_APPEND('\r'); } +<str,vararg>\\t { STR_APPEND('\t'); } +<str,vararg>\\b { STR_APPEND('\b'); } +<str,vararg>\\f { STR_APPEND('\f'); } +<str,vararg>\\(.|\n) { STR_APPEND(yytext[1]); } + + /* regexp backref inside string/arg */ +<str,vararg>[$][0-9] { + if (str_ptr != str_buf) { + /* return what we have so far and scan '$x' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + yyless(0); + return T_STRING; + } + else { + yylval->num = yytext[1] - '0'; + return T_REGEX_BACKREF; + } +} + +<str,vararg>[^\\\n"'%}$]+ { + char *cp = yytext; + while (*cp != '\0') { + STR_APPEND(*cp); + cp++; + } +} + + /* variable inside string/arg */ +<str,vararg>%\{ { + if (str_ptr != str_buf) { + /* return what we have so far and scan '%{' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + yyless(0); + str_ptr = str_buf; + return T_STRING; + } + else { + yy_push_state(var, yyscanner); + return T_VAR_BEGIN; + } +} + +<vararg>[%$] { + STR_APPEND(yytext[0]); +} + +<str>[%}$] { + STR_APPEND(yytext[0]); +} + +%\{ { + yy_push_state(var, yyscanner); + return T_VAR_BEGIN; +} + +[$][0-9] { + yylval->num = yytext[1] - '0'; + return T_REGEX_BACKREF; +} + + /* + * fixed name variable expansion %{XXX} and function call in %{func:arg} syntax + */ +<var>[a-zA-Z][a-zA-Z0-9_]* { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_ID; +} + +<var>\} { + yy_pop_state(yyscanner); + return T_VAR_END; +} + +<var>: { + BEGIN(vararg); + return yytext[0]; +} + +<var>.|\n { + char *msg = apr_psprintf(yyextra->pool, + "Invalid character in variable name '%c'", yytext[0]); + PERROR(msg); +} + +<vararg>\} { + if (str_ptr != str_buf) { + /* return what we have so far and scan '}' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + yyless(0); + return T_STRING; + } + else { + yy_pop_state(yyscanner); + return T_VAR_END; + } +} + + /* + * Regular Expression + */ +"m"[/#$%^,;:_\?\|\^\-\!\.\'\"] { + regex_del = yytext[1]; + regex_ptr = regex_buf; + BEGIN(regex); +} +"/" { + regex_del = yytext[0]; + regex_ptr = regex_buf; + BEGIN(regex); +} +<regex>.|\n { + if (yytext[0] == regex_del) { + *regex_ptr = '\0'; + BEGIN(regex_flags); + } + else { + *regex_ptr++ = yytext[0]; + if (regex_ptr >= regex_buf + sizeof(regex_buf)) + PERROR("Regexp too long"); + } +} +<regex_flags>i { + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + BEGIN(INITIAL); + return T_REGEX_I; +} +<regex_flags>.|\n { + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + yyless(0); + BEGIN(INITIAL); + return T_REGEX; +} +<regex_flags><<EOF>> { + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + BEGIN(INITIAL); + return T_REGEX; +} + + /* + * Operators + */ +==? { return T_OP_STR_EQ; } +"!=" { return T_OP_STR_NE; } +"<" { return T_OP_STR_LT; } +"<=" { return T_OP_STR_LE; } +">" { return T_OP_STR_GT; } +">=" { return T_OP_STR_GE; } +"=~" { return T_OP_REG; } +"!~" { return T_OP_NRE; } +"and" { return T_OP_AND; } +"&&" { return T_OP_AND; } +"or" { return T_OP_OR; } +"||" { return T_OP_OR; } +"not" { return T_OP_NOT; } +"!" { return T_OP_NOT; } +"." { return T_OP_CONCAT; } +"-in" { return T_OP_IN; } +"-eq" { return T_OP_EQ; } +"-ne" { return T_OP_NE; } +"-ge" { return T_OP_GE; } +"-le" { return T_OP_LE; } +"-gt" { return T_OP_GT; } +"-lt" { return T_OP_LT; } + + /* for compatibility with ssl_expr */ +"lt" { return T_OP_LT; } +"le" { return T_OP_LE; } +"gt" { return T_OP_GT; } +"ge" { return T_OP_GE; } +"ne" { return T_OP_NE; } +"eq" { return T_OP_EQ; } +"in" { return T_OP_IN; } + +"-"[a-zA-Z_] { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1); + return T_OP_UNARY; +} + +"-"[a-zA-Z_][a-zA-Z_0-9]+ { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1); + return T_OP_BINARY; +} + + /* + * Specials + */ +"true" { return T_TRUE; } +"false" { return T_FALSE; } + + /* + * Digits + */ +-?[0-9]+ { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_DIGIT; +} + + /* + * Identifiers + */ +[a-zA-Z][a-zA-Z0-9_]* { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_ID; +} + + /* + * These are parts of the grammar and are returned as is + */ +[(){},:] { + return yytext[0]; +} + + /* + * Anything else is an error + */ +.|\n { + char *msg = apr_psprintf(yyextra->pool, "Parse error near '%c'", yytext[0]); + PERROR(msg); +} + +%% + + diff --git a/server/util_fcgi.c b/server/util_fcgi.c new file mode 100644 index 0000000..7fb2c8c --- /dev/null +++ b/server/util_fcgi.c @@ -0,0 +1,290 @@ +/* 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_core.h" +#include "http_log.h" +#include "util_fcgi.h" + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +AP_DECLARE(void) ap_fcgi_header_to_array(ap_fcgi_header *h, + unsigned char a[]) +{ + a[AP_FCGI_HDR_VERSION_OFFSET] = h->version; + a[AP_FCGI_HDR_TYPE_OFFSET] = h->type; + a[AP_FCGI_HDR_REQUEST_ID_B1_OFFSET] = h->requestIdB1; + a[AP_FCGI_HDR_REQUEST_ID_B0_OFFSET] = h->requestIdB0; + a[AP_FCGI_HDR_CONTENT_LEN_B1_OFFSET] = h->contentLengthB1; + a[AP_FCGI_HDR_CONTENT_LEN_B0_OFFSET] = h->contentLengthB0; + a[AP_FCGI_HDR_PADDING_LEN_OFFSET] = h->paddingLength; + a[AP_FCGI_HDR_RESERVED_OFFSET] = h->reserved; +} + +AP_DECLARE(void) ap_fcgi_header_from_array(ap_fcgi_header *h, + unsigned char a[]) +{ + h->version = a[AP_FCGI_HDR_VERSION_OFFSET]; + h->type = a[AP_FCGI_HDR_TYPE_OFFSET]; + h->requestIdB1 = a[AP_FCGI_HDR_REQUEST_ID_B1_OFFSET]; + h->requestIdB0 = a[AP_FCGI_HDR_REQUEST_ID_B0_OFFSET]; + h->contentLengthB1 = a[AP_FCGI_HDR_CONTENT_LEN_B1_OFFSET]; + h->contentLengthB0 = a[AP_FCGI_HDR_CONTENT_LEN_B0_OFFSET]; + h->paddingLength = a[AP_FCGI_HDR_PADDING_LEN_OFFSET]; + h->reserved = a[AP_FCGI_HDR_RESERVED_OFFSET]; +} + +AP_DECLARE(void) ap_fcgi_header_fields_from_array(unsigned char *version, + unsigned char *type, + apr_uint16_t *request_id, + apr_uint16_t *content_len, + unsigned char *padding_len, + unsigned char a[]) +{ + *version = a[AP_FCGI_HDR_VERSION_OFFSET]; + *type = a[AP_FCGI_HDR_TYPE_OFFSET]; + *request_id = (a[AP_FCGI_HDR_REQUEST_ID_B1_OFFSET] << 8) + + a[AP_FCGI_HDR_REQUEST_ID_B0_OFFSET]; + *content_len = (a[AP_FCGI_HDR_CONTENT_LEN_B1_OFFSET] << 8) + + a[AP_FCGI_HDR_CONTENT_LEN_B0_OFFSET]; + *padding_len = a[AP_FCGI_HDR_PADDING_LEN_OFFSET]; +} + +AP_DECLARE(void) ap_fcgi_begin_request_body_to_array(ap_fcgi_begin_request_body *h, + unsigned char a[]) +{ + a[AP_FCGI_BRB_ROLEB1_OFFSET] = h->roleB1; + a[AP_FCGI_BRB_ROLEB0_OFFSET] = h->roleB0; + a[AP_FCGI_BRB_FLAGS_OFFSET] = h->flags; + a[AP_FCGI_BRB_RESERVED0_OFFSET] = h->reserved[0]; + a[AP_FCGI_BRB_RESERVED1_OFFSET] = h->reserved[1]; + a[AP_FCGI_BRB_RESERVED2_OFFSET] = h->reserved[2]; + a[AP_FCGI_BRB_RESERVED3_OFFSET] = h->reserved[3]; + a[AP_FCGI_BRB_RESERVED4_OFFSET] = h->reserved[4]; +} + +AP_DECLARE(void) ap_fcgi_fill_in_header(ap_fcgi_header *header, + unsigned char type, + apr_uint16_t request_id, + apr_uint16_t content_len, + unsigned char padding_len) +{ + header->version = AP_FCGI_VERSION_1; + + header->type = type; + + header->requestIdB1 = ((request_id >> 8) & 0xff); + header->requestIdB0 = ((request_id) & 0xff); + + header->contentLengthB1 = ((content_len >> 8) & 0xff); + header->contentLengthB0 = ((content_len) & 0xff); + + header->paddingLength = padding_len; + + header->reserved = 0; +} + +AP_DECLARE(void) ap_fcgi_fill_in_request_body(ap_fcgi_begin_request_body *brb, + int role, + unsigned char flags) +{ + brb->roleB1 = ((role >> 8) & 0xff); + brb->roleB0 = (role & 0xff); + brb->flags = flags; + brb->reserved[0] = 0; + brb->reserved[1] = 0; + brb->reserved[2] = 0; + brb->reserved[3] = 0; + brb->reserved[4] = 0; +} + +AP_DECLARE(apr_size_t) ap_fcgi_encoded_env_len(apr_table_t *env, + apr_size_t maxlen, + int *starting_elem) +{ + const apr_array_header_t *envarr; + const apr_table_entry_t *elts; + apr_size_t envlen, actualenvlen; + int i; + + if (maxlen > AP_FCGI_MAX_CONTENT_LEN) { + maxlen = AP_FCGI_MAX_CONTENT_LEN; + } + + envarr = apr_table_elts(env); + elts = (const apr_table_entry_t *) envarr->elts; + + /* envlen - speculative, may overflow the limit + * actualenvlen - len required without overflowing + */ + envlen = actualenvlen = 0; + for (i = *starting_elem; i < envarr->nelts; ) { + apr_size_t keylen, vallen; + + if (!elts[i].key) { + (*starting_elem)++; + i++; + continue; + } + + keylen = strlen(elts[i].key); + + if (keylen >> 7 == 0) { + envlen += 1; + } + else { + envlen += 4; + } + + envlen += keylen; + + vallen = elts[i].val ? strlen(elts[i].val) : 0; + + if (vallen >> 7 == 0) { + envlen += 1; + } + else { + envlen += 4; + } + + envlen += vallen; + + if (envlen > maxlen) { + break; + } + + actualenvlen = envlen; + (*starting_elem)++; + i++; + } + + return actualenvlen; +} + +AP_DECLARE(apr_status_t) ap_fcgi_encode_env(request_rec *r, + apr_table_t *env, + void *buffer, + apr_size_t buflen, + int *starting_elem) +{ + apr_status_t rv = APR_SUCCESS; + const apr_array_header_t *envarr; + const apr_table_entry_t *elts; + char *itr; + int i; + + envarr = apr_table_elts(env); + elts = (const apr_table_entry_t *) envarr->elts; + + itr = buffer; + + for (i = *starting_elem; i < envarr->nelts; ) { + apr_size_t keylen, vallen; + + if (!elts[i].key) { + (*starting_elem)++; + i++; + continue; + } + + keylen = strlen(elts[i].key); + + if (keylen >> 7 == 0) { + if (buflen < 1) { + rv = APR_ENOSPC; /* overflow */ + break; + } + itr[0] = keylen & 0xff; + itr += 1; + buflen -= 1; + } + else { + if (buflen < 4) { + rv = APR_ENOSPC; /* overflow */ + break; + } + itr[0] = ((keylen >> 24) & 0xff) | 0x80; + itr[1] = ((keylen >> 16) & 0xff); + itr[2] = ((keylen >> 8) & 0xff); + itr[3] = ((keylen) & 0xff); + itr += 4; + buflen -= 4; + } + + vallen = elts[i].val ? strlen(elts[i].val) : 0; + + if (vallen >> 7 == 0) { + if (buflen < 1) { + rv = APR_ENOSPC; /* overflow */ + break; + } + itr[0] = vallen & 0xff; + itr += 1; + buflen -= 1; + } + else { + if (buflen < 4) { + rv = APR_ENOSPC; /* overflow */ + break; + } + itr[0] = ((vallen >> 24) & 0xff) | 0x80; + itr[1] = ((vallen >> 16) & 0xff); + itr[2] = ((vallen >> 8) & 0xff); + itr[3] = ((vallen) & 0xff); + itr += 4; + buflen -= 4; + } + + if (buflen < keylen) { + rv = APR_ENOSPC; /* overflow */ + break; + } + memcpy(itr, elts[i].key, keylen); + itr += keylen; + buflen -= keylen; + + if (buflen < vallen) { + rv = APR_ENOSPC; /* overflow */ + break; + } + + if (elts[i].val) { + memcpy(itr, elts[i].val, vallen); + itr += vallen; + } + + if (buflen == vallen) { + (*starting_elem)++; + i++; + break; /* filled up predicted space, as expected */ + } + + buflen -= vallen; + + (*starting_elem)++; + i++; + } + + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02492) + "ap_fcgi_encode_env: out of space " + "encoding environment"); + } + + return rv; +} diff --git a/server/util_filter.c b/server/util_filter.c new file mode 100644 index 0000000..51e24f6 --- /dev/null +++ b/server/util_filter.c @@ -0,0 +1,732 @@ +/* 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 APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_lib.h" +#include "apr_hash.h" +#include "apr_strings.h" + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "util_filter.h" + +/* NOTE: Apache's current design doesn't allow a pool to be passed thru, + so we depend on a global to hold the correct pool +*/ +#define FILTER_POOL apr_hook_global_pool +#include "ap_hooks.h" /* for apr_hook_global_pool */ + +/* +** This macro returns true/false if a given filter should be inserted BEFORE +** another filter. This will happen when one of: 1) there isn't another +** filter; 2) that filter has a higher filter type (class); 3) that filter +** corresponds to a different request. +*/ +#define INSERT_BEFORE(f, before_this) ((before_this) == NULL \ + || (before_this)->frec->ftype > (f)->frec->ftype \ + || (before_this)->r != (f)->r) + +/* Trie structure to hold the mapping from registered + * filter names to filters + */ + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +typedef struct filter_trie_node filter_trie_node; + +typedef struct { + int c; + filter_trie_node *child; +} filter_trie_child_ptr; + +/* Each trie node has an array of pointers to its children. + * The array is kept in sorted order so that add_any_filter() + * can do a binary search + */ +struct filter_trie_node { + ap_filter_rec_t *frec; + filter_trie_child_ptr *children; + int nchildren; + int size; +}; + +#define TRIE_INITIAL_SIZE 4 + +/* Link a trie node to its parent + */ +static void trie_node_link(apr_pool_t *p, filter_trie_node *parent, + filter_trie_node *child, int c) +{ + int i, j; + + if (parent->nchildren == parent->size) { + filter_trie_child_ptr *new; + parent->size *= 2; + new = (filter_trie_child_ptr *)apr_palloc(p, parent->size * + sizeof(filter_trie_child_ptr)); + memcpy(new, parent->children, parent->nchildren * + sizeof(filter_trie_child_ptr)); + parent->children = new; + } + + for (i = 0; i < parent->nchildren; i++) { + if (c == parent->children[i].c) { + return; + } + else if (c < parent->children[i].c) { + break; + } + } + for (j = parent->nchildren; j > i; j--) { + parent->children[j].c = parent->children[j - 1].c; + parent->children[j].child = parent->children[j - 1].child; + } + parent->children[i].c = c; + parent->children[i].child = child; + + parent->nchildren++; +} + +/* Allocate a new node for a trie. + * If parent is non-NULL, link the new node under the parent node with + * key 'c' (or, if an existing child node matches, return that one) + */ +static filter_trie_node *trie_node_alloc(apr_pool_t *p, + filter_trie_node *parent, char c) +{ + filter_trie_node *new_node; + if (parent) { + int i; + for (i = 0; i < parent->nchildren; i++) { + if (c == parent->children[i].c) { + return parent->children[i].child; + } + else if (c < parent->children[i].c) { + break; + } + } + new_node = + (filter_trie_node *)apr_palloc(p, sizeof(filter_trie_node)); + trie_node_link(p, parent, new_node, c); + } + else { /* No parent node */ + new_node = (filter_trie_node *)apr_palloc(p, + sizeof(filter_trie_node)); + } + + new_node->frec = NULL; + new_node->nchildren = 0; + new_node->size = TRIE_INITIAL_SIZE; + new_node->children = (filter_trie_child_ptr *)apr_palloc(p, + new_node->size * sizeof(filter_trie_child_ptr)); + return new_node; +} + +static filter_trie_node *registered_output_filters = NULL; +static filter_trie_node *registered_input_filters = NULL; + + +static apr_status_t filter_cleanup(void *ctx) +{ + registered_output_filters = NULL; + registered_input_filters = NULL; + return APR_SUCCESS; +} + +static ap_filter_rec_t *get_filter_handle(const char *name, + const filter_trie_node *filter_set) +{ + if (filter_set) { + const char *n; + const filter_trie_node *node; + + node = filter_set; + for (n = name; *n; n++) { + int start, end; + start = 0; + end = node->nchildren - 1; + while (end >= start) { + int middle = (end + start) / 2; + char ch = node->children[middle].c; + if (*n == ch) { + node = node->children[middle].child; + break; + } + else if (*n < ch) { + end = middle - 1; + } + else { + start = middle + 1; + } + } + if (end < start) { + node = NULL; + break; + } + } + + if (node && node->frec) { + return node->frec; + } + } + return NULL; +} + +AP_DECLARE(ap_filter_rec_t *)ap_get_output_filter_handle(const char *name) +{ + return get_filter_handle(name, registered_output_filters); +} + +AP_DECLARE(ap_filter_rec_t *)ap_get_input_filter_handle(const char *name) +{ + return get_filter_handle(name, registered_input_filters); +} + +static ap_filter_rec_t *register_filter(const char *name, + ap_filter_func filter_func, + ap_init_filter_func filter_init, + ap_filter_type ftype, + filter_trie_node **reg_filter_set) +{ + ap_filter_rec_t *frec; + char *normalized_name; + const char *n; + filter_trie_node *node; + + if (!*reg_filter_set) { + *reg_filter_set = trie_node_alloc(FILTER_POOL, NULL, 0); + } + + normalized_name = apr_pstrdup(FILTER_POOL, name); + ap_str_tolower(normalized_name); + + node = *reg_filter_set; + for (n = normalized_name; *n; n++) { + filter_trie_node *child = trie_node_alloc(FILTER_POOL, node, *n); + if (apr_isalpha(*n)) { + trie_node_link(FILTER_POOL, node, child, apr_toupper(*n)); + } + node = child; + } + if (node->frec) { + frec = node->frec; + } + else { + frec = apr_pcalloc(FILTER_POOL, sizeof(*frec)); + node->frec = frec; + frec->name = normalized_name; + } + frec->filter_func = filter_func; + frec->filter_init_func = filter_init; + frec->ftype = ftype; + + apr_pool_cleanup_register(FILTER_POOL, NULL, filter_cleanup, + apr_pool_cleanup_null); + return frec; +} + +AP_DECLARE(ap_filter_rec_t *) ap_register_input_filter(const char *name, + ap_in_filter_func filter_func, + ap_init_filter_func filter_init, + ap_filter_type ftype) +{ + ap_filter_func f; + f.in_func = filter_func; + return register_filter(name, f, filter_init, ftype, + ®istered_input_filters); +} + +AP_DECLARE(ap_filter_rec_t *) ap_register_output_filter(const char *name, + ap_out_filter_func filter_func, + ap_init_filter_func filter_init, + ap_filter_type ftype) +{ + return ap_register_output_filter_protocol(name, filter_func, + filter_init, ftype, 0); +} + +AP_DECLARE(ap_filter_rec_t *) ap_register_output_filter_protocol( + const char *name, + ap_out_filter_func filter_func, + ap_init_filter_func filter_init, + ap_filter_type ftype, + unsigned int proto_flags) +{ + ap_filter_rec_t* ret ; + ap_filter_func f; + f.out_func = filter_func; + ret = register_filter(name, f, filter_init, ftype, + ®istered_output_filters); + ret->proto_flags = proto_flags ; + return ret ; +} + +static ap_filter_t *add_any_filter_handle(ap_filter_rec_t *frec, void *ctx, + request_rec *r, conn_rec *c, + ap_filter_t **r_filters, + ap_filter_t **p_filters, + ap_filter_t **c_filters) +{ + apr_pool_t *p = frec->ftype < AP_FTYPE_CONNECTION && r ? r->pool : c->pool; + ap_filter_t *f = apr_palloc(p, sizeof(*f)); + ap_filter_t **outf; + + if (frec->ftype < AP_FTYPE_PROTOCOL) { + if (r) { + outf = r_filters; + } + else { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(00080) + "a content filter was added without a request: %s", frec->name); + return NULL; + } + } + else if (frec->ftype < AP_FTYPE_CONNECTION) { + if (r) { + outf = p_filters; + } + else { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(00081) + "a protocol filter was added without a request: %s", frec->name); + return NULL; + } + } + else { + outf = c_filters; + } + + f->frec = frec; + f->ctx = ctx; + /* f->r must always be NULL for connection filters */ + f->r = frec->ftype < AP_FTYPE_CONNECTION ? r : NULL; + f->c = c; + f->next = NULL; + + if (INSERT_BEFORE(f, *outf)) { + f->next = *outf; + + if (*outf) { + ap_filter_t *first = NULL; + + if (r) { + /* If we are adding our first non-connection filter, + * Then don't try to find the right location, it is + * automatically first. + */ + if (*r_filters != *c_filters) { + first = *r_filters; + while (first && (first->next != (*outf))) { + first = first->next; + } + } + } + if (first && first != (*outf)) { + first->next = f; + } + } + *outf = f; + } + else { + ap_filter_t *fscan = *outf; + while (!INSERT_BEFORE(f, fscan->next)) + fscan = fscan->next; + + f->next = fscan->next; + fscan->next = f; + } + + if (frec->ftype < AP_FTYPE_CONNECTION && (*r_filters == *c_filters)) { + *r_filters = *p_filters; + } + return f; +} + +static ap_filter_t *add_any_filter(const char *name, void *ctx, + request_rec *r, conn_rec *c, + const filter_trie_node *reg_filter_set, + ap_filter_t **r_filters, + ap_filter_t **p_filters, + ap_filter_t **c_filters) +{ + if (reg_filter_set) { + const char *n; + const filter_trie_node *node; + + node = reg_filter_set; + for (n = name; *n; n++) { + int start, end; + start = 0; + end = node->nchildren - 1; + while (end >= start) { + int middle = (end + start) / 2; + char ch = node->children[middle].c; + if (*n == ch) { + node = node->children[middle].child; + break; + } + else if (*n < ch) { + end = middle - 1; + } + else { + start = middle + 1; + } + } + if (end < start) { + node = NULL; + break; + } + } + + if (node && node->frec) { + return add_any_filter_handle(node->frec, ctx, r, c, r_filters, + p_filters, c_filters); + } + } + + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, r ? r->connection : c, APLOGNO(00082) + "an unknown filter was not added: %s", name); + return NULL; +} + +AP_DECLARE(ap_filter_t *) ap_add_input_filter(const char *name, void *ctx, + request_rec *r, conn_rec *c) +{ + return add_any_filter(name, ctx, r, c, registered_input_filters, + r ? &r->input_filters : NULL, + r ? &r->proto_input_filters : NULL, &c->input_filters); +} + +AP_DECLARE(ap_filter_t *) ap_add_input_filter_handle(ap_filter_rec_t *f, + void *ctx, + request_rec *r, + conn_rec *c) +{ + return add_any_filter_handle(f, ctx, r, c, r ? &r->input_filters : NULL, + r ? &r->proto_input_filters : NULL, + &c->input_filters); +} + +AP_DECLARE(ap_filter_t *) ap_add_output_filter(const char *name, void *ctx, + request_rec *r, conn_rec *c) +{ + return add_any_filter(name, ctx, r, c, registered_output_filters, + r ? &r->output_filters : NULL, + r ? &r->proto_output_filters : NULL, &c->output_filters); +} + +AP_DECLARE(ap_filter_t *) ap_add_output_filter_handle(ap_filter_rec_t *f, + void *ctx, + request_rec *r, + conn_rec *c) +{ + return add_any_filter_handle(f, ctx, r, c, r ? &r->output_filters : NULL, + r ? &r->proto_output_filters : NULL, + &c->output_filters); +} + +static void remove_any_filter(ap_filter_t *f, ap_filter_t **r_filt, ap_filter_t **p_filt, + ap_filter_t **c_filt) +{ + ap_filter_t **curr = r_filt ? r_filt : c_filt; + ap_filter_t *fscan = *curr; + + if (p_filt && *p_filt == f) + *p_filt = (*p_filt)->next; + + if (*curr == f) { + *curr = (*curr)->next; + return; + } + + while (fscan->next != f) { + if (!(fscan = fscan->next)) { + return; + } + } + + fscan->next = f->next; +} + +AP_DECLARE(void) ap_remove_input_filter(ap_filter_t *f) +{ + remove_any_filter(f, f->r ? &f->r->input_filters : NULL, + f->r ? &f->r->proto_input_filters : NULL, + &f->c->input_filters); +} + +AP_DECLARE(void) ap_remove_output_filter(ap_filter_t *f) +{ + remove_any_filter(f, f->r ? &f->r->output_filters : NULL, + f->r ? &f->r->proto_output_filters : NULL, + &f->c->output_filters); +} + +AP_DECLARE(apr_status_t) ap_remove_input_filter_byhandle(ap_filter_t *next, + const char *handle) +{ + ap_filter_t *found = NULL; + ap_filter_rec_t *filter; + + if (!handle) { + return APR_EINVAL; + } + filter = ap_get_input_filter_handle(handle); + if (!filter) { + return APR_NOTFOUND; + } + + while (next) { + if (next->frec == filter) { + found = next; + break; + } + next = next->next; + } + if (found) { + ap_remove_input_filter(found); + return APR_SUCCESS; + } + return APR_NOTFOUND; +} + +AP_DECLARE(apr_status_t) ap_remove_output_filter_byhandle(ap_filter_t *next, + const char *handle) +{ + ap_filter_t *found = NULL; + ap_filter_rec_t *filter; + + if (!handle) { + return APR_EINVAL; + } + filter = ap_get_output_filter_handle(handle); + if (!filter) { + return APR_NOTFOUND; + } + + while (next) { + if (next->frec == filter) { + found = next; + break; + } + next = next->next; + } + if (found) { + ap_remove_output_filter(found); + return APR_SUCCESS; + } + return APR_NOTFOUND; +} + + +/* + * Read data from the next filter in the filter stack. Data should be + * modified in the bucket brigade that is passed in. The core allocates the + * bucket brigade, modules that wish to replace large chunks of data or to + * save data off to the side should probably create their own temporary + * brigade especially for that use. + */ +AP_DECLARE(apr_status_t) ap_get_brigade(ap_filter_t *next, + apr_bucket_brigade *bb, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + if (next) { + return next->frec->filter_func.in_func(next, bb, mode, block, + readbytes); + } + return AP_NOBODY_READ; +} + +/* Pass the buckets to the next filter in the filter stack. If the + * current filter is a handler, we should get NULL passed in instead of + * the current filter. At that point, we can just call the first filter in + * the stack, or r->output_filters. + */ +AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *next, + apr_bucket_brigade *bb) +{ + if (next) { + apr_bucket *e; + if ((e = APR_BRIGADE_LAST(bb)) && APR_BUCKET_IS_EOS(e) && next->r) { + /* This is only safe because HTTP_HEADER filter is always in + * the filter stack. This ensures that there is ALWAYS a + * request-based filter that we can attach this to. If the + * HTTP_FILTER is removed, and another filter is not put in its + * place, then handlers like mod_cgi, which attach their own + * EOS bucket to the brigade will be broken, because we will + * get two EOS buckets on the same request. + */ + next->r->eos_sent = 1; + + /* remember the eos for internal redirects, too */ + if (next->r->prev) { + request_rec *prev = next->r->prev; + + while (prev) { + prev->eos_sent = 1; + prev = prev->prev; + } + } + } + return next->frec->filter_func.out_func(next, bb); + } + return AP_NOBODY_WROTE; +} + +/* Pass the buckets to the next filter in the filter stack + * checking return status for filter errors. + * returns: OK if ap_pass_brigade returns APR_SUCCESS + * AP_FILTER_ERROR if filter error exists + * HTTP_INTERNAL_SERVER_ERROR for all other cases + * logged with optional errmsg + */ +AP_DECLARE(apr_status_t) ap_pass_brigade_fchk(request_rec *r, + apr_bucket_brigade *bb, + const char *fmt, + ...) +{ + apr_status_t rv; + + rv = ap_pass_brigade(r->output_filters, bb); + if (rv != APR_SUCCESS) { + if (rv != AP_FILTER_ERROR) { + if (!fmt) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00083) + "ap_pass_brigade returned %d", rv); + else { + va_list ap; + const char *res; + va_start(ap, fmt); + res = apr_pvsprintf(r->pool, fmt, ap); + va_end(ap); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03158) + "%s", res); + } + return HTTP_INTERNAL_SERVER_ERROR; + } + return AP_FILTER_ERROR; + } + return OK; +} + +AP_DECLARE(apr_status_t) ap_save_brigade(ap_filter_t *f, + apr_bucket_brigade **saveto, + apr_bucket_brigade **b, apr_pool_t *p) +{ + apr_bucket *e; + apr_status_t rv, srv = APR_SUCCESS; + + /* If have never stored any data in the filter, then we had better + * create an empty bucket brigade so that we can concat. + */ + if (!(*saveto)) { + *saveto = apr_brigade_create(p, f->c->bucket_alloc); + } + + for (e = APR_BRIGADE_FIRST(*b); + e != APR_BRIGADE_SENTINEL(*b); + e = APR_BUCKET_NEXT(e)) + { + rv = apr_bucket_setaside(e, p); + + /* If the bucket type does not implement setaside, then + * (hopefully) morph it into a bucket type which does, and set + * *that* aside... */ + if (rv == APR_ENOTIMPL) { + const char *s; + apr_size_t n; + + rv = apr_bucket_read(e, &s, &n, APR_BLOCK_READ); + if (rv == APR_SUCCESS) { + rv = apr_bucket_setaside(e, p); + } + } + + if (rv != APR_SUCCESS) { + srv = rv; + /* Return an error but still save the brigade if + * ->setaside() is really not implemented. */ + if (rv != APR_ENOTIMPL) { + return rv; + } + } + } + APR_BRIGADE_CONCAT(*saveto, *b); + return srv; +} + +AP_DECLARE_NONSTD(apr_status_t) ap_filter_flush(apr_bucket_brigade *bb, + void *ctx) +{ + ap_filter_t *f = ctx; + apr_status_t rv; + + rv = ap_pass_brigade(f, bb); + + /* Before invocation of the flush callback, apr_brigade_write et + * al may place transient buckets in the brigade, which will fall + * out of scope after returning. Empty the brigade here, to avoid + * issues with leaving such buckets in the brigade if some filter + * fails and leaves a non-empty brigade. */ + apr_brigade_cleanup(bb); + + return rv; +} + +AP_DECLARE(apr_status_t) ap_fflush(ap_filter_t *f, apr_bucket_brigade *bb) +{ + apr_bucket *b; + + b = apr_bucket_flush_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + return ap_pass_brigade(f, bb); +} + +AP_DECLARE_NONSTD(apr_status_t) ap_fputstrs(ap_filter_t *f, + apr_bucket_brigade *bb, ...) +{ + va_list args; + apr_status_t rv; + + va_start(args, bb); + rv = apr_brigade_vputstrs(bb, ap_filter_flush, f, args); + va_end(args); + return rv; +} + +AP_DECLARE_NONSTD(apr_status_t) ap_fprintf(ap_filter_t *f, + apr_bucket_brigade *bb, + const char *fmt, + ...) +{ + va_list args; + apr_status_t rv; + + va_start(args, fmt); + rv = apr_brigade_vprintf(bb, ap_filter_flush, f, fmt, args); + va_end(args); + return rv; +} +AP_DECLARE(void) ap_filter_protocol(ap_filter_t *f, unsigned int flags) +{ + f->frec->proto_flags = flags ; +} diff --git a/server/util_md5.c b/server/util_md5.c new file mode 100644 index 0000000..148c60c --- /dev/null +++ b/server/util_md5.c @@ -0,0 +1,166 @@ +/* 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. + */ + +/************************************************************************ + * NCSA HTTPd Server + * Software Development Group + * National Center for Supercomputing Applications + * University of Illinois at Urbana-Champaign + * 605 E. Springfield, Champaign, IL 61820 + * httpd@ncsa.uiuc.edu + * + * Copyright (C) 1995, Board of Trustees of the University of Illinois + * + ************************************************************************ + * + * md5.c: NCSA HTTPd code which uses the md5c.c RSA Code + * + * Original Code Copyright (C) 1994, Jeff Hostetler, Spyglass, Inc. + * Portions of Content-MD5 code Copyright (C) 1993, 1994 by Carnegie Mellon + * University (see Copyright below). + * Portions of Content-MD5 code Copyright (C) 1991 Bell Communications + * Research, Inc. (Bellcore) (see Copyright below). + * Portions extracted from mpack, John G. Myers - jgm+@cmu.edu + * Content-MD5 Code contributed by Martin Hamilton (martin@net.lut.ac.uk) + * + */ + + + +/* md5.c --Module Interface to MD5. */ +/* Jeff Hostetler, Spyglass, Inc., 1994. */ + +#include "ap_config.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "httpd.h" +#include "util_md5.h" +#include "util_ebcdic.h" + +AP_DECLARE(char *) ap_md5_binary(apr_pool_t *p, const unsigned char *buf, int length) +{ + apr_md5_ctx_t my_md5; + unsigned char hash[APR_MD5_DIGESTSIZE]; + char result[2 * APR_MD5_DIGESTSIZE + 1]; + + /* + * Take the MD5 hash of the string argument. + */ + + apr_md5_init(&my_md5); +#if APR_CHARSET_EBCDIC + apr_md5_set_xlate(&my_md5, ap_hdrs_to_ascii); +#endif + apr_md5_update(&my_md5, buf, (unsigned int)length); + apr_md5_final(hash, &my_md5); + + ap_bin2hex(hash, APR_MD5_DIGESTSIZE, result); + + return apr_pstrndup(p, result, APR_MD5_DIGESTSIZE*2); +} + +AP_DECLARE(char *) ap_md5(apr_pool_t *p, const unsigned char *string) +{ + return ap_md5_binary(p, string, (int) strlen((char *)string)); +} + +/* these portions extracted from mpack, John G. Myers - jgm+@cmu.edu */ + +/* (C) Copyright 1993,1994 by Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of Carnegie + * Mellon University not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. Carnegie Mellon University makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore) + * + * Permission to use, copy, modify, and distribute this material + * for any purpose and without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies, and that the name of Bellcore not be + * used in advertising or publicity pertaining to this + * material without the specific, prior written permission + * of an authorized representative of Bellcore. BELLCORE + * MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY + * OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS", + * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. + */ + +static char basis_64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +AP_DECLARE(char *) ap_md5contextTo64(apr_pool_t *a, apr_md5_ctx_t *context) +{ + unsigned char digest[18]; + char *encodedDigest; + int i; + char *p; + + encodedDigest = (char *) apr_pcalloc(a, 25 * sizeof(char)); + + apr_md5_final(digest, context); + digest[sizeof(digest) - 1] = digest[sizeof(digest) - 2] = 0; + + p = encodedDigest; + for (i = 0; i < sizeof(digest); i += 3) { + *p++ = basis_64[digest[i] >> 2]; + *p++ = basis_64[((digest[i] & 0x3) << 4) | ((int) (digest[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((digest[i + 1] & 0xF) << 2) | ((int) (digest[i + 2] & 0xC0) >> 6)]; + *p++ = basis_64[digest[i + 2] & 0x3F]; + } + *p-- = '\0'; + *p-- = '='; + *p-- = '='; + return encodedDigest; +} + +AP_DECLARE(char *) ap_md5digest(apr_pool_t *p, apr_file_t *infile) +{ + apr_md5_ctx_t context; + unsigned char buf[4096]; /* keep this a multiple of 64 */ + apr_size_t nbytes; + apr_off_t offset = 0L; + + apr_md5_init(&context); + nbytes = sizeof(buf); + while (apr_file_read(infile, buf, &nbytes) == APR_SUCCESS) { + apr_md5_update(&context, buf, nbytes); + nbytes = sizeof(buf); + } + apr_file_seek(infile, APR_SET, &offset); + return ap_md5contextTo64(p, &context); +} + diff --git a/server/util_mutex.c b/server/util_mutex.c new file mode 100644 index 0000000..e9249f6 --- /dev/null +++ b/server/util_mutex.c @@ -0,0 +1,561 @@ +/* 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. + */ + +/* + * util_mutex.c: Useful functions for determining allowable + * mutexes and mutex settings + */ + + +#include "apr.h" +#include "apr_hash.h" +#include "apr_strings.h" +#include "apr_lib.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_main.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "util_mutex.h" +#if AP_NEED_SET_MUTEX_PERMS +#include "unixd.h" +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> /* getpid() */ +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +AP_DECLARE(apr_status_t) ap_parse_mutex(const char *arg, apr_pool_t *pool, + apr_lockmech_e *mutexmech, + const char **mutexfile) +{ + /* Split arg into meth and file */ + char *meth = apr_pstrdup(pool, arg); + char *file = strchr(meth, ':'); + if (file) { + *(file++) = '\0'; + if (!*file) { + file = NULL; + } + } + + /* APR determines temporary filename unless overridden below, + * we presume file indicates an mutexfile is a file path + * unless the method sets mutexfile=file and NULLs file + */ + *mutexfile = NULL; + + if (!strcasecmp(meth, "none") || !strcasecmp(meth, "no")) { + return APR_ENOLOCK; + } + + /* NOTE: previously, 'yes' implied 'sem' */ + if (!strcasecmp(meth, "default") || !strcasecmp(meth, "yes")) { + *mutexmech = APR_LOCK_DEFAULT; + } +#if APR_HAS_FCNTL_SERIALIZE + else if (!strcasecmp(meth, "fcntl") || !strcasecmp(meth, "file")) { + *mutexmech = APR_LOCK_FCNTL; + } +#endif +#if APR_HAS_FLOCK_SERIALIZE + else if (!strcasecmp(meth, "flock") || !strcasecmp(meth, "file")) { + *mutexmech = APR_LOCK_FLOCK; + } +#endif +#if APR_HAS_POSIXSEM_SERIALIZE + else if (!strcasecmp(meth, "posixsem") || !strcasecmp(meth, "sem")) { + *mutexmech = APR_LOCK_POSIXSEM; + /* Posix/SysV semaphores aren't file based, use the literal name + * if provided and fall back on APR's default if not. Today, APR + * will ignore it, but once supported it has an absurdly short limit. + */ + if (file) { + *mutexfile = apr_pstrdup(pool, file); + + file = NULL; + } + } +#endif +#if APR_HAS_SYSVSEM_SERIALIZE + else if (!strcasecmp(meth, "sysvsem") || !strcasecmp(meth, "sem")) { + *mutexmech = APR_LOCK_SYSVSEM; + } +#endif +#if APR_HAS_PROC_PTHREAD_SERIALIZE + else if (!strcasecmp(meth, "pthread")) { + *mutexmech = APR_LOCK_PROC_PTHREAD; + } +#endif + else { + return APR_ENOTIMPL; + } + + /* Unless the method above assumed responsibility for setting up + * mutexfile and NULLing out file, presume it is a file we + * are looking to use + */ + if (file) { + *mutexfile = ap_server_root_relative(pool, file); + if (!*mutexfile) { + return APR_BADARG; + } + } + + return APR_SUCCESS; +} + +typedef struct { + apr_int32_t options; + int set; + int none; + int omit_pid; + apr_lockmech_e mech; + const char *dir; +} mutex_cfg_t; + +/* hash is created the first time a module calls ap_mutex_register(), + * rather than attempting to be the REALLY_REALLY_FIRST pre-config + * hook; it is cleaned up when the associated pool goes away; assume + * pconf is the pool passed to ap_mutex_register() + */ +static apr_hash_t *mxcfg_by_type; + +AP_DECLARE_NONSTD(void) ap_mutex_init(apr_pool_t *p) +{ + mutex_cfg_t *def; + + if (mxcfg_by_type) { + return; + } + + mxcfg_by_type = apr_hash_make(p); + apr_pool_cleanup_register(p, &mxcfg_by_type, ap_pool_cleanup_set_null, + apr_pool_cleanup_null); + + /* initialize default mutex configuration */ + def = apr_pcalloc(p, sizeof *def); + def->mech = APR_LOCK_DEFAULT; + def->dir = ap_runtime_dir_relative(p, ""); + apr_hash_set(mxcfg_by_type, "default", APR_HASH_KEY_STRING, def); +} + +AP_DECLARE_NONSTD(const char *)ap_set_mutex(cmd_parms *cmd, void *dummy, + const char *arg) +{ + apr_pool_t *p = cmd->pool; + apr_pool_t *ptemp = cmd->temp_pool; + const char **elt; + const char *mechdir; + int no_mutex = 0, omit_pid = 0; + apr_array_header_t *type_list; + apr_lockmech_e mech; + apr_status_t rv; + const char *mutexdir; + mutex_cfg_t *mxcfg; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + mechdir = ap_getword_conf(cmd->pool, &arg); + if (*mechdir == '\0') { + return "Mutex requires at least a mechanism argument (" + AP_ALL_AVAILABLE_MUTEXES_STRING ")"; + } + + rv = ap_parse_mutex(mechdir, p, &mech, &mutexdir); + if (rv == APR_ENOTIMPL) { + return apr_pstrcat(p, "Invalid Mutex argument ", mechdir, + " (" AP_ALL_AVAILABLE_MUTEXES_STRING ")", NULL); + } + else if (rv == APR_BADARG + || (mutexdir && !ap_is_directory(ptemp, mutexdir))) { + return apr_pstrcat(p, "Invalid Mutex directory in argument ", + mechdir, NULL); + } + else if (rv == APR_ENOLOCK) { /* "none" */ + no_mutex = 1; + } + + /* "OmitPID" can appear at the end of the list, so build a list of + * mutex type names while looking for "OmitPID" (anywhere) or the end + */ + type_list = apr_array_make(cmd->pool, 4, sizeof(const char *)); + while (*arg) { + const char *s = ap_getword_conf(cmd->pool, &arg); + + if (!strcasecmp(s, "omitpid")) { + omit_pid = 1; + } + else { + const char **new_type = (const char **)apr_array_push(type_list); + *new_type = s; + } + } + + if (apr_is_empty_array(type_list)) { /* no mutex type? assume "default" */ + const char **new_type = (const char **)apr_array_push(type_list); + *new_type = "default"; + } + + while ((elt = (const char **)apr_array_pop(type_list)) != NULL) { + const char *type = *elt; + mxcfg = apr_hash_get(mxcfg_by_type, type, APR_HASH_KEY_STRING); + if (!mxcfg) { + return apr_psprintf(p, "Mutex type %s is not valid", type); + } + + mxcfg->none = 0; /* in case that was the default */ + mxcfg->omit_pid = omit_pid; + + mxcfg->set = 1; + if (no_mutex) { + if (!(mxcfg->options & AP_MUTEX_ALLOW_NONE)) { + return apr_psprintf(p, + "None is not allowed for mutex type %s", + type); + } + mxcfg->none = 1; + } + else { + mxcfg->mech = mech; + if (mutexdir) { /* retain mutex default if not configured */ + mxcfg->dir = mutexdir; + } + } + } + + return NULL; +} + +AP_DECLARE(apr_status_t) ap_mutex_register(apr_pool_t *pconf, + const char *type, + const char *default_dir, + apr_lockmech_e default_mech, + apr_int32_t options) +{ + mutex_cfg_t *mxcfg = apr_pcalloc(pconf, sizeof *mxcfg); + + if ((options & ~(AP_MUTEX_ALLOW_NONE | AP_MUTEX_DEFAULT_NONE))) { + return APR_EINVAL; + } + + ap_mutex_init(pconf); /* in case this mod's pre-config ran before core's */ + + mxcfg->options = options; + if (options & AP_MUTEX_DEFAULT_NONE) { + mxcfg->none = 1; + } + mxcfg->dir = default_dir; /* usually NULL */ + mxcfg->mech = default_mech; /* usually APR_LOCK_DEFAULT */ + apr_hash_set(mxcfg_by_type, type, APR_HASH_KEY_STRING, mxcfg); + + return APR_SUCCESS; +} + +static int mutex_needs_file(apr_lockmech_e mech) +{ + if (mech != APR_LOCK_FLOCK + && mech != APR_LOCK_FCNTL +#if APR_USE_FLOCK_SERIALIZE || APR_USE_FCNTL_SERIALIZE + && mech != APR_LOCK_DEFAULT +#endif + ) { + return 0; + } + return 1; +} + +static const char *get_mutex_filename(apr_pool_t *p, mutex_cfg_t *mxcfg, + const char *type, + const char *instance_id) +{ + const char *pid_suffix = ""; + + if (!mutex_needs_file(mxcfg->mech)) { + return NULL; + } + +#if HAVE_UNISTD_H + if (!mxcfg->omit_pid) { + pid_suffix = apr_psprintf(p, ".%" APR_PID_T_FMT, getpid()); + } +#endif + + return ap_server_root_relative(p, + apr_pstrcat(p, + mxcfg->dir, + "/", + type, + instance_id ? "-" : "", + instance_id ? instance_id : "", + pid_suffix, + NULL)); +} + +static mutex_cfg_t *mxcfg_lookup(apr_pool_t *p, const char *type) +{ + mutex_cfg_t *defcfg, *mxcfg, *newcfg; + + defcfg = apr_hash_get(mxcfg_by_type, "default", APR_HASH_KEY_STRING); + + /* MUST exist in table, or wasn't registered */ + mxcfg = apr_hash_get(mxcfg_by_type, type, APR_HASH_KEY_STRING); + if (!mxcfg) { + return NULL; + } + + /* order of precedence: + * 1. Mutex directive for this mutex + * 2. Mutex directive for "default" + * 3. Defaults for this mutex from ap_mutex_register() + * 4. Global defaults + */ + + if (mxcfg->set) { + newcfg = mxcfg; + } + else if (defcfg->set) { + newcfg = defcfg; + } + else if (mxcfg->none || mxcfg->mech != APR_LOCK_DEFAULT) { + newcfg = mxcfg; + } + else { + newcfg = defcfg; + } + + if (!newcfg->none && mutex_needs_file(newcfg->mech) && !newcfg->dir) { + /* a file-based mutex mechanism was configured, but + * without a mutex file directory; go back through + * the chain to find the directory, store in new + * mutex cfg structure + */ + newcfg = apr_pmemdup(p, newcfg, sizeof *newcfg); + + /* !true if dir not already set: mxcfg->set && defcfg->dir */ + if (defcfg->set && defcfg->dir) { + newcfg->dir = defcfg->dir; + } + else if (mxcfg->dir) { + newcfg->dir = mxcfg->dir; + } + else { + newcfg->dir = defcfg->dir; + } + } + + return newcfg; +} + +static void log_bad_create_options(server_rec *s, const char *type) +{ + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(00021) + "Invalid options were specified when creating the %s mutex", + type); +} + +static void log_unknown_type(server_rec *s, const char *type) +{ + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(00022) + "Can't create mutex of unknown type %s", type); +} + +static void log_create_failure(apr_status_t rv, server_rec *s, const char *type, + const char *fname) +{ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(00023) + "Couldn't create the %s mutex %s%s%s", type, + fname ? "(file " : "", + fname ? fname : "", + fname ? ")" : ""); +} + +#ifdef AP_NEED_SET_MUTEX_PERMS +static void log_perms_failure(apr_status_t rv, server_rec *s, const char *type) +{ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(00024) + "Couldn't set permissions on the %s mutex; " + "check User and Group directives", + type); +} +#endif + +AP_DECLARE(apr_status_t) ap_global_mutex_create(apr_global_mutex_t **mutex, + const char **name, + const char *type, + const char *instance_id, + server_rec *s, apr_pool_t *p, + apr_int32_t options) +{ + apr_status_t rv; + const char *fname; + mutex_cfg_t *mxcfg = mxcfg_lookup(p, type); + + if (options) { + log_bad_create_options(s, type); + return APR_EINVAL; + } + + if (!mxcfg) { + log_unknown_type(s, type); + return APR_EINVAL; + } + + if (mxcfg->none) { + *mutex = NULL; + return APR_SUCCESS; + } + + fname = get_mutex_filename(p, mxcfg, type, instance_id); + + rv = apr_global_mutex_create(mutex, fname, mxcfg->mech, p); + if (rv != APR_SUCCESS) { + log_create_failure(rv, s, type, fname); + return rv; + } + + if (name) + *name = fname; + +#ifdef AP_NEED_SET_MUTEX_PERMS + rv = ap_unixd_set_global_mutex_perms(*mutex); + if (rv != APR_SUCCESS) { + log_perms_failure(rv, s, type); + } +#endif + + return rv; +} + +AP_DECLARE(apr_status_t) ap_proc_mutex_create(apr_proc_mutex_t **mutex, + const char **name, + const char *type, + const char *instance_id, + server_rec *s, apr_pool_t *p, + apr_int32_t options) +{ + apr_status_t rv; + const char *fname; + mutex_cfg_t *mxcfg = mxcfg_lookup(p, type); + + if (options) { + log_bad_create_options(s, type); + return APR_EINVAL; + } + + if (!mxcfg) { + log_unknown_type(s, type); + return APR_EINVAL; + } + + if (mxcfg->none) { + *mutex = NULL; + return APR_SUCCESS; + } + + fname = get_mutex_filename(p, mxcfg, type, instance_id); + + rv = apr_proc_mutex_create(mutex, fname, mxcfg->mech, p); + if (rv != APR_SUCCESS) { + log_create_failure(rv, s, type, fname); + return rv; + } + + if (name) + *name = fname; + +#ifdef AP_NEED_SET_MUTEX_PERMS + rv = ap_unixd_set_proc_mutex_perms(*mutex); + if (rv != APR_SUCCESS) { + log_perms_failure(rv, s, type); + } +#endif + + return rv; +} + +AP_CORE_DECLARE(void) ap_dump_mutexes(apr_pool_t *p, server_rec *s, apr_file_t *out) +{ + apr_hash_index_t *idx; + mutex_cfg_t *defcfg = apr_hash_get(mxcfg_by_type, "default", APR_HASH_KEY_STRING); + for (idx = apr_hash_first(p, mxcfg_by_type); idx; idx = apr_hash_next(idx)) + { + mutex_cfg_t *mxcfg; + const char *name, *mech = "<unknown>"; + const void *name_; + const char *dir = ""; + apr_hash_this(idx, &name_, NULL, NULL); + name = name_; + mxcfg = mxcfg_lookup(p, name); + if (mxcfg == defcfg && strcmp(name, "default") != 0) { + apr_file_printf(out, "Mutex %s: using_defaults\n", name); + continue; + } + if (mxcfg->none) { + apr_file_printf(out, "Mutex %s: none\n", name); + continue; + } + switch (mxcfg->mech) { + case APR_LOCK_DEFAULT: + mech = "default"; + break; +#if APR_HAS_FCNTL_SERIALIZE + case APR_LOCK_FCNTL: + mech = "fcntl"; + break; +#endif +#if APR_HAS_FLOCK_SERIALIZE + case APR_LOCK_FLOCK: + mech = "flock"; + break; +#endif +#if APR_HAS_POSIXSEM_SERIALIZE + case APR_LOCK_POSIXSEM: + mech = "posixsem"; + break; +#endif +#if APR_HAS_SYSVSEM_SERIALIZE + case APR_LOCK_SYSVSEM: + mech = "sysvsem"; + break; +#endif +#if APR_HAS_PROC_PTHREAD_SERIALIZE + case APR_LOCK_PROC_PTHREAD: + mech = "pthread"; + break; +#endif + default: + ap_assert(0); + } + + if (mxcfg->dir) + dir = ap_server_root_relative(p, mxcfg->dir); + + apr_file_printf(out, "Mutex %s: dir=\"%s\" mechanism=%s %s\n", name, dir, mech, + mxcfg->omit_pid ? "[OmitPid]" : ""); + } +} diff --git a/server/util_pcre.c b/server/util_pcre.c new file mode 100644 index 0000000..f2cb1bb --- /dev/null +++ b/server/util_pcre.c @@ -0,0 +1,343 @@ +/* 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 code is based on pcreposix.c from the PCRE Library distribution, + * as originally written by Philip Hazel <ph10@cam.ac.uk>, and forked by + * the Apache HTTP Server project to provide POSIX-style regex function + * wrappers around underlying PCRE library functions for httpd. + * + * The original source file pcreposix.c is copyright and licensed as follows; + + Copyright (c) 1997-2004 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#include "httpd.h" +#include "apr_strings.h" +#include "apr_tables.h" +#include "pcre.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#ifndef POSIX_MALLOC_THRESHOLD +#define POSIX_MALLOC_THRESHOLD (10) +#endif + +/* Table of error strings corresponding to POSIX error codes; must be + * kept in synch with include/ap_regex.h's AP_REG_E* definitions. + */ + +static const char *const pstring[] = { + "", /* Dummy for value 0 */ + "internal error", /* AP_REG_ASSERT */ + "failed to get memory", /* AP_REG_ESPACE */ + "bad argument", /* AP_REG_INVARG */ + "match failed" /* AP_REG_NOMATCH */ +}; + +AP_DECLARE(apr_size_t) ap_regerror(int errcode, const ap_regex_t *preg, + char *errbuf, apr_size_t errbuf_size) +{ + const char *message, *addmessage; + apr_size_t length, addlength; + + message = (errcode >= (int)(sizeof(pstring) / sizeof(char *))) ? + "unknown error code" : pstring[errcode]; + length = strlen(message) + 1; + + addmessage = " at offset "; + addlength = (preg != NULL && (int)preg->re_erroffset != -1) ? + strlen(addmessage) + 6 : 0; + + if (errbuf_size > 0) { + if (addlength > 0 && errbuf_size >= length + addlength) + apr_snprintf(errbuf, errbuf_size, "%s%s%-6d", message, addmessage, + (int)preg->re_erroffset); + else + apr_cpystrn(errbuf, message, errbuf_size); + } + + return length + addlength; +} + + + + +/************************************************* + * Free store held by a regex * + *************************************************/ + +AP_DECLARE(void) ap_regfree(ap_regex_t *preg) +{ + (pcre_free)(preg->re_pcre); +} + + + + +/************************************************* + * Compile a regular expression * + *************************************************/ + +static int default_cflags = AP_REG_DOLLAR_ENDONLY; + +AP_DECLARE(int) ap_regcomp_get_default_cflags(void) +{ + return default_cflags; +} + +AP_DECLARE(void) ap_regcomp_set_default_cflags(int cflags) +{ + default_cflags = cflags; +} + +AP_DECLARE(int) ap_regcomp_default_cflag_by_name(const char *name) +{ + int cflag = 0; + + if (ap_cstr_casecmp(name, "ICASE") == 0) { + cflag = AP_REG_ICASE; + } + else if (ap_cstr_casecmp(name, "DOTALL") == 0) { + cflag = AP_REG_DOTALL; + } + else if (ap_cstr_casecmp(name, "DOLLAR_ENDONLY") == 0) { + cflag = AP_REG_DOLLAR_ENDONLY; + } + else if (ap_cstr_casecmp(name, "EXTENDED") == 0) { + cflag = AP_REG_EXTENDED; + } + + return cflag; +} + +/* + * Arguments: + * preg points to a structure for recording the compiled expression + * pattern the pattern to compile + * cflags compilation flags + * + * Returns: 0 on success + * various non-zero codes on failure +*/ +AP_DECLARE(int) ap_regcomp(ap_regex_t * preg, const char *pattern, int cflags) +{ + const char *errorptr; + int erroffset; + int errcode = 0; + int options = PCRE_DUPNAMES; + + cflags |= default_cflags; + if ((cflags & AP_REG_ICASE) != 0) + options |= PCRE_CASELESS; + if ((cflags & AP_REG_NEWLINE) != 0) + options |= PCRE_MULTILINE; + if ((cflags & AP_REG_DOTALL) != 0) + options |= PCRE_DOTALL; + if ((cflags & AP_REG_DOLLAR_ENDONLY) != 0) + options |= PCRE_DOLLAR_ENDONLY; + + preg->re_pcre = + pcre_compile2(pattern, options, &errcode, &errorptr, &erroffset, NULL); + preg->re_erroffset = erroffset; + + if (preg->re_pcre == NULL) { + /* + * There doesn't seem to be constants defined for compile time error + * codes. 21 is "failed to get memory" according to pcreapi(3). + */ + if (errcode == 21) + return AP_REG_ESPACE; + return AP_REG_INVARG; + } + + pcre_fullinfo((const pcre *)preg->re_pcre, NULL, + PCRE_INFO_CAPTURECOUNT, &(preg->re_nsub)); + return 0; +} + + + + +/************************************************* + * Match a regular expression * + *************************************************/ + +/* Unfortunately, PCRE requires 3 ints of working space for each captured + * substring, so we have to get and release working store instead of just using + * the POSIX structures as was done in earlier releases when PCRE needed only 2 + * ints. However, if the number of possible capturing brackets is small, use a + * block of store on the stack, to reduce the use of malloc/free. The threshold + * is in a macro that can be changed at configure time. + */ +AP_DECLARE(int) ap_regexec(const ap_regex_t *preg, const char *string, + apr_size_t nmatch, ap_regmatch_t *pmatch, + int eflags) +{ + return ap_regexec_len(preg, string, strlen(string), nmatch, pmatch, + eflags); +} + +AP_DECLARE(int) ap_regexec_len(const ap_regex_t *preg, const char *buff, + apr_size_t len, apr_size_t nmatch, + ap_regmatch_t *pmatch, int eflags) +{ + int rc; + int options = 0; + int *ovector = NULL; + int small_ovector[POSIX_MALLOC_THRESHOLD * 3]; + int allocated_ovector = 0; + + if ((eflags & AP_REG_NOTBOL) != 0) + options |= PCRE_NOTBOL; + if ((eflags & AP_REG_NOTEOL) != 0) + options |= PCRE_NOTEOL; + + ((ap_regex_t *)preg)->re_erroffset = (apr_size_t)(-1); /* Only has meaning after compile */ + + if (nmatch > 0) { + if (nmatch <= POSIX_MALLOC_THRESHOLD) { + ovector = &(small_ovector[0]); + } + else { + ovector = (int *)malloc(sizeof(int) * nmatch * 3); + if (ovector == NULL) + return AP_REG_ESPACE; + allocated_ovector = 1; + } + } + + rc = pcre_exec((const pcre *)preg->re_pcre, NULL, buff, (int)len, + 0, options, ovector, nmatch * 3); + + if (rc == 0) + rc = nmatch; /* All captured slots were filled in */ + + if (rc >= 0) { + apr_size_t i; + for (i = 0; i < (apr_size_t)rc; i++) { + pmatch[i].rm_so = ovector[i * 2]; + pmatch[i].rm_eo = ovector[i * 2 + 1]; + } + if (allocated_ovector) + free(ovector); + for (; i < nmatch; i++) + pmatch[i].rm_so = pmatch[i].rm_eo = -1; + return 0; + } + + else { + if (allocated_ovector) + free(ovector); + switch (rc) { + case PCRE_ERROR_NOMATCH: + return AP_REG_NOMATCH; + case PCRE_ERROR_NULL: + return AP_REG_INVARG; + case PCRE_ERROR_BADOPTION: + return AP_REG_INVARG; + case PCRE_ERROR_BADMAGIC: + return AP_REG_INVARG; + case PCRE_ERROR_UNKNOWN_NODE: + return AP_REG_ASSERT; + case PCRE_ERROR_NOMEMORY: + return AP_REG_ESPACE; +#ifdef PCRE_ERROR_MATCHLIMIT + case PCRE_ERROR_MATCHLIMIT: + return AP_REG_ESPACE; +#endif +#ifdef PCRE_ERROR_BADUTF8 + case PCRE_ERROR_BADUTF8: + return AP_REG_INVARG; +#endif +#ifdef PCRE_ERROR_BADUTF8_OFFSET + case PCRE_ERROR_BADUTF8_OFFSET: + return AP_REG_INVARG; +#endif + default: + return AP_REG_ASSERT; + } + } +} + +AP_DECLARE(int) ap_regname(const ap_regex_t *preg, + apr_array_header_t *names, const char *prefix, + int upper) +{ + int namecount; + int nameentrysize; + int i; + char *nametable; + + pcre_fullinfo((const pcre *)preg->re_pcre, NULL, + PCRE_INFO_NAMECOUNT, &namecount); + pcre_fullinfo((const pcre *)preg->re_pcre, NULL, + PCRE_INFO_NAMEENTRYSIZE, &nameentrysize); + pcre_fullinfo((const pcre *)preg->re_pcre, NULL, + PCRE_INFO_NAMETABLE, &nametable); + + for (i = 0; i < namecount; i++) { + const char *offset = nametable + i * nameentrysize; + int capture = ((offset[0] << 8) + offset[1]); + while (names->nelts <= capture) { + apr_array_push(names); + } + if (upper || prefix) { + char *name = ((char **) names->elts)[capture] = + prefix ? apr_pstrcat(names->pool, prefix, offset + 2, + NULL) : + apr_pstrdup(names->pool, offset + 2); + if (upper) { + ap_str_toupper(name); + } + } + else { + ((const char **)names->elts)[capture] = offset + 2; + } + } + + return namecount; +} + +/* End of pcreposix.c */ diff --git a/server/util_regex.c b/server/util_regex.c new file mode 100644 index 0000000..2a30d68 --- /dev/null +++ b/server/util_regex.c @@ -0,0 +1,210 @@ +/* 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 "apr.h" +#include "apr_lib.h" +#include "apr_pools.h" +#include "apr_strings.h" +#include "ap_config.h" +#include "ap_regex.h" +#include "httpd.h" + +static apr_status_t rxplus_cleanup(void *preg) +{ + ap_regfree((ap_regex_t *) preg); + return APR_SUCCESS; +} + +AP_DECLARE(ap_rxplus_t*) ap_rxplus_compile(apr_pool_t *pool, + const char *pattern) +{ + /* perl style patterns + * add support for more as and when wanted + * substitute: s/rx/subs/ + * match: m/rx/ or just /rx/ + */ + + /* allow any nonalnum delimiter as first or second char. + * If we ever use this with non-string pattern we'll need an extra check + */ + const char *endp = 0; + const char *str = pattern; + const char *rxstr; + ap_rxplus_t *ret = apr_pcalloc(pool, sizeof(ap_rxplus_t)); + char delim = 0; + enum { SUBSTITUTE = 's', MATCH = 'm'} action = MATCH; + + if (!apr_isalnum(pattern[0])) { + delim = *str++; + } + else if (pattern[0] == 's' && !apr_isalnum(pattern[1])) { + action = SUBSTITUTE; + delim = pattern[1]; + str += 2; + } + else if (pattern[0] == 'm' && !apr_isalnum(pattern[1])) { + delim = pattern[1]; + str += 2; + } + /* TODO: support perl's after/before */ + /* FIXME: fix these simplminded delims */ + + /* we think there's a delimiter. Allow for it not to be if unmatched */ + if (delim) { + endp = ap_strchr_c(str, delim); + } + if (!endp) { /* there's no delim or flags */ + if (ap_regcomp(&ret->rx, pattern, 0) == 0) { + apr_pool_cleanup_register(pool, &ret->rx, rxplus_cleanup, + apr_pool_cleanup_null); + return ret; + } + else { + return NULL; + } + } + + /* We have a delimiter. Use it to extract the regexp */ + rxstr = apr_pstrmemdup(pool, str, endp-str); + + /* If it's a substitution, we need the replacement string + * TODO: possible future enhancement - support other parsing + * in the replacement string. + */ + if (action == SUBSTITUTE) { + str = endp+1; + if (!*str || (endp = ap_strchr_c(str, delim), !endp)) { + /* missing replacement string is an error */ + return NULL; + } + ret->subs = apr_pstrmemdup(pool, str, endp-str); + } + + /* anything after the current delimiter is flags */ + while (*++endp) { + switch (*endp) { + case 'i': ret->flags |= AP_REG_ICASE; break; + case 'm': ret->flags |= AP_REG_NEWLINE; break; + case 'n': ret->flags |= AP_REG_NOMEM; break; + case 'g': ret->flags |= AP_REG_MULTI; break; + case 's': ret->flags |= AP_REG_DOTALL; break; + case '^': ret->flags |= AP_REG_NOTBOL; break; + case '$': ret->flags |= AP_REG_NOTEOL; break; + default: break; /* we should probably be stricter here */ + } + } + if (ap_regcomp(&ret->rx, rxstr, ret->flags) == 0) { + apr_pool_cleanup_register(pool, &ret->rx, rxplus_cleanup, + apr_pool_cleanup_null); + } + else { + return NULL; + } + if (!(ret->flags & AP_REG_NOMEM)) { + /* count size of memory required, starting at 1 for the whole-match + * Simpleminded should be fine 'cos regcomp already checked syntax + */ + ret->nmatch = 1; + while (*rxstr) { + switch (*rxstr++) { + case '\\': /* next char is escaped - skip it */ + if (*rxstr != 0) { + ++rxstr; + } + break; + case '(': /* unescaped bracket implies memory */ + ++ret->nmatch; + break; + default: + break; + } + } + ret->pmatch = apr_palloc(pool, ret->nmatch*sizeof(ap_regmatch_t)); + } + return ret; +} + +AP_DECLARE(int) ap_rxplus_exec(apr_pool_t *pool, ap_rxplus_t *rx, + const char *pattern, char **newpattern) +{ + int ret = 1; + int startl, oldl, newl, diffsz; + const char *remainder; + char *subs; +/* snrf process_regexp from mod_headers */ + if (ap_regexec(&rx->rx, pattern, rx->nmatch, rx->pmatch, rx->flags) != 0) { + rx->match = NULL; + return 0; /* no match, nothing to do */ + } + rx->match = pattern; + if (rx->subs) { + *newpattern = ap_pregsub(pool, rx->subs, pattern, + rx->nmatch, rx->pmatch); + if (!*newpattern) { + return 0; /* FIXME - should we do more to handle error? */ + } + startl = rx->pmatch[0].rm_so; + oldl = rx->pmatch[0].rm_eo - startl; + newl = strlen(*newpattern); + diffsz = newl - oldl; + remainder = pattern + startl + oldl; + if (rx->flags & AP_REG_MULTI) { + /* recurse to do any further matches */ + ret += ap_rxplus_exec(pool, rx, remainder, &subs); + if (ret > 1) { + /* a further substitution happened */ + diffsz += strlen(subs) - strlen(remainder); + remainder = subs; + } + } + subs = apr_palloc(pool, strlen(pattern) + 1 + diffsz); + memcpy(subs, pattern, startl); + memcpy(subs+startl, *newpattern, newl); + strcpy(subs+startl+newl, remainder); + *newpattern = subs; + } + return ret; +} +#ifdef DOXYGEN +AP_DECLARE(int) ap_rxplus_nmatch(ap_rxplus_t *rx) +{ + return (rx->match != NULL) ? rx->nmatch : 0; +} +#endif + +/* If this blows up on you, see the notes in the header/apidoc + * rx->match is a pointer and it's your responsibility to ensure + * it hasn't gone out-of-scope since the last ap_rxplus_exec + */ +AP_DECLARE(void) ap_rxplus_match(ap_rxplus_t *rx, int n, int *len, + const char **match) +{ + if (n >= 0 && n < ap_rxplus_nmatch(rx)) { + *match = rx->match + rx->pmatch[n].rm_so; + *len = rx->pmatch[n].rm_eo - rx->pmatch[n].rm_so; + } + else { + *len = -1; + *match = NULL; + } +} +AP_DECLARE(char*) ap_rxplus_pmatch(apr_pool_t *pool, ap_rxplus_t *rx, int n) +{ + int len; + const char *match; + ap_rxplus_match(rx, n, &len, &match); + return apr_pstrndup(pool, match, len); +} diff --git a/server/util_script.c b/server/util_script.c new file mode 100644 index 0000000..599ba58 --- /dev/null +++ b/server/util_script.c @@ -0,0 +1,899 @@ +/* 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 "apr.h" +#include "apr_lib.h" +#include "apr_strings.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_main.h" +#include "http_log.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_request.h" /* for sub_req_lookup_uri() */ +#include "util_script.h" +#include "apr_date.h" /* For apr_date_parse_http() */ +#include "util_ebcdic.h" + +#ifdef OS2 +#define INCL_DOS +#include <os2.h> +#endif + +/* + * Various utility functions which are common to a whole lot of + * script-type extensions mechanisms, and might as well be gathered + * in one place (if only to avoid creating inter-module dependancies + * where there don't have to be). + */ + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +static char *http2env(request_rec *r, const char *w) +{ + char *res = (char *)apr_palloc(r->pool, sizeof("HTTP_") + strlen(w)); + char *cp = res; + char c; + + *cp++ = 'H'; + *cp++ = 'T'; + *cp++ = 'T'; + *cp++ = 'P'; + *cp++ = '_'; + + while ((c = *w++) != 0) { + if (apr_isalnum(c)) { + *cp++ = apr_toupper(c); + } + else if (c == '-') { + *cp++ = '_'; + } + else { + if (APLOGrtrace1(r)) + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "Not exporting header with invalid name as envvar: %s", + ap_escape_logitem(r->pool, w)); + return NULL; + } + } + *cp = 0; + + return res; +} + +static void add_unless_null(apr_table_t *table, const char *name, const char *val) +{ + if (name && val) { + apr_table_addn(table, name, val); + } +} + +static void env2env(apr_table_t *table, const char *name) +{ + add_unless_null(table, name, getenv(name)); +} + +AP_DECLARE(char **) ap_create_environment(apr_pool_t *p, apr_table_t *t) +{ + const apr_array_header_t *env_arr = apr_table_elts(t); + const apr_table_entry_t *elts = (const apr_table_entry_t *) env_arr->elts; + char **env = (char **) apr_palloc(p, (env_arr->nelts + 2) * sizeof(char *)); + int i, j; + char *tz; + char *whack; + + j = 0; + if (!apr_table_get(t, "TZ")) { + tz = getenv("TZ"); + if (tz != NULL) { + env[j++] = apr_pstrcat(p, "TZ=", tz, NULL); + } + } + for (i = 0; i < env_arr->nelts; ++i) { + if (!elts[i].key) { + continue; + } + env[j] = apr_pstrcat(p, elts[i].key, "=", elts[i].val, NULL); + whack = env[j]; + if (apr_isdigit(*whack)) { + *whack++ = '_'; + } + while (*whack != '=') { +#ifdef WIN32 + if (!apr_isalnum(*whack) && *whack != '(' && *whack != ')') { +#else + if (!apr_isalnum(*whack)) { +#endif + *whack = '_'; + } + ++whack; + } + ++j; + } + + env[j] = NULL; + return env; +} + +AP_DECLARE(void) ap_add_common_vars(request_rec *r) +{ + apr_table_t *e; + server_rec *s = r->server; + conn_rec *c = r->connection; + core_dir_config *conf = + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + const char *env_temp; + const apr_array_header_t *hdrs_arr = apr_table_elts(r->headers_in); + const apr_table_entry_t *hdrs = (const apr_table_entry_t *) hdrs_arr->elts; + int i; + apr_port_t rport; + char *q; + + /* use a temporary apr_table_t which we'll overlap onto + * r->subprocess_env later + * (exception: if r->subprocess_env is empty at the start, + * write directly into it) + */ + if (apr_is_empty_table(r->subprocess_env)) { + e = r->subprocess_env; + } + else { + e = apr_table_make(r->pool, 25 + hdrs_arr->nelts); + } + + /* First, add environment vars from headers... this is as per + * CGI specs, though other sorts of scripting interfaces see + * the same vars... + */ + + for (i = 0; i < hdrs_arr->nelts; ++i) { + if (!hdrs[i].key) { + continue; + } + + /* A few headers are special cased --- Authorization to prevent + * rogue scripts from capturing passwords; content-type and -length + * for no particular reason. + */ + + if (!strcasecmp(hdrs[i].key, "Content-type")) { + apr_table_addn(e, "CONTENT_TYPE", hdrs[i].val); + } + else if (!strcasecmp(hdrs[i].key, "Content-length")) { + apr_table_addn(e, "CONTENT_LENGTH", hdrs[i].val); + } + /* HTTP_PROXY collides with a popular envvar used to configure + * proxies, don't let clients set/override it. But, if you must... + */ +#ifndef SECURITY_HOLE_PASS_PROXY + else if (!ap_cstr_casecmp(hdrs[i].key, "Proxy")) { + ; + } +#endif + /* + * You really don't want to disable this check, since it leaves you + * wide open to CGIs stealing passwords and people viewing them + * in the environment with "ps -e". But, if you must... + */ +#ifndef SECURITY_HOLE_PASS_AUTHORIZATION + else if (!strcasecmp(hdrs[i].key, "Authorization") + || !strcasecmp(hdrs[i].key, "Proxy-Authorization")) { + if (conf->cgi_pass_auth == AP_CGI_PASS_AUTH_ON) { + add_unless_null(e, http2env(r, hdrs[i].key), hdrs[i].val); + } + } +#endif + else + add_unless_null(e, http2env(r, hdrs[i].key), hdrs[i].val); + } + + env_temp = apr_table_get(r->subprocess_env, "PATH"); + if (env_temp == NULL) { + env_temp = getenv("PATH"); + } + if (env_temp == NULL) { + env_temp = DEFAULT_PATH; + } + apr_table_addn(e, "PATH", apr_pstrdup(r->pool, env_temp)); + +#if defined(WIN32) + env2env(e, "SystemRoot"); + env2env(e, "COMSPEC"); + env2env(e, "PATHEXT"); + env2env(e, "WINDIR"); +#elif defined(OS2) + env2env(e, "COMSPEC"); + env2env(e, "ETC"); + env2env(e, "DPATH"); + env2env(e, "PERLLIB_PREFIX"); +#elif defined(BEOS) + env2env(e, "LIBRARY_PATH"); +#elif defined(DARWIN) + env2env(e, "DYLD_LIBRARY_PATH"); +#elif defined(_AIX) + env2env(e, "LIBPATH"); +#elif defined(__HPUX__) + /* HPUX PARISC 2.0W knows both, otherwise redundancy is harmless */ + env2env(e, "SHLIB_PATH"); + env2env(e, "LD_LIBRARY_PATH"); +#else /* Some Unix */ + env2env(e, "LD_LIBRARY_PATH"); +#endif + + apr_table_addn(e, "SERVER_SIGNATURE", ap_psignature("", r)); + apr_table_addn(e, "SERVER_SOFTWARE", ap_get_server_banner()); + apr_table_addn(e, "SERVER_NAME", + ap_escape_html(r->pool, ap_get_server_name_for_url(r))); + apr_table_addn(e, "SERVER_ADDR", r->connection->local_ip); /* Apache */ + apr_table_addn(e, "SERVER_PORT", + apr_psprintf(r->pool, "%u", ap_get_server_port(r))); + add_unless_null(e, "REMOTE_HOST", + ap_get_useragent_host(r, REMOTE_HOST, NULL)); + apr_table_addn(e, "REMOTE_ADDR", r->useragent_ip); + apr_table_addn(e, "DOCUMENT_ROOT", ap_document_root(r)); /* Apache */ + apr_table_setn(e, "REQUEST_SCHEME", ap_http_scheme(r)); + apr_table_addn(e, "CONTEXT_PREFIX", ap_context_prefix(r)); + apr_table_addn(e, "CONTEXT_DOCUMENT_ROOT", ap_context_document_root(r)); + apr_table_addn(e, "SERVER_ADMIN", s->server_admin); /* Apache */ + if (apr_table_get(r->notes, "proxy-noquery") && (q = ap_strchr(r->filename, '?'))) { + char *script_filename = apr_pstrmemdup(r->pool, r->filename, q - r->filename); + apr_table_addn(e, "SCRIPT_FILENAME", script_filename); + } + else { + apr_table_addn(e, "SCRIPT_FILENAME", r->filename); /* Apache */ + } + + rport = c->client_addr->port; + apr_table_addn(e, "REMOTE_PORT", apr_itoa(r->pool, rport)); + + if (r->user) { + apr_table_addn(e, "REMOTE_USER", r->user); + } + else if (r->prev) { + request_rec *back = r->prev; + + while (back) { + if (back->user) { + apr_table_addn(e, "REDIRECT_REMOTE_USER", back->user); + break; + } + back = back->prev; + } + } + add_unless_null(e, "AUTH_TYPE", r->ap_auth_type); + env_temp = ap_get_remote_logname(r); + if (env_temp) { + apr_table_addn(e, "REMOTE_IDENT", apr_pstrdup(r->pool, env_temp)); + } + + /* Apache custom error responses. If we have redirected set two new vars */ + + if (r->prev) { + if (conf->qualify_redirect_url != AP_CORE_CONFIG_ON) { + add_unless_null(e, "REDIRECT_URL", r->prev->uri); + } + else { + /* PR#57785: reconstruct full URL here */ + apr_uri_t *uri = &r->prev->parsed_uri; + if (!uri->scheme) { + uri->scheme = (char*)ap_http_scheme(r->prev); + } + if (!uri->port) { + uri->port = ap_get_server_port(r->prev); + uri->port_str = apr_psprintf(r->pool, "%u", uri->port); + } + if (!uri->hostname) { + uri->hostname = (char*)ap_get_server_name_for_url(r->prev); + } + add_unless_null(e, "REDIRECT_URL", + apr_uri_unparse(r->pool, uri, 0)); + } + add_unless_null(e, "REDIRECT_QUERY_STRING", r->prev->args); + } + + if (e != r->subprocess_env) { + apr_table_overlap(r->subprocess_env, e, APR_OVERLAP_TABLES_SET); + } +} + +/* This "cute" little function comes about because the path info on + * filenames and URLs aren't always the same. So we take the two, + * and find as much of the two that match as possible. + */ + +AP_DECLARE(int) ap_find_path_info(const char *uri, const char *path_info) +{ + int lu = strlen(uri); + int lp = strlen(path_info); + + while (lu-- && lp-- && uri[lu] == path_info[lp]) { + if (path_info[lp] == '/') { + while (lu && uri[lu-1] == '/') lu--; + } + } + + if (lu == -1) { + lu = 0; + } + + while (uri[lu] != '\0' && uri[lu] != '/') { + lu++; + } + return lu; +} + +/* Obtain the Request-URI from the original request-line, returning + * a new string from the request pool containing the URI or "". + */ +static char *original_uri(request_rec *r) +{ + char *first, *last; + + if (r->the_request == NULL) { + return (char *) apr_pcalloc(r->pool, 1); + } + + first = r->the_request; /* use the request-line */ + + while (*first && !apr_isspace(*first)) { + ++first; /* skip over the method */ + } + while (apr_isspace(*first)) { + ++first; /* and the space(s) */ + } + + last = first; + while (*last && !apr_isspace(*last)) { + ++last; /* end at next whitespace */ + } + + return apr_pstrmemdup(r->pool, first, last - first); +} + +AP_DECLARE(void) ap_add_cgi_vars(request_rec *r) +{ + apr_table_t *e = r->subprocess_env; + core_dir_config *conf = + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + int request_uri_from_original = 1; + const char *request_uri_rule; + + apr_table_setn(e, "GATEWAY_INTERFACE", "CGI/1.1"); + apr_table_setn(e, "SERVER_PROTOCOL", r->protocol); + apr_table_setn(e, "REQUEST_METHOD", r->method); + apr_table_setn(e, "QUERY_STRING", r->args ? r->args : ""); + + if (conf->cgi_var_rules) { + request_uri_rule = apr_hash_get(conf->cgi_var_rules, "REQUEST_URI", + APR_HASH_KEY_STRING); + if (request_uri_rule && !strcmp(request_uri_rule, "current-uri")) { + request_uri_from_original = 0; + } + } + apr_table_setn(e, "REQUEST_URI", + request_uri_from_original ? original_uri(r) : r->uri); + + /* Note that the code below special-cases scripts run from includes, + * because it "knows" that the sub_request has been hacked to have the + * args and path_info of the original request, and not any that may have + * come with the script URI in the include command. Ugh. + */ + + if (!strcmp(r->protocol, "INCLUDED")) { + apr_table_setn(e, "SCRIPT_NAME", r->uri); + if (r->path_info && *r->path_info) { + apr_table_setn(e, "PATH_INFO", r->path_info); + } + } + else if (!r->path_info || !*r->path_info) { + apr_table_setn(e, "SCRIPT_NAME", r->uri); + } + else { + int path_info_start = ap_find_path_info(r->uri, r->path_info); + + apr_table_setn(e, "SCRIPT_NAME", + apr_pstrndup(r->pool, r->uri, path_info_start)); + + apr_table_setn(e, "PATH_INFO", r->path_info); + } + + if (r->path_info && r->path_info[0]) { + /* + * To get PATH_TRANSLATED, treat PATH_INFO as a URI path. + * Need to re-escape it for this, since the entire URI was + * un-escaped before we determined where the PATH_INFO began. + */ + request_rec *pa_req; + + pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r, + NULL); + + if (pa_req->filename) { + char *pt = apr_pstrcat(r->pool, pa_req->filename, pa_req->path_info, + NULL); +#ifdef WIN32 + /* We need to make this a real Windows path name */ + apr_filepath_merge(&pt, "", pt, APR_FILEPATH_NATIVE, r->pool); +#endif + apr_table_setn(e, "PATH_TRANSLATED", pt); + } + ap_destroy_sub_req(pa_req); + } +} + + +static int set_cookie_doo_doo(void *v, const char *key, const char *val) +{ + apr_table_addn(v, key, val); + return 1; +} + +#define HTTP_UNSET (-HTTP_OK) +#define SCRIPT_LOG_MARK __FILE__,__LINE__,module_index + +AP_DECLARE(int) ap_scan_script_header_err_core_ex(request_rec *r, char *buffer, + int (*getsfunc) (char *, int, void *), + void *getsfunc_data, + int module_index) +{ + char x[MAX_STRING_LEN]; + char *w, *l; + int p; + int cgi_status = HTTP_UNSET; + apr_table_t *merge; + apr_table_t *cookie_table; + int trace_log = APLOG_R_MODULE_IS_LEVEL(r, module_index, APLOG_TRACE1); + int first_header = 1; + + if (buffer) { + *buffer = '\0'; + } + w = buffer ? buffer : x; + + /* temporary place to hold headers to merge in later */ + merge = apr_table_make(r->pool, 10); + + /* The HTTP specification says that it is legal to merge duplicate + * headers into one. Some browsers that support Cookies don't like + * merged headers and prefer that each Set-Cookie header is sent + * separately. Lets humour those browsers by not merging. + * Oh what a pain it is. + */ + cookie_table = apr_table_make(r->pool, 2); + apr_table_do(set_cookie_doo_doo, cookie_table, r->err_headers_out, "Set-Cookie", NULL); + + while (1) { + + int rv = (*getsfunc) (w, MAX_STRING_LEN - 1, getsfunc_data); + if (rv == 0) { + const char *msg = "Premature end of script headers"; + if (first_header) + msg = "End of script output before headers"; + /* Intentional no APLOGNO */ + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, + "%s: %s", msg, + apr_filepath_name_get(r->filename)); + return HTTP_INTERNAL_SERVER_ERROR; + } + else if (rv == -1) { + /* Intentional no APLOGNO */ + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, + "Script timed out before returning headers: %s", + apr_filepath_name_get(r->filename)); + return HTTP_GATEWAY_TIME_OUT; + } + + /* Delete terminal (CR?)LF */ + + p = strlen(w); + /* Indeed, the host's '\n': + '\012' for UNIX; '\015' for MacOS; '\025' for OS/390 + -- whatever the script generates. + */ + if (p > 0 && w[p - 1] == '\n') { + if (p > 1 && w[p - 2] == CR) { + w[p - 2] = '\0'; + } + else { + w[p - 1] = '\0'; + } + } + + /* + * If we've finished reading the headers, check to make sure any + * HTTP/1.1 conditions are met. If so, we're done; normal processing + * will handle the script's output. If not, just return the error. + * The appropriate thing to do would be to send the script process a + * SIGPIPE to let it know we're ignoring it, close the channel to the + * script process, and *then* return the failed-to-meet-condition + * error. Otherwise we'd be waiting for the script to finish + * blithering before telling the client the output was no good. + * However, we don't have the information to do that, so we have to + * leave it to an upper layer. + */ + if (w[0] == '\0') { + int cond_status = OK; + + /* PR#38070: This fails because it gets confused when a + * CGI Status header overrides ap_meets_conditions. + * + * We can fix that by dropping ap_meets_conditions when + * Status has been set. Since this is the only place + * cgi_status gets used, let's test it explicitly. + * + * The alternative would be to ignore CGI Status when + * ap_meets_conditions returns anything interesting. + * That would be safer wrt HTTP, but would break CGI. + */ + if ((cgi_status == HTTP_UNSET) && (r->method_number == M_GET)) { + cond_status = ap_meets_conditions(r); + } + apr_table_overlap(r->err_headers_out, merge, + APR_OVERLAP_TABLES_MERGE); + if (!apr_is_empty_table(cookie_table)) { + /* the cookies have already been copied to the cookie_table */ + apr_table_unset(r->err_headers_out, "Set-Cookie"); + r->err_headers_out = apr_table_overlay(r->pool, + r->err_headers_out, cookie_table); + } + return cond_status; + } + + if (trace_log) { + if (first_header) + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE4, 0, r, + "Headers from script '%s':", + apr_filepath_name_get(r->filename)); + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE4, 0, r, " %s", w); + } + + /* if we see a bogus header don't ignore it. Shout and scream */ + +#if APR_CHARSET_EBCDIC + /* Chances are that we received an ASCII header text instead of + * the expected EBCDIC header lines. Try to auto-detect: + */ + if (!(l = strchr(w, ':'))) { + int maybeASCII = 0, maybeEBCDIC = 0; + unsigned char *cp, native; + apr_size_t inbytes_left, outbytes_left; + + for (cp = w; *cp != '\0'; ++cp) { + native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp); + if (apr_isprint(*cp) && !apr_isprint(native)) + ++maybeEBCDIC; + if (!apr_isprint(*cp) && apr_isprint(native)) + ++maybeASCII; + } + if (maybeASCII > maybeEBCDIC) { + ap_log_error(SCRIPT_LOG_MARK, APLOG_ERR, 0, r->server, + APLOGNO(02660) "CGI Interface Error: " + "Script headers apparently ASCII: (CGI = %s)", + r->filename); + inbytes_left = outbytes_left = cp - w; + apr_xlate_conv_buffer(ap_hdrs_from_ascii, + w, &inbytes_left, w, &outbytes_left); + } + } +#endif /*APR_CHARSET_EBCDIC*/ + if (!(l = strchr(w, ':'))) { + if (!buffer) { + /* Soak up all the script output - may save an outright kill */ + while ((*getsfunc)(w, MAX_STRING_LEN - 1, getsfunc_data) > 0) { + continue; + } + } + + /* Intentional no APLOGNO */ + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, + "malformed header from script '%s': Bad header: %.30s", + apr_filepath_name_get(r->filename), w); + return HTTP_INTERNAL_SERVER_ERROR; + } + + *l++ = '\0'; + while (apr_isspace(*l)) { + ++l; + } + + if (!strcasecmp(w, "Content-type")) { + char *tmp; + + /* Nuke trailing whitespace */ + + char *endp = l + strlen(l) - 1; + while (endp > l && apr_isspace(*endp)) { + *endp-- = '\0'; + } + + tmp = apr_pstrdup(r->pool, l); + ap_content_type_tolower(tmp); + ap_set_content_type(r, tmp); + } + /* + * If the script returned a specific status, that's what + * we'll use - otherwise we assume 200 OK. + */ + else if (!strcasecmp(w, "Status")) { + r->status = cgi_status = atoi(l); + if (!ap_is_HTTP_VALID_RESPONSE(cgi_status)) + /* Intentional no APLOGNO */ + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, + "Invalid status line from script '%s': %.30s", + apr_filepath_name_get(r->filename), l); + else + if (APLOGrtrace1(r)) + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE1, 0, r, + "Status line from script '%s': %.30s", + apr_filepath_name_get(r->filename), l); + r->status_line = apr_pstrdup(r->pool, l); + } + else if (!strcasecmp(w, "Location")) { + apr_table_set(r->headers_out, w, l); + } + else if (!strcasecmp(w, "Content-Length")) { + apr_table_set(r->headers_out, w, l); + } + else if (!strcasecmp(w, "Content-Range")) { + apr_table_set(r->headers_out, w, l); + } + else if (!strcasecmp(w, "Transfer-Encoding")) { + apr_table_set(r->headers_out, w, l); + } + else if (!strcasecmp(w, "ETag")) { + apr_table_set(r->headers_out, w, l); + } + /* + * If the script gave us a Last-Modified header, we can't just + * pass it on blindly because of restrictions on future values. + */ + else if (!strcasecmp(w, "Last-Modified")) { + ap_update_mtime(r, apr_date_parse_http(l)); + ap_set_last_modified(r); + } + else if (!strcasecmp(w, "Set-Cookie")) { + apr_table_add(cookie_table, w, l); + } + else { + apr_table_add(merge, w, l); + } + first_header = 0; + } + /* never reached - we leave this function within the while loop above */ + return OK; +} + +AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, + int (*getsfunc) (char *, int, void *), + void *getsfunc_data) +{ + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc, + getsfunc_data, + APLOG_MODULE_INDEX); +} + +static int getsfunc_FILE(char *buf, int len, void *f) +{ + return apr_file_gets(buf, len, (apr_file_t *) f) == APR_SUCCESS; +} + +AP_DECLARE(int) ap_scan_script_header_err(request_rec *r, apr_file_t *f, + char *buffer) +{ + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_FILE, f, + APLOG_MODULE_INDEX); +} + +AP_DECLARE(int) ap_scan_script_header_err_ex(request_rec *r, apr_file_t *f, + char *buffer, int module_index) +{ + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_FILE, f, + module_index); +} + + +static int getsfunc_BRIGADE(char *buf, int len, void *arg) +{ + apr_bucket_brigade *bb = (apr_bucket_brigade *)arg; + const char *dst_end = buf + len - 1; /* leave room for terminating null */ + char *dst = buf; + apr_bucket *e = APR_BRIGADE_FIRST(bb); + apr_status_t rv; + int done = 0; + + while ((dst < dst_end) && !done && e != APR_BRIGADE_SENTINEL(bb) + && !APR_BUCKET_IS_EOS(e)) { + const char *bucket_data; + apr_size_t bucket_data_len; + const char *src; + const char *src_end; + apr_bucket * next; + + rv = apr_bucket_read(e, &bucket_data, &bucket_data_len, + APR_BLOCK_READ); + if (rv != APR_SUCCESS || (bucket_data_len == 0)) { + *dst = '\0'; + return APR_STATUS_IS_TIMEUP(rv) ? -1 : 0; + } + src = bucket_data; + src_end = bucket_data + bucket_data_len; + while ((src < src_end) && (dst < dst_end) && !done) { + if (*src == '\n') { + done = 1; + } + else if (*src != '\r') { + *dst++ = *src; + } + src++; + } + + if (src < src_end) { + apr_bucket_split(e, src - bucket_data); + } + next = APR_BUCKET_NEXT(e); + apr_bucket_delete(e); + e = next; + } + *dst = 0; + return done; +} + +AP_DECLARE(int) ap_scan_script_header_err_brigade(request_rec *r, + apr_bucket_brigade *bb, + char *buffer) +{ + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_BRIGADE, bb, + APLOG_MODULE_INDEX); +} + +AP_DECLARE(int) ap_scan_script_header_err_brigade_ex(request_rec *r, + apr_bucket_brigade *bb, + char *buffer, + int module_index) +{ + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_BRIGADE, bb, + module_index); +} + + +struct vastrs { + va_list args; + int arg; + const char *curpos; +}; + +static int getsfunc_STRING(char *w, int len, void *pvastrs) +{ + struct vastrs *strs = (struct vastrs*) pvastrs; + const char *p; + int t; + + if (!strs->curpos || !*strs->curpos) { + w[0] = '\0'; + return 0; + } + p = ap_strchr_c(strs->curpos, '\n'); + if (p) + ++p; + else + p = ap_strchr_c(strs->curpos, '\0'); + t = p - strs->curpos; + if (t > len) + t = len; + strncpy (w, strs->curpos, t); + w[t] = '\0'; + if (!strs->curpos[t]) { + ++strs->arg; + strs->curpos = va_arg(strs->args, const char *); + } + else + strs->curpos += t; + return t; +} + +/* ap_scan_script_header_err_strs() accepts additional const char* args... + * each is treated as one or more header lines, and the first non-header + * character is returned to **arg, **data. (The first optional arg is + * counted as 0.) + */ +AP_DECLARE_NONSTD(int) ap_scan_script_header_err_strs_ex(request_rec *r, + char *buffer, + int module_index, + const char **termch, + int *termarg, ...) +{ + struct vastrs strs; + int res; + + va_start(strs.args, termarg); + strs.arg = 0; + strs.curpos = va_arg(strs.args, char*); + res = ap_scan_script_header_err_core_ex(r, buffer, getsfunc_STRING, + (void *) &strs, module_index); + if (termch) + *termch = strs.curpos; + if (termarg) + *termarg = strs.arg; + va_end(strs.args); + return res; +} + +AP_DECLARE_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r, + char *buffer, + const char **termch, + int *termarg, ...) +{ + struct vastrs strs; + int res; + + va_start(strs.args, termarg); + strs.arg = 0; + strs.curpos = va_arg(strs.args, char*); + res = ap_scan_script_header_err_core_ex(r, buffer, getsfunc_STRING, + (void *) &strs, APLOG_MODULE_INDEX); + if (termch) + *termch = strs.curpos; + if (termarg) + *termarg = strs.arg; + va_end(strs.args); + return res; +} + +static void +argstr_to_table(char *str, apr_table_t *parms) +{ + char *key; + char *value; + char *strtok_state; + + if (str == NULL) { + return; + } + + key = apr_strtok(str, "&", &strtok_state); + while (key) { + value = strchr(key, '='); + if (value) { + *value = '\0'; /* Split the string in two */ + value++; /* Skip passed the = */ + } + else { + value = "1"; + } + ap_unescape_url(key); + ap_unescape_url(value); + apr_table_set(parms, key, value); + key = apr_strtok(NULL, "&", &strtok_state); + } +} + +AP_DECLARE(void) ap_args_to_table(request_rec *r, apr_table_t **table) +{ + apr_table_t *t = apr_table_make(r->pool, 10); + argstr_to_table(apr_pstrdup(r->pool, r->args), t); + *table = t; +} diff --git a/server/util_time.c b/server/util_time.c new file mode 100644 index 0000000..3632d0d --- /dev/null +++ b/server/util_time.c @@ -0,0 +1,306 @@ +/* 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 "util_time.h" + + +/* Number of characters needed to format the microsecond part of a timestamp. + * Microseconds have 6 digits plus one separator character makes 7. + * */ +#define AP_CTIME_USEC_LENGTH 7 + +/* Length of ISO 8601 date/time */ +#define AP_CTIME_COMPACT_LEN 20 + + +/* Cache for exploded values of recent timestamps + */ + +struct exploded_time_cache_element { + apr_int64_t t; + apr_time_exp_t xt; + apr_int64_t t_validate; /* please see comments in cached_explode() */ +}; + +/* the "+ 1" is for the current second: */ +#define TIME_CACHE_SIZE (AP_TIME_RECENT_THRESHOLD + 1) + +/* Note that AP_TIME_RECENT_THRESHOLD is defined to + * be a power of two minus one in util_time.h, so that + * we can replace a modulo operation with a bitwise AND + * when hashing items into a cache of size + * AP_TIME_RECENT_THRESHOLD+1 + */ +#define TIME_CACHE_MASK (AP_TIME_RECENT_THRESHOLD) + +static struct exploded_time_cache_element exploded_cache_localtime[TIME_CACHE_SIZE]; +static struct exploded_time_cache_element exploded_cache_gmt[TIME_CACHE_SIZE]; + + +static apr_status_t cached_explode(apr_time_exp_t *xt, apr_time_t t, + struct exploded_time_cache_element *cache, + int use_gmt) +{ + apr_int64_t seconds = apr_time_sec(t); + struct exploded_time_cache_element *cache_element = + &(cache[seconds & TIME_CACHE_MASK]); + struct exploded_time_cache_element cache_element_snapshot; + + /* The cache is implemented as a ring buffer. Each second, + * it uses a different element in the buffer. The timestamp + * in the element indicates whether the element contains the + * exploded time for the current second (vs the time + * 'now - AP_TIME_RECENT_THRESHOLD' seconds ago). If the + * cached value is for the current time, we use it. Otherwise, + * we compute the apr_time_exp_t and store it in this + * cache element. Note that the timestamp in the cache + * element is updated only after the exploded time. Thus + * if two threads hit this cache element simultaneously + * at the start of a new second, they'll both explode the + * time and store it. I.e., the writers will collide, but + * they'll be writing the same value. + */ + if (cache_element->t >= seconds) { + /* There is an intentional race condition in this design: + * in a multithreaded app, one thread might be reading + * from this cache_element to resolve a timestamp from + * TIME_CACHE_SIZE seconds ago at the same time that + * another thread is copying the exploded form of the + * current time into the same cache_element. (I.e., the + * first thread might hit this element of the ring buffer + * just as the element is being recycled.) This can + * also happen at the start of a new second, if a + * reader accesses the cache_element after a writer + * has updated cache_element.t but before the writer + * has finished updating the whole cache_element. + * + * Rather than trying to prevent this race condition + * with locks, we allow it to happen and then detect + * and correct it. The detection works like this: + * Step 1: Take a "snapshot" of the cache element by + * copying it into a temporary buffer. + * Step 2: Check whether the snapshot contains consistent + * data: the timestamps at the start and end of + * the cache_element should both match the 'seconds' + * value that we computed from the input time. + * If these three don't match, then the snapshot + * shows the cache_element in the middle of an + * update, and its contents are invalid. + * Step 3: If the snapshot is valid, use it. Otherwise, + * just give up on the cache and explode the + * input time. + */ + memcpy(&cache_element_snapshot, cache_element, + sizeof(struct exploded_time_cache_element)); + if ((seconds != cache_element_snapshot.t) || + (seconds != cache_element_snapshot.t_validate)) { + /* Invalid snapshot */ + if (use_gmt) { + return apr_time_exp_gmt(xt, t); + } + else { + return apr_time_exp_lt(xt, t); + } + } + else { + /* Valid snapshot */ + memcpy(xt, &(cache_element_snapshot.xt), + sizeof(apr_time_exp_t)); + } + } + else { + apr_status_t r; + if (use_gmt) { + r = apr_time_exp_gmt(xt, t); + } + else { + r = apr_time_exp_lt(xt, t); + } + if (r != APR_SUCCESS) { + return r; + } + cache_element->t = seconds; + memcpy(&(cache_element->xt), xt, sizeof(apr_time_exp_t)); + cache_element->t_validate = seconds; + } + xt->tm_usec = (int)apr_time_usec(t); + return APR_SUCCESS; +} + + +AP_DECLARE(apr_status_t) ap_explode_recent_localtime(apr_time_exp_t * tm, + apr_time_t t) +{ + return cached_explode(tm, t, exploded_cache_localtime, 0); +} + +AP_DECLARE(apr_status_t) ap_explode_recent_gmt(apr_time_exp_t * tm, + apr_time_t t) +{ + return cached_explode(tm, t, exploded_cache_gmt, 1); +} + +AP_DECLARE(apr_status_t) ap_recent_ctime(char *date_str, apr_time_t t) +{ + int len = APR_CTIME_LEN; + return ap_recent_ctime_ex(date_str, t, AP_CTIME_OPTION_NONE, &len); +} + +AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t, + int option, int *len) +{ + /* ### This code is a clone of apr_ctime(), except that it + * uses ap_explode_recent_localtime() instead of apr_time_exp_lt(). + */ + apr_time_exp_t xt; + const char *s; + int real_year; + int needed; + + + /* Calculate the needed buffer length */ + if (option & AP_CTIME_OPTION_COMPACT) + needed = AP_CTIME_COMPACT_LEN; + else + needed = APR_CTIME_LEN; + + if (option & AP_CTIME_OPTION_USEC) { + needed += AP_CTIME_USEC_LENGTH; + } + + /* Check the provided buffer length */ + if (len && *len >= needed) { + *len = needed; + } + else { + if (len != NULL) { + *len = 0; + } + return APR_ENOMEM; + } + + /* example without options: "Wed Jun 30 21:49:08 1993" */ + /* 123456789012345678901234 */ + /* example for compact format: "1993-06-30 21:49:08" */ + /* 1234567890123456789 */ + + ap_explode_recent_localtime(&xt, t); + real_year = 1900 + xt.tm_year; + if (option & AP_CTIME_OPTION_COMPACT) { + int real_month = xt.tm_mon + 1; + *date_str++ = real_year / 1000 + '0'; + *date_str++ = real_year % 1000 / 100 + '0'; + *date_str++ = real_year % 100 / 10 + '0'; + *date_str++ = real_year % 10 + '0'; + *date_str++ = '-'; + *date_str++ = real_month / 10 + '0'; + *date_str++ = real_month % 10 + '0'; + *date_str++ = '-'; + } + else { + s = &apr_day_snames[xt.tm_wday][0]; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = ' '; + s = &apr_month_snames[xt.tm_mon][0]; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = ' '; + } + *date_str++ = xt.tm_mday / 10 + '0'; + *date_str++ = xt.tm_mday % 10 + '0'; + *date_str++ = ' '; + *date_str++ = xt.tm_hour / 10 + '0'; + *date_str++ = xt.tm_hour % 10 + '0'; + *date_str++ = ':'; + *date_str++ = xt.tm_min / 10 + '0'; + *date_str++ = xt.tm_min % 10 + '0'; + *date_str++ = ':'; + *date_str++ = xt.tm_sec / 10 + '0'; + *date_str++ = xt.tm_sec % 10 + '0'; + if (option & AP_CTIME_OPTION_USEC) { + int div; + int usec = (int)xt.tm_usec; + *date_str++ = '.'; + for (div=100000; div>0; div=div/10) { + *date_str++ = usec / div + '0'; + usec = usec % div; + } + } + if (!(option & AP_CTIME_OPTION_COMPACT)) { + *date_str++ = ' '; + *date_str++ = real_year / 1000 + '0'; + *date_str++ = real_year % 1000 / 100 + '0'; + *date_str++ = real_year % 100 / 10 + '0'; + *date_str++ = real_year % 10 + '0'; + } + *date_str++ = 0; + + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_recent_rfc822_date(char *date_str, apr_time_t t) +{ + /* ### This code is a clone of apr_rfc822_date(), except that it + * uses ap_explode_recent_gmt() instead of apr_time_exp_gmt(). + */ + apr_time_exp_t xt; + const char *s; + int real_year; + + ap_explode_recent_gmt(&xt, t); + + /* example: "Sat, 08 Jan 2000 18:31:41 GMT" */ + /* 12345678901234567890123456789 */ + + s = &apr_day_snames[xt.tm_wday][0]; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = ','; + *date_str++ = ' '; + *date_str++ = xt.tm_mday / 10 + '0'; + *date_str++ = xt.tm_mday % 10 + '0'; + *date_str++ = ' '; + s = &apr_month_snames[xt.tm_mon][0]; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = ' '; + real_year = 1900 + xt.tm_year; + /* This routine isn't y10k ready. */ + *date_str++ = real_year / 1000 + '0'; + *date_str++ = real_year % 1000 / 100 + '0'; + *date_str++ = real_year % 100 / 10 + '0'; + *date_str++ = real_year % 10 + '0'; + *date_str++ = ' '; + *date_str++ = xt.tm_hour / 10 + '0'; + *date_str++ = xt.tm_hour % 10 + '0'; + *date_str++ = ':'; + *date_str++ = xt.tm_min / 10 + '0'; + *date_str++ = xt.tm_min % 10 + '0'; + *date_str++ = ':'; + *date_str++ = xt.tm_sec / 10 + '0'; + *date_str++ = xt.tm_sec % 10 + '0'; + *date_str++ = ' '; + *date_str++ = 'G'; + *date_str++ = 'M'; + *date_str++ = 'T'; + *date_str++ = 0; + return APR_SUCCESS; +} diff --git a/server/util_xml.c b/server/util_xml.c new file mode 100644 index 0000000..4845194 --- /dev/null +++ b/server/util_xml.c @@ -0,0 +1,140 @@ +/* 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 "apr_xml.h" + +#include "httpd.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_core.h" + +#include "util_charset.h" +#include "util_xml.h" + + +/* used for reading input blocks */ +#define READ_BLOCKSIZE 2048 + + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +AP_DECLARE(int) ap_xml_parse_input(request_rec * r, apr_xml_doc **pdoc) +{ + apr_xml_parser *parser; + apr_bucket_brigade *brigade; + int seen_eos; + apr_status_t status; + char errbuf[200]; + apr_size_t total_read = 0; + apr_size_t limit_xml_body = ap_get_limit_xml_body(r); + int result = HTTP_BAD_REQUEST; + + parser = apr_xml_parser_create(r->pool); + brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + seen_eos = 0; + total_read = 0; + + do { + apr_bucket *bucket; + + /* read the body, stuffing it into the parser */ + status = ap_get_brigade(r->input_filters, brigade, + AP_MODE_READBYTES, APR_BLOCK_READ, + READ_BLOCKSIZE); + + if (status != APR_SUCCESS) { + result = ap_map_http_request_error(status, HTTP_BAD_REQUEST); + goto read_error; + } + + for (bucket = APR_BRIGADE_FIRST(brigade); + bucket != APR_BRIGADE_SENTINEL(brigade); + bucket = APR_BUCKET_NEXT(bucket)) + { + const char *data; + apr_size_t len; + + if (APR_BUCKET_IS_EOS(bucket)) { + seen_eos = 1; + break; + } + + if (APR_BUCKET_IS_METADATA(bucket)) { + continue; + } + + status = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); + if (status != APR_SUCCESS) { + goto read_error; + } + + total_read += len; + if (limit_xml_body && total_read > limit_xml_body) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00539) + "XML request body is larger than the configured " + "limit of %lu", (unsigned long)limit_xml_body); + result = HTTP_REQUEST_ENTITY_TOO_LARGE; + goto read_error; + } + + status = apr_xml_parser_feed(parser, data, len); + if (status) { + goto parser_error; + } + } + + apr_brigade_cleanup(brigade); + } while (!seen_eos); + + apr_brigade_destroy(brigade); + + /* tell the parser that we're done */ + status = apr_xml_parser_done(parser, pdoc); + if (status) { + /* Some parsers are stupid and return an error on blank documents. */ + if (!total_read) { + *pdoc = NULL; + return OK; + } + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00540) + "XML parser error (at end). status=%d", status); + return HTTP_BAD_REQUEST; + } + +#if APR_CHARSET_EBCDIC + apr_xml_parser_convert_doc(r->pool, *pdoc, ap_hdrs_from_ascii); +#endif + return OK; + + parser_error: + (void) apr_xml_parser_geterror(parser, errbuf, sizeof(errbuf)); + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00541) + "XML Parser Error: %s", errbuf); + + /* FALLTHRU */ + + read_error: + /* make sure the parser is terminated */ + (void) apr_xml_parser_done(parser, NULL); + + apr_brigade_destroy(brigade); + + /* Apache will supply a default error, plus the error log above. */ + return result; +} diff --git a/server/vhost.c b/server/vhost.c new file mode 100644 index 0000000..b23b2dd --- /dev/null +++ b/server/vhost.c @@ -0,0 +1,1267 @@ +/* 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. + */ + +/** + * @file vhost.c + * @brief functions pertaining to virtual host addresses + * (configuration and run-time) + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_lib.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_vhost.h" +#include "http_protocol.h" +#include "http_core.h" + +#if APR_HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +/* + * After all the definitions there's an explanation of how it's all put + * together. + */ + +/* meta-list of name-vhosts. Each server_rec can be in possibly multiple + * lists of name-vhosts. + */ +typedef struct name_chain name_chain; +struct name_chain { + name_chain *next; + server_addr_rec *sar; /* the record causing it to be in + * this chain (needed for port comparisons) */ + server_rec *server; /* the server to use on a match */ +}; + +/* meta-list of ip addresses. Each server_rec can be in possibly multiple + * hash chains since it can have multiple ips. + */ +typedef struct ipaddr_chain ipaddr_chain; +struct ipaddr_chain { + ipaddr_chain *next; + server_addr_rec *sar; /* the record causing it to be in + * this chain (need for both ip addr and port + * comparisons) */ + server_rec *server; /* the server to use if this matches */ + name_chain *names; /* if non-NULL then a list of name-vhosts + * sharing this address */ + name_chain *initialnames; /* no runtime use, temporary storage of first + * NVH'es names */ +}; + +/* This defines the size of the hash table used for hashing ip addresses + * of virtual hosts. It must be a power of two. + */ +#ifndef IPHASH_TABLE_SIZE +#define IPHASH_TABLE_SIZE 256 +#endif + +/* A (n) bucket hash table, each entry has a pointer to a server rec and + * a pointer to the other entries in that bucket. Each individual address, + * even for virtualhosts with multiple addresses, has an entry in this hash + * table. There are extra buckets for _default_, and name-vhost entries. + * + * Note that after config time this is constant, so it is thread-safe. + */ +static ipaddr_chain *iphash_table[IPHASH_TABLE_SIZE]; + +/* dump out statistics about the hash function */ +/* #define IPHASH_STATISTICS */ + +/* list of the _default_ servers */ +static ipaddr_chain *default_list; + +/* whether a config error was seen */ +static int config_error = 0; + +/* config check function */ +static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s); + +/* + * How it's used: + * + * The ip address determines which chain in iphash_table is interesting, then + * a comparison is done down that chain to find the first ipaddr_chain whose + * sar matches the address:port pair. + * + * If that ipaddr_chain has names == NULL then you're done, it's an ip-vhost. + * + * Otherwise it's a name-vhost list, and the default is the server in the + * ipaddr_chain record. We tuck away the ipaddr_chain record in the + * conn_rec field vhost_lookup_data. Later on after the headers we get a + * second chance, and we use the name_chain to figure out what name-vhost + * matches the headers. + * + * If there was no ip address match in the iphash_table then do a lookup + * in the default_list. + * + * How it's put together ... well you should be able to figure that out + * from how it's used. Or something like that. + */ + + +/* called at the beginning of the config */ +AP_DECLARE(void) ap_init_vhost_config(apr_pool_t *p) +{ + memset(iphash_table, 0, sizeof(iphash_table)); + default_list = NULL; + ap_hook_check_config(vhost_check_config, NULL, NULL, APR_HOOK_MIDDLE); +} + + +/* + * Parses a host of the form <address>[:port] + * paddr is used to create a list in the order of input + * **paddr is the ->next pointer of the last entry (or s->addrs) + * *paddr is the variable used to keep track of **paddr between calls + * port is the default port to assume + */ +static const char *get_addresses(apr_pool_t *p, const char *w_, + server_addr_rec ***paddr, + apr_port_t default_port) +{ + apr_sockaddr_t *my_addr; + server_addr_rec *sar; + char *w, *host, *scope_id; + int wild_port; + apr_size_t wlen; + apr_port_t port; + apr_status_t rv; + + if (*w_ == '\0') + return NULL; + + wlen = strlen(w_); /* wlen must be > 0 at this point */ + w = apr_pstrmemdup(p, w_, wlen); + /* apr_parse_addr_port() doesn't understand ":*" so handle that first. */ + wild_port = 0; + if (w[wlen - 1] == '*') { + if (wlen < 2) { + wild_port = 1; + } + else if (w[wlen - 2] == ':') { + w[wlen - 2] = '\0'; + wild_port = 1; + } + } + rv = apr_parse_addr_port(&host, &scope_id, &port, w, p); + /* If the string is "80", apr_parse_addr_port() will be happy and set + * host to NULL and port to 80, so watch out for that. + */ + if (rv != APR_SUCCESS) { + return "The address or port is invalid"; + } + if (!host) { + return "Missing address for VirtualHost"; + } + if (scope_id) { + return "Scope ids are not supported"; + } + if (!port && !wild_port) { + port = default_port; + } + + if (strcmp(host, "*") == 0 || strcasecmp(host, "_default_") == 0) { + rv = apr_sockaddr_info_get(&my_addr, NULL, APR_UNSPEC, port, 0, p); + if (rv) { + return "Could not determine a wildcard address ('0.0.0.0') -- " + "check resolver configuration."; + } + } + else { + rv = apr_sockaddr_info_get(&my_addr, host, APR_UNSPEC, port, 0, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00547) + "Could not resolve host name %s -- ignoring!", host); + return NULL; + } + } + + /* Remember all addresses for the host */ + + do { + sar = apr_pcalloc(p, sizeof(server_addr_rec)); + **paddr = sar; + *paddr = &sar->next; + sar->host_addr = my_addr; + sar->host_port = port; + sar->virthost = host; + my_addr = my_addr->next; + } while (my_addr); + + return NULL; +} + + +/* parse the <VirtualHost> addresses */ +const char *ap_parse_vhost_addrs(apr_pool_t *p, + const char *hostname, + server_rec *s) +{ + server_addr_rec **addrs; + const char *err; + + /* start the list of addresses */ + addrs = &s->addrs; + while (hostname[0]) { + err = get_addresses(p, ap_getword_conf(p, &hostname), &addrs, s->port); + if (err) { + *addrs = NULL; + return err; + } + } + /* terminate the list */ + *addrs = NULL; + if (s->addrs) { + if (s->addrs->host_port) { + /* override the default port which is inherited from main_server */ + s->port = s->addrs->host_port; + } + } + return NULL; +} + + +AP_DECLARE_NONSTD(const char *)ap_set_name_virtual_host(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + static int warnonce = 0; + if (++warnonce == 1) { + ap_log_error(APLOG_MARK, APLOG_NOTICE|APLOG_STARTUP, APR_SUCCESS, NULL, APLOGNO(00548) + "NameVirtualHost has no effect and will be removed in the " + "next release %s:%d", + cmd->directive->filename, + cmd->directive->line_num); + } + + return NULL; +} + + +/* hash table statistics, keep this in here for the beta period so + * we can find out if the hash function is ok + */ +#ifdef IPHASH_STATISTICS +static int iphash_compare(const void *a, const void *b) +{ + return (*(const int *) b - *(const int *) a); +} + + +static void dump_iphash_statistics(server_rec *main_s) +{ + unsigned count[IPHASH_TABLE_SIZE]; + int i; + ipaddr_chain *src; + unsigned total; + char buf[HUGE_STRING_LEN]; + char *p; + + total = 0; + for (i = 0; i < IPHASH_TABLE_SIZE; ++i) { + count[i] = 0; + for (src = iphash_table[i]; src; src = src->next) { + ++count[i]; + if (i < IPHASH_TABLE_SIZE) { + /* don't count the slop buckets in the total */ + ++total; + } + } + } + qsort(count, IPHASH_TABLE_SIZE, sizeof(count[0]), iphash_compare); + p = buf + apr_snprintf(buf, sizeof(buf), + APLOGNO(03235) "iphash: total hashed = %u, avg chain = %u, " + "chain lengths (count x len):", + total, total / IPHASH_TABLE_SIZE); + total = 1; + for (i = 1; i < IPHASH_TABLE_SIZE; ++i) { + if (count[i - 1] != count[i]) { + p += apr_snprintf(p, sizeof(buf) - (p - buf), " %ux%u", + total, count[i - 1]); + total = 1; + } + else { + ++total; + } + } + p += apr_snprintf(p, sizeof(buf) - (p - buf), " %ux%u", + total, count[IPHASH_TABLE_SIZE - 1]); + /* Intentional no APLOGNO */ + /* buf provides APLOGNO */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, main_s, buf); +} +#endif + + +/* This hashing function is designed to get good distribution in the cases + * where the server is handling entire "networks" of servers. i.e. a + * whack of /24s. This is probably the most common configuration for + * ISPs with large virtual servers. + * + * NOTE: This function is symmetric (i.e. collapses all 4 octets + * into one), so machine byte order (big/little endianness) does not matter. + * + * Hash function provided by David Hankins. + */ +static APR_INLINE unsigned hash_inaddr(unsigned key) +{ + key ^= (key >> 16); + return ((key >> 8) ^ key) % IPHASH_TABLE_SIZE; +} + +static APR_INLINE unsigned hash_addr(struct apr_sockaddr_t *sa) +{ + unsigned key; + + /* The key is the last four bytes of the IP address. + * For IPv4, this is the entire address, as always. + * For IPv6, this is usually part of the MAC address. + */ + key = *(unsigned *)((char *)sa->ipaddr_ptr + sa->ipaddr_len - 4); + return hash_inaddr(key); +} + +static ipaddr_chain *new_ipaddr_chain(apr_pool_t *p, + server_rec *s, server_addr_rec *sar) +{ + ipaddr_chain *new; + + new = apr_palloc(p, sizeof(*new)); + new->names = NULL; + new->initialnames = NULL; + new->server = s; + new->sar = sar; + new->next = NULL; + return new; +} + + +static name_chain *new_name_chain(apr_pool_t *p, + server_rec *s, server_addr_rec *sar) +{ + name_chain *new; + + new = apr_palloc(p, sizeof(*new)); + new->server = s; + new->sar = sar; + new->next = NULL; + return new; +} + + +static APR_INLINE ipaddr_chain *find_ipaddr(apr_sockaddr_t *sa) +{ + unsigned bucket; + ipaddr_chain *trav = NULL; + ipaddr_chain *wild_match = NULL; + + /* scan the hash table for an exact match first */ + bucket = hash_addr(sa); + for (trav = iphash_table[bucket]; trav; trav = trav->next) { + server_addr_rec *sar = trav->sar; + apr_sockaddr_t *cur = sar->host_addr; + + if (cur->port == sa->port) { + if (apr_sockaddr_equal(cur, sa)) { + return trav; + } + } + if (wild_match == NULL && (cur->port == 0 || sa->port == 0)) { + if (apr_sockaddr_equal(cur, sa)) { + /* don't break, continue looking for an exact match */ + wild_match = trav; + } + } + } + return wild_match; +} + +static ipaddr_chain *find_default_server(apr_port_t port) +{ + server_addr_rec *sar; + ipaddr_chain *trav = NULL; + ipaddr_chain *wild_match = NULL; + + for (trav = default_list; trav; trav = trav->next) { + sar = trav->sar; + if (sar->host_port == port) { + /* match! */ + return trav; + } + if (wild_match == NULL && sar->host_port == 0) { + /* don't break, continue looking for an exact match */ + wild_match = trav; + } + } + return wild_match; +} + +#if APR_HAVE_IPV6 +#define IS_IN6_ANYADDR(ad) ((ad)->family == APR_INET6 \ + && IN6_IS_ADDR_UNSPECIFIED(&(ad)->sa.sin6.sin6_addr)) +#else +#define IS_IN6_ANYADDR(ad) (0) +#endif + +static void dump_a_vhost(apr_file_t *f, ipaddr_chain *ic) +{ + name_chain *nc; + int len; + char buf[MAX_STRING_LEN]; + apr_sockaddr_t *ha = ic->sar->host_addr; + + if ((ha->family == APR_INET && ha->sa.sin.sin_addr.s_addr == INADDR_ANY) + || IS_IN6_ANYADDR(ha)) { + len = apr_snprintf(buf, sizeof(buf), "*:%u", + ic->sar->host_port); + } + else { + len = apr_snprintf(buf, sizeof(buf), "%pI", ha); + } + if (ic->sar->host_port == 0) { + buf[len-1] = '*'; + } + if (ic->names == NULL) { + apr_file_printf(f, "%-22s %s (%s:%u)\n", buf, + ic->server->server_hostname, + ic->server->defn_name, ic->server->defn_line_number); + return; + } + apr_file_printf(f, "%-22s is a NameVirtualHost\n" + "%8s default server %s (%s:%u)\n", + buf, "", ic->server->server_hostname, + ic->server->defn_name, ic->server->defn_line_number); + for (nc = ic->names; nc; nc = nc->next) { + if (nc->sar->host_port) { + apr_file_printf(f, "%8s port %u ", "", nc->sar->host_port); + } + else { + apr_file_printf(f, "%8s port * ", ""); + } + apr_file_printf(f, "namevhost %s (%s:%u)\n", + nc->server->server_hostname, + nc->server->defn_name, nc->server->defn_line_number); + if (nc->server->names) { + apr_array_header_t *names = nc->server->names; + char **name = (char **)names->elts; + int i; + for (i = 0; i < names->nelts; ++i) { + if (name[i]) { + apr_file_printf(f, "%16s alias %s\n", "", name[i]); + } + } + } + if (nc->server->wild_names) { + apr_array_header_t *names = nc->server->wild_names; + char **name = (char **)names->elts; + int i; + for (i = 0; i < names->nelts; ++i) { + if (name[i]) { + apr_file_printf(f, "%16s wild alias %s\n", "", name[i]); + } + } + } + } +} + +static void dump_vhost_config(apr_file_t *f) +{ + ipaddr_chain *ic; + int i; + + apr_file_printf(f, "VirtualHost configuration:\n"); + + /* non-wildcard servers */ + for (i = 0; i < IPHASH_TABLE_SIZE; ++i) { + for (ic = iphash_table[i]; ic; ic = ic->next) { + dump_a_vhost(f, ic); + } + } + + /* wildcard servers */ + for (ic = default_list; ic; ic = ic->next) { + dump_a_vhost(f, ic); + } +} + + +/* + * When a second or later virtual host maps to the same IP chain, + * add the relevant server names to the chain. Special care is taken + * to avoid adding ic->names until we're sure there are multiple VH'es. + */ +static void add_name_vhost_config(apr_pool_t *p, server_rec *main_s, + server_rec *s, server_addr_rec *sar, + ipaddr_chain *ic) +{ + + name_chain *nc = new_name_chain(p, s, sar); + nc->next = ic->names; + + /* iterating backwards, so each one we see becomes the current default server */ + ic->server = s; + + if (ic->names == NULL) { + if (ic->initialnames == NULL) { + /* first pass, set these names aside in case we see another VH. + * Until then, this looks like an IP-based VH to runtime. + */ + ic->initialnames = nc; + } + else { + /* second pass through this chain -- this really is an NVH, and we + * have two sets of names to link in. + */ + nc->next = ic->initialnames; + ic->names = nc; + ic->initialnames = NULL; + } + } + else { + /* 3rd or more -- just keep stacking the names */ + ic->names = nc; + } +} + +/* compile the tables and such we need to do the run-time vhost lookups */ +AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) +{ + server_addr_rec *sar; + int has_default_vhost_addr; + server_rec *s; + int i; + ipaddr_chain **iphash_table_tail[IPHASH_TABLE_SIZE]; + + /* Main host first */ + s = main_s; + + if (!s->server_hostname) { + s->server_hostname = ap_get_local_host(p); + } + + /* initialize the tails */ + for (i = 0; i < IPHASH_TABLE_SIZE; ++i) { + iphash_table_tail[i] = &iphash_table[i]; + } + + /* The next things to go into the hash table are the virtual hosts + * themselves. They're listed off of main_s->next in the reverse + * order they occurred in the config file, so we insert them at + * the iphash_table_tail but don't advance the tail. + */ + + for (s = main_s->next; s; s = s->next) { + server_addr_rec *sar_prev = NULL; + has_default_vhost_addr = 0; + for (sar = s->addrs; sar; sar = sar->next) { + ipaddr_chain *ic; + char inaddr_any[16] = {0}; /* big enough to handle IPv4 or IPv6 */ + /* XXX: this treats 0.0.0.0 as a "default" server which matches no-exact-match for IPv6 */ + if (!memcmp(sar->host_addr->ipaddr_ptr, inaddr_any, sar->host_addr->ipaddr_len)) { + ic = find_default_server(sar->host_port); + + if (ic && sar->host_port == ic->sar->host_port) { /* we're a match for an existing "default server" */ + if (!sar_prev || memcmp(sar_prev->host_addr->ipaddr_ptr, inaddr_any, sar_prev->host_addr->ipaddr_len) + || sar_prev->host_port != sar->host_port) { + add_name_vhost_config(p, main_s, s, sar, ic); + } + } + else { + /* No default server, or we found a default server but + ** exactly one of us is a wildcard port, which means we want + ** two ip-based vhosts not an NVH with two names + */ + ic = new_ipaddr_chain(p, s, sar); + ic->next = default_list; + default_list = ic; + add_name_vhost_config(p, main_s, s, sar, ic); + } + has_default_vhost_addr = 1; + } + else { + /* see if it matches something we've already got */ + ic = find_ipaddr(sar->host_addr); + + if (!ic || sar->host_port != ic->sar->host_port) { + /* No matching server, or we found a matching server but + ** exactly one of us is a wildcard port, which means we want + ** two ip-based vhosts not an NVH with two names + */ + unsigned bucket = hash_addr(sar->host_addr); + ic = new_ipaddr_chain(p, s, sar); + ic->next = *iphash_table_tail[bucket]; + *iphash_table_tail[bucket] = ic; + } + add_name_vhost_config(p, main_s, s, sar, ic); + } + sar_prev = sar; + } + + /* Ok now we want to set up a server_hostname if the user was + * silly enough to forget one. + * XXX: This is silly we should just crash and burn. + */ + if (!s->server_hostname) { + if (has_default_vhost_addr) { + s->server_hostname = main_s->server_hostname; + } + else if (!s->addrs) { + /* what else can we do? at this point this vhost has + no configured name, probably because they used + DNS in the VirtualHost statement. It's disabled + anyhow by the host matching code. -djg */ + s->server_hostname = + apr_pstrdup(p, "bogus_host_without_forward_dns"); + } + else { + apr_status_t rv; + char *hostname; + + rv = apr_getnameinfo(&hostname, s->addrs->host_addr, 0); + if (rv == APR_SUCCESS) { + s->server_hostname = apr_pstrdup(p, hostname); + } + else { + /* again, what can we do? They didn't specify a + ServerName, and their DNS isn't working. -djg */ + char *ipaddr_str; + + apr_sockaddr_ip_get(&ipaddr_str, s->addrs->host_addr); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, main_s, APLOGNO(00549) + "Failed to resolve server name " + "for %s (check DNS) -- or specify an explicit " + "ServerName", + ipaddr_str); + s->server_hostname = + apr_pstrdup(p, "bogus_host_without_reverse_dns"); + } + } + } + } + +#ifdef IPHASH_STATISTICS + dump_iphash_statistics(main_s); +#endif + if (ap_exists_config_define("DUMP_VHOSTS")) { + apr_file_t *thefile = NULL; + apr_file_open_stdout(&thefile, p); + dump_vhost_config(thefile); + } +} + +static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + return config_error ? !OK : OK; +} + +/***************************************************************************** + * run-time vhost matching functions + */ + +static apr_status_t fix_hostname_v6_literal(request_rec *r, char *host) +{ + char *dst; + int double_colon = 0; + + for (dst = host; *dst; dst++) { + if (apr_isxdigit(*dst)) { + if (apr_isupper(*dst)) { + *dst = apr_tolower(*dst); + } + } + else if (*dst == ':') { + if (*(dst + 1) == ':') { + if (double_colon) + return APR_EINVAL; + double_colon = 1; + } + else if (*(dst + 1) == '.') { + return APR_EINVAL; + } + } + else if (*dst == '.') { + /* For IPv4-mapped IPv6 addresses like ::FFFF:129.144.52.38 */ + if (*(dst + 1) == ':' || *(dst + 1) == '.') + return APR_EINVAL; + } + else { + return APR_EINVAL; + } + } + return APR_SUCCESS; +} + +static apr_status_t fix_hostname_non_v6(request_rec *r, char *host) +{ + char *dst; + + for (dst = host; *dst; dst++) { + if (apr_islower(*dst)) { + /* leave char unchanged */ + } + else if (*dst == '.') { + if (*(dst + 1) == '.') { + return APR_EINVAL; + } + } + else if (apr_isupper(*dst)) { + *dst = apr_tolower(*dst); + } + else if (*dst == '/' || *dst == '\\') { + return APR_EINVAL; + } + } + /* strip trailing gubbins */ + if (dst > host && dst[-1] == '.') { + dst[-1] = '\0'; + } + return APR_SUCCESS; +} + +/* + * If strict mode ever becomes the default, this should be folded into + * fix_hostname_non_v6() + */ +static apr_status_t strict_hostname_check(request_rec *r, char *host) +{ + char *ch; + int is_dotted_decimal = 1, leading_zeroes = 0, dots = 0; + + for (ch = host; *ch; ch++) { + if (apr_isalpha(*ch) || *ch == '-' || *ch == '_') { + is_dotted_decimal = 0; + } + else if (ch[0] == '.') { + dots++; + if (ch[1] == '0' && apr_isdigit(ch[2])) + leading_zeroes = 1; + } + else if (!apr_isdigit(*ch)) { + /* also takes care of multiple Host headers by denying commas */ + goto bad; + } + } + if (is_dotted_decimal) { + if (host[0] == '.' || (host[0] == '0' && apr_isdigit(host[1]))) + leading_zeroes = 1; + if (leading_zeroes || dots != 3) { + /* RFC 3986 7.4 */ + goto bad; + } + } + else { + /* The top-level domain must start with a letter (RFC 1123 2.1) */ + while (ch > host && *ch != '.') + ch--; + if (ch[0] == '.' && ch[1] != '\0' && !apr_isalpha(ch[1])) + goto bad; + } + return APR_SUCCESS; + +bad: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02415) + "[strict] Invalid host name '%s'%s%.6s", + host, *ch ? ", problem near: " : "", ch); + return APR_EINVAL; +} + +/* Lowercase and remove any trailing dot and/or :port from the hostname, + * and check that it is sane. + * + * In most configurations the exact syntax of the hostname isn't + * important so strict sanity checking isn't necessary. However, in + * mass hosting setups (using mod_vhost_alias or mod_rewrite) where + * the hostname is interpolated into the filename, we need to be sure + * that the interpolation doesn't expose parts of the filesystem. + * We don't do strict RFC 952 / RFC 1123 syntax checking in order + * to support iDNS and people who erroneously use underscores. + * Instead we just check for filesystem metacharacters: directory + * separators / and \ and sequences of more than one dot. + */ +static int fix_hostname(request_rec *r, const char *host_header, + unsigned http_conformance) +{ + const char *src; + char *host, *scope_id; + apr_port_t port; + apr_status_t rv; + const char *c; + int is_v6literal = 0; + int strict = (http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + + src = host_header ? host_header : r->hostname; + + /* According to RFC 2616, Host header field CAN be blank */ + if (!*src) { + return is_v6literal; + } + + /* apr_parse_addr_port will interpret a bare integer as a port + * which is incorrect in this context. So treat it separately. + */ + for (c = src; apr_isdigit(*c); ++c); + if (!*c) { + /* pure integer */ + if (strict) { + /* RFC 3986 7.4 */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02416) + "[strict] purely numeric host names not allowed: %s", + src); + goto bad_nolog; + } + r->hostname = src; + return is_v6literal; + } + + if (host_header) { + rv = apr_parse_addr_port(&host, &scope_id, &port, src, r->pool); + if (rv != APR_SUCCESS || scope_id) + goto bad; + if (port) { + /* Don't throw the Host: header's port number away: + save it in parsed_uri -- ap_get_server_port() needs it! */ + /* @@@ XXX there should be a better way to pass the port. + * Like r->hostname, there should be a r->portno + */ + r->parsed_uri.port = port; + r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); + } + if (host_header[0] == '[') + is_v6literal = 1; + } + else { + /* + * Already parsed, surrounding [ ] (if IPv6 literal) and :port have + * already been removed. + */ + host = apr_pstrdup(r->pool, r->hostname); + if (ap_strchr(host, ':') != NULL) + is_v6literal = 1; + } + + if (is_v6literal) { + rv = fix_hostname_v6_literal(r, host); + } + else { + rv = fix_hostname_non_v6(r, host); + if (strict && rv == APR_SUCCESS) + rv = strict_hostname_check(r, host); + } + if (rv != APR_SUCCESS) + goto bad; + + r->hostname = host; + return is_v6literal; + +bad: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00550) + "Client sent malformed Host header: %s", + src); +bad_nolog: + r->status = HTTP_BAD_REQUEST; + return is_v6literal; +} + +/* return 1 if host matches ServerName or ServerAliases */ +static int matches_aliases(server_rec *s, const char *host) +{ + int i; + apr_array_header_t *names; + + /* match ServerName */ + if (!strcasecmp(host, s->server_hostname)) { + return 1; + } + + /* search all the aliases from ServerAlias directive */ + names = s->names; + if (names) { + char **name = (char **) names->elts; + for (i = 0; i < names->nelts; ++i) { + if (!name[i]) continue; + if (!strcasecmp(host, name[i])) + return 1; + } + } + names = s->wild_names; + if (names) { + char **name = (char **) names->elts; + for (i = 0; i < names->nelts; ++i) { + if (!name[i]) continue; + if (!ap_strcasecmp_match(host, name[i])) + return 1; + } + } + return 0; +} + + +/* Suppose a request came in on the same socket as this r, and included + * a header "Host: host:port", would it map to r->server? It's more + * than just that though. When we do the normal matches for each request + * we don't even bother considering Host: etc on non-namevirtualhosts, + * we just call it a match. But here we require the host:port to match + * the ServerName and/or ServerAliases. + */ +AP_DECLARE(int) ap_matches_request_vhost(request_rec *r, const char *host, + apr_port_t port) +{ + server_rec *s; + server_addr_rec *sar; + + s = r->server; + + /* search all the <VirtualHost> values */ + /* XXX: If this is a NameVirtualHost then we may not be doing the Right Thing + * consider: + * + * NameVirtualHost 10.1.1.1 + * <VirtualHost 10.1.1.1> + * ServerName v1 + * </VirtualHost> + * <VirtualHost 10.1.1.1> + * ServerName v2 + * </VirtualHost> + * + * Suppose r->server is v2, and we're asked to match "10.1.1.1". We'll say + * "yup it's v2", when really it isn't... if a request came in for 10.1.1.1 + * it would really go to v1. + */ + for (sar = s->addrs; sar; sar = sar->next) { + if ((sar->host_port == 0 || port == sar->host_port) + && !strcasecmp(host, sar->virthost)) { + return 1; + } + } + + /* the Port has to match now, because the rest don't have ports associated + * with them. */ + if (port != s->port) { + return 0; + } + + return matches_aliases(s, host); +} + + +static void check_hostalias(request_rec *r) +{ + /* + * Even if the request has a Host: header containing a port we ignore + * that port. We always use the physical port of the socket. There + * are a few reasons for this: + * + * - the default of 80 or 443 for SSL is easier to handle this way + * - there is less of a possibility of a security problem + * - it simplifies the data structure + * - the client may have no idea that a proxy somewhere along the way + * translated the request to another ip:port + * - except for the addresses from the VirtualHost line, none of the other + * names we'll match have ports associated with them + */ + const char *host = r->hostname; + apr_port_t port; + server_rec *s; + server_rec *virthost_s; + server_rec *last_s; + name_chain *src; + + virthost_s = NULL; + last_s = NULL; + + port = r->connection->local_addr->port; + + /* Recall that the name_chain is a list of server_addr_recs, some of + * whose ports may not match. Also each server may appear more than + * once in the chain -- specifically, it will appear once for each + * address from its VirtualHost line which matched. We only want to + * do the full ServerName/ServerAlias comparisons once for each + * server, fortunately we know that all the VirtualHost addresses for + * a single server are adjacent to each other. + */ + + for (src = r->connection->vhost_lookup_data; src; src = src->next) { + server_addr_rec *sar; + + /* We only consider addresses on the name_chain which have a matching + * port + */ + sar = src->sar; + if (sar->host_port != 0 && port != sar->host_port) { + continue; + } + + s = src->server; + + /* If we still need to do ServerName and ServerAlias checks for this + * server, do them now. + */ + if (s != last_s) { + /* does it match any ServerName or ServerAlias directive? */ + if (matches_aliases(s, host)) { + goto found; + } + } + last_s = s; + + /* Fallback: does it match the virthost from the sar? */ + if (!strcasecmp(host, sar->virthost)) { + /* only the first match is used */ + if (virthost_s == NULL) { + virthost_s = s; + } + } + } + + /* If ServerName and ServerAlias check failed, we end up here. If it + * matches a VirtualHost, virthost_s is set. Use that as fallback + */ + if (virthost_s) { + s = virthost_s; + goto found; + } + + return; + +found: + /* s is the first matching server, we're done */ + r->server = s; +} + + +static void check_serverpath(request_rec *r) +{ + server_rec *s; + server_rec *last_s; + name_chain *src; + apr_port_t port; + + port = r->connection->local_addr->port; + + /* + * This is in conjunction with the ServerPath code in http_core, so we + * get the right host attached to a non- Host-sending request. + * + * See the comment in check_hostalias about how each vhost can be + * listed multiple times. + */ + + last_s = NULL; + for (src = r->connection->vhost_lookup_data; src; src = src->next) { + /* We only consider addresses on the name_chain which have a matching + * port + */ + if (src->sar->host_port != 0 && port != src->sar->host_port) { + continue; + } + + s = src->server; + if (s == last_s) { + continue; + } + last_s = s; + + if (s->path && !strncmp(r->uri, s->path, s->pathlen) && + (s->path[s->pathlen - 1] == '/' || + r->uri[s->pathlen] == '/' || + r->uri[s->pathlen] == '\0')) { + r->server = s; + return; + } + } +} + +static APR_INLINE const char *construct_host_header(request_rec *r, + int is_v6literal) +{ + struct iovec iov[5]; + apr_size_t nvec = 0; + /* + * We cannot use ap_get_server_name/port here, because we must + * ignore UseCanonicalName/Port. + */ + if (is_v6literal) { + iov[nvec].iov_base = "["; + iov[nvec].iov_len = 1; + nvec++; + } + iov[nvec].iov_base = (void *)r->hostname; + iov[nvec].iov_len = strlen(r->hostname); + nvec++; + if (is_v6literal) { + iov[nvec].iov_base = "]"; + iov[nvec].iov_len = 1; + nvec++; + } + if (r->parsed_uri.port_str) { + iov[nvec].iov_base = ":"; + iov[nvec].iov_len = 1; + nvec++; + iov[nvec].iov_base = r->parsed_uri.port_str; + iov[nvec].iov_len = strlen(r->parsed_uri.port_str); + nvec++; + } + return apr_pstrcatv(r->pool, iov, nvec, NULL); +} + +AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r) +{ + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + const char *host_header = apr_table_get(r->headers_in, "Host"); + int is_v6literal = 0; + int have_hostname_from_url = 0; + + if (r->hostname) { + /* + * If there was a host part in the Request-URI, ignore the 'Host' + * header. + */ + have_hostname_from_url = 1; + is_v6literal = fix_hostname(r, NULL, conf->http_conformance); + } + else if (host_header != NULL) { + is_v6literal = fix_hostname(r, host_header, conf->http_conformance); + } + if (r->status != HTTP_OK) + return; + + if (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE) { + /* + * If we have both hostname from an absoluteURI and a Host header, + * we must ignore the Host header (RFC 2616 5.2). + * To enforce this, we reset the Host header to the value from the + * request line. + */ + if (have_hostname_from_url && host_header != NULL) { + const char *repl = construct_host_header(r, is_v6literal); + apr_table_setn(r->headers_in, "Host", repl); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02417) + "Replacing host header '%s' with host '%s' given " + "in the request uri", host_header, repl); + } + } + + /* check if we tucked away a name_chain */ + if (r->connection->vhost_lookup_data) { + if (r->hostname) + check_hostalias(r); + else + check_serverpath(r); + } +} + +/** + * For every virtual host on this connection, call func_cb. + */ +AP_DECLARE(int) ap_vhost_iterate_given_conn(conn_rec *conn, + ap_vhost_iterate_conn_cb func_cb, + void* baton) +{ + server_rec *s; + server_rec *last_s; + name_chain *src; + apr_port_t port; + int rv = 0; + + if (conn->vhost_lookup_data) { + last_s = NULL; + port = conn->local_addr->port; + + for (src = conn->vhost_lookup_data; src; src = src->next) { + server_addr_rec *sar; + + /* We only consider addresses on the name_chain which have a + * matching port. + */ + sar = src->sar; + if (sar->host_port != 0 && port != sar->host_port) { + continue; + } + + s = src->server; + + if (s == last_s) { + /* we've already done a callback for this vhost. */ + continue; + } + + last_s = s; + + rv = func_cb(baton, conn, s); + + if (rv != 0) { + break; + } + } + } + else { + rv = func_cb(baton, conn, conn->base_server); + } + + return rv; +} + +/* Called for a new connection which has a known local_addr. Note that the + * new connection is assumed to have conn->server == main server. + */ +AP_DECLARE(void) ap_update_vhost_given_ip(conn_rec *conn) +{ + ipaddr_chain *trav; + apr_port_t port; + + /* scan the hash table for an exact match first */ + trav = find_ipaddr(conn->local_addr); + + if (trav) { + /* save the name_chain for later in case this is a name-vhost */ + conn->vhost_lookup_data = trav->names; + conn->base_server = trav->server; + return; + } + + /* maybe there's a default server or wildcard name-based vhost + * matching this port + */ + port = conn->local_addr->port; + + trav = find_default_server(port); + if (trav) { + conn->vhost_lookup_data = trav->names; + conn->base_server = trav->server; + return; + } + + /* otherwise we're stuck with just the main server + * and no name-based vhosts + */ + conn->vhost_lookup_data = NULL; +} |