summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:01:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:01:30 +0000
commit6beeb1b708550be0d4a53b272283e17e5e35fe17 (patch)
tree1ce8673d4aaa948e5554000101f46536a1e4cc29 /server
parentInitial commit. (diff)
downloadapache2-6beeb1b708550be0d4a53b272283e17e5e35fe17.tar.xz
apache2-6beeb1b708550be0d4a53b272283e17e5e35fe17.zip
Adding upstream version 2.4.57.upstream/2.4.57
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--server/.indent.pro54
-rw-r--r--server/Makefile.in105
-rw-r--r--server/NWGNUmakefile261
-rw-r--r--server/buildmark.c29
-rw-r--r--server/config.c2554
-rw-r--r--server/config.m419
-rw-r--r--server/connection.c219
-rw-r--r--server/core.c5675
-rw-r--r--server/core_filters.c868
-rw-r--r--server/eoc_bucket.c55
-rw-r--r--server/eor_bucket.c115
-rw-r--r--server/error_bucket.c77
-rw-r--r--server/gen_test_char.c192
-rw-r--r--server/gen_test_char.dep7
-rw-r--r--server/gen_test_char.dsp94
-rw-r--r--server/gen_test_char.mak234
-rw-r--r--server/listen.c938
-rw-r--r--server/log.c1974
-rw-r--r--server/main.c873
-rw-r--r--server/mpm/MPM.NAMING14
-rw-r--r--server/mpm/Makefile.in4
-rw-r--r--server/mpm/config.m4128
-rw-r--r--server/mpm/config2.m489
-rw-r--r--server/mpm/event/Makefile.in1
-rw-r--r--server/mpm/event/config.m415
-rw-r--r--server/mpm/event/config3.m47
-rw-r--r--server/mpm/event/event.c4078
-rw-r--r--server/mpm/event/mpm_default.h56
-rw-r--r--server/mpm/mpmt_os2/Makefile.in1
-rw-r--r--server/mpm/mpmt_os2/config.m410
-rw-r--r--server/mpm/mpmt_os2/config5.m43
-rw-r--r--server/mpm/mpmt_os2/mpm_default.h57
-rw-r--r--server/mpm/mpmt_os2/mpmt_os2.c614
-rw-r--r--server/mpm/mpmt_os2/mpmt_os2_child.c490
-rw-r--r--server/mpm/netware/mpm_default.h78
-rw-r--r--server/mpm/netware/mpm_netware.c1365
-rw-r--r--server/mpm/prefork/Makefile.in1
-rw-r--r--server/mpm/prefork/config.m47
-rw-r--r--server/mpm/prefork/config3.m41
-rw-r--r--server/mpm/prefork/mpm_default.h51
-rw-r--r--server/mpm/prefork/prefork.c1563
-rw-r--r--server/mpm/winnt/Makefile.in1
-rw-r--r--server/mpm/winnt/child.c1306
-rw-r--r--server/mpm/winnt/config.m410
-rw-r--r--server/mpm/winnt/config3.m42
-rw-r--r--server/mpm/winnt/mpm_default.h60
-rw-r--r--server/mpm/winnt/mpm_winnt.c1783
-rw-r--r--server/mpm/winnt/mpm_winnt.h96
-rw-r--r--server/mpm/winnt/nt_eventlog.c172
-rw-r--r--server/mpm/winnt/service.c1241
-rw-r--r--server/mpm/worker/Makefile.in2
-rw-r--r--server/mpm/worker/config.m411
-rw-r--r--server/mpm/worker/config3.m45
-rw-r--r--server/mpm/worker/mpm_default.h55
-rw-r--r--server/mpm/worker/worker.c2455
-rw-r--r--server/mpm_common.c578
-rw-r--r--server/mpm_fdqueue.c534
-rw-r--r--server/mpm_fdqueue.h110
-rw-r--r--server/mpm_unix.c1108
-rw-r--r--server/protocol.c2605
-rw-r--r--server/provider.c198
-rw-r--r--server/request.c2572
-rw-r--r--server/scoreboard.c713
-rw-r--r--server/ssl.c285
-rw-r--r--server/util.c3789
-rw-r--r--server/util_cfgtree.c46
-rw-r--r--server/util_charset.c28
-rw-r--r--server/util_cookies.c290
-rw-r--r--server/util_debug.c236
-rw-r--r--server/util_ebcdic.c117
-rw-r--r--server/util_expr_eval.c1831
-rw-r--r--server/util_expr_parse.c2130
-rw-r--r--server/util_expr_parse.h104
-rw-r--r--server/util_expr_parse.y217
-rw-r--r--server/util_expr_private.h141
-rw-r--r--server/util_expr_scan.c2669
-rw-r--r--server/util_expr_scan.l400
-rw-r--r--server/util_fcgi.c290
-rw-r--r--server/util_filter.c733
-rw-r--r--server/util_md5.c166
-rw-r--r--server/util_mutex.c561
-rw-r--r--server/util_pcre.c547
-rw-r--r--server/util_regex.c211
-rw-r--r--server/util_script.c942
-rw-r--r--server/util_time.c306
-rw-r--r--server/util_xml.c140
-rw-r--r--server/vhost.c1294
87 files changed, 56066 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..8111877
--- /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 ssl.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..be889db
--- /dev/null
+++ b/server/config.c
@@ -0,0 +1,2554 @@
+/* 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 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' || (ap_cstr_casecmp(w, "on") && ap_cstr_casecmp(w, "off")))
+ return apr_pstrcat(parms->pool, cmd->name, " must be On or Off",
+ NULL);
+
+ return cmd->AP_FLAG(parms, mconfig, ap_cstr_casecmp(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 (!ap_cstr_casecmp(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;
+ const command_rec *cmd;
+ ap_mod_list *ml;
+ char *lname;
+
+ 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);
+
+ lname = apr_pstrdup(temp_pool, cmd_name);
+ ap_str_tolower(lname);
+ ml = apr_hash_get(ap_config_hash, lname, APR_HASH_KEY_STRING);
+
+ if (ml && (cmd = ml->cmd) != 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 (ap_cstr_casecmp(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)
+ && (ap_cstr_casecmp(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,
+ &current, &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, cmd->cmd->name, ": 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 (ap_cstr_casecmp(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;
+}
+
+/**
+ * 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;
+}
+
+typedef struct {
+ server_rec *s;
+ ap_directive_t **conftree;
+} configs;
+
+static const char *process_resource_config_cb(ap_dir_match_t *w, const char *fname)
+{
+ configs *cfgs = w->ctx;
+ return ap_process_resource_config(cfgs->s, fname, cfgs->conftree, w->p, w->ptemp);
+}
+
+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)
+{
+ configs cfgs;
+ ap_dir_match_t w;
+
+ cfgs.s = s;
+ cfgs.conftree = conftree;
+
+ w.prefix = "Include/IncludeOptional: ";
+ w.p = p;
+ w.ptemp = ptemp;
+ w.flags = (optional ? AP_DIR_FLAG_OPTIONAL : AP_DIR_FLAG_NONE) | AP_DIR_FLAG_RECURSIVE;
+ w.cb = process_resource_config_cb;
+ w.ctx = &cfgs;
+ w.depth = 0;
+
+ /* 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 ap_dir_nofnmatch(&w, fname);
+ }
+ 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 ap_dir_fnmatch(&w, rootpath, filepath);
+ }
+}
+
+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 (ap_cstr_casecmp(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..bbc94c4
--- /dev/null
+++ b/server/connection.c
@@ -0,0 +1,219 @@
+/* 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);
+
+ ap_assert(csd != NULL);
+
+ 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
+ return 1;
+#else
+ /* Shut down the socket for write, which will send a FIN
+ * to the peer.
+ */
+ return (c->aborted || apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE));
+#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 (!csd) {
+ /* Be safe with third-party modules that:
+ * ap_set_core_module_config(c->conn_config, NULL)
+ * to no-op ap_lingering_close().
+ */
+ c->aborted = 1;
+ return;
+ }
+
+ if (ap_start_lingering_close(c)) {
+ apr_socket_close(csd);
+ 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)
+{
+ ap_update_vhost_given_ip(c);
+
+ ap_pre_connection(c, csd);
+
+ if (!c->aborted) {
+ ap_run_process_connection(c);
+ }
+}
diff --git a/server/core.c b/server/core.c
new file mode 100644
index 0000000..e1493fd
--- /dev/null
+++ b/server/core.c
@@ -0,0 +1,5675 @@
+/* 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"
+
+#include "apr_version.h"
+#if APR_MAJOR_VERSION < 2
+#include "apu_version.h"
+#endif
+
+#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_ssl.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) 1<<30) /* 1GB */
+
+/* LimitXMLRequestBody handling */
+#define AP_LIMIT_UNSET ((long) -1)
+#define AP_DEFAULT_LIMIT_XML_BODY ((apr_size_t)1000000)
+/* Hard limit for ap_escape_html2() */
+#define AP_MAX_LIMIT_XML_BODY ((apr_size_t)(APR_SIZE_MAX / 6 - 1))
+
+#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
+
+#define AP_FLUSH_MAX_THRESHOLD 65535
+#define AP_FLUSH_MAX_PIPELINED 4
+
+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;
+
+/* Global state allocated out of pconf: variables here MUST be
+ * cleared/reset in reset_config(), a pconf cleanup, to avoid the
+ * variable getting reused after the pool is cleared. */
+static apr_array_header_t *saved_server_config_defines = NULL;
+static apr_table_t *server_config_defined_vars = NULL;
+AP_DECLARE_DATA const char *ap_runtime_dir = 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;
+ }
+
+ if (new->read_buf_size) {
+ conf->read_buf_size = new->read_buf_size;
+ }
+ else {
+ conf->read_buf_size = base->read_buf_size;
+ }
+
+ 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
+
+ conf->flush_max_threshold = AP_FLUSH_MAX_THRESHOLD;
+ conf->flush_max_pipelined = AP_FLUSH_MAX_PIPELINED;
+ }
+ else {
+ conf->flush_max_pipelined = -1;
+ }
+
+ /* 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;
+ conf->merge_slashes = AP_CORE_CONFIG_UNSET;
+
+ conf->strict_host_check= AP_CORE_CONFIG_UNSET;
+
+ 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);
+ AP_CORE_MERGE_FLAG(merge_slashes, conf, base, virt);
+
+
+ conf->flush_max_threshold = (virt->flush_max_threshold)
+ ? virt->flush_max_threshold
+ : base->flush_max_threshold;
+ conf->flush_max_pipelined = (virt->flush_max_pipelined >= 0)
+ ? virt->flush_max_pipelined
+ : base->flush_max_pipelined;
+
+ conf->strict_host_check = (virt->strict_host_check != AP_CORE_CONFIG_UNSET)
+ ? virt->strict_host_check
+ : base->strict_host_check;
+
+ AP_CORE_MERGE_FLAG(strict_host_check, conf, base, virt);
+
+ 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);
+ apr_pool_tag(tmp, "core_reorder_directories");
+ 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 authorization
+ */
+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;
+}
+
+AP_DECLARE(apr_size_t) ap_get_read_buf_size(const request_rec *r)
+{
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+
+ return d->read_buf_size ? d->read_buf_size : AP_IOBUFSIZE;
+}
+
+
+/*****************************************************************
+ *
+ * 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 (ap_cstr_casecmp(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;
+ ap_runtime_dir = 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 = apr_pstrdup(cmd->pool, 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 (!ap_cstr_casecmp(arg, "Off")) {
+ d->add_default_charset = ADD_DEFAULT_CHARSET_OFF;
+ }
+ else if (!ap_cstr_casecmp(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 (ap_cstr_casecmp(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 (!ap_cstr_casecmp(w, "Indexes")) {
+ opt = OPT_INDEXES;
+ }
+ else if (!ap_cstr_casecmp(w, "Includes")) {
+ /* If Includes is permitted, both Includes and
+ * IncludesNOEXEC may be changed. */
+ opt = (OPT_INCLUDES | OPT_INC_WITH_EXEC);
+ }
+ else if (!ap_cstr_casecmp(w, "IncludesNOEXEC")) {
+ opt = OPT_INCLUDES;
+ }
+ else if (!ap_cstr_casecmp(w, "FollowSymLinks")) {
+ opt = OPT_SYM_LINKS;
+ }
+ else if (!ap_cstr_casecmp(w, "SymLinksIfOwnerMatch")) {
+ opt = OPT_SYM_OWNER;
+ }
+ else if (!ap_cstr_casecmp(w, "ExecCGI")) {
+ opt = OPT_EXECCGI;
+ }
+ else if (!ap_cstr_casecmp(w, "MultiViews")) {
+ opt = OPT_MULTI;
+ }
+ else if (!ap_cstr_casecmp(w, "RunScripts")) { /* AI backcompat. Yuck */
+ opt = OPT_MULTI|OPT_EXECCGI;
+ }
+ else if (!ap_cstr_casecmp(w, "None")) {
+ opt = OPT_NONE;
+ }
+ else if (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(w, "Limit")) {
+ d->override |= OR_LIMIT;
+ }
+ else if (!ap_cstr_casecmp(k, "Options")) {
+ d->override |= OR_OPTIONS;
+ if (v)
+ set_allow_opts(cmd, &(d->override_opts), v);
+ else
+ d->override_opts = OPT_ALL;
+ }
+ else if (!ap_cstr_casecmp(w, "FileInfo")) {
+ d->override |= OR_FILEINFO;
+ }
+ else if (!ap_cstr_casecmp(w, "AuthConfig")) {
+ d->override |= OR_AUTHCFG;
+ }
+ else if (!ap_cstr_casecmp(w, "Indexes")) {
+ d->override |= OR_INDEXES;
+ }
+ else if (!ap_cstr_casecmp(w, "Nonfatal")) {
+ if (!v) {
+ return apr_pstrcat(cmd->pool, "=Override, =Unknown or =All expected after ", w, NULL);
+ }
+ else if (!ap_cstr_casecmp(v, "Override")) {
+ d->override |= NONFATAL_OVERRIDE;
+ }
+ else if (!ap_cstr_casecmp(v, "Unknown")) {
+ d->override |= NONFATAL_UNKNOWN;
+ }
+ else if (!ap_cstr_casecmp(v, "All")) {
+ d->override |= NONFATAL_ALL;
+ }
+ }
+ else if (!ap_cstr_casecmp(w, "None")) {
+ d->override = OR_NONE;
+ }
+ else if (!ap_cstr_casecmp(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_core_server_flag(cmd_parms *cmd, void *s_, int flag)
+{
+ core_server_config *conf =
+ ap_get_core_module_config(cmd->server->module_config);
+ return ap_set_flag_slot(cmd, conf, flag);
+}
+
+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 (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(w, "Indexes")) {
+ opt = OPT_INDEXES;
+ }
+ else if (!ap_cstr_casecmp(w, "Includes")) {
+ opt = (OPT_INCLUDES | OPT_INC_WITH_EXEC);
+ }
+ else if (!ap_cstr_casecmp(w, "IncludesNOEXEC")) {
+ opt = OPT_INCLUDES;
+ }
+ else if (!ap_cstr_casecmp(w, "FollowSymLinks")) {
+ opt = OPT_SYM_LINKS;
+ }
+ else if (!ap_cstr_casecmp(w, "SymLinksIfOwnerMatch")) {
+ opt = OPT_SYM_OWNER;
+ }
+ else if (!ap_cstr_casecmp(w, "ExecCGI")) {
+ opt = OPT_EXECCGI;
+ }
+ else if (!ap_cstr_casecmp(w, "MultiViews")) {
+ opt = OPT_MULTI;
+ }
+ else if (!ap_cstr_casecmp(w, "RunScripts")) { /* AI backcompat. Yuck */
+ opt = OPT_MULTI|OPT_EXECCGI;
+ }
+ else if (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(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 ((ap_cstr_casecmp(arg, "off") != 0) && (ap_cstr_casecmp(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 (ap_cstr_casecmp(token, "None") == 0) {
+ if (action != '*') {
+ valid = 0;
+ }
+ else {
+ cfg->etag_bits = bit = ETAG_NONE;
+ explicit = 1;
+ }
+ }
+ else if (ap_cstr_casecmp(token, "All") == 0) {
+ if (action != '*') {
+ valid = 0;
+ }
+ else {
+ explicit = 1;
+ cfg->etag_bits = bit = ETAG_ALL;
+ }
+ }
+ else if (ap_cstr_casecmp(token, "Size") == 0) {
+ bit = ETAG_SIZE;
+ }
+ else if ((ap_cstr_casecmp(token, "LMTime") == 0)
+ || (ap_cstr_casecmp(token, "MTime") == 0)
+ || (ap_cstr_casecmp(token, "LastModified") == 0)) {
+ bit = ETAG_MTIME;
+ }
+ else if (ap_cstr_casecmp(token, "INode") == 0) {
+ bit = ETAG_INODE;
+ }
+ else if (ap_cstr_casecmp(token, "Digest") == 0) {
+ bit = ETAG_DIGEST;
+ }
+ 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 (ap_cstr_casecmp(arg, "on") == 0) {
+ d->enable_mmap = ENABLE_MMAP_ON;
+ }
+ else if (ap_cstr_casecmp(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 (ap_cstr_casecmp(arg, "on") == 0) {
+ d->enable_sendfile = ENABLE_SENDFILE_ON;
+ }
+ else if (ap_cstr_casecmp(arg, "off") == 0) {
+ d->enable_sendfile = ENABLE_SENDFILE_OFF;
+ }
+ else {
+ return "parameter must be 'on' or 'off'";
+ }
+
+ return NULL;
+}
+
+static const char *set_read_buf_size(cmd_parms *cmd, void *d_,
+ const char *arg)
+{
+ core_dir_config *d = d_;
+ apr_off_t size;
+ char *end;
+
+ if (apr_strtoff(&size, arg, &end, 10)
+ || *end || size < 0 || size > APR_UINT32_MAX)
+ return apr_pstrcat(cmd->pool,
+ "parameter must be a number between 0 and "
+ APR_STRINGIFY(APR_UINT32_MAX) "): ",
+ arg, NULL);
+
+ d->read_buf_size = (apr_size_t)size;
+
+ return NULL;
+}
+
+static const char *set_flush_max_threshold(cmd_parms *cmd, void *d_,
+ const char *arg)
+{
+ core_server_config *conf =
+ ap_get_core_module_config(cmd->server->module_config);
+ apr_off_t size;
+ char *end;
+
+ if (apr_strtoff(&size, arg, &end, 10)
+ || *end || size < 0 || size > APR_UINT32_MAX)
+ return apr_pstrcat(cmd->pool,
+ "parameter must be a number between 0 and "
+ APR_STRINGIFY(APR_UINT32_MAX) "): ",
+ arg, NULL);
+
+ conf->flush_max_threshold = (apr_size_t)size;
+
+ return NULL;
+}
+
+static const char *set_flush_max_pipelined(cmd_parms *cmd, void *d_,
+ const char *arg)
+{
+ core_server_config *conf =
+ ap_get_core_module_config(cmd->server->module_config);
+ apr_off_t num;
+ char *end;
+
+ if (apr_strtoff(&num, arg, &end, 10)
+ || *end || num < -1 || num > APR_INT32_MAX)
+ return apr_pstrcat(cmd->pool,
+ "parameter must be a number between -1 and "
+ APR_STRINGIFY(APR_INT32_MAX) ": ",
+ arg, NULL);
+
+ conf->flush_max_pipelined = (apr_int32_t)num;
+
+ 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,
+ &current, &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;
+
+ /*
+ * At least on Windows, if the path we are testing is not valid (for example
+ * a path on a USB key that is not plugged), 'ap_server_root_relative()' will
+ * return NULL. In such a case, consider that the file is not there and that
+ * the section should be skipped.
+ */
+ relative = ap_server_root_relative(cmd->temp_pool, arg);
+ return (relative &&
+ (apr_stat(&sb, relative, APR_FINFO_TYPE, cmd->temp_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 (ap_cstr_casecmp(arg, "On") == 0) {
+ d->server_signature = srv_sig_on;
+ }
+ else if (ap_cstr_casecmp(arg, "Off") == 0) {
+ d->server_signature = srv_sig_off;
+ }
+ else if (ap_cstr_casecmp(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 == ap_cstr_casecmp(arg, "on")) {
+ d->allow_encoded_slashes = 1;
+ d->decode_encoded_slashes = 1; /* for compatibility with 2.0 & 2.2 */
+ } else if (0 == ap_cstr_casecmp(arg, "off")) {
+ d->allow_encoded_slashes = 0;
+ d->decode_encoded_slashes = 0;
+ } else if (0 == ap_cstr_casecmp(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 (!ap_cstr_casecmp(arg, "on")) {
+ d->hostname_lookups = HOSTNAME_LOOKUP_ON;
+ }
+ else if (!ap_cstr_casecmp(arg, "off")) {
+ d->hostname_lookups = HOSTNAME_LOOKUP_OFF;
+ }
+ else if (!ap_cstr_casecmp(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 (ap_cstr_casecmp(arg, "on") == 0) {
+ d->accept_path_info = AP_REQ_ACCEPT_PATH_INFO;
+ }
+ else if (ap_cstr_casecmp(arg, "off") == 0) {
+ d->accept_path_info = AP_REQ_REJECT_PATH_INFO;
+ }
+ else if (ap_cstr_casecmp(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 (ap_cstr_casecmp(arg, "on") == 0) {
+ d->use_canonical_name = USE_CANONICAL_NAME_ON;
+ }
+ else if (ap_cstr_casecmp(arg, "off") == 0) {
+ d->use_canonical_name = USE_CANONICAL_NAME_OFF;
+ }
+ else if (ap_cstr_casecmp(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 (ap_cstr_casecmp(arg, "on") == 0) {
+ d->use_canonical_phys_port = USE_CANONICAL_PHYS_PORT_ON;
+ }
+ else if (ap_cstr_casecmp(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 (!ap_cstr_casecmp(arg, "OS")) {
+ ap_server_tokens = SrvTk_OS;
+ }
+ else if (!ap_cstr_casecmp(arg, "Min") || !ap_cstr_casecmp(arg, "Minimal")) {
+ ap_server_tokens = SrvTk_MINIMAL;
+ }
+ else if (!ap_cstr_casecmp(arg, "Major")) {
+ ap_server_tokens = SrvTk_MAJOR;
+ }
+ else if (!ap_cstr_casecmp(arg, "Minor") ) {
+ ap_server_tokens = SrvTk_MINOR;
+ }
+ else if (!ap_cstr_casecmp(arg, "Prod") || !ap_cstr_casecmp(arg, "ProductOnly")) {
+ ap_server_tokens = SrvTk_PRODUCT_ONLY;
+ }
+ else if (!ap_cstr_casecmp(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.";
+
+ /* zero is AP_MAX_LIMIT_XML_BODY (implicitly) */
+ if ((apr_size_t)conf->limit_xml_body > AP_MAX_LIMIT_XML_BODY)
+ return apr_psprintf(cmd->pool, "LimitXMLRequestBody must not exceed "
+ "%" APR_SIZE_T_FMT, AP_MAX_LIMIT_XML_BODY);
+
+ 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 (!ap_cstr_casecmp(arg, "none")) {
+ val = AP_MAXRANGES_NORANGES;
+ }
+ else if (!ap_cstr_casecmp(arg, "default")) {
+ val = AP_MAXRANGES_DEFAULT;
+ }
+ else if (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(arg, "none")) {
+ val = AP_MAXRANGES_NORANGES;
+ }
+ else if (!ap_cstr_casecmp(arg, "default")) {
+ val = AP_MAXRANGES_DEFAULT;
+ }
+ else if (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(arg, "none")) {
+ val = AP_MAXRANGES_NORANGES;
+ }
+ else if (!ap_cstr_casecmp(arg, "default")) {
+ val = AP_MAXRANGES_DEFAULT;
+ }
+ else if (!ap_cstr_casecmp(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;
+ if (conf->limit_xml_body == 0)
+ return AP_MAX_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 (ap_cstr_casecmp(arg1, "on") == 0) {
+ conf->trace_enable = AP_TRACE_ENABLE;
+ }
+ else if (ap_cstr_casecmp(arg1, "off") == 0) {
+ conf->trace_enable = AP_TRACE_DISABLE;
+ }
+ else if (ap_cstr_casecmp(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 (ap_cstr_casecmp(arg, "on") == 0) {
+ conf->protocols_honor_order = 1;
+ }
+ else if (ap_cstr_casecmp(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 (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(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"),
+AP_INIT_TAKE1("ReadBufferSize", set_read_buf_size, NULL, ACCESS_CONF|RSRC_CONF,
+ "Size (in bytes) of the memory buffers used to read data"),
+AP_INIT_TAKE1("FlushMaxThreshold", set_flush_max_threshold, NULL, RSRC_CONF,
+ "Maximum threshold above which pending data are flushed to the network"),
+AP_INIT_TAKE1("FlushMaxPipelined", set_flush_max_pipelined, NULL, RSRC_CONF,
+ "Maximum number of pipelined responses (pending) above which they are "
+ "flushed to the network"),
+
+/* 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 the REDIRECT_URL environment variable is fully "
+ "qualified"),
+AP_INIT_FLAG("StrictHostCheck", set_core_server_flag,
+ (void *)APR_OFFSETOF(core_server_config, strict_host_check),
+ RSRC_CONF,
+ "Controls whether a hostname match is required"),
+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"),
+AP_INIT_FLAG("MergeSlashes", set_core_server_flag,
+ (void *)APR_OFFSETOF(core_server_config, merge_slashes),
+ RSRC_CONF,
+ "Controls whether consecutive slashes in the URI path are merged"),
+{ 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' '%s'", r->uri, 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_fd(r, fd);
+ 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
+#if APR_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 6)
+ if (d->read_buf_size) {
+ apr_bucket_file_set_buf_size(e, d->read_buf_size);
+ }
+#endif
+ }
+
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+
+ status = ap_pass_brigade(r->output_filters, bb);
+ apr_brigade_cleanup(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_DEFAULT);
+
+ 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);
+ ap_setup_ssl_optional_fns(pconf);
+ 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->pool = ptrans;
+ c->conn_config = ap_create_conn_config(ptrans);
+ c->notes = apr_table_make(ptrans, 5);
+
+ 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;
+ }
+ if (apr_sockaddr_ip_get(&c->local_ip, c->local_addr)) {
+#if APR_HAVE_SOCKADDR_UN
+ if (c->local_addr->family == APR_UNIX) {
+ c->local_ip = apr_pstrndup(c->pool, c->local_addr->ipaddr_ptr,
+ c->local_addr->ipaddr_len);
+ }
+ else
+#endif
+ c->local_ip = apr_pstrdup(c->pool, "unknown");
+ }
+
+ 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;
+ }
+ if (apr_sockaddr_ip_get(&c->client_ip, c->client_addr)) {
+#if APR_HAVE_SOCKADDR_UN
+ if (c->client_addr->family == APR_UNIX) {
+ c->client_ip = apr_pstrndup(c->pool, c->client_addr->ipaddr_ptr,
+ c->client_addr->ipaddr_len);
+ }
+ else
+#endif
+ c->client_ip = apr_pstrdup(c->pool, "unknown");
+ }
+
+ 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_status_t rv;
+
+ if (c->master) {
+ return DONE;
+ }
+
+ net = apr_palloc(c->pool, sizeof(*net));
+ /* 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_pre_connection(conn_rec *c, void *csd)
+{
+ int rc = OK;
+
+ rc = ap_run_pre_connection(c, csd);
+ if (rc != OK && rc != DONE) {
+ c->aborted = 1;
+ /*
+ * In case we errored, the pre_connection hook of the core
+ * module maybe did not run (it is APR_HOOK_REALLY_LAST) and
+ * hence we missed to
+ *
+ * - Put the socket in c->conn_config
+ * - Setup core output and input filters
+ * - Set socket options and timeouts
+ *
+ * Hence call it in this case.
+ */
+ if (!ap_get_conn_socket(c)) {
+ core_pre_connection(c, csd);
+ }
+ }
+ return rc;
+}
+
+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..d8a661f
--- /dev/null
+++ b/server/core_filters.c
@@ -0,0 +1,868 @@
+/* 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 */
+#include "apr_version.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 "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_pool_t *deferred_write_pool;
+ apr_size_t bytes_written;
+ struct iovec *vec;
+ apr_size_t nvec;
+};
+
+
+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 != (apr_size_t)-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, &copy_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,
+ core_output_filter_ctx_t *ctx,
+ conn_rec *c);
+
+static apr_status_t writev_nonblocking(apr_socket_t *s,
+ apr_bucket_brigade *bb,
+ core_output_filter_ctx_t *ctx,
+ apr_size_t bytes_to_write,
+ apr_size_t nvec,
+ conn_rec *c);
+
+#if APR_HAS_SENDFILE
+static apr_status_t sendfile_nonblocking(apr_socket_t *s,
+ apr_bucket *bucket,
+ core_output_filter_ctx_t *ctx,
+ conn_rec *c);
+#endif
+
+/* XXX: Should these be configurable parameters? */
+#define THRESHOLD_MIN_WRITE 4096
+
+/* 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;
+
+static int should_send_brigade(apr_bucket_brigade *bb, conn_rec *c, int *flush)
+{
+ core_server_config *conf =
+ ap_get_core_module_config(c->base_server->module_config);
+ apr_size_t total_bytes = 0, non_file_bytes = 0;
+ apr_uint32_t eor_buckets = 0;
+ apr_bucket *bucket;
+ int need_flush = 0;
+
+ /* Scan through the brigade and decide whether we need to flush it,
+ * based on the following rules:
+ *
+ * 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 flush_max_threshold bytes in non-file
+ * buckets: Do blocking writes until the amount of data in the
+ * buffer is less than flush_max_threshold. (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 flush_max_pipelined EOR buckets:
+ * Do blocking writes until less than flush_max_pipelined 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: otherwise ap_save_brigade()
+ * could read the whole bucket into memory.
+ */
+ for (bucket = APR_BRIGADE_FIRST(bb);
+ bucket != APR_BRIGADE_SENTINEL(bb);
+ bucket = APR_BUCKET_NEXT(bucket)) {
+
+ if (!APR_BUCKET_IS_METADATA(bucket)) {
+ if (bucket->length == (apr_size_t)-1) {
+ if (flush) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c,
+ "core_output_filter: flushing because "
+ "of morphing bucket");
+ }
+ need_flush = 1;
+ break;
+ }
+
+ total_bytes += bucket->length;
+ if (!APR_BUCKET_IS_FILE(bucket)) {
+ non_file_bytes += bucket->length;
+ if (non_file_bytes > conf->flush_max_threshold) {
+ if (flush) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c,
+ "core_output_filter: flushing because "
+ "of max threshold");
+ }
+ need_flush = 1;
+ break;
+ }
+ }
+ }
+ else if (APR_BUCKET_IS_FLUSH(bucket)) {
+ if (flush) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c,
+ "core_output_filter: flushing because "
+ "of FLUSH bucket");
+ }
+ need_flush = 1;
+ break;
+ }
+ else if (AP_BUCKET_IS_EOR(bucket)
+ && conf->flush_max_pipelined >= 0
+ && ++eor_buckets > conf->flush_max_pipelined) {
+ if (flush) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c,
+ "core_output_filter: flushing because "
+ "of max pipelined");
+ }
+ need_flush = 1;
+ break;
+ }
+ }
+ if (flush) {
+ *flush = need_flush;
+ }
+
+ /* Also send if above flush_min_threshold, or if there are FILE buckets */
+ return (need_flush
+ || total_bytes >= THRESHOLD_MIN_WRITE
+ || total_bytes > non_file_bytes);
+}
+
+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_status_t rv = APR_SUCCESS;
+
+ /* 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 buffered_bb brigade with correct lifetime. Passing
+ * NULL to ap_save_brigade() would result in a brigade
+ * allocated from bb->pool which might be wrong.
+ */
+ 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;
+ }
+ }
+ else if (new_bb == NULL) {
+ c->data_in_output_filters = 0;
+ return APR_SUCCESS;
+ }
+
+ if (!new_bb || should_send_brigade(bb, c, NULL)) {
+ apr_socket_t *sock = net->client_socket;
+ apr_interval_time_t sock_timeout = 0;
+
+ /* Non-blocking writes on the socket in any case. */
+ apr_socket_timeout_get(sock, &sock_timeout);
+ apr_socket_timeout_set(sock, 0);
+
+ do {
+ rv = send_brigade_nonblocking(sock, bb, ctx, c);
+ if (new_bb && APR_STATUS_IS_EAGAIN(rv)) {
+ /* Scan through the brigade and decide whether we must absolutely
+ * flush the remaining data, based on should_send_brigade() &flush
+ * rules. If so, wait for writability and retry, otherwise we did
+ * our best already and can wait for the next call.
+ */
+ int flush;
+ (void)should_send_brigade(bb, c, &flush);
+ if (flush) {
+ apr_int32_t nfd;
+ apr_pollfd_t pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.reqevents = APR_POLLOUT;
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = sock;
+ pfd.p = c->pool;
+ do {
+ rv = apr_poll(&pfd, 1, &nfd, sock_timeout);
+ } while (APR_STATUS_IS_EINTR(rv));
+ }
+ }
+ } while (rv == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb));
+
+ /* Restore original socket timeout before leaving. */
+ apr_socket_timeout_set(sock, sock_timeout);
+ }
+
+ 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");
+ /*
+ * Set c->aborted before apr_brigade_cleanup to have the correct status
+ * when logging the request as apr_brigade_cleanup triggers the logging
+ * of the request if it contains an EOR bucket.
+ */
+ c->aborted = 1;
+ apr_brigade_cleanup(bb);
+ return rv;
+ }
+
+ 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)
+{
+ apr_bucket *bucket;
+
+ /* Don't set aside leading empty buckets, all previous data have been
+ * consumed so it's safe to delete them now.
+ */
+ while (((bucket = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) &&
+ (APR_BUCKET_IS_METADATA(bucket) || (bucket->length == 0))) {
+ apr_bucket_delete(bucket);
+ }
+
+ c->data_in_output_filters = 0;
+ 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 NVEC_MIN 16
+#define NVEC_MAX NVEC_MIN
+#else
+#if APR_MAX_IOVEC_SIZE > 16
+#define NVEC_MIN 16
+#else
+#define NVEC_MIN APR_MAX_IOVEC_SIZE
+#endif
+#define NVEC_MAX APR_MAX_IOVEC_SIZE
+#endif
+
+static APR_INLINE int is_in_memory_bucket(apr_bucket *b)
+{
+ /* These buckets' data are already in memory. */
+ return APR_BUCKET_IS_HEAP(b)
+ || APR_BUCKET_IS_POOL(b)
+ || APR_BUCKET_IS_TRANSIENT(b)
+ || APR_BUCKET_IS_IMMORTAL(b);
+}
+
+#if APR_HAS_SENDFILE
+static APR_INLINE int can_sendfile_bucket(apr_bucket *b)
+{
+ /* Use sendfile to send the bucket unless:
+ * - the bucket is not a file bucket, or
+ * - the file is too small for sendfile to be useful, or
+ * - sendfile is disabled in the httpd config via "EnableSendfile off".
+ */
+ if (APR_BUCKET_IS_FILE(b) && b->length >= AP_MIN_SENDFILE_BYTES) {
+ apr_file_t *file = ((apr_bucket_file *)b->data)->fd;
+ return apr_file_flags_get(file) & APR_SENDFILE_ENABLED;
+ }
+ else {
+ return 0;
+ }
+}
+#endif
+
+#if defined(WIN32) && (APR_MAJOR_VERSION == 1 && APR_MINOR_VERSION <= 7)
+#undef APR_TCP_NOPUSH_FLAG
+#define APR_TCP_NOPUSH_FLAG 0
+#endif
+
+static APR_INLINE void sock_nopush(apr_socket_t *s, int to)
+{
+ /* Disable TCP_NOPUSH handling on OSX since unsetting it won't push
+ * retained data, which might introduce delays if further data don't
+ * come soon enough or cause the last chunk to be sent only when the
+ * connection is shutdown (e.g. after KeepAliveTimeout).
+ */
+#if APR_TCP_NOPUSH_FLAG && !defined(__APPLE__)
+ (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, to);
+#endif
+}
+
+static apr_status_t send_brigade_nonblocking(apr_socket_t *s,
+ apr_bucket_brigade *bb,
+ core_output_filter_ctx_t *ctx,
+ conn_rec *c)
+{
+ apr_status_t rv = APR_SUCCESS;
+ core_server_config *conf =
+ ap_get_core_module_config(c->base_server->module_config);
+ apr_size_t nvec = 0, nbytes = 0;
+ apr_bucket *bucket, *next;
+ const char *data;
+ apr_size_t length;
+
+ for (bucket = APR_BRIGADE_FIRST(bb);
+ bucket != APR_BRIGADE_SENTINEL(bb);
+ bucket = next) {
+ next = APR_BUCKET_NEXT(bucket);
+
+#if APR_HAS_SENDFILE
+ if (can_sendfile_bucket(bucket)) {
+ if (nvec > 0) {
+ sock_nopush(s, 1);
+ rv = writev_nonblocking(s, bb, ctx, nbytes, nvec, c);
+ if (rv != APR_SUCCESS) {
+ goto cleanup;
+ }
+ nbytes = 0;
+ nvec = 0;
+ }
+ rv = sendfile_nonblocking(s, bucket, ctx, c);
+ if (rv != APR_SUCCESS) {
+ goto cleanup;
+ }
+ continue;
+ }
+#endif /* APR_HAS_SENDFILE */
+
+ if (bucket->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, bb, ctx, nbytes, nvec, c);
+ if (rv != APR_SUCCESS) {
+ goto cleanup;
+ }
+ nbytes = 0;
+ nvec = 0;
+ }
+ sock_nopush(s, 0);
+
+ rv = apr_bucket_read(bucket, &data, &length, APR_BLOCK_READ);
+ }
+ if (rv != APR_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* reading may have split the bucket, so recompute next: */
+ next = APR_BUCKET_NEXT(bucket);
+ }
+
+ if (!bucket->length) {
+ /* Don't delete empty buckets until all the previous ones have been
+ * sent (nvec == 0); this must happen in sequence since metabuckets
+ * like EOR could free the data still pointed to by the iovec. So
+ * unless the latter is empty, let writev_nonblocking() cleanup the
+ * brigade in order.
+ */
+ if (!nvec) {
+ apr_bucket_delete(bucket);
+ }
+ continue;
+ }
+
+ /* Make sure that these new data fit in our iovec. */
+ if (nvec == ctx->nvec) {
+ if (nvec == NVEC_MAX) {
+ sock_nopush(s, 1);
+ rv = writev_nonblocking(s, bb, ctx, nbytes, nvec, c);
+ if (rv != APR_SUCCESS) {
+ goto cleanup;
+ }
+ nbytes = 0;
+ nvec = 0;
+ }
+ else {
+ struct iovec *newvec;
+ apr_size_t newn = nvec * 2;
+ if (newn < NVEC_MIN) {
+ newn = NVEC_MIN;
+ }
+ else if (newn > NVEC_MAX) {
+ newn = NVEC_MAX;
+ }
+ newvec = apr_palloc(c->pool, newn * sizeof(struct iovec));
+ if (nvec) {
+ memcpy(newvec, ctx->vec, nvec * sizeof(struct iovec));
+ }
+ ctx->vec = newvec;
+ ctx->nvec = newn;
+ }
+ }
+ nbytes += length;
+ ctx->vec[nvec].iov_base = (void *)data;
+ ctx->vec[nvec].iov_len = length;
+ nvec++;
+
+ /* Flush above max threshold, unless the brigade still contains in
+ * memory buckets which we want to try writing in the same pass (if
+ * we are at the end of the brigade, the write will happen outside
+ * the loop anyway).
+ */
+ if (nbytes > conf->flush_max_threshold
+ && next != APR_BRIGADE_SENTINEL(bb)
+ && !is_in_memory_bucket(next)) {
+ sock_nopush(s, 1);
+ rv = writev_nonblocking(s, bb, ctx, nbytes, nvec, c);
+ if (rv != APR_SUCCESS) {
+ goto cleanup;
+ }
+ nbytes = 0;
+ nvec = 0;
+ }
+ }
+ if (nvec > 0) {
+ rv = writev_nonblocking(s, bb, ctx, nbytes, nvec, c);
+ }
+
+cleanup:
+ sock_nopush(s, 0);
+ return rv;
+}
+
+static apr_status_t writev_nonblocking(apr_socket_t *s,
+ apr_bucket_brigade *bb,
+ core_output_filter_ctx_t *ctx,
+ apr_size_t bytes_to_write,
+ apr_size_t nvec,
+ conn_rec *c)
+{
+ apr_status_t rv;
+ struct iovec *vec = ctx->vec;
+ apr_size_t bytes_written = 0;
+ apr_size_t i, offset = 0;
+
+ do {
+ apr_size_t n = 0;
+ rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n);
+ bytes_written += n;
+
+ for (i = offset; i < nvec; ) {
+ apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
+ if (!bucket->length) {
+ apr_bucket_delete(bucket);
+ }
+ else if (n >= vec[i].iov_len) {
+ apr_bucket_delete(bucket);
+ n -= vec[i++].iov_len;
+ offset++;
+ }
+ else {
+ if (n) {
+ 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;
+ }
+ }
+ } while (rv == APR_SUCCESS && bytes_written < bytes_to_write);
+
+ if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) {
+ ap__logio_add_bytes_out(c, bytes_written);
+ }
+ ctx->bytes_written += bytes_written;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, rv, c,
+ "writev_nonblocking: %"APR_SIZE_T_FMT"/%"APR_SIZE_T_FMT,
+ bytes_written, bytes_to_write);
+ return rv;
+}
+
+#if APR_HAS_SENDFILE
+
+static apr_status_t sendfile_nonblocking(apr_socket_t *s,
+ apr_bucket *bucket,
+ core_output_filter_ctx_t *ctx,
+ conn_rec *c)
+{
+ apr_status_t rv;
+ apr_file_t *file = ((apr_bucket_file *)bucket->data)->fd;
+ apr_size_t bytes_written = bucket->length; /* bytes_to_write for now */
+ apr_off_t file_offset = bucket->start;
+
+ rv = apr_socket_sendfile(s, file, NULL, &file_offset, &bytes_written, 0);
+ if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) {
+ ap__logio_add_bytes_out(c, bytes_written);
+ }
+ ctx->bytes_written += bytes_written;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, rv, c,
+ "sendfile_nonblocking: %" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT,
+ bytes_written, bucket->length);
+ if (bytes_written >= bucket->length) {
+ apr_bucket_delete(bucket);
+ }
+ else if (bytes_written > 0) {
+ apr_bucket_split(bucket, bytes_written);
+ apr_bucket_delete(bucket);
+ if (rv == APR_SUCCESS) {
+ rv = APR_EAGAIN;
+ }
+ }
+ 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..ecb809c
--- /dev/null
+++ b/server/eor_bucket.c
@@ -0,0 +1,115 @@
+/* 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"
+
+typedef struct {
+ apr_bucket_refcount refcount;
+ request_rec *data;
+} ap_bucket_eor;
+
+static apr_status_t eor_bucket_cleanup(void *data)
+{
+ request_rec **rp = data;
+
+ if (*rp) {
+ request_rec *r = *rp;
+ /*
+ * If eor_bucket_destroy is called after us, this prevents
+ * eor_bucket_destroy from trying to destroy the pool again.
+ */
+ *rp = 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)
+{
+ ap_bucket_eor *h;
+
+ h = apr_bucket_alloc(sizeof(*h), b->list);
+ h->data = r;
+
+ b = apr_bucket_shared_make(b, h, 0, 0);
+ 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;
+ b = ap_bucket_eor_make(b, r);
+ if (r) {
+ ap_bucket_eor *h = b->data;
+ /*
+ * 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, &h->data, eor_bucket_cleanup);
+ }
+ return b;
+}
+
+static void eor_bucket_destroy(void *data)
+{
+ ap_bucket_eor *h = data;
+
+ if (apr_bucket_shared_destroy(h)) {
+ request_rec *r = h->data;
+ if (r) {
+ /* eor_bucket_cleanup will be called when the pool gets destroyed */
+ apr_pool_destroy(r->pool);
+ }
+ apr_bucket_free(h);
+ }
+}
+
+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_shared_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..248216b
--- /dev/null
+++ b/server/gen_test_char.c
@@ -0,0 +1,192 @@
+/* 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)
+#define T_URI_UNRESERVED (0x200)
+
+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"
+ "#define T_URI_UNRESERVED (%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,
+ T_URI_UNRESERVED
+ );
+
+ 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 easier 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;
+ }
+
+ /* Characters in the RFC 3986 "unreserved" set.
+ * https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 */
+ if (c && (apr_isalnum(c) || strchr("-._~", c))) {
+ flags |= T_URI_UNRESERVED;
+ }
+
+ printf("0x%03x%c", flags, (c < 255) ? ',' : ' ');
+ }
+
+ printf("\n};\n\n");
+
+ printf(
+ "/* we assume the folks using this ensure 0 <= c < 256... which means\n"
+ " * you need a cast to (unsigned char) first, you can't just plug a\n"
+ " * char in here and get it to work, because if char is signed then it\n"
+ " * will first be sign extended.\n"
+ " */\n"
+ "#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f))\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..5242c2a
--- /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 guarantee); 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..cc04c38
--- /dev/null
+++ b/server/log.c
@@ -0,0 +1,1974 @@
+/* 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 destroy 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 child's 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);
+ }
+
+ if (rc == APR_SUCCESS) {
+ 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;
+ char *temp_fname;
+ apr_fileperms_t perms;
+ char pidstr[64];
+
+ 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);
+ }
+
+ temp_fname = apr_pstrcat(p, fname, ".XXXXXX", NULL);
+ rv = apr_file_mktemp(&pid_file, temp_fname,
+ APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00099)
+ "could not create %s", temp_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_snprintf(pidstr, sizeof pidstr, "%" APR_PID_T_FMT APR_EOL_STR, mypid);
+
+ perms = APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD;
+ if (((rv = apr_file_perms_set(temp_fname, perms)) != APR_SUCCESS && rv != APR_ENOTIMPL)
+ || (rv = apr_file_write_full(pid_file, pidstr, strlen(pidstr), NULL)) != APR_SUCCESS
+ || (rv = apr_file_close(pid_file)) != APR_SUCCESS
+ || (rv = apr_file_rename(temp_fname, fname, p)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(10231)
+ "%s: Failed creating pid file %s",
+ ap_server_argv0, temp_fname);
+ exit(1);
+ }
+
+ 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)
+{
+ const 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..7da7aa2
--- /dev/null
+++ b/server/main.c
@@ -0,0 +1,873 @@
+/* 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_thread_proc.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, PCRE %s\n",
+ apr_version_string(), ap_pcre_version_string(AP_REG_PCRE_LOADED));
+ printf("Compiled using: APR %s, PCRE %s\n",
+ APR_VERSION_STRING, ap_pcre_version_string(AP_REG_PCRE_COMPILED));
+#else
+ printf("Server loaded: APR %s, APR-UTIL %s, PCRE %s\n",
+ apr_version_string(), apu_version_string(),
+ ap_pcre_version_string(AP_REG_PCRE_LOADED));
+ printf("Compiled using: APR %s, APR-UTIL %s, PCRE %s\n",
+ APR_VERSION_STRING, APU_VERSION_STRING,
+ ap_pcre_version_string(AP_REG_PCRE_COMPILED));
+#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 valuable 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]);
+
+#if AP_HAS_THREAD_LOCAL
+ {
+ apr_status_t rv;
+ apr_thread_t *thd = NULL;
+ if ((rv = ap_thread_main_create(&thd, process->pool))) {
+ char ctimebuff[APR_CTIME_LEN];
+ apr_ctime(ctimebuff, apr_time_now());
+ fprintf(stderr, "[%s] [crit] (%d) %s: failed "
+ "to initialize thread context, exiting\n",
+ ctimebuff, rv, (*argv)[0]);
+ apr_terminate();
+ exit(1);
+ }
+ }
+#endif
+
+ 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..3672f44
--- /dev/null
+++ b/server/mpm/event/event.c
@@ -0,0 +1,4078 @@
+/* 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 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;
+ /** Is lingering close from defer_lingering_close()? */
+ int deferred_linger;
+};
+
+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 elem_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 global queues_next_expiry with the one of the
+ * first entry of this queue (oldest) if it expires before.
+ */
+ el = APR_RING_FIRST(&q->head);
+ elem_expiry = el->queue_timestamp + q->timeout;
+ next_expiry = queues_next_expiry;
+ if (!next_expiry || next_expiry > elem_expiry + TIMEOUT_FUDGE_FACTOR) {
+ queues_next_expiry = elem_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_daemon_used;
+
+ /*
+ * 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;
+ /*
+ * Workers that still active, i.e. are not shutting down gracefully.
+ */
+ int active_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(int *busy)
+{
+ 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;
+ }
+ }
+ else if (busy) {
+ *busy = 1;
+ }
+ return 1;
+}
+
+static APR_INLINE int should_enable_listensocks(void)
+{
+ return !dying && listeners_disabled() && !connections_above_limit(NULL);
+}
+
+static void close_socket_nonblocking_(apr_socket_t *csd,
+ const char *from, int line)
+{
+ apr_status_t rv;
+ apr_os_sock_t fd = -1;
+
+ /* close_worker_sockets() may have closed it already */
+ rv = apr_os_sock_get(&fd, csd);
+ ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf,
+ "closing socket %i/%pp from %s:%i", (int)fd, csd, from, line);
+ if (rv == APR_SUCCESS && fd == -1) {
+ return;
+ }
+
+ apr_socket_timeout_set(csd, 0);
+ 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);
+ }
+}
+#define close_socket_nonblocking(csd) \
+ close_socket_nonblocking_(csd, __FUNCTION__, __LINE__)
+
+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;
+ close_socket_nonblocking(csd);
+ }
+ }
+}
+
+static void wakeup_listener(void)
+{
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "wake up listener%s", listener_may_exit ? " again" : "");
+
+ 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 */
+ }
+
+ ap_run_child_stopping(pchild, mode == ST_GRACEFUL);
+}
+
+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_daemon_used;
+ 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_stopped(int slot, pid_t pid, ap_generation_t gen)
+{
+ if (slot != -1) { /* child had a scoreboard slot? */
+ process_score *ps = &ap_scoreboard_image->parent[slot];
+ int i;
+
+ pid = ps->pid;
+ gen = ps->generation;
+ for (i = 0; i < threads_per_child; i++) {
+ ap_update_child_status_from_indexes(slot, i, SERVER_DEAD, NULL);
+ }
+ ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_EXITED);
+ if (ps->quiescing != 2) { /* vs perform_idle_server_maintenance() */
+ retained->active_daemons--;
+ }
+ retained->total_daemons--;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "Child %d stopped: pid %d, gen %d, "
+ "active %d/%d, total %d/%d/%d, quiescing %d",
+ slot, (int)pid, (int)gen,
+ retained->active_daemons, active_daemons_limit,
+ retained->total_daemons, retained->max_daemon_used,
+ server_limit, ps->quiescing);
+ ps->not_accepting = 0;
+ ps->quiescing = 0;
+ ps->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;
+
+ retained->total_daemons++;
+ retained->active_daemons++;
+ 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);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "Child %d started: pid %d, gen %d, "
+ "active %d/%d, total %d/%d/%d",
+ slot, (int)pid, (int)gen,
+ retained->active_daemons, active_daemons_limit,
+ retained->total_daemons, retained->max_daemon_used,
+ server_limit);
+}
+
+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 (terminate_mode == ST_INIT) {
+ ap_run_child_stopping(pchild, 0);
+ }
+
+ if (pchild) {
+ apr_pool_destroy(pchild);
+ }
+
+ if (one_process) {
+ event_note_child_stopped(/* 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_;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, cs->c,
+ "cleanup connection from state %i", (int)cs->pub.state);
+ switch (cs->pub.state) {
+ case CONN_STATE_LINGER:
+ 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)
+ || should_enable_listensocks())) {
+ apr_pollset_wakeup(event_pollset);
+ }
+ if (dying) {
+ /* Help worker_thread_should_exit_early() */
+ ap_queue_interrupt_one(worker_queue);
+ }
+ 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);
+}
+
+/*
+ * 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: nonblocking, can be called from anywhere provided cs is not
+ * in any timeout queue or in the pollset.
+ */
+static int defer_lingering_close(event_conn_state_t *cs)
+{
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, cs->c,
+ "deferring close from state %i", (int)cs->pub.state);
+
+ /* The connection is not shutdown() yet strictly speaking, but it's not
+ * in any queue nor handled by a worker either (will be very soon), so
+ * to account for it somewhere we bump lingering_count now (and set
+ * deferred_linger for process_lingering_close() to know).
+ */
+ cs->pub.state = CONN_STATE_LINGER;
+ apr_atomic_inc32(&lingering_count);
+ cs->deferred_linger = 1;
+ for (;;) {
+ event_conn_state_t *chain = cs->chain = defer_linger_chain;
+ if (apr_atomic_casptr((void *)&defer_linger_chain, cs,
+ chain) != chain) {
+ /* Race lost, try again */
+ continue;
+ }
+ return 1;
+ }
+}
+
+/* Close the connection and release its resources (ptrans), either because an
+ * unrecoverable error occured (queues or pollset add/remove) or more usually
+ * if lingering close timed out.
+ * Pre-condition: nonblocking, can be called from anywhere provided cs is not
+ * in any timeout queue or in the pollset.
+ */
+static void close_connection(event_conn_state_t *cs)
+{
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, cs->c,
+ "closing connection from state %i", (int)cs->pub.state);
+
+ close_socket_nonblocking(cs->pfd.desc.s);
+ ap_queue_info_push_pool(worker_queue_info, cs->p);
+}
+
+/* Shutdown the connection in case of timeout, error or resources shortage.
+ * This starts short lingering close if not already there, or directly closes
+ * the connection otherwise.
+ * Pre-condition: nonblocking, can be called from anywhere provided cs is not
+ * in any timeout queue or in the pollset.
+ */
+static int shutdown_connection(event_conn_state_t *cs)
+{
+ if (cs->pub.state < CONN_STATE_LINGER) {
+ apr_table_setn(cs->c->notes, "short-lingering-close", "1");
+ defer_lingering_close(cs);
+ }
+ else {
+ close_connection(cs);
+ }
+ return 1;
+}
+
+/*
+ * 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);
+
+static void update_reqevents_from_sense(event_conn_state_t *cs, int sense)
+{
+ if (sense < 0) {
+ sense = cs->pub.sense;
+ }
+ if (sense == CONN_SENSE_WANT_READ) {
+ cs->pfd.reqevents = APR_POLLIN | APR_POLLHUP;
+ }
+ else {
+ cs->pfd.reqevents = APR_POLLOUT;
+ }
+ /* POLLERR is usually returned event only, but some pollset
+ * backends may require it in reqevents to do the right thing,
+ * so it shouldn't hurt (ignored otherwise).
+ */
+ cs->pfd.reqevents |= APR_POLLERR;
+
+ /* Reset to default for the next round */
+ cs->pub.sense = CONN_SENSE_DEFAULT;
+}
+
+/*
+ * 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;
+ c->cs = &cs->pub;
+ cs->c = c;
+ 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.desc.s = sock;
+ update_reqevents_from_sense(cs, CONN_SENSE_WANT_READ);
+ 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_pre_connection(c, sock);
+ if (rc != OK && rc != DONE) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(00469)
+ "process_socket: connection aborted");
+ }
+
+ /**
+ * 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 interacts 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 ||
+ cs->pub.sense == CONN_SENSE_WANT_READ) {
+ /* Still in WRITE_COMPLETION_STATE:
+ * Set a read/write timeout for this connection, and let the
+ * event thread poll for read/writeability.
+ */
+ cs->queue_timestamp = apr_time_now();
+ notify_suspend(cs);
+
+ update_reqevents_from_sense(cs, -1);
+ 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");
+ close_connection(cs);
+ signal_threads(ST_GRACEFUL);
+ }
+ else {
+ apr_thread_mutex_unlock(timeout_mutex);
+ }
+ return;
+ }
+ else if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted) {
+ 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 if (!listener_may_exit) {
+ cs->pub.state = CONN_STATE_CHECK_REQUEST_LINE_READABLE;
+ }
+ else {
+ cs->pub.state = CONN_STATE_LINGER;
+ }
+ }
+
+ 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. */
+ update_reqevents_from_sense(cs, CONN_SENSE_WANT_READ);
+ 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");
+ close_connection(cs);
+ signal_threads(ST_GRACEFUL);
+ }
+ else {
+ apr_thread_mutex_unlock(timeout_mutex);
+ }
+ return;
+ }
+
+ if (cs->pub.state == CONN_STATE_SUSPENDED) {
+ apr_atomic_inc32(&suspended_count);
+ notify_suspend(cs);
+ return;
+ }
+
+ /* CONN_STATE_LINGER[_*] fall through process_lingering_close() */
+ if (cs->pub.state >= CONN_STATE_LINGER) {
+ process_lingering_close(cs);
+ return;
+ }
+}
+
+/* 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 int close_listeners(int *closed)
+{
+ ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf,
+ "clos%s listeners (connection_count=%u)",
+ *closed ? "ed" : "ing", apr_atomic_read32(&connection_count));
+ if (!*closed) {
+ int i;
+
+ ap_close_listeners_ex(my_bucket->listeners);
+ *closed = 1; /* once */
+
+ 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);
+
+ return 1;
+ }
+ return 0;
+}
+
+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 (cs) {
+ shutdown_connection(cs);
+ }
+ else {
+ if (csd) {
+ close_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 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.link, timer_event_t, link)) {
+ te = APR_RING_FIRST(&timer_free_ring.link);
+ 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 global timers_next_expiry with this event's
+ * if it expires before.
+ */
+ 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;
+}
+
+
+/*
+ * Flush data and close our side of the connection, then drain incoming data.
+ * If the latter would block put the connection in one of the linger timeout
+ * queues to be called back when ready, and repeat until it's closed by peer.
+ * 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.
+ * Pre-condition: cs is not in any timeout queue and not in the pollset,
+ * timeout_mutex is not locked
+ */
+#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;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, cs->c,
+ "lingering close from state %i", (int)cs->pub.state);
+ AP_DEBUG_ASSERT(cs->pub.state >= CONN_STATE_LINGER);
+
+ if (cs->pub.state == CONN_STATE_LINGER) {
+ /* defer_lingering_close() may have bumped lingering_count already */
+ if (!cs->deferred_linger) {
+ apr_atomic_inc32(&lingering_count);
+ }
+
+ apr_socket_timeout_set(csd, apr_time_from_sec(SECONDS_TO_LINGER));
+ if (ap_start_lingering_close(cs->c)) {
+ notify_suspend(cs);
+ close_connection(cs);
+ return;
+ }
+
+ cs->queue_timestamp = apr_time_now();
+ /* Clear APR_INCOMPLETE_READ if it was ever set, we'll do the poll()
+ * at the listener only from now, if needed.
+ */
+ apr_socket_opt_set(csd, APR_INCOMPLETE_READ, 0);
+ /*
+ * 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;
+ }
+ notify_suspend(cs);
+ }
+
+ apr_socket_timeout_set(csd, 0);
+ do {
+ nbytes = sizeof(dummybuf);
+ rv = apr_socket_recv(csd, dummybuf, &nbytes);
+ } while (rv == APR_SUCCESS);
+
+ if (!APR_STATUS_IS_EAGAIN(rv)) {
+ close_connection(cs);
+ return;
+ }
+
+ /* (Re)queue the connection to come back when readable */
+ update_reqevents_from_sense(cs, CONN_SENSE_WANT_READ);
+ 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");
+ close_connection(cs);
+ signal_threads(ST_GRACEFUL);
+ return;
+ }
+ apr_thread_mutex_unlock(timeout_mutex);
+}
+
+/* call 'func' for all elements of 'q' above 'expiry'.
+ * 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 expiry,
+ int (*func)(event_conn_state_t *))
+{
+ apr_uint32_t total = 0, count;
+ event_conn_state_t *first, *cs, *last;
+ struct event_conn_state_t trash;
+ struct timeout_queue *qp;
+ apr_status_t rv;
+
+ if (!*q->total) {
+ return;
+ }
+
+ APR_RING_INIT(&trash.timeout_list, 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 expiry was given (zero means 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 expiry (~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 (expiry && cs->queue_timestamp + qp->timeout > expiry
+ && cs->queue_timestamp < expiry + qp->timeout) {
+ /* Since this is the next expiring entry of this queue, update
+ * the global queues_next_expiry if it's later than this one.
+ */
+ apr_time_t elem_expiry = cs->queue_timestamp + qp->timeout;
+ apr_time_t next_expiry = queues_next_expiry;
+ if (!next_expiry
+ || next_expiry > elem_expiry + TIMEOUT_FUDGE_FACTOR) {
+ queues_next_expiry = elem_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.timeout_list, 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.timeout_list);
+ 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 expiry)
+{
+ /* If all workers are busy, we kill older keep-alive connections so
+ * that they may connect to another process.
+ */
+ if (!expiry && *keepalive_q->total) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf,
+ "All workers are busy or dying, will shutdown %u "
+ "keep-alive connections", *keepalive_q->total);
+ }
+ process_timeout_queue(keepalive_q, expiry, shutdown_connection);
+}
+
+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.
+ */
+ apr_signal(LISTENER_SIGNAL, dummy_signal_handler);
+ unblock_signal(LISTENER_SIGNAL);
+
+ for (;;) {
+ timer_event_t *te;
+ const apr_pollfd_t *out_pfd;
+ apr_int32_t num = 0;
+ apr_interval_time_t timeout;
+ apr_time_t now, expiry = -1;
+ int workers_were_busy = 0;
+
+ if (conns_this_child <= 0)
+ check_infinite_requests();
+
+ if (listener_may_exit) {
+ int first_close = close_listeners(&closed);
+
+ if (terminate_mode == ST_UNGRACEFUL
+ || apr_atomic_read32(&connection_count) == 0)
+ break;
+
+ /* Don't wait in poll() for the first close (i.e. dying now), we
+ * want to maintain the queues and schedule defer_linger_chain ASAP
+ * to kill kept-alive connection and shutdown the workers and child
+ * faster.
+ */
+ if (first_close) {
+ goto do_maintenance; /* with expiry == -1 */
+ }
+ }
+
+ 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),
+ apr_atomic_read32(write_completion_q->total),
+ apr_atomic_read32(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.
+ */
+ now = apr_time_now();
+ timeout = -1;
+
+ /* Push expired timers to a worker, the first remaining one determines
+ * the maximum time to poll() below, if any.
+ */
+ expiry = timers_next_expiry;
+ if (expiry && expiry < now) {
+ apr_thread_mutex_lock(g_timer_skiplist_mtx);
+ while ((te = apr_skiplist_peek(timer_skiplist))) {
+ if (te->when > now) {
+ timers_next_expiry = te->when;
+ timeout = 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. */
+ expiry = queues_next_expiry;
+ if (expiry
+ && (timeout < 0
+ || expiry <= now
+ || timeout > expiry - now)) {
+ timeout = expiry > now ? expiry - now : 0;
+ }
+
+ /* 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 < 0
+ || timeout > NON_WAKEABLE_POLL_TIMEOUT)) {
+ timeout = NON_WAKEABLE_POLL_TIMEOUT;
+ }
+ else if (timeout > 0) {
+ /* apr_pollset_poll() might round down the timeout to milliseconds,
+ * let's forcibly round up here to never return before the timeout.
+ */
+ timeout = apr_time_from_msec(
+ apr_time_as_msec(timeout + apr_time_from_msec(1) - 1)
+ );
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,
+ "polling with timeout=%" APR_TIME_T_FMT
+ " queues_timeout=%" APR_TIME_T_FMT
+ " timers_timeout=%" APR_TIME_T_FMT,
+ timeout, queues_next_expiry - now,
+ timers_next_expiry - now);
+
+ rc = apr_pollset_poll(event_pollset, timeout, &num, &out_pfd);
+ if (rc != APR_SUCCESS) {
+ if (!APR_STATUS_IS_EINTR(rc) && !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 (APLOGtrace7(ap_server_conf)) {
+ now = apr_time_now();
+ ap_log_error(APLOG_MARK, APLOG_TRACE7, rc, ap_server_conf,
+ "polled with num=%u exit=%d/%d conns=%d"
+ " queues_timeout=%" APR_TIME_T_FMT
+ " timers_timeout=%" APR_TIME_T_FMT,
+ num, listener_may_exit, dying,
+ apr_atomic_read32(&connection_count),
+ queues_next_expiry - now, timers_next_expiry - now);
+ }
+
+ /* XXX possible optimization: stash the current time for use as
+ * r->request_time for new requests or queues maintenance
+ */
+
+ 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");
+ close_connection(cs);
+ signal_threads(ST_GRACEFUL);
+ 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 shutdown so the choice is to favor
+ * incoming/alive connections.
+ */
+ get_worker(&have_idle_worker, blocking,
+ &workers_were_busy);
+ if (!have_idle_worker) {
+ shutdown_connection(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(&workers_were_busy)) {
+ 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));
+ }
+ 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 */
+
+ /* We process the timeout queues here only when the global
+ * queues_next_expiry is passed. 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.
+ */
+ expiry = queues_next_expiry;
+do_maintenance:
+ if (expiry && expiry < (now = apr_time_now())) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,
+ "queues maintenance with timeout=%" APR_TIME_T_FMT,
+ expiry > 0 ? expiry - now : -1);
+ apr_thread_mutex_lock(timeout_mutex);
+
+ /* Steps 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(now);
+ }
+ /* Step 2: write completion timeouts */
+ process_timeout_queue(write_completion_q, now,
+ defer_lingering_close);
+ /* Step 3: (normal) lingering close completion timeouts */
+ if (dying && linger_q->timeout > short_linger_q->timeout) {
+ /* Dying, force short timeout for normal lingering close */
+ linger_q->timeout = short_linger_q->timeout;
+ }
+ process_timeout_queue(linger_q, now, shutdown_connection);
+ /* Step 4: (short) lingering close completion timeouts */
+ process_timeout_queue(short_linger_q, now, shutdown_connection);
+
+ apr_thread_mutex_unlock(timeout_mutex);
+ ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,
+ "queues maintained with timeout=%" APR_TIME_T_FMT,
+ queues_next_expiry > now ? queues_next_expiry - now
+ : -1);
+
+ ps->keep_alive = apr_atomic_read32(keepalive_q->total);
+ ps->write_completion = apr_atomic_read32(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)
+ && apr_atomic_read32(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 (!workers_were_busy && should_enable_listensocks()) {
+ enable_listensocks();
+ }
+ } /* listener main loop */
+
+ 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);
+
+ for (;;) {
+ 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;
+ }
+ /* A new idler may have changed connections_above_limit(),
+ * let the listener know and decide.
+ */
+ if (listener_is_wakeable && should_enable_listensocks()) {
+ apr_pollset_wakeup(event_pollset);
+ }
+ 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.link, 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;
+ AP_DEBUG_ASSERT(cs->pub.state == CONN_STATE_LINGER);
+
+ worker_sockets[thread_slot] = csd = cs->pfd.desc.s;
+ 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 = ap_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)
+ "ap_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.link, 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 explicitly
+ * 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 | APR_POLLHUP | APR_POLLERR;
+ 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 = ap_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)
+ "ap_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 (!dying) {
+ apr_sleep(apr_time_from_msec(500));
+ if (dying || ++iter > 10) {
+ break;
+ }
+ /* listener has not stopped accepting yet */
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf,
+ "listener has not stopped accepting yet (%d iter)", iter);
+ wakeup_listener();
+ }
+ 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");
+
+#if AP_HAS_THREAD_LOCAL
+ if (!one_process) {
+ apr_thread_t *thd = NULL;
+ if ((rv = ap_thread_main_create(&thd, pchild))) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(10377)
+ "Couldn't initialize child main thread");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+ }
+#endif
+
+ /* 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" (implicitly) 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 = ap_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)
+ "ap_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
+ */
+ apr_signal(SIGTERM, dummy_signal_handler);
+ unblock_signal(SIGTERM);
+ /* 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).
+ */
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf,
+ "%s termination received, joining workers",
+ rv == AP_MPM_PODX_GRACEFUL ? "graceful" : "ungraceful");
+ join_workers(ts->listener, threads);
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf,
+ "%s termination, workers joined, exiting",
+ rv == AP_MPM_PODX_GRACEFUL ? "graceful" : "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_daemon_used) {
+ retained->max_daemon_used = 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) {
+#if AP_HAS_THREAD_LOCAL
+ ap_thread_current_after_fork();
+#endif
+
+ 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;
+ }
+
+ event_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 < 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 *max_daemon_used)
+{
+ int num_buckets = retained->mpm->num_buckets;
+ int idle_thread_count = 0;
+ process_score *ps;
+ int free_length = 0;
+ int free_slots[MAX_SPAWN_RATE];
+ int last_non_dead = -1;
+ int active_thread_count = 0;
+ int i, j;
+
+ for (i = 0; i < server_limit; ++i) {
+ if (num_buckets > 1 && (i % num_buckets) != child_bucket) {
+ /* We only care about child_bucket in this call */
+ continue;
+ }
+ if (i >= retained->max_daemon_used &&
+ 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) {
+ int child_threads_active = 0;
+ if (ps->quiescing == 1) {
+ ps->quiescing = 2;
+ retained->active_daemons--;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "Child %d quiescing: pid %d, gen %d, "
+ "active %d/%d, total %d/%d/%d",
+ i, (int)ps->pid, (int)ps->generation,
+ retained->active_daemons, active_daemons_limit,
+ retained->total_daemons, retained->max_daemon_used,
+ server_limit);
+ }
+ for (j = 0; j < threads_per_child; j++) {
+ int status = ap_scoreboard_image->servers[i][j].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) {
+ ++idle_thread_count;
+ }
+ if (status >= SERVER_READY && status < SERVER_GRACEFUL) {
+ ++child_threads_active;
+ }
+ }
+ active_thread_count += child_threads_active;
+ if (child_threads_active == threads_per_child) {
+ had_healthy_child = 1;
+ }
+ last_non_dead = i;
+ }
+ else if (free_length < retained->idle_spawn_rate[child_bucket]) {
+ free_slots[free_length++] = i;
+ }
+ }
+ if (*max_daemon_used < last_non_dead + 1) {
+ *max_daemon_used = last_non_dead + 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 if (child_bucket < num_buckets - 1) {
+ /* check for had_healthy_child up to the last child bucket */
+ return;
+ }
+ 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;
+ }
+ }
+
+ AP_DEBUG_ASSERT(retained->active_daemons <= retained->total_daemons
+ && retained->total_daemons <= retained->max_daemon_used
+ && retained->max_daemon_used <= server_limit);
+
+ 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 if there are stopping ones
+ * already (i.e. active_daemons != total_daemons) and not enough
+ * slack space in the scoreboard for a graceful restart.
+ *
+ * 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
+ */
+ int do_kill = (retained->active_daemons == retained->total_daemons
+ || (server_limit - retained->total_daemons >
+ active_daemons_limit));
+ ap_log_error(APLOG_MARK, APLOG_TRACE5, 0, ap_server_conf,
+ "%shutting down one child: "
+ "active %d/%d, total %d/%d/%d, "
+ "idle threads %d, max workers %d",
+ (do_kill) ? "S" : "Not s",
+ retained->active_daemons, active_daemons_limit,
+ retained->total_daemons, retained->max_daemon_used,
+ server_limit, idle_thread_count, max_workers);
+ if (do_kill) {
+ ap_mpm_podx_signal(all_buckets[child_bucket].pod,
+ AP_MPM_PODX_GRACEFUL);
+ }
+ else {
+ /* Wait for dying daemon(s) to exit */
+ }
+ retained->idle_spawn_rate[child_bucket] = 1;
+ }
+ else if (idle_thread_count < min_spare_threads / num_buckets) {
+ if (active_thread_count >= max_workers / num_buckets) {
+ 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 (free_length + retained->active_daemons > active_daemons_limit) {
+ if (retained->active_daemons < active_daemons_limit) {
+ free_length = active_daemons_limit - retained->active_daemons;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf,
+ "server is at active daemons limit, spawning "
+ "of %d children cancelled: active %d/%d, "
+ "total %d/%d/%d, rate %d", free_length,
+ retained->active_daemons, active_daemons_limit,
+ retained->total_daemons, retained->max_daemon_used,
+ server_limit, retained->idle_spawn_rate[child_bucket]);
+ /* reset the spawning rate and prevent its growth below */
+ retained->idle_spawn_rate[child_bucket] = 1;
+ ++retained->hold_off_on_exponential_spawning;
+ free_length = 0;
+ }
+ }
+ 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, retained->active_daemons,
+ retained->total_daemons);
+ }
+ for (i = 0; i < free_length; ++i) {
+ int slot = free_slots[i];
+ if (make_child(ap_server_conf, slot, child_bucket) < 0) {
+ continue;
+ }
+ if (*max_daemon_used < slot + 1) {
+ *max_daemon_used = slot + 1;
+ }
+ }
+ /* 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 = retained->mpm->num_buckets;
+ int max_daemon_used = 0;
+ int successive_kills = 0;
+ 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) {
+ event_note_child_stopped(child_slot, 0, 0);
+
+ if (processed_status == APEXIT_CHILDSICK) {
+ /* resource shortage, minimize the fork rate */
+ retained->idle_spawn_rate[child_slot % num_buckets] = 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,
+ child_slot % num_buckets);
+ --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. If a child is
+ * killed by a signal (faulting) we want to restart it ASAP
+ * though, up to 3 successive faults or we stop this until
+ * a timeout happens again (to avoid the flood of fork()ed
+ * processes that keep being killed early).
+ */
+ if (child_slot < 0 || !APR_PROC_CHECK_SIGNALED(exitwhy)) {
+ continue;
+ }
+ if (++successive_kills >= 3) {
+ if (successive_kills % 10 == 3) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
+ ap_server_conf, APLOGNO(10392)
+ "children are killed successively!");
+ }
+ continue;
+ }
+ ++remaining_children_to_start;
+ }
+ else {
+ successive_kills = 0;
+ }
+
+ 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;
+ }
+
+ max_daemon_used = 0;
+ for (i = 0; i < num_buckets; i++) {
+ perform_idle_server_maintenance(i, &max_daemon_used);
+ }
+ retained->max_daemon_used = max_daemon_used;
+ }
+}
+
+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);
+ 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_stopped);
+
+ 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_stopped);
+
+ 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_stopped);
+
+ active_children = 0;
+ for (index = 0; index < retained->max_daemon_used; ++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_stopped);
+
+ 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 {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00494)
+ "SIGHUP received. Attempting to restart");
+ /* 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_stopped);
+ }
+
+ 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));
+ if (retained->idle_spawn_rate) /* NULL at startup */
+ 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();
+ 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;
+ listener_is_wakeable = 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..b3adb03
--- /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 maintenance
+ * 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 = 0;
+
+/* 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 (slot + 1 > ap_max_daemons_limit) {
+ ap_max_daemons_limit = slot + 1;
+ }
+
+ 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..f405cd2
--- /dev/null
+++ b/server/mpm/mpmt_os2/mpmt_os2_child.c
@@ -0,0 +1,490 @@
+/* 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);
+ apr_pool_tag(pconn, "transaction");
+ 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( &reg_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..e89fdef
--- /dev/null
+++ b/server/mpm/netware/mpm_netware.c
@@ -0,0 +1,1365 @@
+/* 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);
+ apr_pool_tag(pmain, "pmain");
+ 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..b5adb57
--- /dev/null
+++ b/server/mpm/prefork/prefork.c
@@ -0,0 +1,1563 @@
+/* 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 (code == 0) {
+ ap_run_child_stopping(pchild, 0);
+ }
+
+ 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;
+
+#if APR_HAS_THREADS
+static void child_sigmask(sigset_t *new_mask, sigset_t *old_mask)
+{
+#if defined(SIGPROCMASK_SETS_THREAD_MASK)
+ sigprocmask(SIG_SETMASK, new_mask, old_mask);
+#else
+ pthread_sigmask(SIG_SETMASK, new_mask, old_mask);
+#endif
+}
+#endif
+
+static void child_main(int child_num_arg, int child_bucket)
+{
+#if APR_HAS_THREADS
+ apr_thread_t *thd = NULL;
+ sigset_t sig_mask;
+#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 AP_HAS_THREAD_LOCAL
+ if (one_process) {
+ thd = ap_thread_current();
+ }
+ else if ((status = ap_thread_main_create(&thd, pchild))) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(10378)
+ "Couldn't initialize child main thread");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+#elif APR_HAS_THREADS
+ {
+ apr_os_thread_t osthd = apr_os_thread_current();
+ apr_os_thread_put(&thd, &osthd, pchild);
+ }
+#endif
+#if APR_HAS_THREADS
+ ap_assert(thd != NULL);
+#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);
+ }
+
+#if APR_HAS_THREADS
+ /* Save the signal mask and block all the signals from being received by
+ * threads potentially created in child_init() hooks (e.g. mod_watchdog).
+ */
+ child_sigmask(NULL, &sig_mask);
+ {
+ apr_status_t rv;
+ rv = apr_setup_signal_thread();
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(10271)
+ "Couldn't initialize signal thread");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+ }
+#endif /* APR_HAS_THREADS */
+
+ ap_run_child_init(pchild, ap_server_conf);
+
+#if APR_HAS_THREADS
+ /* Restore the original signal mask for this main thread, the only one
+ * that should possibly get interrupted by signals.
+ */
+ child_sigmask(&sig_mask, NULL);
+#endif
+
+ 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 = slot % retained->mpm->num_buckets;
+ 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) {
+#if AP_HAS_THREAD_LOCAL
+ ap_thread_current_after_fork();
+#endif
+
+ 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-conditions in signals
+ * handling when httpd is running 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);
+ }
+
+ 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) < 0) {
+ break;
+ }
+ --number_to_start;
+ }
+}
+
+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_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) {
+ static int bucket_kill_child_record = -1;
+ /* 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) {
+ 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 (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);
+ /* 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);
+ --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->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, p /* == 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..05151a8
--- /dev/null
+++ b/server/mpm/winnt/child.c
@@ -0,0 +1,1306 @@
+/* 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 = 0;
+ 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 = NULL;
+ apr_os_thread_t osthd = NULL;
+ 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;
+
+#if AP_HAS_THREAD_LOCAL
+ if (ap_thread_current_create(&thd, NULL, pchild) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(10376)
+ "Couldn't initialize worker thread, thread locals won't "
+ "be available");
+ osthd = apr_os_thread_current();
+ }
+#else
+ osthd = apr_os_thread_current();
+#endif
+
+ 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;
+ }
+
+ if (osthd) {
+ 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);
+
+#if AP_HAS_THREAD_LOCAL
+ if (!osthd) {
+ apr_pool_destroy(apr_thread_pool_get(thd));
+ }
+#endif
+
+ 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..1b8962e
--- /dev/null
+++ b/server/mpm/winnt/mpm_winnt.c
@@ -0,0 +1,1783 @@
+/* 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 available 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);
+ apr_pool_tag(ptemp, "create_process");
+
+ /* 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 = ap_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] = ap_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
+ * condition.
+ *
+ * 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 implicit 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 privileges; 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
+ */
+ 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(NULL, 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);
+ 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(NULL, 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);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Create the start mutex, as an unnamed object for security.
+ * The 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..cd49ee6
--- /dev/null
+++ b/server/mpm/winnt/nt_eventlog.c
@@ -0,0 +1,172 @@
+/* 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);
+ apr_pool_tag(p, "service_stderr_thread");
+
+ 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(&regkey, 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..2e473cf
--- /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 Administrator?");
+ 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 Administrator?");
+ 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 Administrator?");
+ 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 Administrator?");
+ 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..7b572bd
--- /dev/null
+++ b/server/mpm/worker/worker.c
@@ -0,0 +1,2455 @@
+/* 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 */
+ }
+
+ ap_run_child_stopping(pchild, mode == ST_GRACEFUL);
+}
+
+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 (terminate_mode == ST_INIT) {
+ ap_run_child_stopping(pchild, 0);
+ }
+
+ 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.
+ */
+ apr_signal(LISTENER_SIGNAL, dummy_signal_handler);
+ unblock_signal(LISTENER_SIGNAL);
+
+ /* 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
+ apr_signal(WORKER_SIGNAL, dummy_signal_handler);
+ unblock_signal(WORKER_SIGNAL);
+#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 = ap_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)
+ "ap_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 explicitly
+ * 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 = ap_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)
+ "ap_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");
+
+#if AP_HAS_THREAD_LOCAL
+ if (!one_process) {
+ apr_thread_t *thd = NULL;
+ if ((rv = ap_thread_main_create(&thd, pchild))) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(10375)
+ "Couldn't initialize child main thread");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+ }
+#endif
+
+ /* 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" (implicitly) 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 = ap_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)
+ "ap_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
+ */
+ apr_signal(SIGTERM, dummy_signal_handler);
+ unblock_signal(SIGTERM);
+ /* 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) {
+#if AP_HAS_THREAD_LOCAL
+ ap_thread_current_after_fork();
+#endif
+
+ 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;
+ 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 = retained->mpm->num_buckets;
+ int idle_thread_count;
+ 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;
+ int i, j;
+
+ /* 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 any_dying_threads = 0;
+ int any_dead_threads = 0;
+ int all_dead_threads = 1;
+ int child_threads_active = 0;
+
+ if (num_buckets > 1 && (i % num_buckets) != child_bucket) {
+ /* We only care about child_bucket in this call */
+ continue;
+ }
+ 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++) {
+ int status = ap_scoreboard_image->servers[i][j].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) {
+ ++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) {
+ ++total_non_dead;
+ }
+ if (ps->pid != 0) {
+ last_non_dead = i;
+ }
+ }
+
+ retained->max_daemons_limit = last_non_dead + 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 if (child_bucket < num_buckets - 1) {
+ /* check for had_healthy_child up to the last child bucket */
+ return;
+ }
+ 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;
+ }
+ }
+
+ 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 >= max_workers / num_buckets) {
+ /* 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 = retained->mpm->num_buckets;
+ int successive_kills = 0;
+ 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[child_slot % num_buckets] = 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,
+ child_slot % num_buckets);
+ --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. If a child is
+ * killed by a signal (faulting) we want to restart it ASAP
+ * though, up to 3 successive faults or we stop this until
+ * a timeout happens again (to avoid the flood of fork()ed
+ * processes that keep being killed early).
+ */
+ if (child_slot < 0 || !APR_PROC_CHECK_SIGNALED(exitwhy)) {
+ continue;
+ }
+ if (++successive_kills >= 3) {
+ if (successive_kills % 10 == 3) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
+ ap_server_conf, APLOGNO(10393)
+ "children are killed successively!");
+ }
+ continue;
+ }
+ ++remaining_children_to_start;
+ }
+ else {
+ successive_kills = 0;
+ }
+
+ 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);
+ }
+ }
+}
+
+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);
+ 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));
+ if (retained->idle_spawn_rate) /* NULL at startup */
+ 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->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..9bcd760
--- /dev/null
+++ b/server/mpm_common.c
@@ -0,0 +1,578 @@
+/* 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) \
+ APR_HOOK_LINK(child_stopping)
+
+#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))
+AP_IMPLEMENT_HOOK_VOID(child_stopping,
+ (apr_pool_t *pchild, int graceful),
+ (pchild, graceful))
+
+/* 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..3697ca7
--- /dev/null
+++ b/server/mpm_fdqueue.c
@@ -0,0 +1,534 @@
+/* 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 (queue->terminated) {
+ return APR_EOF;
+ }
+
+ 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..1047f88
--- /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;
+ volatile 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..8c4d233
--- /dev/null
+++ b/server/mpm_unix.c
@@ -0,0 +1,1108 @@
+/* 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;
+ }
+ apr_pool_tag(p, "dummy_connection");
+
+ /* 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 && ap_cstr_casecmp(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 && ap_cstr_casecmp(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 administrators 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..6f9540a
--- /dev/null
+++ b/server/protocol.c
@@ -0,0 +1,2605 @@
+/* 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) {
+ ap_log_data(APLOG_MARK, APLOG_DEBUG, ap_server_conf,
+ "NULL bytes in header", *s, *read, 0);
+ *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
+ && !ap_cstr_casecmp(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;
+ if (r->parsed_uri.path) {
+ r->uri = r->parsed_uri.path;
+ }
+ else if (r->method_number == M_OPTIONS) {
+ r->uri = apr_pstrdup(r->pool, "*");
+ }
+ else {
+ r->uri = 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)
+{
+ 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));
+
+ /* Set r->request_time before any logging, mod_unique_id needs it. */
+ r->request_time = apr_time_now();
+
+ 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));
+ }
+
+ return 1;
+}
+
+AP_DECLARE(int) ap_parse_request_line(request_rec *r)
+{
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config);
+ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+ 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;
+ apr_size_t len = 0;
+ char *uri, *ll;
+
+ 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 = "";
+ 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 = "";
+ 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);
+ if (r->status == HTTP_OK
+ && (r->parsed_uri.path != NULL)
+ && (r->parsed_uri.path[0] != '/')
+ && (r->method_number != M_OPTIONS
+ || strcmp(r->parsed_uri.path, "*") != 0)) {
+ /* Invalid request-target per RFC 7230 section 5.3 */
+ r->status = HTTP_BAD_REQUEST;
+ }
+
+ /* 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;
+}
+
+AP_DECLARE(int) ap_check_request_header(request_rec *r)
+{
+ core_server_config *conf;
+ int strict_host_check;
+ const char *expect;
+ int access_status;
+
+ conf = ap_get_core_module_config(r->server->module_config);
+
+ /* update what we think the virtual host is based on the headers we've
+ * now read. may update status.
+ */
+ strict_host_check = (conf->strict_host_check == AP_CORE_CONFIG_ON);
+ access_status = ap_update_vhost_from_headers_ex(r, strict_host_check);
+ if (strict_host_check && access_status != HTTP_OK) {
+ if (r->server == ap_server_conf) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10156)
+ "Requested hostname '%s' did not match any ServerName/ServerAlias "
+ "in the global server configuration ", r->hostname);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10157)
+ "Requested hostname '%s' did not match any ServerName/ServerAlias "
+ "in the matching virtual host (default vhost for "
+ "current connection is %s:%u)",
+ r->hostname, r->server->defn_name, r->server->defn_line_number);
+ }
+ r->status = access_status;
+ }
+ if (r->status != HTTP_OK) {
+ return 0;
+ }
+
+ 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.
+ */
+ 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);
+ r->status = HTTP_BAD_REQUEST;
+ return 0;
+ }
+
+ 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 (ap_cstr_casecmp(expect, "100-continue") == 0) {
+ r->expecting_100 = 1;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00570)
+ "client sent an unrecognized expectation value "
+ "of Expect: %s", expect);
+ r->status = HTTP_EXPECTATION_FAILED;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+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);
+}
+
+AP_DECLARE(request_rec *) ap_create_request(conn_rec *conn)
+{
+ request_rec *r;
+ apr_pool_t *p;
+
+ 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->header_only = 0;
+ 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;
+
+ return r;
+}
+
+/* Apply the server's timeout/config to the connection/request. */
+static void apply_server_config(request_rec *r)
+{
+ apr_socket_t *csd;
+
+ csd = ap_get_conn_socket(r->connection);
+ apr_socket_timeout_set(csd, r->server->timeout);
+
+ r->per_dir_config = r->server->lookup_defaults;
+}
+
+request_rec *ap_read_request(conn_rec *conn)
+{
+ int access_status;
+ apr_bucket_brigade *tmp_bb;
+
+ request_rec *r = ap_create_request(conn);
+
+ tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ conn->keepalive = AP_CONN_UNKNOWN;
+
+ ap_run_pre_read_request(r, conn);
+
+ /* Get the request... */
+ if (!read_request_line(r, tmp_bb) || !ap_parse_request_line(r)) {
+ apr_brigade_cleanup(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;
+ goto die_unusable_input;
+
+ case HTTP_REQUEST_TIME_OUT:
+ /* Just log, no further action on this connection. */
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, NULL);
+ if (!r->connection->keepalives)
+ ap_run_log_transaction(r);
+ break;
+ }
+ /* Not worth dying with. */
+ conn->keepalive = AP_CONN_CLOSE;
+ apr_pool_destroy(r->pool);
+ goto ignore;
+ }
+ apr_brigade_cleanup(tmp_bb);
+
+ /* 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.
+ */
+ apply_server_config(r);
+
+ if (!r->assbackwards) {
+ const char *tenc, *clen;
+
+ ap_get_mime_headers_core(r, tmp_bb);
+ apr_brigade_cleanup(tmp_bb);
+ if (r->status != HTTP_OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00567)
+ "request failed: error reading the headers");
+ access_status = r->status;
+ goto die_unusable_input;
+ }
+
+ clen = apr_table_get(r->headers_in, "Content-Length");
+ if (clen) {
+ apr_off_t cl;
+
+ if (!ap_parse_strict_length(&cl, clen)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10242)
+ "client sent invalid Content-Length "
+ "(%s): %s", clen, r->uri);
+ access_status = HTTP_BAD_REQUEST;
+ goto die_unusable_input;
+ }
+ }
+
+ tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ if (tenc) {
+ /* https://tools.ietf.org/html/rfc7230
+ * 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 (!ap_is_chunked(r->pool, tenc)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02539)
+ "client sent unknown Transfer-Encoding "
+ "(%s): %s", tenc, r->uri);
+ access_status = HTTP_BAD_REQUEST;
+ goto die_unusable_input;
+ }
+
+ /* https://tools.ietf.org/html/rfc7230
+ * 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".
+ */
+ if (clen) {
+ apr_table_unset(r->headers_in, "Content-Length");
+
+ /* Don't reuse this connection anyway to avoid confusion with
+ * intermediaries and request/reponse spltting.
+ */
+ conn->keepalive = AP_CONN_CLOSE;
+ }
+ }
+ }
+
+ /*
+ * 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);
+
+ /* Validate Host/Expect headers and select vhost. */
+ if (!ap_check_request_header(r)) {
+ /* we may have switched to another server still */
+ apply_server_config(r);
+ access_status = r->status;
+ goto die_before_hooks;
+ }
+
+ /* we may have switched to another server */
+ apply_server_config(r);
+
+ if ((access_status = ap_post_read_request(r))) {
+ goto die;
+ }
+
+ AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
+ (char *)r->uri, (char *)r->server->defn_name,
+ r->status);
+ return r;
+
+ /* Everything falls through on failure */
+
+die_unusable_input:
+ /* Input filters are in an undeterminate state, cleanup (including
+ * CORE_IN's socket) such that any further attempt to read is EOF.
+ */
+ {
+ ap_filter_t *f = conn->input_filters;
+ while (f) {
+ if (f->frec == ap_core_input_filter_handle) {
+ core_net_rec *net = f->ctx;
+ apr_brigade_cleanup(net->in_ctx->b);
+ break;
+ }
+ ap_remove_input_filter(f);
+ f = f->next;
+ }
+ conn->input_filters = r->input_filters = f;
+ conn->keepalive = AP_CONN_CLOSE;
+ }
+
+die_before_hooks:
+ /* First call to ap_die() (non recursive) */
+ r->status = HTTP_OK;
+
+die:
+ ap_die(access_status, r);
+
+ /* ap_die() sent the response through the output filters, we must now
+ * end the request with an EOR bucket for stream/pipeline accounting.
+ */
+ {
+ apr_bucket_brigade *eor_bb;
+ eor_bb = apr_brigade_create(conn->pool, conn->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(eor_bb,
+ ap_bucket_eor_create(conn->bucket_alloc, r));
+ ap_pass_brigade(conn->output_filters, eor_bb);
+ apr_brigade_cleanup(eor_bb);
+ }
+
+ignore:
+ r = NULL;
+ AP_READ_REQUEST_FAILURE((uintptr_t)r);
+ return NULL;
+}
+
+AP_DECLARE(int) ap_post_read_request(request_rec *r)
+{
+ int status;
+
+ if ((status = ap_run_post_read_request(r))) {
+ return status;
+ }
+
+ /* Enforce http(s) only scheme for non-forward-proxy requests */
+ if (!r->proxyreq
+ && r->parsed_uri.scheme
+ && (ap_cstr_casecmpn(r->parsed_uri.scheme, "http", 4) != 0
+ || (r->parsed_uri.scheme[4] != '\0'
+ && (apr_tolower(r->parsed_uri.scheme[4]) != 's'
+ || r->parsed_uri.scheme[5] != '\0')))) {
+ return HTTP_BAD_REQUEST;
+ }
+
+ return OK;
+}
+
+/* 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, int status)
+{
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ if (status != OK) {
+ b = ap_bucket_error_create(status, NULL, r->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ ap_pass_brigade(r->output_filters, bb);
+ apr_brigade_cleanup(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, OK);
+ }
+}
+
+/* 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)
+{
+ int status = ap_discard_request_body(r);
+
+ /* tell the filter chain there is no more content coming */
+ if (!r->eos_sent) {
+ end_output_stream(r, status);
+ }
+}
+
+/*
+ * 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)) || ap_cstr_casecmp(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 (ap_cstr_casecmp(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 (nbyte < 0)
+ return -1;
+
+ 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)
+{
+ int 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) {
+ va_end(va);
+ 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 *response_line = NULL;
+ const char *status_line;
+ 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) {
+ if (!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 = r->status_line;
+ if (status_line == NULL) {
+ status_line = ap_get_status_line_ex(r->pool, r->status);
+ }
+ response_line = apr_pstrcat(r->pool,
+ AP_SERVER_PROTOCOL " ", status_line, CRLF,
+ NULL);
+ ap_xlate_proto_to_ascii(response_line, strlen(response_line));
+
+ x.f = r->connection->output_filters;
+ x.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ ap_fputs(x.f, x.bb, response_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 (maybe -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..f54fb5e
--- /dev/null
+++ b/server/provider.c
@@ -0,0 +1,198 @@
+/* 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 = NULL;
+ 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) {
+ goto out;
+ }
+
+ provider_group_hash = apr_hash_get(global_providers_names, provider_group,
+ APR_HASH_KEY_STRING);
+
+ if (provider_group_hash == NULL) {
+ goto out;
+ }
+
+ h = apr_hash_get(provider_group_hash, provider_version, APR_HASH_KEY_STRING);
+
+ if (h == NULL) {
+ goto out;
+ }
+
+ ret = apr_array_make(pool, apr_hash_count(h), sizeof(ap_list_provider_names_t));
+ 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);
+ }
+
+out:
+ if (ret == NULL) {
+ ret = apr_array_make(pool, 1, sizeof(ap_list_provider_names_t));
+ }
+ 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..5599b2c
--- /dev/null
+++ b/server/request.c
@@ -0,0 +1,2572 @@
+/* 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(pre_translate_name)
+ 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,pre_translate_name,
+ (request_rec *r), (r), DECLINED)
+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;
+}
+
+static int walk_location_and_if(request_rec *r)
+{
+ int access_status;
+ core_dir_config *d;
+
+ 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;
+
+ return OK;
+}
+
+/* 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 access_status = DECLINED;
+ int file_req = (r->main && r->filename);
+ core_server_config *sconf =
+ ap_get_core_module_config(r->server->module_config);
+ unsigned int normalize_flags;
+
+ normalize_flags = AP_NORMALIZE_NOT_ABOVE_ROOT;
+ if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) {
+ normalize_flags |= AP_NORMALIZE_MERGE_SLASHES;
+ }
+ if (file_req) {
+ /* File subrequests can have a relative path. */
+ normalize_flags |= AP_NORMALIZE_ALLOW_RELATIVE;
+ }
+
+ if (r->parsed_uri.path) {
+ /* Normalize: remove /./ and shrink /../ segments, plus
+ * decode unreserved chars (first time only to avoid
+ * double decoding after ap_unescape_url() below).
+ */
+ if (!ap_normalize_path(r->parsed_uri.path,
+ normalize_flags |
+ AP_NORMALIZE_DECODE_UNRESERVED)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10244)
+ "invalid URI path (%s)", r->unparsed_uri);
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ /* 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 (pre_)translate_name kill the request.
+ */
+ if (!file_req) {
+ ap_conf_vector_t *per_dir_config = r->per_dir_config;
+
+ if ((access_status = walk_location_and_if(r))) {
+ return access_status;
+ }
+
+ /* Let pre_translate_name hooks work with non-decoded URIs, and
+ * eventually prevent further URI transformations (return DONE).
+ */
+ access_status = ap_run_pre_translate_name(r);
+ if (ap_is_HTTP_ERROR(access_status)) {
+ return access_status;
+ }
+
+ /* Throw away pre_trans only merging */
+ r->per_dir_config = per_dir_config;
+ }
+
+ /* Ignore URL unescaping for translated URIs already */
+ if (access_status != DONE && r->parsed_uri.path) {
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ /* Unreserved chars were already decoded by ap_normalize_path() */
+ unsigned int unescape_flags = AP_UNESCAPE_URL_KEEP_UNRESERVED;
+ if (!d->allow_encoded_slashes) {
+ unescape_flags |= AP_UNESCAPE_URL_FORBID_SLASHES;
+ }
+ else if (!d->decode_encoded_slashes) {
+ unescape_flags |= AP_UNESCAPE_URL_KEEP_SLASHES;
+ }
+ access_status = ap_unescape_url_ex(r->parsed_uri.path, unescape_flags);
+ 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 path (%s), "
+ "returning 404", r->unparsed_uri);
+ }
+ }
+ return access_status;
+ }
+
+ if (d->allow_encoded_slashes && d->decode_encoded_slashes) {
+ /* Decoding slashes might have created new // or /./ or /../
+ * segments (e.g. "/.%2F/"), so re-normalize.
+ */
+ ap_normalize_path(r->parsed_uri.path, normalize_flags);
+ }
+ }
+
+ /* Same, translate_name is not suited for file subrequests */
+ if (!file_req) {
+ if ((access_status = walk_location_and_if(r))) {
+ return access_status;
+ }
+
+ 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 = walk_location_and_if(r))) {
+ return access_status;
+ }
+
+ 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);
+ apr_pool_tag(rxpool, "directory_walk_rxpool");
+ }
+ 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 = apr_pstrdup(r->pool, 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);
+
+ /*
+ * When merge_slashes is set to AP_CORE_CONFIG_OFF the slashes in r->uri
+ * have not been merged. But for Location walks we always go with merged
+ * slashes no matter what merge_slashes is set to.
+ */
+ if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) {
+ 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 = apr_pstrdup(r->pool, 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);
+ apr_pool_tag(rxpool, "location_walk_rxpool");
+ }
+ 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);
+ apr_pool_tag(rxpool, "file_walk_rxpool");
+ }
+ 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..12dd56a
--- /dev/null
+++ b/server/scoreboard.c
@@ -0,0 +1,713 @@
+/* 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_set_conn_count(ap_sb_handle_t *sb, request_rec *r,
+ unsigned short conn_count)
+{
+ worker_score *ws;
+
+ if (!sb)
+ return;
+
+ ws = &ap_scoreboard_image->servers[sb->child_num][sb->thread_num];
+ ws->conn_count = conn_count;
+}
+
+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 && 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/ssl.c b/server/ssl.c
new file mode 100644
index 0000000..edf958b
--- /dev/null
+++ b/server/ssl.c
@@ -0,0 +1,285 @@
+/* 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.
+ */
+
+/*
+ * ssl.c --- routines for SSL/TLS server infrastructure.
+ *
+ */
+
+#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_connection.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_main.h"
+#include "http_ssl.h"
+#include "http_log.h" /* For errors detected in basic auth common
+ * support code... */
+#include "mod_core.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(ssl_conn_is_ssl)
+ APR_HOOK_LINK(ssl_var_lookup)
+ APR_HOOK_LINK(ssl_add_cert_files)
+ APR_HOOK_LINK(ssl_add_fallback_cert_files)
+ APR_HOOK_LINK(ssl_answer_challenge)
+ APR_HOOK_LINK(ssl_ocsp_prime_hook)
+ APR_HOOK_LINK(ssl_ocsp_get_resp_hook)
+ APR_HOOK_LINK(ssl_bind_outgoing)
+)
+
+APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
+static APR_OPTIONAL_FN_TYPE(ssl_is_https) *module_ssl_is_https;
+APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *));
+static APR_OPTIONAL_FN_TYPE(ssl_proxy_enable) *module_ssl_proxy_enable;
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *));
+static APR_OPTIONAL_FN_TYPE(ssl_engine_disable) *module_ssl_engine_disable;
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_set, (conn_rec *,
+ ap_conf_vector_t *,
+ int proxy, int enable));
+static APR_OPTIONAL_FN_TYPE(ssl_engine_set) *module_ssl_engine_set;
+
+
+static int ssl_is_https(conn_rec *c)
+{
+ /* Someone retrieved the optional function., not knowing about the
+ * new API. We redirect them to what they should have invoked. */
+ return ap_ssl_conn_is_ssl(c);
+}
+
+AP_DECLARE(int) ap_ssl_conn_is_ssl(conn_rec *c)
+{
+ int r = (ap_run_ssl_conn_is_ssl(c) == OK);
+ if (r == 0 && module_ssl_is_https) {
+ r = module_ssl_is_https(c);
+ }
+ return r;
+}
+
+static int ssl_engine_set(conn_rec *c,
+ ap_conf_vector_t *per_dir_config,
+ int proxy, int enable)
+{
+ if (proxy) {
+ return ap_ssl_bind_outgoing(c, per_dir_config, enable) == OK;
+ }
+ else if (module_ssl_engine_set) {
+ return module_ssl_engine_set(c, per_dir_config, 0, enable);
+ }
+ else if (enable && module_ssl_proxy_enable) {
+ return module_ssl_proxy_enable(c);
+ }
+ else if (!enable && module_ssl_engine_disable) {
+ return module_ssl_engine_disable(c);
+ }
+ return 0;
+}
+
+static int ssl_proxy_enable(conn_rec *c)
+{
+ return ap_ssl_bind_outgoing(c, NULL, 1);
+}
+
+static int ssl_engine_disable(conn_rec *c)
+{
+ return ap_ssl_bind_outgoing(c, NULL, 0);
+}
+
+AP_DECLARE(int) ap_ssl_bind_outgoing(conn_rec *c, struct ap_conf_vector_t *dir_conf,
+ int enable_ssl)
+{
+ int rv, enabled = 0;
+
+ c->outgoing = 1;
+ rv = ap_run_ssl_bind_outgoing(c, dir_conf, enable_ssl);
+ enabled = (rv == OK);
+ if (enable_ssl && !enabled) {
+ /* the hooks did not take over. Is there an old skool optional that will? */
+ if (module_ssl_engine_set) {
+ enabled = module_ssl_engine_set(c, dir_conf, 1, 1);
+ }
+ else if (module_ssl_proxy_enable) {
+ enabled = module_ssl_proxy_enable(c);
+ }
+ }
+ else {
+ /* !enable_ssl || enabled
+ * any existing optional funcs need to not enable here */
+ if (module_ssl_engine_set) {
+ module_ssl_engine_set(c, dir_conf, 1, 0);
+ }
+ else if (module_ssl_engine_disable) {
+ module_ssl_engine_disable(c);
+ }
+ }
+ if (enable_ssl && !enabled) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0,
+ c, APLOGNO(01961) " failed to enable ssl support "
+ "[Hint: if using mod_ssl, see SSLProxyEngine]");
+ return DECLINED;
+ }
+ return OK;
+}
+
+AP_DECLARE(int) ap_ssl_has_outgoing_handlers(void)
+{
+ apr_array_header_t *hooks = ap_hook_get_ssl_bind_outgoing();
+ return (hooks && hooks->nelts > 0)
+ || module_ssl_engine_set || module_ssl_proxy_enable;
+}
+
+APR_DECLARE_OPTIONAL_FN(const char *, ssl_var_lookup,
+ (apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r,
+ const char *name));
+static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *module_ssl_var_lookup;
+
+static const char *ssl_var_lookup(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r,
+ const char *name)
+{
+ /* Someone retrieved the optional function., not knowing about the
+ * new API. We redirect them to what they should have invoked. */
+ return ap_ssl_var_lookup(p, s, c, r, name);
+}
+
+AP_DECLARE(const char *) ap_ssl_var_lookup(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r,
+ const char *name)
+{
+ const char *val = ap_run_ssl_var_lookup(p, s, c, r, name);
+ if (val == NULL && module_ssl_var_lookup) {
+ val = module_ssl_var_lookup(p, s, c, r, name);
+ }
+ return val;
+}
+
+AP_DECLARE(void) ap_setup_ssl_optional_fns(apr_pool_t *pool)
+{
+ /* Run as core's very early 'post config' hook, check for any already
+ * installed optional functions related to SSL and save them. Install
+ * our own instances that invoke the new hooks. */
+ APR_OPTIONAL_FN_TYPE(ssl_is_https) *fn_is_https;
+ APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *fn_ssl_var_lookup;
+
+ fn_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
+ module_ssl_is_https = (fn_is_https
+ && fn_is_https != ssl_is_https)? fn_is_https : NULL;
+ APR_REGISTER_OPTIONAL_FN(ssl_is_https);
+
+ fn_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
+ module_ssl_var_lookup = (fn_ssl_var_lookup
+ && fn_ssl_var_lookup != ssl_var_lookup)? fn_ssl_var_lookup : NULL;
+ APR_REGISTER_OPTIONAL_FN(ssl_var_lookup);
+
+ module_ssl_proxy_enable = APR_RETRIEVE_OPTIONAL_FN(ssl_proxy_enable);
+ APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable);
+ module_ssl_engine_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);
+ APR_REGISTER_OPTIONAL_FN(ssl_engine_disable);
+ module_ssl_engine_set = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_set);
+ APR_REGISTER_OPTIONAL_FN(ssl_engine_set);
+}
+
+AP_DECLARE(apr_status_t) ap_ssl_add_cert_files(server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files,
+ apr_array_header_t *key_files)
+{
+ int rv = ap_run_ssl_add_cert_files(s, p, cert_files, key_files);
+ return (rv == OK || rv == DECLINED)? APR_SUCCESS : APR_EGENERAL;
+}
+
+AP_DECLARE(apr_status_t) ap_ssl_add_fallback_cert_files(server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files,
+ apr_array_header_t *key_files)
+{
+ int rv = ap_run_ssl_add_fallback_cert_files(s, p, cert_files, key_files);
+ return (rv == OK || rv == DECLINED)? APR_SUCCESS : APR_EGENERAL;
+}
+
+AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name,
+ const char **pcert_pem, const char **pkey_pem)
+{
+ return (ap_run_ssl_answer_challenge(c, server_name, pcert_pem, pkey_pem) == OK);
+}
+
+AP_DECLARE(apr_status_t) ap_ssl_ocsp_prime(server_rec *s, apr_pool_t *p,
+ const char *id, apr_size_t id_len,
+ const char *pem)
+{
+ int rv = ap_run_ssl_ocsp_prime_hook(s, p, id, id_len, pem);
+ return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL);
+}
+
+AP_DECLARE(apr_status_t) ap_ssl_ocsp_get_resp(server_rec *s, conn_rec *c,
+ const char *id, apr_size_t id_len,
+ ap_ssl_ocsp_copy_resp *cb, void *userdata)
+{
+ int rv = ap_run_ssl_ocsp_get_resp_hook(s, c, id, id_len, cb, userdata);
+ return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL);
+}
+
+AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_conn_is_ssl,
+ (conn_rec *c), (c), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,ssl_var_lookup,
+ (apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *name),
+ (p, s, c, r, name), NULL)
+AP_IMPLEMENT_HOOK_RUN_ALL(int, ssl_add_cert_files,
+ (server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files, apr_array_header_t *key_files),
+ (s, p, cert_files, key_files), OK, DECLINED)
+AP_IMPLEMENT_HOOK_RUN_ALL(int, ssl_add_fallback_cert_files,
+ (server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files, apr_array_header_t *key_files),
+ (s, p, cert_files, key_files), OK, DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_answer_challenge,
+ (conn_rec *c, const char *server_name, const char **pcert_pem, const char **pkey_pem),
+ (c, server_name, pcert_pem, pkey_pem), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_prime_hook,
+ (server_rec *s, apr_pool_t *p, const char *id, apr_size_t id_len, const char *pem),
+ (s, p, id, id_len, pem), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_get_resp_hook,
+ (server_rec *s, conn_rec *c, const char *id, apr_size_t id_len,
+ ap_ssl_ocsp_copy_resp *cb, void *userdata),
+ (s, c, id, id_len, cb, userdata), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,ssl_bind_outgoing,(conn_rec *c, ap_conf_vector_t *dir_conf, int require_ssl),
+ (c, dir_conf, require_ssl), DECLINED)
diff --git a/server/util.c b/server/util.c
new file mode 100644
index 0000000..2015e43
--- /dev/null
+++ b/server/util.c
@@ -0,0 +1,3789 @@
+/* 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 "apr_fnmatch.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"
+
+/* Win32/NetWare/OS2 need to check for both forward and back slashes
+ * in ap_normalize_path() 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
+
+/* maximum nesting level for config directories */
+#ifndef AP_MAX_FNMATCH_DIR_DEPTH
+#define AP_MAX_FNMATCH_DIR_DEPTH (128)
+#endif
+
+/*
+ * 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)
+{
+ apr_size_t x, y;
+
+ for (x = 0, y = 0; expected[y]; ++y, ++x) {
+ 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 (!str[x])
+ 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)
+{
+ apr_size_t 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)
+{
+ for (; *str; str++)
+ if ((*str == '*') || (*str == '?'))
+ 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;
+}
+
+/* Forward declare */
+static char x2c(const char *what);
+
+#define IS_SLASH_OR_NUL(s) (s == '\0' || IS_SLASH(s))
+
+/*
+ * Inspired by mod_jk's jk_servlet_normalize().
+ */
+AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags)
+{
+ int ret = 1;
+ apr_size_t l = 1, w = 1, n;
+ int decode_unreserved = (flags & AP_NORMALIZE_DECODE_UNRESERVED) != 0;
+
+ if (!IS_SLASH(path[0])) {
+ /* Besides "OPTIONS *", a request-target should start with '/'
+ * per RFC 7230 section 5.3, so anything else is invalid.
+ */
+ if (path[0] == '*' && path[1] == '\0') {
+ return 1;
+ }
+ /* However, AP_NORMALIZE_ALLOW_RELATIVE can be used to bypass
+ * this restriction (e.g. for subrequest file lookups).
+ */
+ if (!(flags & AP_NORMALIZE_ALLOW_RELATIVE) || path[0] == '\0') {
+ return 0;
+ }
+
+ l = w = 0;
+ }
+
+ while (path[l] != '\0') {
+ /* RFC-3986 section 2.3:
+ * For consistency, percent-encoded octets in the ranges of
+ * ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
+ * period (%2E), underscore (%5F), or tilde (%7E) should [...]
+ * be decoded to their corresponding unreserved characters by
+ * URI normalizers.
+ */
+ if (decode_unreserved && path[l] == '%') {
+ if (apr_isxdigit(path[l + 1]) && apr_isxdigit(path[l + 2])) {
+ const char c = x2c(&path[l + 1]);
+ if (TEST_CHAR(c, T_URI_UNRESERVED)) {
+ /* Replace last char and fall through as the current
+ * read position */
+ l += 2;
+ path[l] = c;
+ }
+ }
+ else {
+ /* Invalid encoding */
+ ret = 0;
+ }
+ }
+
+ if (w == 0 || IS_SLASH(path[w - 1])) {
+ /* Collapse ///// sequences to / */
+ if ((flags & AP_NORMALIZE_MERGE_SLASHES) && IS_SLASH(path[l])) {
+ do {
+ l++;
+ } while (IS_SLASH(path[l]));
+ continue;
+ }
+
+ if (path[l] == '.') {
+ /* Remove /./ segments */
+ if (IS_SLASH_OR_NUL(path[l + 1])) {
+ l++;
+ if (path[l]) {
+ l++;
+ }
+ continue;
+ }
+
+ /* Remove /xx/../ segments (or /xx/.%2e/ when
+ * AP_NORMALIZE_DECODE_UNRESERVED is set since we
+ * decoded only the first dot above).
+ */
+ n = l + 1;
+ if ((path[n] == '.' || (decode_unreserved
+ && path[n] == '%'
+ && path[++n] == '2'
+ && (path[++n] == 'e'
+ || path[n] == 'E')))
+ && IS_SLASH_OR_NUL(path[n + 1])) {
+ /* Wind w back to remove the previous segment */
+ if (w > 1) {
+ do {
+ w--;
+ } while (w && !IS_SLASH(path[w - 1]));
+ }
+ else {
+ /* Already at root, ignore and return a failure
+ * if asked to.
+ */
+ if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) {
+ ret = 0;
+ }
+ }
+
+ /* Move l forward to the next segment */
+ l = n + 1;
+ if (path[l]) {
+ l++;
+ }
+ continue;
+ }
+ }
+ }
+
+ path[w++] = path[l++];
+ }
+ path[w] = '\0';
+
+ return ret;
+}
+
+/*
+ * Parse .. so we don't compromise security
+ */
+AP_DECLARE(void) ap_getparents(char *name)
+{
+ if (!ap_normalize_path(name, AP_NORMALIZE_NOT_ABOVE_ROOT |
+ AP_NORMALIZE_ALLOW_RELATIVE)) {
+ name[0] = '\0';
+ }
+}
+
+AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path)
+{
+
+ char *d, *s;
+
+ if (!*name) {
+ return;
+ }
+
+ s = d = name;
+
+#ifdef HAVE_UNC_PATHS
+ /* Check for UNC names. Leave leading two slashes. */
+ if (is_fs_path && s[0] == '/' && s[1] == '/')
+ *d++ = *s++;
+#endif
+
+ while (*s) {
+ if ((*d++ = *s) == '/') {
+ do {
+ ++s;
+ } while (*s == '/');
+ }
+ else {
+ ++s;
+ }
+ }
+ *d = '\0';
+}
+
+AP_DECLARE(void) ap_no2slash(char *name)
+{
+ ap_no2slash_ex(name, 1);
+}
+
+/*
+ * 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)
+ ap_cstr_casecmp(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 (!ap_cstr_casecmpn((const char *)start_token, (const char *)tok,
+ s - start_token)) {
+ return 1;
+ }
+ if (!*s) {
+ return 0;
+ }
+ }
+}
+
+static const char *find_last_token(apr_pool_t *p, const char *line,
+ const char *tok)
+{
+ int llen, tlen, lidx;
+
+ if (!line)
+ return NULL;
+
+ llen = strlen(line);
+ tlen = strlen(tok);
+ lidx = llen - tlen;
+
+ if (lidx < 0 ||
+ (lidx > 0 && !(apr_isspace(line[lidx - 1]) || line[lidx - 1] == ',')))
+ return NULL;
+
+ if (ap_cstr_casecmpn(&line[lidx], tok, tlen) == 0) {
+ return &line[lidx];
+ }
+ return NULL;
+}
+
+AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line,
+ const char *tok)
+{
+ return find_last_token(p, line, tok) != NULL;
+}
+
+AP_DECLARE(int) ap_is_chunked(apr_pool_t *p, const char *line)
+{
+ const char *s;
+
+ if (!line)
+ return 0;
+ if (!ap_cstr_casecmp(line, "chunked")) {
+ return 1;
+ }
+
+ s = find_last_token(p, line, "chunked");
+
+ if (!s) return 0;
+
+ /* eat spaces right-to-left to see what precedes "chunked" */
+ while (--s > line) {
+ if (*s != ' ') break;
+ }
+
+ /* found delim, or leading ws (input wasn't parsed by httpd as a header) */
+ if (*s == ',' || *s == ' ') {
+ return 1;
+ }
+ return 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,
+ unsigned int flags)
+{
+ const int keep_slashes = (flags & AP_UNESCAPE_URL_KEEP_SLASHES) != 0,
+ forbid_slashes = (flags & AP_UNESCAPE_URL_FORBID_SLASHES) != 0,
+ keep_unreserved = (flags & AP_UNESCAPE_URL_KEEP_UNRESERVED) != 0;
+ 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_slashes && IS_SLASH(decoded))
+ || (forbid && ap_strchr_c(forbid, decoded))) {
+ badpath = 1;
+ *x = decoded;
+ y += 2;
+ }
+ else if ((keep_unreserved && TEST_CHAR(decoded,
+ T_URI_UNRESERVED))
+ || (keep_slashes && IS_SLASH(decoded))
+ || (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, 0);
+}
+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, 0);
+ } else {
+ /* reserve (do not decode) encoded slashes */
+ return unescape_url(url, NULL, SLASHES, 0);
+ }
+}
+AP_DECLARE(int) ap_unescape_url_ex(char *url, unsigned int flags)
+{
+ return unescape_url(url, NULL, NULL, flags);
+}
+
+#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, "/;?", 0);
+}
+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, 0);
+}
+
+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, 0);
+}
+
+/* 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)
+{
+ apr_size_t i, j;
+ char *x;
+
+ /* first, count the number of extra characters */
+ for (i = 0, j = 0; s[i] != '\0'; i++) {
+ if (i + j > APR_SIZE_MAX - 6) {
+ abort();
+ }
+ 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], "&lt;", 4);
+ j += 3;
+ }
+ else if (s[i] == '>') {
+ memcpy(&x[j], "&gt;", 4);
+ j += 3;
+ }
+ else if (s[i] == '&') {
+ memcpy(&x[j], "&amp;", 5);
+ j += 4;
+ }
+ else if (s[i] == '"') {
+ memcpy(&x[j], "&quot;", 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;
+
+ decoded = (char *) apr_palloc(p, apr_base64_decode_len(bufcoded));
+ apr_base64_decode(decoded, bufcoded);
+
+ return decoded;
+}
+
+AP_DECLARE(char *) ap_pbase64encode(apr_pool_t *p, char *string)
+{
+ char *encoded;
+ int l = strlen(string);
+
+ encoded = (char *) apr_palloc(p, apr_base64_encode_len(l));
+ apr_base64_encode(encoded, string, l);
+
+ 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)
+{
+ apr_size_t size, extra = 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') {
+ if (*inchr == '"') {
+ extra++;
+ }
+ /*
+ * If we find a slosh, and it's not the last byte in the string,
+ * it's escaping something - advance past both bytes.
+ */
+ else if ((*inchr == '\\') && (inchr[1] != '\0')) {
+ inchr++;
+ }
+ inchr++;
+ }
+
+ if (!extra) {
+ return apr_pstrdup(p, instring);
+ }
+
+ /* How large will the string become, once we escaped all the quotes?
+ * The tricky cases are
+ * - an `instring` that is already longer than `ptrdiff_t`
+ * can hold (which is an undefined case in C, as C defines ptrdiff_t as
+ * a signed difference between pointers into the same array and one index
+ * beyond).
+ * - an `instring` that, including the `extra` chars we want to add, becomes
+ * even larger than apr_size_t can handle.
+ * Since this function was not designed to ever return NULL for failure, we
+ * can only trigger a hard assertion failure. It seems more a programming
+ * mistake (or failure to verify the input causing this) that leads to this
+ * situation.
+ */
+ ap_assert(inchr - instring > 0);
+ size = ((apr_size_t)(inchr - instring)) + 1;
+ ap_assert(size + extra > size);
+
+ outstring = apr_palloc(p, size + extra);
+ 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 == '"') {
+ *outchr++ = '\\';
+ }
+ else if ((*inchr == '\\') && (inchr[1] != '\0')) {
+ *outchr++ = *inchr++;
+ }
+ *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.
+ */
+#define CHECK_OVERFLOW(a, b) if (a > b) return APR_EGENERAL
+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;
+ apr_uint64_t check;
+
+ tout = apr_strtoi64(timeout_parameter, &endp, 10);
+ if (errno) {
+ return errno;
+ }
+ if (!endp || !*endp) {
+ time_str = default_time_unit;
+ }
+ else {
+ time_str = endp;
+ }
+
+ if (tout < 0) {
+ return APR_EGENERAL;
+ }
+
+ switch (*time_str) {
+ /* Time is in seconds */
+ case 's':
+ CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX));
+ check = apr_time_from_sec(tout);
+ break;
+ /* Time is in hours */
+ case 'h':
+ CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX / 3600));
+ check = apr_time_from_sec(tout * 3600);
+ break;
+ case 'm':
+ switch (*(++time_str)) {
+ /* Time is in milliseconds */
+ case 's':
+ CHECK_OVERFLOW(tout, apr_time_as_msec(APR_INT64_MAX));
+ check = apr_time_from_msec(tout);
+ break;
+ /* Time is in minutes */
+ case 'i':
+ CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX / 60));
+ check = apr_time_from_sec(tout * 60);
+ break;
+ default:
+ return APR_EGENERAL;
+ }
+ break;
+ default:
+ return APR_EGENERAL;
+ }
+
+ *timeout = (apr_interval_time_t)check;
+ return APR_SUCCESS;
+}
+#undef CHECK_OVERFLOW
+
+AP_DECLARE(int) ap_parse_strict_length(apr_off_t *len, const char *str)
+{
+ char *end;
+
+ return (apr_isdigit(*str)
+ && apr_strtoff(len, str, &end, 10) == APR_SUCCESS
+ && *end == '\0');
+}
+
+/**
+ * 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;
+ const char *cls;
+
+ return (!r->header_only
+ && (r->kept_body
+ || apr_table_get(r->headers_in, "Transfer-Encoding")
+ || ((cls = apr_table_get(r->headers_in, "Content-Length"))
+ && ap_parse_strict_length(&cl, cls) && cl > 0)));
+}
+
+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 || ap_cstr_casecmpn("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) {
+ pair = (ap_form_pair_t *) apr_array_push(pairs);
+ pair->name = apr_pstrmemdup(r->pool, buffer, offset);
+ pair->value = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ state = FORM_VALUE;
+ offset = 0;
+ }
+ 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;
+}
+
+static 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);
+ /* Happens if APR was compiled with APR_POOL_DEBUG */
+ if (allocator == NULL) {
+ apr_allocator_create(&allocator);
+ ap_assert(allocator != NULL);
+ }
+ 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;
+}
+
+#if APR_HAS_THREADS
+
+#if APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL)
+
+#define ap_thread_current_create apr_thread_current_create
+
+#else /* APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) */
+
+#if AP_HAS_THREAD_LOCAL
+
+struct thread_ctx {
+ apr_thread_start_t func;
+ void *data;
+};
+
+static AP_THREAD_LOCAL apr_thread_t *current_thread = NULL;
+
+static void *APR_THREAD_FUNC thread_start(apr_thread_t *thread, void *data)
+{
+ struct thread_ctx *ctx = data;
+
+ current_thread = thread;
+ return ctx->func(thread, ctx->data);
+}
+
+AP_DECLARE(apr_status_t) ap_thread_create(apr_thread_t **thread,
+ apr_threadattr_t *attr,
+ apr_thread_start_t func,
+ void *data, apr_pool_t *pool)
+{
+ struct thread_ctx *ctx = apr_palloc(pool, sizeof(*ctx));
+
+ ctx->func = func;
+ ctx->data = data;
+ return apr_thread_create(thread, attr, thread_start, ctx, pool);
+}
+
+#endif /* AP_HAS_THREAD_LOCAL */
+
+AP_DECLARE(apr_status_t) ap_thread_current_create(apr_thread_t **current,
+ apr_threadattr_t *attr,
+ apr_pool_t *pool)
+{
+ apr_status_t rv;
+ apr_abortfunc_t abort_fn = apr_pool_abort_get(pool);
+ apr_allocator_t *allocator;
+ apr_os_thread_t osthd;
+ apr_pool_t *p;
+
+ *current = ap_thread_current();
+ if (*current) {
+ return APR_EEXIST;
+ }
+
+ rv = apr_allocator_create(&allocator);
+ if (rv != APR_SUCCESS) {
+ if (abort_fn)
+ abort_fn(rv);
+ return rv;
+ }
+ rv = apr_pool_create_unmanaged_ex(&p, abort_fn, allocator);
+ if (rv != APR_SUCCESS) {
+ apr_allocator_destroy(allocator);
+ return rv;
+ }
+ apr_allocator_owner_set(allocator, p);
+
+ osthd = apr_os_thread_current();
+ rv = apr_os_thread_put(current, &osthd, p);
+ if (rv != APR_SUCCESS) {
+ apr_pool_destroy(p);
+ return rv;
+ }
+
+#if AP_HAS_THREAD_LOCAL
+ current_thread = *current;
+#endif
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(void) ap_thread_current_after_fork(void)
+{
+#if AP_HAS_THREAD_LOCAL
+ current_thread = NULL;
+#endif
+}
+
+AP_DECLARE(apr_thread_t *) ap_thread_current(void)
+{
+#if AP_HAS_THREAD_LOCAL
+ return current_thread;
+#else
+ return NULL;
+#endif
+}
+
+#endif /* APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) */
+
+static apr_status_t main_thread_cleanup(void *arg)
+{
+ apr_thread_t *thd = arg;
+ apr_pool_destroy(apr_thread_pool_get(thd));
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(apr_status_t) ap_thread_main_create(apr_thread_t **thread,
+ apr_pool_t *pool)
+{
+ apr_status_t rv;
+ apr_threadattr_t *attr = NULL;
+
+ /* Create an apr_thread_t for the main child thread to set up its Thread
+ * Local Storage. Since it's detached and won't apr_thread_exit(), destroy
+ * its pool before exiting via a cleanup of the given pool.
+ */
+ if ((rv = apr_threadattr_create(&attr, pool))
+ || (rv = apr_threadattr_detach_set(attr, 1))
+ || (rv = ap_thread_current_create(thread, attr, pool))) {
+ *thread = NULL;
+ return rv;
+ }
+
+ apr_pool_cleanup_register(pool, *thread, main_thread_cleanup,
+ apr_pool_cleanup_null);
+ return APR_SUCCESS;
+}
+
+#endif /* APR_HAS_THREADS */
+
+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;
+}
+
+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);
+}
+
+AP_DECLARE(ap_dir_match_t *)ap_dir_cfgmatch(cmd_parms *cmd, int flags,
+ const char *(*cb)(ap_dir_match_t *w, const char *fname), void *ctx)
+{
+ ap_dir_match_t *w = apr_palloc(cmd->temp_pool, sizeof(*w));
+
+ w->prefix = apr_pstrcat(cmd->pool, cmd->cmd->name, ": ", NULL);
+ w->p = cmd->pool;
+ w->ptemp = cmd->temp_pool;
+ w->flags = flags;
+ w->cb = cb;
+ w->ctx = ctx;
+ w->depth = 0;
+
+ return w;
+}
+
+AP_DECLARE(const char *)ap_dir_nofnmatch(ap_dir_match_t *w, const char *fname)
+{
+ const char *error;
+ apr_status_t rv;
+
+ if ((w->flags & AP_DIR_FLAG_RECURSIVE) && ap_is_directory(w->ptemp, fname)) {
+ apr_dir_t *dirp;
+ apr_finfo_t dirent;
+ int current;
+ apr_array_header_t *candidates = NULL;
+ fnames *fnew;
+ char *path = apr_pstrdup(w->ptemp, fname);
+
+ if (++w->depth > AP_MAX_FNMATCH_DIR_DEPTH) {
+ return apr_psprintf(w->p, "%sDirectory '%s' exceeds the maximum include "
+ "directory nesting level of %u. You have "
+ "probably a recursion somewhere.", w->prefix ? w->prefix : "", path,
+ AP_MAX_FNMATCH_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, w->ptemp);
+ if (rv != APR_SUCCESS) {
+ return apr_psprintf(w->p, "%sCould not open directory %s: %pm",
+ w->prefix ? w->prefix : "", path, &rv);
+ }
+
+ candidates = apr_array_make(w->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(w->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 = ap_dir_nofnmatch(w, fnew->fname);
+ if (error) {
+ return error;
+ }
+ }
+ }
+
+ w->depth--;
+
+ return NULL;
+ }
+ else if (w->flags & AP_DIR_FLAG_OPTIONAL) {
+ /* If the optional 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, w->ptemp) != APR_SUCCESS
+ || finfo.filetype == APR_NOFILE)
+ return NULL;
+ }
+
+ return w->cb(w, fname);
+}
+
+AP_DECLARE(const char *)ap_dir_fnmatch(ap_dir_match_t *w, const char *path,
+ const char *fname)
+{
+ 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(w->ptemp, fname, rest - fname);
+ rest++;
+ }
+
+ /* optimisation - if the filename isn't a wildcard, process it directly */
+ if (!apr_fnmatch_test(fname)) {
+ path = path ? ap_make_full_path(w->ptemp, path, fname) : fname;
+ if (!rest) {
+ return ap_dir_nofnmatch(w, path);
+ }
+ else {
+ return ap_dir_fnmatch(w, path, rest);
+ }
+ }
+
+ /*
+ * 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, w->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 && (w->flags & AP_DIR_FLAG_OPTIONAL)) {
+ return NULL;
+ }
+ return apr_psprintf(w->p, "%sCould not open directory %s: %pm",
+ w->prefix ? w->prefix : "", path, &rv);
+ }
+
+ candidates = apr_array_make(w->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(w->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 = ap_dir_nofnmatch(w, fnew->fname);
+ }
+ else {
+ error = ap_dir_fnmatch(w, fnew->fname, rest);
+ }
+ if (error) {
+ return error;
+ }
+ }
+ }
+ else {
+
+ if (!(w->flags & AP_DIR_FLAG_OPTIONAL)) {
+ return apr_psprintf(w->p, "%sNo matches for the wildcard '%s' in '%s', failing",
+ w->prefix ? w->prefix : "", fname, path);
+ }
+ }
+
+ return NULL;
+}
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..7d6ae1e
--- /dev/null
+++ b/server/util_expr_eval.c
@@ -0,0 +1,1831 @@
+/* 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 "http_ssl.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 "apr_version.h"
+#if APR_VERSION_AT_LEAST(1,5,0)
+#include "apr_escape.h"
+#endif
+
+#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)
+{
+ apr_size_t 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, (unsigned int)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);
+}
+
+#if APR_VERSION_AT_LEAST(1,6,0)
+static const char *ldap_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ return apr_pescape_ldap(ctx->p, arg, APR_ESCAPE_STRING, APR_ESCAPE_LDAP_ALL);
+}
+#endif
+
+#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, 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 (ap_ssl_conn_is_ssl(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 },
+#if APR_VERSION_AT_LEAST(1,6,0)
+ { ldap_func, "ldap", NULL, 0 },
+#endif
+ { 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 (ap_cstr_casecmp(*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 = !ap_cstr_casecmp(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 = "Invalid 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_http2 = APR_RETRIEVE_OPTIONAL_FN(http2_is_h2);
+ 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..664f649
--- /dev/null
+++ b/server/util_filter.c
@@ -0,0 +1,733 @@
+/* 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,
+ &registered_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,
+ &registered_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 = APR_BRIGADE_LAST(bb);
+
+ if (e != APR_BRIGADE_SENTINEL(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..bba3b88
--- /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;
+
+ /*
+ * 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);
+
+ result = apr_palloc(p, 2 * APR_MD5_DIGESTSIZE + 1);
+ ap_bin2hex(hash, APR_MD5_DIGESTSIZE, result); /* sets final '\0' */
+ return result;
+}
+
+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..0a9dc50
--- /dev/null
+++ b/server/util_pcre.c
@@ -0,0 +1,547 @@
+/* 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 "apr_thread_proc.h"
+
+#ifdef HAVE_PCRE2
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include "pcre2.h"
+#define PCREn(x) PCRE2_ ## x
+#else
+#include "pcre.h"
+#define PCREn(x) PCRE_ ## x
+#endif
+
+/* PCRE_DUPNAMES is only present since version 6.7 of PCRE */
+#if !defined(PCRE_DUPNAMES) && !defined(HAVE_PCRE2)
+#error PCRE Version 6.7 or later required!
+#else
+
+#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(const char *) ap_pcre_version_string(int which)
+{
+#ifdef HAVE_PCRE2
+ static char buf[80];
+#endif
+ switch (which) {
+ case AP_REG_PCRE_COMPILED:
+ return APR_STRINGIFY(PCREn(MAJOR)) "." APR_STRINGIFY(PCREn(MINOR)) " " APR_STRINGIFY(PCREn(DATE));
+ case AP_REG_PCRE_LOADED:
+#ifdef HAVE_PCRE2
+ pcre2_config(PCRE2_CONFIG_VERSION, buf);
+ return buf;
+#else
+ return pcre_version();
+#endif
+ default:
+ return "Unknown";
+ }
+}
+
+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)
+{
+#ifdef HAVE_PCRE2
+ pcre2_code_free(preg->re_pcre);
+#else
+ (pcre_free)(preg->re_pcre);
+#endif
+}
+
+
+
+
+/*************************************************
+ * Compile a regular expression *
+ *************************************************/
+
+static int default_cflags = AP_REG_DEFAULT;
+
+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)
+{
+#ifdef HAVE_PCRE2
+ uint32_t capcount;
+ size_t erroffset;
+#else
+ const char *errorptr;
+ int erroffset;
+#endif
+ int errcode = 0;
+ int options = PCREn(DUPNAMES);
+
+ if ((cflags & AP_REG_NO_DEFAULT) == 0)
+ cflags |= default_cflags;
+
+ if ((cflags & AP_REG_ICASE) != 0)
+ options |= PCREn(CASELESS);
+ if ((cflags & AP_REG_NEWLINE) != 0)
+ options |= PCREn(MULTILINE);
+ if ((cflags & AP_REG_DOTALL) != 0)
+ options |= PCREn(DOTALL);
+ if ((cflags & AP_REG_DOLLAR_ENDONLY) != 0)
+ options |= PCREn(DOLLAR_ENDONLY);
+
+#ifdef HAVE_PCRE2
+ preg->re_pcre = pcre2_compile((const unsigned char *)pattern,
+ PCRE2_ZERO_TERMINATED, options, &errcode,
+ &erroffset, NULL);
+#else
+ preg->re_pcre = pcre_compile2(pattern, options, &errcode,
+ &errorptr, &erroffset, NULL);
+#endif
+
+ preg->re_erroffset = erroffset;
+ if (preg->re_pcre == NULL) {
+ /* Internal ERR21 is "failed to get memory" according to pcreapi(3) */
+ if (errcode == 21)
+ return AP_REG_ESPACE;
+ return AP_REG_INVARG;
+ }
+
+#ifdef HAVE_PCRE2
+ pcre2_pattern_info((const pcre2_code *)preg->re_pcre,
+ PCRE2_INFO_CAPTURECOUNT, &capcount);
+ preg->re_nsub = capcount;
+#else
+ pcre_fullinfo((const pcre *)preg->re_pcre, NULL,
+ PCRE_INFO_CAPTURECOUNT, &(preg->re_nsub));
+#endif
+ 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.
+ * Yet more unfortunately, PCRE2 wants an opaque context by providing the API
+ * to allocate and free it, so to minimize these calls we maintain one opaque
+ * context per thread (in Thread Local Storage, TLS) grown as needed, and while
+ * at it we do the same for PCRE1 ints vectors. Note that this requires a fast
+ * TLS mechanism to be worth it, which is the case of apr_thread_data_get/set()
+ * from/to ap_thread_current() when AP_HAS_THREAD_LOCAL; otherwise we'll do
+ * the allocation and freeing for each ap_regexec().
+ */
+
+#ifdef HAVE_PCRE2
+typedef pcre2_match_data* match_data_pt;
+typedef size_t* match_vector_pt;
+#else
+typedef int* match_data_pt;
+typedef int* match_vector_pt;
+#endif
+
+static APR_INLINE
+match_data_pt alloc_match_data(apr_size_t size,
+ match_vector_pt small_vector)
+{
+ match_data_pt data;
+
+#ifdef HAVE_PCRE2
+ data = pcre2_match_data_create(size, NULL);
+#else
+ if (size > POSIX_MALLOC_THRESHOLD) {
+ data = malloc(size * sizeof(int) * 3);
+ }
+ else {
+ data = small_vector;
+ }
+#endif
+
+ return data;
+}
+
+static APR_INLINE
+void free_match_data(match_data_pt data, apr_size_t size)
+{
+#ifdef HAVE_PCRE2
+ pcre2_match_data_free(data);
+#else
+ if (size > POSIX_MALLOC_THRESHOLD) {
+ free(data);
+ }
+#endif
+}
+
+#if AP_HAS_THREAD_LOCAL && !defined(APREG_NO_THREAD_LOCAL)
+
+struct apreg_tls {
+ match_data_pt data;
+ apr_size_t size;
+};
+
+#ifdef HAVE_PCRE2
+static apr_status_t apreg_tls_cleanup(void *arg)
+{
+ struct apreg_tls *tls = arg;
+ pcre2_match_data_free(tls->data); /* NULL safe */
+ return APR_SUCCESS;
+}
+#endif
+
+static match_data_pt get_match_data(apr_size_t size,
+ match_vector_pt small_vector,
+ int *to_free)
+{
+ apr_thread_t *current;
+ struct apreg_tls *tls = NULL;
+
+ /* Even though AP_HAS_THREAD_LOCAL, we may still be called by a
+ * native/non-apr thread, let's fall back to alloc/free in this case.
+ */
+ current = ap_thread_current();
+ if (!current) {
+ *to_free = 1;
+ return alloc_match_data(size, small_vector);
+ }
+
+ apr_thread_data_get((void **)&tls, "apreg", current);
+ if (!tls || tls->size < size) {
+ apr_pool_t *tp = apr_thread_pool_get(current);
+ if (!tls) {
+ tls = apr_pcalloc(tp, sizeof(*tls));
+#ifdef HAVE_PCRE2
+ apr_thread_data_set(tls, "apreg", apreg_tls_cleanup, current);
+#else
+ apr_thread_data_set(tls, "apreg", NULL, current);
+#endif
+ }
+
+ tls->size *= 2;
+ if (tls->size < size) {
+ tls->size = size;
+ if (tls->size < POSIX_MALLOC_THRESHOLD) {
+ tls->size = POSIX_MALLOC_THRESHOLD;
+ }
+ }
+
+#ifdef HAVE_PCRE2
+ pcre2_match_data_free(tls->data); /* NULL safe */
+ tls->data = pcre2_match_data_create(tls->size, NULL);
+ if (!tls->data) {
+ tls->size = 0;
+ return NULL;
+ }
+#else
+ tls->data = apr_palloc(tp, tls->size * sizeof(int) * 3);
+#endif
+ }
+
+ return tls->data;
+}
+
+#else /* AP_HAS_THREAD_LOCAL && !defined(APREG_NO_THREAD_LOCAL) */
+
+static APR_INLINE match_data_pt get_match_data(apr_size_t size,
+ match_vector_pt small_vector,
+ int *to_free)
+{
+ *to_free = 1;
+ return alloc_match_data(size, small_vector);
+}
+
+#endif /* AP_HAS_THREAD_LOCAL && !defined(APREG_NO_THREAD_LOCAL) */
+
+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, to_free = 0;
+ match_vector_pt ovector = NULL;
+ apr_size_t ncaps = (apr_size_t)preg->re_nsub + 1;
+#ifdef HAVE_PCRE2
+ match_data_pt data = get_match_data(ncaps, NULL, &to_free);
+#else
+ int small_vector[POSIX_MALLOC_THRESHOLD * 3];
+ match_data_pt data = get_match_data(ncaps, small_vector, &to_free);
+#endif
+
+ if (!data) {
+ return AP_REG_ESPACE;
+ }
+
+ if ((eflags & AP_REG_NOTBOL) != 0)
+ options |= PCREn(NOTBOL);
+ if ((eflags & AP_REG_NOTEOL) != 0)
+ options |= PCREn(NOTEOL);
+
+#ifdef HAVE_PCRE2
+ rc = pcre2_match((const pcre2_code *)preg->re_pcre,
+ (const unsigned char *)buff, len,
+ 0, options, data, NULL);
+ ovector = pcre2_get_ovector_pointer(data);
+#else
+ ovector = data;
+ rc = pcre_exec((const pcre *)preg->re_pcre, NULL, buff, (int)len,
+ 0, options, ovector, ncaps * 3);
+#endif
+
+ if (rc >= 0) {
+ apr_size_t n = rc, i;
+ if (n == 0 || n > nmatch)
+ rc = n = nmatch; /* All capture slots were filled in */
+ for (i = 0; i < n; i++) {
+ pmatch[i].rm_so = ovector[i * 2];
+ pmatch[i].rm_eo = ovector[i * 2 + 1];
+ }
+ for (; i < nmatch; i++)
+ pmatch[i].rm_so = pmatch[i].rm_eo = -1;
+ if (to_free) {
+ free_match_data(data, ncaps);
+ }
+ return 0;
+ }
+ else {
+ if (to_free) {
+ free_match_data(data, ncaps);
+ }
+#ifdef HAVE_PCRE2
+ if (rc <= PCRE2_ERROR_UTF8_ERR1 && rc >= PCRE2_ERROR_UTF8_ERR21)
+ return AP_REG_INVARG;
+#endif
+ switch (rc) {
+ case PCREn(ERROR_NOMATCH):
+ return AP_REG_NOMATCH;
+ case PCREn(ERROR_NULL):
+ return AP_REG_INVARG;
+ case PCREn(ERROR_BADOPTION):
+ return AP_REG_INVARG;
+ case PCREn(ERROR_BADMAGIC):
+ return AP_REG_INVARG;
+ case PCREn(ERROR_NOMEMORY):
+ return AP_REG_ESPACE;
+#if defined(HAVE_PCRE2) || defined(PCRE_ERROR_MATCHLIMIT)
+ case PCREn(ERROR_MATCHLIMIT):
+ return AP_REG_ESPACE;
+#endif
+#if defined(PCRE_ERROR_UNKNOWN_NODE)
+ case PCRE_ERROR_UNKNOWN_NODE:
+ return AP_REG_ASSERT;
+#endif
+#if defined(PCRE_ERROR_BADUTF8)
+ case PCREn(ERROR_BADUTF8):
+ return AP_REG_INVARG;
+#endif
+#if defined(PCRE_ERROR_BADUTF8_OFFSET)
+ case PCREn(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)
+{
+ char *nametable;
+
+#ifdef HAVE_PCRE2
+ uint32_t namecount;
+ uint32_t nameentrysize;
+ uint32_t i;
+ pcre2_pattern_info((const pcre2_code *)preg->re_pcre,
+ PCRE2_INFO_NAMECOUNT, &namecount);
+ pcre2_pattern_info((const pcre2_code *)preg->re_pcre,
+ PCRE2_INFO_NAMEENTRYSIZE, &nameentrysize);
+ pcre2_pattern_info((const pcre2_code *)preg->re_pcre,
+ PCRE2_INFO_NAMETABLE, &nametable);
+#else
+ int namecount;
+ int nameentrysize;
+ int i;
+ 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);
+#endif
+
+ 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;
+}
+
+#endif /* PCRE_DUPNAMES defined */
+
+/* End of pcreposix.c */
diff --git a/server/util_regex.c b/server/util_regex.c
new file mode 100644
index 0000000..5405f8d
--- /dev/null
+++ b/server/util_regex.c
@@ -0,0 +1,211 @@
+/* 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 */
+ ret->flags = ap_regcomp_get_default_cflags() & AP_REG_DOLLAR_ENDONLY;
+ 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, AP_REG_NO_DEFAULT | 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..45c49d5
--- /dev/null
+++ b/server/util_script.c
@@ -0,0 +1,942 @@
+/* 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 dependencies
+ * 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 (!ap_cstr_casecmp(hdrs[i].key, "Content-type")) {
+ apr_table_addn(e, "CONTENT_TYPE", hdrs[i].val);
+ }
+ else if (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(hdrs[i].key, "Authorization")
+ || !ap_cstr_casecmp(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 (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(w, "Location")) {
+ apr_table_set(r->headers_out, w, l);
+ }
+ else if (!ap_cstr_casecmp(w, "Content-Length")) {
+ apr_table_set(r->headers_out, w, l);
+ }
+ else if (!ap_cstr_casecmp(w, "Content-Range")) {
+ apr_table_set(r->headers_out, w, l);
+ }
+ else if (!ap_cstr_casecmp(w, "Transfer-Encoding")) {
+ apr_table_set(r->headers_out, w, l);
+ }
+ else if (!ap_cstr_casecmp(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 or invalid values.
+ */
+ else if (!ap_cstr_casecmp(w, "Last-Modified")) {
+ apr_time_t parsed_date = apr_date_parse_http(l);
+ if (parsed_date != APR_DATE_BAD) {
+ ap_update_mtime(r, parsed_date);
+ ap_set_last_modified(r);
+ if (APLOGrtrace1(r)) {
+ apr_time_t last_modified_date = apr_date_parse_http(apr_table_get(r->headers_out,
+ "Last-Modified"));
+ /*
+ * A Last-Modified header value coming from a (F)CGI source
+ * is considered HTTP input so we assume the GMT timezone.
+ * The following logs should inform the admin about violations
+ * and related actions taken by httpd.
+ * The apr_date_parse_rfc function is 'timezone aware'
+ * and it will be used to generate a more informative set of logs
+ * (we don't use it as a replacement of apr_date_parse_http
+ * for the aforementioned reason).
+ */
+ apr_time_t parsed_date_tz_aware = apr_date_parse_rfc(l);
+
+ /*
+ * The parsed Last-Modified header datestring has been replaced by httpd.
+ */
+ if (parsed_date > last_modified_date) {
+ ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE1, 0, r,
+ "The Last-Modified header value %s (%s) "
+ "has been replaced with '%s'", l,
+ parsed_date != parsed_date_tz_aware ? "not in GMT"
+ : "in the future",
+ apr_table_get(r->headers_out, "Last-Modified"));
+ /*
+ * Last-Modified header datestring not in GMT and not considered in the future
+ * by httpd (like now() + 1 hour in the PST timezone). No action is taken but
+ * the admin is warned about the violation.
+ */
+ } else if (parsed_date != parsed_date_tz_aware) {
+ ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE1, 0, r,
+ "The Last-Modified header value is not set "
+ "within the GMT timezone (as required)");
+ }
+ }
+ }
+ else {
+ ap_log_rerror(SCRIPT_LOG_MARK, APLOG_INFO, 0, r, APLOGNO(10247)
+ "Ignored invalid header value: Last-Modified: '%s'", l);
+ }
+ }
+ else if (!ap_cstr_casecmp(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..22806fa
--- /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 (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..489c141
--- /dev/null
+++ b/server/vhost.c
@@ -0,0 +1,1294 @@
+/* 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"
+#include "http_main.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);
+}
+
+
+/*
+ * Updates r->server from ServerName/ServerAlias. Per the interaction
+ * of ip and name-based vhosts, it only looks in the best match from the
+ * connection-level ip-based matching.
+ * Returns HTTP_BAD_REQUEST if there was no match.
+ */
+static int update_server_from_aliases(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;
+ }
+ }
+
+ /* 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;
+ }
+ }
+
+ last_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;
+ }
+
+ if (!r->connection->vhost_lookup_data) {
+ if (matches_aliases(r->server, host)) {
+ s = r->server;
+ goto found;
+ }
+ }
+ return HTTP_BAD_REQUEST;
+
+found:
+ /* s is the first matching server, we're done */
+ r->server = s;
+ return HTTP_OK;
+}
+
+
+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 update_server_from_aliases 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)
+{
+ ap_update_vhost_from_headers_ex(r, 0);
+}
+
+AP_DECLARE(int) ap_update_vhost_from_headers_ex(request_rec *r, int require_match)
+{
+ 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;
+ int rc = HTTP_OK;
+
+ 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 (!require_match && r->status != HTTP_OK)
+ return HTTP_OK;
+
+ 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)
+ rc = update_server_from_aliases(r);
+ else
+ check_serverpath(r);
+ }
+ else if (require_match && r->hostname) {
+ /* check the base server config */
+ rc = update_server_from_aliases(r);
+ }
+
+ return rc;
+}
+
+/**
+ * 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;
+}