summaryrefslogtreecommitdiff
path: root/modules/proxy
diff options
context:
space:
mode:
Diffstat (limited to 'modules/proxy')
-rw-r--r--modules/proxy/.indent.pro58
-rw-r--r--modules/proxy/CHANGES223
-rw-r--r--modules/proxy/Makefile.in3
-rw-r--r--modules/proxy/NWGNUmakefile249
-rw-r--r--modules/proxy/NWGNUproxy272
-rw-r--r--modules/proxy/NWGNUproxyajp273
-rw-r--r--modules/proxy/NWGNUproxybalancer271
-rw-r--r--modules/proxy/NWGNUproxycon256
-rw-r--r--modules/proxy/NWGNUproxyftp267
-rw-r--r--modules/proxy/NWGNUproxyhtp268
-rw-r--r--modules/proxy/ajp.h477
-rw-r--r--modules/proxy/ajp_header.c754
-rw-r--r--modules/proxy/ajp_header.h175
-rw-r--r--modules/proxy/ajp_link.c126
-rw-r--r--modules/proxy/ajp_msg.c602
-rw-r--r--modules/proxy/config.m442
-rw-r--r--modules/proxy/libproxy.exp1
-rw-r--r--modules/proxy/mod_proxy.c1984
-rw-r--r--modules/proxy/mod_proxy.dsp115
-rw-r--r--modules/proxy/mod_proxy.h722
-rw-r--r--modules/proxy/mod_proxy_ajp.c558
-rw-r--r--modules/proxy/mod_proxy_ajp.dsp135
-rw-r--r--modules/proxy/mod_proxy_balancer.c943
-rw-r--r--modules/proxy/mod_proxy_balancer.dsp111
-rw-r--r--modules/proxy/mod_proxy_connect.c401
-rw-r--r--modules/proxy/mod_proxy_connect.dsp111
-rw-r--r--modules/proxy/mod_proxy_ftp.c1891
-rw-r--r--modules/proxy/mod_proxy_ftp.dsp111
-rw-r--r--modules/proxy/mod_proxy_http.c1742
-rw-r--r--modules/proxy/mod_proxy_http.dsp111
-rw-r--r--modules/proxy/proxy_util.c2188
31 files changed, 15440 insertions, 0 deletions
diff --git a/modules/proxy/.indent.pro b/modules/proxy/.indent.pro
new file mode 100644
index 00000000..e2cd357a
--- /dev/null
+++ b/modules/proxy/.indent.pro
@@ -0,0 +1,58 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tapr_bucket_brigade
+-Tapr_pool_t
+-Tap_filter_t
+-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
+-Tproxy_server_conf
diff --git a/modules/proxy/CHANGES b/modules/proxy/CHANGES
new file mode 100644
index 00000000..cca99a90
--- /dev/null
+++ b/modules/proxy/CHANGES
@@ -0,0 +1,223 @@
+******************************************
+* PLEASE NOTE: Now that development for *
+* mod_proxy has been folded back into *
+* the httpd-2.1 tree, this file has *
+* been depreciated. Proxy changes should *
+* be noted in httpd-2.1's CHANGES file. *
+* This file exists for historical *
+* purposes. *
+******************************************
+
+mod_proxy changes for httpd 2.0.29-dev
+ *) don't do keepalives for sub-requests. [Ian Holsman]
+
+ *) fix up proxypass handling [Ian Holsman]
+
+ *) don't send If-Modified-Since, Cache-Control, or If-None-Match on
+ a subrequest [Ian Holsman]
+
+mod_proxy changes for httpd 2.0.26-dev
+ *) Add New option 'HTTPProxyOverrideReturnedErrors'. By Turning the
+ Flag on, you will mask the error pages returned by the proxied
+ server, and will it will be handled as if your server generated
+ the error. This change was put in so that a 404 on a included
+ r-proxied component will act in the same manner as a 404 on a
+ included file. [Ian Holsman <ianh@cnet.com>]
+
+mod_proxy changes for httpd 2.0.25-dev
+
+ *) Split proxy: space using <Proxy[Match] > directive blocks from
+ the <Directory[Match] > and <Files[Match] > blocks. Mod_proxy
+ now bypasses the directory and files testing phase (and skips
+ the http TRACE default handler on it's own, as well). Note that
+ <Location > blocks continue to be processed for proxy: requests.
+ [William Rowe <wrowe@covalent.net>]
+
+ *) apr_uri type/function namespace changes in apr_uri functions
+ [Doug MacEachern <dougm@covalent.net>]
+
+mod_proxy changes for httpd 2.0.23-dev
+
+ *) break the proxy_http_handler into multiple smaller functions.
+ [John Barbee <barbee@veribox.net>]
+
+ *) Fix the proxy when the origin server sends back a 100
+ Continue response. [John Barbee <barbee@veribox.net>]
+
+ *) Change 'readbytes' from apr_size_t to apr_off_t due to change
+ in ap_get_brigade's parameters [John Barbee <barbee@veribox.net>]
+
+mod_proxy changes for httpd 2.0.20-dev
+ *) Timeout added for backend connections.
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) Fix abort code path in proxy_http.c, similar to FTP fix.
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) Fix FTP ABOR command execution path.
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) FTP return code variable cleanup; fixed problem in login
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) Get PORT working again in the ftp proxy.
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) Return result code check for FTP QUIT, after fixing
+ problems with passive connection handling.
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) Reorganize ap_proxy_string_read() internally to not process eos
+ buckets.
+ [Chuck Murcko <chuck@topsail.org>]
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) Remove result code check for FTP QUIT command. Some servers send
+ nothing at all back in response to QUIT.
+ [Chuck Murcko <chuck@topsail.org>]
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+mod_proxy changes for httpd 2.0.19
+
+ *) Reverse previous patch since the core reverted.
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) Remove indirection on number of bytes to read for input filters.
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) Fixed a problem with directory listing corruption in the
+ PROXY_DIR filter.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) mod_proxy and the proxy submodules now build properly as DSOs.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Stopped the HTTP proxy from trying to read entity bodies when there
+ wasn't one (response was 1xx, 204, 205 or 304).
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Made sure dates were canonicalised correctly when passed to the client
+ browser through the HTTP proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Split each individual proxy protocol into separate modules.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Added Max-Forwards support for all request types so as to prevent
+ loops.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fix warnings about byte count type on Darwin (connect handler).
+ [Chuck Murcko <chuck@topsail.org>]
+
+mod_proxy changes for httpd 2.0.18
+
+ *) IPV6 EPSV support for IPV6 in FTP proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) FTP directory filter works now.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fixed some thread-safety issues with the HTTP proxy in mod_proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) PASV FTP works now.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Reworked the line-at-a-time read from the control connection to
+ workaround a stray empty bucket returned by the HTTP_IN filter.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Stopped the CORE filter from sending off an HTTP response when a
+ CONNECT tunnel was closed.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fixed the poll() loop in proxy_connect.c -> it works now!!!
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Converted send_dir() to ap_proxy_send_dir_filter() in proxy_ftp.c.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+mod_proxy changes for httpd 2.0.17
+
+ *) Major rework of ap_proxy_ftp_handler() to use filters (begone foul
+ BUFF!!!). It compiles, but is untested, and the build environment needs
+ to be fixed to include proxy_ftp.c.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Cleanup of dead functions within proxy_util.c.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Reworked the storage of the client socket between keepalive connections
+ to fix some nasty problems with the socket lasting longer than the
+ memory pool it was allocated from.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fixed bug where a hostname without a "." in it (such as "localhost")
+ would not trigger an IP address check with ProxyBlock.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+mod_proxy changes for httpd 2.0.16
+
+ *) Fixed ProxyBlock bugs with ap_proxy_http_handler() and
+ ap_proxy_connect_handler().
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Updated ap_proxy_connect_handler() to support APR, while
+ moving some common code between http_handler and connect_handler
+ to proxy_util.c.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Updated mod_proxy.html docs to include v2.0 configuration.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fixed problem where responses without entity bodies would cause
+ the directly following proxy keepalive request to fail.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+mod_proxy changes for httpd 2.0.15
+
+ *) Added support for downstream keepalives in mod_proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Changed mod_proxy ap_proxy_http_handler() to support APR properly.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fix problem where incoming response headers were not being returned
+ to the client in mod_proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Added X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Server to
+ reverse proxied request headers in mod_proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) replace INADDR_NONE with APR_INADDR_NONE [Ian Holsman <IanH@cnet.com>]
+
+ *) Fix problem with proxy configuration where globally set
+ configuration options were overridden inside virtual hosts.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fix ProxyReceiveBufferSize where default value was left
+ uninitialised.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Some small changes:
+ - Ensured hop-by-hop headers were stripped as per
+ RFC2616 13.5.1.
+ - Upgraded version code to HTTP/1.1.
+ - Added Connection: close until Keepalives come.
+ - Some cosmetic fixes and commenting.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+mod_proxy changes for httpd 2.0.14
+
+ *) removed ProxyNoCache and ProxyCacheForceCompletion config directives,
+ since we no longer directly cache from this module
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) removed cache
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) initial rerebuild for 2.0
+ [Chuck Murcko <chuck@topsail.org>]
+
diff --git a/modules/proxy/Makefile.in b/modules/proxy/Makefile.in
new file mode 100644
index 00000000..7c5c149d
--- /dev/null
+++ b/modules/proxy/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/proxy/NWGNUmakefile b/modules/proxy/NWGNUmakefile
new file mode 100644
index 00000000..841673e4
--- /dev/null
+++ b/modules/proxy/NWGNUmakefile
@@ -0,0 +1,249 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)\build\NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# 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 =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxy.nlm \
+ $(OBJDIR)/proxycon.nlm \
+ $(OBJDIR)/proxyftp.nlm \
+ $(OBJDIR)/proxyhtp.nlm \
+ $(OBJDIR)/proxybalancer.nlm \
+ $(OBJDIR)/proxyajp.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 = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ copy $(OBJDIR)\*.nlm $(INSTALL)\Apache2\modules\*.*
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/modules/proxy/NWGNUproxy b/modules/proxy/NWGNUproxy
new file mode 100644
index 00000000..ac36e50b
--- /dev/null
+++ b/modules/proxy/NWGNUproxy
@@ -0,0 +1,272 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)\build\NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(AP_WORK)/modules/http \
+ $(AP_WORK)/modules/arch/netware \
+ $(AP_WORK)/modules/ssl \
+ $(AP_WORK)/modules/generators \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(APR) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ -prefix pre_nw.h \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxy
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# 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 = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = AUTOUNLOAD, 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 =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxy.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy.o \
+ $(OBJDIR)/proxy_util.o \
+ $(OBJDIR)/libprews.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 = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ 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 = \
+ @$(APR)/aprlib.imp \
+ @$(NWOS)/httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_module \
+ proxy_hook_scheme_handler \
+ proxy_hook_canon_handler \
+ proxy_hook_pre_request \
+ proxy_hook_post_request \
+ ap_proxy_ssl_enable \
+ ap_proxy_ssl_disable \
+ ap_proxy_conn_is_https \
+ ap_proxy_ssl_val \
+ proxy_run_fixups \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/modules/proxy/NWGNUproxyajp b/modules/proxy/NWGNUproxyajp
new file mode 100644
index 00000000..3d65d706
--- /dev/null
+++ b/modules/proxy/NWGNUproxyajp
@@ -0,0 +1,273 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)\build\NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(AP_WORK)/modules/http \
+ $(AP_WORK)/modules/arch/netware \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(APR) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ -relax_pointers \
+ $(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 = proxyajp
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy AJP Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy AJP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# 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 = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = AUTOUNLOAD, 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 =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxyajp.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_ajp.o \
+ $(OBJDIR)/proxy_util.o \
+ $(OBJDIR)/ajp_header.o \
+ $(OBJDIR)/ajp_msg.o \
+ $(OBJDIR)/ajp_link.o \
+ $(OBJDIR)/libprews.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 = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(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 = \
+ @$(APR)/aprlib.imp \
+ @$(NWOS)/httpd.imp \
+ @libc.imp \
+ proxy_module \
+ proxy_hook_scheme_handler \
+ proxy_hook_canon_handler \
+ proxy_run_fixups \
+ ap_proxy_ssl_enable \
+ ap_proxy_ssl_disable \
+ ap_proxy_conn_is_https \
+ ap_proxy_ssl_val \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_ajp_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/modules/proxy/NWGNUproxybalancer b/modules/proxy/NWGNUproxybalancer
new file mode 100644
index 00000000..0f7635fa
--- /dev/null
+++ b/modules/proxy/NWGNUproxybalancer
@@ -0,0 +1,271 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)\build\NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(AP_WORK)/modules/http \
+ $(AP_WORK)/modules/arch/netware \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(APR) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ -prefix pre_nw.h \
+ $(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 = proxybalancer
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Balancer Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Prxy Blncr Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# 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 = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = AUTOUNLOAD, 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 =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxybalancer.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_balancer.o \
+ $(OBJDIR)/proxy_util.o \
+ $(OBJDIR)/libprews.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 = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(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 = \
+ @$(APR)/aprlib.imp \
+ @$(NWOS)/httpd.imp \
+ @libc.imp \
+ proxy_module \
+ proxy_hook_scheme_handler \
+ proxy_hook_canon_handler \
+ proxy_hook_pre_request \
+ proxy_hook_post_request \
+ proxy_hook_load_lbmethods \
+ proxy_run_fixups \
+ ap_proxy_ssl_enable \
+ ap_proxy_ssl_disable \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_balancer_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/modules/proxy/NWGNUproxycon b/modules/proxy/NWGNUproxycon
new file mode 100644
index 00000000..1e044b68
--- /dev/null
+++ b/modules/proxy/NWGNUproxycon
@@ -0,0 +1,256 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)\build\NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(AP_WORK)/modules/http \
+ $(AP_WORK)/modules/arch/netware \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(APR) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ -prefix pre_nw.h \
+ $(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 = proxycon
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Connection Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy Conn Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# 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 = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = AUTOUNLOAD, 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 =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxycon.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_connect.o \
+ $(OBJDIR)/proxy_util.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 = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(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 = \
+ @$(APR)/aprlib.imp \
+ @$(NWOS)/httpd.imp \
+ @libc.imp \
+ proxy_module \
+ proxy_hook_scheme_handler \
+ proxy_hook_canon_handler \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_connect_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/modules/proxy/NWGNUproxyftp b/modules/proxy/NWGNUproxyftp
new file mode 100644
index 00000000..c8a1bd84
--- /dev/null
+++ b/modules/proxy/NWGNUproxyftp
@@ -0,0 +1,267 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)\build\NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(AP_WORK)/modules/http \
+ $(AP_WORK)/modules/arch/netware \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(APR) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ -prefix pre_nw.h \
+ $(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 = proxyftp
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy FTP Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy FTP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# 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 = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = AUTOUNLOAD, 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 =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxyftp.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_ftp.o \
+ $(OBJDIR)/proxy_util.o \
+ $(OBJDIR)/libprews.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 = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(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 = \
+ @$(APR)/aprlib.imp \
+ @$(NWOS)/httpd.imp \
+ @libc.imp \
+ proxy_module \
+ proxy_hook_scheme_handler \
+ proxy_hook_canon_handler \
+ ap_proxy_ssl_enable \
+ ap_proxy_ssl_disable \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_ftp_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/modules/proxy/NWGNUproxyhtp b/modules/proxy/NWGNUproxyhtp
new file mode 100644
index 00000000..bf4f8701
--- /dev/null
+++ b/modules/proxy/NWGNUproxyhtp
@@ -0,0 +1,268 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)\build\NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(AP_WORK)/modules/http \
+ $(AP_WORK)/modules/arch/netware \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(APR) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ -prefix pre_nw.h \
+ $(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 = proxyhtp
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy HTTP Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy HTTP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# 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 = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = AUTOUNLOAD, 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 =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxyhtp.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_http.o \
+ $(OBJDIR)/proxy_util.o \
+ $(OBJDIR)/libprews.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 = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(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 = \
+ @$(APR)/aprlib.imp \
+ @$(NWOS)/httpd.imp \
+ @libc.imp \
+ proxy_module \
+ proxy_hook_scheme_handler \
+ proxy_hook_canon_handler \
+ proxy_run_fixups \
+ ap_proxy_ssl_enable \
+ ap_proxy_ssl_disable \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_http_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/modules/proxy/ajp.h b/modules/proxy/ajp.h
new file mode 100644
index 00000000..d10f3976
--- /dev/null
+++ b/modules/proxy/ajp.h
@@ -0,0 +1,477 @@
+/* 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 ajp.h
+ * @brief Apache Jserv Protocol
+ *
+ * @defgroup AJP_defines AJP definitions
+ * @ingroup MOD_PROXY
+ * @{
+ */
+
+#ifndef AJP_H
+#define AJP_H
+
+#include "apr_version.h"
+#include "apr.h"
+
+#include "apr_hooks.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_md5.h"
+#include "apr_network_io.h"
+#include "apr_poll.h"
+#include "apr_pools.h"
+#include "apr_strings.h"
+#include "apr_uri.h"
+#include "apr_date.h"
+#include "apr_fnmatch.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if APR_HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#define AJP13_DEF_HOST "127.0.0.1"
+#ifdef NETWARE
+#define AJP13_DEF_PORT 9009 /* default to 9009 since 8009 is used by OS */
+#else
+#define AJP13_DEF_PORT 8009
+#endif
+
+/* The following environment variables match mod_ssl! */
+#define AJP13_HTTPS_INDICATOR "HTTPS"
+#define AJP13_SSL_CLIENT_CERT_INDICATOR "SSL_CLIENT_CERT"
+#define AJP13_SSL_CIPHER_INDICATOR "SSL_CIPHER"
+#define AJP13_SSL_SESSION_INDICATOR "SSL_SESSION_ID"
+#define AJP13_SSL_KEY_SIZE_INDICATOR "SSL_CIPHER_USEKEYSIZE"
+
+#if APR_CHARSET_EBCDIC
+
+#define USE_CHARSET_EBCDIC
+#define ajp_xlate_to_ascii(b, l) ap_xlate_proto_to_ascii(b, l)
+#define ajp_xlate_from_ascii(b, l) ap_xlate_proto_from_ascii(b, l)
+
+#else /* APR_CHARSET_EBCDIC */
+
+#define ajp_xlate_to_ascii(b, l)
+#define ajp_xlate_from_ascii(b, l)
+
+#endif
+
+#ifdef AJP_USE_HTTPD_WRAP
+#include "httpd_wrap.h"
+#else
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_log.h"
+#endif
+
+#include "mod_proxy.h"
+
+
+/** AJP Specific error codes
+ */
+/** Buffer overflow exception */
+#define AJP_EOVERFLOW (APR_OS_START_USERERR + 1)
+/** Destination Buffer is to small */
+#define AJP_ETOSMALL (APR_OS_START_USERERR + 2)
+/** Invalid input parameters */
+#define AJP_EINVAL (APR_OS_START_USERERR + 3)
+/** Bad message signature */
+#define AJP_EBAD_SIGNATURE (APR_OS_START_USERERR + 4)
+/** Incoming message too bg */
+#define AJP_ETOBIG (APR_OS_START_USERERR + 5)
+/** Missing message header */
+#define AJP_ENO_HEADER (APR_OS_START_USERERR + 6)
+/** Bad message header */
+#define AJP_EBAD_HEADER (APR_OS_START_USERERR + 7)
+/** Bad message */
+#define AJP_EBAD_MESSAGE (APR_OS_START_USERERR + 8)
+/** Cant log via AJP14 */
+#define AJP_ELOGFAIL (APR_OS_START_USERERR + 9)
+/** Bad request method */
+#define AJP_EBAD_METHOD (APR_OS_START_USERERR + 10)
+
+
+/** A structure that represents ajp message */
+typedef struct ajp_msg ajp_msg_t;
+
+/** A structure that represents ajp message */
+struct ajp_msg
+{
+ /** The buffer holding a AJP message */
+ apr_byte_t *buf;
+ /** The length of AJP message header (defaults to AJP_HEADER_LEN) */
+ apr_size_t header_len;
+ /** The length of AJP message */
+ apr_size_t len;
+ /** The current read position */
+ apr_size_t pos;
+ /** Flag indicating the origing of the message */
+ int server_side;
+};
+
+/**
+ * Signature for the messages sent from Apache to tomcat
+ */
+#define AJP13_WS_HEADER 0x1234
+#define AJP_HEADER_LEN 4
+#define AJP_HEADER_SZ_LEN 2
+#define AJP_MSG_BUFFER_SZ (8*1024)
+#define AJP13_MAX_SEND_BODY_SZ (AJP_MSG_BUFFER_SZ - 6)
+
+/** Send a request from web server to container*/
+#define CMD_AJP13_FORWARD_REQUEST (unsigned char)2
+/** Write a body chunk from the servlet container to the web server */
+#define CMD_AJP13_SEND_BODY_CHUNK (unsigned char)3
+/** Send response headers from the servlet container to the web server. */
+#define CMD_AJP13_SEND_HEADERS (unsigned char)4
+/** Marks the end of response. */
+#define CMD_AJP13_END_RESPONSE (unsigned char)5
+/** Get further data from the web server if it hasn't all been transferred yet. */
+#define CMD_AJP13_GET_BODY_CHUNK (unsigned char)6
+/** The web server asks the container to shut itself down. */
+#define CMD_AJP13_SHUTDOWN (unsigned char)7
+/** Webserver ask container to take control (logon phase) */
+#define CMD_AJP13_PING (unsigned char)8
+/** Container response to cping request */
+#define CMD_AJP13_CPONG (unsigned char)9
+/** Webserver check if container is alive, since container should respond by cpong */
+#define CMD_AJP13_CPING (unsigned char)10
+
+/** @} */
+
+/**
+ * @defgroup AJP_api AJP API functions
+ * @ingroup MOD_PROXY
+ * @{
+ */
+/**
+ * Check a new AJP Message by looking at signature and return its size
+ *
+ * @param msg AJP Message to check
+ * @param len Pointer to returned len
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_check_header(ajp_msg_t *msg, apr_size_t *len);
+
+/**
+ * Reset an AJP Message
+ *
+ * @param msg AJP Message to reset
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_reset(ajp_msg_t *msg);
+
+/**
+ * Reuse an AJP Message
+ *
+ * @param msg AJP Message to reuse
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_reuse(ajp_msg_t *msg);
+
+/**
+ * Mark the end of an AJP Message
+ *
+ * @param msg AJP Message to end
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_end(ajp_msg_t *msg);
+
+/**
+ * Add an unsigned 32bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint32(ajp_msg_t *msg, apr_uint32_t value);
+
+/**
+ * Add an unsigned 16bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint16(ajp_msg_t *msg, apr_uint16_t value);
+
+/**
+ * Add an unsigned 8bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint8(ajp_msg_t *msg, apr_byte_t value);
+
+/**
+ * Add a String in AJP message, and transform the String in ASCII
+ * if convert is set and we're on an EBCDIC machine
+ *
+ * @param msg AJP Message to get value from
+ * @param value Pointer to String
+ * @param convert When set told to convert String to ASCII
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_string_ex(ajp_msg_t *msg, const char *value,
+ int convert);
+/**
+ * Add a String in AJP message, and transform
+ * the String in ASCII if we're on an EBCDIC machine
+ */
+#define ajp_msg_append_string(m, v) ajp_msg_append_string_ex(m, v, 1)
+
+/**
+ * Add a String in AJP message.
+ */
+#define ajp_msg_append_string_ascii(m, v) ajp_msg_append_string_ex(m, v, 0)
+
+/**
+ * Add a Byte array to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value Pointer to Byte array
+ * @param valuelen Byte array len
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_bytes(ajp_msg_t *msg, const apr_byte_t *value,
+ apr_size_t valuelen);
+
+/**
+ * Get a 32bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint32(ajp_msg_t *msg, apr_uint32_t *rvalue);
+
+/**
+ * Get a 16bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint16(ajp_msg_t *msg, apr_uint16_t *rvalue);
+
+/**
+ * Peek a 16bits unsigned value from AJP Message, position in message
+ * is not updated
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_peek_uint16(ajp_msg_t *msg, apr_uint16_t *rvalue);
+
+/**
+ * Get a 8bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint8(ajp_msg_t *msg, apr_byte_t *rvalue);
+
+/**
+ * Peek a 8bits unsigned value from AJP Message, position in message
+ * is not updated
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_peek_uint8(ajp_msg_t *msg, apr_byte_t *rvalue);
+
+/**
+ * Get a String value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_string(ajp_msg_t *msg, const char **rvalue);
+
+
+/**
+ * Get a Byte array from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @param rvalueLen Pointer where Byte array len will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_bytes(ajp_msg_t *msg, apr_byte_t **rvalue,
+ apr_size_t *rvalue_len);
+
+/**
+ * Create an AJP Message from pool
+ *
+ * @param pool memory pool to allocate AJP message from
+ * @param rmsg Pointer to newly created AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_create(apr_pool_t *pool, ajp_msg_t **rmsg);
+
+/**
+ * Recopy an AJP Message to another
+ *
+ * @param smsg source AJP message
+ * @param dmsg destination AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_copy(ajp_msg_t *smsg, ajp_msg_t *dmsg);
+
+/**
+ * Serialize in an AJP Message a PING command
+ *
+ * +-----------------------+
+ * | PING CMD (1 byte) |
+ * +-----------------------+
+ *
+ * @param smsg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_serialize_ping(ajp_msg_t *msg);
+
+/**
+ * Serialize in an AJP Message a CPING command
+ *
+ * +-----------------------+
+ * | CPING CMD (1 byte) |
+ * +-----------------------+
+ *
+ * @param smsg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_serialize_cping(ajp_msg_t *msg);
+
+/**
+ * Dump up to the first 1024 bytes on an AJP Message
+ *
+ * @param pool pool to allocate from
+ * @param msg AJP Message to dump
+ * @param err error string to display
+ * @return dump message
+ */
+char * ajp_msg_dump(apr_pool_t *pool, ajp_msg_t *msg, char *err);
+
+/**
+ * Send an AJP message to backend
+ *
+ * @param soct backend socket
+ * @param smsg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_ilink_send(apr_socket_t *sock, ajp_msg_t *msg);
+
+/**
+ * Receive an AJP message from backend
+ *
+ * @param sock backend socket
+ * @param smsg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_ilink_receive(apr_socket_t *sock, ajp_msg_t *msg);
+
+/**
+ * Build the ajp header message and send it
+ * @param sock backend socket
+ * @param r current request
+ * @uri uri requested uri
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_send_header(apr_socket_t *sock, request_rec *r,
+ apr_uri_t *uri);
+
+/**
+ * Read the ajp message and return the type of the message.
+ * @param sock backend socket
+ * @param r current request
+ * @param msg returned AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_read_header(apr_socket_t *sock,
+ request_rec *r,
+ ajp_msg_t **msg);
+
+/**
+ * Allocate a msg to send data
+ * @param pool pool to allocate from
+ * @param ptr data buffer
+ * @param len the length of allocated data buffer
+ * @param msg returned AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_alloc_data_msg(apr_pool_t *pool, char **ptr,
+ apr_size_t *len, ajp_msg_t **msg);
+
+/**
+ * Send the data message
+ * @param sock backend socket
+ * @param msg AJP message to send
+ * @param len AJP message length
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_send_data_msg(apr_socket_t *sock,
+ ajp_msg_t *msg, apr_size_t len);
+
+/**
+ * Parse the message type
+ * @param r current request
+ * @param msg AJP message
+ * @return AJP message type.
+ */
+int ajp_parse_type(request_rec *r, ajp_msg_t *msg);
+
+/**
+ * Parse the header message from container
+ * @param r current request
+ * @param msg AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_parse_header(request_rec *r, proxy_dir_conf *conf,
+ ajp_msg_t *msg);
+
+/**
+ * Parse the message body and return data address and length
+ * @param r current request
+ * @param msg AJP message
+ * @param len returned AJP message length
+ * @param ptr returned data
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_parse_data(request_rec *r, ajp_msg_t *msg,
+ apr_uint16_t *len, char **ptr);
+
+/** @} */
+
+#endif /* AJP_H */
+
diff --git a/modules/proxy/ajp_header.c b/modules/proxy/ajp_header.c
new file mode 100644
index 00000000..5d082531
--- /dev/null
+++ b/modules/proxy/ajp_header.c
@@ -0,0 +1,754 @@
+/* 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 "ajp_header.h"
+#include "ajp.h"
+
+static const char *response_trans_headers[] = {
+ "Content-Type",
+ "Content-Language",
+ "Content-Length",
+ "Date",
+ "Last-Modified",
+ "Location",
+ "Set-Cookie",
+ "Set-Cookie2",
+ "Servlet-Engine",
+ "Status",
+ "WWW-Authenticate"
+};
+
+static const char *long_res_header_for_sc(int sc)
+{
+ const char *rc = NULL;
+ sc = sc & 0X00FF;
+ if(sc <= SC_RES_HEADERS_NUM && sc > 0) {
+ rc = response_trans_headers[sc - 1];
+ }
+
+ return rc;
+}
+
+#define UNKNOWN_METHOD (-1)
+
+static int sc_for_req_header(const char *header_name)
+{
+ char header[16];
+ apr_size_t len = strlen(header_name);
+ const char *p = header_name;
+ int i = 0;
+
+ /* ACCEPT-LANGUAGE is the longest headeer
+ * that is of interest.
+ */
+ if (len < 4 || len > 15)
+ return UNKNOWN_METHOD;
+
+ while (*p)
+ header[i++] = apr_toupper(*p++);
+ header[i] = '\0';
+ p = &header[1];
+
+ switch (header[0]) {
+ case 'A':
+ if (memcmp(p, "CCEPT", 5) == 0) {
+ if (!header[6])
+ return SC_ACCEPT;
+ else if (header[6] == '-') {
+ p += 6;
+ if (memcmp(p, "CHARSET", 7) == 0)
+ return SC_ACCEPT_CHARSET;
+ else if (memcmp(p, "ENCODING", 8) == 0)
+ return SC_ACCEPT_ENCODING;
+ else if (memcmp(p, "LANGUAGE", 8) == 0)
+ return SC_ACCEPT_LANGUAGE;
+ else
+ return UNKNOWN_METHOD;
+ }
+ else
+ return UNKNOWN_METHOD;
+ }
+ else if (memcmp(p, "UTHORIZATION", 12) == 0)
+ return SC_AUTHORIZATION;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'C':
+ if(memcmp(p, "OOKIE2", 6) == 0)
+ return SC_COOKIE2;
+ else if (memcmp(p, "OOKIE", 5) == 0)
+ return SC_COOKIE;
+ else if(memcmp(p, "ONNECTION", 9) == 0)
+ return SC_CONNECTION;
+ else if(memcmp(p, "ONTENT-TYPE", 11) == 0)
+ return SC_CONTENT_TYPE;
+ else if(memcmp(p, "ONTENT-LENGTH", 13) == 0)
+ return SC_CONTENT_LENGTH;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'H':
+ if(memcmp(p, "OST", 3) == 0)
+ return SC_HOST;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'P':
+ if(memcmp(p, "RAGMA", 5) == 0)
+ return SC_PRAGMA;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'R':
+ if(memcmp(p, "EFERER", 6) == 0)
+ return SC_REFERER;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'U':
+ if(memcmp(p, "SER-AGENT", 9) == 0)
+ return SC_USER_AGENT;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ /* NOTREACHED */
+}
+
+/* Apache method number to SC methods transform table */
+static const unsigned char sc_for_req_method_table[] = {
+ SC_M_GET,
+ SC_M_PUT,
+ SC_M_POST,
+ SC_M_DELETE,
+ 0, /* M_DELETE */
+ SC_M_OPTIONS,
+ SC_M_TRACE,
+ 0, /* M_PATCH */
+ SC_M_PROPFIND,
+ SC_M_PROPPATCH,
+ SC_M_MKCOL,
+ SC_M_COPY,
+ SC_M_MOVE,
+ SC_M_LOCK,
+ SC_M_UNLOCK,
+ SC_M_VERSION_CONTROL,
+ SC_M_CHECKOUT,
+ SC_M_UNCHECKOUT,
+ SC_M_CHECKIN,
+ SC_M_UPDATE,
+ SC_M_LABEL,
+ SC_M_REPORT,
+ SC_M_MKWORKSPACE,
+ SC_M_MKACTIVITY,
+ SC_M_BASELINE_CONTROL,
+ SC_M_MERGE,
+ 0 /* M_INVALID */
+};
+
+static int sc_for_req_method_by_id(int method_id)
+{
+ if (method_id < 0 || method_id > M_INVALID)
+ return UNKNOWN_METHOD;
+ else
+ return sc_for_req_method_table[method_id] ?
+ sc_for_req_method_table[method_id] : UNKNOWN_METHOD;
+}
+
+/*
+ * Message structure
+ *
+ *
+AJPV13_REQUEST/AJPV14_REQUEST=
+ request_prefix (1) (byte)
+ method (byte)
+ protocol (string)
+ req_uri (string)
+ remote_addr (string)
+ remote_host (string)
+ server_name (string)
+ server_port (short)
+ is_ssl (boolean)
+ num_headers (short)
+ num_headers*(req_header_name header_value)
+
+ ?context (byte)(string)
+ ?servlet_path (byte)(string)
+ ?remote_user (byte)(string)
+ ?auth_type (byte)(string)
+ ?query_string (byte)(string)
+ ?jvm_route (byte)(string)
+ ?ssl_cert (byte)(string)
+ ?ssl_cipher (byte)(string)
+ ?ssl_session (byte)(string)
+ ?ssl_key_size (byte)(int) via JkOptions +ForwardKeySize
+ request_terminator (byte)
+ ?body content_length*(var binary)
+
+ */
+
+static apr_status_t ajp_marshal_into_msgb(ajp_msg_t *msg,
+ request_rec *r,
+ apr_uri_t *uri)
+{
+ int method;
+ apr_uint32_t i, num_headers = 0;
+ apr_byte_t is_ssl;
+ char *remote_host;
+ const char *session_route, *envvar;
+ const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
+ const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Into ajp_marshal_into_msgb");
+
+ if ((method = sc_for_req_method_by_id(r->method_number)) == UNKNOWN_METHOD) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb - No such method %s",
+ r->method);
+ return AJP_EBAD_METHOD;
+ }
+
+ is_ssl = (apr_byte_t) ap_proxy_conn_is_https(r->connection);
+
+ if (r->headers_in && apr_table_elts(r->headers_in)) {
+ const apr_array_header_t *t = apr_table_elts(r->headers_in);
+ num_headers = t->nelts;
+ }
+
+ remote_host = (char *)ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_HOST, NULL);
+
+ ajp_msg_reset(msg);
+
+ if (ajp_msg_append_uint8(msg, CMD_AJP13_FORWARD_REQUEST) ||
+ ajp_msg_append_uint8(msg, method) ||
+ ajp_msg_append_string(msg, r->protocol) ||
+ ajp_msg_append_string(msg, uri->path) ||
+ ajp_msg_append_string(msg, r->connection->remote_ip) ||
+ ajp_msg_append_string(msg, remote_host) ||
+ ajp_msg_append_string(msg, ap_get_server_name(r)) ||
+ ajp_msg_append_uint16(msg, (apr_uint16_t)r->connection->local_addr->port) ||
+ ajp_msg_append_uint8(msg, is_ssl) ||
+ ajp_msg_append_uint16(msg, (apr_uint16_t) num_headers)) {
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the message begining");
+ return APR_EGENERAL;
+ }
+
+ for (i = 0 ; i < num_headers ; i++) {
+ int sc;
+ const apr_array_header_t *t = apr_table_elts(r->headers_in);
+ const apr_table_entry_t *elts = (apr_table_entry_t *)t->elts;
+
+ if ((sc = sc_for_req_header(elts[i].key)) != UNKNOWN_METHOD) {
+ if (ajp_msg_append_uint16(msg, (apr_uint16_t)sc)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the header name");
+ return AJP_EOVERFLOW;
+ }
+ }
+ else {
+ if (ajp_msg_append_string(msg, elts[i].key)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the header name");
+ return AJP_EOVERFLOW;
+ }
+ }
+
+ if (ajp_msg_append_string(msg, elts[i].val)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the header value");
+ return AJP_EOVERFLOW;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "ajp_marshal_into_msgb: Header[%d] [%s] = [%s]",
+ i, elts[i].key, elts[i].val);
+ }
+
+/* XXXX need to figure out how to do this
+ if (s->secret) {
+ if (ajp_msg_append_uint8(msg, SC_A_SECRET) ||
+ ajp_msg_append_string(msg, s->secret)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "Error ajp_marshal_into_msgb - "
+ "Error appending secret");
+ return APR_EGENERAL;
+ }
+ }
+ */
+
+ if (r->user) {
+ if (ajp_msg_append_uint8(msg, SC_A_REMOTE_USER) ||
+ ajp_msg_append_string(msg, r->user)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the remote user");
+ return AJP_EOVERFLOW;
+ }
+ }
+ if (r->ap_auth_type) {
+ if (ajp_msg_append_uint8(msg, SC_A_AUTH_TYPE) ||
+ ajp_msg_append_string(msg, r->ap_auth_type)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the auth type");
+ return AJP_EOVERFLOW;
+ }
+ }
+ /* XXXX ebcdic (args converted?) */
+ if (uri->query) {
+ if (ajp_msg_append_uint8(msg, SC_A_QUERY_STRING) ||
+ ajp_msg_append_string(msg, uri->query)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the query string");
+ return AJP_EOVERFLOW;
+ }
+ }
+ if ((session_route = apr_table_get(r->notes, "session-route"))) {
+ if (ajp_msg_append_uint8(msg, SC_A_JVM_ROUTE) ||
+ ajp_msg_append_string(msg, session_route)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the jvm route");
+ return AJP_EOVERFLOW;
+ }
+ }
+/* XXX: Is the subprocess_env a right place?
+ * <Location /examples>
+ * ProxyPass ajp://remote:8009/servlets-examples
+ * SetEnv SSL_SESSION_ID CUSTOM_SSL_SESSION_ID
+ * </Location>
+ */
+ /*
+ * Only lookup SSL variables if we are currently running HTTPS.
+ * Furthermore ensure that only variables get set in the AJP message
+ * that are not NULL and not empty.
+ */
+ if (is_ssl) {
+ if ((envvar = ap_proxy_ssl_val(r->pool, r->server, r->connection, r,
+ AJP13_SSL_CLIENT_CERT_INDICATOR))
+ && envvar[0]) {
+ if (ajp_msg_append_uint8(msg, SC_A_SSL_CERT)
+ || ajp_msg_append_string(msg, envvar)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the SSL certificates");
+ return AJP_EOVERFLOW;
+ }
+ }
+
+ if ((envvar = ap_proxy_ssl_val(r->pool, r->server, r->connection, r,
+ AJP13_SSL_CIPHER_INDICATOR))
+ && envvar[0]) {
+ if (ajp_msg_append_uint8(msg, SC_A_SSL_CIPHER)
+ || ajp_msg_append_string(msg, envvar)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the SSL ciphers");
+ return AJP_EOVERFLOW;
+ }
+ }
+
+ if ((envvar = ap_proxy_ssl_val(r->pool, r->server, r->connection, r,
+ AJP13_SSL_SESSION_INDICATOR))
+ && envvar[0]) {
+ if (ajp_msg_append_uint8(msg, SC_A_SSL_SESSION)
+ || ajp_msg_append_string(msg, envvar)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the SSL session");
+ return AJP_EOVERFLOW;
+ }
+ }
+
+ /* ssl_key_size is required by Servlet 2.3 API */
+ if ((envvar = ap_proxy_ssl_val(r->pool, r->server, r->connection, r,
+ AJP13_SSL_KEY_SIZE_INDICATOR))
+ && envvar[0]) {
+
+ if (ajp_msg_append_uint8(msg, SC_A_SSL_KEY_SIZE)
+ || ajp_msg_append_uint16(msg, (unsigned short) atoi(envvar))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "Error ajp_marshal_into_msgb - "
+ "Error appending the SSL key size");
+ return APR_EGENERAL;
+ }
+ }
+ }
+ /* Use the environment vars prefixed with AJP_
+ * and pass it to the header striping that prefix.
+ */
+ for (i = 0; i < (apr_uint32_t)arr->nelts; i++) {
+ if (!strncmp(elts[i].key, "AJP_", 4)) {
+ if (ajp_msg_append_uint8(msg, SC_A_REQ_ATTRIBUTE) ||
+ ajp_msg_append_string(msg, elts[i].key + 4) ||
+ ajp_msg_append_string(msg, elts[i].val)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending attribute %s=%s",
+ elts[i].key, elts[i].val);
+ return AJP_EOVERFLOW;
+ }
+ }
+ }
+
+ if (ajp_msg_append_uint8(msg, SC_A_ARE_DONE)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_marshal_into_msgb: "
+ "Error appending the message end");
+ return AJP_EOVERFLOW;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "ajp_marshal_into_msgb: Done");
+ return APR_SUCCESS;
+}
+
+/*
+AJPV13_RESPONSE/AJPV14_RESPONSE:=
+ response_prefix (2)
+ status (short)
+ status_msg (short)
+ num_headers (short)
+ num_headers*(res_header_name header_value)
+ *body_chunk
+ terminator boolean <! -- recycle connection or not -->
+
+req_header_name :=
+ sc_req_header_name | (string)
+
+res_header_name :=
+ sc_res_header_name | (string)
+
+header_value :=
+ (string)
+
+body_chunk :=
+ length (short)
+ body length*(var binary)
+
+ */
+
+
+static apr_status_t ajp_unmarshal_response(ajp_msg_t *msg,
+ request_rec *r,
+ proxy_dir_conf *dconf)
+{
+ apr_uint16_t status;
+ apr_status_t rc;
+ const char *ptr;
+ apr_uint16_t num_headers;
+ int i;
+
+ rc = ajp_msg_get_uint16(msg, &status);
+
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_unmarshal_response: Null status");
+ return rc;
+ }
+ r->status = status;
+
+ rc = ajp_msg_get_string(msg, &ptr);
+ if (rc == APR_SUCCESS) {
+ r->status_line = apr_psprintf(r->pool, "%d %s", status, ptr);
+#if defined(AS400) || defined(_OSD_POSIX)
+ ap_xlate_proto_from_ascii(r->status_line, strlen(r->status_line));
+#endif
+ } else {
+ r->status_line = NULL;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "ajp_unmarshal_response: status = %d", status);
+
+ rc = ajp_msg_get_uint16(msg, &num_headers);
+ if (rc == APR_SUCCESS) {
+ r->headers_out = apr_table_make(r->pool, num_headers);
+ } else {
+ r->headers_out = NULL;
+ num_headers = 0;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "ajp_unmarshal_response: Number of headers is = %d",
+ num_headers);
+
+ for(i = 0 ; i < (int) num_headers ; i++) {
+ apr_uint16_t name;
+ const char *stringname;
+ const char *value;
+ rc = ajp_msg_peek_uint16(msg, &name);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+
+ if ((name & 0XFF00) == 0XA000) {
+ ajp_msg_get_uint16(msg, &name);
+ stringname = long_res_header_for_sc(name);
+ if (stringname == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_unmarshal_response: "
+ "No such sc (%08x)",
+ name);
+ return AJP_EBAD_HEADER;
+ }
+ } else {
+ name = 0;
+ rc = ajp_msg_get_string(msg, &stringname);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_unmarshal_response: "
+ "Null header name");
+ return rc;
+ }
+#if defined(AS400) || defined(_OSD_POSIX)
+ ap_xlate_proto_from_ascii(stringname, strlen(stringname));
+#endif
+ }
+
+ rc = ajp_msg_get_string(msg, &value);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_unmarshal_response: "
+ "Null header value");
+ return rc;
+ }
+
+ /* Set-Cookie need additional processing */
+ if (!strcasecmp(stringname, "Set-Cookie")) {
+ value = ap_proxy_cookie_reverse_map(r, dconf, value);
+ }
+ /* Location, Content-Location, URI and Destination need additional
+ * processing */
+ else if (!strcasecmp(stringname, "Location")
+ || !strcasecmp(stringname, "Content-Location")
+ || !strcasecmp(stringname, "URI")
+ || !strcasecmp(stringname, "Destination"))
+ {
+ value = ap_proxy_location_reverse_map(r, dconf, value);
+ }
+
+#if defined(AS400) || defined(_OSD_POSIX)
+ ap_xlate_proto_from_ascii(value, strlen(value));
+#endif
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "ajp_unmarshal_response: Header[%d] [%s] = [%s]",
+ i, stringname, value);
+
+ apr_table_add(r->headers_out, stringname, value);
+
+ /* Content-type needs an additional handling */
+ if (memcmp(stringname, "Content-Type", 12) == 0) {
+ /* add corresponding filter */
+ ap_set_content_type(r, apr_pstrdup(r->pool, value));
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "ajp_unmarshal_response: ap_set_content_type done");
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Build the ajp header message and send it
+ */
+apr_status_t ajp_send_header(apr_socket_t *sock,
+ request_rec *r,
+ apr_uri_t *uri)
+{
+ ajp_msg_t *msg;
+ apr_status_t rc;
+
+ rc = ajp_msg_create(r->pool, &msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_send_header: ajp_msg_create failed");
+ return rc;
+ }
+
+ rc = ajp_marshal_into_msgb(msg, r, uri);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_send_header: ajp_marshal_into_msgb failed");
+ return rc;
+ }
+
+ rc = ajp_ilink_send(sock, msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_send_header: ajp_ilink_send failed");
+ return rc;
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Read the ajp message and return the type of the message.
+ */
+apr_status_t ajp_read_header(apr_socket_t *sock,
+ request_rec *r,
+ ajp_msg_t **msg)
+{
+ apr_byte_t result;
+ apr_status_t rc;
+
+ if (*msg) {
+ rc = ajp_msg_reuse(*msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_read_header: ajp_msg_reuse failed");
+ return rc;
+ }
+ }
+ else {
+ rc = ajp_msg_create(r->pool, msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_read_header: ajp_msg_create failed");
+ return rc;
+ }
+ }
+ ajp_msg_reset(*msg);
+ rc = ajp_ilink_receive(sock, *msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_read_header: ajp_ilink_receive failed");
+ return rc;
+ }
+ rc = ajp_msg_peek_uint8(*msg, &result);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "ajp_read_header: ajp_ilink_received %02x", result);
+ return APR_SUCCESS;
+}
+
+/* parse the msg to read the type */
+int ajp_parse_type(request_rec *r, ajp_msg_t *msg)
+{
+ apr_byte_t result;
+ ajp_msg_peek_uint8(msg, &result);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "ajp_parse_type: got %02x", result);
+ return (int) result;
+}
+
+/* parse the header */
+apr_status_t ajp_parse_header(request_rec *r, proxy_dir_conf *conf,
+ ajp_msg_t *msg)
+{
+ apr_byte_t result;
+ apr_status_t rc;
+
+ rc = ajp_msg_get_uint8(msg, &result);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_parse_headers: ajp_msg_get_byte failed");
+ return rc;
+ }
+ if (result != CMD_AJP13_SEND_HEADERS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_parse_headers: wrong type %02x expecting 0x04", result);
+ return AJP_EBAD_HEADER;
+ }
+ return ajp_unmarshal_response(msg, r, conf);
+}
+
+/* parse the body and return data address and length */
+apr_status_t ajp_parse_data(request_rec *r, ajp_msg_t *msg,
+ apr_uint16_t *len, char **ptr)
+{
+ apr_byte_t result;
+ apr_status_t rc;
+ apr_uint16_t expected_len;
+
+ rc = ajp_msg_get_uint8(msg, &result);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_parse_data: ajp_msg_get_byte failed");
+ return rc;
+ }
+ if (result != CMD_AJP13_SEND_BODY_CHUNK) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_parse_data: wrong type %02x expecting 0x03", result);
+ return AJP_EBAD_HEADER;
+ }
+ rc = ajp_msg_get_uint16(msg, len);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ /*
+ * msg->len contains the complete length of the message including all
+ * headers. So the expected length for a CMD_AJP13_SEND_BODY_CHUNK is
+ * msg->len minus the sum of
+ * AJP_HEADER_LEN : The length of the header to every AJP message.
+ * AJP_HEADER_SZ_LEN : The header giving the size of the chunk.
+ * 1 : The CMD_AJP13_SEND_BODY_CHUNK indicator byte (0x03).
+ * 1 : The last byte of this message always seems to be
+ * 0x00 and is not part of the chunk.
+ */
+ expected_len = msg->len - (AJP_HEADER_LEN + AJP_HEADER_SZ_LEN + 1 + 1);
+ if (*len != expected_len) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "ajp_parse_data: Wrong chunk length. Length of chunk is %i,"
+ " expected length is %i.", *len, expected_len);
+ return AJP_EBAD_HEADER;
+ }
+ *ptr = (char *)&(msg->buf[msg->pos]);
+ return APR_SUCCESS;
+}
+
+/*
+ * Allocate a msg to send data
+ */
+apr_status_t ajp_alloc_data_msg(apr_pool_t *pool, char **ptr, apr_size_t *len,
+ ajp_msg_t **msg)
+{
+ apr_status_t rc;
+
+ if ((rc = ajp_msg_create(pool, msg)) != APR_SUCCESS)
+ return rc;
+ ajp_msg_reset(*msg);
+ *ptr = (char *)&((*msg)->buf[6]);
+ *len = AJP_MSG_BUFFER_SZ-6;
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Send the data message
+ */
+apr_status_t ajp_send_data_msg(apr_socket_t *sock,
+ ajp_msg_t *msg, apr_size_t len)
+{
+
+ msg->buf[4] = (apr_byte_t)((len >> 8) & 0xFF);
+ msg->buf[5] = (apr_byte_t)(len & 0xFF);
+
+ msg->len += len + 2; /* + 1 XXXX where is '\0' */
+
+ return ajp_ilink_send(sock, msg);
+
+}
diff --git a/modules/proxy/ajp_header.h b/modules/proxy/ajp_header.h
new file mode 100644
index 00000000..bb6adae3
--- /dev/null
+++ b/modules/proxy/ajp_header.h
@@ -0,0 +1,175 @@
+/* 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 ajp_header.h
+ * @brief AJP defines
+ *
+ * @addtogroup AJP_defines
+ * @{
+ */
+
+#ifndef AJP_HEADER_H
+#define AJP_HEADER_H
+
+/*
+ * Conditional request attributes
+ *
+ */
+#define SC_A_CONTEXT (unsigned char)1
+#define SC_A_SERVLET_PATH (unsigned char)2
+#define SC_A_REMOTE_USER (unsigned char)3
+#define SC_A_AUTH_TYPE (unsigned char)4
+#define SC_A_QUERY_STRING (unsigned char)5
+#define SC_A_JVM_ROUTE (unsigned char)6
+#define SC_A_SSL_CERT (unsigned char)7
+#define SC_A_SSL_CIPHER (unsigned char)8
+#define SC_A_SSL_SESSION (unsigned char)9
+#define SC_A_REQ_ATTRIBUTE (unsigned char)10
+#define SC_A_SSL_KEY_SIZE (unsigned char)11 /* only in if JkOptions +ForwardKeySize */
+#define SC_A_SECRET (unsigned char)12
+#define SC_A_ARE_DONE (unsigned char)0xFF
+
+/*
+ * Request methods, coded as numbers instead of strings.
+ * The list of methods was taken from Section 5.1.1 of RFC 2616,
+ * RFC 2518, the ACL IETF draft, and the DeltaV IESG Proposed Standard.
+ * Method = "OPTIONS"
+ * | "GET"
+ * | "HEAD"
+ * | "POST"
+ * | "PUT"
+ * | "DELETE"
+ * | "TRACE"
+ * | "PROPFIND"
+ * | "PROPPATCH"
+ * | "MKCOL"
+ * | "COPY"
+ * | "MOVE"
+ * | "LOCK"
+ * | "UNLOCK"
+ * | "ACL"
+ * | "REPORT"
+ * | "VERSION-CONTROL"
+ * | "CHECKIN"
+ * | "CHECKOUT"
+ * | "UNCHECKOUT"
+ * | "SEARCH"
+ * | "MKWORKSPACE"
+ * | "UPDATE"
+ * | "LABEL"
+ * | "MERGE"
+ * | "BASELINE-CONTROL"
+ * | "MKACTIVITY"
+ *
+ */
+#define SC_M_OPTIONS (unsigned char)1
+#define SC_M_GET (unsigned char)2
+#define SC_M_HEAD (unsigned char)3
+#define SC_M_POST (unsigned char)4
+#define SC_M_PUT (unsigned char)5
+#define SC_M_DELETE (unsigned char)6
+#define SC_M_TRACE (unsigned char)7
+#define SC_M_PROPFIND (unsigned char)8
+#define SC_M_PROPPATCH (unsigned char)9
+#define SC_M_MKCOL (unsigned char)10
+#define SC_M_COPY (unsigned char)11
+#define SC_M_MOVE (unsigned char)12
+#define SC_M_LOCK (unsigned char)13
+#define SC_M_UNLOCK (unsigned char)14
+#define SC_M_ACL (unsigned char)15
+#define SC_M_REPORT (unsigned char)16
+#define SC_M_VERSION_CONTROL (unsigned char)17
+#define SC_M_CHECKIN (unsigned char)18
+#define SC_M_CHECKOUT (unsigned char)19
+#define SC_M_UNCHECKOUT (unsigned char)20
+#define SC_M_SEARCH (unsigned char)21
+#define SC_M_MKWORKSPACE (unsigned char)22
+#define SC_M_UPDATE (unsigned char)23
+#define SC_M_LABEL (unsigned char)24
+#define SC_M_MERGE (unsigned char)25
+#define SC_M_BASELINE_CONTROL (unsigned char)26
+#define SC_M_MKACTIVITY (unsigned char)27
+
+
+/*
+ * Frequent request headers, these headers are coded as numbers
+ * instead of strings.
+ *
+ * Accept
+ * Accept-Charset
+ * Accept-Encoding
+ * Accept-Language
+ * Authorization
+ * Connection
+ * Content-Type
+ * Content-Length
+ * Cookie
+ * Cookie2
+ * Host
+ * Pragma
+ * Referer
+ * User-Agent
+ *
+ */
+
+#define SC_ACCEPT (unsigned short)0xA001
+#define SC_ACCEPT_CHARSET (unsigned short)0xA002
+#define SC_ACCEPT_ENCODING (unsigned short)0xA003
+#define SC_ACCEPT_LANGUAGE (unsigned short)0xA004
+#define SC_AUTHORIZATION (unsigned short)0xA005
+#define SC_CONNECTION (unsigned short)0xA006
+#define SC_CONTENT_TYPE (unsigned short)0xA007
+#define SC_CONTENT_LENGTH (unsigned short)0xA008
+#define SC_COOKIE (unsigned short)0xA009
+#define SC_COOKIE2 (unsigned short)0xA00A
+#define SC_HOST (unsigned short)0xA00B
+#define SC_PRAGMA (unsigned short)0xA00C
+#define SC_REFERER (unsigned short)0xA00D
+#define SC_USER_AGENT (unsigned short)0xA00E
+
+/*
+ * Frequent response headers, these headers are coded as numbers
+ * instead of strings.
+ *
+ * Content-Type
+ * Content-Language
+ * Content-Length
+ * Date
+ * Last-Modified
+ * Location
+ * Set-Cookie
+ * Servlet-Engine
+ * Status
+ * WWW-Authenticate
+ *
+ */
+
+#define SC_RESP_CONTENT_TYPE (unsigned short)0xA001
+#define SC_RESP_CONTENT_LANGUAGE (unsigned short)0xA002
+#define SC_RESP_CONTENT_LENGTH (unsigned short)0xA003
+#define SC_RESP_DATE (unsigned short)0xA004
+#define SC_RESP_LAST_MODIFIED (unsigned short)0xA005
+#define SC_RESP_LOCATION (unsigned short)0xA006
+#define SC_RESP_SET_COOKIE (unsigned short)0xA007
+#define SC_RESP_SET_COOKIE2 (unsigned short)0xA008
+#define SC_RESP_SERVLET_ENGINE (unsigned short)0xA009
+#define SC_RESP_STATUS (unsigned short)0xA00A
+#define SC_RESP_WWW_AUTHENTICATE (unsigned short)0xA00B
+#define SC_RES_HEADERS_NUM 11
+
+#endif /* AJP_HEADER_H */
+/** @} */
diff --git a/modules/proxy/ajp_link.c b/modules/proxy/ajp_link.c
new file mode 100644
index 00000000..04c8f092
--- /dev/null
+++ b/modules/proxy/ajp_link.c
@@ -0,0 +1,126 @@
+/* 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 "ajp.h"
+
+
+apr_status_t ajp_ilink_send(apr_socket_t *sock, ajp_msg_t *msg)
+{
+ char *buf;
+ apr_status_t status;
+ apr_size_t length;
+
+ if (sock == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "ajp_ilink_send(): NULL socket provided");
+ return AJP_EINVAL;
+ }
+
+ ajp_msg_end(msg);
+
+ length = msg->len;
+ buf = (char *)msg->buf;
+
+ do {
+ apr_size_t written = length;
+
+ status = apr_socket_send(sock, buf, &written);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, NULL,
+ "ajp_ilink_send(): send failed");
+ return status;
+ }
+ length -= written;
+ buf += written;
+ } while (length);
+
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t ilink_read(apr_socket_t *sock, apr_byte_t *buf,
+ apr_size_t len)
+{
+ apr_size_t length = len;
+ apr_size_t rdlen = 0;
+ apr_status_t status;
+
+ while (rdlen < len) {
+
+ status = apr_socket_recv(sock, (char *)(buf + rdlen), &length);
+
+ if (status == APR_EOF)
+ return status; /* socket closed. */
+ else if (APR_STATUS_IS_EAGAIN(status))
+ continue;
+ else if (status != APR_SUCCESS)
+ return status; /* any error. */
+
+ rdlen += length;
+ length = len - rdlen;
+ }
+ return APR_SUCCESS;
+}
+
+
+apr_status_t ajp_ilink_receive(apr_socket_t *sock, ajp_msg_t *msg)
+{
+ apr_status_t status;
+ apr_size_t hlen;
+ apr_size_t blen;
+
+ if (sock == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "ajp_ilink_receive(): NULL socket provided");
+ return AJP_EINVAL;
+ }
+
+ hlen = msg->header_len;
+
+ status = ilink_read(sock, msg->buf, hlen);
+
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, NULL,
+ "ajp_ilink_receive() can't receive header");
+ return AJP_ENO_HEADER;
+ }
+
+ status = ajp_msg_check_header(msg, &blen);
+
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "ajp_ilink_receive() received bad header");
+ return AJP_EBAD_HEADER;
+ }
+
+ status = ilink_read(sock, msg->buf + hlen, blen);
+
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, NULL,
+ "ajp_ilink_receive() error while receiving message body "
+ "of length %" APR_SIZE_T_FMT,
+ hlen);
+ return AJP_EBAD_MESSAGE;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "ajp_ilink_receive() received packet len=%" APR_SIZE_T_FMT
+ "type=%d",
+ blen, (int)msg->buf[hlen]);
+
+ return APR_SUCCESS;
+}
+
diff --git a/modules/proxy/ajp_msg.c b/modules/proxy/ajp_msg.c
new file mode 100644
index 00000000..600fd39e
--- /dev/null
+++ b/modules/proxy/ajp_msg.c
@@ -0,0 +1,602 @@
+/* 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 "ajp.h"
+
+
+static char *hex_table = "0123456789ABCDEF";
+
+/**
+ * Dump up to the first 1024 bytes on an AJP Message
+ *
+ * @param pool pool to allocate from
+ * @param msg AJP Message to dump
+ * @param err error string to display
+ * @return dump message
+ */
+char * ajp_msg_dump(apr_pool_t *pool, ajp_msg_t *msg, char *err)
+{
+ apr_size_t i, j;
+ char line[80];
+ char *current;
+ char *rv, *p;
+ apr_size_t bl = 8192;
+ apr_byte_t x;
+ apr_size_t len = msg->len;
+
+ /* Display only first 1024 bytes */
+ if (len > 1024)
+ len = 1024;
+ rv = apr_palloc(pool, bl);
+ apr_snprintf(rv, bl,
+ "ajp_msg_dump(): %s pos=%" APR_SIZE_T_FMT
+ " len=%" APR_SIZE_T_FMT " max=%d\n",
+ err, msg->pos, msg->len, AJP_MSG_BUFFER_SZ);
+ bl -= strlen(rv);
+ p = rv + strlen(rv);
+ for (i = 0; i < len; i += 16) {
+ current = line;
+
+ for (j = 0; j < 16; j++) {
+ x = msg->buf[i + j];
+
+ *current++ = hex_table[x >> 4];
+ *current++ = hex_table[x & 0x0f];
+ *current++ = ' ';
+ }
+ *current++ = ' ';
+ *current++ = '-';
+ *current++ = ' ';
+ for (j = 0; j < 16; j++) {
+ x = msg->buf[i + j];
+
+ if (x > 0x20 && x < 0x7F) {
+ *current++ = x;
+ }
+ else {
+ *current++ = '.';
+ }
+ }
+
+ *current++ = '\0';
+ apr_snprintf(p, bl,
+ "ajp_msg_dump(): %.4lx %s\n",
+ (unsigned long)i, line);
+ bl -= strlen(rv);
+ p = rv + strlen(rv);
+
+ }
+
+ return rv;
+}
+
+
+/**
+ * Check a new AJP Message by looking at signature and return its size
+ *
+ * @param msg AJP Message to check
+ * @param len Pointer to returned len
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_check_header(ajp_msg_t *msg, apr_size_t *len)
+{
+ apr_byte_t *head = msg->buf;
+ apr_size_t msglen;
+
+ if (!((head[0] == 0x41 && head[1] == 0x42) ||
+ (head[0] == 0x12 && head[1] == 0x34))) {
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "ajp_check_msg_header() got bad signature %x%x",
+ head[0], head[1]);
+
+ return AJP_EBAD_SIGNATURE;
+ }
+
+ msglen = ((head[2] & 0xff) << 8);
+ msglen += (head[3] & 0xFF);
+
+ if (msglen > AJP_MSG_BUFFER_SZ) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "ajp_check_msg_header() incoming message is "
+ "too big %" APR_SIZE_T_FMT ", max is %d",
+ msglen, AJP_MSG_BUFFER_SZ);
+ return AJP_ETOBIG;
+ }
+
+ msg->len = msglen + AJP_HEADER_LEN;
+ msg->pos = AJP_HEADER_LEN;
+ *len = msglen;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Reset an AJP Message
+ *
+ * @param msg AJP Message to reset
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_reset(ajp_msg_t *msg)
+{
+ msg->len = AJP_HEADER_LEN;
+ msg->pos = AJP_HEADER_LEN;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Reuse an AJP Message
+ *
+ * @param msg AJP Message to reuse
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_reuse(ajp_msg_t *msg)
+{
+ apr_byte_t *buf;
+
+ buf = msg->buf;
+ memset(msg, 0, sizeof(ajp_msg_t));
+ msg->buf = buf;
+ msg->header_len = AJP_HEADER_LEN;
+ ajp_msg_reset(msg);
+ return APR_SUCCESS;
+}
+
+/**
+ * Mark the end of an AJP Message
+ *
+ * @param msg AJP Message to end
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_end(ajp_msg_t *msg)
+{
+ apr_size_t len = msg->len - AJP_HEADER_LEN;
+
+ if (msg->server_side) {
+ msg->buf[0] = 0x41;
+ msg->buf[1] = 0x42;
+ }
+ else {
+ msg->buf[0] = 0x12;
+ msg->buf[1] = 0x34;
+ }
+
+ msg->buf[2] = (apr_byte_t)((len >> 8) & 0xFF);
+ msg->buf[3] = (apr_byte_t)(len & 0xFF);
+
+ return APR_SUCCESS;
+}
+
+static APR_INLINE int ajp_log_overflow(ajp_msg_t *msg, const char *context)
+{
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "%s(): BufferOverflowException %" APR_SIZE_T_FMT
+ " %" APR_SIZE_T_FMT,
+ context, msg->pos, msg->len);
+ return AJP_EOVERFLOW;
+}
+
+/**
+ * Add an unsigned 32bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint32(ajp_msg_t *msg, apr_uint32_t value)
+{
+ apr_size_t len = msg->len;
+
+ if ((len + 4) > AJP_MSG_BUFFER_SZ) {
+ return ajp_log_overflow(msg, "ajp_msg_append_uint32");
+ }
+
+ msg->buf[len] = (apr_byte_t)((value >> 24) & 0xFF);
+ msg->buf[len + 1] = (apr_byte_t)((value >> 16) & 0xFF);
+ msg->buf[len + 2] = (apr_byte_t)((value >> 8) & 0xFF);
+ msg->buf[len + 3] = (apr_byte_t)(value & 0xFF);
+
+ msg->len += 4;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Add an unsigned 16bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint16(ajp_msg_t *msg, apr_uint16_t value)
+{
+ apr_size_t len = msg->len;
+
+ if ((len + 2) > AJP_MSG_BUFFER_SZ) {
+ return ajp_log_overflow(msg, "ajp_msg_append_uint16");
+ }
+
+ msg->buf[len] = (apr_byte_t)((value >> 8) & 0xFF);
+ msg->buf[len + 1] = (apr_byte_t)(value & 0xFF);
+
+ msg->len += 2;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Add an unsigned 8bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint8(ajp_msg_t *msg, apr_byte_t value)
+{
+ apr_size_t len = msg->len;
+
+ if ((len + 1) > AJP_MSG_BUFFER_SZ) {
+ return ajp_log_overflow(msg, "ajp_msg_append_uint8");
+ }
+
+ msg->buf[len] = value;
+ msg->len += 1;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Add a String in AJP message, and transform the String in ASCII
+ * if convert is set and we're on an EBCDIC machine
+ *
+ * @param msg AJP Message to get value from
+ * @param value Pointer to String
+ * @param convert When set told to convert String to ASCII
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_string_ex(ajp_msg_t *msg, const char *value,
+ int convert)
+{
+ size_t len;
+
+ if (value == NULL) {
+ return(ajp_msg_append_uint16(msg, 0xFFFF));
+ }
+
+ len = strlen(value);
+ if ((msg->len + len + 2) > AJP_MSG_BUFFER_SZ) {
+ return ajp_log_overflow(msg, "ajp_msg_append_cvt_string");
+ }
+
+ /* ignore error - we checked once */
+ ajp_msg_append_uint16(msg, (apr_uint16_t)len);
+
+ /* We checked for space !! */
+ memcpy(msg->buf + msg->len, value, len + 1); /* including \0 */
+
+ if (convert) /* convert from EBCDIC if needed */
+ ajp_xlate_to_ascii((char *)msg->buf + msg->len, len + 1);
+
+ msg->len += len + 1;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Add a Byte array to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value Pointer to Byte array
+ * @param valuelen Byte array len
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_bytes(ajp_msg_t *msg, const apr_byte_t *value,
+ apr_size_t valuelen)
+{
+ if (! valuelen) {
+ return APR_SUCCESS; /* Shouldn't we indicate an error ? */
+ }
+
+ if ((msg->len + valuelen) > AJP_MSG_BUFFER_SZ) {
+ return ajp_log_overflow(msg, "ajp_msg_append_bytes");
+ }
+
+ /* We checked for space !! */
+ memcpy(msg->buf + msg->len, value, valuelen);
+ msg->len += valuelen;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Get a 32bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint32(ajp_msg_t *msg, apr_uint32_t *rvalue)
+{
+ apr_uint32_t value;
+
+ if ((msg->pos + 3) > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_get_uint32");
+ }
+
+ value = ((msg->buf[(msg->pos++)] & 0xFF) << 24);
+ value |= ((msg->buf[(msg->pos++)] & 0xFF) << 16);
+ value |= ((msg->buf[(msg->pos++)] & 0xFF) << 8);
+ value |= ((msg->buf[(msg->pos++)] & 0xFF));
+
+ *rvalue = value;
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Get a 16bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint16(ajp_msg_t *msg, apr_uint16_t *rvalue)
+{
+ apr_uint16_t value;
+
+ if ((msg->pos + 1) > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_get_uint16");
+ }
+
+ value = ((msg->buf[(msg->pos++)] & 0xFF) << 8);
+ value += ((msg->buf[(msg->pos++)] & 0xFF));
+
+ *rvalue = value;
+ return APR_SUCCESS;
+}
+
+/**
+ * Peek a 16bits unsigned value from AJP Message, position in message
+ * is not updated
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_peek_uint16(ajp_msg_t *msg, apr_uint16_t *rvalue)
+{
+ apr_uint16_t value;
+
+ if ((msg->pos + 1) > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_peek_uint16");
+ }
+
+ value = ((msg->buf[(msg->pos)] & 0xFF) << 8);
+ value += ((msg->buf[(msg->pos + 1)] & 0xFF));
+
+ *rvalue = value;
+ return APR_SUCCESS;
+}
+
+/**
+ * Peek a 8bits unsigned value from AJP Message, position in message
+ * is not updated
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_peek_uint8(ajp_msg_t *msg, apr_byte_t *rvalue)
+{
+ if (msg->pos > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_peek_uint8");
+ }
+
+ *rvalue = msg->buf[msg->pos];
+ return APR_SUCCESS;
+}
+
+/**
+ * Get a 8bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint8(ajp_msg_t *msg, apr_byte_t *rvalue)
+{
+
+ if (msg->pos > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_get_uint8");
+ }
+
+ *rvalue = msg->buf[msg->pos++];
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Get a String value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_string(ajp_msg_t *msg, const char **rvalue)
+{
+ apr_uint16_t size;
+ apr_size_t start;
+ apr_status_t status;
+
+ status = ajp_msg_get_uint16(msg, &size);
+ start = msg->pos;
+
+ if ((status != APR_SUCCESS) || (size + start > AJP_MSG_BUFFER_SZ)) {
+ return ajp_log_overflow(msg, "ajp_msg_get_string");
+ }
+
+ msg->pos += (apr_size_t)size;
+ msg->pos++; /* a String in AJP is NULL terminated */
+
+ *rvalue = (const char *)(msg->buf + start);
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Get a Byte array from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @param rvalueLen Pointer where Byte array len will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_bytes(ajp_msg_t *msg, apr_byte_t **rvalue,
+ apr_size_t *rvalue_len)
+{
+ apr_uint16_t size;
+ apr_size_t start;
+ apr_status_t status;
+
+ status = ajp_msg_get_uint16(msg, &size);
+ /* save the current position */
+ start = msg->pos;
+
+ if ((status != APR_SUCCESS) || (size + start > AJP_MSG_BUFFER_SZ)) {
+ return ajp_log_overflow(msg, "ajp_msg_get_bytes");
+ }
+ msg->pos += (apr_size_t)size; /* only bytes, no trailer */
+
+ *rvalue = msg->buf + start;
+ *rvalue_len = size;
+
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Create an AJP Message from pool
+ *
+ * @param pool memory pool to allocate AJP message from
+ * @param rmsg Pointer to newly created AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_create(apr_pool_t *pool, ajp_msg_t **rmsg)
+{
+ ajp_msg_t *msg = (ajp_msg_t *)apr_pcalloc(pool, sizeof(ajp_msg_t));
+
+ if (!msg) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "ajp_msg_create(): can't allocate AJP message memory");
+ return APR_ENOPOOL;
+ }
+
+ msg->server_side = 0;
+
+ msg->buf = (apr_byte_t *)apr_palloc(pool, AJP_MSG_BUFFER_SZ);
+
+ /* XXX: This should never happen
+ * In case if the OS cannont allocate 8K of data
+ * we are in serious trouble
+ * No need to check the alloc return value, cause the
+ * core dump is probably the best solution anyhow.
+ */
+ if (msg->buf == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "ajp_msg_create(): can't allocate AJP message memory");
+ return APR_ENOPOOL;
+ }
+
+ msg->len = 0;
+ msg->header_len = AJP_HEADER_LEN;
+ *rmsg = msg;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Recopy an AJP Message to another
+ *
+ * @param smsg source AJP message
+ * @param dmsg destination AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_copy(ajp_msg_t *smsg, ajp_msg_t *dmsg)
+{
+ if (dmsg == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "ajp_msg_copy(): destination msg is null");
+ return AJP_EINVAL;
+ }
+
+ if (smsg->len > AJP_MSG_BUFFER_SZ) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "ajp_msg_copy(): destination buffer too "
+ "small %" APR_SIZE_T_FMT ", max size is %d",
+ smsg->len, AJP_MSG_BUFFER_SZ);
+ return AJP_ETOSMALL;
+ }
+
+ memcpy(dmsg->buf, smsg->buf, smsg->len);
+ dmsg->len = smsg->len;
+ dmsg->pos = smsg->pos;
+
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Serialize in an AJP Message a PING command
+ *
+ * +-----------------------+
+ * | PING CMD (1 byte) |
+ * +-----------------------+
+ *
+ * @param smsg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_serialize_ping(ajp_msg_t *msg)
+{
+ apr_status_t rc;
+ ajp_msg_reset(msg);
+
+ if ((rc = ajp_msg_append_uint8(msg, CMD_AJP13_PING)) != APR_SUCCESS)
+ return rc;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Serialize in an AJP Message a CPING command
+ *
+ * +-----------------------+
+ * | CPING CMD (1 byte) |
+ * +-----------------------+
+ *
+ * @param smsg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_serialize_cping(ajp_msg_t *msg)
+{
+ apr_status_t rc;
+ ajp_msg_reset(msg);
+
+ if ((rc = ajp_msg_append_uint8(msg, CMD_AJP13_CPING)) != APR_SUCCESS)
+ return rc;
+
+ return APR_SUCCESS;
+}
diff --git a/modules/proxy/config.m4 b/modules/proxy/config.m4
new file mode 100644
index 00000000..f131ee66
--- /dev/null
+++ b/modules/proxy/config.m4
@@ -0,0 +1,42 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(proxy)
+
+if test "$enable_proxy" = "shared"; then
+ proxy_mods_enable=shared
+elif test "$enable_proxy" = "yes"; then
+ proxy_mods_enable=yes
+else
+ proxy_mods_enable=no
+fi
+
+proxy_objs="mod_proxy.lo proxy_util.lo"
+APACHE_MODULE(proxy, Apache proxy module, $proxy_objs, , $proxy_mods_enable)
+
+proxy_connect_objs="mod_proxy_connect.lo"
+proxy_ftp_objs="mod_proxy_ftp.lo"
+proxy_http_objs="mod_proxy_http.lo"
+proxy_ajp_objs="mod_proxy_ajp.lo ajp_header.lo ajp_link.lo ajp_msg.lo"
+proxy_balancer_objs="mod_proxy_balancer.lo"
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time and
+ # these sub-modules need some from the main proxy module
+ proxy_connect_objs="$proxy_connect_objs mod_proxy.la"
+ proxy_ftp_objs="$proxy_ftp_objs mod_proxy.la"
+ proxy_http_objs="$proxy_http_objs mod_proxy.la"
+ proxy_ajp_objs="$proxy_ajp_objs mod_proxy.la"
+ proxy_balancer_objs="$proxy_balancer_objs mod_proxy.la"
+ ;;
+esac
+
+APACHE_MODULE(proxy_connect, Apache proxy CONNECT module, $proxy_connect_objs, , $proxy_mods_enable)
+APACHE_MODULE(proxy_ftp, Apache proxy FTP module, $proxy_ftp_objs, , $proxy_mods_enable)
+APACHE_MODULE(proxy_http, Apache proxy HTTP module, $proxy_http_objs, , $proxy_mods_enable)
+APACHE_MODULE(proxy_ajp, Apache proxy AJP module, $proxy_ajp_objs, , $proxy_mods_enable)
+APACHE_MODULE(proxy_balancer, Apache proxy BALANCER module, $proxy_balancer_objs, , $proxy_mods_enable)
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current/../generators])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/proxy/libproxy.exp b/modules/proxy/libproxy.exp
new file mode 100644
index 00000000..a20f2378
--- /dev/null
+++ b/modules/proxy/libproxy.exp
@@ -0,0 +1 @@
+proxy_module
diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c
new file mode 100644
index 00000000..09ef3a72
--- /dev/null
+++ b/modules/proxy/mod_proxy.c
@@ -0,0 +1,1984 @@
+/* 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 CORE_PRIVATE
+
+#include "mod_proxy.h"
+#include "mod_core.h"
+#include "apr_optional.h"
+#include "scoreboard.h"
+#include "mod_status.h"
+
+#if (MODULE_MAGIC_NUMBER_MAJOR > 20020903)
+#include "mod_ssl.h"
+#else
+APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
+ (apr_pool_t *, server_rec *,
+ conn_rec *, request_rec *, char *));
+#endif
+
+#ifndef MAX
+#define MAX(x,y) ((x) >= (y) ? (x) : (y))
+#endif
+
+/*
+ * A Web proxy module. Stages:
+ *
+ * translate_name: set filename to proxy:<URL>
+ * map_to_storage: run proxy_walk (rather than directory_walk/file_walk)
+ * can't trust directory_walk/file_walk since these are
+ * not in our filesystem. Prevents mod_http from serving
+ * the TRACE request we will set aside to handle later.
+ * type_checker: set type to PROXY_MAGIC_TYPE if filename begins proxy:
+ * fix_ups: convert the URL stored in the filename to the
+ * canonical form.
+ * handler: handle proxy requests
+ */
+
+/* -------------------------------------------------------------- */
+/* Translate the URL into a 'filename' */
+
+#define PROXY_COPY_CONF_PARAMS(w, c) \
+ do { \
+ (w)->timeout = (c)->timeout; \
+ (w)->timeout_set = (c)->timeout_set; \
+ (w)->recv_buffer_size = (c)->recv_buffer_size; \
+ (w)->recv_buffer_size_set = (c)->recv_buffer_size_set; \
+ (w)->io_buffer_size = (c)->io_buffer_size; \
+ (w)->io_buffer_size_set = (c)->io_buffer_size_set; \
+ } while (0)
+
+static const char *set_worker_param(apr_pool_t *p,
+ proxy_worker *worker,
+ const char *key,
+ const char *val)
+{
+
+ int ival;
+ if (!strcasecmp(key, "loadfactor")) {
+ /* Normalized load factor. Used with BalancerMamber,
+ * it is a number between 1 and 100.
+ */
+ worker->lbfactor = atoi(val);
+ if (worker->lbfactor < 1 || worker->lbfactor > 100)
+ return "LoadFactor must be number between 1..100";
+ }
+ else if (!strcasecmp(key, "retry")) {
+ /* If set it will give the retry timeout for the worker
+ * The default value is 60 seconds, meaning that if
+ * in error state, it will be retried after that timeout.
+ */
+ ival = atoi(val);
+ if (ival < 1)
+ return "Retry must be at least one second";
+ worker->retry = apr_time_from_sec(ival);
+ }
+ else if (!strcasecmp(key, "ttl")) {
+ /* Time in seconds that will destroy all the connections
+ * that exced the smax
+ */
+ ival = atoi(val);
+ if (ival < 1)
+ return "TTL must be at least one second";
+ worker->ttl = apr_time_from_sec(ival);
+ }
+ else if (!strcasecmp(key, "min")) {
+ /* Initial number of connections to remote
+ */
+ ival = atoi(val);
+ if (ival < 0)
+ return "Min must be a positive number";
+ worker->min = ival;
+ }
+ else if (!strcasecmp(key, "max")) {
+ /* Maximum number of connections to remote
+ */
+ ival = atoi(val);
+ if (ival < 0)
+ return "Max must be a positive number";
+ worker->hmax = ival;
+ }
+ /* XXX: More inteligent naming needed */
+ else if (!strcasecmp(key, "smax")) {
+ /* Maximum number of connections to remote that
+ * will not be destroyed
+ */
+ ival = atoi(val);
+ if (ival < 0)
+ return "Smax must be a positive number";
+ worker->smax = ival;
+ }
+ else if (!strcasecmp(key, "acquire")) {
+ /* Acquire timeout in milliseconds.
+ * If set this will be the maximum time to
+ * wait for a free connection.
+ */
+ ival = atoi(val);
+ if (ival < 1)
+ return "Acquire must be at least one mili second";
+ worker->acquire = apr_time_make(0, ival * 1000);
+ worker->acquire_set = 1;
+ }
+ else if (!strcasecmp(key, "timeout")) {
+ /* Connection timeout in seconds.
+ * Defaults to server timeout.
+ */
+ ival = atoi(val);
+ if (ival < 1)
+ return "Timeout must be at least one second";
+ worker->timeout = apr_time_from_sec(ival);
+ worker->timeout_set = 1;
+ }
+ else if (!strcasecmp(key, "iobuffersize")) {
+ long s = atol(val);
+ worker->io_buffer_size = ((s > AP_IOBUFSIZE) ? s : AP_IOBUFSIZE);
+ worker->io_buffer_size_set = 1;
+ }
+ else if (!strcasecmp(key, "receivebuffersize")) {
+ ival = atoi(val);
+ if (ival < 512 && ival != 0) {
+ return "ReceiveBufferSize must be >= 512 bytes, or 0 for system default.";
+ }
+ worker->recv_buffer_size = ival;
+ worker->recv_buffer_size_set = 1;
+ }
+ else if (!strcasecmp(key, "keepalive")) {
+ if (!strcasecmp(val, "on"))
+ worker->keepalive = 1;
+ else if (!strcasecmp(val, "off"))
+ worker->keepalive = 0;
+ else
+ return "KeepAlive must be On|Off";
+ worker->keepalive_set = 1;
+ }
+ else if (!strcasecmp(key, "route")) {
+ /* Worker route.
+ */
+ if (strlen(val) > PROXY_WORKER_MAX_ROUTE_SIZ)
+ return "Route length must be < 64 characters";
+ worker->route = apr_pstrdup(p, val);
+ }
+ else if (!strcasecmp(key, "redirect")) {
+ /* Worker redirection route.
+ */
+ if (strlen(val) > PROXY_WORKER_MAX_ROUTE_SIZ)
+ return "Redirect length must be < 64 characters";
+ worker->redirect = apr_pstrdup(p, val);
+ }
+ else if (!strcasecmp(key, "status")) {
+ const char *v;
+ int mode = 1;
+ /* Worker status.
+ */
+ for (v = val; *v; v++) {
+ if (*v == '+') {
+ mode = 1;
+ v++;
+ }
+ else if (*v == '-') {
+ mode = 0;
+ v++;
+ }
+ if (*v == 'D' || *v == 'd') {
+ if (mode)
+ worker->status |= PROXY_WORKER_DISABLED;
+ else
+ worker->status &= ~PROXY_WORKER_DISABLED;
+ }
+ else if (*v == 'S' || *v == 's') {
+ if (mode)
+ worker->status |= PROXY_WORKER_STOPPED;
+ else
+ worker->status &= ~PROXY_WORKER_STOPPED;
+ }
+ else if (*v == 'E' || *v == 'e') {
+ if (mode)
+ worker->status |= PROXY_WORKER_IN_ERROR;
+ else
+ worker->status &= ~PROXY_WORKER_IN_ERROR;
+ }
+ else {
+ return "Unknow status parameter option";
+ }
+ }
+ }
+ else if (!strcasecmp(key, "flushpackets")) {
+ if (!strcasecmp(val, "on"))
+ worker->flush_packets = flush_on;
+ else if (!strcasecmp(val, "off"))
+ worker->flush_packets = flush_off;
+ else if (!strcasecmp(val, "auto"))
+ worker->flush_packets = flush_auto;
+ else
+ return "flushpackets must be on|off|auto";
+ }
+ else if (!strcasecmp(key, "flushwait")) {
+ ival = atoi(val);
+ if (ival > 1000 || ival < 0) {
+ return "flushwait must be <= 1000, or 0 for system default of 10 millseconds.";
+ }
+ if (ival == 0)
+ worker->flush_wait = PROXY_FLUSH_WAIT;
+ else
+ worker->flush_wait = ival * 1000; /* change to microseconds */
+ }
+ else {
+ return "unknown Worker parameter";
+ }
+ return NULL;
+}
+
+static const char *set_balancer_param(proxy_server_conf *conf,
+ apr_pool_t *p,
+ proxy_balancer *balancer,
+ const char *key,
+ const char *val)
+{
+
+ int ival;
+ if (!strcasecmp(key, "stickysession")) {
+ /* Balancer sticky session name.
+ * Set to something like JSESSIONID or
+ * PHPSESSIONID, etc..,
+ */
+ balancer->sticky = apr_pstrdup(p, val);
+ }
+ else if (!strcasecmp(key, "nofailover")) {
+ /* If set to 'on' the session will break
+ * if the worker is in error state or
+ * disabled.
+ */
+ if (!strcasecmp(val, "on"))
+ balancer->sticky_force = 1;
+ else if (!strcasecmp(val, "off"))
+ balancer->sticky_force = 0;
+ else
+ return "failover must be On|Off";
+ }
+ else if (!strcasecmp(key, "timeout")) {
+ /* Balancer timeout in seconds.
+ * If set this will be the maximum time to
+ * wait for a free worker.
+ * Default is not to wait.
+ */
+ ival = atoi(val);
+ if (ival < 1)
+ return "timeout must be at least one second";
+ balancer->timeout = apr_time_from_sec(ival);
+ }
+ else if (!strcasecmp(key, "maxattempts")) {
+ /* Maximum number of failover attempts before
+ * giving up.
+ */
+ ival = atoi(val);
+ if (ival < 0)
+ return "maximum number of attempts must be a positive number";
+ balancer->max_attempts = ival;
+ balancer->max_attempts_set = 1;
+ }
+ else if (!strcasecmp(key, "lbmethod")) {
+ proxy_balancer_method *provider;
+ provider = ap_lookup_provider(PROXY_LBMETHOD, val, "0");
+ if (provider) {
+ balancer->lbmethod = provider;
+ return NULL;
+ }
+ return "unknown lbmethod";
+ }
+ else {
+ return "unknown Balancer parameter";
+ }
+ return NULL;
+}
+
+static int alias_match(const char *uri, const char *alias_fakename)
+{
+ const char *end_fakename = alias_fakename + strlen(alias_fakename);
+ const char *aliasp = alias_fakename, *urip = uri;
+ const char *end_uri = uri + strlen(uri);
+
+ while (aliasp < end_fakename && urip < end_uri) {
+ if (*aliasp == '/') {
+ /* any number of '/' in the alias matches any number in
+ * the supplied URI, but there must be at least one...
+ */
+ if (*urip != '/')
+ return 0;
+
+ while (*aliasp == '/')
+ ++aliasp;
+ while (*urip == '/')
+ ++urip;
+ }
+ else {
+ /* Other characters are compared literally */
+ if (*urip++ != *aliasp++)
+ return 0;
+ }
+ }
+
+ /* fixup badly encoded stuff (e.g. % as last character) */
+ if (aliasp > end_fakename) {
+ aliasp = end_fakename;
+ }
+ if (urip > end_uri) {
+ urip = end_uri;
+ }
+
+ /* We reach the end of the uri before the end of "alias_fakename"
+ * for example uri is "/" and alias_fakename "/examples"
+ */
+ if (urip == end_uri && aliasp!=end_fakename) {
+ return 0;
+ }
+
+ /* Check last alias path component matched all the way */
+ if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/')
+ return 0;
+
+ /* Return number of characters from URI which matched (may be
+ * greater than length of alias, since we may have matched
+ * doubled slashes)
+ */
+
+ return urip - uri;
+}
+
+/* Detect if an absoluteURI should be proxied or not. Note that we
+ * have to do this during this phase because later phases are
+ * "short-circuiting"... i.e. translate_names will end when the first
+ * module returns OK. So for example, if the request is something like:
+ *
+ * GET http://othervhost/cgi-bin/printenv HTTP/1.0
+ *
+ * mod_alias will notice the /cgi-bin part and ScriptAlias it and
+ * short-circuit the proxy... just because of the ordering in the
+ * configuration file.
+ */
+static int proxy_detect(request_rec *r)
+{
+ void *sconf = r->server->module_config;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+
+ /* Ick... msvc (perhaps others) promotes ternary short results to int */
+
+ if (conf->req && r->parsed_uri.scheme) {
+ /* but it might be something vhosted */
+ if (!(r->parsed_uri.hostname
+ && !strcasecmp(r->parsed_uri.scheme, ap_http_scheme(r))
+ && ap_matches_request_vhost(r, r->parsed_uri.hostname,
+ (apr_port_t)(r->parsed_uri.port_str ? r->parsed_uri.port
+ : ap_default_port(r))))) {
+ r->proxyreq = PROXYREQ_PROXY;
+ r->uri = r->unparsed_uri;
+ r->filename = apr_pstrcat(r->pool, "proxy:", r->uri, NULL);
+ r->handler = "proxy-server";
+ }
+ }
+ /* We need special treatment for CONNECT proxying: it has no scheme part */
+ else if (conf->req && r->method_number == M_CONNECT
+ && r->parsed_uri.hostname
+ && r->parsed_uri.port_str) {
+ r->proxyreq = PROXYREQ_PROXY;
+ r->uri = r->unparsed_uri;
+ r->filename = apr_pstrcat(r->pool, "proxy:", r->uri, NULL);
+ r->handler = "proxy-server";
+ }
+ return DECLINED;
+}
+
+static int proxy_trans(request_rec *r)
+{
+ void *sconf = r->server->module_config;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+ int i, len;
+ struct proxy_alias *ent = (struct proxy_alias *) conf->aliases->elts;
+
+ if (r->proxyreq) {
+ /* someone has already set up the proxy, it was possibly ourselves
+ * in proxy_detect
+ */
+ return OK;
+ }
+
+ /* XXX: since r->uri has been manipulated already we're not really
+ * compliant with RFC1945 at this point. But this probably isn't
+ * an issue because this is a hybrid proxy/origin server.
+ */
+
+ for (i = 0; i < conf->aliases->nelts; i++) {
+ len = alias_match(r->uri, ent[i].fake);
+
+ if (len > 0) {
+ if ((ent[i].real[0] == '!') && (ent[i].real[1] == 0)) {
+ return DECLINED;
+ }
+
+ r->filename = apr_pstrcat(r->pool, "proxy:", ent[i].real,
+ r->uri + len, NULL);
+ r->handler = "proxy-server";
+ r->proxyreq = PROXYREQ_REVERSE;
+ return OK;
+ }
+ }
+ return DECLINED;
+}
+
+static int proxy_walk(request_rec *r)
+{
+ proxy_server_conf *sconf = ap_get_module_config(r->server->module_config,
+ &proxy_module);
+ ap_conf_vector_t *per_dir_defaults = r->server->lookup_defaults;
+ ap_conf_vector_t **sec_proxy = (ap_conf_vector_t **) sconf->sec_proxy->elts;
+ ap_conf_vector_t *entry_config;
+ proxy_dir_conf *entry_proxy;
+ int num_sec = sconf->sec_proxy->nelts;
+ /* XXX: shouldn't we use URI here? Canonicalize it first?
+ * Pass over "proxy:" prefix
+ */
+ const char *proxyname = r->filename + 6;
+ int j;
+
+ for (j = 0; j < num_sec; ++j)
+ {
+ entry_config = sec_proxy[j];
+ entry_proxy = ap_get_module_config(entry_config, &proxy_module);
+
+ /* XXX: What about case insensitive matching ???
+ * Compare regex, fnmatch or string as appropriate
+ * If the entry doesn't relate, then continue
+ */
+ if (entry_proxy->r
+ ? ap_regexec(entry_proxy->r, proxyname, 0, NULL, 0)
+ : (entry_proxy->p_is_fnmatch
+ ? apr_fnmatch(entry_proxy->p, proxyname, 0)
+ : strncmp(proxyname, entry_proxy->p,
+ strlen(entry_proxy->p)))) {
+ continue;
+ }
+ per_dir_defaults = ap_merge_per_dir_configs(r->pool, per_dir_defaults,
+ entry_config);
+ }
+
+ r->per_dir_config = per_dir_defaults;
+
+ return OK;
+}
+
+static int proxy_map_location(request_rec *r)
+{
+ int access_status;
+
+ if (!r->proxyreq || !r->filename || strncmp(r->filename, "proxy:", 6) != 0)
+ return DECLINED;
+
+ /* Don't let the core or mod_http map_to_storage hooks handle this,
+ * We don't need directory/file_walk, and we want to TRACE on our own.
+ */
+ if ((access_status = proxy_walk(r))) {
+ ap_die(access_status, r);
+ return access_status;
+ }
+
+ return OK;
+}
+/* -------------------------------------------------------------- */
+/* Fixup the filename */
+
+/*
+ * Canonicalise the URL
+ */
+static int proxy_fixup(request_rec *r)
+{
+ char *url, *p;
+ int access_status;
+
+ if (!r->proxyreq || !r->filename || strncmp(r->filename, "proxy:", 6) != 0)
+ return DECLINED;
+
+ /* XXX: Shouldn't we try this before we run the proxy_walk? */
+ url = &r->filename[6];
+
+ /* canonicalise each specific scheme */
+ if ((access_status = proxy_run_canon_handler(r, url))) {
+ return access_status;
+ }
+
+ p = strchr(url, ':');
+ if (p == NULL || p == url)
+ return HTTP_BAD_REQUEST;
+
+ return OK; /* otherwise; we've done the best we can */
+}
+/* Send a redirection if the request contains a hostname which is not */
+/* fully qualified, i.e. doesn't have a domain name appended. Some proxy */
+/* servers like Netscape's allow this and access hosts from the local */
+/* domain in this case. I think it is better to redirect to a FQDN, since */
+/* these will later be found in the bookmarks files. */
+/* The "ProxyDomain" directive determines what domain will be appended */
+static int proxy_needsdomain(request_rec *r, const char *url, const char *domain)
+{
+ char *nuri;
+ const char *ref;
+
+ /* We only want to worry about GETs */
+ if (!r->proxyreq || r->method_number != M_GET || !r->parsed_uri.hostname)
+ return DECLINED;
+
+ /* If host does contain a dot already, or it is "localhost", decline */
+ if (strchr(r->parsed_uri.hostname, '.') != NULL
+ || strcasecmp(r->parsed_uri.hostname, "localhost") == 0)
+ return DECLINED; /* host name has a dot already */
+
+ ref = apr_table_get(r->headers_in, "Referer");
+
+ /* Reassemble the request, but insert the domain after the host name */
+ /* Note that the domain name always starts with a dot */
+ r->parsed_uri.hostname = apr_pstrcat(r->pool, r->parsed_uri.hostname,
+ domain, NULL);
+ nuri = apr_uri_unparse(r->pool,
+ &r->parsed_uri,
+ APR_URI_UNP_REVEALPASSWORD);
+
+ apr_table_set(r->headers_out, "Location", nuri);
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "Domain missing: %s sent to %s%s%s", r->uri,
+ apr_uri_unparse(r->pool, &r->parsed_uri,
+ APR_URI_UNP_OMITUSERINFO),
+ ref ? " from " : "", ref ? ref : "");
+
+ return HTTP_MOVED_PERMANENTLY;
+}
+
+/* -------------------------------------------------------------- */
+/* Invoke handler */
+
+static int proxy_handler(request_rec *r)
+{
+ char *uri, *scheme, *p;
+ const char *p2;
+ void *sconf = r->server->module_config;
+ proxy_server_conf *conf = (proxy_server_conf *)
+ ap_get_module_config(sconf, &proxy_module);
+ apr_array_header_t *proxies = conf->proxies;
+ struct proxy_remote *ents = (struct proxy_remote *) proxies->elts;
+ int i, rc, access_status;
+ int direct_connect = 0;
+ const char *str;
+ long maxfwd;
+ proxy_balancer *balancer = NULL;
+ proxy_worker *worker = NULL;
+ int attempts = 0, max_attempts = 0;
+ struct dirconn_entry *list = (struct dirconn_entry *)conf->dirconn->elts;
+
+ /* is this for us? */
+ if (!r->proxyreq || !r->filename || strncmp(r->filename, "proxy:", 6) != 0)
+ return DECLINED;
+
+ /* handle max-forwards / OPTIONS / TRACE */
+ if ((str = apr_table_get(r->headers_in, "Max-Forwards"))) {
+ maxfwd = strtol(str, NULL, 10);
+ if (maxfwd < 1) {
+ switch (r->method_number) {
+ case M_TRACE: {
+ int access_status;
+ r->proxyreq = PROXYREQ_NONE;
+ if ((access_status = ap_send_http_trace(r)))
+ ap_die(access_status, r);
+ else
+ ap_finalize_request_protocol(r);
+ return OK;
+ }
+ case M_OPTIONS: {
+ int access_status;
+ r->proxyreq = PROXYREQ_NONE;
+ if ((access_status = ap_send_http_options(r)))
+ ap_die(access_status, r);
+ else
+ ap_finalize_request_protocol(r);
+ return OK;
+ }
+ default: {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Max-Forwards has reached zero - proxy loop?");
+ }
+ }
+ }
+ maxfwd = (maxfwd > 0) ? maxfwd - 1 : 0;
+ }
+ else {
+ /* set configured max-forwards */
+ maxfwd = conf->maxfwd;
+ }
+ apr_table_set(r->headers_in, "Max-Forwards",
+ apr_psprintf(r->pool, "%ld", (maxfwd > 0) ? maxfwd : 0));
+
+ if (r->method_number == M_TRACE) {
+ core_server_config *coreconf = (core_server_config *)
+ ap_get_module_config(sconf, &core_module);
+
+ if (coreconf->trace_enable == AP_TRACE_DISABLE)
+ {
+ /* Allow "error-notes" string to be printed by ap_send_error_response()
+ * Note; this goes nowhere, canned error response need an overhaul.
+ */
+ apr_table_setn(r->notes, "error-notes",
+ "TRACE forbidden by server configuration");
+ apr_table_setn(r->notes, "verbose-error-to", "*");
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "proxy: TRACE forbidden by server configuration");
+ return HTTP_FORBIDDEN;
+ }
+
+ /* Can't test ap_should_client_block, we aren't ready to send
+ * the client a 100 Continue response till the connection has
+ * been established
+ */
+ if (coreconf->trace_enable != AP_TRACE_EXTENDED
+ && (r->read_length || r->read_chunked || r->remaining))
+ {
+ /* Allow "error-notes" string to be printed by ap_send_error_response()
+ * Note; this goes nowhere, canned error response need an overhaul.
+ */
+ apr_table_setn(r->notes, "error-notes",
+ "TRACE with request body is not allowed");
+ apr_table_setn(r->notes, "verbose-error-to", "*");
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "proxy: TRACE with request body is not allowed");
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+ }
+
+ uri = r->filename + 6;
+ p = strchr(uri, ':');
+ if (p == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "proxy_handler no URL in %s", r->filename);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* If the host doesn't have a domain name, add one and redirect. */
+ if (conf->domain != NULL) {
+ rc = proxy_needsdomain(r, uri, conf->domain);
+ if (ap_is_HTTP_REDIRECT(rc))
+ return HTTP_MOVED_PERMANENTLY;
+ }
+
+ scheme = apr_pstrndup(r->pool, uri, p - uri);
+ /* Check URI's destination host against NoProxy hosts */
+ /* Bypass ProxyRemote server lookup if configured as NoProxy */
+ for (direct_connect = i = 0; i < conf->dirconn->nelts &&
+ !direct_connect; i++) {
+ direct_connect = list[i].matcher(&list[i], r);
+ }
+#if DEBUGGING
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ (direct_connect) ? "NoProxy for %s" : "UseProxy for %s",
+ r->uri);
+#endif
+
+ do {
+ char *url = uri;
+ /* Try to obtain the most suitable worker */
+ access_status = ap_proxy_pre_request(&worker, &balancer, r, conf, &url);
+ if (access_status != OK) {
+ /*
+ * Only return if access_status is not HTTP_SERVICE_UNAVAILABLE
+ * This gives other modules the chance to hook into the
+ * request_status hook and decide what to do in this situation.
+ */
+ if (access_status != HTTP_SERVICE_UNAVAILABLE)
+ return access_status;
+ /*
+ * Ensure that balancer is NULL if worker is NULL to prevent
+ * potential problems in the post_request hook.
+ */
+ if (!worker)
+ balancer = NULL;
+ goto cleanup;
+ }
+ if (balancer && balancer->max_attempts_set && !max_attempts)
+ max_attempts = balancer->max_attempts;
+ /* firstly, try a proxy, unless a NoProxy directive is active */
+ if (!direct_connect) {
+ for (i = 0; i < proxies->nelts; i++) {
+ p2 = ap_strchr_c(ents[i].scheme, ':'); /* is it a partial URL? */
+ if (strcmp(ents[i].scheme, "*") == 0 ||
+ (ents[i].use_regex &&
+ ap_regexec(ents[i].regexp, url, 0, NULL, 0) == 0) ||
+ (p2 == NULL && strcasecmp(scheme, ents[i].scheme) == 0) ||
+ (p2 != NULL &&
+ strncasecmp(url, ents[i].scheme,
+ strlen(ents[i].scheme)) == 0)) {
+
+ /* handle the scheme */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Trying to run scheme_handler against proxy");
+ access_status = proxy_run_scheme_handler(r, worker,
+ conf, url,
+ ents[i].hostname,
+ ents[i].port);
+
+ /* an error or success */
+ if (access_status != DECLINED &&
+ access_status != HTTP_BAD_GATEWAY) {
+ goto cleanup;
+ }
+ /* we failed to talk to the upstream proxy */
+ }
+ }
+ }
+
+ /* otherwise, try it direct */
+ /* N.B. what if we're behind a firewall, where we must use a proxy or
+ * give up??
+ */
+
+ /* handle the scheme */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Running scheme %s handler (attempt %d)",
+ scheme, attempts);
+ access_status = proxy_run_scheme_handler(r, worker, conf,
+ url, NULL, 0);
+ if (access_status == OK)
+ break;
+ else if (access_status == HTTP_INTERNAL_SERVER_ERROR) {
+ /* Unrecoverable server error.
+ * We can not failover to another worker.
+ * Mark the worker as unusable if member of load balancer
+ */
+ if (balancer)
+ worker->s->status |= PROXY_WORKER_IN_ERROR;
+ break;
+ }
+ else if (access_status == HTTP_SERVICE_UNAVAILABLE) {
+ /* Recoverable server error.
+ * We can failover to another worker
+ * Mark the worker as unusable if member of load balancer
+ */
+ if (balancer) {
+ worker->s->status |= PROXY_WORKER_IN_ERROR;
+ }
+ }
+ else {
+ /* Unrecoverable error.
+ * Return the origin status code to the client.
+ */
+ break;
+ }
+ /* Try again if the worker is unusable and the service is
+ * unavailable.
+ */
+ } while (!PROXY_WORKER_IS_USABLE(worker) &&
+ max_attempts > attempts++);
+
+ if (DECLINED == access_status) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
+ "proxy: No protocol handler was valid for the URL %s. "
+ "If you are using a DSO version of mod_proxy, make sure "
+ "the proxy submodules are included in the configuration "
+ "using LoadModule.", r->uri);
+ access_status = HTTP_FORBIDDEN;
+ goto cleanup;
+ }
+cleanup:
+ if (balancer) {
+ int post_status = proxy_run_post_request(worker, balancer, r, conf);
+ if (post_status == DECLINED) {
+ post_status = OK; /* no post_request handler available */
+ /* TODO: recycle direct worker */
+ }
+ }
+
+ proxy_run_request_status(&access_status, r);
+
+ return access_status;
+}
+
+/* -------------------------------------------------------------- */
+/* Setup configurable data */
+
+static void * create_proxy_config(apr_pool_t *p, server_rec *s)
+{
+ proxy_server_conf *ps = apr_pcalloc(p, sizeof(proxy_server_conf));
+
+ ps->sec_proxy = apr_array_make(p, 10, sizeof(ap_conf_vector_t *));
+ ps->proxies = apr_array_make(p, 10, sizeof(struct proxy_remote));
+ ps->aliases = apr_array_make(p, 10, sizeof(struct proxy_alias));
+ ps->noproxies = apr_array_make(p, 10, sizeof(struct noproxy_entry));
+ ps->dirconn = apr_array_make(p, 10, sizeof(struct dirconn_entry));
+ ps->allowed_connect_ports = apr_array_make(p, 10, sizeof(int));
+ ps->workers = apr_array_make(p, 10, sizeof(proxy_worker));
+ ps->balancers = apr_array_make(p, 10, sizeof(proxy_balancer));
+ ps->forward = NULL;
+ ps->reverse = NULL;
+ ps->domain = NULL;
+ ps->viaopt = via_off; /* initially backward compatible with 1.3.1 */
+ ps->viaopt_set = 0; /* 0 means default */
+ ps->req = 0;
+ ps->req_set = 0;
+ ps->recv_buffer_size = 0; /* this default was left unset for some reason */
+ ps->recv_buffer_size_set = 0;
+ ps->io_buffer_size = AP_IOBUFSIZE;
+ ps->io_buffer_size_set = 0;
+ ps->maxfwd = DEFAULT_MAX_FORWARDS;
+ ps->maxfwd_set = 0;
+ ps->error_override = 0;
+ ps->error_override_set = 0;
+ ps->preserve_host_set = 0;
+ ps->preserve_host = 0;
+ ps->timeout = 0;
+ ps->timeout_set = 0;
+ ps->badopt = bad_error;
+ ps->badopt_set = 0;
+ ps->pool = p;
+
+ return ps;
+}
+
+static void * merge_proxy_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ proxy_server_conf *ps = apr_pcalloc(p, sizeof(proxy_server_conf));
+ proxy_server_conf *base = (proxy_server_conf *) basev;
+ proxy_server_conf *overrides = (proxy_server_conf *) overridesv;
+
+ ps->proxies = apr_array_append(p, base->proxies, overrides->proxies);
+ ps->sec_proxy = apr_array_append(p, base->sec_proxy, overrides->sec_proxy);
+ ps->aliases = apr_array_append(p, base->aliases, overrides->aliases);
+ ps->noproxies = apr_array_append(p, base->noproxies, overrides->noproxies);
+ ps->dirconn = apr_array_append(p, base->dirconn, overrides->dirconn);
+ ps->allowed_connect_ports = apr_array_append(p, base->allowed_connect_ports, overrides->allowed_connect_ports);
+ ps->workers = apr_array_append(p, base->workers, overrides->workers);
+ ps->balancers = apr_array_append(p, base->balancers, overrides->balancers);
+ ps->forward = overrides->forward ? overrides->forward : base->forward;
+ ps->reverse = overrides->reverse ? overrides->reverse : base->reverse;
+
+ ps->domain = (overrides->domain == NULL) ? base->domain : overrides->domain;
+ ps->viaopt = (overrides->viaopt_set == 0) ? base->viaopt : overrides->viaopt;
+ ps->req = (overrides->req_set == 0) ? base->req : overrides->req;
+ ps->recv_buffer_size = (overrides->recv_buffer_size_set == 0) ? base->recv_buffer_size : overrides->recv_buffer_size;
+ ps->io_buffer_size = (overrides->io_buffer_size_set == 0) ? base->io_buffer_size : overrides->io_buffer_size;
+ ps->maxfwd = (overrides->maxfwd_set == 0) ? base->maxfwd : overrides->maxfwd;
+ ps->error_override = (overrides->error_override_set == 0) ? base->error_override : overrides->error_override;
+ ps->preserve_host = (overrides->preserve_host_set == 0) ? base->preserve_host : overrides->preserve_host;
+ ps->timeout= (overrides->timeout_set == 0) ? base->timeout : overrides->timeout;
+ ps->badopt = (overrides->badopt_set == 0) ? base->badopt : overrides->badopt;
+ ps->proxy_status = (overrides->proxy_status_set == 0) ? base->proxy_status : overrides->proxy_status;
+ ps->pool = p;
+ return ps;
+}
+
+static void *create_proxy_dir_config(apr_pool_t *p, char *dummy)
+{
+ proxy_dir_conf *new =
+ (proxy_dir_conf *) apr_pcalloc(p, sizeof(proxy_dir_conf));
+
+ /* Filled in by proxysection, when applicable */
+
+ /* Put these in the dir config so they work inside <Location> */
+ new->raliases = apr_array_make(p, 10, sizeof(struct proxy_alias));
+ new->cookie_paths = apr_array_make(p, 10, sizeof(struct proxy_alias));
+ new->cookie_domains = apr_array_make(p, 10, sizeof(struct proxy_alias));
+ new->cookie_path_str = apr_strmatch_precompile(p, "path=", 0);
+ new->cookie_domain_str = apr_strmatch_precompile(p, "domain=", 0);
+
+ return (void *) new;
+}
+
+static void *merge_proxy_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+ proxy_dir_conf *new = (proxy_dir_conf *) apr_pcalloc(p, sizeof(proxy_dir_conf));
+ proxy_dir_conf *add = (proxy_dir_conf *) addv;
+ proxy_dir_conf *base = (proxy_dir_conf *) basev;
+
+ new->p = add->p;
+ new->p_is_fnmatch = add->p_is_fnmatch;
+ new->r = add->r;
+
+ /* Put these in the dir config so they work inside <Location> */
+ new->raliases = apr_array_append(p, base->raliases, add->raliases);
+ new->cookie_paths
+ = apr_array_append(p, base->cookie_paths, add->cookie_paths);
+ new->cookie_domains
+ = apr_array_append(p, base->cookie_domains, add->cookie_domains);
+ new->cookie_path_str = base->cookie_path_str;
+ new->cookie_domain_str = base->cookie_domain_str;
+ return new;
+}
+
+
+static const char *
+ add_proxy(cmd_parms *cmd, void *dummy, const char *f1, const char *r1, int regex)
+{
+ server_rec *s = cmd->server;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
+ struct proxy_remote *new;
+ char *p, *q;
+ char *r, *f, *scheme;
+ ap_regex_t *reg = NULL;
+ int port;
+
+ r = apr_pstrdup(cmd->pool, r1);
+ scheme = apr_pstrdup(cmd->pool, r1);
+ f = apr_pstrdup(cmd->pool, f1);
+ p = strchr(r, ':');
+ if (p == NULL || p[1] != '/' || p[2] != '/' || p[3] == '\0') {
+ if (regex)
+ return "ProxyRemoteMatch: Bad syntax for a remote proxy server";
+ else
+ return "ProxyRemote: Bad syntax for a remote proxy server";
+ }
+ else {
+ scheme[p-r] = 0;
+ }
+ q = strchr(p + 3, ':');
+ if (q != NULL) {
+ if (sscanf(q + 1, "%u", &port) != 1 || port > 65535) {
+ if (regex)
+ return "ProxyRemoteMatch: Bad syntax for a remote proxy server (bad port number)";
+ else
+ return "ProxyRemote: Bad syntax for a remote proxy server (bad port number)";
+ }
+ *q = '\0';
+ }
+ else
+ port = -1;
+ *p = '\0';
+ if (regex) {
+ reg = ap_pregcomp(cmd->pool, f, AP_REG_EXTENDED);
+ if (!reg)
+ return "Regular expression for ProxyRemoteMatch could not be compiled.";
+ }
+ else
+ if (strchr(f, ':') == NULL)
+ ap_str_tolower(f); /* lowercase scheme */
+ ap_str_tolower(p + 3); /* lowercase hostname */
+
+ if (port == -1) {
+ port = apr_uri_port_of_scheme(scheme);
+ }
+
+ new = apr_array_push(conf->proxies);
+ new->scheme = f;
+ new->protocol = r;
+ new->hostname = p + 3;
+ new->port = port;
+ new->regexp = reg;
+ new->use_regex = regex;
+ return NULL;
+}
+
+static const char *
+ add_proxy_noregex(cmd_parms *cmd, void *dummy, const char *f1, const char *r1)
+{
+ return add_proxy(cmd, dummy, f1, r1, 0);
+}
+
+static const char *
+ add_proxy_regex(cmd_parms *cmd, void *dummy, const char *f1, const char *r1)
+{
+ return add_proxy(cmd, dummy, f1, r1, 1);
+}
+
+static const char *
+ add_pass(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ server_rec *s = cmd->server;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
+ struct proxy_alias *new;
+ char *f = cmd->path;
+ char *r = NULL;
+ char *word;
+ apr_table_t *params = apr_table_make(cmd->pool, 5);
+ const apr_array_header_t *arr;
+ const apr_table_entry_t *elts;
+ int i;
+
+ while (*arg) {
+ word = ap_getword_conf(cmd->pool, &arg);
+ if (!f)
+ f = word;
+ else if (!r)
+ r = word;
+ else {
+ char *val = strchr(word, '=');
+ if (!val) {
+ if (cmd->path)
+ return "Invalid ProxyPass parameter. Parameter must be "
+ "in the form 'key=value'";
+ else
+ return "ProxyPass can not have a path when defined in a location";
+ }
+ else
+ *val++ = '\0';
+ apr_table_setn(params, word, val);
+ }
+ };
+
+ if (r == NULL)
+ return "ProxyPass needs a path when not defined in a location";
+
+ new = apr_array_push(conf->aliases);
+ new->fake = apr_pstrdup(cmd->pool, f);
+ new->real = apr_pstrdup(cmd->pool, r);
+ if (r[0] == '!' && r[1] == '\0')
+ return NULL;
+
+ arr = apr_table_elts(params);
+ elts = (const apr_table_entry_t *)arr->elts;
+ /* Distinguish the balancer from woker */
+ if (strncasecmp(r, "balancer:", 9) == 0) {
+ proxy_balancer *balancer = ap_proxy_get_balancer(cmd->pool, conf, r);
+ if (!balancer) {
+ const char *err = ap_proxy_add_balancer(&balancer,
+ cmd->pool,
+ conf, r);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
+ }
+ for (i = 0; i < arr->nelts; i++) {
+ const char *err = set_balancer_param(conf, cmd->pool, balancer, elts[i].key,
+ elts[i].val);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
+ }
+ }
+ else {
+ proxy_worker *worker = ap_proxy_get_worker(cmd->temp_pool, conf, r);
+ if (!worker) {
+ const char *err = ap_proxy_add_worker(&worker, cmd->pool, conf, r);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
+ }
+ PROXY_COPY_CONF_PARAMS(worker, conf);
+
+ for (i = 0; i < arr->nelts; i++) {
+ const char *err = set_worker_param(cmd->pool, worker, elts[i].key,
+ elts[i].val);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
+ }
+ }
+ return NULL;
+}
+
+static const char *
+ add_pass_reverse(cmd_parms *cmd, void *dconf, const char *f, const char *r)
+{
+ proxy_dir_conf *conf = dconf;
+ struct proxy_alias *new;
+
+ if (r!=NULL && cmd->path == NULL ) {
+ new = apr_array_push(conf->raliases);
+ new->fake = f;
+ new->real = r;
+ } else if (r==NULL && cmd->path != NULL) {
+ new = apr_array_push(conf->raliases);
+ new->fake = cmd->path;
+ new->real = f;
+ } else {
+ if ( r == NULL)
+ return "ProxyPassReverse needs a path when not defined in a location";
+ else
+ return "ProxyPassReverse can not have a path when defined in a location";
+ }
+
+ return NULL;
+}
+static const char*
+ cookie_path(cmd_parms *cmd, void *dconf, const char *f, const char *r)
+{
+ proxy_dir_conf *conf = dconf;
+ struct proxy_alias *new;
+
+ new = apr_array_push(conf->cookie_paths);
+ new->fake = f;
+ new->real = r;
+
+ return NULL;
+}
+static const char*
+ cookie_domain(cmd_parms *cmd, void *dconf, const char *f, const char *r)
+{
+ proxy_dir_conf *conf = dconf;
+ struct proxy_alias *new;
+
+ new = apr_array_push(conf->cookie_domains);
+ new->fake = f;
+ new->real = r;
+
+ return NULL;
+}
+
+static const char *
+ set_proxy_exclude(cmd_parms *parms, void *dummy, const char *arg)
+{
+ server_rec *s = parms->server;
+ proxy_server_conf *conf =
+ ap_get_module_config(s->module_config, &proxy_module);
+ struct noproxy_entry *new;
+ struct noproxy_entry *list = (struct noproxy_entry *) conf->noproxies->elts;
+ struct apr_sockaddr_t *addr;
+ int found = 0;
+ int i;
+
+ /* Don't duplicate entries */
+ for (i = 0; i < conf->noproxies->nelts; i++) {
+ if (strcasecmp(arg, list[i].name) == 0) { /* ignore case for host names */
+ found = 1;
+ }
+ }
+
+ if (!found) {
+ new = apr_array_push(conf->noproxies);
+ new->name = arg;
+ if (APR_SUCCESS == apr_sockaddr_info_get(&addr, new->name, APR_UNSPEC, 0, 0, parms->pool)) {
+ new->addr = addr;
+ }
+ else {
+ new->addr = NULL;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Set the ports CONNECT can use
+ */
+static const char *
+ set_allowed_ports(cmd_parms *parms, void *dummy, const char *arg)
+{
+ server_rec *s = parms->server;
+ proxy_server_conf *conf =
+ ap_get_module_config(s->module_config, &proxy_module);
+ int *New;
+
+ if (!apr_isdigit(arg[0]))
+ return "AllowCONNECT: port number must be numeric";
+
+ New = apr_array_push(conf->allowed_connect_ports);
+ *New = atoi(arg);
+ return NULL;
+}
+
+/* Similar to set_proxy_exclude(), but defining directly connected hosts,
+ * which should never be accessed via the configured ProxyRemote servers
+ */
+static const char *
+ set_proxy_dirconn(cmd_parms *parms, void *dummy, const char *arg)
+{
+ server_rec *s = parms->server;
+ proxy_server_conf *conf =
+ ap_get_module_config(s->module_config, &proxy_module);
+ struct dirconn_entry *New;
+ struct dirconn_entry *list = (struct dirconn_entry *) conf->dirconn->elts;
+ int found = 0;
+ int i;
+
+ /* Don't duplicate entries */
+ for (i = 0; i < conf->dirconn->nelts; i++) {
+ if (strcasecmp(arg, list[i].name) == 0)
+ found = 1;
+ }
+
+ if (!found) {
+ New = apr_array_push(conf->dirconn);
+ New->name = apr_pstrdup(parms->pool, arg);
+ New->hostaddr = NULL;
+
+ if (ap_proxy_is_ipaddr(New, parms->pool)) {
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Parsed addr %s", inet_ntoa(New->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Parsed mask %s", inet_ntoa(New->mask));
+#endif
+ }
+ else if (ap_proxy_is_domainname(New, parms->pool)) {
+ ap_str_tolower(New->name);
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Parsed domain %s", New->name);
+#endif
+ }
+ else if (ap_proxy_is_hostname(New, parms->pool)) {
+ ap_str_tolower(New->name);
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Parsed host %s", New->name);
+#endif
+ }
+ else {
+ ap_proxy_is_word(New, parms->pool);
+#if DEBUGGING
+ fprintf(stderr, "Parsed word %s\n", New->name);
+#endif
+ }
+ }
+ return NULL;
+}
+
+static const char *
+ set_proxy_domain(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ if (arg[0] != '.')
+ return "ProxyDomain: domain name must start with a dot.";
+
+ psf->domain = arg;
+ return NULL;
+}
+
+static const char *
+ set_proxy_req(cmd_parms *parms, void *dummy, int flag)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ psf->req = flag;
+ psf->req_set = 1;
+
+ if (flag && !psf->forward) {
+ psf->forward = ap_proxy_create_worker(parms->pool);
+ psf->forward->name = "proxy:forward";
+ psf->forward->hostname = "*";
+ psf->forward->scheme = "*";
+ }
+ return NULL;
+}
+
+static const char *
+ set_proxy_error_override(cmd_parms *parms, void *dummy, int flag)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ psf->error_override = flag;
+ psf->error_override_set = 1;
+ return NULL;
+}
+static const char *
+ set_preserve_host(cmd_parms *parms, void *dummy, int flag)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ psf->preserve_host = flag;
+ psf->preserve_host_set = 1;
+ return NULL;
+}
+
+static const char *
+ set_recv_buffer_size(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+ int s = atoi(arg);
+ if (s < 512 && s != 0) {
+ return "ProxyReceiveBufferSize must be >= 512 bytes, or 0 for system default.";
+ }
+
+ psf->recv_buffer_size = s;
+ psf->recv_buffer_size_set = 1;
+ return NULL;
+}
+
+static const char *
+ set_io_buffer_size(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+ long s = atol(arg);
+
+ psf->io_buffer_size = ((s > AP_IOBUFSIZE) ? s : AP_IOBUFSIZE);
+ psf->io_buffer_size_set = 1;
+ return NULL;
+}
+
+static const char *
+ set_max_forwards(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+ long s = atol(arg);
+ if (s < 0) {
+ return "ProxyMaxForwards must be greater or equal to zero..";
+ }
+
+ psf->maxfwd = s;
+ psf->maxfwd_set = 1;
+ return NULL;
+}
+static const char*
+ set_proxy_timeout(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+ int timeout;
+
+ timeout=atoi(arg);
+ if (timeout<1) {
+ return "Proxy Timeout must be at least 1 second.";
+ }
+ psf->timeout_set=1;
+ psf->timeout=apr_time_from_sec(timeout);
+
+ return NULL;
+}
+
+static const char*
+ set_via_opt(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ if (strcasecmp(arg, "Off") == 0)
+ psf->viaopt = via_off;
+ else if (strcasecmp(arg, "On") == 0)
+ psf->viaopt = via_on;
+ else if (strcasecmp(arg, "Block") == 0)
+ psf->viaopt = via_block;
+ else if (strcasecmp(arg, "Full") == 0)
+ psf->viaopt = via_full;
+ else {
+ return "ProxyVia must be one of: "
+ "off | on | full | block";
+ }
+
+ psf->viaopt_set = 1;
+ return NULL;
+}
+
+static const char*
+ set_bad_opt(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ if (strcasecmp(arg, "IsError") == 0)
+ psf->badopt = bad_error;
+ else if (strcasecmp(arg, "Ignore") == 0)
+ psf->badopt = bad_ignore;
+ else if (strcasecmp(arg, "StartBody") == 0)
+ psf->badopt = bad_body;
+ else {
+ return "ProxyBadHeader must be one of: "
+ "IsError | Ignore | StartBody";
+ }
+
+ psf->badopt_set = 1;
+ return NULL;
+}
+
+static const char*
+ set_status_opt(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ if (strcasecmp(arg, "Off") == 0)
+ psf->proxy_status = status_off;
+ else if (strcasecmp(arg, "On") == 0)
+ psf->proxy_status = status_on;
+ else if (strcasecmp(arg, "Full") == 0)
+ psf->proxy_status = status_full;
+ else {
+ return "ProxyStatus must be one of: "
+ "off | on | block";
+ }
+
+ psf->proxy_status_set = 1;
+ return NULL;
+}
+
+static const char *add_member(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ server_rec *s = cmd->server;
+ proxy_server_conf *conf =
+ ap_get_module_config(s->module_config, &proxy_module);
+ proxy_balancer *balancer;
+ proxy_worker *worker;
+ char *path = cmd->path;
+ char *name = NULL;
+ char *word;
+ apr_table_t *params = apr_table_make(cmd->pool, 5);
+ const apr_array_header_t *arr;
+ const apr_table_entry_t *elts;
+ int i;
+
+ if (cmd->path)
+ path = apr_pstrdup(cmd->pool, cmd->path);
+ while (*arg) {
+ word = ap_getword_conf(cmd->pool, &arg);
+ if (!path)
+ path = word;
+ else if (!name)
+ name = word;
+ else {
+ char *val = strchr(word, '=');
+ if (!val)
+ if (cmd->path)
+ return "BalancerMember can not have a balancer name when defined in a location";
+ else
+ return "Invalid BalancerMember parameter. Parameter must "
+ "be in the form 'key=value'";
+ else
+ *val++ = '\0';
+ apr_table_setn(params, word, val);
+ }
+ }
+ if (!path)
+ return "BalancerMember must define balancer name when outside <Proxy > section";
+ if (!name)
+ return "BalancerMember must define remote proxy server";
+
+ ap_str_tolower(path); /* lowercase scheme://hostname */
+
+ /* Try to find existing worker */
+ worker = ap_proxy_get_worker(cmd->temp_pool, conf, name);
+ if (!worker) {
+ const char *err;
+ if ((err = ap_proxy_add_worker(&worker, cmd->pool, conf, name)) != NULL)
+ return apr_pstrcat(cmd->temp_pool, "BalancerMember ", err, NULL);
+ }
+ PROXY_COPY_CONF_PARAMS(worker, conf);
+
+ arr = apr_table_elts(params);
+ elts = (const apr_table_entry_t *)arr->elts;
+ for (i = 0; i < arr->nelts; i++) {
+ const char *err = set_worker_param(cmd->pool, worker, elts[i].key,
+ elts[i].val);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "BalancerMember ", err, NULL);
+ }
+ /* Try to find the balancer */
+ balancer = ap_proxy_get_balancer(cmd->temp_pool, conf, path);
+ if (!balancer) {
+ const char *err = ap_proxy_add_balancer(&balancer,
+ cmd->pool,
+ conf, path);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "BalancerMember ", err, NULL);
+ }
+ /* Add the worker to the load balancer */
+ ap_proxy_add_worker_to_balancer(cmd->pool, balancer, worker);
+ return NULL;
+}
+
+static const char *
+ set_proxy_param(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ server_rec *s = cmd->server;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
+ char *name = NULL;
+ char *word, *val;
+ proxy_balancer *balancer = NULL;
+ proxy_worker *worker = NULL;
+ const char *err;
+
+ if (cmd->directive->parent &&
+ strncasecmp(cmd->directive->parent->directive,
+ "<Proxy", 6) == 0) {
+ const char *pargs = cmd->directive->parent->args;
+ /* Directive inside <Proxy section
+ * Parent directive arg is the worker/balancer name.
+ */
+ name = ap_getword_conf(cmd->temp_pool, &pargs);
+ if ((word = ap_strchr(name, '>')))
+ *word = '\0';
+ }
+ else {
+ /* Standard set directive with worker/balancer
+ * name as first param.
+ */
+ name = ap_getword_conf(cmd->temp_pool, &arg);
+ }
+
+ if (strncasecmp(name, "balancer:", 9) == 0) {
+ balancer = ap_proxy_get_balancer(cmd->pool, conf, name);
+ if (!balancer) {
+ return apr_pstrcat(cmd->temp_pool, "ProxySet can not find '",
+ name, "' Balancer.", NULL);
+ }
+ }
+ else {
+ worker = ap_proxy_get_worker(cmd->temp_pool, conf, name);
+ if (!worker) {
+ return apr_pstrcat(cmd->temp_pool, "ProxySet can not find '",
+ name, "' Worker.", NULL);
+ }
+ }
+
+ while (*arg) {
+ word = ap_getword_conf(cmd->pool, &arg);
+ val = strchr(word, '=');
+ if (!val) {
+ return "Invalid ProxySet parameter. Parameter must be "
+ "in the form 'key=value'";
+ }
+ else
+ *val++ = '\0';
+ if (worker)
+ err = set_worker_param(cmd->pool, worker, word, val);
+ else
+ err = set_balancer_param(conf, cmd->pool, balancer, word, val);
+
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxySet: ", err, " ", word, "=", val, "; ", name, NULL);
+ }
+
+ return NULL;
+}
+
+static void ap_add_per_proxy_conf(server_rec *s, ap_conf_vector_t *dir_config)
+{
+ proxy_server_conf *sconf = ap_get_module_config(s->module_config,
+ &proxy_module);
+ void **new_space = (void **)apr_array_push(sconf->sec_proxy);
+
+ *new_space = dir_config;
+}
+
+static const char *proxysection(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;
+ proxy_dir_conf *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_LOC_FILE|NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive missing closing '>'", NULL);
+ }
+
+ arg=apr_pstrndup(cmd->pool, arg, endp-arg);
+
+ if (!arg) {
+ if (thiscmd->cmd_data)
+ return "<ProxyMatch > block must specify a path";
+ else
+ return "<Proxy > block must specify a path";
+ }
+
+ cmd->path = ap_getword_conf(cmd->pool, &arg);
+ cmd->override = OR_ALL|ACCESS_CONF;
+
+ if (!strncasecmp(cmd->path, "proxy:", 6))
+ cmd->path += 6;
+
+ /* XXX Ignore case? What if we proxy a case-insensitive server?!?
+ * While we are at it, shouldn't we also canonicalize the entire
+ * scheme? See proxy_fixup()
+ */
+ if (thiscmd->cmd_data) { /* <ProxyMatch> */
+ 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);
+ if (!cmd->path)
+ return "<Proxy ~ > block must specify a path";
+ if (strncasecmp(cmd->path, "proxy:", 6))
+ cmd->path += 6;
+ 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_dir_conf, cmd->path,
+ &proxy_module, cmd->pool);
+
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_dir_conf);
+ if (errmsg != NULL)
+ return errmsg;
+
+ conf->r = r;
+ conf->p = cmd->path;
+ conf->p_is_fnmatch = apr_fnmatch_test(conf->p);
+
+ ap_add_per_proxy_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 command_rec proxy_cmds[] =
+{
+ AP_INIT_RAW_ARGS("<Proxy", proxysection, NULL, RSRC_CONF,
+ "Container for directives affecting resources located in the proxied "
+ "location"),
+ AP_INIT_RAW_ARGS("<ProxyMatch", proxysection, (void*)1, RSRC_CONF,
+ "Container for directives affecting resources located in the proxied "
+ "location, in regular expression syntax"),
+ AP_INIT_FLAG("ProxyRequests", set_proxy_req, NULL, RSRC_CONF,
+ "on if the true proxy requests should be accepted"),
+ AP_INIT_TAKE2("ProxyRemote", add_proxy_noregex, NULL, RSRC_CONF,
+ "a scheme, partial URL or '*' and a proxy server"),
+ AP_INIT_TAKE2("ProxyRemoteMatch", add_proxy_regex, NULL, RSRC_CONF,
+ "a regex pattern and a proxy server"),
+ AP_INIT_RAW_ARGS("ProxyPass", add_pass, NULL, RSRC_CONF|ACCESS_CONF,
+ "a virtual path and a URL"),
+ AP_INIT_TAKE12("ProxyPassReverse", add_pass_reverse, NULL, RSRC_CONF|ACCESS_CONF,
+ "a virtual path and a URL for reverse proxy behaviour"),
+ AP_INIT_TAKE2("ProxyPassReverseCookiePath", cookie_path, NULL,
+ RSRC_CONF|ACCESS_CONF, "Path rewrite rule for proxying cookies"),
+ AP_INIT_TAKE2("ProxyPassReverseCookieDomain", cookie_domain, NULL,
+ RSRC_CONF|ACCESS_CONF, "Domain rewrite rule for proxying cookies"),
+ AP_INIT_ITERATE("ProxyBlock", set_proxy_exclude, NULL, RSRC_CONF,
+ "A list of names, hosts or domains to which the proxy will not connect"),
+ AP_INIT_TAKE1("ProxyReceiveBufferSize", set_recv_buffer_size, NULL, RSRC_CONF,
+ "Receive buffer size for outgoing HTTP and FTP connections in bytes"),
+ AP_INIT_TAKE1("ProxyIOBufferSize", set_io_buffer_size, NULL, RSRC_CONF,
+ "IO buffer size for outgoing HTTP and FTP connections in bytes"),
+ AP_INIT_TAKE1("ProxyMaxForwards", set_max_forwards, NULL, RSRC_CONF,
+ "The maximum number of proxies a request may be forwarded through."),
+ AP_INIT_ITERATE("NoProxy", set_proxy_dirconn, NULL, RSRC_CONF,
+ "A list of domains, hosts, or subnets to which the proxy will connect directly"),
+ AP_INIT_TAKE1("ProxyDomain", set_proxy_domain, NULL, RSRC_CONF,
+ "The default intranet domain name (in absence of a domain in the URL)"),
+ AP_INIT_ITERATE("AllowCONNECT", set_allowed_ports, NULL, RSRC_CONF,
+ "A list of ports which CONNECT may connect to"),
+ AP_INIT_TAKE1("ProxyVia", set_via_opt, NULL, RSRC_CONF,
+ "Configure Via: proxy header header to one of: on | off | block | full"),
+ AP_INIT_FLAG("ProxyErrorOverride", set_proxy_error_override, NULL, RSRC_CONF,
+ "use our error handling pages instead of the servers' we are proxying"),
+ AP_INIT_FLAG("ProxyPreserveHost", set_preserve_host, NULL, RSRC_CONF,
+ "on if we should preserve host header while proxying"),
+ AP_INIT_TAKE1("ProxyTimeout", set_proxy_timeout, NULL, RSRC_CONF,
+ "Set the timeout (in seconds) for a proxied connection. "
+ "This overrides the server timeout"),
+ AP_INIT_TAKE1("ProxyBadHeader", set_bad_opt, NULL, RSRC_CONF,
+ "How to handle bad header line in response: IsError | Ignore | StartBody"),
+ AP_INIT_RAW_ARGS("BalancerMember", add_member, NULL, RSRC_CONF|ACCESS_CONF,
+ "A balancer name and scheme with list of params"),
+ AP_INIT_TAKE1("ProxyStatus", set_status_opt, NULL, RSRC_CONF,
+ "Configure Status: proxy status to one of: on | off | full"),
+ AP_INIT_RAW_ARGS("ProxySet", set_proxy_param, NULL, RSRC_CONF|ACCESS_CONF,
+ "A balancer or worker name with list of params"),
+ {NULL}
+};
+
+static APR_OPTIONAL_FN_TYPE(ssl_proxy_enable) *proxy_ssl_enable = NULL;
+static APR_OPTIONAL_FN_TYPE(ssl_engine_disable) *proxy_ssl_disable = NULL;
+static APR_OPTIONAL_FN_TYPE(ssl_is_https) *proxy_is_https = NULL;
+static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *proxy_ssl_val = NULL;
+
+PROXY_DECLARE(int) ap_proxy_ssl_enable(conn_rec *c)
+{
+ /*
+ * if c == NULL just check if the optional function was imported
+ * else run the optional function so ssl filters are inserted
+ */
+ if (proxy_ssl_enable) {
+ return c ? proxy_ssl_enable(c) : 1;
+ }
+
+ return 0;
+}
+
+PROXY_DECLARE(int) ap_proxy_ssl_disable(conn_rec *c)
+{
+ if (proxy_ssl_disable) {
+ return proxy_ssl_disable(c);
+ }
+
+ return 0;
+}
+
+PROXY_DECLARE(int) ap_proxy_conn_is_https(conn_rec *c)
+{
+ if (proxy_is_https) {
+ return proxy_is_https(c);
+ }
+ else
+ return 0;
+}
+
+PROXY_DECLARE(const char *) ap_proxy_ssl_val(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r,
+ const char *var)
+{
+ if (proxy_ssl_val) {
+ /* XXX Perhaps the casting useless */
+ return (const char *)proxy_ssl_val(p, s, c, r, (char *)var);
+ }
+ else
+ return NULL;
+}
+
+static int proxy_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+
+ proxy_ssl_enable = APR_RETRIEVE_OPTIONAL_FN(ssl_proxy_enable);
+ proxy_ssl_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);
+ proxy_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
+ proxy_ssl_val = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
+
+ return OK;
+}
+
+/*
+ * proxy Extension to mod_status
+ */
+static int proxy_status_hook(request_rec *r, int flags)
+{
+ int i, n;
+ void *sconf = r->server->module_config;
+ proxy_server_conf *conf = (proxy_server_conf *)
+ ap_get_module_config(sconf, &proxy_module);
+ proxy_balancer *balancer = NULL;
+ proxy_worker *worker = NULL;
+
+ if (flags & AP_STATUS_SHORT || conf->balancers->nelts == 0 ||
+ conf->proxy_status == status_off)
+ return OK;
+
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++) {
+ ap_rputs("<hr />\n<h1>Proxy LoadBalancer Status for ", r);
+ ap_rvputs(r, balancer->name, "</h1>\n\n", NULL);
+ ap_rputs("\n\n<table border=\"0\"><tr>"
+ "<th>SSes</th><th>Timeout</th><th>Method</th>"
+ "</tr>\n<tr>", r);
+ ap_rvputs(r, "<td>", balancer->sticky, NULL);
+ ap_rprintf(r, "</td><td>%" APR_TIME_T_FMT "</td>",
+ apr_time_sec(balancer->timeout));
+ ap_rprintf(r, "<td>%s</td>\n",
+ balancer->lbmethod->name);
+ ap_rputs("</table>\n", r);
+ ap_rputs("\n\n<table border=\"0\"><tr>"
+ "<th>Sch</th><th>Host</th><th>Stat</th>"
+ "<th>Route</th><th>Redir</th>"
+ "<th>F</th><th>Acc</th><th>Wr</th><th>Rd</th>"
+ "</tr>\n", r);
+
+ worker = (proxy_worker *)balancer->workers->elts;
+ for (n = 0; n < balancer->workers->nelts; n++) {
+ char fbuf[50];
+ ap_rvputs(r, "<tr>\n<td>", worker->scheme, "</td>", NULL);
+ ap_rvputs(r, "<td>", worker->hostname, "</td><td>", NULL);
+ if (worker->s->status & PROXY_WORKER_DISABLED)
+ ap_rputs("Dis", r);
+ else if (worker->s->status & PROXY_WORKER_IN_ERROR)
+ ap_rputs("Err", r);
+ else if (worker->s->status & PROXY_WORKER_INITIALIZED)
+ ap_rputs("Ok", r);
+ else
+ ap_rputs("-", r);
+ ap_rvputs(r, "</td><td>", worker->s->route, NULL);
+ ap_rvputs(r, "</td><td>", worker->s->redirect, NULL);
+ ap_rprintf(r, "</td><td>%d</td>", worker->s->lbfactor);
+ ap_rprintf(r, "<td>%d</td><td>", (int)(worker->s->elected));
+ ap_rputs(apr_strfsize(worker->s->transferred, fbuf), r);
+ ap_rputs("</td><td>", r);
+ ap_rputs(apr_strfsize(worker->s->read, fbuf), r);
+ ap_rputs("</td>\n", r);
+
+ /* TODO: Add the rest of dynamic worker data */
+ ap_rputs("</tr>\n", r);
+
+ ++worker;
+ }
+ ap_rputs("</table>\n", r);
+ ++balancer;
+ }
+ ap_rputs("<hr /><table>\n"
+ "<tr><th>SSes</th><td>Sticky session name</td></tr>\n"
+ "<tr><th>Timeout</th><td>Balancer Timeout</td></tr>\n"
+ "<tr><th>Sch</th><td>Connection scheme</td></tr>\n"
+ "<tr><th>Host</th><td>Backend Hostname</td></tr>\n"
+ "<tr><th>Stat</th><td>Worker status</td></tr>\n"
+ "<tr><th>Route</th><td>Session Route</td></tr>\n"
+ "<tr><th>Redir</th><td>Session Route Redirection</td></tr>\n"
+ "<tr><th>F</th><td>Load Balancer Factor in %</td></tr>\n"
+ "<tr><th>Acc</th><td>Number of requests</td></tr>\n"
+ "<tr><th>Wr</th><td>Number of bytes transferred</td></tr>\n"
+ "<tr><th>Rd</th><td>Number of bytes read</td></tr>\n"
+ "</table>", r);
+
+ return OK;
+}
+
+static void child_init(apr_pool_t *p, server_rec *s)
+{
+ proxy_worker *reverse = NULL;
+
+ while (s) {
+ void *sconf = s->module_config;
+ proxy_server_conf *conf;
+ proxy_worker *worker;
+ int i;
+
+ conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module);
+ /* Initialize worker's shared scoreboard data */
+ worker = (proxy_worker *)conf->workers->elts;
+ for (i = 0; i < conf->workers->nelts; i++) {
+ ap_proxy_initialize_worker_share(conf, worker, s);
+ ap_proxy_initialize_worker(worker, s);
+ worker++;
+ }
+ /* Initialize forward worker if defined */
+ if (conf->forward) {
+ ap_proxy_initialize_worker_share(conf, conf->forward, s);
+ ap_proxy_initialize_worker(conf->forward, s);
+ /* Do not disable worker in case of errors */
+ conf->forward->s->status |= PROXY_WORKER_IGNORE_ERRORS;
+ /* Disable address cache for generic forward worker */
+ conf->forward->is_address_reusable = 0;
+ }
+ if (!reverse) {
+ reverse = ap_proxy_create_worker(p);
+ reverse->name = "proxy:reverse";
+ reverse->hostname = "*";
+ reverse->scheme = "*";
+ ap_proxy_initialize_worker_share(conf, reverse, s);
+ ap_proxy_initialize_worker(reverse, s);
+ /* Do not disable worker in case of errors */
+ reverse->s->status |= PROXY_WORKER_IGNORE_ERRORS;
+ /* Disable address cache for generic reverse worker */
+ reverse->is_address_reusable = 0;
+ }
+ conf->reverse = reverse;
+ s = s->next;
+ }
+}
+
+/*
+ * This routine is called before the server processes the configuration
+ * files. There is no return value.
+ */
+static int proxy_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ APR_OPTIONAL_HOOK(ap, status_hook, proxy_status_hook, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ /* Reset workers count on gracefull restart */
+ proxy_lb_workers = 0;
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* fixup before mod_rewrite, so that the proxied url will not
+ * escaped accidentally by our fixup.
+ */
+ static const char * const aszSucc[]={ "mod_rewrite.c", NULL };
+ /* Only the mpm_winnt has child init hook handler.
+ * make sure that we are called after the mpm
+ * initializes.
+ */
+ static const char *const aszPred[] = { "mpm_winnt.c", NULL};
+
+ APR_REGISTER_OPTIONAL_FN(ap_proxy_lb_workers);
+ /* handler */
+ ap_hook_handler(proxy_handler, NULL, NULL, APR_HOOK_FIRST);
+ /* filename-to-URI translation */
+ ap_hook_translate_name(proxy_trans, aszSucc, NULL, APR_HOOK_FIRST);
+ /* walk <Proxy > entries and suppress default TRACE behavior */
+ ap_hook_map_to_storage(proxy_map_location, NULL,NULL, APR_HOOK_FIRST);
+ /* fixups */
+ ap_hook_fixups(proxy_fixup, NULL, aszSucc, APR_HOOK_FIRST);
+ /* post read_request handling */
+ ap_hook_post_read_request(proxy_detect, NULL, NULL, APR_HOOK_FIRST);
+ /* pre config handling */
+ ap_hook_pre_config(proxy_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ /* post config handling */
+ ap_hook_post_config(proxy_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ /* child init handling */
+ ap_hook_child_init(child_init, aszPred, NULL, APR_HOOK_MIDDLE);
+
+}
+
+module AP_MODULE_DECLARE_DATA proxy_module =
+{
+ STANDARD20_MODULE_STUFF,
+ create_proxy_dir_config, /* create per-directory config structure */
+ merge_proxy_dir_config, /* merge per-directory config structures */
+ create_proxy_config, /* create per-server config structure */
+ merge_proxy_config, /* merge per-server config structures */
+ proxy_cmds, /* command table */
+ register_hooks
+};
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(scheme_handler)
+ APR_HOOK_LINK(canon_handler)
+ APR_HOOK_LINK(pre_request)
+ APR_HOOK_LINK(post_request)
+ APR_HOOK_LINK(request_status)
+)
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, scheme_handler,
+ (request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyhost,
+ apr_port_t proxyport),(r,worker,conf,
+ url,proxyhost,proxyport),DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, canon_handler,
+ (request_rec *r, char *url),(r,
+ url),DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, pre_request, (
+ proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf,
+ char **url),(worker,balancer,
+ r,conf,url),DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, post_request,
+ (proxy_worker *worker,
+ proxy_balancer *balancer,
+ request_rec *r,
+ proxy_server_conf *conf),(worker,
+ balancer,r,conf),DECLINED)
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, fixups,
+ (request_rec *r), (r),
+ OK, DECLINED)
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, request_status,
+ (int *status, request_rec *r),
+ (status, r),
+ OK, DECLINED)
diff --git a/modules/proxy/mod_proxy.dsp b/modules/proxy/mod_proxy.dsp
new file mode 100644
index 00000000..b9cb1e9c
--- /dev/null
+++ b/modules/proxy/mod_proxy.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy - Win32 Release
+!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 "mod_proxy.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 "mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "PROXY_DECLARE_EXPORT" /Fd"Release\mod_proxy_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_proxy.so" /d "LONG_NAME=proxy_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:"Release/mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /debug /out:"Release/mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_proxy - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# 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 "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "PROXY_DECLARE_EXPORT" /Fd"Debug\mod_proxy_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_proxy.so" /d "LONG_NAME=proxy_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy - Win32 Release"
+# Name "mod_proxy - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\proxy_util.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h
new file mode 100644
index 00000000..2af400dc
--- /dev/null
+++ b/modules/proxy/mod_proxy.h
@@ -0,0 +1,722 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_PROXY_H
+#define MOD_PROXY_H
+
+/**
+ * @file mod_proxy.h
+ * @brief Proxy Extension Module for Apache
+ *
+ * @defgroup MOD_PROXY mod_proxy
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+/*
+
+ Also note numerous FIXMEs and CHECKMEs which should be eliminated.
+
+ This code is once again experimental!
+
+ Things to do:
+
+ 1. Make it completely work (for FTP too)
+
+ 2. HTTP/1.1
+
+ Chuck Murcko <chuck@topsail.org> 02-06-01
+
+ */
+
+#define CORE_PRIVATE
+
+#include "apr_hooks.h"
+#include "apr.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_md5.h"
+#include "apr_network_io.h"
+#include "apr_pools.h"
+#include "apr_strings.h"
+#include "apr_uri.h"
+#include "apr_date.h"
+#include "apr_strmatch.h"
+#include "apr_fnmatch.h"
+#include "apr_reslist.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "ap_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_connection.h"
+#include "util_filter.h"
+#include "util_ebcdic.h"
+#include "ap_provider.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if APR_HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+/* for proxy_canonenc() */
+enum enctype {
+ enc_path, enc_search, enc_user, enc_fpath, enc_parm
+};
+
+#if APR_CHARSET_EBCDIC
+#define CRLF "\r\n"
+#else /*APR_CHARSET_EBCDIC*/
+#define CRLF "\015\012"
+#endif /*APR_CHARSET_EBCDIC*/
+
+/* default Max-Forwards header setting */
+#define DEFAULT_MAX_FORWARDS 10
+
+/* static information about a remote proxy */
+struct proxy_remote {
+ const char *scheme; /* the schemes handled by this proxy, or '*' */
+ const char *protocol; /* the scheme used to talk to this proxy */
+ const char *hostname; /* the hostname of this proxy */
+ apr_port_t port; /* the port for this proxy */
+ ap_regex_t *regexp; /* compiled regex (if any) for the remote */
+ int use_regex; /* simple boolean. True if we have a regex pattern */
+};
+
+struct proxy_alias {
+ const char *real;
+ const char *fake;
+};
+
+struct dirconn_entry {
+ char *name;
+ struct in_addr addr, mask;
+ struct apr_sockaddr_t *hostaddr;
+ int (*matcher) (struct dirconn_entry * This, request_rec *r);
+};
+
+struct noproxy_entry {
+ const char *name;
+ struct apr_sockaddr_t *addr;
+};
+
+typedef struct proxy_balancer proxy_balancer;
+typedef struct proxy_worker proxy_worker;
+typedef struct proxy_conn_pool proxy_conn_pool;
+typedef struct proxy_balancer_method proxy_balancer_method;
+
+typedef struct {
+ apr_array_header_t *proxies;
+ apr_array_header_t *sec_proxy;
+ apr_array_header_t *aliases;
+ apr_array_header_t *noproxies;
+ apr_array_header_t *dirconn;
+ apr_array_header_t *allowed_connect_ports;
+ apr_array_header_t *workers;
+ apr_array_header_t *balancers;
+ proxy_worker *forward; /* forward proxy worker */
+ proxy_worker *reverse; /* reverse "module-driven" proxy worker */
+ const char *domain; /* domain name to use in absence of a domain name in the request */
+ int req; /* true if proxy requests are enabled */
+ char req_set;
+ enum {
+ via_off,
+ via_on,
+ via_block,
+ via_full
+ } viaopt; /* how to deal with proxy Via: headers */
+ char viaopt_set;
+ apr_size_t recv_buffer_size;
+ char recv_buffer_size_set;
+ apr_size_t io_buffer_size;
+ char io_buffer_size_set;
+ long maxfwd;
+ char maxfwd_set;
+ /**
+ * the following setting masks the error page
+ * returned from the 'proxied server' and just
+ * forwards the status code upwards.
+ * This allows the main server (us) to generate
+ * the error page, (so it will look like a error
+ * returned from the rest of the system
+ */
+ int error_override;
+ int error_override_set;
+ int preserve_host;
+ int preserve_host_set;
+ apr_interval_time_t timeout;
+ char timeout_set;
+ enum {
+ bad_error,
+ bad_ignore,
+ bad_body
+ } badopt; /* how to deal with bad headers */
+ char badopt_set;
+/* putting new stuff on the end maximises binary back-compatibility.
+ * the strmatch_patterns are really a const just to have a
+ * case-independent strstr.
+ */
+ enum {
+ status_off,
+ status_on,
+ status_full
+ } proxy_status; /* Status display options */
+ char proxy_status_set;
+ apr_pool_t *pool; /* Pool used for allocating this struct */
+} proxy_server_conf;
+
+
+typedef struct {
+ const char *p; /* The path */
+ int p_is_fnmatch; /* Is this path an fnmatch candidate? */
+ ap_regex_t *r; /* Is this a regex? */
+
+/* ProxyPassReverse and friends are documented as working inside
+ * <Location>. But in fact they never have done in the case of
+ * more than one <Location>, because the server_conf can't see it.
+ * We need to move them to the per-dir config.
+ * Discussed in February:
+ * http://marc.theaimsgroup.com/?l=apache-httpd-dev&m=110726027118798&w=2
+ */
+ apr_array_header_t *raliases;
+ apr_array_header_t* cookie_paths;
+ apr_array_header_t* cookie_domains;
+ const apr_strmatch_pattern* cookie_path_str;
+ const apr_strmatch_pattern* cookie_domain_str;
+} proxy_dir_conf;
+
+typedef struct {
+ conn_rec *connection;
+ const char *hostname;
+ apr_port_t port;
+ int is_ssl;
+ apr_pool_t *pool; /* Subpool used for creating socket */
+ apr_socket_t *sock; /* Connection socket */
+ apr_sockaddr_t *addr; /* Preparsed remote address info */
+ apr_uint32_t flags; /* Conection flags */
+ int close; /* Close 'this' connection */
+ int close_on_recycle; /* Close the connection when returning to pool */
+ proxy_worker *worker; /* Connection pool this connection belogns to */
+ void *data; /* per scheme connection data */
+#if APR_HAS_THREADS
+ int inreslist; /* connection in apr_reslist? */
+#endif
+} proxy_conn_rec;
+
+typedef struct {
+ float cache_completion; /* completion percentage */
+ int content_length; /* length of the content */
+} proxy_completion;
+
+/* Connection pool */
+struct proxy_conn_pool {
+ apr_pool_t *pool; /* The pool used in constructor and destructor calls */
+ apr_sockaddr_t *addr; /* Preparsed remote address info */
+#if APR_HAS_THREADS
+ apr_reslist_t *res; /* Connection resource list */
+#endif
+ proxy_conn_rec *conn; /* Single connection for prefork mpm's */
+};
+
+/* woker status flags */
+#define PROXY_WORKER_INITIALIZED 0x0001
+#define PROXY_WORKER_IGNORE_ERRORS 0x0002
+#define PROXY_WORKER_IN_SHUTDOWN 0x0010
+#define PROXY_WORKER_DISABLED 0x0020
+#define PROXY_WORKER_STOPPED 0x0040
+#define PROXY_WORKER_IN_ERROR 0x0080
+
+#define PROXY_WORKER_IS_USABLE(f) (!((f)->s->status & 0x00F0))
+
+/* default worker retry timeout in seconds */
+#define PROXY_WORKER_DEFAULT_RETRY 60
+#define PROXY_WORKER_MAX_ROUTE_SIZ 63
+
+/* Runtime worker status informations. Shared in scoreboard */
+typedef struct {
+ int status;
+ apr_time_t error_time; /* time of the last error */
+ int retries; /* number of retries on this worker */
+ int lbstatus; /* Current lbstatus */
+ int lbfactor; /* dynamic lbfactor */
+ apr_off_t transferred;/* Number of bytes transferred to remote */
+ apr_off_t read; /* Number of bytes read from remote */
+ apr_size_t elected; /* Number of times the worker was elected */
+ char route[PROXY_WORKER_MAX_ROUTE_SIZ+1];
+ char redirect[PROXY_WORKER_MAX_ROUTE_SIZ+1];
+ void *context; /* general purpose storage */
+} proxy_worker_stat;
+
+/* Worker configuration */
+struct proxy_worker {
+ int id; /* scoreboard id */
+ apr_interval_time_t retry; /* retry interval */
+ int lbfactor; /* initial load balancing factor */
+ const char *name;
+ const char *scheme; /* scheme to use ajp|http|https */
+ const char *hostname; /* remote backend address */
+ const char *route; /* balancing route */
+ const char *redirect; /* temporary balancing redirection route */
+ int status; /* temporary worker status */
+ apr_port_t port;
+ int min; /* Desired minimum number of available connections */
+ int smax; /* Soft maximum on the total number of connections */
+ int hmax; /* Hard maximum on the total number of connections */
+ apr_interval_time_t ttl; /* maximum amount of time in seconds a connection
+ * may be available while exceeding the soft limit */
+ apr_interval_time_t timeout; /* connection timeout */
+ char timeout_set;
+ apr_interval_time_t acquire; /* acquire timeout when the maximum number of connections is exceeded */
+ char acquire_set;
+ apr_size_t recv_buffer_size;
+ char recv_buffer_size_set;
+ apr_size_t io_buffer_size;
+ char io_buffer_size_set;
+ char keepalive;
+ char keepalive_set;
+ proxy_conn_pool *cp; /* Connection pool to use */
+ proxy_worker_stat *s; /* Shared data */
+ void *opaque; /* per scheme worker data */
+ int is_address_reusable;
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *mutex; /* Thread lock for updating address cache */
+#endif
+ void *context; /* general purpose storage */
+ enum {
+ flush_off,
+ flush_on,
+ flush_auto
+ } flush_packets; /* control AJP flushing */
+ int flush_wait; /* poll wait time in microseconds if flush_auto */
+};
+
+/*
+ * Wait 10000 microseconds to find out if more data is currently
+ * available at the backend. Just an arbitrary choose.
+ */
+#define PROXY_FLUSH_WAIT 10000
+
+struct proxy_balancer {
+ apr_array_header_t *workers; /* array of proxy_workers */
+ const char *name; /* name of the load balancer */
+ const char *sticky; /* sticky session identifier */
+ int sticky_force; /* Disable failover for sticky sessions */
+ apr_interval_time_t timeout; /* Timeout for waiting on free connection */
+ int max_attempts; /* Number of attempts before failing */
+ char max_attempts_set;
+ proxy_balancer_method *lbmethod;
+
+ /* XXX: Perhaps we will need the proc mutex too.
+ * Altrough we are only using arithmetic operations
+ * it may lead to a incorrect calculations.
+ * For now use only the thread mutex.
+ */
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *mutex; /* Thread lock for updating lb params */
+#endif
+ void *context; /* general purpose storage */
+};
+
+struct proxy_balancer_method {
+ const char *name; /* name of the load balancer method*/
+ proxy_worker *(*finder)(proxy_balancer *balancer,
+ request_rec *r);
+ void *context; /* general purpose storage */
+};
+
+#if APR_HAS_THREADS
+#define PROXY_THREAD_LOCK(x) apr_thread_mutex_lock((x)->mutex)
+#define PROXY_THREAD_UNLOCK(x) apr_thread_mutex_unlock((x)->mutex)
+#else
+#define PROXY_THREAD_LOCK(x) APR_SUCCESS
+#define PROXY_THREAD_UNLOCK(x) APR_SUCCESS
+#endif
+
+/* hooks */
+
+/* Create a set of PROXY_DECLARE(type), PROXY_DECLARE_NONSTD(type) and
+ * PROXY_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define PROXY_DECLARE(type) type
+#define PROXY_DECLARE_NONSTD(type) type
+#define PROXY_DECLARE_DATA
+#elif defined(PROXY_DECLARE_STATIC)
+#define PROXY_DECLARE(type) type __stdcall
+#define PROXY_DECLARE_NONSTD(type) type
+#define PROXY_DECLARE_DATA
+#elif defined(PROXY_DECLARE_EXPORT)
+#define PROXY_DECLARE(type) __declspec(dllexport) type __stdcall
+#define PROXY_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define PROXY_DECLARE_DATA __declspec(dllexport)
+#else
+#define PROXY_DECLARE(type) __declspec(dllimport) type __stdcall
+#define PROXY_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define PROXY_DECLARE_DATA __declspec(dllimport)
+#endif
+
+/**
+ * Hook an optional proxy hook. Unlike static hooks, this uses a macro
+ * instead of a function.
+ */
+#define PROXY_OPTIONAL_HOOK(name,fn,pre,succ,order) \
+ APR_OPTIONAL_HOOK(proxy,name,fn,pre,succ,order)
+
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, scheme_handler, (request_rec *r,
+ proxy_worker *worker, proxy_server_conf *conf, char *url,
+ const char *proxyhost, apr_port_t proxyport))
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, canon_handler, (request_rec *r,
+ char *url))
+
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, create_req, (request_rec *r, request_rec *pr))
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, fixups, (request_rec *r))
+
+/**
+ * pre request hook.
+ * It will return the most suitable worker at the moment
+ * and coresponding balancer.
+ * The url is rewritten from balancer://cluster/uri to scheme://host:port/uri
+ * and then the scheme_handler is called.
+ *
+ */
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, pre_request, (proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf, char **url))
+/**
+ * post request hook.
+ * It is called after request for updating runtime balancer status.
+ */
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, post_request, (proxy_worker *worker,
+ proxy_balancer *balancer, request_rec *r,
+ proxy_server_conf *conf))
+
+/**
+ * request status hook
+ * It is called after all proxy processing has been done. This gives other
+ * modules a chance to create default content on failure, for example
+ */
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, request_status,
+ (int *status, request_rec *r))
+
+/* proxy_util.c */
+
+PROXY_DECLARE(request_rec *)ap_proxy_make_fake_req(conn_rec *c, request_rec *r);
+PROXY_DECLARE(int) ap_proxy_hex2c(const char *x);
+PROXY_DECLARE(void) ap_proxy_c2hex(int ch, char *x);
+PROXY_DECLARE(char *)ap_proxy_canonenc(apr_pool_t *p, const char *x, int len, enum enctype t,
+ int forcedec, int proxyreq);
+PROXY_DECLARE(char *)ap_proxy_canon_netloc(apr_pool_t *p, char **const urlp, char **userp,
+ char **passwordp, char **hostp, apr_port_t *port);
+PROXY_DECLARE(const char *)ap_proxy_date_canon(apr_pool_t *p, const char *x);
+PROXY_DECLARE(int) ap_proxy_liststr(const char *list, const char *val);
+PROXY_DECLARE(char *)ap_proxy_removestr(apr_pool_t *pool, const char *list, const char *val);
+PROXY_DECLARE(int) ap_proxy_hex2sec(const char *x);
+PROXY_DECLARE(void) ap_proxy_sec2hex(int t, char *y);
+PROXY_DECLARE(int) ap_proxyerror(request_rec *r, int statuscode, const char *message);
+PROXY_DECLARE(int) ap_proxy_is_ipaddr(struct dirconn_entry *This, apr_pool_t *p);
+PROXY_DECLARE(int) ap_proxy_is_domainname(struct dirconn_entry *This, apr_pool_t *p);
+PROXY_DECLARE(int) ap_proxy_is_hostname(struct dirconn_entry *This, apr_pool_t *p);
+PROXY_DECLARE(int) ap_proxy_is_word(struct dirconn_entry *This, apr_pool_t *p);
+PROXY_DECLARE(int) ap_proxy_checkproxyblock(request_rec *r, proxy_server_conf *conf, apr_sockaddr_t *uri_addr);
+PROXY_DECLARE(int) ap_proxy_pre_http_request(conn_rec *c, request_rec *r);
+PROXY_DECLARE(apr_status_t) ap_proxy_string_read(conn_rec *c, apr_bucket_brigade *bb, char *buff, size_t bufflen, int *eos);
+PROXY_DECLARE(void) ap_proxy_table_unmerge(apr_pool_t *p, apr_table_t *t, char *key);
+/* DEPRECATED (will be replaced with ap_proxy_connect_backend */
+PROXY_DECLARE(int) ap_proxy_connect_to_backend(apr_socket_t **, const char *, apr_sockaddr_t *, const char *, proxy_server_conf *, server_rec *, apr_pool_t *);
+PROXY_DECLARE(int) ap_proxy_ssl_enable(conn_rec *c);
+PROXY_DECLARE(int) ap_proxy_ssl_disable(conn_rec *c);
+PROXY_DECLARE(int) ap_proxy_conn_is_https(conn_rec *c);
+PROXY_DECLARE(const char *) ap_proxy_ssl_val(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *var);
+
+/* Header mapping functions, and a typedef of their signature */
+PROXY_DECLARE(const char *) ap_proxy_location_reverse_map(request_rec *r, proxy_dir_conf *conf, const char *url);
+PROXY_DECLARE(const char *) ap_proxy_cookie_reverse_map(request_rec *r, proxy_dir_conf *conf, const char *str);
+
+#if !defined(WIN32)
+typedef const char *(*ap_proxy_header_reverse_map_fn)(request_rec *,
+ proxy_dir_conf *, const char *);
+#elif defined(PROXY_DECLARE_STATIC)
+typedef const char *(__stdcall *ap_proxy_header_reverse_map_fn)(request_rec *,
+ proxy_dir_conf *, const char *);
+#elif defined(PROXY_DECLARE_EXPORT)
+typedef __declspec(dllexport) const char *
+ (__stdcall *ap_proxy_header_reverse_map_fn)(request_rec *,
+ proxy_dir_conf *, const char *);
+#else
+typedef __declspec(dllimport) const char *
+ (__stdcall *ap_proxy_header_reverse_map_fn)(request_rec *,
+ proxy_dir_conf *, const char *);
+#endif
+
+
+/* Connection pool API */
+/**
+ * Get the worker from proxy configuration
+ * @param p memory pool used for finding worker
+ * @param conf current proxy server configuration
+ * @param url url to find the worker from
+ * @return proxy_worker or NULL if not found
+ */
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url);
+/**
+ * Add the worker to proxy configuration
+ * @param worker the new worker
+ * @param p memory pool to allocate worker from
+ * @param conf current proxy server configuration
+ * @param url url containing worker name
+ * @return error message or NULL if successfull
+ */
+PROXY_DECLARE(const char *) ap_proxy_add_worker(proxy_worker **worker,
+ apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url);
+
+/**
+ * Create new worker
+ * @param p memory pool to allocate worker from
+ * @return new worker
+ */
+PROXY_DECLARE(proxy_worker *) ap_proxy_create_worker(apr_pool_t *p);
+
+/**
+ * Initize the worker's shared data
+ * @param conf current proxy server configuration
+ * @param worker worker to initialize
+ * @param s current server record
+ * @param worker worker to initialize
+ */
+PROXY_DECLARE(void) ap_proxy_initialize_worker_share(proxy_server_conf *conf,
+ proxy_worker *worker,
+ server_rec *s);
+
+
+/**
+ * Initize the worker
+ * @param worker worker to initialize
+ * @param s current server record
+ * @return APR_SUCCESS or error code
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_initialize_worker(proxy_worker *worker,
+ server_rec *s);
+/**
+ * Get the balancer from proxy configuration
+ * @param p memory pool used for finding balancer
+ * @param conf current proxy server configuration
+ * @param url url to find the worker from. Has to have balancer:// prefix
+ * @return proxy_balancer or NULL if not found
+ */
+PROXY_DECLARE(proxy_balancer *) ap_proxy_get_balancer(apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url);
+/**
+ * Add the balancer to proxy configuration
+ * @param balancer the new balancer
+ * @param p memory pool to allocate balancer from
+ * @param conf current proxy server configuration
+ * @param url url containing balancer name
+ * @return error message or NULL if successfull
+ */
+PROXY_DECLARE(const char *) ap_proxy_add_balancer(proxy_balancer **balancer,
+ apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url);
+
+/**
+ * Add the worker to the balancer
+ * @param pool memory pool for adding worker
+ * @param balancer balancer to add to
+ * @param balancer worker to add
+ * @note Single worker can be added to multiple balancers.
+ */
+PROXY_DECLARE(void) ap_proxy_add_worker_to_balancer(apr_pool_t *pool,
+ proxy_balancer *balancer,
+ proxy_worker *worker);
+/**
+ * Get the most suitable worker and(or) balancer for the request
+ * @param worker worker used for processing request
+ * @param balancer balancer used for processing request
+ * @param r current request
+ * @param conf current proxy server configuration
+ * @param url request url that balancer can rewrite.
+ * @return OK or HTTP_XXX error
+ * @note It calls balancer pre_request hook if the url starts with balancer://
+ * The balancer then rewrites the url to particular worker, like http://host:port
+ */
+PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf,
+ char **url);
+/**
+ * Post request worker and balancer cleanup
+ * @param worker worker used for processing request
+ * @param balancer balancer used for processing request
+ * @param r current request
+ * @param conf current proxy server configuration
+ * @return OK or HTTP_XXX error
+ * @note When ever the pre_request is called, the post_request has to be
+ * called too.
+ */
+PROXY_DECLARE(int) ap_proxy_post_request(proxy_worker *worker,
+ proxy_balancer *balancer,
+ request_rec *r,
+ proxy_server_conf *conf);
+
+/**
+ * Request status function
+ * @param status status of proxy request
+ * @return OK or DECLINED
+ */
+ PROXY_DECLARE(int) ap_proxy_request_status(int *status, request_rec *r);
+
+/**
+ * Deternime backend hostname and port
+ * @param p memory pool used for processing
+ * @param r current request
+ * @param conf current proxy server configuration
+ * @param worker worker used for processing request
+ * @param conn proxy connection struct
+ * @param uri processed uri
+ * @param url request url
+ * @param proxyname are we connecting directly or via s proxy
+ * @param proxyport proxy host port
+ * @param server_portstr Via headers server port
+ * @param server_portstr_size size of the server_portstr buffer
+ * @return OK or HTTP_XXX error
+ */
+PROXY_DECLARE(int) ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
+ proxy_server_conf *conf,
+ proxy_worker *worker,
+ proxy_conn_rec *conn,
+ apr_uri_t *uri,
+ char **url,
+ const char *proxyname,
+ apr_port_t proxyport,
+ char *server_portstr,
+ int server_portstr_size);
+/**
+ * Mark a worker for retry
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conf current proxy server configuration
+ * @param worker worker used for retrying
+ * @param s current server record
+ * @return OK if marked for retry, DECLINED otherwise
+ * @note Worker will be marker for retry if the time of the last retry
+ * has been ellapsed. In case there is no retry option set, defaults to
+ * number_of_retries seconds.
+ */
+PROXY_DECLARE(int) ap_proxy_retry_worker(const char *proxy_function,
+ proxy_worker *worker,
+ server_rec *s);
+/**
+ * Acquire a connection from workers connection pool
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param worker worker used for obtaining connection
+ * @param s current server record
+ * @return OK or HTTP_XXX error
+ * @note If the number of connections is exhaused the function will
+ * block untill the timeout is reached.
+ */
+PROXY_DECLARE(int) ap_proxy_acquire_connection(const char *proxy_function,
+ proxy_conn_rec **conn,
+ proxy_worker *worker,
+ server_rec *s);
+/**
+ * Release a connection back to worker connection pool
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param s current server record
+ * @return OK or HTTP_XXX error
+ * @note The connection will be closed if conn->close_on_release is set
+ */
+PROXY_DECLARE(int) ap_proxy_release_connection(const char *proxy_function,
+ proxy_conn_rec *conn,
+ server_rec *s);
+/**
+ * Make a connection to the backend
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param worker connection worker
+ * @param s current server record
+ * @return OK or HTTP_XXX error
+ * @note In case the socket already exists for conn, just check the link
+ * status.
+ */
+PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
+ proxy_conn_rec *conn,
+ proxy_worker *worker,
+ server_rec *s);
+/**
+ * Make a connection record for backend connection
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param c client connection record
+ * @param s current server record
+ * @return OK or HTTP_XXX error
+ */
+PROXY_DECLARE(int) ap_proxy_connection_create(const char *proxy_function,
+ proxy_conn_rec *conn,
+ conn_rec *c, server_rec *s);
+/**
+ * Signal the upstream chain that the connection to the backend broke in the
+ * middle of the response. This is done by sending an error bucket with
+ * status HTTP_BAD_GATEWAY and an EOS bucket up the filter chain.
+ * @param r current request record of client request
+ * @param brigade The brigade that is sent through the output filter chain
+ */
+PROXY_DECLARE(void) ap_proxy_backend_broke(request_rec *r,
+ apr_bucket_brigade *brigade);
+
+/* Scoreboard */
+#if MODULE_MAGIC_NUMBER_MAJOR > 20020903
+#define PROXY_HAS_SCOREBOARD 1
+#else
+#define PROXY_HAS_SCOREBOARD 0
+#endif
+
+#define PROXY_LBMETHOD "proxylbmethod"
+
+/* The number of dynamic workers that can be added when reconfiguring.
+ * If this limit is reached you must stop and restart the server.
+ */
+#define PROXY_DYNAMIC_BALANCER_LIMIT 16
+/**
+ * Calculate number of maximum number of workers in scoreboard.
+ * @return number of workers to allocate in the scoreboard
+ */
+int ap_proxy_lb_workers(void);
+
+/* For proxy_util */
+extern module PROXY_DECLARE_DATA proxy_module;
+
+extern int PROXY_DECLARE_DATA proxy_lb_workers;
+
+#endif /*MOD_PROXY_H*/
+/** @} */
diff --git a/modules/proxy/mod_proxy_ajp.c b/modules/proxy/mod_proxy_ajp.c
new file mode 100644
index 00000000..1a0d6e15
--- /dev/null
+++ b/modules/proxy/mod_proxy_ajp.c
@@ -0,0 +1,558 @@
+/* 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.
+ */
+
+/* AJP routines for Apache proxy */
+
+#include "mod_proxy.h"
+#include "ajp.h"
+
+module AP_MODULE_DECLARE_DATA proxy_ajp_module;
+
+/*
+ * Canonicalise http-like URLs.
+ * scheme is the scheme for the URL
+ * url is the URL starting with the first '/'
+ * def_port is the default port for this scheme.
+ */
+static int proxy_ajp_canon(request_rec *r, char *url)
+{
+ char *host, *path, *search, sport[7];
+ const char *err;
+ apr_port_t port = AJP13_DEF_PORT;
+
+ /* ap_port_of_scheme() */
+ if (strncasecmp(url, "ajp:", 4) == 0) {
+ url += 4;
+ }
+ else {
+ return DECLINED;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: AJP: canonicalising URL %s", url);
+
+ /*
+ * do syntactic check.
+ * We break the URL into host, port, path, search
+ */
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "error parsing URL %s: %s",
+ url, err);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * now parse path/search args, according to rfc1738
+ *
+ * N.B. if this isn't a true proxy request, then the URL _path_
+ * has already been decoded. True proxy requests have
+ * r->uri == r->unparsed_uri, and no others have that property.
+ */
+ if (r->uri == r->unparsed_uri) {
+ search = strchr(url, '?');
+ if (search != NULL)
+ *(search++) = '\0';
+ }
+ else
+ search = r->args;
+
+ /* process path */
+ path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+ r->proxyreq);
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+
+ if (ap_strchr_c(host, ':')) {
+ /* if literal IPv6 address */
+ host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+ }
+ r->filename = apr_pstrcat(r->pool, "proxy:ajp://", host, sport,
+ "/", path, (search) ? "?" : "",
+ (search) ? search : "", NULL);
+ return OK;
+}
+
+/*
+ * XXX: AJP Auto Flushing
+ *
+ * When processing CMD_AJP13_SEND_BODY_CHUNK AJP messages we will do a poll
+ * with FLUSH_WAIT miliseconds timeout to determine if more data is currently
+ * available at the backend. If there is no more data available, we flush
+ * the data to the client by adding a flush bucket to the brigade we pass
+ * up the filter chain.
+ * This is only a bandaid to fix the AJP/1.3 protocol shortcoming of not
+ * sending (actually not having defined) a flush message, when the data
+ * should be flushed to the client. As soon as this protocol shortcoming is
+ * fixed this code should be removed.
+ *
+ * For further discussion see PR37100.
+ * http://issues.apache.org/bugzilla/show_bug.cgi?id=37100
+ */
+
+/*
+ * process the request and write the response.
+ */
+static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
+ proxy_conn_rec *conn,
+ conn_rec *origin,
+ proxy_dir_conf *conf,
+ apr_uri_t *uri,
+ char *url, char *server_portstr)
+{
+ apr_status_t status;
+ int result;
+ apr_bucket *e;
+ apr_bucket_brigade *input_brigade;
+ apr_bucket_brigade *output_brigade;
+ ajp_msg_t *msg;
+ apr_size_t bufsiz;
+ char *buff;
+ apr_uint16_t size;
+ const char *tenc;
+ int havebody = 1;
+ int isok = 1;
+ apr_off_t bb_len;
+ int data_sent = 0;
+ int rv = 0;
+ apr_int32_t conn_poll_fd;
+ apr_pollfd_t *conn_poll;
+
+ /*
+ * Send the AJP request to the remote server
+ */
+
+ /* send request headers */
+ status = ajp_send_header(conn->sock, r, uri);
+ if (status != APR_SUCCESS) {
+ conn->close++;
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: AJP: request failed to %pI (%s)",
+ conn->worker->cp->addr,
+ conn->worker->hostname);
+ if (status == AJP_EOVERFLOW)
+ return HTTP_BAD_REQUEST;
+ else
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ /* allocate an AJP message to store the data of the buckets */
+ status = ajp_alloc_data_msg(r->pool, &buff, &bufsiz, &msg);
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close++;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: ajp_alloc_data_msg failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* read the first bloc of data */
+ input_brigade = apr_brigade_create(p, r->connection->bucket_alloc);
+ tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ if (tenc && (strcasecmp(tenc, "chunked") == 0)) {
+ /* The AJP protocol does not want body data yet */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: request is chunked");
+ } else {
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ AJP13_MAX_SEND_BODY_SZ);
+
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: ap_get_brigade failed");
+ apr_brigade_destroy(input_brigade);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* have something */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: APR_BUCKET_IS_EOS");
+ }
+
+ /* Try to send something */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: data to read (max %" APR_SIZE_T_FMT
+ " at %" APR_SIZE_T_FMT ")", bufsiz, msg->pos);
+
+ status = apr_brigade_flatten(input_brigade, buff, &bufsiz);
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close++;
+ apr_brigade_destroy(input_brigade);
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: apr_brigade_flatten");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_brigade_cleanup(input_brigade);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: got %" APR_SIZE_T_FMT " bytes of data", bufsiz);
+ if (bufsiz > 0) {
+ status = ajp_send_data_msg(conn->sock, msg, bufsiz);
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close++;
+ apr_brigade_destroy(input_brigade);
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: send failed to %pI (%s)",
+ conn->worker->cp->addr,
+ conn->worker->hostname);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ conn->worker->s->transferred += bufsiz;
+ }
+ }
+
+ /* read the response */
+ conn->data = NULL;
+ status = ajp_read_header(conn->sock, r,
+ (ajp_msg_t **)&(conn->data));
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close++;
+ apr_brigade_destroy(input_brigade);
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: read response failed from %pI (%s)",
+ conn->worker->cp->addr,
+ conn->worker->hostname);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ /* parse the reponse */
+ result = ajp_parse_type(r, conn->data);
+ output_brigade = apr_brigade_create(p, r->connection->bucket_alloc);
+
+ /*
+ * Prepare apr_pollfd_t struct for possible later check if there is currently
+ * data available from the backend (do not flush response to client)
+ * or not (flush response to client)
+ */
+ conn_poll = apr_pcalloc(p, sizeof(apr_pollfd_t));
+ conn_poll->reqevents = APR_POLLIN;
+ conn_poll->desc_type = APR_POLL_SOCKET;
+ conn_poll->desc.s = conn->sock;
+
+ bufsiz = AJP13_MAX_SEND_BODY_SZ;
+ while (isok) {
+ switch (result) {
+ case CMD_AJP13_GET_BODY_CHUNK:
+ if (havebody) {
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ /* This is the end */
+ bufsiz = 0;
+ havebody = 0;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, status, r->server,
+ "proxy: APR_BUCKET_IS_EOS");
+ } else {
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES,
+ APR_BLOCK_READ,
+ AJP13_MAX_SEND_BODY_SZ);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, status,
+ r->server,
+ "ap_get_brigade failed");
+ break;
+ }
+ bufsiz = AJP13_MAX_SEND_BODY_SZ;
+ status = apr_brigade_flatten(input_brigade, buff,
+ &bufsiz);
+ apr_brigade_cleanup(input_brigade);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, status,
+ r->server,
+ "apr_brigade_flatten failed");
+ break;
+ }
+ }
+
+ ajp_msg_reset(msg);
+ /* will go in ajp_send_data_msg */
+ status = ajp_send_data_msg(conn->sock, msg, bufsiz);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, status, r->server,
+ "ajp_send_data_msg failed");
+ break;
+ }
+ conn->worker->s->transferred += bufsiz;
+ } else {
+ /*
+ * something is wrong TC asks for more body but we are
+ * already at the end of the body data
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "ap_proxy_ajp_request error read after end");
+ isok = 0;
+ }
+ break;
+ case CMD_AJP13_SEND_HEADERS:
+ /* AJP13_SEND_HEADERS: process them */
+ status = ajp_parse_header(r, conf, conn->data);
+ if (status != APR_SUCCESS) {
+ isok = 0;
+ }
+ break;
+ case CMD_AJP13_SEND_BODY_CHUNK:
+ /* AJP13_SEND_BODY_CHUNK: piece of data */
+ status = ajp_parse_data(r, conn->data, &size, &buff);
+ if (status == APR_SUCCESS) {
+ e = apr_bucket_transient_create(buff, size,
+ r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(output_brigade, e);
+
+ if ( (conn->worker->flush_packets == flush_on) ||
+ ( (conn->worker->flush_packets == flush_auto) &&
+ (apr_poll(conn_poll, 1, &conn_poll_fd,
+ conn->worker->flush_wait)
+ == APR_TIMEUP) ) ) {
+ e = apr_bucket_flush_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(output_brigade, e);
+ }
+ apr_brigade_length(output_brigade, 0, &bb_len);
+ if (bb_len != -1)
+ conn->worker->s->read += bb_len;
+ if (ap_pass_brigade(r->output_filters,
+ output_brigade) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "proxy: error processing body");
+ isok = 0;
+ }
+ data_sent = 1;
+ apr_brigade_cleanup(output_brigade);
+ }
+ else {
+ isok = 0;
+ }
+ break;
+ case CMD_AJP13_END_RESPONSE:
+ e = apr_bucket_eos_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(output_brigade, e);
+ if (ap_pass_brigade(r->output_filters,
+ output_brigade) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "proxy: error processing body");
+ isok = 0;
+ }
+ /* XXX: what about flush here? See mod_jk */
+ data_sent = 1;
+ break;
+ default:
+ isok = 0;
+ break;
+ }
+
+ /*
+ * If connection has been aborted by client: Stop working.
+ * Nevertheless, we regard our operation so far as a success:
+ * So do not set isok to 0 and set result to CMD_AJP13_END_RESPONSE
+ * But: Close this connection to the backend.
+ */
+ if (r->connection->aborted) {
+ conn->close++;
+ result = CMD_AJP13_END_RESPONSE;
+ break;
+ }
+
+ if (!isok)
+ break;
+
+ if (result == CMD_AJP13_END_RESPONSE)
+ break;
+
+ /* read the response */
+ status = ajp_read_header(conn->sock, r,
+ (ajp_msg_t **)&(conn->data));
+ if (status != APR_SUCCESS) {
+ isok = 0;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, status, r->server,
+ "ajp_read_header failed");
+ break;
+ }
+ result = ajp_parse_type(r, conn->data);
+ }
+ apr_brigade_destroy(input_brigade);
+
+ /*
+ * Clear output_brigade to remove possible buckets that remained there
+ * after an error.
+ */
+ apr_brigade_cleanup(output_brigade);
+
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close++;
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: send body failed to %pI (%s)",
+ conn->worker->cp->addr,
+ conn->worker->hostname);
+ /*
+ * If we already send data, signal a broken backend connection
+ * upwards in the chain.
+ */
+ if (data_sent) {
+ ap_proxy_backend_broke(r, output_brigade);
+ /* Return DONE to avoid error messages being added to the stream */
+ rv = DONE;
+ } else
+ rv = HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ /*
+ * Ensure that we sent an EOS bucket thru the filter chain, if we already
+ * have sent some data. Maybe ap_proxy_backend_broke was called and added
+ * one to the brigade already (no longer making it empty). So we should
+ * not do this in this case.
+ */
+ if (data_sent && !r->eos_sent && APR_BRIGADE_EMPTY(output_brigade)) {
+ e = apr_bucket_eos_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(output_brigade, e);
+ }
+
+ /* If we have added something to the brigade above, sent it */
+ if (!APR_BRIGADE_EMPTY(output_brigade))
+ ap_pass_brigade(r->output_filters, output_brigade);
+
+ apr_brigade_destroy(output_brigade);
+
+ if (rv)
+ return rv;
+
+ /* Nice we have answer to send to the client */
+ if (result == CMD_AJP13_END_RESPONSE && isok) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: got response from %pI (%s)",
+ conn->worker->cp->addr,
+ conn->worker->hostname);
+ return OK;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: got bad response (%d) from %pI (%s)",
+ result,
+ conn->worker->cp->addr,
+ conn->worker->hostname);
+
+ /* We had a failure: Close connection to backend */
+ conn->close++;
+ return HTTP_SERVICE_UNAVAILABLE;
+}
+
+/*
+ * This handles ajp:// URLs
+ */
+static int proxy_ajp_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ int status;
+ char server_portstr[32];
+ conn_rec *origin = NULL;
+ proxy_conn_rec *backend = NULL;
+ const char *scheme = "AJP";
+ proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_module);
+
+ /*
+ * Note: Memory pool allocation.
+ * A downstream keepalive connection is always connected to the existence
+ * (or not) of an upstream keepalive connection. If this is not done then
+ * load balancing against multiple backend servers breaks (one backend
+ * server ends up taking 100% of the load), and the risk is run of
+ * downstream keepalive connections being kept open unnecessarily. This
+ * keeps webservers busy and ties up resources.
+ *
+ * As a result, we allocate all sockets out of the upstream connection
+ * pool, and when we want to reuse a socket, we check first whether the
+ * connection ID of the current upstream connection is the same as that
+ * of the connection when the socket was opened.
+ */
+ apr_pool_t *p = r->connection->pool;
+ apr_uri_t *uri = apr_palloc(r->connection->pool, sizeof(*uri));
+
+
+ if (strncasecmp(url, "ajp:", 4) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: AJP: declining URL %s", url);
+ return DECLINED;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: AJP: serving URL %s", url);
+
+ /* create space for state information */
+ if (!backend) {
+ status = ap_proxy_acquire_connection(scheme, &backend, worker,
+ r->server);
+ if (status != OK) {
+ if (backend) {
+ backend->close_on_recycle = 1;
+ ap_proxy_release_connection(scheme, backend, r->server);
+ }
+ return status;
+ }
+ }
+
+ backend->is_ssl = 0;
+ backend->close_on_recycle = 0;
+
+ /* Step One: Determine Who To Connect To */
+ status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &url, proxyname, proxyport,
+ server_portstr,
+ sizeof(server_portstr));
+
+ if (status != OK)
+ goto cleanup;
+
+ /* Step Two: Make the Connection */
+ if (ap_proxy_connect_backend(scheme, backend, worker, r->server)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "proxy: AJP: failed to make connection to backend: %s",
+ backend->hostname);
+ status = HTTP_SERVICE_UNAVAILABLE;
+ goto cleanup;
+ }
+
+ /* Step Three: Process the Request */
+ status = ap_proxy_ajp_request(p, r, backend, origin, dconf, uri, url,
+ server_portstr);
+
+cleanup:
+ /* Do not close the socket */
+ ap_proxy_release_connection(scheme, backend, r->server);
+ return status;
+}
+
+static void ap_proxy_http_register_hook(apr_pool_t *p)
+{
+ proxy_hook_scheme_handler(proxy_ajp_handler, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(proxy_ajp_canon, NULL, NULL, APR_HOOK_FIRST);
+}
+
+module AP_MODULE_DECLARE_DATA proxy_ajp_module = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_proxy_http_register_hook /* register hooks */
+};
+
diff --git a/modules/proxy/mod_proxy_ajp.dsp b/modules/proxy/mod_proxy_ajp.dsp
new file mode 100644
index 00000000..14e0730c
--- /dev/null
+++ b/modules/proxy/mod_proxy_ajp.dsp
@@ -0,0 +1,135 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_ajp" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_ajp - Win32 Release
+!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 "mod_proxy_ajp.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 "mod_proxy_ajp.mak" CFG="mod_proxy_ajp - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_ajp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_ajp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_ajp - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_ajp_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_ajp.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_proxy_ajp.so" /d "LONG_NAME=proxy_ajp_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:"Release/mod_proxy_ajp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /debug /out:"Release/mod_proxy_ajp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_proxy_ajp - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# 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 "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_ajp_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_ajp.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_proxy_ajp.so" /d "LONG_NAME=proxy_ajp_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_ajp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_ajp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_ajp - Win32 Release"
+# Name "mod_proxy_ajp - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_ajp.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Group "Ajp Files"
+
+# PROP Default_Filter ""
+# Begin Source File
+
+SOURCE=.\ajp.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\ajp_header.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ajp_header.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\ajp_link.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ajp_msg.c
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_balancer.c b/modules/proxy/mod_proxy_balancer.c
new file mode 100644
index 00000000..c506f48f
--- /dev/null
+++ b/modules/proxy/mod_proxy_balancer.c
@@ -0,0 +1,943 @@
+/* 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.
+ */
+
+/* Load balancer module for Apache proxy */
+
+#define CORE_PRIVATE
+
+#include "mod_proxy.h"
+#include "ap_mpm.h"
+#include "apr_version.h"
+#include "apr_hooks.h"
+
+module AP_MODULE_DECLARE_DATA proxy_balancer_module;
+
+static int proxy_balancer_canon(request_rec *r, char *url)
+{
+ char *host, *path, *search;
+ const char *err;
+ apr_port_t port = 0;
+
+ if (strncasecmp(url, "balancer:", 9) == 0) {
+ url += 9;
+ }
+ else {
+ return DECLINED;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: BALANCER: canonicalising URL %s", url);
+
+ /* do syntatic check.
+ * We break the URL into host, port, path, search
+ */
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "error parsing URL %s: %s",
+ url, err);
+ return HTTP_BAD_REQUEST;
+ }
+ /* now parse path/search args, according to rfc1738 */
+ /* N.B. if this isn't a true proxy request, then the URL _path_
+ * has already been decoded. True proxy requests have r->uri
+ * == r->unparsed_uri, and no others have that property.
+ */
+ if (r->uri == r->unparsed_uri) {
+ search = strchr(url, '?');
+ if (search != NULL)
+ *(search++) = '\0';
+ }
+ else
+ search = r->args;
+
+ /* process path */
+ path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, r->proxyreq);
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+
+ r->filename = apr_pstrcat(r->pool, "proxy:balancer://", host,
+ "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
+ return OK;
+}
+
+static int init_balancer_members(proxy_server_conf *conf, server_rec *s,
+ proxy_balancer *balancer)
+{
+ int i;
+ proxy_worker *workers;
+
+ workers = (proxy_worker *)balancer->workers->elts;
+
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ ap_proxy_initialize_worker_share(conf, workers, s);
+ ap_proxy_initialize_worker(workers, s);
+ ++workers;
+ }
+
+ workers = (proxy_worker *)balancer->workers->elts;
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ /* Set to the original configuration */
+ workers[i].s->lbstatus = workers[i].s->lbfactor =
+ (workers[i].lbfactor ? workers[i].lbfactor : 1);
+ }
+ /* Set default number of attempts to the number of
+ * workers.
+ */
+ if (!balancer->max_attempts_set && balancer->workers->nelts > 1) {
+ balancer->max_attempts = balancer->workers->nelts - 1;
+ balancer->max_attempts_set = 1;
+ }
+ return 0;
+}
+
+/* Retrieve the parameter with the given name
+ * Something like 'JSESSIONID=12345...N'
+ */
+static char *get_path_param(apr_pool_t *pool, char *url,
+ const char *name)
+{
+ char *path = NULL;
+
+ for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
+ path += strlen(name);
+ if (*path == '=') {
+ /*
+ * Session path was found, get it's value
+ */
+ ++path;
+ if (strlen(path)) {
+ char *q;
+ path = apr_pstrdup(pool, path);
+ if ((q = strchr(path, '?')))
+ *q = '\0';
+ return path;
+ }
+ }
+ }
+ return NULL;
+}
+
+static char *get_cookie_param(request_rec *r, const char *name)
+{
+ const char *cookies;
+ const char *start_cookie;
+
+ if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
+ for (start_cookie = ap_strstr_c(cookies, name); start_cookie;
+ start_cookie = ap_strstr_c(start_cookie + 1, name)) {
+ if (start_cookie == cookies ||
+ start_cookie[-1] == ';' ||
+ start_cookie[-1] == ',' ||
+ isspace(start_cookie[-1])) {
+
+ start_cookie += strlen(name);
+ while(*start_cookie && isspace(*start_cookie))
+ ++start_cookie;
+ if (*start_cookie == '=' && start_cookie[1]) {
+ /*
+ * Session cookie was found, get it's value
+ */
+ char *end_cookie, *cookie;
+ ++start_cookie;
+ cookie = apr_pstrdup(r->pool, start_cookie);
+ if ((end_cookie = strchr(cookie, ';')) != NULL)
+ *end_cookie = '\0';
+ if((end_cookie = strchr(cookie, ',')) != NULL)
+ *end_cookie = '\0';
+ return cookie;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+/* Find the worker that has the 'route' defined
+ */
+static proxy_worker *find_route_worker(proxy_balancer *balancer,
+ const char *route)
+{
+ int i;
+ proxy_worker *worker = (proxy_worker *)balancer->workers->elts;
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ if (*(worker->s->route) && strcmp(worker->s->route, route) == 0) {
+ return worker;
+ }
+ worker++;
+ }
+ return NULL;
+}
+
+static proxy_worker *find_session_route(proxy_balancer *balancer,
+ request_rec *r,
+ char **route,
+ char **url)
+{
+ proxy_worker *worker = NULL;
+
+ if (!balancer->sticky)
+ return NULL;
+ /* Try to find the sticky route inside url */
+ *route = get_path_param(r->pool, *url, balancer->sticky);
+ if (!*route)
+ *route = get_cookie_param(r, balancer->sticky);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: BALANCER: Found value %s for "
+ "stickysession %s", *route, balancer->sticky);
+ /*
+ * If we found a value for sticksession, find the first '.' within.
+ * Everything after '.' (if present) is our route.
+ */
+ if ((*route) && ((*route = strchr(*route, '.')) != NULL ))
+ (*route)++;
+ if ((*route) && (**route)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: BALANCER: Found route %s", *route);
+ /* We have a route in path or in cookie
+ * Find the worker that has this route defined.
+ */
+ worker = find_route_worker(balancer, *route);
+ if (worker && !PROXY_WORKER_IS_USABLE(worker)) {
+ /* We have a worker that is unusable.
+ * It can be in error or disabled, but in case
+ * it has a redirection set use that redirection worker.
+ * This enables to safely remove the member from the
+ * balancer. Of course you will need a some kind of
+ * session replication between those two remote.
+ */
+ if (*worker->s->redirect)
+ worker = find_route_worker(balancer, worker->s->redirect);
+ /* Check if the redirect worker is usable */
+ if (worker && !PROXY_WORKER_IS_USABLE(worker))
+ worker = NULL;
+ }
+ return worker;
+ }
+ else
+ return NULL;
+}
+
+static proxy_worker *find_best_worker(proxy_balancer *balancer,
+ request_rec *r)
+{
+ proxy_worker *candidate = NULL;
+
+ if (PROXY_THREAD_LOCK(balancer) != APR_SUCCESS)
+ return NULL;
+
+ candidate = (*balancer->lbmethod->finder)(balancer, r);
+
+/*
+ PROXY_THREAD_UNLOCK(balancer);
+ return NULL;
+*/
+
+ PROXY_THREAD_UNLOCK(balancer);
+
+ if (candidate == NULL) {
+ /* All the workers are in error state or disabled.
+ * If the balancer has a timeout sleep for a while
+ * and try again to find the worker. The chances are
+ * that some other thread will release a connection.
+ * By default the timeout is not set, and the server
+ * returns SERVER_BUSY.
+ */
+#if APR_HAS_THREADS
+ if (balancer->timeout) {
+ /* XXX: This can perhaps be build using some
+ * smarter mechanism, like tread_cond.
+ * But since the statuses can came from
+ * different childs, use the provided algo.
+ */
+ apr_interval_time_t timeout = balancer->timeout;
+ apr_interval_time_t step, tval = 0;
+ /* Set the timeout to 0 so that we don't
+ * end in infinite loop
+ */
+ balancer->timeout = 0;
+ step = timeout / 100;
+ while (tval < timeout) {
+ apr_sleep(step);
+ /* Try again */
+ if ((candidate = find_best_worker(balancer, r)))
+ break;
+ tval += step;
+ }
+ /* restore the timeout */
+ balancer->timeout = timeout;
+ }
+#endif
+ }
+ return candidate;
+}
+
+static int rewrite_url(request_rec *r, proxy_worker *worker,
+ char **url)
+{
+ const char *scheme = strstr(*url, "://");
+ const char *path = NULL;
+
+ if (scheme)
+ path = ap_strchr_c(scheme + 3, '/');
+
+ /* we break the URL into host, port, uri */
+ if (!worker) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
+ "missing worker. URI cannot be parsed: ", *url,
+ NULL));
+ }
+
+ *url = apr_pstrcat(r->pool, worker->name, path, NULL);
+
+ return OK;
+}
+
+static int proxy_balancer_pre_request(proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf, char **url)
+{
+ int access_status;
+ proxy_worker *runtime;
+ char *route = NULL;
+ apr_status_t rv;
+
+ *worker = NULL;
+ /* Step 1: check if the url is for us
+ * The url we can handle starts with 'balancer://'
+ * If balancer is already provided skip the search
+ * for balancer, because this is failover attempt.
+ */
+ if (!*balancer &&
+ !(*balancer = ap_proxy_get_balancer(r->pool, conf, *url)))
+ return DECLINED;
+
+ /* Step 2: find the session route */
+
+ runtime = find_session_route(*balancer, r, &route, url);
+ /* Lock the LoadBalancer
+ * XXX: perhaps we need the process lock here
+ */
+ if ((rv = PROXY_THREAD_LOCK(*balancer)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
+ "proxy: BALANCER: lock");
+ return DECLINED;
+ }
+ if (runtime) {
+ int i, total_factor = 0;
+ proxy_worker *workers;
+ /* We have a sticky load balancer
+ * Update the workers status
+ * so that even session routes get
+ * into account.
+ */
+ workers = (proxy_worker *)(*balancer)->workers->elts;
+ for (i = 0; i < (*balancer)->workers->nelts; i++) {
+ /* Take into calculation only the workers that are
+ * not in error state or not disabled.
+ */
+ if (PROXY_WORKER_IS_USABLE(workers)) {
+ workers->s->lbstatus += workers->s->lbfactor;
+ total_factor += workers->s->lbfactor;
+ }
+ workers++;
+ }
+ runtime->s->lbstatus -= total_factor;
+ runtime->s->elected++;
+
+ *worker = runtime;
+ }
+ else if (route && (*balancer)->sticky_force) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "proxy: BALANCER: (%s). All workers are in error state for route (%s)",
+ (*balancer)->name, route);
+ PROXY_THREAD_UNLOCK(*balancer);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ PROXY_THREAD_UNLOCK(*balancer);
+ if (!*worker) {
+ runtime = find_best_worker(*balancer, r);
+ if (!runtime) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "proxy: BALANCER: (%s). All workers are in error state",
+ (*balancer)->name);
+
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ *worker = runtime;
+ }
+
+ /* Rewrite the url from 'balancer://url'
+ * to the 'worker_scheme://worker_hostname[:worker_port]/url'
+ * This replaces the balancers fictional name with the
+ * real hostname of the elected worker.
+ */
+ access_status = rewrite_url(r, *worker, url);
+ /* Add the session route to request notes if present */
+ if (route) {
+ apr_table_setn(r->notes, "session-sticky", (*balancer)->sticky);
+ apr_table_setn(r->notes, "session-route", route);
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: BALANCER (%s) worker (%s) rewritten to %s",
+ (*balancer)->name, (*worker)->name, *url);
+
+ return access_status;
+}
+
+static int proxy_balancer_post_request(proxy_worker *worker,
+ proxy_balancer *balancer,
+ request_rec *r,
+ proxy_server_conf *conf)
+{
+ apr_status_t rv;
+
+ if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
+ "proxy: BALANCER: lock");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ /* TODO: calculate the bytes transferred
+ * This will enable to elect the worker that has
+ * the lowest load.
+ * The bytes transferred depends on the protocol
+ * used, so each protocol handler should keep the
+ * track on that.
+ */
+
+ PROXY_THREAD_UNLOCK(balancer);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy_balancer_post_request for (%s)", balancer->name);
+
+ return OK;
+}
+
+static void recalc_factors(proxy_balancer *balancer)
+{
+ int i;
+ proxy_worker *workers;
+
+
+ /* Recalculate lbfactors */
+ workers = (proxy_worker *)balancer->workers->elts;
+ /* Special case if there is only one worker it's
+ * load factor will always be 1
+ */
+ if (balancer->workers->nelts == 1) {
+ workers->s->lbstatus = workers->s->lbfactor = 1;
+ return;
+ }
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ /* Update the status entries */
+ workers[i].s->lbstatus = workers[i].s->lbfactor;
+ }
+}
+
+/* Manages the loadfactors and member status
+ */
+static int balancer_handler(request_rec *r)
+{
+ void *sconf = r->server->module_config;
+ proxy_server_conf *conf = (proxy_server_conf *)
+ ap_get_module_config(sconf, &proxy_module);
+ proxy_balancer *balancer, *bsel = NULL;
+ proxy_worker *worker, *wsel = NULL;
+ apr_table_t *params = apr_table_make(r->pool, 10);
+ int access_status;
+ int i, n;
+ const char *name;
+
+ /* is this for us? */
+ if (strcmp(r->handler, "balancer-manager"))
+ return DECLINED;
+ r->allowed = (AP_METHOD_BIT << M_GET);
+ if (r->method_number != M_GET)
+ return DECLINED;
+
+ if (r->args) {
+ char *args = apr_pstrdup(r->pool, r->args);
+ char *tok, *val;
+ while (args && *args) {
+ if ((val = ap_strchr(args, '='))) {
+ *val++ = '\0';
+ if ((tok = ap_strchr(val, '&')))
+ *tok++ = '\0';
+ /*
+ * Special case: workers are allowed path information
+ */
+ if ((access_status = ap_unescape_url(val)) != OK)
+ if (strcmp(args, "w") || (access_status != HTTP_NOT_FOUND))
+ return access_status;
+ apr_table_setn(params, args, val);
+ args = tok;
+ }
+ else
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ if ((name = apr_table_get(params, "b")))
+ bsel = ap_proxy_get_balancer(r->pool, conf,
+ apr_pstrcat(r->pool, "balancer://", name, NULL));
+ if ((name = apr_table_get(params, "w"))) {
+ proxy_worker *ws;
+
+ ws = ap_proxy_get_worker(r->pool, conf, name);
+ if (ws) {
+ worker = (proxy_worker *)bsel->workers->elts;
+ for (n = 0; n < bsel->workers->nelts; n++) {
+ if (strcasecmp(worker->name, ws->name) == 0) {
+ wsel = worker;
+ break;
+ }
+ ++worker;
+ }
+ }
+ }
+ /* First set the params */
+ if (bsel) {
+ const char *val;
+ if ((val = apr_table_get(params, "ss"))) {
+ if (strlen(val))
+ bsel->sticky = apr_pstrdup(conf->pool, val);
+ else
+ bsel->sticky = NULL;
+ }
+ if ((val = apr_table_get(params, "tm"))) {
+ int ival = atoi(val);
+ if (ival >= 0)
+ bsel->timeout = apr_time_from_sec(ival);
+ }
+ if ((val = apr_table_get(params, "fa"))) {
+ int ival = atoi(val);
+ if (ival >= 0)
+ bsel->max_attempts = ival;
+ bsel->max_attempts_set = 1;
+ }
+ if ((val = apr_table_get(params, "lm"))) {
+ proxy_balancer_method *provider;
+ provider = ap_lookup_provider(PROXY_LBMETHOD, val, "0");
+ if (provider) {
+ bsel->lbmethod = provider;
+ }
+ }
+ }
+ if (wsel) {
+ const char *val;
+ if ((val = apr_table_get(params, "lf"))) {
+ int ival = atoi(val);
+ if (ival >= 1 && ival <= 100) {
+ wsel->s->lbfactor = ival;
+ if (bsel)
+ recalc_factors(bsel);
+ }
+ }
+ if ((val = apr_table_get(params, "wr"))) {
+ if (strlen(val) && strlen(val) < PROXY_WORKER_MAX_ROUTE_SIZ)
+ strcpy(wsel->s->route, val);
+ else
+ *wsel->s->route = '\0';
+ }
+ if ((val = apr_table_get(params, "rr"))) {
+ if (strlen(val) && strlen(val) < PROXY_WORKER_MAX_ROUTE_SIZ)
+ strcpy(wsel->s->redirect, val);
+ else
+ *wsel->s->redirect = '\0';
+ }
+ if ((val = apr_table_get(params, "dw"))) {
+ if (!strcasecmp(val, "Disable"))
+ wsel->s->status |= PROXY_WORKER_DISABLED;
+ else if (!strcasecmp(val, "Enable"))
+ wsel->s->status &= ~PROXY_WORKER_DISABLED;
+ }
+
+ }
+ if (apr_table_get(params, "xml")) {
+ ap_set_content_type(r, "text/xml");
+ ap_rputs("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", r);
+ ap_rputs("<httpd:manager xmlns:httpd=\"http://httpd.apache.org\">\n", r);
+ ap_rputs(" <httpd:balancers>\n", r);
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++) {
+ ap_rputs(" <httpd:balancer>\n", r);
+ ap_rvputs(r, " <httpd:name>", balancer->name, "</httpd:name>\n", NULL);
+ ap_rputs(" <httpd:workers>\n", r);
+ worker = (proxy_worker *)balancer->workers->elts;
+ for (n = 0; n < balancer->workers->nelts; n++) {
+ ap_rputs(" <httpd:worker>\n", r);
+ ap_rvputs(r, " <httpd:scheme>", worker->scheme,
+ "</httpd:scheme>\n", NULL);
+ ap_rvputs(r, " <httpd:hostname>", worker->hostname,
+ "</httpd:hostname>\n", NULL);
+ ap_rprintf(r, " <httpd:loadfactor>%d</httpd:loadfactor>\n",
+ worker->s->lbfactor);
+ ap_rputs(" </httpd:worker>\n", r);
+ ++worker;
+ }
+ ap_rputs(" </httpd:workers>\n", r);
+ ap_rputs(" </httpd:balancer>\n", r);
+ ++balancer;
+ }
+ ap_rputs(" </httpd:balancers>\n", r);
+ ap_rputs("</httpd:manager>", r);
+ }
+ else {
+ ap_set_content_type(r, "text/html");
+ ap_rputs(DOCTYPE_HTML_3_2
+ "<html><head><title>Balancer Manager</title></head>\n", r);
+ ap_rputs("<body><h1>Load Balancer Manager for ", r);
+ ap_rvputs(r, ap_get_server_name(r), "</h1>\n\n", NULL);
+ ap_rvputs(r, "<dl><dt>Server Version: ",
+ ap_get_server_version(), "</dt>\n", NULL);
+ ap_rvputs(r, "<dt>Server Built: ",
+ ap_get_server_built(), "\n</dt></dl>\n", NULL);
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++) {
+
+ ap_rputs("<hr />\n<h3>LoadBalancer Status for ", r);
+ ap_rvputs(r, "<a href=\"", r->uri, "?b=",
+ balancer->name + sizeof("balancer://") - 1,
+ "\">", NULL);
+ ap_rvputs(r, balancer->name, "</a></h3>\n\n", NULL);
+ ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
+ "<th>StickySession</th><th>Timeout</th><th>FailoverAttempts</th><th>Method</th>"
+ "</tr>\n<tr>", r);
+ ap_rvputs(r, "<td>", balancer->sticky, NULL);
+ ap_rprintf(r, "</td><td>%" APR_TIME_T_FMT "</td>",
+ apr_time_sec(balancer->timeout));
+ ap_rprintf(r, "<td>%d</td>\n", balancer->max_attempts);
+ ap_rprintf(r, "<td>%s</td>\n",
+ balancer->lbmethod->name);
+ ap_rputs("</table>\n<br />", r);
+ ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
+ "<th>Worker URL</th>"
+ "<th>Route</th><th>RouteRedir</th>"
+ "<th>Factor</th><th>Status</th>"
+ "</tr>\n", r);
+
+ worker = (proxy_worker *)balancer->workers->elts;
+ for (n = 0; n < balancer->workers->nelts; n++) {
+
+ ap_rvputs(r, "<tr>\n<td><a href=\"", r->uri, "?b=",
+ balancer->name + sizeof("balancer://") - 1, "&w=",
+ ap_escape_uri(r->pool, worker->name),
+ "\">", NULL);
+ ap_rvputs(r, worker->name, "</a></td>", NULL);
+ ap_rvputs(r, "<td>", worker->s->route, NULL);
+ ap_rvputs(r, "</td><td>", worker->s->redirect, NULL);
+ ap_rprintf(r, "</td><td>%d</td><td>", worker->s->lbfactor);
+ if (worker->s->status & PROXY_WORKER_DISABLED)
+ ap_rputs("Dis", r);
+ else if (worker->s->status & PROXY_WORKER_IN_ERROR)
+ ap_rputs("Err", r);
+ else if (worker->s->status & PROXY_WORKER_INITIALIZED)
+ ap_rputs("Ok", r);
+ else
+ ap_rputs("-", r);
+ ap_rputs("</td></tr>\n", r);
+
+ ++worker;
+ }
+ ap_rputs("</table>\n", r);
+ ++balancer;
+ }
+ ap_rputs("<hr />\n", r);
+ if (wsel && bsel) {
+ ap_rputs("<h3>Edit worker settings for ", r);
+ ap_rvputs(r, wsel->name, "</h3>\n", NULL);
+ ap_rvputs(r, "<form method=\"GET\" action=\"", NULL);
+ ap_rvputs(r, r->uri, "\">\n<dl>", NULL);
+ ap_rputs("<table><tr><td>Load factor:</td><td><input name=\"lf\" type=text ", r);
+ ap_rprintf(r, "value=\"%d\"></td><tr>\n", wsel->s->lbfactor);
+ ap_rputs("<tr><td>Route:</td><td><input name=\"wr\" type=text ", r);
+ ap_rvputs(r, "value=\"", wsel->route, NULL);
+ ap_rputs("\"></td><tr>\n", r);
+ ap_rputs("<tr><td>Route Redirect:</td><td><input name=\"rr\" type=text ", r);
+ ap_rvputs(r, "value=\"", wsel->redirect, NULL);
+ ap_rputs("\"></td><tr>\n", r);
+ ap_rputs("<tr><td>Status:</td><td>Disabled: <input name=\"dw\" value=\"Disable\" type=radio", r);
+ if (wsel->s->status & PROXY_WORKER_DISABLED)
+ ap_rputs(" checked", r);
+ ap_rputs("> | Enabled: <input name=\"dw\" value=\"Enable\" type=radio", r);
+ if (!(wsel->s->status & PROXY_WORKER_DISABLED))
+ ap_rputs(" checked", r);
+ ap_rputs("></td><tr>\n", r);
+ ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
+ ap_rvputs(r, "</table>\n<input type=hidden name=\"w\" ", NULL);
+ ap_rvputs(r, "value=\"", ap_escape_uri(r->pool, wsel->name), "\">\n", NULL);
+ ap_rvputs(r, "<input type=hidden name=\"b\" ", NULL);
+ ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1,
+ "\">\n</form>\n", NULL);
+ ap_rputs("<hr />\n", r);
+ }
+ else if (bsel) {
+ ap_rputs("<h3>Edit balancer settings for ", r);
+ ap_rvputs(r, bsel->name, "</h3>\n", NULL);
+ ap_rvputs(r, "<form method=\"GET\" action=\"", NULL);
+ ap_rvputs(r, r->uri, "\">\n<dl>", NULL);
+ ap_rputs("<table><tr><td>StickySession Identifier:</td><td><input name=\"ss\" type=text ", r);
+ if (bsel->sticky)
+ ap_rvputs(r, "value=\"", bsel->sticky, "\"", NULL);
+ ap_rputs("></td><tr>\n<tr><td>Timeout:</td><td><input name=\"tm\" type=text ", r);
+ ap_rprintf(r, "value=\"%" APR_TIME_T_FMT "\"></td></tr>\n",
+ apr_time_sec(bsel->timeout));
+ ap_rputs("<tr><td>Failover Attempts:</td><td><input name=\"fa\" type=text ", r);
+ ap_rprintf(r, "value=\"%d\"></td></tr>\n",
+ bsel->max_attempts);
+ ap_rputs("<tr><td>LB Method:</td><td><select name=\"lm\">", r);
+ {
+ apr_array_header_t *methods;
+ ap_list_provider_names_t *method;
+ int i;
+ methods = ap_list_provider_names(r->pool, PROXY_LBMETHOD, "0");
+ method = (ap_list_provider_names_t *)methods->elts;
+ for (i = 0; i < methods->nelts; i++) {
+ ap_rprintf(r, "<option value=\"%s\" %s>%s</option>", method->provider_name,
+ (!strcasecmp(bsel->lbmethod->name, method->provider_name)) ? "selected" : "",
+ method->provider_name);
+ method++;
+ }
+ }
+ ap_rputs("</select></td></tr>\n", r);
+ ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
+ ap_rvputs(r, "</table>\n<input type=hidden name=\"b\" ", NULL);
+ ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1,
+ "\">\n</form>\n", NULL);
+ ap_rputs("<hr />\n", r);
+ }
+ ap_rputs(ap_psignature("",r), r);
+ ap_rputs("</body></html>\n", r);
+ }
+ return OK;
+}
+
+static void child_init(apr_pool_t *p, server_rec *s)
+{
+ while (s) {
+ void *sconf = s->module_config;
+ proxy_server_conf *conf;
+ proxy_balancer *balancer;
+ int i;
+ conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module);
+
+ /* Initialize shared scoreboard data */
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++) {
+ init_balancer_members(conf, s, balancer);
+ balancer++;
+ }
+ s = s->next;
+ }
+
+}
+
+/*
+ * The idea behind the find_best_byrequests scheduler is the following:
+ *
+ * lbfactor is "how much we expect this worker to work", or "the worker's
+ * normalized work quota".
+ *
+ * lbstatus is "how urgent this worker has to work to fulfill its quota
+ * of work".
+ *
+ * We distribute each worker's work quota to the worker, and then look
+ * which of them needs to work most urgently (biggest lbstatus). This
+ * worker is then selected for work, and its lbstatus reduced by the
+ * total work quota we distributed to all workers. Thus the sum of all
+ * lbstatus does not change.(*)
+ *
+ * If some workers are disabled, the others will
+ * still be scheduled correctly.
+ *
+ * If a balancer is configured as follows:
+ *
+ * worker a b c d
+ * lbfactor 25 25 25 25
+ *
+ * And b gets disabled, the following schedule is produced:
+ *
+ * a c d a c d a c d ...
+ *
+ * Note that the above lbfactor setting is the *exact* same as:
+ *
+ * worker a b c d
+ * lbfactor 1 1 1 1
+ *
+ * Asymmetric configurations work as one would expect. For
+ * example:
+ *
+ * worker a b c d
+ * lbfactor 1 1 1 2
+ *
+ * would have a, b and c all handling about the same
+ * amount of load with d handling twice what a or b
+ * or c handles individually. So we could see:
+ *
+ * b a d c d a c d b d ...
+ *
+ */
+
+static proxy_worker *find_best_byrequests(proxy_balancer *balancer,
+ request_rec *r)
+{
+ int i;
+ int total_factor = 0;
+ proxy_worker *worker = (proxy_worker *)balancer->workers->elts;
+ proxy_worker *mycandidate = NULL;
+
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: Entering byrequests for BALANCER (%s)",
+ balancer->name);
+
+ /* First try to see if we have available candidate */
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ /* If the worker is in error state run
+ * retry on that worker. It will be marked as
+ * operational if the retry timeout is elapsed.
+ * The worker might still be unusable, but we try
+ * anyway.
+ */
+ if (!PROXY_WORKER_IS_USABLE(worker))
+ ap_proxy_retry_worker("BALANCER", worker, r->server);
+ /* Take into calculation only the workers that are
+ * not in error state or not disabled.
+ */
+ if (PROXY_WORKER_IS_USABLE(worker)) {
+ worker->s->lbstatus += worker->s->lbfactor;
+ total_factor += worker->s->lbfactor;
+ if (!mycandidate || worker->s->lbstatus > mycandidate->s->lbstatus)
+ mycandidate = worker;
+ }
+ worker++;
+ }
+
+ if (mycandidate) {
+ mycandidate->s->lbstatus -= total_factor;
+ mycandidate->s->elected++;
+ }
+
+ return mycandidate;
+}
+
+/*
+ * The idea behind the find_best_bytraffic scheduler is the following:
+ *
+ * We know the amount of traffic (bytes in and out) handled by each
+ * worker. We normalize that traffic by each workers' weight. So assuming
+ * a setup as below:
+ *
+ * worker a b c
+ * lbfactor 1 1 3
+ *
+ * the scheduler will allow worker c to handle 3 times the
+ * traffic of a and b. If each request/response results in the
+ * same amount of traffic, then c would be accessed 3 times as
+ * often as a or b. If, for example, a handled a request that
+ * resulted in a large i/o bytecount, then b and c would be
+ * chosen more often, to even things out.
+ */
+static proxy_worker *find_best_bytraffic(proxy_balancer *balancer,
+ request_rec *r)
+{
+ int i;
+ apr_off_t mytraffic = 0;
+ apr_off_t curmin = 0;
+ proxy_worker *worker = (proxy_worker *)balancer->workers->elts;
+ proxy_worker *mycandidate = NULL;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: Entering bytraffic for BALANCER (%s)",
+ balancer->name);
+
+ /* First try to see if we have available candidate */
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ /* If the worker is in error state run
+ * retry on that worker. It will be marked as
+ * operational if the retry timeout is elapsed.
+ * The worker might still be unusable, but we try
+ * anyway.
+ */
+ if (!PROXY_WORKER_IS_USABLE(worker))
+ ap_proxy_retry_worker("BALANCER", worker, r->server);
+ /* Take into calculation only the workers that are
+ * not in error state or not disabled.
+ */
+ if (PROXY_WORKER_IS_USABLE(worker)) {
+ mytraffic = (worker->s->transferred/worker->s->lbfactor) +
+ (worker->s->read/worker->s->lbfactor);
+ if (!mycandidate || mytraffic < curmin) {
+ mycandidate = worker;
+ curmin = mytraffic;
+ }
+ }
+ worker++;
+ }
+
+ if (mycandidate) {
+ mycandidate->s->elected++;
+ }
+
+ return mycandidate;
+}
+
+/*
+ * How to add additional lbmethods:
+ * 1. Create func which determines "best" candidate worker
+ * (eg: find_best_bytraffic, above)
+ * 2. Register it as a provider.
+ */
+static const proxy_balancer_method byrequests =
+{
+ "byrequests",
+ &find_best_byrequests,
+ NULL
+};
+
+static const proxy_balancer_method bytraffic =
+{
+ "bytraffic",
+ &find_best_bytraffic,
+ NULL
+};
+
+static void ap_proxy_balancer_register_hook(apr_pool_t *p)
+{
+ /* Only the mpm_winnt has child init hook handler.
+ * make sure that we are called after the mpm
+ * initializes and after the mod_proxy
+ */
+ static const char *const aszPred[] = { "mpm_winnt.c", "mod_proxy.c", NULL};
+ /* manager handler */
+ ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_child_init(child_init, aszPred, NULL, APR_HOOK_MIDDLE);
+ proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(proxy_balancer_canon, NULL, NULL, APR_HOOK_FIRST);
+ ap_register_provider(p, PROXY_LBMETHOD, "bytraffic", "0", &bytraffic);
+ ap_register_provider(p, PROXY_LBMETHOD, "byrequests", "0", &byrequests);
+}
+
+module AP_MODULE_DECLARE_DATA proxy_balancer_module = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_proxy_balancer_register_hook /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_balancer.dsp b/modules/proxy/mod_proxy_balancer.dsp
new file mode 100644
index 00000000..eafa5e3b
--- /dev/null
+++ b/modules/proxy/mod_proxy_balancer.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_balancer" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_balancer - Win32 Release
+!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 "mod_proxy_balancer.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 "mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_balancer - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_balancer - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_balancer - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_balancer_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_balancer.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_proxy_balancer.so" /d "LONG_NAME=proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:"Release/mod_proxy_balancer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_proxy_balancer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_proxy_balancer - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# 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 "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_balancer_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_balancer.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_proxy_balancer.so" /d "LONG_NAME=proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_balancer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_balancer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_balancer - Win32 Release"
+# Name "mod_proxy_balancer - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_balancer.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_connect.c b/modules/proxy/mod_proxy_connect.c
new file mode 100644
index 00000000..d0519dde
--- /dev/null
+++ b/modules/proxy/mod_proxy_connect.c
@@ -0,0 +1,401 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* CONNECT method for Apache proxy */
+
+#define CORE_PRIVATE
+
+#include "mod_proxy.h"
+#include "apr_poll.h"
+
+module AP_MODULE_DECLARE_DATA proxy_connect_module;
+
+/*
+ * This handles Netscape CONNECT method secure proxy requests.
+ * A connection is opened to the specified host and data is
+ * passed through between the WWW site and the browser.
+ *
+ * This code is based on the INTERNET-DRAFT document
+ * "Tunneling SSL Through a WWW Proxy" currently at
+ * http://www.mcom.com/newsref/std/tunneling_ssl.html.
+ *
+ * If proxyhost and proxyport are set, we send a CONNECT to
+ * the specified proxy..
+ *
+ * FIXME: this doesn't log the number of bytes sent, but
+ * that may be okay, since the data is supposed to
+ * be transparent. In fact, this doesn't log at all
+ * yet. 8^)
+ * FIXME: doesn't check any headers initally sent from the
+ * client.
+ * FIXME: should allow authentication, but hopefully the
+ * generic proxy authentication is good enough.
+ * FIXME: no check for r->assbackwards, whatever that is.
+ */
+
+static int allowed_port(proxy_server_conf *conf, int port)
+{
+ int i;
+ int *list = (int *) conf->allowed_connect_ports->elts;
+
+ for(i = 0; i < conf->allowed_connect_ports->nelts; i++) {
+ if(port == list[i])
+ return 1;
+ }
+ return 0;
+}
+
+/* canonicalise CONNECT URLs. */
+static int proxy_connect_canon(request_rec *r, char *url)
+{
+
+ if (r->method_number != M_CONNECT) {
+ return DECLINED;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: canonicalising URL %s", url);
+
+ return OK;
+}
+
+/* CONNECT handler */
+static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ apr_pool_t *p = r->pool;
+ apr_socket_t *sock;
+ apr_status_t err, rv;
+ apr_size_t i, o, nbytes;
+ char buffer[HUGE_STRING_LEN];
+ apr_socket_t *client_socket = ap_get_module_config(r->connection->conn_config, &core_module);
+ int failed;
+ apr_pollset_t *pollset;
+ apr_pollfd_t pollfd;
+ const apr_pollfd_t *signalled;
+ apr_int32_t pollcnt, pi;
+ apr_int16_t pollevent;
+ apr_sockaddr_t *uri_addr, *connect_addr;
+
+ apr_uri_t uri;
+ const char *connectname;
+ int connectport = 0;
+
+ /* is this for us? */
+ if (r->method_number != M_CONNECT) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: declining URL %s", url);
+ return DECLINED;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: serving URL %s", url);
+
+
+ /*
+ * Step One: Determine Who To Connect To
+ *
+ * Break up the URL to determine the host to connect to
+ */
+
+ /* we break the URL into host, port, uri */
+ if (APR_SUCCESS != apr_uri_parse_hostinfo(p, url, &uri)) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ apr_pstrcat(p, "URI cannot be parsed: ", url, NULL));
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: connecting %s to %s:%d", url, uri.hostname, uri.port);
+
+ /* do a DNS lookup for the destination host */
+ err = apr_sockaddr_info_get(&uri_addr, uri.hostname, APR_UNSPEC, uri.port, 0, p);
+
+ /* are we connecting directly, or via a proxy? */
+ if (proxyname) {
+ connectname = proxyname;
+ connectport = proxyport;
+ err = apr_sockaddr_info_get(&connect_addr, proxyname, APR_UNSPEC, proxyport, 0, p);
+ }
+ else {
+ connectname = uri.hostname;
+ connectport = uri.port;
+ connect_addr = uri_addr;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: connecting to remote proxy %s on port %d", connectname, connectport);
+
+ /* check if ProxyBlock directive on this host */
+ if (OK != ap_proxy_checkproxyblock(r, conf, uri_addr)) {
+ return ap_proxyerror(r, HTTP_FORBIDDEN,
+ "Connect to remote machine blocked");
+ }
+
+ /* Check if it is an allowed port */
+ if (conf->allowed_connect_ports->nelts == 0) {
+ /* Default setting if not overridden by AllowCONNECT */
+ switch (uri.port) {
+ case APR_URI_HTTPS_DEFAULT_PORT:
+ case APR_URI_SNEWS_DEFAULT_PORT:
+ break;
+ default:
+ /* XXX can we call ap_proxyerror() here to get a nice log message? */
+ return HTTP_FORBIDDEN;
+ }
+ } else if(!allowed_port(conf, uri.port)) {
+ /* XXX can we call ap_proxyerror() here to get a nice log message? */
+ return HTTP_FORBIDDEN;
+ }
+
+ /*
+ * Step Two: Make the Connection
+ *
+ * We have determined who to connect to. Now make the connection.
+ */
+
+ /* get all the possible IP addresses for the destname and loop through them
+ * until we get a successful connection
+ */
+ if (APR_SUCCESS != err) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY, apr_pstrcat(p,
+ "DNS lookup failure for: ",
+ connectname, NULL));
+ }
+
+ /*
+ * At this point we have a list of one or more IP addresses of
+ * the machine to connect to. If configured, reorder this
+ * list so that the "best candidate" is first try. "best
+ * candidate" could mean the least loaded server, the fastest
+ * responding server, whatever.
+ *
+ * For now we do nothing, ie we get DNS round robin.
+ * XXX FIXME
+ */
+ failed = ap_proxy_connect_to_backend(&sock, "CONNECT", connect_addr,
+ connectname, conf, r->server,
+ r->pool);
+
+ /* handle a permanent error from the above loop */
+ if (failed) {
+ if (proxyname) {
+ return DECLINED;
+ }
+ else {
+ return HTTP_BAD_GATEWAY;
+ }
+ }
+
+ /*
+ * Step Three: Send the Request
+ *
+ * Send the HTTP/1.1 CONNECT request to the remote server
+ */
+
+ /* we are acting as a tunnel - the output filter stack should
+ * be completely empty, because when we are done here we are done completely.
+ * We add the NULL filter to the stack to do this...
+ */
+ r->output_filters = NULL;
+ r->connection->output_filters = NULL;
+
+
+ /* If we are connecting through a remote proxy, we need to pass
+ * the CONNECT request on to it.
+ */
+ if (proxyport) {
+ /* FIXME: Error checking ignored.
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: sending the CONNECT request to the remote proxy");
+ nbytes = apr_snprintf(buffer, sizeof(buffer),
+ "CONNECT %s HTTP/1.0" CRLF, r->uri);
+ apr_socket_send(sock, buffer, &nbytes);
+ nbytes = apr_snprintf(buffer, sizeof(buffer),
+ "Proxy-agent: %s" CRLF CRLF, ap_get_server_version());
+ apr_socket_send(sock, buffer, &nbytes);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: Returning 200 OK Status");
+ nbytes = apr_snprintf(buffer, sizeof(buffer),
+ "HTTP/1.0 200 Connection Established" CRLF);
+ ap_xlate_proto_to_ascii(buffer, nbytes);
+ apr_socket_send(client_socket, buffer, &nbytes);
+ nbytes = apr_snprintf(buffer, sizeof(buffer),
+ "Proxy-agent: %s" CRLF CRLF, ap_get_server_version());
+ ap_xlate_proto_to_ascii(buffer, nbytes);
+ apr_socket_send(client_socket, buffer, &nbytes);
+#if 0
+ /* This is safer code, but it doesn't work yet. I'm leaving it
+ * here so that I can fix it later.
+ */
+ r->status = HTTP_OK;
+ r->header_only = 1;
+ apr_table_set(r->headers_out, "Proxy-agent: %s", ap_get_server_version());
+ ap_rflush(r);
+#endif
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: setting up poll()");
+
+ /*
+ * Step Four: Handle Data Transfer
+ *
+ * Handle two way transfer of data over the socket (this is a tunnel).
+ */
+
+/* r->sent_bodyct = 1;*/
+
+ if ((rv = apr_pollset_create(&pollset, 2, r->pool, 0)) != APR_SUCCESS)
+ {
+ apr_socket_close(sock);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: CONNECT: error apr_pollset_create()");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Add client side to the poll */
+ pollfd.p = r->pool;
+ pollfd.desc_type = APR_POLL_SOCKET;
+ pollfd.reqevents = APR_POLLIN;
+ pollfd.desc.s = client_socket;
+ pollfd.client_data = NULL;
+ apr_pollset_add(pollset, &pollfd);
+
+ /* Add the server side to the poll */
+ pollfd.desc.s = sock;
+ apr_pollset_add(pollset, &pollfd);
+
+ while (1) { /* Infinite loop until error (one side closes the connection) */
+ if ((rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled)) != APR_SUCCESS) {
+ apr_socket_close(sock);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "proxy: CONNECT: error apr_poll()");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+#ifdef DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: woke from select(), i=%d", pollcnt);
+#endif
+
+ for (pi = 0; pi < pollcnt; pi++) {
+ const apr_pollfd_t *cur = &signalled[pi];
+
+ if (cur->desc.s == sock) {
+ pollevent = cur->rtnevents;
+ if (pollevent & APR_POLLIN) {
+#ifdef DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: sock was set");
+#endif
+ nbytes = sizeof(buffer);
+ rv = apr_socket_recv(sock, buffer, &nbytes);
+ if (rv == APR_SUCCESS) {
+ o = 0;
+ i = nbytes;
+ while(i > 0)
+ {
+ nbytes = i;
+ /* This is just plain wrong. No module should ever write directly
+ * to the client. For now, this works, but this is high on my list of
+ * things to fix. The correct line is:
+ * if ((nbytes = ap_rwrite(buffer + o, nbytes, r)) < 0)
+ * rbb
+ */
+ rv = apr_socket_send(client_socket, buffer + o, &nbytes);
+ if (rv != APR_SUCCESS)
+ break;
+ o += nbytes;
+ i -= nbytes;
+ }
+ }
+ else
+ break;
+ }
+ else if ((pollevent & APR_POLLERR) || (pollevent & APR_POLLHUP))
+ break;
+ }
+ else if (cur->desc.s == client_socket) {
+ pollevent = cur->rtnevents;
+ if (pollevent & APR_POLLIN) {
+#ifdef DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: client was set");
+#endif
+ nbytes = sizeof(buffer);
+ rv = apr_socket_recv(client_socket, buffer, &nbytes);
+ if (rv == APR_SUCCESS) {
+ o = 0;
+ i = nbytes;
+#ifdef DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: read %d from client", i);
+#endif
+ while(i > 0)
+ {
+ nbytes = i;
+ rv = apr_socket_send(sock, buffer + o, &nbytes);
+ if (rv != APR_SUCCESS)
+ break;
+ o += nbytes;
+ i -= nbytes;
+ }
+ }
+ else
+ break;
+ }
+ else if ((pollevent & APR_POLLERR) || (pollevent & APR_POLLHUP)) {
+ rv = APR_EOF;
+ break;
+ }
+ }
+ else
+ break;
+ }
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: CONNECT: finished with poll() - cleaning up");
+
+ /*
+ * Step Five: Clean Up
+ *
+ * Close the socket and clean up
+ */
+
+ apr_socket_close(sock);
+
+ return OK;
+}
+
+static void ap_proxy_connect_register_hook(apr_pool_t *p)
+{
+ proxy_hook_scheme_handler(proxy_connect_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ proxy_hook_canon_handler(proxy_connect_canon, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+module AP_MODULE_DECLARE_DATA proxy_connect_module = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_proxy_connect_register_hook /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_connect.dsp b/modules/proxy/mod_proxy_connect.dsp
new file mode 100644
index 00000000..49da7d59
--- /dev/null
+++ b/modules/proxy/mod_proxy_connect.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_connect" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_connect - Win32 Release
+!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 "mod_proxy_connect.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 "mod_proxy_connect.mak" CFG="mod_proxy_connect - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_connect - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_connect - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_connect - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_connect_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_connect.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_proxy_connect.so" /d "LONG_NAME=proxy_connect_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:"Release/mod_proxy_connect.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_proxy_connect.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_proxy_connect - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# 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 "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_connect_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_connect.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_proxy_connect.so" /d "LONG_NAME=proxy_connect_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_connect.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_connect.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_connect - Win32 Release"
+# Name "mod_proxy_connect - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_connect.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_ftp.c b/modules/proxy/mod_proxy_ftp.c
new file mode 100644
index 00000000..c56fca4e
--- /dev/null
+++ b/modules/proxy/mod_proxy_ftp.c
@@ -0,0 +1,1891 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* FTP routines for Apache proxy */
+
+#include "mod_proxy.h"
+#if APR_HAVE_TIME_H
+#include <time.h>
+#endif
+#include "apr_version.h"
+
+#if (APR_MAJOR_VERSION < 1)
+#undef apr_socket_create
+#define apr_socket_create apr_socket_create_ex
+#endif
+
+#define AUTODETECT_PWD
+/* Automatic timestamping (Last-Modified header) based on MDTM is used if:
+ * 1) the FTP server supports the MDTM command and
+ * 2) HAVE_TIMEGM (preferred) or HAVE_GMTOFF is available at compile time
+ */
+#define USE_MDTM
+
+
+module AP_MODULE_DECLARE_DATA proxy_ftp_module;
+
+/*
+ * Decodes a '%' escaped string, and returns the number of characters
+ */
+static int decodeenc(char *x)
+{
+ int i, j, ch;
+
+ if (x[0] == '\0')
+ return 0; /* special case for no characters */
+ for (i = 0, j = 0; x[i] != '\0'; i++, j++) {
+ /* decode it if not already done */
+ ch = x[i];
+ if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
+ ch = ap_proxy_hex2c(&x[i + 1]);
+ i += 2;
+ }
+ x[j] = ch;
+ }
+ x[j] = '\0';
+ return j;
+}
+
+/*
+ * Escape the globbing characters in a path used as argument to
+ * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
+ * ftpd assumes '\\' as a quoting character to escape special characters.
+ * Returns: escaped string
+ */
+#define FTP_GLOBBING_CHARS "*?[{~"
+static char *ftp_escape_globbingchars(apr_pool_t *p, const char *path)
+{
+ char *ret = apr_palloc(p, 2*strlen(path)+sizeof(""));
+ char *d;
+ for (d = ret; *path; ++path) {
+ if (strchr(FTP_GLOBBING_CHARS, *path) != NULL)
+ *d++ = '\\';
+ *d++ = *path;
+ }
+ *d = '\0';
+ return ret;
+}
+
+/*
+ * Check for globbing characters in a path used as argument to
+ * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
+ * ftpd assumes '\\' as a quoting character to escape special characters.
+ * Returns: 0 (no globbing chars, or all globbing chars escaped), 1 (globbing chars)
+ */
+static int ftp_check_globbingchars(const char *path)
+{
+ for ( ; *path; ++path) {
+ if (*path == '\\')
+ ++path;
+ if (*path != '\0' && strchr(FTP_GLOBBING_CHARS, *path) != NULL)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*
+ * checks an encoded ftp string for bad characters, namely, CR, LF or
+ * non-ascii character
+ */
+static int ftp_check_string(const char *x)
+{
+ int i, ch = 0;
+#if APR_CHARSET_EBCDIC
+ char buf[1];
+#endif
+
+ for (i = 0; x[i] != '\0'; i++) {
+ ch = x[i];
+ if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
+ ch = ap_proxy_hex2c(&x[i + 1]);
+ i += 2;
+ }
+#if !APR_CHARSET_EBCDIC
+ if (ch == '\015' || ch == '\012' || (ch & 0x80))
+#else /* APR_CHARSET_EBCDIC */
+ if (ch == '\r' || ch == '\n')
+ return 0;
+ buf[0] = ch;
+ ap_xlate_proto_to_ascii(buf, 1);
+ if (buf[0] & 0x80)
+#endif /* APR_CHARSET_EBCDIC */
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Canonicalise ftp URLs.
+ */
+static int proxy_ftp_canon(request_rec *r, char *url)
+{
+ char *user, *password, *host, *path, *parms, *strp, sport[7];
+ apr_pool_t *p = r->pool;
+ const char *err;
+ apr_port_t port, def_port;
+
+ /* */
+ if (strncasecmp(url, "ftp:", 4) == 0) {
+ url += 4;
+ }
+ else {
+ return DECLINED;
+ }
+ def_port = apr_uri_port_of_scheme("ftp");
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: canonicalising URL %s", url);
+
+ port = def_port;
+ err = ap_proxy_canon_netloc(p, &url, &user, &password, &host, &port);
+ if (err)
+ return HTTP_BAD_REQUEST;
+ if (user != NULL && !ftp_check_string(user))
+ return HTTP_BAD_REQUEST;
+ if (password != NULL && !ftp_check_string(password))
+ return HTTP_BAD_REQUEST;
+
+ /* now parse path/parameters args, according to rfc1738 */
+ /*
+ * N.B. if this isn't a true proxy request, then the URL path (but not
+ * query args) has already been decoded. This gives rise to the problem
+ * of a ; being decoded into the path.
+ */
+ strp = strchr(url, ';');
+ if (strp != NULL) {
+ *(strp++) = '\0';
+ parms = ap_proxy_canonenc(p, strp, strlen(strp), enc_parm, 0,
+ r->proxyreq);
+ if (parms == NULL)
+ return HTTP_BAD_REQUEST;
+ }
+ else
+ parms = "";
+
+ path = ap_proxy_canonenc(p, url, strlen(url), enc_path, 0, r->proxyreq);
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+ if (!ftp_check_string(path))
+ return HTTP_BAD_REQUEST;
+
+ if (r->proxyreq && r->args != NULL) {
+ if (strp != NULL) {
+ strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_parm, 1, r->proxyreq);
+ if (strp == NULL)
+ return HTTP_BAD_REQUEST;
+ parms = apr_pstrcat(p, parms, "?", strp, NULL);
+ }
+ else {
+ strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_fpath, 1, r->proxyreq);
+ if (strp == NULL)
+ return HTTP_BAD_REQUEST;
+ path = apr_pstrcat(p, path, "?", strp, NULL);
+ }
+ r->args = NULL;
+ }
+
+/* now, rebuild URL */
+
+ if (port != def_port)
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+ else
+ sport[0] = '\0';
+
+ if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
+ host = apr_pstrcat(p, "[", host, "]", NULL);
+ }
+ r->filename = apr_pstrcat(p, "proxy:ftp://", (user != NULL) ? user : "",
+ (password != NULL) ? ":" : "",
+ (password != NULL) ? password : "",
+ (user != NULL) ? "@" : "", host, sport, "/", path,
+ (parms[0] != '\0') ? ";" : "", parms, NULL);
+
+ return OK;
+}
+
+/* we chop lines longer than 80 characters */
+#define MAX_LINE_LEN 80
+
+/*
+ * Reads response lines, returns both the ftp status code and
+ * remembers the response message in the supplied buffer
+ */
+static int ftp_getrc_msg(conn_rec *ftp_ctrl, apr_bucket_brigade *bb, char *msgbuf, int msglen)
+{
+ int status;
+ char response[MAX_LINE_LEN];
+ char buff[5];
+ char *mb = msgbuf, *me = &msgbuf[msglen];
+ apr_status_t rv;
+ int eos;
+
+ if (APR_SUCCESS != (rv = ap_proxy_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
+ return -1;
+ }
+/*
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "proxy: <FTP: %s", response);
+*/
+ if (!apr_isdigit(response[0]) || !apr_isdigit(response[1]) ||
+ !apr_isdigit(response[2]) || (response[3] != ' ' && response[3] != '-'))
+ status = 0;
+ else
+ status = 100 * response[0] + 10 * response[1] + response[2] - 111 * '0';
+
+ mb = apr_cpystrn(mb, response + 4, me - mb);
+
+ if (response[3] == '-') {
+ memcpy(buff, response, 3);
+ buff[3] = ' ';
+ do {
+ if (APR_SUCCESS != (rv = ap_proxy_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
+ return -1;
+ }
+ mb = apr_cpystrn(mb, response + (' ' == response[0] ? 1 : 4), me - mb);
+ } while (memcmp(response, buff, 4) != 0);
+ }
+
+ return status;
+}
+
+/* this is a filter that turns a raw ASCII directory listing into pretty HTML */
+
+/* ideally, mod_proxy should simply send the raw directory list up the filter
+ * stack to mod_autoindex, which in theory should turn the raw ascii into
+ * pretty html along with all the bells and whistles it provides...
+ *
+ * all in good time...! :)
+ */
+
+typedef struct {
+ apr_bucket_brigade *in;
+ char buffer[MAX_STRING_LEN];
+ enum {
+ HEADER, BODY, FOOTER
+ } state;
+} proxy_dir_ctx_t;
+
+/* fallback regex for ls -s1; ($0..$2) == 3 */
+#define LS_REG_PATTERN "^ *([0-9]+) +([^ ]+)$"
+#define LS_REG_MATCH 3
+
+static apr_status_t proxy_send_dir_filter(ap_filter_t *f,
+ apr_bucket_brigade *in)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ apr_pool_t *p = r->pool;
+ apr_bucket_brigade *out = apr_brigade_create(p, c->bucket_alloc);
+ apr_status_t rv;
+
+ register int n;
+ char *dir, *path, *reldir, *site, *str, *type;
+
+ const char *pwd = apr_table_get(r->notes, "Directory-PWD");
+ const char *readme = apr_table_get(r->notes, "Directory-README");
+
+ proxy_dir_ctx_t *ctx = f->ctx;
+
+ if (!ctx) {
+ f->ctx = ctx = apr_pcalloc(p, sizeof(*ctx));
+ ctx->in = apr_brigade_create(p, c->bucket_alloc);
+ ctx->buffer[0] = 0;
+ ctx->state = HEADER;
+ }
+
+ /* combine the stored and the new */
+ APR_BRIGADE_CONCAT(ctx->in, in);
+
+ if (HEADER == ctx->state) {
+
+ /* basedir is either "", or "/%2f" for the "squid %2f hack" */
+ const char *basedir = ""; /* By default, path is relative to the $HOME dir */
+ char *wildcard = NULL;
+
+ /* Save "scheme://site" prefix without password */
+ site = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO);
+ /* ... and path without query args */
+ path = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITSITEPART | APR_URI_UNP_OMITQUERY);
+
+ /* If path began with /%2f, change the basedir */
+ if (strncasecmp(path, "/%2f", 4) == 0) {
+ basedir = "/%2f";
+ }
+
+ /* Strip off a type qualifier. It is ignored for dir listings */
+ if ((type = strstr(path, ";type=")) != NULL)
+ *type++ = '\0';
+
+ (void)decodeenc(path);
+
+ while (path[1] == '/') /* collapse multiple leading slashes to one */
+ ++path;
+
+ reldir = strrchr(path, '/');
+ if (reldir != NULL && ftp_check_globbingchars(reldir)) {
+ wildcard = &reldir[1];
+ reldir[0] = '\0'; /* strip off the wildcard suffix */
+ }
+
+ /* Copy path, strip (all except the last) trailing slashes */
+ /* (the trailing slash is needed for the dir component loop below) */
+ path = dir = apr_pstrcat(p, path, "/", NULL);
+ for (n = strlen(path); n > 1 && path[n - 1] == '/' && path[n - 2] == '/'; --n)
+ path[n - 1] = '\0';
+
+ /* Add a link to the root directory (if %2f hack was used) */
+ str = (basedir[0] != '\0') ? "<a href=\"/%2f/\">%2f</a>/" : "";
+
+ /* print "ftp://host/" */
+ str = apr_psprintf(p, DOCTYPE_HTML_3_2
+ "<html>\n <head>\n <title>%s%s%s</title>\n"
+ " </head>\n"
+ " <body>\n <h2>Directory of "
+ "<a href=\"/\">%s</a>/%s",
+ site, basedir, ap_escape_html(p, path),
+ site, str);
+
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
+ p, c->bucket_alloc));
+
+ for (dir = path+1; (dir = strchr(dir, '/')) != NULL; )
+ {
+ *dir = '\0';
+ if ((reldir = strrchr(path+1, '/'))==NULL) {
+ reldir = path+1;
+ }
+ else
+ ++reldir;
+ /* print "path/" component */
+ str = apr_psprintf(p, "<a href=\"%s%s/\">%s</a>/", basedir,
+ ap_escape_uri(p, path),
+ ap_escape_html(p, reldir));
+ *dir = '/';
+ while (*dir == '/')
+ ++dir;
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
+ strlen(str), p,
+ c->bucket_alloc));
+ }
+ if (wildcard != NULL) {
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(wildcard,
+ strlen(wildcard), p,
+ c->bucket_alloc));
+ }
+
+ /* If the caller has determined the current directory, and it differs */
+ /* from what the client requested, then show the real name */
+ if (pwd == NULL || strncmp(pwd, path, strlen(pwd)) == 0) {
+ str = apr_psprintf(p, "</h2>\n\n <hr />\n\n<pre>");
+ }
+ else {
+ str = apr_psprintf(p, "</h2>\n\n(%s)\n\n <hr />\n\n<pre>",
+ ap_escape_html(p, pwd));
+ }
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
+ p, c->bucket_alloc));
+
+ /* print README */
+ if (readme) {
+ str = apr_psprintf(p, "%s\n</pre>\n\n<hr />\n\n<pre>\n",
+ ap_escape_html(p, readme));
+
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
+ strlen(str), p,
+ c->bucket_alloc));
+ }
+
+ /* make sure page intro gets sent out */
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
+ if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
+ return rv;
+ }
+ apr_brigade_cleanup(out);
+
+ ctx->state = BODY;
+ }
+
+ /* loop through each line of directory */
+ while (BODY == ctx->state) {
+ char *filename;
+ int found = 0;
+ int eos = 0;
+
+ ap_regex_t *re = NULL;
+ ap_regmatch_t re_result[LS_REG_MATCH];
+
+ /* Compile the output format of "ls -s1" as a fallback for non-unix ftp listings */
+ re = ap_pregcomp(p, LS_REG_PATTERN, AP_REG_EXTENDED);
+ ap_assert(re != NULL);
+
+ /* get a complete line */
+ /* if the buffer overruns - throw data away */
+ while (!found && !APR_BRIGADE_EMPTY(ctx->in)) {
+ char *pos, *response;
+ apr_size_t len, max;
+ apr_bucket *e;
+
+ e = APR_BRIGADE_FIRST(ctx->in);
+ if (APR_BUCKET_IS_EOS(e)) {
+ eos = 1;
+ break;
+ }
+ if (APR_SUCCESS != (rv = apr_bucket_read(e, (const char **)&response, &len, APR_BLOCK_READ))) {
+ return rv;
+ }
+ pos = memchr(response, APR_ASCII_LF, len);
+ if (pos != NULL) {
+ if ((response + len) != (pos + 1)) {
+ len = pos - response + 1;
+ apr_bucket_split(e, pos - response + 1);
+ }
+ found = 1;
+ }
+ max = sizeof(ctx->buffer) - strlen(ctx->buffer) - 1;
+ if (len > max) {
+ len = max;
+ }
+
+ /* len+1 to leave space for the trailing nil char */
+ apr_cpystrn(ctx->buffer+strlen(ctx->buffer), response, len+1);
+
+ APR_BUCKET_REMOVE(e);
+ apr_bucket_destroy(e);
+ }
+
+ /* EOS? jump to footer */
+ if (eos) {
+ ctx->state = FOOTER;
+ break;
+ }
+
+ /* not complete? leave and try get some more */
+ if (!found) {
+ return APR_SUCCESS;
+ }
+
+ {
+ apr_size_t n = strlen(ctx->buffer);
+ if (ctx->buffer[n-1] == CRLF[1]) /* strip trailing '\n' */
+ ctx->buffer[--n] = '\0';
+ if (ctx->buffer[n-1] == CRLF[0]) /* strip trailing '\r' if present */
+ ctx->buffer[--n] = '\0';
+ }
+
+ /* a symlink? */
+ if (ctx->buffer[0] == 'l' && (filename = strstr(ctx->buffer, " -> ")) != NULL) {
+ char *link_ptr = filename;
+
+ do {
+ filename--;
+ } while (filename[0] != ' ' && filename > ctx->buffer);
+ if (filename > ctx->buffer)
+ *(filename++) = '\0';
+ *(link_ptr++) = '\0';
+ str = apr_psprintf(p, "%s <a href=\"%s\">%s %s</a>\n",
+ ap_escape_html(p, ctx->buffer),
+ ap_escape_uri(p, filename),
+ ap_escape_html(p, filename),
+ ap_escape_html(p, link_ptr));
+ }
+
+ /* a directory/file? */
+ else if (ctx->buffer[0] == 'd' || ctx->buffer[0] == '-' || ctx->buffer[0] == 'l' || apr_isdigit(ctx->buffer[0])) {
+ int searchidx = 0;
+ char *searchptr = NULL;
+ int firstfile = 1;
+ if (apr_isdigit(ctx->buffer[0])) { /* handle DOS dir */
+ searchptr = strchr(ctx->buffer, '<');
+ if (searchptr != NULL)
+ *searchptr = '[';
+ searchptr = strchr(ctx->buffer, '>');
+ if (searchptr != NULL)
+ *searchptr = ']';
+ }
+
+ filename = strrchr(ctx->buffer, ' ');
+ *(filename++) = '\0';
+
+ /* handle filenames with spaces in 'em */
+ if (!strcmp(filename, ".") || !strcmp(filename, "..") || firstfile) {
+ firstfile = 0;
+ searchidx = filename - ctx->buffer;
+ }
+ else if (searchidx != 0 && ctx->buffer[searchidx] != 0) {
+ *(--filename) = ' ';
+ ctx->buffer[searchidx - 1] = '\0';
+ filename = &ctx->buffer[searchidx];
+ }
+
+ /* Append a slash to the HREF link for directories */
+ if (!strcmp(filename, ".") || !strcmp(filename, "..") || ctx->buffer[0] == 'd') {
+ str = apr_psprintf(p, "%s <a href=\"%s/\">%s</a>\n",
+ ap_escape_html(p, ctx->buffer),
+ ap_escape_uri(p, filename),
+ ap_escape_html(p, filename));
+ }
+ else {
+ str = apr_psprintf(p, "%s <a href=\"%s\">%s</a>\n",
+ ap_escape_html(p, ctx->buffer),
+ ap_escape_uri(p, filename),
+ ap_escape_html(p, filename));
+ }
+ }
+ /* Try a fallback for listings in the format of "ls -s1" */
+ else if (0 == ap_regexec(re, ctx->buffer, LS_REG_MATCH, re_result, 0)) {
+
+ filename = apr_pstrndup(p, &ctx->buffer[re_result[2].rm_so], re_result[2].rm_eo - re_result[2].rm_so);
+
+ str = apr_pstrcat(p, ap_escape_html(p, apr_pstrndup(p, ctx->buffer, re_result[2].rm_so)),
+ "<a href=\"", ap_escape_uri(p, filename), "\">",
+ ap_escape_html(p, filename), "</a>\n", NULL);
+ }
+ else {
+ strcat(ctx->buffer, "\n"); /* re-append the newline */
+ str = ap_escape_html(p, ctx->buffer);
+ }
+
+ /* erase buffer for next time around */
+ ctx->buffer[0] = 0;
+
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
+ c->bucket_alloc));
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
+ if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
+ return rv;
+ }
+ apr_brigade_cleanup(out);
+
+ }
+
+ if (FOOTER == ctx->state) {
+ str = apr_psprintf(p, "</pre>\n\n <hr />\n\n %s\n\n </body>\n</html>\n", ap_psignature("", r));
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
+ c->bucket_alloc));
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_eos_create(c->bucket_alloc));
+ if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
+ return rv;
+ }
+ apr_brigade_destroy(out);
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Generic "send FTP command to server" routine, using the control socket.
+ * Returns the FTP returncode (3 digit code)
+ * Allows for tracing the FTP protocol (in LogLevel debug)
+ */
+static int
+proxy_ftp_command(const char *cmd, request_rec *r, conn_rec *ftp_ctrl,
+ apr_bucket_brigade *bb, char **pmessage)
+{
+ char *crlf;
+ int rc;
+ char message[HUGE_STRING_LEN];
+
+ /* If cmd == NULL, we retrieve the next ftp response line */
+ if (cmd != NULL) {
+ conn_rec *c = r->connection;
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(cmd, strlen(cmd), r->pool, c->bucket_alloc));
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(c->bucket_alloc));
+ ap_pass_brigade(ftp_ctrl->output_filters, bb);
+
+ /* strip off the CRLF for logging */
+ apr_cpystrn(message, cmd, sizeof(message));
+ if ((crlf = strchr(message, '\r')) != NULL ||
+ (crlf = strchr(message, '\n')) != NULL)
+ *crlf = '\0';
+ if (strncmp(message,"PASS ", 5) == 0)
+ strcpy(&message[5], "****");
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy:>FTP: %s", message);
+ }
+
+ rc = ftp_getrc_msg(ftp_ctrl, bb, message, sizeof message);
+ if (rc == -1 || rc == 421)
+ strcpy(message,"<unable to read result>");
+ if ((crlf = strchr(message, '\r')) != NULL ||
+ (crlf = strchr(message, '\n')) != NULL)
+ *crlf = '\0';
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy:<FTP: %3.3u %s", rc, message);
+
+ if (pmessage != NULL)
+ *pmessage = apr_pstrdup(r->pool, message);
+
+ return rc;
+}
+
+/* Set ftp server to TYPE {A,I,E} before transfer of a directory or file */
+static int ftp_set_TYPE(char xfer_type, request_rec *r, conn_rec *ftp_ctrl,
+ apr_bucket_brigade *bb, char **pmessage)
+{
+ char old_type[2] = { 'A', '\0' }; /* After logon, mode is ASCII */
+ int ret = HTTP_OK;
+ int rc;
+
+ /* set desired type */
+ old_type[0] = xfer_type;
+
+ rc = proxy_ftp_command(apr_pstrcat(r->pool, "TYPE ", old_type, CRLF, NULL),
+ r, ftp_ctrl, bb, pmessage);
+/* responses: 200, 421, 500, 501, 504, 530 */
+ /* 200 Command okay. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 504 Command not implemented for that parameter. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ else if (rc != 200 && rc != 504) {
+ ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Unable to set transfer type");
+ }
+/* Allow not implemented */
+ else if (rc == 504)
+ /* ignore it silently */;
+
+ return ret;
+}
+
+
+/* Return the current directory which we have selected on the FTP server, or NULL */
+static char *ftp_get_PWD(request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
+{
+ char *cwd = NULL;
+ char *ftpmessage = NULL;
+
+ /* responses: 257, 500, 501, 502, 421, 550 */
+ /* 257 "<directory-name>" <commentary> */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 550 Requested action not taken. */
+ switch (proxy_ftp_command("PWD" CRLF, r, ftp_ctrl, bb, &ftpmessage)) {
+ case -1:
+ case 421:
+ case 550:
+ ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Failed to read PWD on ftp server");
+ break;
+
+ case 257: {
+ const char *dirp = ftpmessage;
+ cwd = ap_getword_conf(r->pool, &dirp);
+ }
+ }
+ return cwd;
+}
+
+
+/* Common routine for failed authorization (i.e., missing or wrong password)
+ * to an ftp service. This causes most browsers to retry the request
+ * with username and password (which was presumably queried from the user)
+ * supplied in the Authorization: header.
+ * Note that we "invent" a realm name which consists of the
+ * ftp://user@host part of the reqest (sans password -if supplied but invalid-)
+ */
+static int ftp_unauthorized(request_rec *r, int log_it)
+{
+ r->proxyreq = PROXYREQ_NONE;
+ /*
+ * Log failed requests if they supplied a password (log username/password
+ * guessing attempts)
+ */
+ if (log_it)
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "proxy: missing or failed auth to %s",
+ apr_uri_unparse(r->pool,
+ &r->parsed_uri, APR_URI_UNP_OMITPATHINFO));
+
+ apr_table_setn(r->err_headers_out, "WWW-Authenticate",
+ apr_pstrcat(r->pool, "Basic realm=\"",
+ apr_uri_unparse(r->pool, &r->parsed_uri,
+ APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO),
+ "\"", NULL));
+
+ return HTTP_UNAUTHORIZED;
+}
+
+static
+apr_status_t proxy_ftp_cleanup(request_rec *r, proxy_conn_rec *backend)
+{
+
+ backend->close_on_recycle = 1;
+ ap_set_module_config(r->connection->conn_config, &proxy_ftp_module, NULL);
+ ap_proxy_release_connection("FTP", backend, r->server);
+
+ return OK;
+}
+
+static
+int ftp_proxyerror(request_rec *r, proxy_conn_rec *conn, int statuscode, const char *message)
+{
+ proxy_ftp_cleanup(r, conn);
+ return ap_proxyerror(r, statuscode, message);
+}
+/*
+ * Handles direct access of ftp:// URLs
+ * Original (Non-PASV) version from
+ * Troy Morrison <spiffnet@zoom.com>
+ * PASV added by Chuck
+ * Filters by [Graham Leggett <minfrin@sharp.fm>]
+ */
+static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf, char *url,
+ const char *proxyhost, apr_port_t proxyport)
+{
+ apr_pool_t *p = r->pool;
+ conn_rec *c = r->connection;
+ proxy_conn_rec *backend;
+ apr_socket_t *sock, *local_sock, *data_sock = NULL;
+ apr_sockaddr_t *connect_addr = NULL;
+ apr_status_t rv;
+ conn_rec *origin, *data = NULL;
+ apr_status_t err = APR_SUCCESS;
+ apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
+ char *buf, *connectname;
+ apr_port_t connectport;
+ char buffer[MAX_STRING_LEN];
+ char *ftpmessage = NULL;
+ char *path, *strp, *type_suffix, *cwd = NULL;
+ apr_uri_t uri;
+ char *user = NULL;
+/* char *account = NULL; how to supply an account in a URL? */
+ const char *password = NULL;
+ int len, rc;
+ int one = 1;
+ char *size = NULL;
+ char xfer_type = 'A'; /* after ftp login, the default is ASCII */
+ int dirlisting = 0;
+#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
+ apr_time_t mtime = 0L;
+#endif
+
+ /* stuff for PASV mode */
+ int connect = 0, use_port = 0;
+ char dates[APR_RFC822_DATE_LEN];
+ int status;
+ apr_pool_t *address_pool;
+
+ /* is this for us? */
+ if (proxyhost) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: declining URL %s - proxyhost %s specified:", url, proxyhost);
+ return DECLINED; /* proxy connections are via HTTP */
+ }
+ if (strncasecmp(url, "ftp:", 4)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: declining URL %s - not ftp:", url);
+ return DECLINED; /* only interested in FTP */
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: serving URL %s", url);
+
+
+ /*
+ * I: Who Do I Connect To? -----------------------
+ *
+ * Break up the URL to determine the host to connect to
+ */
+
+ /* we only support GET and HEAD */
+ if (r->method_number != M_GET)
+ return HTTP_NOT_IMPLEMENTED;
+
+ /* We break the URL into host, port, path-search */
+ if (r->parsed_uri.hostname == NULL) {
+ if (APR_SUCCESS != apr_uri_parse(p, url, &uri)) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ apr_psprintf(p, "URI cannot be parsed: %s", url));
+ }
+ connectname = uri.hostname;
+ connectport = uri.port;
+ path = apr_pstrdup(p, uri.path);
+ }
+ else {
+ connectname = r->parsed_uri.hostname;
+ connectport = r->parsed_uri.port;
+ path = apr_pstrdup(p, r->parsed_uri.path);
+ }
+ if (connectport == 0) {
+ connectport = apr_uri_port_of_scheme("ftp");
+ }
+ path = (path != NULL && path[0] != '\0') ? &path[1] : "";
+
+ type_suffix = strchr(path, ';');
+ if (type_suffix != NULL)
+ *(type_suffix++) = '\0';
+
+ if (type_suffix != NULL && strncmp(type_suffix, "type=", 5) == 0
+ && apr_isalpha(type_suffix[5])) {
+ /* "type=d" forces a dir listing.
+ * The other types (i|a|e) are directly used for the ftp TYPE command
+ */
+ if ( ! (dirlisting = (apr_tolower(type_suffix[5]) == 'd')))
+ xfer_type = apr_toupper(type_suffix[5]);
+
+ /* Check valid types, rather than ignoring invalid types silently: */
+ if (strchr("AEI", xfer_type) == NULL)
+ return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
+ "ftp proxy supports only types 'a', 'i', or 'e': \"",
+ type_suffix, "\" is invalid.", NULL));
+ }
+ else {
+ /* make binary transfers the default */
+ xfer_type = 'I';
+ }
+
+
+ /*
+ * The "Authorization:" header must be checked first. We allow the user
+ * to "override" the URL-coded user [ & password ] in the Browsers'
+ * User&Password Dialog. NOTE that this is only marginally more secure
+ * than having the password travel in plain as part of the URL, because
+ * Basic Auth simply uuencodes the plain text password. But chances are
+ * still smaller that the URL is logged regularly.
+ */
+ if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
+ && strcasecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
+ && (password = ap_pbase64decode(r->pool, password))[0] != ':') {
+ /*
+ * Note that this allocation has to be made from r->connection->pool
+ * because it has the lifetime of the connection. The other
+ * allocations are temporary and can be tossed away any time.
+ */
+ user = ap_getword_nulls(r->connection->pool, &password, ':');
+ r->ap_auth_type = "Basic";
+ r->user = r->parsed_uri.user = user;
+ }
+ else if ((user = r->parsed_uri.user) != NULL) {
+ user = apr_pstrdup(p, user);
+ decodeenc(user);
+ if ((password = r->parsed_uri.password) != NULL) {
+ char *tmp = apr_pstrdup(p, password);
+ decodeenc(tmp);
+ password = tmp;
+ }
+ }
+ else {
+ user = "anonymous";
+ password = "apache-proxy@";
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: connecting %s to %s:%d", url, connectname, connectport);
+
+ if (worker->is_address_reusable) {
+ if (!worker->cp->addr) {
+ if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, err, r->server,
+ "proxy: FTP: lock");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ connect_addr = worker->cp->addr;
+ address_pool = worker->cp->pool;
+ }
+ else
+ address_pool = r->pool;
+
+ /* do a DNS lookup for the destination host */
+ if (!connect_addr)
+ err = apr_sockaddr_info_get(&(connect_addr),
+ connectname, APR_UNSPEC,
+ connectport, 0,
+ address_pool);
+ if (worker->is_address_reusable && !worker->cp->addr) {
+ worker->cp->addr = connect_addr;
+ PROXY_THREAD_UNLOCK(worker);
+ }
+ /*
+ * get all the possible IP addresses for the destname and loop through
+ * them until we get a successful connection
+ */
+ if (APR_SUCCESS != err) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY, apr_pstrcat(p,
+ "DNS lookup failure for: ",
+ connectname, NULL));
+ }
+
+ /* check if ProxyBlock directive on this host */
+ if (OK != ap_proxy_checkproxyblock(r, conf, connect_addr)) {
+ return ap_proxyerror(r, HTTP_FORBIDDEN,
+ "Connect to remote machine blocked");
+ }
+
+ /* create space for state information */
+ backend = (proxy_conn_rec *) ap_get_module_config(c->conn_config, &proxy_ftp_module);
+ if (!backend) {
+ status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server);
+ if (status != OK) {
+ if (backend) {
+ backend->close_on_recycle = 1;
+ ap_proxy_release_connection("FTP", backend, r->server);
+ }
+ return status;
+ }
+ /* TODO: see if ftp could use determine_connection */
+ backend->addr = connect_addr;
+ ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
+ }
+
+
+ /*
+ * II: Make the Connection -----------------------
+ *
+ * We have determined who to connect to. Now make the connection.
+ */
+
+
+ if (ap_proxy_connect_backend("FTP", backend, worker, r->server)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: an error occurred creating a new connection to %pI (%s)",
+ connect_addr, connectname);
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ if (!backend->connection) {
+ status = ap_proxy_connection_create("FTP", backend, c, r->server);
+ if (status != OK) {
+ proxy_ftp_cleanup(r, backend);
+ return status;
+ }
+ }
+
+ /* Use old naming */
+ origin = backend->connection;
+ sock = backend->sock;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: control connection complete");
+
+
+ /*
+ * III: Send Control Request -------------------------
+ *
+ * Log into the ftp server, send the username & password, change to the
+ * correct directory...
+ */
+
+
+ /* possible results: */
+ /* 120 Service ready in nnn minutes. */
+ /* 220 Service ready for new user. */
+ /* 421 Service not available, closing control connection. */
+ rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
+ }
+ if (rc == 120) {
+ /*
+ * RFC2616 states: 14.37 Retry-After
+ *
+ * The Retry-After response-header field can be used with a 503 (Service
+ * Unavailable) response to indicate how long the service is expected
+ * to be unavailable to the requesting client. [...] The value of
+ * this field can be either an HTTP-date or an integer number of
+ * seconds (in decimal) after the time of the response. Retry-After
+ * = "Retry-After" ":" ( HTTP-date | delta-seconds )
+ */
+ char *secs_str = ftpmessage;
+ time_t secs;
+
+ /* Look for a number, preceded by whitespace */
+ while (*secs_str)
+ if ((secs_str==ftpmessage || apr_isspace(secs_str[-1])) &&
+ apr_isdigit(secs_str[0]))
+ break;
+ if (*secs_str != '\0') {
+ secs = atol(secs_str);
+ apr_table_add(r->headers_out, "Retry-After",
+ apr_psprintf(p, "%lu", (unsigned long)(60 * secs)));
+ }
+ return ftp_proxyerror(r, backend, HTTP_SERVICE_UNAVAILABLE, ftpmessage);
+ }
+ if (rc != 220) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ rc = proxy_ftp_command(apr_pstrcat(p, "USER ", user, CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* possible results; 230, 331, 332, 421, 500, 501, 530 */
+ /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */
+ /* 230 User logged in, proceed. */
+ /* 331 User name okay, need password. */
+ /* 332 Need account for login. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* (This may include errors such as command line too long.) */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
+ }
+ if (rc == 530) {
+ proxy_ftp_cleanup(r, backend);
+ return ftp_unauthorized(r, 1); /* log it: user name guessing
+ * attempt? */
+ }
+ if (rc != 230 && rc != 331) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ if (rc == 331) { /* send password */
+ if (password == NULL) {
+ proxy_ftp_cleanup(r, backend);
+ return ftp_unauthorized(r, 0);
+ }
+
+ rc = proxy_ftp_command(apr_pstrcat(p, "PASS ", password, CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */
+ /* 230 User logged in, proceed. */
+ /* 332 Need account for login. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 503 Bad sequence of commands. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 332) {
+ return ftp_proxyerror(r, backend, HTTP_UNAUTHORIZED,
+ apr_pstrcat(p, "Need account for login: ", ftpmessage, NULL));
+ }
+ /* @@@ questionable -- we might as well return a 403 Forbidden here */
+ if (rc == 530) {
+ proxy_ftp_cleanup(r, backend);
+ return ftp_unauthorized(r, 1); /* log it: passwd guessing
+ * attempt? */
+ }
+ if (rc != 230 && rc != 202) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+ }
+ apr_table_set(r->notes, "Directory-README", ftpmessage);
+
+
+ /* Special handling for leading "%2f": this enforces a "cwd /"
+ * out of the $HOME directory which was the starting point after login
+ */
+ if (strncasecmp(path, "%2f", 3) == 0) {
+ path += 3;
+ while (*path == '/') /* skip leading '/' (after root %2f) */
+ ++path;
+
+ rc = proxy_ftp_command("CWD /" CRLF, r, origin, bb, &ftpmessage);
+ if (rc == -1 || rc == 421)
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+
+ /*
+ * set the directory (walk directory component by component): this is
+ * what we must do if we don't know the OS type of the remote machine
+ */
+ for (;;) {
+ strp = strchr(path, '/');
+ if (strp == NULL)
+ break;
+ *strp = '\0';
+
+ len = decodeenc(path); /* Note! This decodes a %2f -> "/" */
+
+ if (strchr(path, '/')) { /* are there now any '/' characters? */
+ return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
+ "Use of /%2f is only allowed at the base directory");
+ }
+
+ /* NOTE: FTP servers do globbing on the path.
+ * So we need to escape the URI metacharacters.
+ * We use a special glob-escaping routine to escape globbing chars.
+ * We could also have extended gen_test_char.c with a special T_ESCAPE_FTP_PATH
+ */
+ rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
+ ftp_escape_globbingchars(p, path), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ *strp = '/';
+ /* responses: 250, 421, 500, 501, 502, 530, 550 */
+ /* 250 Requested file action okay, completed. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ /* 550 Requested action not taken. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 550) {
+ return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
+ }
+ if (rc != 250) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ path = strp + 1;
+ }
+
+ /*
+ * IV: Make Data Connection? -------------------------
+ *
+ * Try EPSV, if that fails... try PASV, if that fails... try PORT.
+ */
+/* this temporarily switches off EPSV/PASV */
+/*goto bypass;*/
+
+ /* set up data connection - EPSV */
+ {
+ apr_sockaddr_t *data_addr;
+ char *data_ip;
+ apr_port_t data_port;
+
+ /*
+ * The EPSV command replaces PASV where both IPV4 and IPV6 is
+ * supported. Only the port is returned, the IP address is always the
+ * same as that on the control connection. Example: Entering Extended
+ * Passive Mode (|||6446|)
+ */
+ rc = proxy_ftp_command("EPSV" CRLF,
+ r, origin, bb, &ftpmessage);
+ /* possible results: 227, 421, 500, 501, 502, 530 */
+ /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc != 229 && rc != 500 && rc != 501 && rc != 502) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+ else if (rc == 229) {
+ char *pstr;
+ char *tok_cntx;
+
+ pstr = ftpmessage;
+ pstr = apr_strtok(pstr, " ", &tok_cntx); /* separate result code */
+ if (pstr != NULL) {
+ if (*(pstr + strlen(pstr) + 1) == '=') {
+ pstr += strlen(pstr) + 2;
+ }
+ else {
+ pstr = apr_strtok(NULL, "(", &tok_cntx); /* separate address &
+ * port params */
+ if (pstr != NULL)
+ pstr = apr_strtok(NULL, ")", &tok_cntx);
+ }
+ }
+
+ if (pstr) {
+ apr_sockaddr_t *epsv_addr;
+ data_port = atoi(pstr + 3);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: EPSV contacting remote host on port %d",
+ data_port);
+
+ if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: FTP: error creating EPSV socket");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+#if !defined (TPF) && !defined(BEOS)
+ if (conf->recv_buffer_size > 0
+ && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
+ conf->recv_buffer_size))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: FTP: apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
+ }
+#endif
+
+ /* make the connection */
+ apr_socket_addr_get(&data_addr, APR_REMOTE, sock);
+ apr_sockaddr_ip_get(&data_ip, data_addr);
+ apr_sockaddr_info_get(&epsv_addr, data_ip, connect_addr->family, data_port, 0, p);
+ rv = apr_socket_connect(data_sock, epsv_addr);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
+ "proxy: FTP: EPSV attempt to connect to %pI failed - Firewall/NAT?", epsv_addr);
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
+ "EPSV attempt to connect to %pI failed - firewall/NAT?", epsv_addr));
+ }
+ else {
+ connect = 1;
+ }
+ }
+ else {
+ /* and try the regular way */
+ apr_socket_close(data_sock);
+ }
+ }
+ }
+
+ /* set up data connection - PASV */
+ if (!connect) {
+ rc = proxy_ftp_command("PASV" CRLF,
+ r, origin, bb, &ftpmessage);
+ /* possible results: 227, 421, 500, 501, 502, 530 */
+ /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc != 227 && rc != 502) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+ else if (rc == 227) {
+ unsigned int h0, h1, h2, h3, p0, p1;
+ char *pstr;
+ char *tok_cntx;
+
+/* FIXME: Check PASV against RFC1123 */
+
+ pstr = ftpmessage;
+ pstr = apr_strtok(pstr, " ", &tok_cntx); /* separate result code */
+ if (pstr != NULL) {
+ if (*(pstr + strlen(pstr) + 1) == '=') {
+ pstr += strlen(pstr) + 2;
+ }
+ else {
+ pstr = apr_strtok(NULL, "(", &tok_cntx); /* separate address &
+ * port params */
+ if (pstr != NULL)
+ pstr = apr_strtok(NULL, ")", &tok_cntx);
+ }
+ }
+
+/* FIXME: Only supports IPV4 - fix in RFC2428 */
+
+ if (pstr != NULL && (sscanf(pstr,
+ "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) {
+
+ apr_sockaddr_t *pasv_addr;
+ apr_port_t pasvport = (p1 << 8) + p0;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: PASV contacting host %d.%d.%d.%d:%d",
+ h3, h2, h1, h0, pasvport);
+
+ if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: error creating PASV socket");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+#if !defined (TPF) && !defined(BEOS)
+ if (conf->recv_buffer_size > 0
+ && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
+ conf->recv_buffer_size))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: FTP: apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
+ }
+#endif
+
+ /* make the connection */
+ apr_sockaddr_info_get(&pasv_addr, apr_psprintf(p, "%d.%d.%d.%d", h3, h2, h1, h0), connect_addr->family, pasvport, 0, p);
+ rv = apr_socket_connect(data_sock, pasv_addr);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
+ "proxy: FTP: PASV attempt to connect to %pI failed - Firewall/NAT?", pasv_addr);
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
+ "PASV attempt to connect to %pI failed - firewall/NAT?", pasv_addr));
+ }
+ else {
+ connect = 1;
+ }
+ }
+ else {
+ /* and try the regular way */
+ apr_socket_close(data_sock);
+ }
+ }
+ }
+/*bypass:*/
+
+ /* set up data connection - PORT */
+ if (!connect) {
+ apr_sockaddr_t *local_addr;
+ char *local_ip;
+ apr_port_t local_port;
+ unsigned int h0, h1, h2, h3, p0, p1;
+
+ if ((rv = apr_socket_create(&local_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: FTP: error creating local socket");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_socket_addr_get(&local_addr, APR_LOCAL, sock);
+ local_port = local_addr->port;
+ apr_sockaddr_ip_get(&local_ip, local_addr);
+
+ if ((rv = apr_socket_opt_set(local_sock, APR_SO_REUSEADDR, one))
+ != APR_SUCCESS) {
+#ifndef _OSD_POSIX /* BS2000 has this option "always on" */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: FTP: error setting reuseaddr option");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+#endif /* _OSD_POSIX */
+ }
+
+ apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
+
+ if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: FTP: error binding to ftp data socket %pI", local_addr);
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* only need a short queue */
+ if ((rv = apr_socket_listen(local_sock, 2)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: FTP: error listening to ftp data socket %pI", local_addr);
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+/* FIXME: Sent PORT here */
+
+ if (local_ip && (sscanf(local_ip,
+ "%d.%d.%d.%d", &h3, &h2, &h1, &h0) == 4)) {
+ p1 = (local_port >> 8);
+ p0 = (local_port & 0xFF);
+
+ rc = proxy_ftp_command(apr_psprintf(p, "PORT %d,%d,%d,%d,%d,%d" CRLF, h3, h2, h1, h0, p1, p0),
+ r, origin, bb, &ftpmessage);
+ /* possible results: 200, 421, 500, 501, 502, 530 */
+ /* 200 Command okay. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc != 200) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, buffer);
+ }
+
+ /* signal that we must use the EPRT/PORT loop */
+ use_port = 1;
+ }
+ else {
+/* IPV6 FIXME:
+ * The EPRT command replaces PORT where both IPV4 and IPV6 is supported. The first
+ * number (1,2) indicates the protocol type. Examples:
+ * EPRT |1|132.235.1.2|6275|
+ * EPRT |2|1080::8:800:200C:417A|5282|
+ */
+ return ftp_proxyerror(r, backend, HTTP_NOT_IMPLEMENTED,
+ "Connect to IPV6 ftp server using EPRT not supported. Enable EPSV.");
+ }
+ }
+
+
+ /*
+ * V: Set The Headers -------------------
+ *
+ * Get the size of the request, set up the environment for HTTP.
+ */
+
+ /* set request; "path" holds last path component */
+ len = decodeenc(path);
+
+ if (strchr(path, '/')) { /* are there now any '/' characters? */
+ return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
+ "Use of /%2f is only allowed at the base directory");
+ }
+
+ /* If len == 0 then it must be a directory (you can't RETR nothing)
+ * Also, don't allow to RETR by wildcard. Instead, create a dirlisting
+ */
+ if (len == 0 || ftp_check_globbingchars(path)) {
+ dirlisting = 1;
+ }
+ else {
+ /* (from FreeBSD ftpd):
+ * SIZE is not in RFC959, but Postel has blessed it and
+ * it will be in the updated RFC.
+ *
+ * Return size of file in a format suitable for
+ * using with RESTART (we just count bytes).
+ */
+ /* from draft-ietf-ftpext-mlst-14.txt:
+ * This value will
+ * change depending on the current STRUcture, MODE and TYPE of the data
+ * connection, or a data connection which would be created were one
+ * created now. Thus, the result of the SIZE command is dependent on
+ * the currently established STRU, MODE and TYPE parameters.
+ */
+ /* Therefore: switch to binary if the user did not specify ";type=a" */
+ ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
+ rc = proxy_ftp_command(apr_pstrcat(p, "SIZE ",
+ ftp_escape_globbingchars(p, path), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ else if (rc == 213) {/* Size command ok */
+ int j;
+ for (j = 0; apr_isdigit(ftpmessage[j]); j++)
+ ;
+ ftpmessage[j] = '\0';
+ if (ftpmessage[0] != '\0')
+ size = ftpmessage; /* already pstrdup'ed: no copy necessary */
+ }
+ else if (rc == 550) { /* Not a regular file */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: SIZE shows this is a directory");
+ dirlisting = 1;
+ rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
+ ftp_escape_globbingchars(p, path), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* possible results: 250, 421, 500, 501, 502, 530, 550 */
+ /* 250 Requested file action okay, completed. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ /* 550 Requested action not taken. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 550) {
+ return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
+ }
+ if (rc != 250) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+ path = "";
+ len = 0;
+ }
+ }
+
+ cwd = ftp_get_PWD(r, origin, bb);
+ if (cwd != NULL) {
+ apr_table_set(r->notes, "Directory-PWD", cwd);
+ }
+
+ if (dirlisting) {
+ ftp_set_TYPE('A', r, origin, bb, NULL);
+ /* If the current directory contains no slash, we are talking to
+ * a non-unix ftp system. Try LIST instead of "LIST -lag", it
+ * should return a long listing anyway (unlike NLST).
+ * Some exotic FTP servers might choke on the "-lag" switch.
+ */
+ /* Note that we do not escape the path here, to allow for
+ * queries like: ftp://user@host/apache/src/server/http_*.c
+ */
+ if (len != 0)
+ buf = apr_pstrcat(p, "LIST ", path, CRLF, NULL);
+ else if (cwd == NULL || strchr(cwd, '/') != NULL)
+ buf = apr_pstrcat(p, "LIST -lag", CRLF, NULL);
+ else
+ buf = "LIST" CRLF;
+ }
+ else {
+ /* switch to binary if the user did not specify ";type=a" */
+ ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
+#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
+ /* from draft-ietf-ftpext-mlst-14.txt:
+ * The FTP command, MODIFICATION TIME (MDTM), can be used to determine
+ * when a file in the server NVFS was last modified. <..>
+ * The syntax of a time value is:
+ * time-val = 14DIGIT [ "." 1*DIGIT ] <..>
+ * Symbolically, a time-val may be viewed as
+ * YYYYMMDDHHMMSS.sss
+ * The "." and subsequent digits ("sss") are optional. <..>
+ * Time values are always represented in UTC (GMT)
+ */
+ rc = proxy_ftp_command(apr_pstrcat(p, "MDTM ", ftp_escape_globbingchars(p, path), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* then extract the Last-Modified time from it (YYYYMMDDhhmmss or YYYYMMDDhhmmss.xxx GMT). */
+ if (rc == 213) {
+ struct {
+ char YYYY[4+1];
+ char MM[2+1];
+ char DD[2+1];
+ char hh[2+1];
+ char mm[2+1];
+ char ss[2+1];
+ } time_val;
+ if (6 == sscanf(ftpmessage, "%4[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]",
+ time_val.YYYY, time_val.MM, time_val.DD, time_val.hh, time_val.mm, time_val.ss)) {
+ struct tm tms;
+ memset (&tms, '\0', sizeof tms);
+ tms.tm_year = atoi(time_val.YYYY) - 1900;
+ tms.tm_mon = atoi(time_val.MM) - 1;
+ tms.tm_mday = atoi(time_val.DD);
+ tms.tm_hour = atoi(time_val.hh);
+ tms.tm_min = atoi(time_val.mm);
+ tms.tm_sec = atoi(time_val.ss);
+#ifdef HAVE_TIMEGM /* Does system have timegm()? */
+ mtime = timegm(&tms);
+ mtime *= APR_USEC_PER_SEC;
+#elif HAVE_GMTOFF /* does struct tm have a member tm_gmtoff? */
+ /* mktime will subtract the local timezone, which is not what we want.
+ * Add it again because the MDTM string is GMT
+ */
+ mtime = mktime(&tms);
+ mtime += tms.tm_gmtoff;
+ mtime *= APR_USEC_PER_SEC;
+#else
+ mtime = 0L;
+#endif
+ }
+ }
+#endif /* USE_MDTM */
+/* FIXME: Handle range requests - send REST */
+ buf = apr_pstrcat(p, "RETR ", ftp_escape_globbingchars(p, path), CRLF, NULL);
+ }
+ rc = proxy_ftp_command(buf, r, origin, bb, &ftpmessage);
+ /* rc is an intermediate response for the LIST or RETR commands */
+
+ /*
+ * RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530,
+ * 550 NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502,
+ * 530
+ */
+ /* 110 Restart marker reply. */
+ /* 125 Data connection already open; transfer starting. */
+ /* 150 File status okay; about to open data connection. */
+ /* 226 Closing data connection. */
+ /* 250 Requested file action okay, completed. */
+ /* 421 Service not available, closing control connection. */
+ /* 425 Can't open data connection. */
+ /* 426 Connection closed; transfer aborted. */
+ /* 450 Requested file action not taken. */
+ /* 451 Requested action aborted. Local error in processing. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 530 Not logged in. */
+ /* 550 Requested action not taken. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 550) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: RETR failed, trying LIST instead");
+
+ /* Directory Listings should always be fetched in ASCII mode */
+ dirlisting = 1;
+ ftp_set_TYPE('A', r, origin, bb, NULL);
+
+ rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
+ ftp_escape_globbingchars(p, path), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* possible results: 250, 421, 500, 501, 502, 530, 550 */
+ /* 250 Requested file action okay, completed. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ /* 550 Requested action not taken. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 550) {
+ return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
+ }
+ if (rc != 250) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ /* Update current directory after CWD */
+ cwd = ftp_get_PWD(r, origin, bb);
+ if (cwd != NULL) {
+ apr_table_set(r->notes, "Directory-PWD", cwd);
+ }
+
+ /* See above for the "LIST" vs. "LIST -lag" discussion. */
+ rc = proxy_ftp_command((cwd == NULL || strchr(cwd, '/') != NULL)
+ ? "LIST -lag" CRLF : "LIST" CRLF,
+ r, origin, bb, &ftpmessage);
+
+ /* rc is an intermediate response for the LIST command (125 transfer starting, 150 opening data connection) */
+ if (rc == -1 || rc == 421)
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc != 125 && rc != 150 && rc != 226 && rc != 250) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ r->status = HTTP_OK;
+ r->status_line = "200 OK";
+
+ apr_rfc822_date(dates, r->request_time);
+ apr_table_setn(r->headers_out, "Date", dates);
+ apr_table_setn(r->headers_out, "Server", ap_get_server_version());
+
+ /* set content-type */
+ if (dirlisting) {
+ ap_set_content_type(r, "text/html");
+ }
+ else {
+ if (r->content_type) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: Content-Type set to %s", r->content_type);
+ }
+ else {
+ ap_set_content_type(r, ap_default_type(r));
+ }
+ if (xfer_type != 'A' && size != NULL) {
+ /* We "trust" the ftp server to really serve (size) bytes... */
+ apr_table_setn(r->headers_out, "Content-Length", size);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: Content-Length set to %s", size);
+ }
+ }
+ apr_table_setn(r->headers_out, "Content-Type", r->content_type);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: Content-Type set to %s", r->content_type);
+
+#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
+ if (mtime != 0L) {
+ char datestr[APR_RFC822_DATE_LEN];
+ apr_rfc822_date(datestr, mtime);
+ apr_table_set(r->headers_out, "Last-Modified", datestr);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: Last-Modified set to %s", datestr);
+ }
+#endif /* USE_MDTM */
+
+ /* If an encoding has been set by mistake, delete it.
+ * @@@ FIXME (e.g., for ftp://user@host/file*.tar.gz,
+ * @@@ the encoding is currently set to x-gzip)
+ */
+ if (dirlisting && r->content_encoding != NULL)
+ r->content_encoding = NULL;
+
+ /* set content-encoding (not for dir listings, they are uncompressed)*/
+ if (r->content_encoding != NULL && r->content_encoding[0] != '\0') {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: Content-Encoding set to %s", r->content_encoding);
+ apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
+ }
+
+ /* wait for connection */
+ if (use_port) {
+ for (;;) {
+ rv = apr_socket_accept(&data_sock, local_sock, r->pool);
+ if (rv == APR_EINTR) {
+ continue;
+ }
+ else if (rv == APR_SUCCESS) {
+ break;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "proxy: FTP: failed to accept data connection");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_BAD_GATEWAY;
+ }
+ }
+ }
+
+ /* the transfer socket is now open, create a new connection */
+ data = ap_run_create_connection(p, r->server, data_sock, r->connection->id,
+ r->connection->sbh, c->bucket_alloc);
+ if (!data) {
+ /*
+ * the peer reset the connection already; ap_run_create_connection() closed
+ * the socket
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: an error occurred creating the transfer connection");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* set up the connection filters */
+ rc = ap_run_pre_connection(data, data_sock);
+ if (rc != OK && rc != DONE) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: pre_connection setup failed (%d)",
+ rc);
+ data->aborted = 1;
+ proxy_ftp_cleanup(r, backend);
+ return rc;
+ }
+
+ /*
+ * VI: Receive the Response ------------------------
+ *
+ * Get response from the remote ftp socket, and pass it up the filter chain.
+ */
+
+ /* send response */
+ r->sent_bodyct = 1;
+
+ if (dirlisting) {
+ /* insert directory filter */
+ ap_add_output_filter("PROXY_SEND_DIR", NULL, r, r->connection);
+ }
+
+ /* send body */
+ if (!r->header_only) {
+ apr_bucket *e;
+ int finish = FALSE;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: start body send");
+
+ /* read the body, pass it to the output filters */
+ while (ap_get_brigade(data->input_filters,
+ bb,
+ AP_MODE_READBYTES,
+ APR_BLOCK_READ,
+ conf->io_buffer_size) == APR_SUCCESS) {
+#if DEBUGGING
+ {
+ apr_off_t readbytes;
+ apr_brigade_length(bb, 0, &readbytes);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ r->server, "proxy (PID %d): readbytes: %#x",
+ getpid(), readbytes);
+ }
+#endif
+ /* sanity check */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ apr_brigade_cleanup(bb);
+ break;
+ }
+
+ /* found the last brigade? */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
+ /* if this is the last brigade, cleanup the
+ * backend connection first to prevent the
+ * backend server from hanging around waiting
+ * for a slow client to eat these bytes
+ */
+ ap_flush_conn(data);
+ apr_socket_close(data_sock);
+ data_sock = NULL;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: data connection closed");
+ /* signal that we must leave */
+ finish = TRUE;
+ }
+
+ /* if no EOS yet, then we must flush */
+ if (FALSE == finish) {
+ e = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+
+ /* try send what we read */
+ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
+ || c->aborted) {
+ /* Ack! Phbtt! Die! User aborted! */
+ finish = TRUE;
+ }
+
+ /* make sure we always clean up after ourselves */
+ apr_brigade_cleanup(bb);
+
+ /* if we are done, leave */
+ if (TRUE == finish) {
+ break;
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: end body send");
+
+ }
+ if (data_sock) {
+ ap_flush_conn(data);
+ apr_socket_close(data_sock);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: FTP: data connection closed");
+ }
+
+ /* Retrieve the final response for the RETR or LIST commands */
+ rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
+ apr_brigade_cleanup(bb);
+
+ /*
+ * VII: Clean Up -------------
+ *
+ * If there are no KeepAlives, or if the connection has been signalled to
+ * close, close the socket and clean up
+ */
+
+ /* finish */
+ rc = proxy_ftp_command("QUIT" CRLF,
+ r, origin, bb, &ftpmessage);
+ /* responses: 221, 500 */
+ /* 221 Service closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ ap_flush_conn(origin);
+ proxy_ftp_cleanup(r, backend);
+
+ apr_brigade_destroy(bb);
+ return OK;
+}
+
+static void ap_proxy_ftp_register_hook(apr_pool_t *p)
+{
+ /* hooks */
+ proxy_hook_scheme_handler(proxy_ftp_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ proxy_hook_canon_handler(proxy_ftp_canon, NULL, NULL, APR_HOOK_MIDDLE);
+ /* filters */
+ ap_register_output_filter("PROXY_SEND_DIR", proxy_send_dir_filter,
+ NULL, AP_FTYPE_RESOURCE);
+}
+
+module AP_MODULE_DECLARE_DATA proxy_ftp_module = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_proxy_ftp_register_hook /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_ftp.dsp b/modules/proxy/mod_proxy_ftp.dsp
new file mode 100644
index 00000000..db6d480b
--- /dev/null
+++ b/modules/proxy/mod_proxy_ftp.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_ftp" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_ftp - Win32 Release
+!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 "mod_proxy_ftp.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 "mod_proxy_ftp.mak" CFG="mod_proxy_ftp - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_ftp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_ftp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_ftp - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_ftp_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_ftp.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_proxy_ftp.so" /d "LONG_NAME=proxy_ftp_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:"Release/mod_proxy_ftp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_proxy_ftp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_proxy_ftp - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# 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 "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_ftp_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_ftp.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_proxy_ftp.so" /d "LONG_NAME=proxy_ftp_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_ftp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_ftp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_ftp - Win32 Release"
+# Name "mod_proxy_ftp - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_ftp.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c
new file mode 100644
index 00000000..a0980b9e
--- /dev/null
+++ b/modules/proxy/mod_proxy_http.c
@@ -0,0 +1,1742 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* HTTP routines for Apache proxy */
+
+#include "mod_proxy.h"
+
+module AP_MODULE_DECLARE_DATA proxy_http_module;
+
+static apr_status_t ap_proxy_http_cleanup(const char *scheme,
+ request_rec *r,
+ proxy_conn_rec *backend);
+
+/*
+ * Canonicalise http-like URLs.
+ * scheme is the scheme for the URL
+ * url is the URL starting with the first '/'
+ * def_port is the default port for this scheme.
+ */
+static int proxy_http_canon(request_rec *r, char *url)
+{
+ char *host, *path, *search, sport[7];
+ const char *err;
+ const char *scheme;
+ apr_port_t port, def_port;
+
+ /* ap_port_of_scheme() */
+ if (strncasecmp(url, "http:", 5) == 0) {
+ url += 5;
+ scheme = "http";
+ }
+ else if (strncasecmp(url, "https:", 6) == 0) {
+ url += 6;
+ scheme = "https";
+ }
+ else {
+ return DECLINED;
+ }
+ def_port = apr_uri_port_of_scheme(scheme);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: HTTP: canonicalising URL %s", url);
+
+ /* do syntatic check.
+ * We break the URL into host, port, path, search
+ */
+ port = def_port;
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "error parsing URL %s: %s",
+ url, err);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* now parse path/search args, according to rfc1738 */
+ /* N.B. if this isn't a true proxy request, then the URL _path_
+ * has already been decoded. True proxy requests have r->uri
+ * == r->unparsed_uri, and no others have that property.
+ */
+ if (r->uri == r->unparsed_uri) {
+ search = strchr(url, '?');
+ if (search != NULL)
+ *(search++) = '\0';
+ }
+ else
+ search = r->args;
+
+ /* process path */
+ path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, r->proxyreq);
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+
+ if (port != def_port)
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+ else
+ sport[0] = '\0';
+
+ if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
+ host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+ }
+ r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
+ "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
+ return OK;
+}
+
+/* Clear all connection-based headers from the incoming headers table */
+static void ap_proxy_clear_connection(apr_pool_t *p, apr_table_t *headers)
+{
+ const char *name;
+ char *next = apr_pstrdup(p, apr_table_get(headers, "Connection"));
+
+ apr_table_unset(headers, "Proxy-Connection");
+ if (!next)
+ return;
+
+ while (*next) {
+ name = next;
+ while (*next && !apr_isspace(*next) && (*next != ',')) {
+ ++next;
+ }
+ while (*next && (apr_isspace(*next) || (*next == ','))) {
+ *next = '\0';
+ ++next;
+ }
+ apr_table_unset(headers, name);
+ }
+ apr_table_unset(headers, "Connection");
+}
+
+static void add_te_chunked(apr_pool_t *p,
+ apr_bucket_alloc_t *bucket_alloc,
+ apr_bucket_brigade *header_brigade)
+{
+ apr_bucket *e;
+ char *buf;
+ const char te_hdr[] = "Transfer-Encoding: chunked" CRLF;
+
+ buf = apr_pmemdup(p, te_hdr, sizeof(te_hdr)-1);
+ ap_xlate_proto_to_ascii(buf, sizeof(te_hdr)-1);
+
+ e = apr_bucket_pool_create(buf, sizeof(te_hdr)-1, p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+}
+
+static void add_cl(apr_pool_t *p,
+ apr_bucket_alloc_t *bucket_alloc,
+ apr_bucket_brigade *header_brigade,
+ const char *cl_val)
+{
+ apr_bucket *e;
+ char *buf;
+
+ buf = apr_pstrcat(p, "Content-Length: ",
+ cl_val,
+ CRLF,
+ NULL);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+}
+
+#define ASCII_CRLF "\015\012"
+#define ASCII_ZERO "\060"
+
+static void terminate_headers(apr_bucket_alloc_t *bucket_alloc,
+ apr_bucket_brigade *header_brigade)
+{
+ apr_bucket *e;
+
+ /* add empty line at the end of the headers */
+ e = apr_bucket_immortal_create(ASCII_CRLF, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+}
+
+static apr_status_t pass_brigade(apr_bucket_alloc_t *bucket_alloc,
+ request_rec *r, proxy_conn_rec *conn,
+ conn_rec *origin, apr_bucket_brigade *bb,
+ int flush)
+{
+ apr_status_t status;
+ apr_off_t transferred;
+
+ if (flush) {
+ apr_bucket *e = apr_bucket_flush_create(bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+ apr_brigade_length(bb, 0, &transferred);
+ if (transferred != -1)
+ conn->worker->s->transferred += transferred;
+ status = ap_pass_brigade(origin->output_filters, bb);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: pass request body failed to %pI (%s)",
+ conn->addr, conn->hostname);
+ return status;
+ }
+ apr_brigade_cleanup(bb);
+ return APR_SUCCESS;
+}
+
+#define MAX_MEM_SPOOL 16384
+
+static apr_status_t stream_reqbody_chunked(apr_pool_t *p,
+ request_rec *r,
+ proxy_conn_rec *p_conn,
+ conn_rec *origin,
+ apr_bucket_brigade *header_brigade,
+ apr_bucket_brigade *input_brigade)
+{
+ int seen_eos = 0;
+ apr_size_t hdr_len;
+ apr_off_t bytes;
+ apr_status_t status;
+ apr_bucket_alloc_t *bucket_alloc = r->connection->bucket_alloc;
+ apr_bucket_brigade *bb;
+ apr_bucket *e;
+
+ add_te_chunked(p, bucket_alloc, header_brigade);
+ terminate_headers(bucket_alloc, header_brigade);
+
+ while (!APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade)))
+ {
+ char chunk_hdr[20]; /* must be here due to transient bucket. */
+
+ /* If this brigade contains EOS, either stop or remove it. */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ seen_eos = 1;
+
+ /* We can't pass this EOS to the output_filters. */
+ e = APR_BRIGADE_LAST(input_brigade);
+ apr_bucket_delete(e);
+ }
+
+ apr_brigade_length(input_brigade, 1, &bytes);
+
+ hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr),
+ "%" APR_UINT64_T_HEX_FMT CRLF,
+ (apr_uint64_t)bytes);
+
+ ap_xlate_proto_to_ascii(chunk_hdr, hdr_len);
+ e = apr_bucket_transient_create(chunk_hdr, hdr_len,
+ bucket_alloc);
+ APR_BRIGADE_INSERT_HEAD(input_brigade, e);
+
+ /*
+ * Append the end-of-chunk CRLF
+ */
+ e = apr_bucket_immortal_create(ASCII_CRLF, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+
+ if (header_brigade) {
+ /* we never sent the header brigade, so go ahead and
+ * take care of that now
+ */
+ bb = header_brigade;
+
+ /*
+ * Save input_brigade in bb brigade. (At least) in the SSL case
+ * input_brigade contains transient buckets whose data would get
+ * overwritten during the next call of ap_get_brigade in the loop.
+ * ap_save_brigade ensures these buckets to be set aside.
+ * Calling ap_save_brigade with NULL as filter is OK, because
+ * bb brigade already has been created and does not need to get
+ * created by ap_save_brigade.
+ */
+ status = ap_save_brigade(NULL, &bb, &input_brigade, p);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ header_brigade = NULL;
+ }
+ else {
+ bb = input_brigade;
+ }
+
+ /* The request is flushed below this loop with chunk EOS header */
+ status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 0);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ if (seen_eos) {
+ break;
+ }
+
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ HUGE_STRING_LEN);
+
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+
+ if (header_brigade) {
+ /* we never sent the header brigade because there was no request body;
+ * send it now
+ */
+ bb = header_brigade;
+ }
+ else {
+ if (!APR_BRIGADE_EMPTY(input_brigade)) {
+ /* input brigade still has an EOS which we can't pass to the output_filters. */
+ e = APR_BRIGADE_LAST(input_brigade);
+ AP_DEBUG_ASSERT(APR_BUCKET_IS_EOS(e));
+ apr_bucket_delete(e);
+ }
+ bb = input_brigade;
+ }
+
+ e = apr_bucket_immortal_create(ASCII_ZERO ASCII_CRLF
+ /* <trailers> */
+ ASCII_CRLF,
+ 5, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+
+ /* Now we have headers-only, or the chunk EOS mark; flush it */
+ status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1);
+ return status;
+}
+
+static apr_status_t stream_reqbody_cl(apr_pool_t *p,
+ request_rec *r,
+ proxy_conn_rec *p_conn,
+ conn_rec *origin,
+ apr_bucket_brigade *header_brigade,
+ apr_bucket_brigade *input_brigade,
+ const char *old_cl_val)
+{
+ int seen_eos = 0;
+ apr_status_t status = APR_SUCCESS;
+ apr_bucket_alloc_t *bucket_alloc = r->connection->bucket_alloc;
+ apr_bucket_brigade *bb;
+ apr_bucket *e;
+ apr_off_t cl_val = 0;
+ apr_off_t bytes;
+ apr_off_t bytes_streamed = 0;
+
+ if (old_cl_val) {
+ add_cl(p, bucket_alloc, header_brigade, old_cl_val);
+ cl_val = atol(old_cl_val);
+ }
+ terminate_headers(bucket_alloc, header_brigade);
+
+ while (!APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade)))
+ {
+ apr_brigade_length(input_brigade, 1, &bytes);
+ bytes_streamed += bytes;
+
+ /* If this brigade contains EOS, either stop or remove it. */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ seen_eos = 1;
+
+ /* We can't pass this EOS to the output_filters. */
+ e = APR_BRIGADE_LAST(input_brigade);
+ apr_bucket_delete(e);
+ }
+
+ /* C-L < bytes streamed?!?
+ * We will error out after the body is completely
+ * consumed, but we can't stream more bytes at the
+ * back end since they would in part be interpreted
+ * as another request! If nothing is sent, then
+ * just send nothing.
+ *
+ * Prevents HTTP Response Splitting.
+ */
+ if (bytes_streamed > cl_val)
+ continue;
+
+ if (header_brigade) {
+ /* we never sent the header brigade, so go ahead and
+ * take care of that now
+ */
+ bb = header_brigade;
+
+ /*
+ * Save input_brigade in bb brigade. (At least) in the SSL case
+ * input_brigade contains transient buckets whose data would get
+ * overwritten during the next call of ap_get_brigade in the loop.
+ * ap_save_brigade ensures these buckets to be set aside.
+ * Calling ap_save_brigade with NULL as filter is OK, because
+ * bb brigade already has been created and does not need to get
+ * created by ap_save_brigade.
+ */
+ status = ap_save_brigade(NULL, &bb, &input_brigade, p);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ header_brigade = NULL;
+ }
+ else {
+ bb = input_brigade;
+ }
+
+ /* Once we hit EOS, we are ready to flush. */
+ status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, seen_eos);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ if (seen_eos) {
+ break;
+ }
+
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ HUGE_STRING_LEN);
+
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+
+ if (bytes_streamed != cl_val) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "proxy: client %s given Content-Length did not match"
+ " number of body bytes read", r->connection->remote_ip);
+ return APR_EOF;
+ }
+
+ if (header_brigade) {
+ /* we never sent the header brigade since there was no request
+ * body; send it now with the flush flag
+ */
+ bb = header_brigade;
+ status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1);
+ }
+ return status;
+}
+
+static apr_status_t spool_reqbody_cl(apr_pool_t *p,
+ request_rec *r,
+ proxy_conn_rec *p_conn,
+ conn_rec *origin,
+ apr_bucket_brigade *header_brigade,
+ apr_bucket_brigade *input_brigade,
+ int force_cl)
+{
+ int seen_eos = 0;
+ apr_status_t status;
+ apr_bucket_alloc_t *bucket_alloc = r->connection->bucket_alloc;
+ apr_bucket_brigade *body_brigade;
+ apr_bucket *e;
+ apr_off_t bytes, bytes_spooled = 0, fsize = 0;
+ apr_file_t *tmpfile = NULL;
+
+ body_brigade = apr_brigade_create(p, bucket_alloc);
+
+ while (!APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade)))
+ {
+ /* If this brigade contains EOS, either stop or remove it. */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ seen_eos = 1;
+
+ /* We can't pass this EOS to the output_filters. */
+ e = APR_BRIGADE_LAST(input_brigade);
+ apr_bucket_delete(e);
+ }
+
+ apr_brigade_length(input_brigade, 1, &bytes);
+
+ if (bytes_spooled + bytes > MAX_MEM_SPOOL) {
+ /* can't spool any more in memory; write latest brigade to disk */
+ if (tmpfile == NULL) {
+ const char *temp_dir;
+ char *template;
+
+ status = apr_temp_dir_get(&temp_dir, p);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: search for temporary directory failed");
+ return status;
+ }
+ apr_filepath_merge(&template, temp_dir,
+ "modproxy.tmp.XXXXXX",
+ APR_FILEPATH_NATIVE, p);
+ status = apr_file_mktemp(&tmpfile, template, 0, p);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: creation of temporary file in directory %s failed",
+ temp_dir);
+ return status;
+ }
+ }
+ for (e = APR_BRIGADE_FIRST(input_brigade);
+ e != APR_BRIGADE_SENTINEL(input_brigade);
+ e = APR_BUCKET_NEXT(e)) {
+ const char *data;
+ apr_size_t bytes_read, bytes_written;
+
+ apr_bucket_read(e, &data, &bytes_read, APR_BLOCK_READ);
+ status = apr_file_write_full(tmpfile, data, bytes_read, &bytes_written);
+ if (status != APR_SUCCESS) {
+ const char *tmpfile_name;
+
+ if (apr_file_name_get(&tmpfile_name, tmpfile) != APR_SUCCESS) {
+ tmpfile_name = "(unknown)";
+ }
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: write to temporary file %s failed",
+ tmpfile_name);
+ return status;
+ }
+ AP_DEBUG_ASSERT(bytes_read == bytes_written);
+ fsize += bytes_written;
+ }
+ apr_brigade_cleanup(input_brigade);
+ }
+ else {
+
+ /*
+ * Save input_brigade in body_brigade. (At least) in the SSL case
+ * input_brigade contains transient buckets whose data would get
+ * overwritten during the next call of ap_get_brigade in the loop.
+ * ap_save_brigade ensures these buckets to be set aside.
+ * Calling ap_save_brigade with NULL as filter is OK, because
+ * body_brigade already has been created and does not need to get
+ * created by ap_save_brigade.
+ */
+ status = ap_save_brigade(NULL, &body_brigade, &input_brigade, p);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ }
+
+ bytes_spooled += bytes;
+
+ if (seen_eos) {
+ break;
+ }
+
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ HUGE_STRING_LEN);
+
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+
+ if (bytes_spooled || force_cl) {
+ add_cl(p, bucket_alloc, header_brigade, apr_off_t_toa(p, bytes_spooled));
+ }
+ terminate_headers(bucket_alloc, header_brigade);
+ APR_BRIGADE_CONCAT(header_brigade, body_brigade);
+ if (tmpfile) {
+ /* For platforms where the size of the file may be larger than
+ * that which can be stored in a single bucket (where the
+ * length field is an apr_size_t), split it into several
+ * buckets: */
+ if (sizeof(apr_off_t) > sizeof(apr_size_t)
+ && fsize > AP_MAX_SENDFILE) {
+ e = apr_bucket_file_create(tmpfile, 0, AP_MAX_SENDFILE, p,
+ bucket_alloc);
+ while (fsize > AP_MAX_SENDFILE) {
+ apr_bucket *ce;
+ apr_bucket_copy(e, &ce);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, ce);
+ e->start += AP_MAX_SENDFILE;
+ fsize -= AP_MAX_SENDFILE;
+ }
+ e->length = (apr_size_t)fsize; /* Resize just the last bucket */
+ }
+ else {
+ e = apr_bucket_file_create(tmpfile, 0, (apr_size_t)fsize, p,
+ bucket_alloc);
+ }
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+ }
+ /* This is all a single brigade, pass with flush flagged */
+ status = pass_brigade(bucket_alloc, r, p_conn, origin, header_brigade, 1);
+ return status;
+}
+
+static
+apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r,
+ proxy_conn_rec *p_conn, conn_rec *origin,
+ proxy_server_conf *conf,
+ apr_uri_t *uri,
+ char *url, char *server_portstr)
+{
+ conn_rec *c = r->connection;
+ apr_bucket_alloc_t *bucket_alloc = c->bucket_alloc;
+ apr_bucket_brigade *header_brigade;
+ apr_bucket_brigade *input_brigade;
+ apr_bucket_brigade *temp_brigade;
+ apr_bucket *e;
+ char *buf;
+ const apr_array_header_t *headers_in_array;
+ const apr_table_entry_t *headers_in;
+ int counter;
+ apr_status_t status;
+ enum rb_methods {RB_INIT, RB_STREAM_CL, RB_STREAM_CHUNKED, RB_SPOOL_CL};
+ enum rb_methods rb_method = RB_INIT;
+ const char *old_cl_val = NULL;
+ const char *old_te_val = NULL;
+ apr_off_t bytes_read = 0;
+ apr_off_t bytes;
+ int force10;
+ apr_table_t *headers_in_copy;
+
+ header_brigade = apr_brigade_create(p, origin->bucket_alloc);
+
+ /*
+ * Send the HTTP/1.1 request to the remote server
+ */
+
+ if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
+ buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL);
+ force10 = 1;
+ p_conn->close++;
+ } else {
+ buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL);
+ force10 = 0;
+ }
+ if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) {
+ origin->keepalive = AP_CONN_CLOSE;
+ p_conn->close++;
+ }
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+ if (conf->preserve_host == 0) {
+ if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
+ buf = apr_pstrcat(p, "Host: ", uri->hostname, ":", uri->port_str,
+ CRLF, NULL);
+ } else {
+ buf = apr_pstrcat(p, "Host: ", uri->hostname, CRLF, NULL);
+ }
+ }
+ else {
+ /* don't want to use r->hostname, as the incoming header might have a
+ * port attached
+ */
+ const char* hostname = apr_table_get(r->headers_in,"Host");
+ if (!hostname) {
+ hostname = r->server->server_hostname;
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ "proxy: no HTTP 0.9 request (with no host line) "
+ "on incoming request and preserve host set "
+ "forcing hostname to be %s for uri %s",
+ hostname,
+ r->uri );
+ }
+ buf = apr_pstrcat(p, "Host: ", hostname, CRLF, NULL);
+ }
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+
+ /* handle Via */
+ if (conf->viaopt == via_block) {
+ /* Block all outgoing Via: headers */
+ apr_table_unset(r->headers_in, "Via");
+ } else if (conf->viaopt != via_off) {
+ const char *server_name = ap_get_server_name(r);
+ /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
+ * then the server name returned by ap_get_server_name() is the
+ * origin server name (which does make too much sense with Via: headers)
+ * so we use the proxy vhost's name instead.
+ */
+ if (server_name == r->hostname)
+ server_name = r->server->server_hostname;
+ /* Create a "Via:" request header entry and merge it */
+ /* Generate outgoing Via: header with/without server comment: */
+ apr_table_mergen(r->headers_in, "Via",
+ (conf->viaopt == via_full)
+ ? apr_psprintf(p, "%d.%d %s%s (%s)",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name, server_portstr,
+ AP_SERVER_BASEVERSION)
+ : apr_psprintf(p, "%d.%d %s%s",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name, server_portstr)
+ );
+ }
+
+ /* X-Forwarded-*: handling
+ *
+ * XXX Privacy Note:
+ * -----------------
+ *
+ * These request headers are only really useful when the mod_proxy
+ * is used in a reverse proxy configuration, so that useful info
+ * about the client can be passed through the reverse proxy and on
+ * to the backend server, which may require the information to
+ * function properly.
+ *
+ * In a forward proxy situation, these options are a potential
+ * privacy violation, as information about clients behind the proxy
+ * are revealed to arbitrary servers out there on the internet.
+ *
+ * The HTTP/1.1 Via: header is designed for passing client
+ * information through proxies to a server, and should be used in
+ * a forward proxy configuation instead of X-Forwarded-*. See the
+ * ProxyVia option for details.
+ */
+
+ if (PROXYREQ_REVERSE == r->proxyreq) {
+ const char *buf;
+
+ /* Add X-Forwarded-For: so that the upstream has a chance to
+ * determine, where the original request came from.
+ */
+ apr_table_mergen(r->headers_in, "X-Forwarded-For",
+ c->remote_ip);
+
+ /* Add X-Forwarded-Host: so that upstream knows what the
+ * original request hostname was.
+ */
+ if ((buf = apr_table_get(r->headers_in, "Host"))) {
+ apr_table_mergen(r->headers_in, "X-Forwarded-Host", buf);
+ }
+
+ /* Add X-Forwarded-Server: so that upstream knows what the
+ * name of this proxy server is (if there are more than one)
+ * XXX: This duplicates Via: - do we strictly need it?
+ */
+ apr_table_mergen(r->headers_in, "X-Forwarded-Server",
+ r->server->server_hostname);
+ }
+
+ proxy_run_fixups(r);
+ /*
+ * Make a copy of the headers_in table before clearing the connection
+ * headers as we need the connection headers later in the http output
+ * filter to prepare the correct response headers.
+ *
+ * Note: We need to take r->pool for apr_table_copy as the key / value
+ * pairs in r->headers_in have been created out of r->pool and
+ * p might be (and actually is) a longer living pool.
+ * This would trigger the bad pool ancestry abort in apr_table_copy if
+ * apr is compiled with APR_POOL_DEBUG.
+ */
+ headers_in_copy = apr_table_copy(r->pool, r->headers_in);
+ ap_proxy_clear_connection(p, headers_in_copy);
+ /* send request headers */
+ headers_in_array = apr_table_elts(headers_in_copy);
+ headers_in = (const apr_table_entry_t *) headers_in_array->elts;
+ for (counter = 0; counter < headers_in_array->nelts; counter++) {
+ if (headers_in[counter].key == NULL
+ || headers_in[counter].val == NULL
+
+ /* Already sent */
+ || !strcasecmp(headers_in[counter].key, "Host")
+
+ /* Clear out hop-by-hop request headers not to send
+ * RFC2616 13.5.1 says we should strip these headers
+ */
+ || !strcasecmp(headers_in[counter].key, "Keep-Alive")
+ || !strcasecmp(headers_in[counter].key, "TE")
+ || !strcasecmp(headers_in[counter].key, "Trailer")
+ || !strcasecmp(headers_in[counter].key, "Upgrade")
+
+ /* XXX: @@@ FIXME: "Proxy-Authorization" should *only* be
+ * suppressed if THIS server requested the authentication,
+ * not when a frontend proxy requested it!
+ *
+ * The solution to this problem is probably to strip out
+ * the Proxy-Authorisation header in the authorisation
+ * code itself, not here. This saves us having to signal
+ * somehow whether this request was authenticated or not.
+ */
+ || !strcasecmp(headers_in[counter].key,"Proxy-Authorization")
+ || !strcasecmp(headers_in[counter].key,"Proxy-Authenticate")) {
+ continue;
+ }
+
+ /* Skip Transfer-Encoding and Content-Length for now.
+ */
+ if (!strcasecmp(headers_in[counter].key, "Transfer-Encoding")) {
+ old_te_val = headers_in[counter].val;
+ continue;
+ }
+ if (!strcasecmp(headers_in[counter].key, "Content-Length")) {
+ old_cl_val = headers_in[counter].val;
+ continue;
+ }
+
+ /* for sub-requests, ignore freshness/expiry headers */
+ if (r->main) {
+ if ( !strcasecmp(headers_in[counter].key, "If-Match")
+ || !strcasecmp(headers_in[counter].key, "If-Modified-Since")
+ || !strcasecmp(headers_in[counter].key, "If-Range")
+ || !strcasecmp(headers_in[counter].key, "If-Unmodified-Since")
+ || !strcasecmp(headers_in[counter].key, "If-None-Match")) {
+ continue;
+ }
+ }
+
+ buf = apr_pstrcat(p, headers_in[counter].key, ": ",
+ headers_in[counter].val, CRLF,
+ NULL);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+ }
+
+ /* We have headers, let's figure out our request body... */
+ input_brigade = apr_brigade_create(p, bucket_alloc);
+
+ /* sub-requests never use keepalives, and mustn't pass request bodies.
+ * Because the new logic looks at input_brigade, we will self-terminate
+ * input_brigade and jump past all of the request body logic...
+ * Reading anything with ap_get_brigade is likely to consume the
+ * main request's body or read beyond EOS - which would be unplesant.
+ */
+ if (r->main) {
+ /* XXX: Why DON'T sub-requests use keepalives? */
+ p_conn->close++;
+ if (old_cl_val) {
+ old_cl_val = NULL;
+ apr_table_unset(r->headers_in, "Content-Length");
+ }
+ if (old_te_val) {
+ old_te_val = NULL;
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ }
+ rb_method = RB_STREAM_CL;
+ e = apr_bucket_eos_create(input_brigade->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ goto skip_body;
+ }
+
+ /* WE only understand chunked. Other modules might inject
+ * (and therefore, decode) other flavors but we don't know
+ * that the can and have done so unless they they remove
+ * their decoding from the headers_in T-E list.
+ * XXX: Make this extensible, but in doing so, presume the
+ * encoding has been done by the extensions' handler, and
+ * do not modify add_te_chunked's logic
+ */
+ if (old_te_val && strcmp(old_te_val, "chunked") != 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "proxy: %s Transfer-Encoding is not supported",
+ old_te_val);
+ return APR_EINVAL;
+ }
+
+ if (old_cl_val && old_te_val) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_ENOTIMPL, r->server,
+ "proxy: client %s (%s) requested Transfer-Encoding "
+ "chunked body with Content-Length (C-L ignored)",
+ c->remote_ip, c->remote_host ? c->remote_host: "");
+ apr_table_unset(r->headers_in, "Content-Length");
+ old_cl_val = NULL;
+ origin->keepalive = AP_CONN_CLOSE;
+ p_conn->close++;
+ }
+
+ /* Prefetch MAX_MEM_SPOOL bytes
+ *
+ * This helps us avoid any election of C-L v.s. T-E
+ * request bodies, since we are willing to keep in
+ * memory this much data, in any case. This gives
+ * us an instant C-L election if the body is of some
+ * reasonable size.
+ */
+ temp_brigade = apr_brigade_create(p, bucket_alloc);
+ do {
+ status = ap_get_brigade(r->input_filters, temp_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ MAX_MEM_SPOOL - bytes_read);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: prefetch request body failed to %pI (%s)"
+ " from %s (%s)",
+ p_conn->addr, p_conn->hostname ? p_conn->hostname: "",
+ c->remote_ip, c->remote_host ? c->remote_host: "");
+ return status;
+ }
+
+ apr_brigade_length(temp_brigade, 1, &bytes);
+ bytes_read += bytes;
+
+ /*
+ * Save temp_brigade in input_brigade. (At least) in the SSL case
+ * temp_brigade contains transient buckets whose data would get
+ * overwritten during the next call of ap_get_brigade in the loop.
+ * ap_save_brigade ensures these buckets to be set aside.
+ * Calling ap_save_brigade with NULL as filter is OK, because
+ * input_brigade already has been created and does not need to get
+ * created by ap_save_brigade.
+ */
+ status = ap_save_brigade(NULL, &input_brigade, &temp_brigade, p);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: processing prefetched request body failed"
+ " to %pI (%s) from %s (%s)",
+ p_conn->addr, p_conn->hostname ? p_conn->hostname: "",
+ c->remote_ip, c->remote_host ? c->remote_host: "");
+ return status;
+ }
+
+ /* Ensure we don't hit a wall where we have a buffer too small
+ * for ap_get_brigade's filters to fetch us another bucket,
+ * surrender once we hit 80 bytes less than MAX_MEM_SPOOL
+ * (an arbitrary value.)
+ */
+ } while ((bytes_read < MAX_MEM_SPOOL - 80)
+ && !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade)));
+
+ /* Use chunked request body encoding or send a content-length body?
+ *
+ * Prefer C-L when:
+ *
+ * We have no request body (handled by RB_STREAM_CL)
+ *
+ * We have a request body length <= MAX_MEM_SPOOL
+ *
+ * The administrator has setenv force-proxy-request-1.0
+ *
+ * The client sent a C-L body, and the administrator has
+ * not setenv proxy-sendchunked or has set setenv proxy-sendcl
+ *
+ * The client sent a T-E body, and the administrator has
+ * setenv proxy-sendcl, and not setenv proxy-sendchunked
+ *
+ * If both proxy-sendcl and proxy-sendchunked are set, the
+ * behavior is the same as if neither were set, large bodies
+ * that can't be read will be forwarded in their original
+ * form of C-L, or T-E.
+ *
+ * To ensure maximum compatibility, setenv proxy-sendcl
+ * To reduce server resource use, setenv proxy-sendchunked
+ *
+ * Then address specific servers with conditional setenv
+ * options to restore the default behavior where desireable.
+ *
+ * We have to compute content length by reading the entire request
+ * body; if request body is not small, we'll spool the remaining
+ * input to a temporary file. Chunked is always preferable.
+ *
+ * We can only trust the client-provided C-L if the T-E header
+ * is absent, and the filters are unchanged (the body won't
+ * be resized by another content filter).
+ */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ /* The whole thing fit, so our decision is trivial, use
+ * the filtered bytes read from the client for the request
+ * body Content-Length.
+ *
+ * If we expected no body, and read no body, do not set
+ * the Content-Length.
+ */
+ if (old_cl_val || old_te_val || bytes_read) {
+ old_cl_val = apr_off_t_toa(r->pool, bytes_read);
+ }
+ rb_method = RB_STREAM_CL;
+ }
+ else if (old_te_val) {
+ if (force10
+ || (apr_table_get(r->subprocess_env, "proxy-sendcl")
+ && !apr_table_get(r->subprocess_env, "proxy-sendchunks"))) {
+ rb_method = RB_SPOOL_CL;
+ }
+ else {
+ rb_method = RB_STREAM_CHUNKED;
+ }
+ }
+ else if (old_cl_val) {
+ if (r->input_filters == r->proto_input_filters) {
+ rb_method = RB_STREAM_CL;
+ }
+ else if (!force10
+ && apr_table_get(r->subprocess_env, "proxy-sendchunks")
+ && !apr_table_get(r->subprocess_env, "proxy-sendcl")) {
+ rb_method = RB_STREAM_CHUNKED;
+ }
+ else {
+ rb_method = RB_SPOOL_CL;
+ }
+ }
+ else {
+ /* This is an appropriate default; very efficient for no-body
+ * requests, and has the behavior that it will not add any C-L
+ * when the old_cl_val is NULL.
+ */
+ rb_method = RB_SPOOL_CL;
+ }
+
+/* Yes I hate gotos. This is the subrequest shortcut */
+skip_body:
+ /*
+ * Handle Connection: header if we do HTTP/1.1 request:
+ * If we plan to close the backend connection sent Connection: close
+ * otherwise sent Connection: Keep-Alive.
+ */
+ if (!force10) {
+ if (p_conn->close || p_conn->close_on_recycle) {
+ buf = apr_pstrdup(p, "Connection: close" CRLF);
+ }
+ else {
+ buf = apr_pstrdup(p, "Connection: Keep-Alive" CRLF);
+ }
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+ }
+
+ /* send the request body, if any. */
+ switch(rb_method) {
+ case RB_STREAM_CHUNKED:
+ status = stream_reqbody_chunked(p, r, p_conn, origin, header_brigade,
+ input_brigade);
+ break;
+ case RB_STREAM_CL:
+ status = stream_reqbody_cl(p, r, p_conn, origin, header_brigade,
+ input_brigade, old_cl_val);
+ break;
+ case RB_SPOOL_CL:
+ status = spool_reqbody_cl(p, r, p_conn, origin, header_brigade,
+ input_brigade, (old_cl_val != NULL)
+ || (old_te_val != NULL)
+ || (bytes_read > 0));
+ break;
+ default:
+ /* shouldn't be possible */
+ status = APR_EINVAL;
+ break;
+ }
+
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: pass request body failed to %pI (%s)"
+ " from %s (%s)",
+ p_conn->addr,
+ p_conn->hostname ? p_conn->hostname: "",
+ c->remote_ip,
+ c->remote_host ? c->remote_host: "");
+ return status;
+ }
+
+ return APR_SUCCESS;
+}
+
+static void process_proxy_header(request_rec* r, proxy_dir_conf* c,
+ const char* key, const char* value)
+{
+ static const char* date_hdrs[]
+ = { "Date", "Expires", "Last-Modified", NULL } ;
+ static const struct {
+ const char* name;
+ ap_proxy_header_reverse_map_fn func;
+ } transform_hdrs[] = {
+ { "Location", ap_proxy_location_reverse_map } ,
+ { "Content-Location", ap_proxy_location_reverse_map } ,
+ { "URI", ap_proxy_location_reverse_map } ,
+ { "Destination", ap_proxy_location_reverse_map } ,
+ { "Set-Cookie", ap_proxy_cookie_reverse_map } ,
+ { NULL, NULL }
+ } ;
+ int i ;
+ for ( i = 0 ; date_hdrs[i] ; ++i ) {
+ if ( !strcasecmp(date_hdrs[i], key) ) {
+ apr_table_add(r->headers_out, key,
+ ap_proxy_date_canon(r->pool, value)) ;
+ return ;
+ }
+ }
+ for ( i = 0 ; transform_hdrs[i].name ; ++i ) {
+ if ( !strcasecmp(transform_hdrs[i].name, key) ) {
+ apr_table_add(r->headers_out, key,
+ (*transform_hdrs[i].func)(r, c, value)) ;
+ return ;
+ }
+ }
+ apr_table_add(r->headers_out, key, value) ;
+ return ;
+}
+
+/*
+ * Note: pread_len is the length of the response that we've mistakenly
+ * read (assuming that we don't consider that an error via
+ * ProxyBadHeader StartBody). This depends on buffer actually being
+ * local storage to the calling code in order for pread_len to make
+ * any sense at all, since we depend on buffer still containing
+ * what was read by ap_getline() upon return.
+ */
+static void ap_proxy_read_headers(request_rec *r, request_rec *rr,
+ char *buffer, int size,
+ conn_rec *c, int *pread_len)
+{
+ int len;
+ char *value, *end;
+ char field[MAX_STRING_LEN];
+ int saw_headers = 0;
+ void *sconf = r->server->module_config;
+ proxy_server_conf *psc;
+ proxy_dir_conf *dconf;
+
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ psc = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+
+ r->headers_out = apr_table_make(r->pool, 20);
+ *pread_len = 0;
+
+ /*
+ * Read header lines until we get the empty separator line, a read error,
+ * the connection closes (EOF), or we timeout.
+ */
+ while ((len = ap_getline(buffer, size, rr, 1)) > 0) {
+
+ if (!(value = strchr(buffer, ':'))) { /* Find the colon separator */
+
+ /* We may encounter invalid headers, usually from buggy
+ * MS IIS servers, so we need to determine just how to handle
+ * them. We can either ignore them, assume that they mark the
+ * start-of-body (eg: a missing CRLF) or (the default) mark
+ * the headers as totally bogus and return a 500. The sole
+ * exception is an extra "HTTP/1.0 200, OK" line sprinkled
+ * in between the usual MIME headers, which is a favorite
+ * IIS bug.
+ */
+ /* XXX: The mask check is buggy if we ever see an HTTP/1.10 */
+
+ if (!apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
+ if (psc->badopt == bad_error) {
+ /* Nope, it wasn't even an extra HTTP header. Give up. */
+ r->headers_out = NULL;
+ return ;
+ }
+ else if (psc->badopt == bad_body) {
+ /* if we've already started loading headers_out, then
+ * return what we've accumulated so far, in the hopes
+ * that they are useful; also note that we likely pre-read
+ * the first line of the response.
+ */
+ if (saw_headers) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
+ "proxy: Starting body due to bogus non-header in headers "
+ "returned by %s (%s)", r->uri, r->method);
+ *pread_len = len;
+ return ;
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
+ "proxy: No HTTP headers "
+ "returned by %s (%s)", r->uri, r->method);
+ return ;
+ }
+ }
+ }
+ /* this is the psc->badopt == bad_ignore case */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
+ "proxy: Ignoring bogus HTTP header "
+ "returned by %s (%s)", r->uri, r->method);
+ continue;
+ }
+
+ *value = '\0';
+ ++value;
+ /* XXX: RFC2068 defines only SP and HT as whitespace, this test is
+ * wrong... and so are many others probably.
+ */
+ while (apr_isspace(*value))
+ ++value; /* Skip to start of value */
+
+ /* should strip trailing whitespace as well */
+ for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --
+end)
+ *end = '\0';
+
+ /* make sure we add so as not to destroy duplicated headers
+ * Modify headers requiring canonicalisation and/or affected
+ * by ProxyPassReverse and family with process_proxy_header
+ */
+ process_proxy_header(r, dconf, buffer, value) ;
+ saw_headers = 1;
+
+ /* the header was too long; at the least we should skip extra data */
+ if (len >= size - 1) {
+ while ((len = ap_getline(field, MAX_STRING_LEN, rr, 1))
+ >= MAX_STRING_LEN - 1) {
+ /* soak up the extra data */
+ }
+ if (len == 0) /* time to exit the larger loop as well */
+ break;
+ }
+ }
+}
+
+
+
+static int addit_dammit(void *v, const char *key, const char *val)
+{
+ apr_table_addn(v, key, val);
+ return 1;
+}
+
+static
+apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
+ proxy_conn_rec *backend,
+ conn_rec *origin,
+ proxy_server_conf *conf,
+ char *server_portstr) {
+ conn_rec *c = r->connection;
+ char buffer[HUGE_STRING_LEN];
+ const char *buf;
+ char keepchar;
+ request_rec *rp;
+ apr_bucket *e;
+ apr_bucket_brigade *bb;
+ int len, backasswards;
+ int interim_response; /* non-zero whilst interim 1xx responses
+ * are being read. */
+ int pread_len = 0;
+ apr_table_t *save_table;
+ int backend_broke = 0;
+
+ bb = apr_brigade_create(p, c->bucket_alloc);
+
+ /* Get response from the remote server, and pass it up the
+ * filter chain
+ */
+
+ rp = ap_proxy_make_fake_req(origin, r);
+ /* In case anyone needs to know, this is a fake request that is really a
+ * response.
+ */
+ rp->proxyreq = PROXYREQ_RESPONSE;
+ do {
+ apr_brigade_cleanup(bb);
+
+ len = ap_getline(buffer, sizeof(buffer), rp, 0);
+ if (len == 0) {
+ /* handle one potential stray CRLF */
+ len = ap_getline(buffer, sizeof(buffer), rp, 0);
+ }
+ if (len <= 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "proxy: error reading status line from remote "
+ "server %s", backend->hostname);
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ /* XXX: Is this a real headers length send from remote? */
+ backend->worker->s->read += len;
+
+ /* Is it an HTTP/1 response?
+ * This is buggy if we ever see an HTTP/1.10
+ */
+ if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
+ int major, minor;
+
+ if (2 != sscanf(buffer, "HTTP/%u.%u", &major, &minor)) {
+ major = 1;
+ minor = 1;
+ }
+ /* If not an HTTP/1 message or
+ * if the status line was > 8192 bytes
+ */
+ else if ((buffer[5] != '1') || (len >= sizeof(buffer)-1)) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "Corrupt status line returned by remote "
+ "server: ", buffer, NULL));
+ }
+ backasswards = 0;
+
+ keepchar = buffer[12];
+ buffer[12] = '\0';
+ r->status = atoi(&buffer[9]);
+
+ if (keepchar != '\0') {
+ buffer[12] = keepchar;
+ } else {
+ /* 2616 requires the space in Status-Line; the origin
+ * server may have sent one but ap_rgetline_core will
+ * have stripped it. */
+ buffer[12] = ' ';
+ buffer[13] = '\0';
+ }
+ r->status_line = apr_pstrdup(p, &buffer[9]);
+
+
+ /* read the headers. */
+ /* N.B. for HTTP/1.0 clients, we have to fold line-wrapped headers*/
+ /* Also, take care with headers with multiple occurences. */
+
+ /* First, tuck away all already existing cookies */
+ save_table = apr_table_make(r->pool, 2);
+ apr_table_do(addit_dammit, save_table, r->headers_out,
+ "Set-Cookie", NULL);
+
+ /* shove the headers direct into r->headers_out */
+ ap_proxy_read_headers(r, rp, buffer, sizeof(buffer), origin,
+ &pread_len);
+
+ if (r->headers_out == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
+ r->server, "proxy: bad HTTP/%d.%d header "
+ "returned by %s (%s)", major, minor, r->uri,
+ r->method);
+ backend->close += 1;
+ /*
+ * ap_send_error relies on a headers_out to be present. we
+ * are in a bad position here.. so force everything we send out
+ * to have nothing to do with the incoming packet
+ */
+ r->headers_out = apr_table_make(r->pool,1);
+ r->status = HTTP_BAD_GATEWAY;
+ r->status_line = "bad gateway";
+ return r->status;
+ }
+
+ /* Now, add in the just read cookies */
+ apr_table_do(addit_dammit, save_table, r->headers_out,
+ "Set-Cookie", NULL);
+
+ /* and now load 'em all in */
+ if (!apr_is_empty_table(save_table)) {
+ apr_table_unset(r->headers_out, "Set-Cookie");
+ r->headers_out = apr_table_overlay(r->pool,
+ r->headers_out,
+ save_table);
+ }
+
+ /* can't have both Content-Length and Transfer-Encoding */
+ if (apr_table_get(r->headers_out, "Transfer-Encoding")
+ && apr_table_get(r->headers_out, "Content-Length")) {
+ /*
+ * 2616 section 4.4, point 3: "if both Transfer-Encoding
+ * and Content-Length are received, the latter MUST be
+ * ignored";
+ *
+ * To help mitigate HTTP Splitting, unset Content-Length
+ * and shut down the backend server connection
+ * XXX: We aught to treat such a response as uncachable
+ */
+ apr_table_unset(r->headers_out, "Content-Length");
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: server %s returned Transfer-Encoding"
+ " and Content-Length", backend->hostname);
+ backend->close += 1;
+ }
+
+ /* strip connection listed hop-by-hop headers from response */
+ backend->close += ap_proxy_liststr(apr_table_get(r->headers_out,
+ "Connection"),
+ "close");
+ ap_proxy_clear_connection(p, r->headers_out);
+ if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
+ ap_set_content_type(r, apr_pstrdup(p, buf));
+ }
+ ap_proxy_pre_http_request(origin,rp);
+
+ /* handle Via header in response */
+ if (conf->viaopt != via_off && conf->viaopt != via_block) {
+ const char *server_name = ap_get_server_name(r);
+ /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
+ * then the server name returned by ap_get_server_name() is the
+ * origin server name (which does make too much sense with Via: headers)
+ * so we use the proxy vhost's name instead.
+ */
+ if (server_name == r->hostname)
+ server_name = r->server->server_hostname;
+ /* create a "Via:" response header entry and merge it */
+ apr_table_mergen(r->headers_out, "Via",
+ (conf->viaopt == via_full)
+ ? apr_psprintf(p, "%d.%d %s%s (%s)",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name,
+ server_portstr,
+ AP_SERVER_BASEVERSION)
+ : apr_psprintf(p, "%d.%d %s%s",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name,
+ server_portstr)
+ );
+ }
+
+ /* cancel keepalive if HTTP/1.0 or less */
+ if ((major < 1) || (minor < 1)) {
+ backend->close += 1;
+ origin->keepalive = AP_CONN_CLOSE;
+ }
+ } else {
+ /* an http/0.9 response */
+ backasswards = 1;
+ r->status = 200;
+ r->status_line = "200 OK";
+ backend->close += 1;
+ }
+
+ interim_response = ap_is_HTTP_INFO(r->status);
+ if (interim_response) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "proxy: HTTP: received interim %d response",
+ r->status);
+ }
+ /* Moved the fixups of Date headers and those affected by
+ * ProxyPassReverse/etc from here to ap_proxy_read_headers
+ */
+
+ if ((r->status == 401) && (conf->error_override != 0)) {
+ const char *buf;
+ const char *wa = "WWW-Authenticate";
+ if ((buf = apr_table_get(r->headers_out, wa))) {
+ apr_table_set(r->err_headers_out, wa, buf);
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: origin server sent 401 without WWW-Authenticate header");
+ }
+ }
+
+ r->sent_bodyct = 1;
+ /*
+ * Is it an HTTP/0.9 response or did we maybe preread the 1st line of
+ * the response? If so, load the extra data. These are 2 mutually
+ * exclusive possibilities, that just happen to require very
+ * similar behavior.
+ */
+ if (backasswards || pread_len) {
+ apr_ssize_t cntr = (apr_ssize_t)pread_len;
+ if (backasswards) {
+ /*@@@FIXME:
+ * At this point in response processing of a 0.9 response,
+ * we don't know yet whether data is binary or not.
+ * mod_charset_lite will get control later on, so it cannot
+ * decide on the conversion of this buffer full of data.
+ * However, chances are that we are not really talking to an
+ * HTTP/0.9 server, but to some different protocol, therefore
+ * the best guess IMHO is to always treat the buffer as "text/x":
+ */
+ ap_xlate_proto_to_ascii(buffer, len);
+ cntr = (apr_ssize_t)len;
+ }
+ e = apr_bucket_heap_create(buffer, cntr, NULL, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+
+ /* send body - but only if a body is expected */
+ if ((!r->header_only) && /* not HEAD request */
+ !interim_response && /* not any 1xx response */
+ (r->status != HTTP_NO_CONTENT) && /* not 204 */
+ (r->status != HTTP_NOT_MODIFIED)) { /* not 304 */
+
+ /* We need to copy the output headers and treat them as input
+ * headers as well. BUT, we need to do this before we remove
+ * TE, so that they are preserved accordingly for
+ * ap_http_filter to know where to end.
+ */
+ rp->headers_in = apr_table_copy(r->pool, r->headers_out);
+
+ apr_table_unset(r->headers_out,"Transfer-Encoding");
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: start body send");
+
+ /*
+ * if we are overriding the errors, we can't put the content
+ * of the page into the brigade
+ */
+ if (conf->error_override == 0 || ap_is_HTTP_SUCCESS(r->status)) {
+ /* read the body, pass it to the output filters */
+ apr_read_type_e mode = APR_NONBLOCK_READ;
+ int finish = FALSE;
+
+ do {
+ apr_off_t readbytes;
+ apr_status_t rv;
+
+ rv = ap_get_brigade(rp->input_filters, bb,
+ AP_MODE_READBYTES, mode,
+ conf->io_buffer_size);
+
+ /* ap_get_brigade will return success with an empty brigade
+ * for a non-blocking read which would block: */
+ if (APR_STATUS_IS_EAGAIN(rv)
+ || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb))) {
+ /* flush to the client and switch to blocking mode */
+ e = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ if (ap_pass_brigade(r->output_filters, bb)
+ || c->aborted) {
+ backend->close = 1;
+ break;
+ }
+ apr_brigade_cleanup(bb);
+ mode = APR_BLOCK_READ;
+ continue;
+ }
+ else if (rv == APR_EOF) {
+ break;
+ }
+ else if (rv != APR_SUCCESS) {
+ /* In this case, we are in real trouble because
+ * our backend bailed on us. Pass along a 502 error
+ * error bucket
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+ "proxy: error reading response");
+ ap_proxy_backend_broke(r, bb);
+ ap_pass_brigade(r->output_filters, bb);
+ backend_broke = 1;
+ backend->close = 1;
+ break;
+ }
+ /* next time try a non-blocking read */
+ mode = APR_NONBLOCK_READ;
+
+ apr_brigade_length(bb, 0, &readbytes);
+ backend->worker->s->read += readbytes;
+#if DEBUGGING
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ r->server, "proxy (PID %d): readbytes: %#x",
+ getpid(), readbytes);
+ }
+#endif
+ /* sanity check */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ apr_brigade_cleanup(bb);
+ break;
+ }
+
+ /* found the last brigade? */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
+ /* signal that we must leave */
+ finish = TRUE;
+ }
+
+ /* try send what we read */
+ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
+ || c->aborted) {
+ /* Ack! Phbtt! Die! User aborted! */
+ backend->close = 1; /* this causes socket close below */
+ finish = TRUE;
+ }
+
+ /* make sure we always clean up after ourselves */
+ apr_brigade_cleanup(bb);
+
+ } while (!finish);
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: end body send");
+ }
+ else if (!interim_response) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: header only");
+
+ /* Pass EOS bucket down the filter chain. */
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
+ || c->aborted) {
+ /* Ack! Phbtt! Die! User aborted! */
+ backend->close = 1; /* this causes socket close below */
+ }
+
+ apr_brigade_cleanup(bb);
+ }
+ } while (interim_response);
+
+ /* If our connection with the client is to be aborted, return DONE. */
+ if (c->aborted || backend_broke) {
+ return DONE;
+ }
+
+ if (conf->error_override) {
+ /* the code above this checks for 'OK' which is what the hook expects */
+ if (ap_is_HTTP_SUCCESS(r->status))
+ return OK;
+ else {
+ /* clear r->status for override error, otherwise ErrorDocument
+ * thinks that this is a recursive error, and doesn't find the
+ * custom error page
+ */
+ int status = r->status;
+ r->status = HTTP_OK;
+ /* Discard body, if one is expected */
+ if ((status != HTTP_NO_CONTENT) && /* not 204 */
+ (status != HTTP_NOT_MODIFIED)) { /* not 304 */
+ ap_discard_request_body(rp);
+ }
+ return status;
+ }
+ } else
+ return OK;
+}
+
+static
+apr_status_t ap_proxy_http_cleanup(const char *scheme, request_rec *r,
+ proxy_conn_rec *backend)
+{
+ ap_proxy_release_connection(scheme, backend, r->server);
+ return OK;
+}
+
+/*
+ * This handles http:// URLs, and other URLs using a remote proxy over http
+ * If proxyhost is NULL, then contact the server directly, otherwise
+ * go via the proxy.
+ * Note that if a proxy is used, then URLs other than http: can be accessed,
+ * also, if we have trouble which is clearly specific to the proxy, then
+ * we return DECLINED so that we can try another proxy. (Or the direct
+ * route.)
+ */
+static int proxy_http_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ int status;
+ char server_portstr[32];
+ char *scheme;
+ const char *proxy_function;
+ const char *u;
+ proxy_conn_rec *backend = NULL;
+ int is_ssl = 0;
+
+ /* Note: Memory pool allocation.
+ * A downstream keepalive connection is always connected to the existence
+ * (or not) of an upstream keepalive connection. If this is not done then
+ * load balancing against multiple backend servers breaks (one backend
+ * server ends up taking 100% of the load), and the risk is run of
+ * downstream keepalive connections being kept open unnecessarily. This
+ * keeps webservers busy and ties up resources.
+ *
+ * As a result, we allocate all sockets out of the upstream connection
+ * pool, and when we want to reuse a socket, we check first whether the
+ * connection ID of the current upstream connection is the same as that
+ * of the connection when the socket was opened.
+ */
+ apr_pool_t *p = r->connection->pool;
+ conn_rec *c = r->connection;
+ apr_uri_t *uri = apr_palloc(r->connection->pool, sizeof(*uri));
+
+ /* find the scheme */
+ u = strchr(url, ':');
+ if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0')
+ return DECLINED;
+ if ((u - url) > 14)
+ return HTTP_BAD_REQUEST;
+ scheme = apr_pstrndup(c->pool, url, u - url);
+ /* scheme is lowercase */
+ ap_str_tolower(scheme);
+ /* is it for us? */
+ if (strcmp(scheme, "https") == 0) {
+ if (!ap_proxy_ssl_enable(NULL)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: HTTPS: declining URL %s"
+ " (mod_ssl not configured?)", url);
+ return DECLINED;
+ }
+ is_ssl = 1;
+ proxy_function = "HTTPS";
+ }
+ else if (!(strcmp(scheme, "http") == 0 || (strcmp(scheme, "ftp") == 0 && proxyname))) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: HTTP: declining URL %s", url);
+ return DECLINED; /* only interested in HTTP, or FTP via proxy */
+ }
+ else {
+ if (*scheme == 'h')
+ proxy_function = "HTTP";
+ else
+ proxy_function = "FTP";
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: HTTP: serving URL %s", url);
+
+
+ /* create space for state information */
+ if ((status = ap_proxy_acquire_connection(proxy_function, &backend,
+ worker, r->server)) != OK)
+ goto cleanup;
+
+
+ backend->is_ssl = is_ssl;
+ /*
+ * TODO: Currently we cannot handle persistent SSL backend connections,
+ * because we recreate backend->connection for each request and thus
+ * try to initialize an already existing SSL connection. This does
+ * not work.
+ */
+ if (is_ssl)
+ backend->close_on_recycle = 1;
+
+ /* Step One: Determine Who To Connect To */
+ if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &url, proxyname,
+ proxyport, server_portstr,
+ sizeof(server_portstr))) != OK)
+ goto cleanup;
+
+ /* Step Two: Make the Connection */
+ if (ap_proxy_connect_backend(proxy_function, backend, worker, r->server)) {
+ if (r->proxyreq == PROXYREQ_PROXY)
+ status = HTTP_NOT_FOUND;
+ else
+ status = HTTP_SERVICE_UNAVAILABLE;
+ goto cleanup;
+ }
+
+ /* Step Three: Create conn_rec */
+ if (!backend->connection) {
+ if ((status = ap_proxy_connection_create(proxy_function, backend,
+ c, r->server)) != OK)
+ goto cleanup;
+ }
+
+ /* Step Four: Send the Request */
+ if ((status = ap_proxy_http_request(p, r, backend, backend->connection,
+ conf, uri, url, server_portstr)) != OK)
+ goto cleanup;
+
+ /* Step Five: Receive the Response */
+ if ((status = ap_proxy_http_process_response(p, r, backend,
+ backend->connection,
+ conf, server_portstr)) != OK)
+ goto cleanup;
+
+ /* Step Six: Clean Up */
+
+cleanup:
+ if (backend) {
+ if (status != OK)
+ backend->close = 1;
+ ap_proxy_http_cleanup(proxy_function, r, backend);
+ }
+ return status;
+}
+
+static void ap_proxy_http_register_hook(apr_pool_t *p)
+{
+ proxy_hook_scheme_handler(proxy_http_handler, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(proxy_http_canon, NULL, NULL, APR_HOOK_FIRST);
+}
+
+module AP_MODULE_DECLARE_DATA proxy_http_module = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_proxy_http_register_hook/* register hooks */
+};
+
diff --git a/modules/proxy/mod_proxy_http.dsp b/modules/proxy/mod_proxy_http.dsp
new file mode 100644
index 00000000..3d544718
--- /dev/null
+++ b/modules/proxy/mod_proxy_http.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_http" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_http - Win32 Release
+!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 "mod_proxy_http.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 "mod_proxy_http.mak" CFG="mod_proxy_http - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_http - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_http - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_http - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_http_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_http.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_proxy_http.so" /d "LONG_NAME=proxy_http_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:"Release/mod_proxy_http.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_proxy_http.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_proxy_http - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# 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 "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_http_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_http.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_proxy_http.so" /d "LONG_NAME=proxy_http_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_http.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_proxy_http.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_http - Win32 Release"
+# Name "mod_proxy_http - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_http.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c
new file mode 100644
index 00000000..908c704a
--- /dev/null
+++ b/modules/proxy/proxy_util.c
@@ -0,0 +1,2188 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Utility routines for Apache proxy */
+#include "mod_proxy.h"
+#include "ap_mpm.h"
+#include "scoreboard.h"
+#include "apr_version.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h> /* for getpid() */
+#endif
+
+#if (APR_MAJOR_VERSION < 1)
+#undef apr_socket_create
+#define apr_socket_create apr_socket_create_ex
+#endif
+
+/* Global balancer counter */
+int PROXY_DECLARE_DATA proxy_lb_workers = 0;
+static int lb_workers_limit = 0;
+
+static int proxy_match_ipaddr(struct dirconn_entry *This, request_rec *r);
+static int proxy_match_domainname(struct dirconn_entry *This, request_rec *r);
+static int proxy_match_hostname(struct dirconn_entry *This, request_rec *r);
+static int proxy_match_word(struct dirconn_entry *This, request_rec *r);
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, create_req,
+ (request_rec *r, request_rec *pr), (r, pr),
+ OK, DECLINED)
+
+/* already called in the knowledge that the characters are hex digits */
+PROXY_DECLARE(int) ap_proxy_hex2c(const char *x)
+{
+ int i, ch;
+
+#if !APR_CHARSET_EBCDIC
+ ch = x[0];
+ if (apr_isdigit(ch))
+ i = ch - '0';
+ else if (apr_isupper(ch))
+ i = ch - ('A' - 10);
+ else
+ i = ch - ('a' - 10);
+ i <<= 4;
+
+ ch = x[1];
+ if (apr_isdigit(ch))
+ i += ch - '0';
+ else if (apr_isupper(ch))
+ i += ch - ('A' - 10);
+ else
+ i += ch - ('a' - 10);
+ return i;
+#else /*APR_CHARSET_EBCDIC*/
+ /*
+ * we assume that the hex value refers to an ASCII character
+ * so convert to EBCDIC so that it makes sense locally;
+ *
+ * example:
+ *
+ * client specifies %20 in URL to refer to a space char;
+ * at this point we're called with EBCDIC "20"; after turning
+ * EBCDIC "20" into binary 0x20, we then need to assume that 0x20
+ * represents an ASCII char and convert 0x20 to EBCDIC, yielding
+ * 0x40
+ */
+ char buf[1];
+
+ if (1 == sscanf(x, "%2x", &i)) {
+ buf[0] = i & 0xFF;
+ ap_xlate_proto_from_ascii(buf, 1);
+ return buf[0];
+ }
+ else {
+ return 0;
+ }
+#endif /*APR_CHARSET_EBCDIC*/
+}
+
+PROXY_DECLARE(void) ap_proxy_c2hex(int ch, char *x)
+{
+#if !APR_CHARSET_EBCDIC
+ int i;
+
+ x[0] = '%';
+ i = (ch & 0xF0) >> 4;
+ if (i >= 10)
+ x[1] = ('A' - 10) + i;
+ else
+ x[1] = '0' + i;
+
+ i = ch & 0x0F;
+ if (i >= 10)
+ x[2] = ('A' - 10) + i;
+ else
+ x[2] = '0' + i;
+#else /*APR_CHARSET_EBCDIC*/
+ static const char ntoa[] = { "0123456789ABCDEF" };
+ char buf[1];
+
+ ch &= 0xFF;
+
+ buf[0] = ch;
+ ap_xlate_proto_to_ascii(buf, 1);
+
+ x[0] = '%';
+ x[1] = ntoa[(buf[0] >> 4) & 0x0F];
+ x[2] = ntoa[buf[0] & 0x0F];
+ x[3] = '\0';
+#endif /*APR_CHARSET_EBCDIC*/
+}
+
+/*
+ * canonicalise a URL-encoded string
+ */
+
+/*
+ * Convert a URL-encoded string to canonical form.
+ * It decodes characters which need not be encoded,
+ * and encodes those which must be encoded, and does not touch
+ * those which must not be touched.
+ */
+PROXY_DECLARE(char *)ap_proxy_canonenc(apr_pool_t *p, const char *x, int len, enum enctype t,
+ int forcedec, int proxyreq)
+{
+ int i, j, ch;
+ char *y;
+ char *allowed; /* characters which should not be encoded */
+ char *reserved; /* characters which much not be en/de-coded */
+
+/*
+ * N.B. in addition to :@&=, this allows ';' in an http path
+ * and '?' in an ftp path -- this may be revised
+ *
+ * Also, it makes a '+' character in a search string reserved, as
+ * it may be form-encoded. (Although RFC 1738 doesn't allow this -
+ * it only permits ; / ? : @ = & as reserved chars.)
+ */
+ if (t == enc_path)
+ allowed = "$-_.+!*'(),;:@&=";
+ else if (t == enc_search)
+ allowed = "$-_.!*'(),;:@&=";
+ else if (t == enc_user)
+ allowed = "$-_.+!*'(),;@&=";
+ else if (t == enc_fpath)
+ allowed = "$-_.+!*'(),?:@&=";
+ else /* if (t == enc_parm) */
+ allowed = "$-_.+!*'(),?/:@&=";
+
+ if (t == enc_path)
+ reserved = "/";
+ else if (t == enc_search)
+ reserved = "+";
+ else
+ reserved = "";
+
+ y = apr_palloc(p, 3 * len + 1);
+
+ for (i = 0, j = 0; i < len; i++, j++) {
+/* always handle '/' first */
+ ch = x[i];
+ if (strchr(reserved, ch)) {
+ y[j] = ch;
+ continue;
+ }
+/*
+ * decode it if not already done. do not decode reverse proxied URLs
+ * unless specifically forced
+ */
+ if ((forcedec || (proxyreq && proxyreq != PROXYREQ_REVERSE)) && ch == '%') {
+ if (!apr_isxdigit(x[i + 1]) || !apr_isxdigit(x[i + 2]))
+ return NULL;
+ ch = ap_proxy_hex2c(&x[i + 1]);
+ i += 2;
+ if (ch != 0 && strchr(reserved, ch)) { /* keep it encoded */
+ ap_proxy_c2hex(ch, &y[j]);
+ j += 2;
+ continue;
+ }
+ }
+/* recode it, if necessary */
+ if (!apr_isalnum(ch) && !strchr(allowed, ch)) {
+ ap_proxy_c2hex(ch, &y[j]);
+ j += 2;
+ }
+ else
+ y[j] = ch;
+ }
+ y[j] = '\0';
+ return y;
+}
+
+/*
+ * Parses network-location.
+ * urlp on input the URL; on output the path, after the leading /
+ * user NULL if no user/password permitted
+ * password holder for password
+ * host holder for host
+ * port port number; only set if one is supplied.
+ *
+ * Returns an error string.
+ */
+PROXY_DECLARE(char *)
+ ap_proxy_canon_netloc(apr_pool_t *p, char **const urlp, char **userp,
+ char **passwordp, char **hostp, apr_port_t *port)
+{
+ char *addr, *scope_id, *strp, *host, *url = *urlp;
+ char *user = NULL, *password = NULL;
+ apr_port_t tmp_port;
+ apr_status_t rv;
+
+ if (url[0] != '/' || url[1] != '/')
+ return "Malformed URL";
+ host = url + 2;
+ url = strchr(host, '/');
+ if (url == NULL)
+ url = "";
+ else
+ *(url++) = '\0'; /* skip seperating '/' */
+
+ /* find _last_ '@' since it might occur in user/password part */
+ strp = strrchr(host, '@');
+
+ if (strp != NULL) {
+ *strp = '\0';
+ user = host;
+ host = strp + 1;
+
+/* find password */
+ strp = strchr(user, ':');
+ if (strp != NULL) {
+ *strp = '\0';
+ password = ap_proxy_canonenc(p, strp + 1, strlen(strp + 1), enc_user, 1, 0);
+ if (password == NULL)
+ return "Bad %-escape in URL (password)";
+ }
+
+ user = ap_proxy_canonenc(p, user, strlen(user), enc_user, 1, 0);
+ if (user == NULL)
+ return "Bad %-escape in URL (username)";
+ }
+ if (userp != NULL) {
+ *userp = user;
+ }
+ if (passwordp != NULL) {
+ *passwordp = password;
+ }
+
+ /*
+ * Parse the host string to separate host portion from optional port.
+ * Perform range checking on port.
+ */
+ rv = apr_parse_addr_port(&addr, &scope_id, &tmp_port, host, p);
+ if (rv != APR_SUCCESS || addr == NULL || scope_id != NULL) {
+ return "Invalid host/port";
+ }
+ if (tmp_port != 0) { /* only update caller's port if port was specified */
+ *port = tmp_port;
+ }
+
+ ap_str_tolower(addr); /* DNS names are case-insensitive */
+
+ *urlp = url;
+ *hostp = addr;
+
+ return NULL;
+}
+
+static const char * const lwday[7] =
+{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
+
+/*
+ * If the date is a valid RFC 850 date or asctime() date, then it
+ * is converted to the RFC 1123 format, otherwise it is not modified.
+ * This routine is not very fast at doing conversions, as it uses
+ * sscanf and sprintf. However, if the date is already correctly
+ * formatted, then it exits very quickly.
+ */
+PROXY_DECLARE(const char *)
+ ap_proxy_date_canon(apr_pool_t *p, const char *x1)
+{
+ char *x = apr_pstrdup(p, x1);
+ int wk, mday, year, hour, min, sec, mon;
+ char *q, month[4], zone[4], week[4];
+
+ q = strchr(x, ',');
+ /* check for RFC 850 date */
+ if (q != NULL && q - x > 3 && q[1] == ' ') {
+ *q = '\0';
+ for (wk = 0; wk < 7; wk++)
+ if (strcmp(x, lwday[wk]) == 0)
+ break;
+ *q = ',';
+ if (wk == 7)
+ return x; /* not a valid date */
+ if (q[4] != '-' || q[8] != '-' || q[11] != ' ' || q[14] != ':' ||
+ q[17] != ':' || strcmp(&q[20], " GMT") != 0)
+ return x;
+ if (sscanf(q + 2, "%u-%3s-%u %u:%u:%u %3s", &mday, month, &year,
+ &hour, &min, &sec, zone) != 7)
+ return x;
+ if (year < 70)
+ year += 2000;
+ else
+ year += 1900;
+ }
+ else {
+/* check for acstime() date */
+ if (x[3] != ' ' || x[7] != ' ' || x[10] != ' ' || x[13] != ':' ||
+ x[16] != ':' || x[19] != ' ' || x[24] != '\0')
+ return x;
+ if (sscanf(x, "%3s %3s %u %u:%u:%u %u", week, month, &mday, &hour,
+ &min, &sec, &year) != 7)
+ return x;
+ for (wk = 0; wk < 7; wk++)
+ if (strcmp(week, apr_day_snames[wk]) == 0)
+ break;
+ if (wk == 7)
+ return x;
+ }
+
+/* check date */
+ for (mon = 0; mon < 12; mon++)
+ if (strcmp(month, apr_month_snames[mon]) == 0)
+ break;
+ if (mon == 12)
+ return x;
+
+ q = apr_palloc(p, 30);
+ apr_snprintf(q, 30, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", apr_day_snames[wk],
+ mday, apr_month_snames[mon], year, hour, min, sec);
+ return q;
+}
+
+PROXY_DECLARE(request_rec *)ap_proxy_make_fake_req(conn_rec *c, request_rec *r)
+{
+ request_rec *rp = apr_pcalloc(c->pool, sizeof(*r));
+
+ rp->pool = c->pool;
+ rp->status = HTTP_OK;
+
+ rp->headers_in = apr_table_make(c->pool, 50);
+ rp->subprocess_env = apr_table_make(c->pool, 50);
+ rp->headers_out = apr_table_make(c->pool, 12);
+ rp->err_headers_out = apr_table_make(c->pool, 5);
+ rp->notes = apr_table_make(c->pool, 5);
+
+ rp->server = r->server;
+ rp->proxyreq = r->proxyreq;
+ rp->request_time = r->request_time;
+ rp->connection = c;
+ rp->output_filters = c->output_filters;
+ rp->input_filters = c->input_filters;
+ rp->proto_output_filters = c->output_filters;
+ rp->proto_input_filters = c->input_filters;
+
+ rp->request_config = ap_create_request_config(c->pool);
+ proxy_run_create_req(r, rp);
+
+ return rp;
+}
+
+
+/*
+ * list is a comma-separated list of case-insensitive tokens, with
+ * optional whitespace around the tokens.
+ * The return returns 1 if the token val is found in the list, or 0
+ * otherwise.
+ */
+PROXY_DECLARE(int) ap_proxy_liststr(const char *list, const char *val)
+{
+ int len, i;
+ const char *p;
+
+ len = strlen(val);
+
+ while (list != NULL) {
+ p = ap_strchr_c(list, ',');
+ if (p != NULL) {
+ i = p - list;
+ do
+ p++;
+ while (apr_isspace(*p));
+ }
+ else
+ i = strlen(list);
+
+ while (i > 0 && apr_isspace(list[i - 1]))
+ i--;
+ if (i == len && strncasecmp(list, val, len) == 0)
+ return 1;
+ list = p;
+ }
+ return 0;
+}
+
+/*
+ * list is a comma-separated list of case-insensitive tokens, with
+ * optional whitespace around the tokens.
+ * if val appears on the list of tokens, it is removed from the list,
+ * and the new list is returned.
+ */
+PROXY_DECLARE(char *)ap_proxy_removestr(apr_pool_t *pool, const char *list, const char *val)
+{
+ int len, i;
+ const char *p;
+ char *new = NULL;
+
+ len = strlen(val);
+
+ while (list != NULL) {
+ p = ap_strchr_c(list, ',');
+ if (p != NULL) {
+ i = p - list;
+ do
+ p++;
+ while (apr_isspace(*p));
+ }
+ else
+ i = strlen(list);
+
+ while (i > 0 && apr_isspace(list[i - 1]))
+ i--;
+ if (i == len && strncasecmp(list, val, len) == 0) {
+ /* do nothing */
+ }
+ else {
+ if (new)
+ new = apr_pstrcat(pool, new, ",", apr_pstrndup(pool, list, i), NULL);
+ else
+ new = apr_pstrndup(pool, list, i);
+ }
+ list = p;
+ }
+ return new;
+}
+
+/*
+ * Converts 8 hex digits to a time integer
+ */
+PROXY_DECLARE(int) ap_proxy_hex2sec(const char *x)
+{
+ int i, ch;
+ unsigned int j;
+
+ for (i = 0, j = 0; i < 8; i++) {
+ ch = x[i];
+ j <<= 4;
+ if (apr_isdigit(ch))
+ j |= ch - '0';
+ else if (apr_isupper(ch))
+ j |= ch - ('A' - 10);
+ else
+ j |= ch - ('a' - 10);
+ }
+ if (j == 0xffffffff)
+ return -1; /* so that it works with 8-byte ints */
+ else
+ return j;
+}
+
+/*
+ * Converts a time integer to 8 hex digits
+ */
+PROXY_DECLARE(void) ap_proxy_sec2hex(int t, char *y)
+{
+ int i, ch;
+ unsigned int j = t;
+
+ for (i = 7; i >= 0; i--) {
+ ch = j & 0xF;
+ j >>= 4;
+ if (ch >= 10)
+ y[i] = ch + ('A' - 10);
+ else
+ y[i] = ch + '0';
+ }
+ y[8] = '\0';
+}
+
+PROXY_DECLARE(int) ap_proxyerror(request_rec *r, int statuscode, const char *message)
+{
+ apr_table_setn(r->notes, "error-notes",
+ apr_pstrcat(r->pool,
+ "The proxy server could not handle the request "
+ "<em><a href=\"", ap_escape_uri(r->pool, r->uri),
+ "\">", ap_escape_html(r->pool, r->method),
+ "&nbsp;",
+ ap_escape_html(r->pool, r->uri), "</a></em>.<p>\n"
+ "Reason: <strong>",
+ ap_escape_html(r->pool, message),
+ "</strong></p>", NULL));
+
+ /* Allow "error-notes" string to be printed by ap_send_error_response() */
+ apr_table_setn(r->notes, "verbose-error-to", apr_pstrdup(r->pool, "*"));
+
+ r->status_line = apr_psprintf(r->pool, "%3.3u Proxy Error", statuscode);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "proxy: %s returned by %s", message, r->uri);
+ return statuscode;
+}
+
+static const char *
+ proxy_get_host_of_request(request_rec *r)
+{
+ char *url, *user = NULL, *password = NULL, *err, *host;
+ apr_port_t port;
+
+ if (r->hostname != NULL)
+ return r->hostname;
+
+ /* Set url to the first char after "scheme://" */
+ if ((url = strchr(r->uri, ':')) == NULL
+ || url[1] != '/' || url[2] != '/')
+ return NULL;
+
+ url = apr_pstrdup(r->pool, &url[1]); /* make it point to "//", which is what proxy_canon_netloc expects */
+
+ err = ap_proxy_canon_netloc(r->pool, &url, &user, &password, &host, &port);
+
+ if (err != NULL)
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "%s", err);
+
+ r->hostname = host;
+
+ return host; /* ought to return the port, too */
+}
+
+/* Return TRUE if addr represents an IP address (or an IP network address) */
+PROXY_DECLARE(int) ap_proxy_is_ipaddr(struct dirconn_entry *This, apr_pool_t *p)
+{
+ const char *addr = This->name;
+ long ip_addr[4];
+ int i, quads;
+ long bits;
+
+ /*
+ * if the address is given with an explicit netmask, use that
+ * Due to a deficiency in apr_inet_addr(), it is impossible to parse
+ * "partial" addresses (with less than 4 quads) correctly, i.e.
+ * 192.168.123 is parsed as 192.168.0.123, which is not what I want.
+ * I therefore have to parse the IP address manually:
+ * if (proxy_readmask(This->name, &This->addr.s_addr, &This->mask.s_addr) == 0)
+ * addr and mask were set by proxy_readmask()
+ * return 1;
+ */
+
+ /*
+ * Parse IP addr manually, optionally allowing
+ * abbreviated net addresses like 192.168.
+ */
+
+ /* Iterate over up to 4 (dotted) quads. */
+ for (quads = 0; quads < 4 && *addr != '\0'; ++quads) {
+ char *tmp;
+
+ if (*addr == '/' && quads > 0) /* netmask starts here. */
+ break;
+
+ if (!apr_isdigit(*addr))
+ return 0; /* no digit at start of quad */
+
+ ip_addr[quads] = strtol(addr, &tmp, 0);
+
+ if (tmp == addr) /* expected a digit, found something else */
+ return 0;
+
+ if (ip_addr[quads] < 0 || ip_addr[quads] > 255) {
+ /* invalid octet */
+ return 0;
+ }
+
+ addr = tmp;
+
+ if (*addr == '.' && quads != 3)
+ ++addr; /* after the 4th quad, a dot would be illegal */
+ }
+
+ for (This->addr.s_addr = 0, i = 0; i < quads; ++i)
+ This->addr.s_addr |= htonl(ip_addr[i] << (24 - 8 * i));
+
+ if (addr[0] == '/' && apr_isdigit(addr[1])) { /* net mask follows: */
+ char *tmp;
+
+ ++addr;
+
+ bits = strtol(addr, &tmp, 0);
+
+ if (tmp == addr) /* expected a digit, found something else */
+ return 0;
+
+ addr = tmp;
+
+ if (bits < 0 || bits > 32) /* netmask must be between 0 and 32 */
+ return 0;
+
+ }
+ else {
+ /*
+ * Determine (i.e., "guess") netmask by counting the
+ * number of trailing .0's; reduce #quads appropriately
+ * (so that 192.168.0.0 is equivalent to 192.168.)
+ */
+ while (quads > 0 && ip_addr[quads - 1] == 0)
+ --quads;
+
+ /* "IP Address should be given in dotted-quad form, optionally followed by a netmask (e.g., 192.168.111.0/24)"; */
+ if (quads < 1)
+ return 0;
+
+ /* every zero-byte counts as 8 zero-bits */
+ bits = 8 * quads;
+
+ if (bits != 32) /* no warning for fully qualified IP address */
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Warning: NetMask not supplied with IP-Addr; guessing: %s/%ld",
+ inet_ntoa(This->addr), bits);
+ }
+
+ This->mask.s_addr = htonl(APR_INADDR_NONE << (32 - bits));
+
+ if (*addr == '\0' && (This->addr.s_addr & ~This->mask.s_addr) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Warning: NetMask and IP-Addr disagree in %s/%ld",
+ inet_ntoa(This->addr), bits);
+ This->addr.s_addr &= This->mask.s_addr;
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " Set to %s/%ld",
+ inet_ntoa(This->addr), bits);
+ }
+
+ if (*addr == '\0') {
+ This->matcher = proxy_match_ipaddr;
+ return 1;
+ }
+ else
+ return (*addr == '\0'); /* okay iff we've parsed the whole string */
+}
+
+/* Return TRUE if addr represents an IP address (or an IP network address) */
+static int proxy_match_ipaddr(struct dirconn_entry *This, request_rec *r)
+{
+ int i, ip_addr[4];
+ struct in_addr addr, *ip;
+ const char *host = proxy_get_host_of_request(r);
+
+ if (host == NULL) /* oops! */
+ return 0;
+
+ memset(&addr, '\0', sizeof addr);
+ memset(ip_addr, '\0', sizeof ip_addr);
+
+ if (4 == sscanf(host, "%d.%d.%d.%d", &ip_addr[0], &ip_addr[1], &ip_addr[2], &ip_addr[3])) {
+ for (addr.s_addr = 0, i = 0; i < 4; ++i)
+ addr.s_addr |= htonl(ip_addr[i] << (24 - 8 * i));
+
+ if (This->addr.s_addr == (addr.s_addr & This->mask.s_addr)) {
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "1)IP-Match: %s[%s] <-> ", host, inet_ntoa(addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s/", inet_ntoa(This->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s", inet_ntoa(This->mask));
+#endif
+ return 1;
+ }
+#if DEBUGGING
+ else {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "1)IP-NoMatch: %s[%s] <-> ", host, inet_ntoa(addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s/", inet_ntoa(This->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s", inet_ntoa(This->mask));
+ }
+#endif
+ }
+ else {
+ struct apr_sockaddr_t *reqaddr;
+
+ if (apr_sockaddr_info_get(&reqaddr, host, APR_UNSPEC, 0, 0, r->pool)
+ != APR_SUCCESS) {
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "2)IP-NoMatch: hostname=%s msg=Host not found",
+ host);
+#endif
+ return 0;
+ }
+
+ /* Try to deal with multiple IP addr's for a host */
+ /* FIXME: This needs to be able to deal with IPv6 */
+ while (reqaddr) {
+ ip = (struct in_addr *) reqaddr->ipaddr_ptr;
+ if (This->addr.s_addr == (ip->s_addr & This->mask.s_addr)) {
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "3)IP-Match: %s[%s] <-> ", host,
+ inet_ntoa(*ip));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s/", inet_ntoa(This->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s", inet_ntoa(This->mask));
+#endif
+ return 1;
+ }
+#if DEBUGGING
+ else {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "3)IP-NoMatch: %s[%s] <-> ", host,
+ inet_ntoa(*ip));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s/", inet_ntoa(This->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s", inet_ntoa(This->mask));
+ }
+#endif
+ reqaddr = reqaddr->next;
+ }
+ }
+
+ return 0;
+}
+
+/* Return TRUE if addr represents a domain name */
+PROXY_DECLARE(int) ap_proxy_is_domainname(struct dirconn_entry *This, apr_pool_t *p)
+{
+ char *addr = This->name;
+ int i;
+
+ /* Domain name must start with a '.' */
+ if (addr[0] != '.')
+ return 0;
+
+ /* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */
+ for (i = 0; apr_isalnum(addr[i]) || addr[i] == '-' || addr[i] == '.'; ++i)
+ continue;
+
+#if 0
+ if (addr[i] == ':') {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "@@@@ handle optional port in proxy_is_domainname()");
+ /* @@@@ handle optional port */
+ }
+#endif
+
+ if (addr[i] != '\0')
+ return 0;
+
+ /* Strip trailing dots */
+ for (i = strlen(addr) - 1; i > 0 && addr[i] == '.'; --i)
+ addr[i] = '\0';
+
+ This->matcher = proxy_match_domainname;
+ return 1;
+}
+
+/* Return TRUE if host "host" is in domain "domain" */
+static int proxy_match_domainname(struct dirconn_entry *This, request_rec *r)
+{
+ const char *host = proxy_get_host_of_request(r);
+ int d_len = strlen(This->name), h_len;
+
+ if (host == NULL) /* some error was logged already */
+ return 0;
+
+ h_len = strlen(host);
+
+ /* @@@ do this within the setup? */
+ /* Ignore trailing dots in domain comparison: */
+ while (d_len > 0 && This->name[d_len - 1] == '.')
+ --d_len;
+ while (h_len > 0 && host[h_len - 1] == '.')
+ --h_len;
+ return h_len > d_len
+ && strncasecmp(&host[h_len - d_len], This->name, d_len) == 0;
+}
+
+/* Return TRUE if host represents a host name */
+PROXY_DECLARE(int) ap_proxy_is_hostname(struct dirconn_entry *This, apr_pool_t *p)
+{
+ struct apr_sockaddr_t *addr;
+ char *host = This->name;
+ int i;
+
+ /* Host names must not start with a '.' */
+ if (host[0] == '.')
+ return 0;
+
+ /* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */
+ for (i = 0; apr_isalnum(host[i]) || host[i] == '-' || host[i] == '.'; ++i);
+
+ if (host[i] != '\0' || apr_sockaddr_info_get(&addr, host, APR_UNSPEC, 0, 0, p) != APR_SUCCESS)
+ return 0;
+
+ This->hostaddr = addr;
+
+ /* Strip trailing dots */
+ for (i = strlen(host) - 1; i > 0 && host[i] == '.'; --i)
+ host[i] = '\0';
+
+ This->matcher = proxy_match_hostname;
+ return 1;
+}
+
+/* Return TRUE if host "host" is equal to host2 "host2" */
+static int proxy_match_hostname(struct dirconn_entry *This, request_rec *r)
+{
+ char *host = This->name;
+ const char *host2 = proxy_get_host_of_request(r);
+ int h2_len;
+ int h1_len;
+
+ if (host == NULL || host2 == NULL)
+ return 0; /* oops! */
+
+ h2_len = strlen(host2);
+ h1_len = strlen(host);
+
+#if 0
+ struct apr_sockaddr_t *addr = *This->hostaddr;
+
+ /* Try to deal with multiple IP addr's for a host */
+ while (addr) {
+ if (addr->ipaddr_ptr == ? ? ? ? ? ? ? ? ? ? ? ? ?)
+ return 1;
+ addr = addr->next;
+ }
+#endif
+
+ /* Ignore trailing dots in host2 comparison: */
+ while (h2_len > 0 && host2[h2_len - 1] == '.')
+ --h2_len;
+ while (h1_len > 0 && host[h1_len - 1] == '.')
+ --h1_len;
+ return h1_len == h2_len
+ && strncasecmp(host, host2, h1_len) == 0;
+}
+
+/* Return TRUE if addr is to be matched as a word */
+PROXY_DECLARE(int) ap_proxy_is_word(struct dirconn_entry *This, apr_pool_t *p)
+{
+ This->matcher = proxy_match_word;
+ return 1;
+}
+
+/* Return TRUE if string "str2" occurs literally in "str1" */
+static int proxy_match_word(struct dirconn_entry *This, request_rec *r)
+{
+ const char *host = proxy_get_host_of_request(r);
+ return host != NULL && ap_strstr_c(host, This->name) != NULL;
+}
+
+/* checks whether a host in uri_addr matches proxyblock */
+PROXY_DECLARE(int) ap_proxy_checkproxyblock(request_rec *r, proxy_server_conf *conf,
+ apr_sockaddr_t *uri_addr)
+{
+ int j;
+ apr_sockaddr_t * src_uri_addr = uri_addr;
+ /* XXX FIXME: conf->noproxies->elts is part of an opaque structure */
+ for (j = 0; j < conf->noproxies->nelts; j++) {
+ struct noproxy_entry *npent = (struct noproxy_entry *) conf->noproxies->elts;
+ struct apr_sockaddr_t *conf_addr = npent[j].addr;
+ uri_addr = src_uri_addr;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: checking remote machine [%s] against [%s]", uri_addr->hostname, npent[j].name);
+ if ((npent[j].name && ap_strstr_c(uri_addr->hostname, npent[j].name))
+ || npent[j].name[0] == '*') {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
+ "proxy: connect to remote machine %s blocked: name %s matched", uri_addr->hostname, npent[j].name);
+ return HTTP_FORBIDDEN;
+ }
+ while (conf_addr) {
+ while (uri_addr) {
+ char *conf_ip;
+ char *uri_ip;
+ apr_sockaddr_ip_get(&conf_ip, conf_addr);
+ apr_sockaddr_ip_get(&uri_ip, uri_addr);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: ProxyBlock comparing %s and %s", conf_ip, uri_ip);
+ if (!apr_strnatcasecmp(conf_ip, uri_ip)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
+ "proxy: connect to remote machine %s blocked: IP %s matched", uri_addr->hostname, conf_ip);
+ return HTTP_FORBIDDEN;
+ }
+ uri_addr = uri_addr->next;
+ }
+ conf_addr = conf_addr->next;
+ }
+ }
+ return OK;
+}
+
+/* set up the minimal filter set */
+PROXY_DECLARE(int) ap_proxy_pre_http_request(conn_rec *c, request_rec *r)
+{
+ ap_add_input_filter("HTTP_IN", NULL, r, c);
+ return OK;
+}
+
+/*
+ * converts a series of buckets into a string
+ * XXX: BillS says this function performs essentially the same function as
+ * ap_rgetline() in protocol.c. Deprecate this function and use ap_rgetline()
+ * instead? I think ap_proxy_string_read() will not work properly on non ASCII
+ * (EBCDIC) machines either.
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_string_read(conn_rec *c, apr_bucket_brigade *bb,
+ char *buff, apr_size_t bufflen, int *eos)
+{
+ apr_bucket *e;
+ apr_status_t rv;
+ char *pos = buff;
+ char *response;
+ int found = 0;
+ apr_size_t len;
+
+ /* start with an empty string */
+ buff[0] = 0;
+ *eos = 0;
+
+ /* loop through each brigade */
+ while (!found) {
+ /* get brigade from network one line at a time */
+ if (APR_SUCCESS != (rv = ap_get_brigade(c->input_filters, bb,
+ AP_MODE_GETLINE,
+ APR_BLOCK_READ,
+ 0))) {
+ return rv;
+ }
+ /* loop through each bucket */
+ while (!found) {
+ if (*eos || APR_BRIGADE_EMPTY(bb)) {
+ /* The connection aborted or timed out */
+ return APR_ECONNABORTED;
+ }
+ e = APR_BRIGADE_FIRST(bb);
+ if (APR_BUCKET_IS_EOS(e)) {
+ *eos = 1;
+ }
+ else {
+ if (APR_SUCCESS != apr_bucket_read(e, (const char **)&response, &len, APR_BLOCK_READ)) {
+ return rv;
+ }
+ /*
+ * is string LF terminated?
+ * XXX: This check can be made more efficient by simply checking
+ * if the last character in the 'response' buffer is an ASCII_LF.
+ * See ap_rgetline() for an example.
+ */
+ if (memchr(response, APR_ASCII_LF, len)) {
+ found = 1;
+ }
+ /* concat strings until buff is full - then throw the data away */
+ if (len > ((bufflen-1)-(pos-buff))) {
+ len = (bufflen-1)-(pos-buff);
+ }
+ if (len > 0) {
+ pos = apr_cpystrn(pos, response, len);
+ }
+ }
+ APR_BUCKET_REMOVE(e);
+ apr_bucket_destroy(e);
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/* unmerge an element in the table */
+PROXY_DECLARE(void) ap_proxy_table_unmerge(apr_pool_t *p, apr_table_t *t, char *key)
+{
+ apr_off_t offset = 0;
+ apr_off_t count = 0;
+ char *value = NULL;
+
+ /* get the value to unmerge */
+ const char *initial = apr_table_get(t, key);
+ if (!initial) {
+ return;
+ }
+ value = apr_pstrdup(p, initial);
+
+ /* remove the value from the headers */
+ apr_table_unset(t, key);
+
+ /* find each comma */
+ while (value[count]) {
+ if (value[count] == ',') {
+ value[count] = 0;
+ apr_table_add(t, key, value + offset);
+ offset = count + 1;
+ }
+ count++;
+ }
+ apr_table_add(t, key, value + offset);
+}
+
+PROXY_DECLARE(const char *) ap_proxy_location_reverse_map(request_rec *r,
+ proxy_dir_conf *conf, const char *url)
+{
+ struct proxy_alias *ent;
+ int i, l1, l2;
+ char *u;
+
+ /*
+ * XXX FIXME: Make sure this handled the ambiguous case of the :<PORT>
+ * after the hostname
+ */
+
+ l1 = strlen(url);
+ ent = (struct proxy_alias *)conf->raliases->elts;
+ for (i = 0; i < conf->raliases->nelts; i++) {
+ l2 = strlen(ent[i].real);
+ if (l1 >= l2 && strncasecmp(ent[i].real, url, l2) == 0) {
+ u = apr_pstrcat(r->pool, ent[i].fake, &url[l2], NULL);
+ return ap_construct_url(r->pool, u, r);
+ }
+ }
+
+ return url;
+}
+
+/*
+ * Cookies are a bit trickier to match: we've got two substrings to worry
+ * about, and we can't just find them with strstr 'cos of case. Regexp
+ * matching would be an easy fix, but for better consistency with all the
+ * other matches we'll refrain and use apr_strmatch to find path=/domain=
+ * and stick to plain strings for the config values.
+ */
+PROXY_DECLARE(const char *) ap_proxy_cookie_reverse_map(request_rec *r,
+ proxy_dir_conf *conf, const char *str)
+{
+ struct proxy_alias *ent;
+ size_t len = strlen(str);
+ const char *newpath = NULL;
+ const char *newdomain = NULL;
+ const char *pathp;
+ const char *domainp;
+ const char *pathe = NULL;
+ const char *domaine = NULL;
+ size_t l1, l2, poffs = 0, doffs = 0;
+ int i;
+ int ddiff = 0;
+ int pdiff = 0;
+ char *ret;
+
+ /*
+ * Find the match and replacement, but save replacing until we've done
+ * both path and domain so we know the new strlen
+ */
+ if ((pathp = apr_strmatch(conf->cookie_path_str, str, len)) != NULL) {
+ pathp += 5;
+ poffs = pathp - str;
+ pathe = ap_strchr_c(pathp, ';');
+ l1 = pathe ? (pathe - pathp) : strlen(pathp);
+ pathe = pathp + l1 ;
+ ent = (struct proxy_alias *)conf->cookie_paths->elts;
+ for (i = 0; i < conf->cookie_paths->nelts; i++) {
+ l2 = strlen(ent[i].fake);
+ if (l1 >= l2 && strncmp(ent[i].fake, pathp, l2) == 0) {
+ newpath = ent[i].real;
+ pdiff = strlen(newpath) - l1;
+ break;
+ }
+ }
+ }
+
+ if ((domainp = apr_strmatch(conf->cookie_domain_str, str, len)) != NULL) {
+ domainp += 7;
+ doffs = domainp - str;
+ domaine = ap_strchr_c(domainp, ';');
+ l1 = domaine ? (domaine - domainp) : strlen(domainp);
+ domaine = domainp + l1;
+ ent = (struct proxy_alias *)conf->cookie_domains->elts;
+ for (i = 0; i < conf->cookie_domains->nelts; i++) {
+ l2 = strlen(ent[i].fake);
+ if (l1 >= l2 && strncasecmp(ent[i].fake, domainp, l2) == 0) {
+ newdomain = ent[i].real;
+ ddiff = strlen(newdomain) - l1;
+ break;
+ }
+ }
+ }
+
+ if (newpath) {
+ ret = apr_palloc(r->pool, len + pdiff + ddiff + 1);
+ l1 = strlen(newpath);
+ if (newdomain) {
+ l2 = strlen(newdomain);
+ if (doffs > poffs) {
+ memcpy(ret, str, poffs);
+ memcpy(ret + poffs, newpath, l1);
+ memcpy(ret + poffs + l1, pathe, domainp - pathe);
+ memcpy(ret + doffs + pdiff, newdomain, l2);
+ strcpy(ret + doffs + pdiff + l2, domaine);
+ }
+ else {
+ memcpy(ret, str, doffs) ;
+ memcpy(ret + doffs, newdomain, l2);
+ memcpy(ret + doffs + l2, domaine, pathp - domaine);
+ memcpy(ret + poffs + ddiff, newpath, l1);
+ strcpy(ret + poffs + ddiff + l1, pathe);
+ }
+ }
+ else {
+ memcpy(ret, str, poffs);
+ memcpy(ret + poffs, newpath, l1);
+ strcpy(ret + poffs + l1, pathe);
+ }
+ }
+ else {
+ if (newdomain) {
+ ret = apr_palloc(r->pool, len + pdiff + ddiff + 1);
+ l2 = strlen(newdomain);
+ memcpy(ret, str, doffs);
+ memcpy(ret + doffs, newdomain, l2);
+ strcpy(ret + doffs+l2, domaine);
+ }
+ else {
+ ret = (char *)str; /* no change */
+ }
+ }
+
+ return ret;
+}
+
+PROXY_DECLARE(proxy_balancer *) ap_proxy_get_balancer(apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url)
+{
+ proxy_balancer *balancer;
+ char *c, *uri = apr_pstrdup(p, url);
+ int i;
+
+ c = strchr(uri, ':');
+ if (c == NULL || c[1] != '/' || c[2] != '/' || c[3] == '\0')
+ return NULL;
+ /* remove path from uri */
+ if ((c = strchr(c + 3, '/')))
+ *c = '\0';
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++) {
+ if (strcasecmp(balancer->name, uri) == 0)
+ return balancer;
+ balancer++;
+ }
+ return NULL;
+}
+
+PROXY_DECLARE(const char *) ap_proxy_add_balancer(proxy_balancer **balancer,
+ apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url)
+{
+ char *c, *q, *uri = apr_pstrdup(p, url);
+ proxy_balancer_method *lbmethod;
+
+ c = strchr(uri, ':');
+ if (c == NULL || c[1] != '/' || c[2] != '/' || c[3] == '\0')
+ return "Bad syntax for a balancer name";
+ /* remove path from uri */
+ if ((q = strchr(c + 3, '/')))
+ *q = '\0';
+
+ ap_str_tolower(uri);
+ *balancer = apr_array_push(conf->balancers);
+ memset(*balancer, 0, sizeof(proxy_balancer));
+
+ /*
+ * NOTE: The default method is byrequests, which we assume
+ * exists!
+ */
+ lbmethod = ap_lookup_provider(PROXY_LBMETHOD, "byrequests", "0");
+ if (!lbmethod) {
+ return "Can't find 'byrequests' lb method";
+ }
+
+ (*balancer)->name = uri;
+ (*balancer)->lbmethod = lbmethod;
+ (*balancer)->workers = apr_array_make(p, 5, sizeof(proxy_worker));
+ /* XXX Is this a right place to create mutex */
+#if APR_HAS_THREADS
+ if (apr_thread_mutex_create(&((*balancer)->mutex),
+ APR_THREAD_MUTEX_DEFAULT, p) != APR_SUCCESS) {
+ /* XXX: Do we need to log something here */
+ return "can not create thread mutex";
+ }
+#endif
+
+ return NULL;
+}
+
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url)
+{
+ proxy_worker *worker;
+ proxy_worker *max_worker = NULL;
+ int max_match = 0;
+ int url_length;
+ int worker_name_length;
+ const char *c;
+ char *url_copy;
+ int i;
+
+ c = ap_strchr_c(url, ':');
+ if (c == NULL || c[1] != '/' || c[2] != '/' || c[3] == '\0')
+ return NULL;
+
+ url_copy = apr_pstrdup(p, url);
+ url_length = strlen(url);
+
+ /*
+ * We need to find the start of the path and
+ * therefore we know the length of the scheme://hostname/
+ * part to we can force-lowercase everything up to
+ * the start of the path.
+ */
+ c = ap_strchr_c(c+3, '/');
+ if (c) {
+ char *pathstart;
+ pathstart = url_copy + (c - url);
+ *pathstart = '\0';
+ ap_str_tolower(url_copy);
+ *pathstart = '/';
+ } else {
+ ap_str_tolower(url_copy);
+ }
+
+ worker = (proxy_worker *)conf->workers->elts;
+
+ /*
+ * Do a "longest match" on the worker name to find the worker that
+ * fits best to the URL.
+ */
+ for (i = 0; i < conf->workers->nelts; i++) {
+ if ( ((worker_name_length = strlen(worker->name)) <= url_length)
+ && (worker_name_length > max_match)
+ && (strncmp(url_copy, worker->name, worker_name_length) == 0) ) {
+ max_worker = worker;
+ max_match = worker_name_length;
+ }
+ worker++;
+ }
+ return max_worker;
+}
+
+#if APR_HAS_THREADS
+static apr_status_t conn_pool_cleanup(void *theworker)
+{
+ proxy_worker *worker = (proxy_worker *)theworker;
+ if (worker->cp->res) {
+ worker->cp->pool = NULL;
+ apr_reslist_destroy(worker->cp->res);
+ }
+ return APR_SUCCESS;
+}
+#endif
+
+static void init_conn_pool(apr_pool_t *p, proxy_worker *worker)
+{
+ apr_pool_t *pool;
+ proxy_conn_pool *cp;
+
+ /*
+ * Create a connection pool's subpool.
+ * This pool is used for connection recycling.
+ * Once the worker is added it is never removed but
+ * it can be disabled.
+ */
+ apr_pool_create(&pool, p);
+ /*
+ * Alloc from the same pool as worker.
+ * proxy_conn_pool is permanently attached to the worker.
+ */
+ cp = (proxy_conn_pool *)apr_pcalloc(p, sizeof(proxy_conn_pool));
+ cp->pool = pool;
+ worker->cp = cp;
+}
+
+PROXY_DECLARE(const char *) ap_proxy_add_worker(proxy_worker **worker,
+ apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url)
+{
+ int rv;
+ apr_uri_t uri;
+
+ rv = apr_uri_parse(p, url, &uri);
+
+ if (rv != APR_SUCCESS) {
+ return "Unable to parse URL";
+ }
+ if (!uri.hostname || !uri.scheme) {
+ return "URL must be absolute!";
+ }
+
+ ap_str_tolower(uri.hostname);
+ ap_str_tolower(uri.scheme);
+ *worker = apr_array_push(conf->workers);
+ memset(*worker, 0, sizeof(proxy_worker));
+ (*worker)->name = apr_uri_unparse(p, &uri, APR_URI_UNP_REVEALPASSWORD);
+ (*worker)->scheme = uri.scheme;
+ (*worker)->hostname = uri.hostname;
+ (*worker)->port = uri.port;
+ (*worker)->id = proxy_lb_workers;
+ (*worker)->flush_packets = flush_off;
+ (*worker)->flush_wait = PROXY_FLUSH_WAIT;
+ /* Increase the total worker count */
+ proxy_lb_workers++;
+ init_conn_pool(p, *worker);
+#if APR_HAS_THREADS
+ if (apr_thread_mutex_create(&((*worker)->mutex),
+ APR_THREAD_MUTEX_DEFAULT, p) != APR_SUCCESS) {
+ /* XXX: Do we need to log something here */
+ return "can not create thread mutex";
+ }
+#endif
+
+ return NULL;
+}
+
+PROXY_DECLARE(proxy_worker *) ap_proxy_create_worker(apr_pool_t *p)
+{
+
+ proxy_worker *worker;
+ worker = (proxy_worker *)apr_pcalloc(p, sizeof(proxy_worker));
+ worker->id = proxy_lb_workers;
+ /* Increase the total worker count */
+ proxy_lb_workers++;
+ init_conn_pool(p, worker);
+
+ return worker;
+}
+
+PROXY_DECLARE(void)
+ap_proxy_add_worker_to_balancer(apr_pool_t *pool, proxy_balancer *balancer,
+ proxy_worker *worker)
+{
+ proxy_worker *runtime;
+
+ runtime = apr_array_push(balancer->workers);
+ memcpy(runtime, worker, sizeof(proxy_worker));
+ runtime->id = proxy_lb_workers;
+ /* Increase the total runtime count */
+ proxy_lb_workers++;
+
+}
+
+PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf, char **url)
+{
+ int access_status;
+
+ access_status = proxy_run_pre_request(worker, balancer, r, conf, url);
+ if (access_status == DECLINED && *balancer == NULL) {
+ *worker = ap_proxy_get_worker(r->pool, conf, *url);
+ if (*worker) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "proxy: %s: found worker %s for %s",
+ (*worker)->scheme, (*worker)->name, *url);
+
+ *balancer = NULL;
+ access_status = OK;
+ }
+ else if (r->proxyreq == PROXYREQ_PROXY) {
+ if (conf->forward) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "proxy: *: found forward proxy worker for %s",
+ *url);
+ *balancer = NULL;
+ *worker = conf->forward;
+ access_status = OK;
+ }
+ }
+ else if (r->proxyreq == PROXYREQ_REVERSE) {
+ if (conf->reverse) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "proxy: *: found reverse proxy worker for %s",
+ *url);
+ *balancer = NULL;
+ *worker = conf->reverse;
+ access_status = OK;
+ }
+ }
+ }
+ else if (access_status == DECLINED && *balancer != NULL) {
+ /* All the workers are busy */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "proxy: all workers are busy. Unable to serve %s",
+ *url);
+ access_status = HTTP_SERVICE_UNAVAILABLE;
+ }
+ return access_status;
+}
+
+PROXY_DECLARE(int) ap_proxy_post_request(proxy_worker *worker,
+ proxy_balancer *balancer,
+ request_rec *r,
+ proxy_server_conf *conf)
+{
+ int access_status;
+ if (balancer)
+ access_status = proxy_run_post_request(worker, balancer, r, conf);
+ else {
+
+
+ access_status = OK;
+ }
+
+ return access_status;
+}
+
+/* DEPRECATED */
+PROXY_DECLARE(int) ap_proxy_connect_to_backend(apr_socket_t **newsock,
+ const char *proxy_function,
+ apr_sockaddr_t *backend_addr,
+ const char *backend_name,
+ proxy_server_conf *conf,
+ server_rec *s,
+ apr_pool_t *p)
+{
+ apr_status_t rv;
+ int connected = 0;
+ int loglevel;
+
+ while (backend_addr && !connected) {
+ if ((rv = apr_socket_create(newsock, backend_addr->family,
+ SOCK_STREAM, 0, p)) != APR_SUCCESS) {
+ loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_error(APLOG_MARK, loglevel, rv, s,
+ "proxy: %s: error creating fam %d socket for target %s",
+ proxy_function,
+ backend_addr->family,
+ backend_name);
+ /*
+ * this could be an IPv6 address from the DNS but the
+ * local machine won't give us an IPv6 socket; hopefully the
+ * DNS returned an additional address to try
+ */
+ backend_addr = backend_addr->next;
+ continue;
+ }
+
+#if !defined(TPF) && !defined(BEOS)
+ if (conf->recv_buffer_size > 0 &&
+ (rv = apr_socket_opt_set(*newsock, APR_SO_RCVBUF,
+ conf->recv_buffer_size))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "apr_socket_opt_set(SO_RCVBUF): Failed to set "
+ "ProxyReceiveBufferSize, using default");
+ }
+#endif
+
+ /* Set a timeout on the socket */
+ if (conf->timeout_set == 1) {
+ apr_socket_timeout_set(*newsock, conf->timeout);
+ }
+ else {
+ apr_socket_timeout_set(*newsock, s->timeout);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: %s: fam %d socket created to connect to %s",
+ proxy_function, backend_addr->family, backend_name);
+
+ /* make the connection out of the socket */
+ rv = apr_socket_connect(*newsock, backend_addr);
+
+ /* if an error occurred, loop round and try again */
+ if (rv != APR_SUCCESS) {
+ apr_socket_close(*newsock);
+ loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_error(APLOG_MARK, loglevel, rv, s,
+ "proxy: %s: attempt to connect to %pI (%s) failed",
+ proxy_function,
+ backend_addr,
+ backend_name);
+ backend_addr = backend_addr->next;
+ continue;
+ }
+ connected = 1;
+ }
+ return connected ? 0 : 1;
+}
+
+static apr_status_t connection_cleanup(void *theconn)
+{
+ proxy_conn_rec *conn = (proxy_conn_rec *)theconn;
+ proxy_worker *worker = conn->worker;
+
+ /*
+ * If the connection pool is NULL the worker
+ * cleanup has been run. Just return.
+ */
+ if (!worker->cp)
+ return APR_SUCCESS;
+
+#if APR_HAS_THREADS
+ /* Sanity check: Did we already return the pooled connection? */
+ if (conn->inreslist) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, conn->pool,
+ "proxy: Pooled connection 0x%pp for worker %s has been"
+ " already returned to the connection pool.", conn,
+ worker->name);
+ return APR_SUCCESS;
+ }
+#endif
+
+ /* determine if the connection need to be closed */
+ if (conn->close_on_recycle || conn->close) {
+ apr_pool_t *p = conn->pool;
+ apr_pool_clear(conn->pool);
+ memset(conn, 0, sizeof(proxy_conn_rec));
+ conn->pool = p;
+ conn->worker = worker;
+ }
+#if APR_HAS_THREADS
+ if (worker->hmax && worker->cp->res) {
+ conn->inreslist = 1;
+ apr_reslist_release(worker->cp->res, (void *)conn);
+ }
+ else
+#endif
+ {
+ worker->cp->conn = conn;
+ }
+
+ /* Allways return the SUCCESS */
+ return APR_SUCCESS;
+}
+
+/* reslist constructor */
+static apr_status_t connection_constructor(void **resource, void *params,
+ apr_pool_t *pool)
+{
+ apr_pool_t *ctx;
+ proxy_conn_rec *conn;
+ proxy_worker *worker = (proxy_worker *)params;
+
+ /*
+ * Create the subpool for each connection
+ * This keeps the memory consumption constant
+ * when disconnecting from backend.
+ */
+ apr_pool_create(&ctx, pool);
+ conn = apr_pcalloc(pool, sizeof(proxy_conn_rec));
+
+ conn->pool = ctx;
+ conn->worker = worker;
+#if APR_HAS_THREADS
+ conn->inreslist = 1;
+#endif
+ *resource = conn;
+
+ return APR_SUCCESS;
+}
+
+#if APR_HAS_THREADS /* only needed when threads are used */
+/* reslist destructor */
+static apr_status_t connection_destructor(void *resource, void *params,
+ apr_pool_t *pool)
+{
+ proxy_conn_rec *conn = (proxy_conn_rec *)resource;
+
+ /* Destroy the pool only if not called from reslist_destroy */
+ if (conn->worker->cp->pool)
+ apr_pool_destroy(conn->pool);
+
+ return APR_SUCCESS;
+}
+#endif
+
+/*
+ * ap_proxy_initialize_worker_share() concerns itself
+ * with initializing those parts of worker which
+ * are, or could be, shared. Basically worker->s
+ */
+PROXY_DECLARE(void) ap_proxy_initialize_worker_share(proxy_server_conf *conf,
+ proxy_worker *worker,
+ server_rec *s)
+{
+#if PROXY_HAS_SCOREBOARD
+ lb_score *score = NULL;
+#else
+ void *score = NULL;
+#endif
+
+ if (worker->s && (worker->s->status & PROXY_WORKER_INITIALIZED)) {
+ /* The worker share is already initialized */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: worker %s already initialized",
+ worker->name);
+ return;
+ }
+#if PROXY_HAS_SCOREBOARD
+ /* Get scoreboard slot */
+ if (ap_scoreboard_image) {
+ score = ap_get_scoreboard_lb(worker->id);
+ if (!score) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+ "proxy: ap_get_scoreboard_lb(%d) failed in child %" APR_PID_T_FMT " for worker %s",
+ worker->id, getpid(), worker->name);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: grabbed scoreboard slot %d in child %" APR_PID_T_FMT " for worker %s",
+ worker->id, getpid(), worker->name);
+ }
+ }
+#endif
+ if (!score) {
+ score = apr_pcalloc(conf->pool, sizeof(proxy_worker_stat));
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: initialized plain memory in child %" APR_PID_T_FMT " for worker %s",
+ getpid(), worker->name);
+ }
+ worker->s = (proxy_worker_stat *)score;
+ /*
+ * recheck to see if we've already been here. Possible
+ * if proxy is using scoreboard to hold shared stats
+ */
+ if (worker->s->status & PROXY_WORKER_INITIALIZED) {
+ /* The worker share is already initialized */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: worker %s already initialized",
+ worker->name);
+ return;
+ }
+ if (worker->route)
+ strcpy(worker->s->route, worker->route);
+ else
+ *worker->s->route = '\0';
+ if (worker->redirect)
+ strcpy(worker->s->redirect, worker->redirect);
+ else
+ *worker->s->redirect = '\0';
+
+ worker->s->status |= (worker->status | PROXY_WORKER_INITIALIZED);
+
+}
+
+PROXY_DECLARE(apr_status_t) ap_proxy_initialize_worker(proxy_worker *worker, server_rec *s)
+{
+ apr_status_t rv;
+
+#if APR_HAS_THREADS
+ int mpm_threads;
+#endif
+
+ if (worker->status & PROXY_WORKER_INITIALIZED) {
+ /* The worker is already initialized */
+ return APR_SUCCESS;
+ }
+
+ /* Set default parameters */
+ if (!worker->retry)
+ worker->retry = apr_time_from_sec(PROXY_WORKER_DEFAULT_RETRY);
+ /* By default address is reusable */
+ worker->is_address_reusable = 1;
+
+#if APR_HAS_THREADS
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &mpm_threads);
+ if (mpm_threads > 1) {
+ /* Set hard max to no more then mpm_threads */
+ if (worker->hmax == 0 || worker->hmax > mpm_threads)
+ worker->hmax = mpm_threads;
+ if (worker->smax == 0 || worker->smax > worker->hmax)
+ worker->smax = worker->hmax;
+ /* Set min to be lower then smax */
+ if (worker->min > worker->smax)
+ worker->min = worker->smax;
+ }
+ else {
+ /* This will supress the apr_reslist creation */
+ worker->min = worker->smax = worker->hmax = 0;
+ }
+ if (worker->hmax) {
+ rv = apr_reslist_create(&(worker->cp->res),
+ worker->min, worker->smax,
+ worker->hmax, worker->ttl,
+ connection_constructor, connection_destructor,
+ worker, worker->cp->pool);
+
+ apr_pool_cleanup_register(worker->cp->pool, (void *)worker,
+ conn_pool_cleanup,
+ apr_pool_cleanup_null);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: initialized worker %d in child %" APR_PID_T_FMT " for (%s) min=%d max=%d smax=%d",
+ worker->id, getpid(), worker->hostname, worker->min,
+ worker->hmax, worker->smax);
+
+#if (APR_MAJOR_VERSION > 0)
+ /* Set the acquire timeout */
+ if (rv == APR_SUCCESS && worker->acquire_set)
+ apr_reslist_timeout_set(worker->cp->res, worker->acquire);
+#endif
+ }
+ else
+#endif
+ {
+
+ rv = connection_constructor((void **)&(worker->cp->conn), worker, worker->cp->pool);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: initialized single connection worker %d in child %" APR_PID_T_FMT " for (%s)",
+ worker->id, getpid(), worker->hostname);
+ }
+ if (rv == APR_SUCCESS) {
+ worker->status |= (PROXY_WORKER_INITIALIZED);
+ }
+ return rv;
+}
+
+PROXY_DECLARE(int) ap_proxy_retry_worker(const char *proxy_function,
+ proxy_worker *worker,
+ server_rec *s)
+{
+ if (worker->s->status & PROXY_WORKER_IN_ERROR) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: %s: retrying the worker for (%s)",
+ proxy_function, worker->hostname);
+ if (apr_time_now() > worker->s->error_time + worker->retry) {
+ ++worker->s->retries;
+ worker->s->status &= ~PROXY_WORKER_IN_ERROR;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: %s: worker for (%s) has been marked for retry",
+ proxy_function, worker->hostname);
+ return OK;
+ }
+ else
+ return DECLINED;
+ }
+ else
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_acquire_connection(const char *proxy_function,
+ proxy_conn_rec **conn,
+ proxy_worker *worker,
+ server_rec *s)
+{
+ apr_status_t rv;
+
+ if (!PROXY_WORKER_IS_USABLE(worker)) {
+ /* Retry the worker */
+ ap_proxy_retry_worker(proxy_function, worker, s);
+
+ if (!PROXY_WORKER_IS_USABLE(worker)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+ "proxy: %s: disabled connection for (%s)",
+ proxy_function, worker->hostname);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ }
+#if APR_HAS_THREADS
+ if (worker->hmax && worker->cp->res) {
+ rv = apr_reslist_acquire(worker->cp->res, (void **)conn);
+ }
+ else
+#endif
+ {
+ /* create the new connection if the previous was destroyed */
+ if (!worker->cp->conn)
+ connection_constructor((void **)conn, worker, worker->cp->pool);
+ else {
+ *conn = worker->cp->conn;
+ worker->cp->conn = NULL;
+ }
+ rv = APR_SUCCESS;
+ }
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "proxy: %s: failed to acquire connection for (%s)",
+ proxy_function, worker->hostname);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: %s: has acquired connection for (%s)",
+ proxy_function, worker->hostname);
+
+ (*conn)->worker = worker;
+ (*conn)->close = 0;
+ (*conn)->close_on_recycle = 0;
+#if APR_HAS_THREADS
+ (*conn)->inreslist = 0;
+#endif
+
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_release_connection(const char *proxy_function,
+ proxy_conn_rec *conn,
+ server_rec *s)
+{
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: %s: has released connection for (%s)",
+ proxy_function, conn->worker->hostname);
+ /* If there is a connection kill it's cleanup */
+ if (conn->connection) {
+ apr_pool_cleanup_kill(conn->connection->pool, conn, connection_cleanup);
+ conn->connection = NULL;
+ }
+ connection_cleanup(conn);
+
+ return OK;
+}
+
+PROXY_DECLARE(int)
+ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
+ proxy_server_conf *conf,
+ proxy_worker *worker,
+ proxy_conn_rec *conn,
+ apr_uri_t *uri,
+ char **url,
+ const char *proxyname,
+ apr_port_t proxyport,
+ char *server_portstr,
+ int server_portstr_size)
+{
+ int server_port;
+ apr_status_t err = APR_SUCCESS;
+
+ /*
+ * Break up the URL to determine the host to connect to
+ */
+
+ /* we break the URL into host, port, uri */
+ if (APR_SUCCESS != apr_uri_parse(p, *url, uri)) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ apr_pstrcat(p,"URI cannot be parsed: ", *url,
+ NULL));
+ }
+ if (!uri->port) {
+ uri->port = apr_uri_port_of_scheme(uri->scheme);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: connecting %s to %s:%d", *url, uri->hostname,
+ uri->port);
+
+ /*
+ * allocate these out of the specified connection pool
+ * The scheme handler decides if this is permanent or
+ * short living pool.
+ */
+ /* are we connecting directly, or via a proxy? */
+ if (!proxyname) {
+ *url = apr_pstrcat(p, uri->path, uri->query ? "?" : "",
+ uri->query ? uri->query : "",
+ uri->fragment ? "#" : "",
+ uri->fragment ? uri->fragment : "", NULL);
+ }
+ /*
+ * Make sure that we pick the the correct and valid worker.
+ * If a single keepalive connection triggers different workers,
+ * then we have a problem (we don't select the correct one).
+ * Do an expensive check in this case, where we compare the
+ * the hostnames associated between the two.
+ *
+ * TODO: Handle this much better...
+ */
+ if (!conn->hostname || !worker->is_address_reusable ||
+ (r->connection->keepalives &&
+ (r->proxyreq == PROXYREQ_PROXY || r->proxyreq == PROXYREQ_REVERSE) &&
+ (strcasecmp(conn->hostname, uri->hostname) != 0) ) ) {
+ if (proxyname) {
+ conn->hostname = apr_pstrdup(conn->pool, proxyname);
+ conn->port = proxyport;
+ } else {
+ conn->hostname = apr_pstrdup(conn->pool, uri->hostname);
+ conn->port = uri->port;
+ }
+ if (conn->sock) {
+ apr_socket_close(conn->sock);
+ conn->sock = NULL;
+ }
+ if (conn->connection) {
+ apr_pool_cleanup_kill(conn->connection->pool, conn, connection_cleanup);
+ conn->connection = NULL;
+ }
+ err = apr_sockaddr_info_get(&(conn->addr),
+ conn->hostname, APR_UNSPEC,
+ conn->port, 0,
+ conn->pool);
+ }
+ else if (!worker->cp->addr) {
+ if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, err, r->server,
+ "proxy: lock");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /*
+ * Worker can have the single constant backend adress.
+ * The single DNS lookup is used once per worker.
+ * If dynamic change is needed then set the addr to NULL
+ * inside dynamic config to force the lookup.
+ */
+ err = apr_sockaddr_info_get(&(worker->cp->addr),
+ conn->hostname, APR_UNSPEC,
+ conn->port, 0,
+ worker->cp->pool);
+ conn->addr = worker->cp->addr;
+ PROXY_THREAD_UNLOCK(worker);
+ }
+ else
+ conn->addr = worker->cp->addr;
+
+ if (err != APR_SUCCESS) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "DNS lookup failure for: ",
+ conn->hostname, NULL));
+ }
+
+ /* Get the server port for the Via headers */
+ {
+ server_port = ap_get_server_port(r);
+ if (ap_is_default_port(server_port, r)) {
+ strcpy(server_portstr,"");
+ } else {
+ apr_snprintf(server_portstr, server_portstr_size, ":%d",
+ server_port);
+ }
+ }
+ /* check if ProxyBlock directive on this host */
+ if (OK != ap_proxy_checkproxyblock(r, conf, conn->addr)) {
+ return ap_proxyerror(r, HTTP_FORBIDDEN,
+ "Connect to remote machine blocked");
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: connected %s to %s:%d", *url, conn->hostname,
+ conn->port);
+ return OK;
+}
+
+static int is_socket_connected(apr_socket_t *sock)
+
+{
+ apr_size_t buffer_len = 1;
+ char test_buffer[1];
+ apr_status_t socket_status;
+ apr_interval_time_t current_timeout;
+
+ /* save timeout */
+ apr_socket_timeout_get(sock, &current_timeout);
+ /* set no timeout */
+ apr_socket_timeout_set(sock, 0);
+ socket_status = apr_socket_recv(sock, test_buffer, &buffer_len);
+ /* put back old timeout */
+ apr_socket_timeout_set(sock, current_timeout);
+ if (APR_STATUS_IS_EOF(socket_status))
+ return 0;
+ else
+ return 1;
+}
+
+PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
+ proxy_conn_rec *conn,
+ proxy_worker *worker,
+ server_rec *s)
+{
+ apr_status_t rv;
+ int connected = 0;
+ int loglevel;
+ apr_sockaddr_t *backend_addr = conn->addr;
+ apr_socket_t *newsock;
+
+ if (conn->sock) {
+ /*
+ * This increases the connection pool size
+ * but the number of dropped connections is
+ * relatively small compared to connection lifetime
+ */
+ if (!(connected = is_socket_connected(conn->sock))) {
+ apr_socket_close(conn->sock);
+ conn->sock = NULL;
+ }
+ }
+ while (backend_addr && !connected) {
+ if ((rv = apr_socket_create(&newsock, backend_addr->family,
+ SOCK_STREAM, APR_PROTO_TCP,
+ conn->pool)) != APR_SUCCESS) {
+ loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_error(APLOG_MARK, loglevel, rv, s,
+ "proxy: %s: error creating fam %d socket for target %s",
+ proxy_function,
+ backend_addr->family,
+ worker->hostname);
+ /*
+ * this could be an IPv6 address from the DNS but the
+ * local machine won't give us an IPv6 socket; hopefully the
+ * DNS returned an additional address to try
+ */
+ backend_addr = backend_addr->next;
+ continue;
+ }
+
+#if !defined(TPF) && !defined(BEOS)
+ if (worker->recv_buffer_size > 0 &&
+ (rv = apr_socket_opt_set(newsock, APR_SO_RCVBUF,
+ worker->recv_buffer_size))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "apr_socket_opt_set(SO_RCVBUF): Failed to set "
+ "ProxyReceiveBufferSize, using default");
+ }
+#endif
+
+ /* Set a timeout on the socket */
+ if (worker->timeout_set == 1) {
+ apr_socket_timeout_set(newsock, worker->timeout);
+ }
+ else {
+ apr_socket_timeout_set(newsock, s->timeout);
+ }
+ /* Set a keepalive option */
+ if (worker->keepalive) {
+ if ((rv = apr_socket_opt_set(newsock,
+ APR_SO_KEEPALIVE, 1)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "apr_socket_opt_set(SO_KEEPALIVE): Failed to set"
+ " Keepalive");
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: %s: fam %d socket created to connect to %s",
+ proxy_function, backend_addr->family, worker->hostname);
+
+ /* make the connection out of the socket */
+ rv = apr_socket_connect(newsock, backend_addr);
+
+ /* if an error occurred, loop round and try again */
+ if (rv != APR_SUCCESS) {
+ apr_socket_close(newsock);
+ loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_error(APLOG_MARK, loglevel, rv, s,
+ "proxy: %s: attempt to connect to %pI (%s) failed",
+ proxy_function,
+ backend_addr,
+ worker->hostname);
+ backend_addr = backend_addr->next;
+ continue;
+ }
+
+ conn->sock = newsock;
+ connected = 1;
+ }
+ /*
+ * Put the entire worker to error state if
+ * the PROXY_WORKER_IGNORE_ERRORS flag is not set.
+ * Altrough some connections may be alive
+ * no further connections to the worker could be made
+ */
+ if (!connected && PROXY_WORKER_IS_USABLE(worker) &&
+ !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
+ worker->s->status |= PROXY_WORKER_IN_ERROR;
+ worker->s->error_time = apr_time_now();
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+ "ap_proxy_connect_backend disabling worker for (%s)",
+ worker->hostname);
+ }
+ else {
+ worker->s->error_time = 0;
+ worker->s->retries = 0;
+ }
+ return connected ? OK : DECLINED;
+}
+
+PROXY_DECLARE(int) ap_proxy_connection_create(const char *proxy_function,
+ proxy_conn_rec *conn,
+ conn_rec *c,
+ server_rec *s)
+{
+ apr_sockaddr_t *backend_addr = conn->addr;
+ int rc;
+
+ /*
+ * The socket is now open, create a new backend server connection
+ */
+ conn->connection = ap_run_create_connection(c->pool, s, conn->sock,
+ c->id, c->sbh,
+ c->bucket_alloc);
+
+ if (!conn->connection) {
+ /*
+ * the peer reset the connection already; ap_run_create_connection()
+ * closed the socket
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ s, "proxy: %s: an error occurred creating a "
+ "new connection to %pI (%s)", proxy_function,
+ backend_addr, conn->hostname);
+ /* XXX: Will be closed when proxy_conn is closed */
+ apr_socket_close(conn->sock);
+ conn->sock = NULL;
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ /*
+ * register the connection cleanup to client connection
+ * so that the connection can be closed or reused
+ */
+ apr_pool_cleanup_register(c->pool, (void *)conn,
+ connection_cleanup,
+ apr_pool_cleanup_null);
+
+ /* For ssl connection to backend */
+ if (conn->is_ssl) {
+ if (!ap_proxy_ssl_enable(conn->connection)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0,
+ s, "proxy: %s: failed to enable ssl support "
+ "for %pI (%s)", proxy_function,
+ backend_addr, conn->hostname);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ else {
+ /* TODO: See if this will break FTP */
+ ap_proxy_ssl_disable(conn->connection);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: %s: connection complete to %pI (%s)",
+ proxy_function, backend_addr, conn->hostname);
+
+ /* set up the connection filters */
+ rc = ap_run_pre_connection(conn->connection, conn->sock);
+ if (rc != OK && rc != DONE) {
+ conn->connection->aborted = 1;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "proxy: %s: pre_connection setup failed (%d)",
+ proxy_function, rc);
+ return rc;
+ }
+
+ return OK;
+}
+
+int ap_proxy_lb_workers(void)
+{
+ /*
+ * Since we can't resize the scoreboard when reconfiguring, we
+ * have to impose a limit on the number of workers, we are
+ * able to reconfigure to.
+ */
+ if (!lb_workers_limit)
+ lb_workers_limit = proxy_lb_workers + PROXY_DYNAMIC_BALANCER_LIMIT;
+ return lb_workers_limit;
+}
+
+PROXY_DECLARE(void) ap_proxy_backend_broke(request_rec *r,
+ apr_bucket_brigade *brigade)
+{
+ apr_bucket *e;
+ conn_rec *c = r->connection;
+
+ r->no_cache = 1;
+ /*
+ * If this is a subrequest, then prevent also caching of the main
+ * request.
+ */
+ if (r->main)
+ r->main->no_cache = 1;
+ e = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL, c->pool,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(brigade, e);
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(brigade, e);
+}