diff options
Diffstat (limited to 'modules/proxy')
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), + " ", + 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, ¤t_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); +} |